diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index ac08f9c15b..c819921780 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -180,12 +180,38 @@ Collection config: `nuxt/content.config.ts` (defines the `handbook` collection) ### Product docs -**Source:** `flowfuse/flowfuse` repo, `docs/` directory — **do not edit in this repo**; cloned automatically at build time by `nuxt/modules/docs-source.ts`. +`/docs` is assembled from two sources. Which one owns a page decides where to edit it, +and one of the two is not editable here at all. + +| Source | Owns | Edit where | +|---|---|---| +| `flowfuse/flowfuse` repo, `docs/` | the product documentation, versioned with the code | that repo, **not here** | +| `nuxt/content-guides/` | this repo's own guides: the Application Guide, the Node-RED guide, and the Node-RED library under `/docs/node-red/` and `/docs/flowfuse-nodes/` | **here**, in place | + +The 38 core-node pages under `/docs/node-red/core-nodes/**` are ordinary guide files in +that second source, not generator output. Each ends with a +`::node-red-help{category=... file=... node=... name=...}` directive; the component +(`nuxt/components/content/NodeRedHelp.vue`) asks `nuxt/server/api/node-red-help.get.ts` for +that node's built-in help, which fetches the Node-RED project's locale file, selects the +matching `` }); - eleventyConfig.addGlobalData("coreNodesArray", () => { - // Read the JSON file with core nodes - const coreNodes = JSON.parse(fs.readFileSync(path.join(__dirname, 'src', '_data', 'coreNodes.json'), 'utf-8')); - - // Transform coreNodes object into an array - return Object.entries(coreNodes).map(([key, nodes]) => ({ key, nodes })); - }) - - eleventyConfig.addAsyncShortcode("coreNodeDoc", async function (category, node) { - return await coreNodeDoc(category, node) - }); - eleventyConfig.addFilter("filterNodeCategory", function(nodes, category) { if (category === "all") { return nodes; diff --git a/lib/core-node-docs.js b/lib/core-node-docs.js deleted file mode 100644 index 1ad3ad08e6..0000000000 --- a/lib/core-node-docs.js +++ /dev/null @@ -1,27 +0,0 @@ -const EleventyFetch = require("@11ty/eleventy-fetch"); -const xpath = require('xpath'); -const dom = require('@xmldom/xmldom').DOMParser; - -module.exports = async function (cat, node) { - let page = ""; - try { - const url = `https://raw.githubusercontent.com/node-red/node-red/master/packages/node_modules/%40node-red/nodes/locales/en-US/${cat}/${node.file}.html` - let data = await EleventyFetch(url, { type: "text" }); - - const doc = new dom({ - locator: {}, - errorHandler: { - warning: function (w) { }, - error: function (e) { }, - fatalError: function (e) { console.error(e) } - } - // As there's a few nodes that have multiple docs in one file, we wrap it to make it - // valid XML - }).parseFromString("" + data.toString() + "", 'text/xml'); - - return xpath.select(`/scripts/script[starts-with(@data-help-name, "${node.xpath}")]/*`, doc).join(" ") - } catch (e) { - console.error(e); - return null; - } -}; diff --git a/nuxt/components/content/CtaImage.vue b/nuxt/components/content/CtaImage.vue index 727b058b0a..38cd998443 100644 --- a/nuxt/components/content/CtaImage.vue +++ b/nuxt/components/content/CtaImage.vue @@ -8,6 +8,17 @@ const props = defineProps<{ src: string alt: string cta: 'sign-up' | 'demo' | 'contact' | 'pricing' + /** + * What the event's `reference` should say, for a page that is not a blog post. + * + * Blog posts leave this unset and get `Blog: ` from the injected title + * below. The Node-RED guides under /docs have no injected title and no post, and + * carried their own reference string in a hand-written `onclick="capture(...)"` while + * they were Eleventy pages - MDC strips `onclick`, so that tracking went silent the + * moment they became content files. This prop is how those pages keep the exact + * reference they were already reporting, rather than a new dimension. + */ + reference?: string }>() const POSITION = 'inline-image' @@ -33,7 +44,7 @@ const postTitle = inject | undefined>('blogPostTitle', undefined) function onClick () { capture(EVENT, { - reference: `Blog: ${postTitle?.value || ''}`, + reference: props.reference || `Blog: ${postTitle?.value || ''}`, position: POSITION, cta_type: props.cta, }) diff --git a/nuxt/components/content/NodeRedHelp.vue b/nuxt/components/content/NodeRedHelp.vue new file mode 100644 index 0000000000..cfdfe97c82 --- /dev/null +++ b/nuxt/components/content/NodeRedHelp.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/nuxt/components/integrations/InstallBox.vue b/nuxt/components/integrations/InstallBox.vue index 51f5f9e55e..87d2a4ef36 100644 --- a/nuxt/components/integrations/InstallBox.vue +++ b/nuxt/components/integrations/InstallBox.vue @@ -1,5 +1,5 @@ @@ -21,7 +21,7 @@ const installPngUrl = '/images/integrations/palette-manager-install.png'

Install in Node-RED via the 0)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Football_in_Bloomington%2C_Indiana%2C_1995.jpg/1920px-Football_in_Bloomington%2C_Indiana%2C_1995.jpg","vt":"str"},{"p":"imageStyle","v":"hot","vt":"str"},{"p":"alpha","v":"[1,0]","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":370,"y":2420,"wires":[["91f63424a0d29cbc"]]},{"id":"f0c3d67e58965419","type":"http request","z":"e1ceeedf31ce1ebd","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":730,"y":2380,"wires":[["a18b3a2b995d44a3"]]},{"id":"6dfc1deb6464d48a","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"","width":"300","data":"image","dataType":"msg","active":true,"x":650,"y":2440,"wires":[["5f4317fdaae093fa"]]},{"id":"86d79d2fe917c839","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"payload.image","width":"300","data":"payload.image","dataType":"msg","active":true,"x":1020,"y":2440,"wires":[["15eb62db5c6df5b0"]]},{"id":"15eb62db5c6df5b0","type":"debug","z":"e1ceeedf31ce1ebd","name":"data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1190,"y":2440,"wires":[]},{"id":"fb988c7f2b5e7ebd","type":"inject","z":"e1ceeedf31ce1ebd","name":"tree (1ch greyscale)","props":[{"p":"url","v":"https://www.jotform.com/blog/wp-content/uploads/2022/02/niko-photos-tGTVxeOr_Rs-unsplash.jpg","vt":"str"},{"p":"imageStyle","v":"greyscale","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":370,"y":2380,"wires":[["91f63424a0d29cbc"]]},{"id":"1276ae55440d8971","type":"inject","z":"e1ceeedf31ce1ebd","name":"bird (viridis)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/3/32/House_sparrow04.jpg","vt":"str"},{"p":"imageStyle","v":"viridis","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":350,"y":2500,"wires":[["91f63424a0d29cbc"]]},{"id":"2d346c4a2bd33b35","type":"inject","z":"e1ceeedf31ce1ebd","name":"cave (density)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/f/f4/Hawaiian_lava_tube.jpg","vt":"str"},{"p":"imageStyle","v":"density","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":350,"y":2580,"wires":[["91f63424a0d29cbc"]]},{"id":"80a435f3656d71c3","type":"inject","z":"e1ceeedf31ce1ebd","name":"octopus (jet A0.9>0.3)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/57/Octopus2.jpg/1920px-Octopus2.jpg","vt":"str"},{"p":"imageStyle","v":"jet","vt":"str"},{"p":"alpha","v":"[0.5,0.9]","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":380,"y":2700,"wires":[["91f63424a0d29cbc"]]},{"id":"f6e800ce3399cb5f","type":"inject","z":"e1ceeedf31ce1ebd","name":"cave (grayscale)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/4/4e/HallOfTheMountainKings.jpg","vt":"str"},{"p":"imageStyle","v":"grayscale","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":360,"y":2620,"wires":[["91f63424a0d29cbc"]]},{"id":"0f4eaaa7bf1dd22e","type":"inject","z":"e1ceeedf31ce1ebd","name":"plane (rainbow)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/55/Spitfire_-_Season_Premiere_Airshow_2018_%28cropped%29.jpg/1920px-Spitfire_-_Season_Premiere_Airshow_2018_%28cropped%29.jpg","vt":"str"},{"p":"colormap","v":"rainbow","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":360,"y":2460,"wires":[["91f63424a0d29cbc"]]},{"id":"2f20edfb8190e918","type":"inject","z":"e1ceeedf31ce1ebd","name":"castle (greys)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/5/50/Bodiam-castle-10My8-1197.jpg","vt":"str"},{"p":"imageStyle","v":"greys","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":350,"y":2540,"wires":[["91f63424a0d29cbc"]]},{"id":"c8f48da588d9c9ce","type":"inject","z":"e1ceeedf31ce1ebd","name":"monkey (rdbu A0>1)","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/4/43/Bonnet_macaque_%28Macaca_radiata%29_Photograph_By_Shantanu_Kuveskar.jpg","vt":"str"},{"p":"imageStyle","v":"rdbu","vt":"str"},{"p":"alpha","v":"[0,1]","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":370,"y":2660,"wires":[["91f63424a0d29cbc"]]},{"id":"814e081d13eb58f6","type":"comment","z":"e1ceeedf31ce1ebd","name":"Image Depth","info":"","x":330,"y":2340,"wires":[]},{"id":"a18b3a2b995d44a3","type":"change","z":"e1ceeedf31ce1ebd","name":"","rules":[{"t":"move","p":"payload","pt":"msg","to":"image","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":855,"y":2380,"wires":[["6dfc1deb6464d48a"]],"l":false},{"id":"91f63424a0d29cbc","type":"junction","z":"e1ceeedf31ce1ebd","x":580,"y":2380,"wires":[["f0c3d67e58965419"]]},{"id":"2eab771c6086f708","type":"global-config","env":[],"modules":{"@flowfuse-nodes/nr-ai-nodes":"0.1.6","node-red-contrib-image-tools":"2.1.1"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/flowfuse/ai/image-classification.md b/nuxt/content-guides/flowfuse-nodes/ai/image-classification.md similarity index 98% rename from src/node-red/flowfuse/ai/image-classification.md rename to nuxt/content-guides/flowfuse-nodes/ai/image-classification.md index 87b5c62418..102fbecfcf 100644 --- a/src/node-red/flowfuse/ai/image-classification.md +++ b/nuxt/content-guides/flowfuse-nodes/ai/image-classification.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: Image Classification - parent: AI +title: Image Classification +metaTitle: "Image Classification" +navTitle: Image Classification +navOrder: 50 meta: - title: Image Classification description: Classify images using ONNX models directly in Node-RED. Supports pre-trained and custom models for tasks like labeling, content moderation, and object recognition. --- -# {{ meta.title }} +# Image Classification The **Image Classification** node enables you to classify images using **ONNX models** directly within **Node-RED**. It supports both **pre-trained** and **custom** models, allowing you to identify objects, detect scenes, or categorize images without requiring an external AI service. @@ -121,6 +121,8 @@ Each object in the output array includes: ## Example Flow -{% renderFlow 400 %} +::render-flow{:height="400"} +```json [{"id":"80afcb4f0920c6ce","type":"http request","z":"e1ceeedf31ce1ebd","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":670,"y":3160,"wires":[["cef35d49d8f7e529"]]},{"id":"cef35d49d8f7e529","type":"change","z":"e1ceeedf31ce1ebd","name":"topK 3, thres: 5%","rules":[{"t":"move","p":"payload","pt":"msg","to":"image","tot":"msg"},{"t":"set","p":"topK","pt":"msg","to":"3","tot":"num"},{"t":"set","p":"thres","pt":"msg","to":"0.05","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":850,"y":3160,"wires":[["d76d57912ca9fd3f"]]},{"id":"d76d57912ca9fd3f","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"","width":"224","data":"image","dataType":"msg","active":true,"x":1010,"y":3160,"wires":[["903b8d0c4750e324"]]},{"id":"fb880704ba86d144","type":"debug","z":"e1ceeedf31ce1ebd","name":"class","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload[0].label & \"(\" & $round(payload[0].score * 100,2) & \"%)\"","statusType":"jsonata","x":870,"y":3220,"wires":[]},{"id":"a8e34856bea9f3cd","type":"image-classification","z":"e1ceeedf31ce1ebd","name":"","property":"images","propertyType":"msg","model":"onnx-community/resnet-50-ONNX","modelType":"name","dtype":"fp16","topK":"1","topKType":"num","threshold":"0.1","thresholdType":"num","x":1080,"y":3800,"wires":[["7bd09a7daa0d5dae","0b07230327b5689c"]]},{"id":"903b8d0c4750e324","type":"image-classification","z":"e1ceeedf31ce1ebd","name":"","property":"image","propertyType":"msg","model":"Xenova/vit-base-patch16-224","modelType":"name","dtype":"q8","topK":"topK","topKType":"msg","threshold":"thres","thresholdType":"msg","x":700,"y":3220,"wires":[["fb880704ba86d144"]]},{"id":"e21197eababa2618","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"images[0]","width":"224","data":"images[0]","dataType":"msg","active":true,"x":660,"y":3800,"wires":[["d2b756bb0a700a0e"]]},{"id":"d2b756bb0a700a0e","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"images[1]","width":"224","data":"images[1]","dataType":"msg","active":true,"x":860,"y":3800,"wires":[["a8e34856bea9f3cd"]]},{"id":"b5768dffc25bf899","type":"inject","z":"e1ceeedf31ce1ebd","name":"beer","props":[{"p":"url","v":"https://stoelzle-lausitz.com/cdn/shop/files/stoelzle-lausitz-bierglaeser-glass-mug-full-beer-foam.png","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":450,"y":3200,"wires":[["49f8de9173d09a2f"]]},{"id":"0ff2faa6328f5e8c","type":"inject","z":"e1ceeedf31ce1ebd","name":"wolf","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Eurasian_wolf_2.jpg/1920px-Eurasian_wolf_2.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":450,"y":3160,"wires":[["49f8de9173d09a2f"]]},{"id":"5833b4f0c1928af6","type":"inject","z":"e1ceeedf31ce1ebd","name":"owl","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/5/56/Bubo_bubo_sibiricus_-_01.JPG/1024px-Bubo_bubo_sibiricus_-_01.JPG","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":450,"y":3240,"wires":[["49f8de9173d09a2f"]]},{"id":"7bd09a7daa0d5dae","type":"debug","z":"e1ceeedf31ce1ebd","name":"class","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"true","targetType":"full","statusVal":"payload[0][0].label & \"(\" & $round(payload[0][0].score * 100,2) & \"%)\"","statusType":"jsonata","x":1270,"y":3760,"wires":[]},{"id":"0b07230327b5689c","type":"debug","z":"e1ceeedf31ce1ebd","name":"class","active":true,"tosidebar":false,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload[1][0].label & \"(\" & $round(payload[1][0].score * 100,2) & \"%)\"","statusType":"jsonata","x":1270,"y":3820,"wires":[]},{"id":"c799ffb88823e69c","type":"comment","z":"e1ceeedf31ce1ebd","name":"Image Classification","info":"","x":470,"y":3060,"wires":[]},{"id":"99eb75b743ba98ea","type":"comment","z":"e1ceeedf31ce1ebd","name":"Batch Image Classification","info":"","x":510,"y":3700,"wires":[]},{"id":"55506dfb9dc40daf","type":"change","z":"e1ceeedf31ce1ebd","name":"move payload images array","rules":[{"t":"set","p":"images","pt":"msg","to":"[]","tot":"json"},{"t":"move","p":"payload","pt":"msg","to":"images[0]","tot":"msg"},{"t":"set","p":"url","pt":"msg","to":"urls[1]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":775,"y":3740,"wires":[["2b8af48bb3f824bc"]],"l":false},{"id":"c9b1c4ff34ec7d02","type":"change","z":"e1ceeedf31ce1ebd","name":"move payload images array","rules":[{"t":"move","p":"payload","pt":"msg","to":"images[1]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":995,"y":3740,"wires":[["e21197eababa2618"]],"l":false},{"id":"c7c8d609c2bfaffc","type":"inject","z":"e1ceeedf31ce1ebd","name":"wolf+clock","props":[{"p":"urls","v":"[]","vt":"json"},{"p":"urls[0]","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Eurasian_wolf_2.jpg/1920px-Eurasian_wolf_2.jpg","vt":"str"},{"p":"urls[1]","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/cf/Pendulum_clock_by_Jacob_Kock%2C_antique_furniture_photography%2C_IMG_0931_edit.jpg/250px-Pendulum_clock_by_Jacob_Kock%2C_antique_furniture_photography%2C_IMG_0931_edit.jpg","vt":"str"},{"p":"url","v":"urls[0]","vt":"msg"},{"p":"preprocessorConfigOverrides","v":"{\"size\": {\"width\":224, \"height\":224}}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":460,"y":3740,"wires":[["2dd6833f25e7a905"]]},{"id":"2dd6833f25e7a905","type":"http request","z":"e1ceeedf31ce1ebd","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":670,"y":3740,"wires":[["55506dfb9dc40daf"]]},{"id":"2b8af48bb3f824bc","type":"http request","z":"e1ceeedf31ce1ebd","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":890,"y":3740,"wires":[["c9b1c4ff34ec7d02"]]},{"id":"79f068f1e84fdef2","type":"inject","z":"e1ceeedf31ce1ebd","name":"","props":[{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":450,"y":3480,"wires":[["c7b482660e93f645"]]},{"id":"c7b482660e93f645","type":"file in","z":"e1ceeedf31ce1ebd","name":"","filename":"dog.jpg","filenameType":"str","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":660,"y":3480,"wires":[["64b15839a5d33ad4"]]},{"id":"70d9f015166b7f63","type":"comment","z":"e1ceeedf31ce1ebd","name":"Auto preprocessing: Using Image as input","info":"","x":560,"y":3120,"wires":[]},{"id":"0c1b6f94772cf829","type":"inject","z":"e1ceeedf31ce1ebd","name":"plane","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/e/eb/British_Airways_Concorde_G-BOAC_03.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":450,"y":3280,"wires":[["49f8de9173d09a2f"]]},{"id":"584b4e035f0e91dd","type":"comment","z":"e1ceeedf31ce1ebd","name":"Auto preprocessing: Using local file Image as input. \\n NOTE: You will need to add a dog.jpg image to test this","info":"","x":600,"y":3420,"wires":[]},{"id":"25a103fa3e5ced6e","type":"image-classification","z":"e1ceeedf31ce1ebd","name":"","property":"payload","propertyType":"msg","model":"onnx-community/resnet-50-ONNX","modelType":"name","dtype":"fp16","topK":"1","topKType":"num","threshold":"0.5","thresholdType":"num","x":700,"y":3540,"wires":[["7af6dd11b8160481"]]},{"id":"7af6dd11b8160481","type":"debug","z":"e1ceeedf31ce1ebd","name":"class","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload[0].label & \"(\" & $round(payload[0].score * 100,2) & \"%)\"","statusType":"jsonata","x":870,"y":3540,"wires":[]},{"id":"64b15839a5d33ad4","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"","width":"180","data":"payload","dataType":"msg","active":true,"x":1010,"y":3480,"wires":[["25a103fa3e5ced6e"]]},{"id":"49f8de9173d09a2f","type":"junction","z":"e1ceeedf31ce1ebd","x":560,"y":3160,"wires":[["80afcb4f0920c6ce"]]},{"id":"1012c4c8ef915cdd","type":"global-config","env":[],"modules":{"node-red-contrib-image-tools":"2.1.1","@flowfuse-nodes/nr-ai-nodes":"0.1.6","@flowfuse/nr-file-nodes":"0.0.8"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/flowfuse/ai/llm-nodes.md b/nuxt/content-guides/flowfuse-nodes/ai/llm-nodes.md similarity index 98% rename from src/node-red/flowfuse/ai/llm-nodes.md rename to nuxt/content-guides/flowfuse-nodes/ai/llm-nodes.md index a6a571d841..b838957d2e 100644 --- a/src/node-red/flowfuse/ai/llm-nodes.md +++ b/nuxt/content-guides/flowfuse-nodes/ai/llm-nodes.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: LLM Nodes - parent: AI - order: 5 +title: LLM Nodes +metaTitle: "LLM Nodes" +navTitle: LLM Nodes +navOrder: 5 meta: - title: LLM Nodes description: Send text prompts to hosted and local large language models from OpenAI, Anthropic, Google Gemini, and Ollama directly within Node-RED flows. --- -# {{ meta.title }} +# LLM Nodes The **LLM nodes** send a text prompt to a large language model and return the model's text response. Four provider nodes are included: **OpenAI**, **Anthropic (Claude)**, **Google Gemini**, and **Ollama**. They share a common configuration and message contract so they can be used interchangeably in a flow. @@ -49,6 +48,8 @@ The node expects a text prompt in the configured input property (default: `msg.p The following example sends the same prompt to OpenAI, Anthropic, and Gemini in parallel. It expects the relevant API keys to be available as environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and `GEMINI_API_KEY`). Each provider branch runs independently, so the flow works with only one or two keys configured. -{% renderFlow %} +::render-flow +```json [{"id":"e4180867a7047b1f","type":"group","z":"6ab76a52887948e7","style":{"stroke":"#555555","stroke-opacity":"1","fill":"rgba(255, 255, 255, 0.03)","fill-opacity":"1","label":true,"label-position":"nw","color":"#f0f0f0"},"nodes":["35993459d37b6f93","152c15fad2618e06","d31647cc21cfb203","810f53d0e1fd9948","8fc45bb0b9dd8aea","0268271895881e33","a8ae18854eb8b60f","28e9d111cbe9274f","cdc5f07bfba274e9","8df6bcc3d93baa32","937896ec93065516"],"x":114,"y":99,"w":652,"h":362},{"id":"35993459d37b6f93","type":"ff-ai-openai","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"","apiKeyEnv":"OPENAI_API_KEY","apiKeyEnvType":"env","model":"gpt-4.1-mini","modelType":"str","temperature":"0.7","temperatureType":"num","maxTokens":"1024","maxTokensType":"num","timeoutMs":"30000","timeoutMsType":"num","system":"","systemType":"str","organization":"","project":"","sendClientRequestId":false,"x":410,"y":140,"wires":[["d31647cc21cfb203"]]},{"id":"152c15fad2618e06","type":"inject","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"ISS info","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"what speed does the ISS orbit the earth and how many times does it orbit in 24h?","payloadType":"str","x":210,"y":140,"wires":[["35993459d37b6f93"]]},{"id":"d31647cc21cfb203","type":"debug","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"openai response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":630,"y":140,"wires":[]},{"id":"810f53d0e1fd9948","type":"ff-ai-anthropic","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"","apiKeyEnv":"ANTHROPIC_API_KEY","apiKeyEnvType":"env","model":"claude-haiku-4-5","modelType":"str","temperature":"0.7","temperatureType":"num","maxTokens":"512","maxTokensType":"num","timeoutMs":"30000","timeoutMsType":"num","system":"","systemType":"str","endpoint":"https://api.anthropic.com/v1/messages","anthropicVersion":"2023-06-01","sendClientRequestId":false,"x":420,"y":220,"wires":[["28e9d111cbe9274f"]]},{"id":"8fc45bb0b9dd8aea","type":"inject","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"ISS info","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"what speed does the ISS orbit the earth and how many times does it orbit in 24h?","payloadType":"str","x":210,"y":220,"wires":[["810f53d0e1fd9948"]]},{"id":"0268271895881e33","type":"ff-ai-gemini","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"","apiKeyEnv":"GEMINI_API_KEY","apiKeyEnvType":"env","model":"gemini-3.1-flash-lite","modelType":"str","temperature":"0.7","temperatureType":"num","maxTokens":"1024","maxTokensType":"num","timeoutMs":"30000","timeoutMsType":"num","system":"","systemType":"str","endpointTemplate":"https://generativelanguage.googleapis.com/v1beta/models/{MODEL}:generateContent","sendClientRequestId":false,"x":410,"y":300,"wires":[["cdc5f07bfba274e9"]]},{"id":"a8ae18854eb8b60f","type":"inject","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"ISS info","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"what speed does the ISS orbit the earth and how many times does it orbit in 24h?","payloadType":"str","x":210,"y":300,"wires":[["0268271895881e33"]]},{"id":"28e9d111cbe9274f","type":"debug","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"claude response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":620,"y":220,"wires":[]},{"id":"cdc5f07bfba274e9","type":"debug","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"gemini response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":620,"y":300,"wires":[]},{"id":"8df6bcc3d93baa32","type":"catch","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"","scope":"group","uncaught":false,"x":230,"y":420,"wires":[["937896ec93065516"]]},{"id":"937896ec93065516","type":"debug","z":"6ab76a52887948e7","g":"e4180867a7047b1f","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":420,"wires":[]}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/flowfuse/ai/object-detection.md b/nuxt/content-guides/flowfuse-nodes/ai/object-detection.md similarity index 98% rename from src/node-red/flowfuse/ai/object-detection.md rename to nuxt/content-guides/flowfuse-nodes/ai/object-detection.md index 5f9e647874..e6d077decb 100644 --- a/src/node-red/flowfuse/ai/object-detection.md +++ b/nuxt/content-guides/flowfuse-nodes/ai/object-detection.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: Object Detection - parent: AI +title: Object Detection +metaTitle: "Object Detection" +navTitle: Object Detection +navOrder: 50 meta: - title: Object Detection description: The Object Detection node identifies and locates objects within images using ONNX models such as YOLO and DETR, enabling real-time computer vision directly in Node-RED without external AI services. --- -# {{ meta.title }} +# Object Detection The **Object Detection** node enables detection of objects within images using **ONNX models**. It supports a wide range of architectures, including **DETR** and **YOLO**, and accepts image data in multiple formats such as Buffers, base64 strings, or tensors. @@ -135,6 +135,8 @@ You can then use a **Function** node for custom post-processing. ## Example Flow -{% renderFlow %} +::render-flow +```json [{"id":"6a3ca8414acfad34","type":"http request","z":"e1ceeedf31ce1ebd","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":610,"y":1180,"wires":[["f4998c6a9004a6ba"]]},{"id":"f4998c6a9004a6ba","type":"change","z":"e1ceeedf31ce1ebd","name":"","rules":[{"t":"set","p":"image","pt":"msg","to":"payload","tot":"msg","dc":true}],"action":"","property":"","from":"","to":"","reg":false,"x":620,"y":1240,"wires":[["d902ee154cb6ceac"]]},{"id":"b1a008f95453208e","type":"inject","z":"e1ceeedf31ce1ebd","name":"people on bikes","props":[{"p":"url","v":"https://learnopencv.com/wp-content/uploads/2021/04/vehicle-traffic-object-detection-test-image.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":380,"y":1240,"wires":[["47d587d70bac373c"]]},{"id":"8d18b7c3c2197981","type":"inject","z":"e1ceeedf31ce1ebd","name":"dog bike truck","props":[{"p":"url","v":"https://djl.ai/examples/src/test/resources/dog_bike_car.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":370,"y":1200,"wires":[["47d587d70bac373c"]]},{"id":"86693a849586867a","type":"inject","z":"e1ceeedf31ce1ebd","name":"desk","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/2/22/Schreibtisch.2.JPG/450px-Schreibtisch.2.JPG","vt":"str"},{"p":"threshold","v":"0.85","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":350,"y":1160,"wires":[["47d587d70bac373c"]]},{"id":"9113dce8475110f5","type":"object-detection","z":"e1ceeedf31ce1ebd","name":"","property":"image","propertyType":"msg","model":"Xenova/detr-resnet-50","modelType":"name","dtype":"fp16","threshold":"threshold","thresholdType":"msg","x":590,"y":1300,"wires":[["2ccb4544d3d5188d"]]},{"id":"2ccb4544d3d5188d","type":"change","z":"e1ceeedf31ce1ebd","name":"","rules":[{"t":"set","p":"result","pt":"msg","to":"payload","tot":"msg"},{"t":"set","p":"payload","pt":"msg","to":"image","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":1300,"wires":[["385ba9c93695c86b"]]},{"id":"a2ef9dc6bf6007e2","type":"debug","z":"e1ceeedf31ce1ebd","name":"debug 15","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"result","targetType":"msg","statusVal":"","statusType":"auto","x":1100,"y":1360,"wires":[]},{"id":"385ba9c93695c86b","type":"function","z":"e1ceeedf31ce1ebd","name":"Pascal VOC to COCO bbox","func":"const annotations = msg.result.slice(0,20)\n// convert Pascal VOC Format bbox to coco format\nconst bboxes = []\nfor (const annotation of annotations) {\n const [ xmin, ymin, xmax, ymax ] = annotation.bbox\n const width = xmax - xmin\n const height = ymax - ymin\n const percent = annotation.score * 100\n bboxes.push({\n label: `${annotation.label} (${percent.toFixed(1)}%)`,\n bbox: [xmin, ymin, width, height]\n })\n}\nmsg.annotations = bboxes\nreturn msg\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1040,"y":1300,"wires":[["da4063f9d3a2548b"]]},{"id":"da0ae2dfd2df248f","type":"inject","z":"e1ceeedf31ce1ebd","name":"football","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/4/42/Football_in_Bloomington%2C_Indiana%2C_1995.jpg/500px-Football_in_Bloomington%2C_Indiana%2C_1995.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":350,"y":1120,"wires":[["47d587d70bac373c"]]},{"id":"f23ba6823adaab87","type":"comment","z":"e1ceeedf31ce1ebd","name":"Object Detection","info":"","x":360,"y":1080,"wires":[]},{"id":"d902ee154cb6ceac","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"","width":"240","data":"image","dataType":"msg","active":true,"x":350,"y":1300,"wires":[["9113dce8475110f5"]]},{"id":"d75c7f464cca14c1","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"","width":"720","data":"payload","dataType":"msg","active":true,"x":830,"y":1360,"wires":[["a2ef9dc6bf6007e2"]]},{"id":"da4063f9d3a2548b","type":"annotate-image","z":"e1ceeedf31ce1ebd","name":"","fill":"","stroke":"#ffA000","lineWidth":"3","fontSize":24,"fontColor":"#ffA000","x":660,"y":1360,"wires":[["d75c7f464cca14c1"]]},{"id":"47d587d70bac373c","type":"junction","z":"e1ceeedf31ce1ebd","x":500,"y":1180,"wires":[["6a3ca8414acfad34"]]},{"id":"8ee41c61e110e410","type":"global-config","env":[],"modules":{"@flowfuse-nodes/nr-ai-nodes":"0.1.6","node-red-contrib-image-tools":"2.1.1","node-red-node-annotate-image":"0.2.0"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/flowfuse/ai/onxx.md b/nuxt/content-guides/flowfuse-nodes/ai/onxx.md similarity index 98% rename from src/node-red/flowfuse/ai/onxx.md rename to nuxt/content-guides/flowfuse-nodes/ai/onxx.md index 2a68fe7d13..905e6e0f4c 100644 --- a/src/node-red/flowfuse/ai/onxx.md +++ b/nuxt/content-guides/flowfuse-nodes/ai/onxx.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: ONXX - parent: AI +title: ONXX +metaTitle: "ONXX" +navTitle: ONXX +navOrder: 50 meta: - title: ONXX - description: The ONNX node allows you to perform AI inference directly in Node-RED using ONNX models, supporting image, object, and numeric predictions without external AI services. + description: The ONNX node allows you to perform AI inference directly in Node-RED using ONNX models, supporting image, object, and numeric predictions without external AI services. --- -# {{ meta.title }} +# ONXX The **ONNX** node allows you to perform AI inference directly in **Node-RED** using **ONNX models**. It can run a wide range of pre-trained or custom models, including image classification, object detection, and numeric prediction tasks. @@ -120,6 +120,8 @@ For batch inputs, use an array of arrays: ## Example Flow -{% renderFlow %} +::render-flow +```json [{"id":"239b5347fce92ca9","type":"function","z":"e1ceeedf31ce1ebd","name":"load labels","func":"// flag to let the node know we are handling preprocessing\nmsg.noPreprocessorConfig = true\n\nmsg.config = {\n label2id : {\n \"apple\": 0,\n \"kiwi\": 1,\n \"mango\": 2\n },\n id2label : {\n \"0\": \"apple\",\n \"1\": \"kiwi\",\n \"2\": \"mango\"\n }\n}\nreturn msg\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":810,"y":5860,"wires":[["117470780ea20643"]]},{"id":"117470780ea20643","type":"function","z":"e1ceeedf31ce1ebd","name":"preprocessing","func":"// ImageNet normalization values\nconst IMG_MEAN = [0.485, 0.456, 0.406]\nconst IMG_STD = [0.229, 0.224, 0.225]\n\n// The width and height expected by the model\nconst WIDTH = 224\nconst HEIGHT = 224\n\n/**\n * Load and preprocess image for PyTorch ONNX model\n * @param {Buffer} buffer - an image\n * @returns {Promise} - Tensor ready for ort.run\n */\nasync function preprocessImage(buffer) {\n // Load and resize image\n const resolved = await sharp(buffer)\n .resize(WIDTH, HEIGHT)\n .raw()\n .toBuffer({ resolveWithObject: true });\n\n const { data, info } = resolved; // data = Uint8Array, info = {width, height, channels}\n const { width, height, channels } = info;\n\n if (channels !== 3) {\n throw new Error(`Expected 3 channels (RGB), got ${channels}`);\n }\n\n // Convert to float32 and normalize\n const floatData = new Float32Array(width * height * channels);\n for (let i = 0; i < width * height; i++) {\n for (let c = 0; c < 3; c++) {\n // data is 0..255, convert to 0..1\n const v = data[i * 3 + c] / 255.0;\n floatData[c * width * height + i] = (v - IMG_MEAN[c]) / IMG_STD[c];\n // Notice: channel-first layout (C,H,W)\n }\n }\n\n return floatData;\n}\n\nmsg.payload = {\n type: 'float32',\n dims: [1, 3, HEIGHT, WIDTH], // 1 image, 3 chanels, HEIGHT, WIDTH\n data: await preprocessImage(msg.payload)\n}\n// msg.payload = await preprocessImage(msg.payload)\nreturn msg\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[{"var":"sharp","module":"sharp"}],"x":1020,"y":5860,"wires":[["36477656b418b6f7"]]},{"id":"36477656b418b6f7","type":"advanced-ai","z":"e1ceeedf31ce1ebd","name":"fruit_classifier","property":"payload","propertyType":"msg","model":"C:/Users/sdmcl/repos/node-red/model_cache/flowfuse/fruit_classifier/onnx/model.onnx","modelType":"path","x":900,"y":5920,"wires":[["aa2b68b2ca30ee34"]]},{"id":"aa2b68b2ca30ee34","type":"function","z":"e1ceeedf31ce1ebd","name":"Sort and Label","func":"/*\nData arrives like so:\nmsg.payload.output.cpuData: {\"0\":1.7446770668029785, 1: -2.4252512454986572, 2: 2.786302089691162}\n*/\nconst cpuData = msg.payload.output.cpuData\nconst labels = []\nconst ids = Object.keys(msg.config.id2label).forEach(id => {\n labels.push(msg.config.id2label[id])\n})\n\n// Convert cpuData to logits array\nconst logits = Object.values(cpuData)\n\n// Softmax function\nfunction softmax(arr) {\n const exp = arr.map(x => Math.exp(x))\n const sum = exp.reduce((a, b) => a + b, 0)\n return exp.map(x => x / sum)\n}\n\nconst probs = softmax(logits)\n\n// Build sorted array of results with labels and probabilities\nconst resultArray = probs\n .map((prob, idx) => ({\n classIndex: idx,\n className: labels[idx] || 'Unknown',\n confidence: prob\n }))\n .filter(item => item.confidence > 0)\n .sort((a, b) => b.confidence - a.confidence)\n\nmsg.payload = resultArray\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1080,"y":5920,"wires":[["f3b605669eb8b00b"]]},{"id":"f3b605669eb8b00b","type":"debug","z":"e1ceeedf31ce1ebd","name":"","active":true,"tosidebar":true,"console":false,"tostatus":true,"complete":"payload","targetType":"msg","statusVal":"payload[0].className & \" (\" & $round(payload[0].confidence * 100, 2) & \"%)\"","statusType":"jsonata","x":1070,"y":5980,"wires":[]},{"id":"114028180bd0659b","type":"inject","z":"e1ceeedf31ce1ebd","name":"apple","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Honeycrisp-Apple.jpg/1200px-Honeycrisp-Apple.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":430,"y":5800,"wires":[["bc8648c963842493"]]},{"id":"709d0cacee000fde","type":"inject","z":"e1ceeedf31ce1ebd","name":"apple","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/c/c1/Fuji_apple.jpg/1200px-Fuji_apple.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":430,"y":5840,"wires":[["bc8648c963842493"]]},{"id":"bc8648c963842493","type":"http request","z":"e1ceeedf31ce1ebd","name":"","method":"GET","ret":"bin","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":630,"y":5800,"wires":[["e6c4bc36b25440e5"]]},{"id":"38ce06ccd2500c74","type":"inject","z":"e1ceeedf31ce1ebd","name":"kiwi","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Kiwi_%28Actinidia_chinensis%29_2_Luc_Viatour.jpg/250px-Kiwi_%28Actinidia_chinensis%29_2_Luc_Viatour.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":430,"y":5880,"wires":[["bc8648c963842493"]]},{"id":"8c2a2e98a21f319b","type":"inject","z":"e1ceeedf31ce1ebd","name":"mango","props":[{"p":"url","v":"https://upload.wikimedia.org/wikipedia/commons/thumb/7/74/Mangos_-_single_and_halved.jpg/500px-Mangos_-_single_and_halved.jpg","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":430,"y":5920,"wires":[["bc8648c963842493"]]},{"id":"e6c4bc36b25440e5","type":"image viewer","z":"e1ceeedf31ce1ebd","name":"","width":"224","data":"payload","dataType":"msg","active":true,"x":610,"y":5860,"wires":[["239b5347fce92ca9"]]},{"id":"5711f41ecfe998d6","type":"comment","z":"e1ceeedf31ce1ebd","name":"Using a re-trained resnet model trained to recognise apples, kiwis and mangos \\n See info in the INFO panel on the sidebar","info":"This Node-RED demo flow requires you to have trained a model to recognize fruit types (apple, kiwi, mango) using a labeled image dataset.\n\nThe process involves:\n1. Setting up your Python environment with PyTorch, TorchVision, ONNX, and ONNX Runtime.\n1. Organizing your dataset into train, validation, and test folders for each class.\n1. Using transfer learning with a pre-trained ResNet18 model, fine-tuned on your images.\n1. Training the model and evaluating its accuracy.\n1. Exporting the trained PyTorch model to ONNX format for interoperability.\n1. Optionally, testing the ONNX model with ONNX Runtime to verify predictions.\n\nOnce you have the exported ONNX model (e.g., fruit_classifier.onnx), you can use it with this demo flow for inference.","x":630,"y":5740,"wires":[]},{"id":"dcc5015a105e33bb","type":"global-config","env":[],"modules":{"@flowfuse-nodes/nr-ai-nodes":"0.1.6","node-red-contrib-image-tools":"2.1.1"}}] -{% endrenderFlow %} \ No newline at end of file +``` +:: diff --git a/nuxt/content-guides/flowfuse-nodes/edge/README.md b/nuxt/content-guides/flowfuse-nodes/edge/README.md new file mode 100644 index 0000000000..3cde9f7818 --- /dev/null +++ b/nuxt/content-guides/flowfuse-nodes/edge/README.md @@ -0,0 +1,41 @@ +--- +title: Edge Certified Nodes +metaTitle: "Edge Certified Nodes" +navTitle: Edge +navOrder: 4 +meta: + description: Documentation for FlowFuse Edge Certified Nodes, including nodes for connecting FlowFuse to industrial protocols, PLCs, SCADA systems, and factory-floor equipment. +--- + +# Edge Certified Nodes + +This section contains documentation for **FlowFuse Edge Certified Nodes** that connect your FlowFuse instances to industrial protocols, PLCs, SCADA systems, and factory-floor equipment. + +FlowFuse Certified Nodes are packages that FlowFuse has vetted for quality, security, and support, and maintains on an ongoing basis. To learn more about what certification means and how these nodes are delivered, [read the FlowFuse Certified Nodes blog post](/blog/2025/07/certified-nodes-v2/). + +

+ +

Note

+ +This section is expanding. We are actively working to bring more Edge Certified Nodes to FlowFuse, and additional documentation will be added here over time. + +
+ +## Nodes + +This section lists the **Edge Certified Nodes** documented in FlowFuse: + +- [RTSP Video Feed](/docs/flowfuse-nodes/edge/rtsp/): Documentation for the FlowFuse RTSP Video Feed node, which connects to an RTSP camera stream and extracts still frames as PNG images for use in flows, dashboards, and local AI models. +- [CIP Suite, EtherNet/IP Nodes](/docs/flowfuse-nodes/edge/cip-suite/): A suite of nodes for reading, writing, and monitoring data on Rockwell Automation and Allen-Bradley PLCs, and other CIP-capable devices, using the EtherNet/IP protocol. +- [Modbus](/docs/flowfuse-nodes/edge/modbus/): A FlowFuse-certified package for reading and writing coils and registers over Modbus TCP, Modbus UDP (where supported), and Serial (RTU/ASCII), and for simulating a Modbus server, all from within your flows. +- [OPC UA for FlowFuse - FlowFuse Certified Node](/docs/flowfuse-nodes/edge/opcua/): Connect a FlowFuse instance to industrial OPC UA servers: read, write, monitor, call methods, browse, read history, work with files, or host your own OPC UA server. A FlowFuse Certified Node. + +

Note

+ +Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. + +
diff --git a/src/node-red/flowfuse/edge/cip-suite.md b/nuxt/content-guides/flowfuse-nodes/edge/cip-suite.md similarity index 95% rename from src/node-red/flowfuse/edge/cip-suite.md rename to nuxt/content-guides/flowfuse-nodes/edge/cip-suite.md index 59beccf64c..cff9eff92e 100644 --- a/src/node-red/flowfuse/edge/cip-suite.md +++ b/nuxt/content-guides/flowfuse-nodes/edge/cip-suite.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: CIP Suite, EtherNet/IP Nodes - parent: Edge - order: 2 +title: CIP Suite, EtherNet/IP Nodes +metaTitle: "CIP Suite, EtherNet/IP Nodes" +navTitle: CIP Suite, EtherNet/IP Nodes +navOrder: 2 meta: - title: CIP Suite, EtherNet/IP Nodes description: A suite of nodes for reading, writing, and monitoring data on Rockwell Automation and Allen-Bradley PLCs, and other CIP-capable devices, using the EtherNet/IP protocol. --- -# {{ meta.title }} +# CIP Suite, EtherNet/IP Nodes A suite of nodes for communicating with Rockwell Automation and Allen-Bradley PLCs, and other CIP-capable devices, using the EtherNet/IP (Ethernet Industrial Protocol) protocol. @@ -18,9 +17,11 @@ The CIP Suite connects FlowFuse flows to industrial control systems across the f This is a **FlowFuse Certified Node**. Unlike community nodes, which vary in quality and can go unmaintained without warning, FlowFuse vets Certified Nodes for quality, security, and support, and maintains them on an ongoing basis. [Read more about Certified Nodes](/blog/2025/07/certified-nodes-v2/). It is built on the [st-ethernet-ip](https://www.npmjs.com/package/st-ethernet-ip) protocol driver. -{% note %} +

Note

+ The CIP Suite is not available by default. It is part of the FlowFuse Edge Certified Nodes catalogue, which is part of the **FlowFuse Edge** offering. Please contact our sales team at [Contact us](/contact-us/) to learn more or to request access. -{% endnote %} + +
## What is EtherNet/IP? @@ -51,7 +52,7 @@ With the CIP Suite, the flow looks like this: 1. **Connect**: a `cip-endpoint` configuration node holds the shared connection to the PLC, with automatic reconnection. 2. **Subscribe**: a `cip-subscribe` node scans the tags cyclically and emits messages on change, with deadband filtering to suppress noise. 3. **Enrich**: a `function` node attaches a line ID, timestamp, or shift code, and converts raw units into something business-readable. -4. **Deliver**: the enriched payload is published to an MQTT broker or Unified Namespace, written to a historian, or dropped straight into [FlowFuse Tables](/node-red/flowfuse/flowfuse-tables/). +4. **Deliver**: the enriched payload is published to an MQTT broker or Unified Namespace, written to a historian, or dropped straight into [FlowFuse Tables](/docs/flowfuse-nodes/flowfuse-tables/). The result: production counts, machine states, and reject rates are available to dashboards, analytics, and other systems within moments of changing on the floor. No polling scripts, no OPC server to license and maintain, no manual tag mapping in a separate SCADA package. @@ -66,7 +67,7 @@ The result: production counts, machine states, and reject rates are available to ### Why it scales beyond one PLC -Only the source and sink nodes are protocol-specific. The transform stages of a flow, such as renaming tags, adding context, and routing data, don't care what protocol the data came from. The same flow pattern you build for one Allen-Bradley line can be reused for a Siemens PLC over [OPC UA](/node-red/flowfuse/edge/opcua/) or a Modbus device just by swapping the protocol nodes, which matters a lot on a mixed-vendor plant floor. +Only the source and sink nodes are protocol-specific. The transform stages of a flow, such as renaming tags, adding context, and routing data, don't care what protocol the data came from. The same flow pattern you build for one Allen-Bradley line can be reused for a Siemens PLC over [OPC UA](/docs/flowfuse-nodes/edge/opcua/) or a Modbus device just by swapping the protocol nodes, which matters a lot on a mixed-vendor plant floor. ## Requirements @@ -78,9 +79,11 @@ Only the source and sink nodes are protocol-specific. The transform stages of a Because this suite is part of the FlowFuse Edge Certified Nodes catalogue, which is part of the **FlowFuse Edge** offering, make sure your account has access before installing. Contact our [sales team](/contact-us/) if you don't. -{% note %} +

Note

+ Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. -{% endnote %} + +
### Install via the Palette Manager (recommended) @@ -289,4 +292,4 @@ The `cip-pccc-read` node returns data-file values from SLC 500, MicroLogix, and ### Connection Drops Frequently - Reduce scan rates to decrease network load. - Check network stability and switch configuration. -- Verify the PLC isn't overloaded with connections from other clients. \ No newline at end of file +- Verify the PLC isn't overloaded with connections from other clients. diff --git a/src/node-red/flowfuse/edge/images/opcua/endpoint-add-new.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-add-new.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/endpoint-add-new.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-add-new.png diff --git a/src/node-red/flowfuse/edge/images/opcua/endpoint-subscriptions.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-subscriptions.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/endpoint-subscriptions.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-subscriptions.png diff --git a/src/node-red/flowfuse/edge/images/opcua/endpoint-verification-secure-error.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-verification-secure-error.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/endpoint-verification-secure-error.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-verification-secure-error.png diff --git a/src/node-red/flowfuse/edge/images/opcua/endpoint-verification-user-access-denied.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-verification-user-access-denied.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/endpoint-verification-user-access-denied.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-verification-user-access-denied.png diff --git a/src/node-red/flowfuse/edge/images/opcua/endpoint-verification.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-verification.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/endpoint-verification.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/endpoint-verification.png diff --git a/src/node-red/flowfuse/edge/images/opcua/example-all-attributes-on-rfid-scan-result.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/example-all-attributes-on-rfid-scan-result.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/example-all-attributes-on-rfid-scan-result.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/example-all-attributes-on-rfid-scan-result.png diff --git a/src/node-red/flowfuse/edge/images/opcua/node-palette.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/node-palette.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/node-palette.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/node-palette.png diff --git a/src/node-red/flowfuse/edge/images/opcua/nodeid-as-browse-path.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/nodeid-as-browse-path.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/nodeid-as-browse-path.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/nodeid-as-browse-path.png diff --git a/src/node-red/flowfuse/edge/images/opcua/opcu-client2-read.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/opcu-client2-read.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/opcu-client2-read.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/opcu-client2-read.png diff --git a/src/node-red/flowfuse/edge/images/opcua/opcua-client2-monitor.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/opcua-client2-monitor.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/opcua-client2-monitor.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/opcua-client2-monitor.png diff --git a/src/node-red/flowfuse/edge/images/opcua/read-showing-endpoint-field.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/read-showing-endpoint-field.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/read-showing-endpoint-field.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/read-showing-endpoint-field.png diff --git a/src/node-red/flowfuse/edge/images/opcua/use-node-browser-to-select-node-id.png b/nuxt/content-guides/flowfuse-nodes/edge/images/opcua/use-node-browser-to-select-node-id.png similarity index 100% rename from src/node-red/flowfuse/edge/images/opcua/use-node-browser-to-select-node-id.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/opcua/use-node-browser-to-select-node-id.png diff --git a/src/node-red/flowfuse/edge/images/rtsp/rtsp-config-node.png b/nuxt/content-guides/flowfuse-nodes/edge/images/rtsp/rtsp-config-node.png similarity index 100% rename from src/node-red/flowfuse/edge/images/rtsp/rtsp-config-node.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/rtsp/rtsp-config-node.png diff --git a/src/node-red/flowfuse/edge/images/rtsp/rtsp-edge-catalog.png b/nuxt/content-guides/flowfuse-nodes/edge/images/rtsp/rtsp-edge-catalog.png similarity index 100% rename from src/node-red/flowfuse/edge/images/rtsp/rtsp-edge-catalog.png rename to nuxt/content-guides/flowfuse-nodes/edge/images/rtsp/rtsp-edge-catalog.png diff --git a/src/node-red/flowfuse/edge/modbus.md b/nuxt/content-guides/flowfuse-nodes/edge/modbus.md similarity index 93% rename from src/node-red/flowfuse/edge/modbus.md rename to nuxt/content-guides/flowfuse-nodes/edge/modbus.md index fc85dfdfd5..827936c55b 100644 --- a/src/node-red/flowfuse/edge/modbus.md +++ b/nuxt/content-guides/flowfuse-nodes/edge/modbus.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: Modbus - parent: Edge - order: 3 +title: Modbus +metaTitle: "Modbus" +navTitle: Modbus +navOrder: 3 meta: - title: Modbus description: A FlowFuse-certified package for reading and writing coils and registers over Modbus TCP, Modbus UDP (where supported), and Serial (RTU/ASCII), and for simulating a Modbus server, all from within your flows. --- -# {{ meta.title }} +# Modbus Connect a FlowFuse instance to Modbus TCP and Serial devices — PLCs, sensors, meters, drives, and gateways. Poll or read coils and registers, write setpoints and commands, and simulate a Modbus server for testing, all from within your flows. @@ -16,9 +15,11 @@ This is a **FlowFuse Certified Node**. Unlike community nodes, which vary in qua ## Get the Certified Node in FlowFuse -{% note %} +

Note

+ The Modbus package is not available by default. It is part of the FlowFuse Edge Certified Nodes catalogue, which is part of the **FlowFuse Edge** offering. Please contact our sales team at [Contact us](/contact-us/) to learn more or to request access. -{% endnote %} + +
### Installation steps @@ -32,9 +33,11 @@ The Modbus package is not available by default. It is part of the FlowFuse Edge The Modbus nodes then appear in your palette, ready to drag onto the canvas. -{% note %} +

Note

+ If your device or hosted instance was already running before Modbus was enabled for your team, it won't show the package in the Install tab search. Restart the instance or device first, then repeat the steps above. -{% endnote %} + +
## What is Modbus? @@ -46,9 +49,11 @@ Modbus is one of the oldest and most widely supported industrial communication p Modbus-Client also offers TELNET and C701 modes for TCP-to-serial gateways. -{% note %} +

Note

+ Modbus now uses **client** for the device making requests and **server** for the device answering them. Older device manuals and tools use **master** and **slave** for the same two roles — a Modbus master is a client, a Modbus slave is a server. This package follows the newer terminology, so the FlowFuse instance is the client and the PLC, meter, or drive it polls is the server. -{% endnote %} + +
Every Modbus device exposes its data as one of four addressable table types, and every read or write targets one of them: @@ -59,17 +64,21 @@ Every Modbus device exposes its data as one of four addressable table types, and | Holding Registers | 3 | 6, 16 | Read/write, 16-bit | `4xxxx` / `40001+` | Setpoints, configuration, read/write process values | | Input Registers | 4 | — | Read-only, 16-bit | `3xxxx` / `30001+` | Read-only measurements — sensor readings | -{% note %} +

Note

+ **Register addressing** Addresses in this package are raw, zero-based protocol addresses. Device manuals often number the same register differently. A manual that lists a holding register as `40001` (or `4x0001`, or `400001` on devices with more than 9,999 registers) is describing the register at address `0`. Other manuals number within the table starting at `1`, so their "register 1" is also address `0`. Check which convention your device's register map uses before entering an address — an off-by-one here is the most common cause of reading the wrong value, and an address one past the end of the table returns Illegal Data Address. -{% endnote %} + +
A device is also identified by a **Unit ID** (also called station address, or slave ID in older documentation), which matters when several logical devices share one connection — for example several RTU devices on the same RS-485 bus, or several logical devices behind one TCP gateway. On a serial bus, valid device addresses are **1–247**. Address **0 is the broadcast address** — a write sent to unit 0 goes to every device on the bus and none of them reply, so a read addressed to unit 0 will always time out. Addresses 248–255 are reserved. Native Modbus TCP devices often ignore the unit ID altogether (`0`, `1`, and `255` are all common), but it matters as soon as a gateway sits in front of serial devices. -{% note %} +

Note

+ Modbus itself moves only raw bits and 16-bit words — the protocol carries no data-type information. A read returns an **array** of booleans (coils/discrete inputs) or **raw 16-bit register values, each `0`–`65535`,** (input/holding registers). Anything larger or more structured — 32-bit integers, floating-point values, scaled measurements — spans multiple registers: the device packs it across them, and you reconstruct it in your flow. See [Decoding register values](#decoding-register-values) for how to do this. -{% endnote %} + +
## Use case @@ -110,9 +119,11 @@ Each node performs one Modbus operation and reuses the shared connection you con | Modbus-Server | Runs a buffer-backed Modbus TCP server inside Node-RED, so it responds to reads and writes from an external Modbus client — useful for testing and simulation. | | Modbus-Queue-Info | Reports (and can reset) the internal request queue depth for a connection, useful for spotting a device that can't keep up with the configured poll rate. | -{% note %} +

Note

+ All nodes that share the same Modbus-Client connection share one underlying socket/serial port and are queued through it in order, so a device is never sent two requests at once. A busy queue (see Modbus-Queue-Info) usually means the poll rate is faster than the device can respond to. -{% endnote %} + +
## Configure a Modbus Client Connection @@ -129,13 +140,17 @@ Every node in this package uses a **Modbus-Client** configuration node to commun - **Reconnect Timeout** — Default: `2000` ms 5. Click **Done**, then **Deploy** the flow. -{% note %} +

Note

+ The connection is shared. Changing its parameters affects every node that uses it, and you must redeploy the flow for connection changes to take effect. To change the endpoint at runtime without redeploying, see [Modbus-Flex-Connector](#modbus-flex-connector). -{% endnote %} -{% note %} +
+ +

Note

+ Keep host/IP addresses and serial port paths in FlowFuse Environment Variables (your instance's **Settings → Environment**) rather than hard-coding them in the connection, so the same flow can be promoted across instances that reach the device differently. -{% endnote %} + +
## Modbus-Read @@ -144,7 +159,7 @@ The Modbus-Read node polls a fixed location on a repeating interval — the node ### Configuration - **FC (Function Code)** — which table to read: Coils, Discrete Inputs, Holding Registers, or Input Registers (FC 1–4). -- **Address** — the starting zero-based address in that table — see [Register addressing](#what-is-modbus%3F) if your device manual or documentation uses 4xxxx-style numbering. +- **Address** — the starting zero-based address in that table — see [Register addressing](#what-is-modbus) if your device manual or documentation uses 4xxxx-style numbering. - **Quantity** — how many consecutive coils/registers to read in one request. - **Poll rate** — how often to repeat the read (milliseconds). - **Unit ID** — overrides the connection's default unit ID for this node, when needed. @@ -155,9 +170,11 @@ Each poll emits a message carrying the raw values on `msg.payload` (an array of [Modbus-Read] (polls every 1000ms) → [function / buffer-parser] → [Debug] ``` -{% note %} +

Note

+ Set the poll rate no faster than the device can reliably answer. A rate that outpaces the device causes requests to back up in the queue, check [Modbus-Queue-Info](#modbus-queue-info) if reads start arriving late or with gaps. -{% endnote %} + +
## Modbus-Getter and Modbus-Flex-Getter @@ -177,9 +194,11 @@ Modbus-Flex-Sequencer reads several configured ranges in order through a single - **Modbus-Write** — the target table (Coils or Holding Registers), address, and quantity are fixed on the node; the value(s) to write come from the incoming message. Uses write function codes FC 5/6 (single coil/register) and FC 15/16 (multiple). - **Modbus-Flex-Write** — the table, address, and value(s) are all taken from the incoming message, so a single node can write to different coils/registers depending on what triggers it. -{% warning %} +

Warning

+ Writes to production equipment change real-world state. Gate write nodes behind validation or an operator confirmation step before deploying to a live instance. -{% endwarning %} + +
## Modbus-Flex-Fc @@ -213,17 +232,21 @@ There are a few common approaches: - **A buffer-parser node** — a dedicated parsing node (such as `node-red-contrib-buffer-parser`) accepts the values array directly, so you don't have to deal with the Buffer question at all. It lets you declare each field's type and endianness in configuration rather than in code, which is convenient when a single response contains many differently-typed fields. - **The package's built-in IO mapping** — a **Modbus-IO-Config** node holds a JSON file that maps IEC-style addresses (`%QW0`, `%IX8.0`, and so on) to names, where the first character of each name selects the data type (`i` integer, `w` word, `u` unsigned, `b` boolean, `f`/`r` float, and so on). A read node can attach that mapping to its output, and **Modbus-Response-Filter** narrows the result down to the named fields a downstream node needs. This is convenient when your address list originates from a PLC export. -{% note %} +

Note

+ Choose the data type and word order that matches how the device packs its data, which is usually documented in the device's Modbus register map. If a decoded value looks wildly wrong (for example a temperature reading in the millions), the most common cause is a word-order mismatch on a multi-register value. Try the other byte/word order before assuming the device or wiring is at fault. -{% endnote %} + +
## Modbus-Response The Modbus-Response node displays the status and content of a Modbus response in the editor. Placed downstream of a read node, it is an inspection/diagnostic aid that shows what came back, which is helpful while building and debugging a flow. -{% note %} +

Note

+ Modbus-Response is for **display and inspection** — it does not convert raw registers into typed values. To turn raw registers into integers, floats, or scaled measurements, decode them in your flow as described in [Decoding register values](#decoding-register-values). -{% endnote %} + +
## Modbus-Server @@ -234,9 +257,11 @@ This is primarily useful for: - **Testing flows without hardware** — stand up a server so a Modbus-Read/Write flow can be built and verified before real hardware is available. - **Local integration testing** — exercise read and write paths end to end against a server running in the same instance. -{% note %} +

Note

+ The in-package Modbus-Server is a **buffer-backed server for demos and tests**. It is not intended to replicate a specific device's complete register-map behaviour. For more elaborate simulation needs, a dedicated Modbus simulation tool is a better fit. -{% endnote %} + +
## Modbus-Queue-Info diff --git a/src/node-red/flowfuse/edge/opcua.md b/nuxt/content-guides/flowfuse-nodes/edge/opcua.md similarity index 92% rename from src/node-red/flowfuse/edge/opcua.md rename to nuxt/content-guides/flowfuse-nodes/edge/opcua.md index 2f4f67751d..560e527341 100644 --- a/src/node-red/flowfuse/edge/opcua.md +++ b/nuxt/content-guides/flowfuse-nodes/edge/opcua.md @@ -1,13 +1,12 @@ --- -eleventyNavigation: - key: OPC UA - parent: Edge +title: OPC UA for FlowFuse - FlowFuse Certified Node +metaTitle: "OPC UA for FlowFuse - FlowFuse Certified Node" +navTitle: OPC UA +navOrder: 50 meta: - title: "OPC UA for FlowFuse - FlowFuse Certified Node" - description: "Connect a FlowFuse instance to industrial OPC UA servers: read, write, monitor, call methods, browse, read history, work with files, or host your own OPC UA server. A FlowFuse Certified Node." + description: 'Connect a FlowFuse instance to industrial OPC UA servers: read, write, monitor, call methods, browse, read history, work with files, or host your own OPC UA server. A FlowFuse Certified Node.' --- - -# {{meta.title}} +# OPC UA for FlowFuse - FlowFuse Certified Node Connect a FlowFuse instance to industrial OPC UA servers. Read and write values, monitor changes in real time, call methods, browse the address space, read history, work with files, or host your own OPC UA server, all from within your flows. @@ -15,24 +14,24 @@ This is a **FlowFuse Certified Node**. Unlike community nodes, which vary in qua ## Table of contents -1. [Overview](#1.-overview) -2. [Get the certified node in FlowFuse](#2.-get-the-certified-node-in-flowfuse) -3. [The node set](#3.-the-node-set) -4. [NodeIds and how to address data](#4.-nodeids-and-how-to-address-data) -5. [Configure a connection](#5.-configure-a-connection) -6. [Read](#6.-read) -7. [Write](#7.-write) -8. [Extension Object](#8.-extension-object) -9. [Call](#9.-call) -10. [Monitor](#10.-monitor) -11. [Monitor Event](#11.-monitor-event) -12. [Browse](#12.-browse) -13. [Explore](#13.-explore) -14. [History Read](#14.-history-read) -15. [File Operation](#15.-file-operation) -16. [Hosting an OPC UA server](#16.-hosting-an-opc-ua-server) -17. [Network requirements](#17.-network-requirements) -18. [Troubleshooting](#18.-troubleshooting) +1. [Overview](#_1-overview) +2. [Get the certified node in FlowFuse](#_2-get-the-certified-node-in-flowfuse) +3. [The node set](#_3-the-node-set) +4. [NodeIds and how to address data](#_4-nodeids-and-how-to-address-data) +5. [Configure a connection](#_5-configure-a-connection) +6. [Read](#_6-read) +7. [Write](#_7-write) +8. [Extension Object](#_8-extension-object) +9. [Call](#_9-call) +10. [Monitor](#_10-monitor) +11. [Monitor Event](#_11-monitor-event) +12. [Browse](#_12-browse) +13. [Explore](#_13-explore) +14. [History Read](#_14-history-read) +15. [File Operation](#_15-file-operation) +16. [Hosting an OPC UA server](#_16-hosting-an-opc-ua-server) +17. [Network requirements](#_17-network-requirements) +18. [Troubleshooting](#_18-troubleshooting) ## 1. Overview @@ -62,14 +61,14 @@ A filling machine exposes `FillLevel`, `LineSpeed`, and an alarm hierarchy throu #### Where this shows up in practice -Real deployments usually combine several of the nodes on a single shared connection (see [The node set](#3.-the-node-set) for what each one does): +Real deployments usually combine several of the nodes on a single shared connection (see [The node set](#_3-the-node-set) for what each one does): -- **Unified Namespace and broker integration**: subscribe to the variables that matter with [Monitor](#10.-monitor) and publish changes to MQTT, so every system in the plant consumes the same live equipment data instead of integrating with each machine separately. -- **Supervisory control**: fetch current values on demand with [Read](#6.-read), push setpoints and recipe parameters back with [Write](#7.-write), and invoke machine operations such as starting a batch or acknowledging an alarm with [Call](#9.-call). Writes change real-world state, so gate them behind validation or an operator confirmation step. -- **Alarm-driven maintenance**: subscribe to a server's alarms and events with [Monitor Event](#11.-monitor-event), filter by type and severity on the server side, and route what remains to notifications, dashboards, or logs, so the maintenance team hears about a fault from the machine, not from the operator. -- **Shift reports, trends, and audits**: pull raw or server-aggregated values over a time range with [History Read](#14.-history-read) to build end-of-shift reports, quality trends, and incident timelines from data the equipment already recorded. -- **Fast commissioning**: map a machine's entire address space with [Explore](#13.-explore) and feed the result straight into a Monitor node, turning hours of manual tag mapping into a single deploy. -- **Exposing flow data to SCADA and historians**: on self-hosted FlowFuse, [host an OPC UA server](#16.-hosting-an-opc-ua-server) so external OPC UA clients, including another FlowFuse instance, can read the data your flows produce. This is not available on FlowFuse Cloud. +- **Unified Namespace and broker integration**: subscribe to the variables that matter with [Monitor](#_10-monitor) and publish changes to MQTT, so every system in the plant consumes the same live equipment data instead of integrating with each machine separately. +- **Supervisory control**: fetch current values on demand with [Read](#_6-read), push setpoints and recipe parameters back with [Write](#_7-write), and invoke machine operations such as starting a batch or acknowledging an alarm with [Call](#_9-call). Writes change real-world state, so gate them behind validation or an operator confirmation step. +- **Alarm-driven maintenance**: subscribe to a server's alarms and events with [Monitor Event](#_11-monitor-event), filter by type and severity on the server side, and route what remains to notifications, dashboards, or logs, so the maintenance team hears about a fault from the machine, not from the operator. +- **Shift reports, trends, and audits**: pull raw or server-aggregated values over a time range with [History Read](#_14-history-read) to build end-of-shift reports, quality trends, and incident timelines from data the equipment already recorded. +- **Fast commissioning**: map a machine's entire address space with [Explore](#_13-explore) and feed the result straight into a Monitor node, turning hours of manual tag mapping into a single deploy. +- **Exposing flow data to SCADA and historians**: on self-hosted FlowFuse, [host an OPC UA server](#_16-hosting-an-opc-ua-server) so external OPC UA clients, including another FlowFuse instance, can read the data your flows produce. This is not available on FlowFuse Cloud. ## 2. Get the Certified Node in FlowFuse @@ -89,13 +88,17 @@ The OPC UA nodes then appear in your palette, ready to drag onto the canvas. !["The OPC UA nodes in the Node-RED palette"](./images/opcua/node-palette.png "The OPC UA nodes in the Node-RED palette"){data-zoomable} -{% note %} +

Note

+ Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. -{% endnote %} -{% note %} +
+ +

Note

+ Keep endpoint URLs, usernames, and passwords in FlowFuse Environment Variables (your instance's **Settings → Environment**) rather than hard-coding them in nodes. This keeps credentials out of your flow JSON and lets you promote the same flow across instances. -{% endnote %} + +
## 3. The Node Set @@ -115,13 +118,17 @@ Each node performs one OPC UA operation and reuses the shared connection you con | History Read | Retrieve raw, modified, or aggregated historical data over a time range. | | File Operation | Read, write, append, or size files on servers implementing the FileType interface. | -{% note %} +

Note

+ Properties of the input message take precedence over a node's own configuration. Configure a node statically, or drive it dynamically from upstream messages. -{% endnote %} -{% note %} +
+ +

Note

+ Each node reports its state in the editor with a coloured status dot, grey (not connected), blue (operation in progress), green (success), red (failure), and on some nodes (such as History Read) yellow (partial success or quality issues). Watch it alongside the debug sidebar when wiring up a flow. -{% endnote %} + +
## 4. NodeIds and How to Address Data @@ -145,9 +152,11 @@ Clicking the browse (`...`) button opens the live address space, so you can pick !["Browsing the live server address space to select a NodeId"](./images/opcua/use-node-browser-to-select-node-id.png "Browsing the live server address space to select a NodeId"){data-zoomable} -{% warning %} +

Warning

+ Namespace indexes are not guaranteed stable across servers. If you move a flow between servers, prefer browse paths or the namespace alias table over hard-coded numeric namespace indexes. -{% endwarning %} + +
### Browse path syntax @@ -169,9 +178,11 @@ Every browse path starts with a reference-type separator and then a sequence of | BadNoMatch | The full path does not exist on the server, or a namespace index is wrong. | | BadReferenceTypeIdInvalid | A reference type name in the path is unknown or not namespace-qualified. | -{% note %} +

Note

+ Browse paths are convenient and portable, but resolution has a cost. For variables you read or write frequently, resolve the path once and use the returned NodeId, or use the verified-browse-path format that caches the NodeId alongside the path. -{% endnote %} + +
## 5. Configure a Connection @@ -189,9 +200,11 @@ Every OPC UA node depends on a connection defined by the **OPC UA Client** confi 4. Click **Save**, then **Done**. 5. Click **Deploy**, then open the debug sidebar to confirm data flows. -{% note %} +

Note

+ The connection is shared. Changing its parameters affects every node that uses it, and you must redeploy the flow for connection changes to take effect. -{% endnote %} + +
### Connection editor areas @@ -214,19 +227,23 @@ The connection is shared. Changing its parameters affects every node that uses i !["A connection rejected with BadUserAccessDenied"](./images/opcua/endpoint-verification-user-access-denied.png "A connection rejected with BadUserAccessDenied"){data-zoomable} -{% warning %} +

Warning

+ A username and password sent over message security mode `None` travel in cleartext. Combine credentials with at least `Sign` mode. -{% endwarning %} + +
### Certificate trust Secure connections rely on mutual certificate trust. By default the client side automatically accepts the server's certificate, so the step that usually needs action is the reverse: **the server must trust the client's certificate**. For a `Sign` or `SignAndEncrypt` connection, download the client certificate from the connection editor (in PEM or DER format) and add it to your OPC UA server's trusted list, otherwise the server refuses the connection. -{% note %} +

Note

+ Auto-accepting the server certificate is convenient but means the client does not verify the server's identity. To enforce server-certificate validation, set `rejectUnauthorized` to `true` in the connection node's global settings. The client then refuses any server whose certificate is not already in its trusted store, so you must add the server's certificate to the client side first. -{% endnote %} -Client and server nodes in your instance share one PKI store. On FlowFuse it lives under `/opcua-for-flow-fuse/PKI`. For trust decisions to survive restarts and redeploys, that directory must be on persistent storage, see [Hosting an OPC UA server](#16.-hosting-an-opc-ua-server) for the storage details, which apply to client connections too. +
+ +Client and server nodes in your instance share one PKI store. On FlowFuse it lives under `/opcua-for-flow-fuse/PKI`. For trust decisions to survive restarts and redeploys, that directory must be on persistent storage, see [Hosting an OPC UA server](#_16-hosting-an-opc-ua-server) for the storage details, which apply to client connections too. If the client certificate is not yet trusted by the server, **Check Connection** reports the handshake failure and reminds you to add the client certificate to the server's trusted list: @@ -249,9 +266,11 @@ The Read node fetches data on demand, it acts only when it receives an input mes 3. `msg.payload` 4. The node's configured NodeId -{% warning %} +

Warning

+ A default Inject node sets `msg.payload` to a timestamp, which silently overrides the node's configured NodeId (priority rule 3). To use the node's own configuration, ensure the Inject node does **not** set `msg.nodeId`, `msg.topic`, or `msg.payload`. -{% endwarning %} + +
### Message properties @@ -372,9 +391,11 @@ A Write returns the original `payload` and `nodeId` plus the result: `statusCode | BadNotWritable | Variable is read-only. | | BadConnectionClosed | Connection was lost during the write. | -{% warning %} +

Warning

+ Writes to production equipment change real-world state. Gate write nodes behind validation or an operator confirmation step before deploying to a live instance. -{% endwarning %} + +
## 8. Extension Object @@ -452,9 +473,11 @@ The Monitor node subscribes to variables and emits a message whenever a value ch - **Notification behaviour**, a monitored item notifies only when the value actually changes (per the OPC UA spec), and once at monitoring start: the initial value is always reported. - **Resilience**, the Monitor node reconnects automatically after a connection loss and re-establishes its subscription and monitored items, resuming without manual intervention. -{% note %} +

Note

+ Each notification carries `msg.sequenceNumber`. Watch it for gaps to detect dropped notifications, for example when a server-side queue overflows under a heavy change rate. -{% endnote %} + +
### NodeId source (priority order) @@ -514,9 +537,11 @@ Example, stop monitoring: msg.payload = []; ``` -{% note %} +

Note

+ Injecting an empty array releases the monitored items but leaves the now-empty subscription in place on the connection, so it is the right way to pause and resume a stream. To tear the session down completely, disconnect the connection instead. -{% endnote %} + +
### Deadband filtering @@ -528,9 +553,11 @@ Injecting an empty array releases the monitored items but leaves the now-empty s | Absolute | Report only when `abs(new − lastReported) > value`. | | Percent | Report only when the change exceeds a percentage of the variable's EURange. Requires the variable to expose an EURange property. | -{% note %} +

Note

+ Deadband compares against the last reported value, not the last sampled value. The initial value is always reported. Use Absolute when a variable has no EURange, Percent silently does nothing without one. -{% endnote %} + +
## 11. Monitor Event @@ -541,7 +568,7 @@ The Monitor Event node subscribes to events and alarms rather than value changes - **Where Clause**, a server-side filter selecting which events to receive. Far more efficient than post-filtering in a Function node. - **Select Clause**: comma-separated fields to retrieve (e.g. `EventId, Time, Message, Severity`). The `...` button opens a graphical selector that browses the event type hierarchy. Request only the fields you use: smaller messages, less processing. Common sets: basic `EventId,Time,Message,Severity`; alarms add `SourceName,ActiveState,AckedState`; audits add `ActionTimeStamp,ClientUserId`. -Match the subscription to the event rate: a slower publishing interval suits alarms and audits, a faster one suits high-frequency process events. For mixed workloads, use separate Monitor Event nodes on different subscriptions (see [Configure a connection](#5.-configure-a-connection)) so critical alarms don't queue behind noisy low-priority events. +Match the subscription to the event rate: a slower publishing interval suits alarms and audits, a faster one suits high-frequency process events. For mixed workloads, use separate Monitor Event nodes on different subscriptions (see [Configure a connection](#_5-configure-a-connection)) so critical alarms don't queue behind noisy low-priority events. ### Where Clause syntax @@ -569,9 +596,11 @@ Where Clause: (empty) Select Clause: EventId,EventType,SourceName,Time,Message,Severity ``` -{% note %} +

Note

+ If no events arrive, verify the server supports events (read the object's `EventNotifier` attribute, a non-zero value means it emits events), point the NodeId at an event-generating object (try `i=2253`), and clear the Where Clause. If too many arrive, tighten the Where Clause by type and severity, or point at specific equipment instead of the Server object. -{% endnote %} + +
## 12. Browse @@ -637,9 +666,11 @@ The output type sets what each leaf variable returns: - **excludeEmpty**, set `msg.excludeEmpty = true` to drop branches that contain no variables, for cleaner output. - **Depth**, Explore follows the full hierarchy under the start node (default maximum depth 10). Rather than a depth setting, control scope by choosing a more specific start NodeId: roughly 2–3 levels for one piece of equipment, 4–6 for a production line, 7–10 for a whole plant. -{% warning %} +

Warning

+ Starting Explore at the Objects folder (`ns=0;i=85`) or another root node traverses the entire server and can return a very large structure. Start from a specific NodeId (or `/Server/ServerStatus` if you are just getting your bearings) and expand scope deliberately. -{% endwarning %} + +
Example, exploring `/Server/ServerStatus` with output type `NodeId` returns a nested object whose leaves are the NodeIds of each variable: @@ -663,9 +694,11 @@ Explore a subtree once to discover all its variables, then feed that structure s [Inject] → [Explore] → [Monitor] → [Debug] ``` -{% note %} +

Note

+ Address spaces rarely change, so cache the Explore result (with a timestamp) and reuse it rather than re-exploring on every deploy. Re-explore periodically only if your server adds or removes variables at runtime. -{% endnote %} + +
## 14. History Read @@ -717,9 +750,11 @@ msg.startTime = "2024-11-20T08:00:00Z"; msg.endTime = "2024-11-20T17:00:00Z"; / | BadTimestampsToReturnInvalid | Invalid timestamp specification. | | BadMaxAgeInvalid | Invalid time range specified. | -{% note %} +

Note

+ Keep time ranges and `numValuesPerNode` bounded, prefer aggregation to thin large ranges for charts, and verify the server supports history before relying on it. For very long ranges, chunk the request into smaller windows. -{% endnote %} + +
## 15. File Operation @@ -764,23 +799,27 @@ msg.payload = "appended log line\n"; // WriteAppend mode | BadNodeIdUnknown | File not found, for WriteAppend, create it first with Write. | | BadNotWritable / BadUserAccessDenied | File is read-only or your account lacks permission. | -{% note %} +

Note

+ Use the Browse node to discover available File objects, and the Read node to inspect file metadata such as `Size`, `OpenCount`, and `UserWritable`. Garbled text usually means the wrong encoding, try a different one. -{% endnote %} + +
## 16. Hosting an OPC UA Server On **self-hosted FlowFuse**, the certified node can run an OPC UA server inside your instance using only Function nodes, no `settings.js` edit, no external module declaration, no extra npm install. -{% caution %} +

Caution

Server hosting is **not supported on FlowFuse Cloud**, Cloud exposes HTTP/HTTPS only and cannot expose the arbitrary TCP port (`opc.tcp://`) a server needs. Use a self-hosted FlowFuse instance and ensure the chosen port is reachable through your container and network configuration. -{% endcaution %} +
When the palette loads, it publishes a bootstrap helper in the Node-RED global context. Retrieve it and destructure `{ bootstrap, opcua }`: `bootstrap` carries the server helpers and `opcua` re-exports the full `node-opcua` namespace. This works even with `functionExternalModules: false`. -{% note %} +

Note

+ `global.get("sterfive")` below is the literal runtime key the certified node exposes. Keep it exactly as written in your Function nodes, or the code will not find the helper. -{% endnote %} + +
### Basic pattern @@ -826,9 +865,11 @@ handle.exposed.temperature.setValueFromSource({ }); ``` -{% note %} +

Note

+ Keep the handle in flow context, never in a local `const`/`let`, a Function node's body re-runs on every message. Prefix context keys with `$` (e.g. `$opcuaHandle`) and avoid the bare key `opcua` (it collides with other vendors), dotted keys (Node-RED reads them as nested paths), and colon-separated keys. Put construction-time options (`port`, `nodesets`, security, `users`) only in the Boot node, never in the Update node. -{% endnote %} + +
### Key `bootstrapServer` options @@ -848,9 +889,11 @@ Keep the handle in flow context, never in a local `const`/`let`, a Function node | `onPopulate` | Callback run once when a new server is built, add your variables, objects, and methods here. | | `forceRebuild` | Set `true` to rebuild without a config change (e.g. after editing the trust store). | -{% note %} +

Note

+ The server's identity is a config hash of `port`, `endpoint`, `applicationName`, `productUri`, `nodesets`, `securityPolicies`, `securityModes`, `allowAnonymous`, and `users`. Change any of these and the next deploy rebuilds the server (re-running `onPopulate`). `onPopulate` and `forceRebuild` are excluded from the hash, so editing `onPopulate` alone does not trigger a rebuild, use `forceRebuild: true` if you need one. -{% endnote %} + +
### Adding methods @@ -940,9 +983,11 @@ Beyond roughly five servers per instance, prefer separate processes; the event l Authentication is declarative through the `users` array. Each entry has a username, a password, and roles. The helper bcrypt-hashes clear-text passwords at boot and maps role names to their NodeIds; a value already in bcrypt form (`$2a$`/`$2b$`/`$2y$` prefix) is passed through verbatim. The `users` array controls only session activation, per-node authorization comes from each variable's access-level attributes combined with role mapping. Set `allowAnonymous: false` to refuse anonymous sessions. The helpers `bootstrap.ensureBcryptHash(plain)` and `bootstrap.isBcryptHash(hash)` are available for tooling. -{% warning %} +

Warning

+ If you omit `users`, the helper installs a default test set (`root/secret`, `gdsadmin/admingds`, `user1/password1`, `user2/password2`) intended only for development. Always set your own `users` for any instance reachable beyond your development machine. -{% endwarning %} + +
### Security, certificates, and PKI storage @@ -950,9 +995,11 @@ Enable secure endpoints with `securityPolicies` and `securityModes`; the server On FlowFuse, the node stores its PKI (its own certificate, the trusted list, and the rejected list) under `/opcua-for-flow-fuse/PKI`. For this to survive restarts and redeploys, that directory must be on persistent storage: on container-based FlowFuse (Cloud, Kubernetes, Docker) this is the persistent volume mounted at `/data/storage`; on the FlowFuse Device Agent the working directory is on the device's local filesystem. If you re-trust a server's certificate after every deploy, your PKI directory is not landing on persistent storage. -{% note %} +

Note

+ Username/password over `MessageSecurityMode.None` travels in cleartext. Combine credentials with at least Sign mode. Disabling `SecurityPolicy.None` entirely can break naive clients that probe without security first. -{% endnote %} + +
### Lifecycle, diagnostics, and stopping @@ -1000,4 +1047,4 @@ The OPC UA port (default 4840) is configurable per server; update the rule if yo | Cannot host a server on FlowFuse Cloud | Server hosting requires a self-hosted FlowFuse instance with the OPC UA TCP port exposed. Cloud exposes HTTP/HTTPS only. | | Re-trusting the server certificate after every deploy | The PKI directory (`/opcua-for-flow-fuse/PKI`) is not on persistent storage. On container-based FlowFuse it must sit under the `/data/storage` persistent volume. Also confirm the certificate is in `trusted`, not `rejected`. | -For help enabling the OPC UA Certified Node, licensing, or anything else, [contact FlowFuse](/contact-us/). \ No newline at end of file +For help enabling the OPC UA Certified Node, licensing, or anything else, [contact FlowFuse](/contact-us/). diff --git a/src/node-red/flowfuse/edge/rtsp.md b/nuxt/content-guides/flowfuse-nodes/edge/rtsp.md similarity index 83% rename from src/node-red/flowfuse/edge/rtsp.md rename to nuxt/content-guides/flowfuse-nodes/edge/rtsp.md index 63be992d27..f8ddd50e66 100644 --- a/src/node-red/flowfuse/edge/rtsp.md +++ b/nuxt/content-guides/flowfuse-nodes/edge/rtsp.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: RTSP Video Feed - parent: Edge - order: 1 +title: RTSP Video Feed +metaTitle: "RTSP Video Feed" +navTitle: RTSP Video Feed +navOrder: 1 meta: - title: RTSP Video Feed description: Documentation for the FlowFuse RTSP Video Feed node, which connects to an RTSP camera stream and extracts still frames as PNG images for use in flows, dashboards, and local AI models. --- -# {{ meta.title }} +# RTSP Video Feed The **RTSP Video Feed** node connects to an [RTSP](https://en.wikipedia.org/wiki/Real-Time_Streaming_Protocol) video stream from an IP camera or NVR and extracts still frames as PNG images. @@ -18,9 +17,11 @@ The node orchestrates `ffmpeg` to acquire and decode the video stream. By handli Extracted frames can either be emitted as messages into the flow or written directly to disk as a numbered sequence of PNG files. See [Operating modes](#operating-modes) for details. -{% note %} +

Note

+ The RTSP Video Feed node is not available by default. It is part of the FlowFuse Edge Certified Nodes catalogue, which is part of the **FlowFuse Edge** offering. Please contact our sales team at [Contact us](/contact-us/) to learn more or to request access. -{% endnote %} + +
## Use case @@ -28,13 +29,13 @@ Most plants and facilities already have IP cameras on the network, but the foota ### Example: catching mislabeled products on a packaging line -A camera above a packaging line already streams to the site NVR. Point the RTSP Video Feed node at that same stream, set it to 1 FPS, and wire it to a [FlowFuse AI node](/node-red/flowfuse/ai/) running an image classification model. Each frame is checked for a missing or misprinted label, and when one is detected, the flow raises a dashboard alert and publishes the offending frame over MQTT for the quality team to review. What used to require an operator glancing at a screen, or a dedicated vision system with its own controller and license, becomes a three-node flow on hardware you already own. +A camera above a packaging line already streams to the site NVR. Point the RTSP Video Feed node at that same stream, set it to 1 FPS, and wire it to a [FlowFuse AI node](/docs/flowfuse-nodes/ai/) running an image classification model. Each frame is checked for a missing or misprinted label, and when one is detected, the flow raises a dashboard alert and publishes the offending frame over MQTT for the quality team to review. What used to require an operator glancing at a screen, or a dedicated vision system with its own controller and license, becomes a three-node flow on hardware you already own. ### Where this shows up in practice -- **Machine vision**: feed frames to a [FlowFuse AI node](/node-red/flowfuse/ai/) for object detection or image classification, then act on the result, for example counting parts on a line, checking a fill level, or verifying a safety gate is closed. +- **Machine vision**: feed frames to a [FlowFuse AI node](/docs/flowfuse-nodes/ai/) for object detection or image classification, then act on the result, for example counting parts on a line, checking a fill level, or verifying a safety gate is closed. - **Remote monitoring**: display the frames live on a [FlowFuse Dashboard](/platform/dashboard/) so an operator can watch a machine or area from anywhere the dashboard is reachable, alongside the process data on the same page. -- **Event snapshots**: route frames through a `function` or `switch` node and keep only the ones that matter, for example the frame captured the moment a PLC tag or sensor reports a fault, and forward it with an [`mqtt out`](/node-red/flowfuse/mqtt/mqtt-out/) node or attach it to an alert. +- **Event snapshots**: route frames through a `function` or `switch` node and keep only the ones that matter, for example the frame captured the moment a PLC tag or sensor reports a fault, and forward it with an [`mqtt out`](/docs/flowfuse-nodes/mqtt/mqtt-out/) node or attach it to an alert. - **Timelapse and archiving**: switch to disk-writing mode and the node writes a numbered sequence of PNG files for later review or timelapse assembly, without adding any messages to the flow. ### Combining video with other plant data @@ -61,9 +62,11 @@ If a prebuilt binary is not available for your platform, the node falls back to ![Palette Manager open on the Install tab with the FlowFuse Edge Certified Nodes collection visible and the RTSP node's Install button highlighted](./images/rtsp/rtsp-edge-catalog.png) *Locating and installing the RTSP Video Feed node from the FlowFuse Edge Certified Nodes catalogue.* -{% note %} +

Note

+ Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. -{% endnote %} + +
## Configuration @@ -98,11 +101,13 @@ The node emits each captured frame as a message at the configured FPS rate. | `msg.payload` | Buffer | The captured frame as a PNG image buffer. | | `msg.topic` | String | The topic configured on the node. | -The output can be wired to any node that accepts an image buffer, including [FlowFuse Dashboard widgets](/platform/dashboard/), [MQTT out nodes](/node-red/flowfuse/mqtt/mqtt-out/), and [FlowFuse AI nodes](/node-red/flowfuse/ai/). +The output can be wired to any node that accepts an image buffer, including [FlowFuse Dashboard widgets](/platform/dashboard/), [MQTT out nodes](/docs/flowfuse-nodes/mqtt/mqtt-out/), and [FlowFuse AI nodes](/docs/flowfuse-nodes/ai/). + +

Note

-{% note %} Every captured frame becomes a message in the flow. A high FPS value increases the number and size of messages being processed. Set FPS no higher than your use case requires. -{% endnote %} + +
### Output disabled @@ -114,4 +119,4 @@ rtsp--.png If **File path** is left empty, files are written to the OS temp directory (e.g. `/tmp`). On many Linux distributions this is a RAM-backed filesystem, so frames consume memory rather than disk space and are cleared on reboot. -The node does not delete files written to disk. At a high FPS rate, files will accumulate and eventually fill the available storage. Monitor available disk space when using this mode. \ No newline at end of file +The node does not delete files written to disk. At a high FPS rate, files will accumulate and eventually fill the available storage. Monitor available disk space when using this mode. diff --git a/src/node-red/flowfuse/flowfuse-tables/index.md b/nuxt/content-guides/flowfuse-nodes/flowfuse-tables/README.md similarity index 60% rename from src/node-red/flowfuse/flowfuse-tables/index.md rename to nuxt/content-guides/flowfuse-nodes/flowfuse-tables/README.md index b60b760931..ab9ecc92a2 100644 --- a/src/node-red/flowfuse/flowfuse-tables/index.md +++ b/nuxt/content-guides/flowfuse-nodes/flowfuse-tables/README.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: FlowFuse Tables - parent: FlowFuse Nodes - order: 3 +title: FlowFuse Tables +metaTitle: "FlowFuse Tables" +navTitle: FlowFuse Tables +navOrder: 3 meta: - title: FlowFuse Tables description: FlowFuse Tables provides managed databases for Node-RED users, offering built-in nodes to query, insert, and manage data easily within FlowFuse flows. --- -# {{ meta.title }} +# FlowFuse Tables **FlowFuse Tables** provides managed databases that can be directly accessed from Node-RED flows on the FlowFuse platform. It simplifies data management and integration by offering purpose-built nodes that allow you to query, insert, and modify data without complex setup. @@ -17,4 +16,4 @@ It simplifies data management and integration by offering purpose-built nodes th The following documents describe the nodes available under FlowFuse Tables: -{% include "navigation-items-list.njk" %} +- [Query](/docs/flowfuse-nodes/flowfuse-tables/query/): The Query node allows you to run SQL queries against FlowFuse Tables, supporting parameterized queries, Mustache templates, and AI-assisted query generation for seamless database interactions. diff --git a/src/node-red/flowfuse/flowfuse-tables/query.md b/nuxt/content-guides/flowfuse-nodes/flowfuse-tables/query.md similarity index 97% rename from src/node-red/flowfuse/flowfuse-tables/query.md rename to nuxt/content-guides/flowfuse-nodes/flowfuse-tables/query.md index 562262de3e..602a123371 100644 --- a/src/node-red/flowfuse/flowfuse-tables/query.md +++ b/nuxt/content-guides/flowfuse-nodes/flowfuse-tables/query.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: Query - parent: FlowFuse Tables +title: Query +metaTitle: "Query" +navTitle: Query +navOrder: 50 meta: - title: Query description: The Query node allows you to run SQL queries against FlowFuse Tables, supporting parameterized queries, Mustache templates, and AI-assisted query generation for seamless database interactions. --- -# {{ meta.title }} +# Query The Query node allows you to write and run queries against database tables managed by [FlowFuse Tables](/docs/user/ff-tables/). The node is pre-configured to connect automatically when used within a FlowFuse Node-RED instance. @@ -33,18 +33,14 @@ SQL queries can be configured directly in the node or passed dynamically via `ms Pass parameters as an array via `msg.params`: ##### Input Data -{% raw %} ```javascript msg.params = [ msg.id ]; ``` -{% endraw %} ##### Query defined in the node -{% raw %} ```sql SELECT * FROM table WHERE id = $1 ``` -{% endraw %} > Tip: For production environments, it is recommended to use parameterized queries instead. Parameterized queries automatically handle quoting and escaping, making them safer and more reliable. @@ -53,30 +49,24 @@ SELECT * FROM table WHERE id = $1 Pass parameters as an object via `msg.queryParameters`: ##### Input Data -{% raw %} ```javascript msg.queryParameters.id = msg.id; ``` -{% endraw %} ##### Query defined in the node -{% raw %} ```sql SELECT * FROM table WHERE id = $id; ``` -{% endraw %} ### Mustache Templates Reference message properties using Mustache syntax: ##### Query defined in the node -{% raw %} ```sql SELECT * FROM table WHERE id = {{{ msg.id }}} SELECT * FROM table WHERE name = '{{{ msg.name }}}' ``` -{% endraw %} > Note: Care must be taken to ensure incoming string data is properly escaped (e.g., single quotes must be doubled: `'` to `''`) to prevent syntax errors and SQL injection. @@ -111,11 +101,11 @@ FlowFuse Tables requires **Enterprise tier** and must be enabled for your team. ## Example Flow -{% renderFlow 500 %} -{% raw %} +::render-flow{:height="500"} +```json [{"id":"9cd7498d4f832f1e","type":"group","z":"9db2d9ed7f00b8af","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["f27702ab8c8a58d2","da6e293fa363b172","f74ede3fbcf4ef32","8bfb39f252c16a79","41e1f9e0e97768a8"],"x":34,"y":39,"w":892,"h":142},{"id":"f27702ab8c8a58d2","type":"tables-query","z":"9db2d9ed7f00b8af","g":"9cd7498d4f832f1e","name":"","query":"","split":false,"rowsPerMsg":1,"x":670,"y":140,"wires":[["8bfb39f252c16a79"]]},{"id":"da6e293fa363b172","type":"inject","z":"9db2d9ed7f00b8af","g":"9cd7498d4f832f1e","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":140,"wires":[["f74ede3fbcf4ef32"]]},{"id":"f74ede3fbcf4ef32","type":"template","z":"9db2d9ed7f00b8af","g":"9cd7498d4f832f1e","name":"Set Query","field":"query","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"CREATE TABLE \"sensor_data\" (\n \"id\" SERIAL PRIMARY KEY,\n \"sensor_id\" TEXT NOT NULL,\n \"timestamp\" TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n \"temperature\" REAL,\n \"unit\" TEXT DEFAULT 'celsius'\n);\n","output":"str","x":300,"y":140,"wires":[["f27702ab8c8a58d2"]]},{"id":"8bfb39f252c16a79","type":"debug","z":"9db2d9ed7f00b8af","g":"9cd7498d4f832f1e","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":830,"y":140,"wires":[]},{"id":"41e1f9e0e97768a8","type":"comment","z":"9db2d9ed7f00b8af","g":"9cd7498d4f832f1e","name":"Pass the query via msg.query","info":"","x":180,"y":80,"wires":[]},{"id":"91fa219afc78e395","type":"group","z":"9db2d9ed7f00b8af","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["aefa193bfd04e5c2","e5bd3fbf9af5e519","6cc5a12665df32cb","1ebe4f1381fe71dd","7a397cd61dd6a210"],"x":34,"y":519,"w":892,"h":142},{"id":"aefa193bfd04e5c2","type":"tables-query","z":"9db2d9ed7f00b8af","g":"91fa219afc78e395","name":"","query":"SELECT * FROM public.sensor_readings WHERE \"temperature\" > {{{msg.temperatureThreshold}}};","split":false,"rowsPerMsg":1,"x":670,"y":620,"wires":[["6cc5a12665df32cb"]]},{"id":"e5bd3fbf9af5e519","type":"inject","z":"9db2d9ed7f00b8af","g":"91fa219afc78e395","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":620,"wires":[["7a397cd61dd6a210"]]},{"id":"6cc5a12665df32cb","type":"debug","z":"9db2d9ed7f00b8af","g":"91fa219afc78e395","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":830,"y":620,"wires":[]},{"id":"1ebe4f1381fe71dd","type":"comment","z":"9db2d9ed7f00b8af","g":"91fa219afc78e395","name":"Using Mustache template","info":"","x":170,"y":560,"wires":[]},{"id":"7a397cd61dd6a210","type":"change","z":"9db2d9ed7f00b8af","g":"91fa219afc78e395","name":"Set temperatureThreshold","rules":[{"t":"set","p":"temperatureThreshold","pt":"msg","to":"20","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":350,"y":620,"wires":[["aefa193bfd04e5c2"]]},{"id":"e00a02a11a322683","type":"group","z":"9db2d9ed7f00b8af","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["14d47d8931a2855b","58a32e0340ed268d","762caff19e1722b7","997d92441f4d2eeb","ae3e4a6940a8e236"],"x":34,"y":359,"w":892,"h":142},{"id":"14d47d8931a2855b","type":"tables-query","z":"9db2d9ed7f00b8af","g":"e00a02a11a322683","name":"","query":"DELETE FROM \"sensor_data\"\nWHERE \"id\" = $id;\n","split":false,"rowsPerMsg":1,"x":670,"y":460,"wires":[["762caff19e1722b7"]]},{"id":"58a32e0340ed268d","type":"inject","z":"9db2d9ed7f00b8af","g":"e00a02a11a322683","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":460,"wires":[["ae3e4a6940a8e236"]]},{"id":"762caff19e1722b7","type":"debug","z":"9db2d9ed7f00b8af","g":"e00a02a11a322683","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":830,"y":460,"wires":[]},{"id":"997d92441f4d2eeb","type":"comment","z":"9db2d9ed7f00b8af","g":"e00a02a11a322683","name":"Named parameterized query","info":"","x":180,"y":400,"wires":[]},{"id":"ae3e4a6940a8e236","type":"change","z":"9db2d9ed7f00b8af","g":"e00a02a11a322683","name":"Set queryParameters","rules":[{"t":"set","p":"queryParameters","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"queryParameters.id","pt":"msg","to":"3","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":340,"y":460,"wires":[["14d47d8931a2855b"]]},{"id":"fd2f3651994d44d3","type":"group","z":"9db2d9ed7f00b8af","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["423532459421ddb0","f14ffc1ff606f377","28579ce56dbb5dab","35f9bc3ff82a3789","68d530295082581a","2cb15cbf3a1a3878"],"x":34,"y":199,"w":892,"h":142},{"id":"423532459421ddb0","type":"tables-query","z":"9db2d9ed7f00b8af","g":"fd2f3651994d44d3","name":"","query":"INSERT INTO \"sensor_data\" (\"sensor_id\", \"temperature\", \"unit\")\nVALUES ($1, $2, $3);\n","split":false,"rowsPerMsg":1,"x":670,"y":300,"wires":[["28579ce56dbb5dab"]]},{"id":"f14ffc1ff606f377","type":"inject","z":"9db2d9ed7f00b8af","g":"fd2f3651994d44d3","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":300,"wires":[["68d530295082581a"]]},{"id":"28579ce56dbb5dab","type":"debug","z":"9db2d9ed7f00b8af","g":"fd2f3651994d44d3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":830,"y":300,"wires":[]},{"id":"35f9bc3ff82a3789","type":"comment","z":"9db2d9ed7f00b8af","g":"fd2f3651994d44d3","name":"Numeric parameterized query","info":"","x":180,"y":240,"wires":[]},{"id":"68d530295082581a","type":"function","z":"9db2d9ed7f00b8af","g":"fd2f3651994d44d3","name":"Simulate Sensor","func":"// Simulate sensor data and output as an object in msg.payload\n\nlet temperature = (20 + Math.random() * 10).toFixed(2);\nlet sensorIds = [\"sensor_01\", \"sensor_02\", \"sensor_03\"];\nlet sensor_id = sensorIds[Math.floor(Math.random() * sensorIds.length)];\n\nmsg.payload = {\n sensor_id: sensor_id,\n temperature: Number(temperature),\n unit: \"celsius\",\n};\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":320,"y":300,"wires":[["2cb15cbf3a1a3878"]]},{"id":"2cb15cbf3a1a3878","type":"change","z":"9db2d9ed7f00b8af","g":"fd2f3651994d44d3","name":"Set Params","rules":[{"t":"set","p":"params","pt":"msg","to":"[msg.payload.sensor_id, msg.payload.temperature, msg.payload.unit]","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":300,"wires":[["423532459421ddb0"]]},{"id":"bf517ea3d503dd5a","type":"global-config","env":[],"modules":{"@flowfuse/nr-tables-nodes":"0.1.0"}}] -{% endraw %} -{% endrenderFlow %} +``` +:: ## Generate Queries with FlowFuse Expert diff --git a/src/node-red/flowfuse/hub/index.md b/nuxt/content-guides/flowfuse-nodes/hub/README.md similarity index 55% rename from src/node-red/flowfuse/hub/index.md rename to nuxt/content-guides/flowfuse-nodes/hub/README.md index c2d6d5980d..765c51767d 100644 --- a/src/node-red/flowfuse/hub/index.md +++ b/nuxt/content-guides/flowfuse-nodes/hub/README.md @@ -1,34 +1,38 @@ --- -eleventyNavigation: - key: Hub - parent: FlowFuse Nodes - order: 5 - title: Hub +title: Hub Certified Nodes +metaTitle: "Hub Certified Nodes" +navTitle: Hub +navOrder: 5 meta: - title: Hub Certified Nodes - description: Documentation for FlowFuse Hub Certified Nodes, including nodes for connecting FlowFuse to IT systems, databases, cloud services, APIs, and enterprise applications. + description: Documentation for FlowFuse Hub Certified Nodes, including nodes for connecting FlowFuse to IT systems, databases, cloud services, APIs, and enterprise applications. --- -# {{ meta.title }} +# Hub Certified Nodes This section contains documentation for **FlowFuse Hub Certified Nodes** that connect your FlowFuse instances to IT systems, databases, cloud services, APIs, and enterprise applications. FlowFuse Certified Nodes are packages that FlowFuse has vetted for quality, security, and support, and maintains on an ongoing basis. To learn more about what certification means and how these nodes are delivered, [read the FlowFuse Certified Nodes blog post](/blog/2025/07/certified-nodes-v2/). -{% note %} +

Note

+ The FlowFuse Hub Certified Nodes catalogue is part of the **FlowFuse Hub** offering. [Contact us](/contact-us/) to get access or to learn more. -{% endnote %} -{% note %} +
+ +

Note

+ This section is expanding. We are actively working to bring more Hub Certified Nodes to FlowFuse, and additional documentation will be added here over time. -{% endnote %} + +
## Nodes This section lists the **Hub Certified Nodes** documented in FlowFuse: -{% include "navigation-items-list.njk" %} +- [Redis](/docs/flowfuse-nodes/hub/redis/): A FlowFuse-certified package that lets you connect to Redis, store and retrieve data, publish and subscribe to messages, execute commands, and integrate Redis into your flows. + +

Note

-{% note %} Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. -{% endnote %} \ No newline at end of file + +
diff --git a/src/node-red/flowfuse/hub/redis.md b/nuxt/content-guides/flowfuse-nodes/hub/redis.md similarity index 98% rename from src/node-red/flowfuse/hub/redis.md rename to nuxt/content-guides/flowfuse-nodes/hub/redis.md index edf32babd9..d7cce99785 100644 --- a/src/node-red/flowfuse/hub/redis.md +++ b/nuxt/content-guides/flowfuse-nodes/hub/redis.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: Redis - parent: Hub - order: 1 +title: Redis +metaTitle: "Redis" +navTitle: Redis +navOrder: 1 meta: - title: Redis - description: A FlowFuse-certified package that lets you connect to Redis, store and retrieve data, publish and subscribe to messages, execute commands, and integrate Redis into your flows. + description: A FlowFuse-certified package that lets you connect to Redis, store and retrieve data, publish and subscribe to messages, execute commands, and integrate Redis into your flows. --- -# {{ meta.title }} +# Redis A FlowFuse-certified package that lets you connect to Redis, store and retrieve data, publish and subscribe to messages, execute commands, and integrate Redis into your flows. @@ -24,9 +23,11 @@ Built on [ioredis](https://github.com/luin/ioredis), so anything ioredis support - Connections are pooled per config node, one connection is reused across every node pointed at the same server config, unless a node explicitly requests a dedicated (blocking) connection - TLS and Redis Cluster support -{% note %} +

Note

+ The Redis node is not available by default. It is part of the FlowFuse Hub Certified Nodes catalogue, which is part of the **FlowFuse Hub** offering. Please contact our sales team at [Contact us](/contact-us/) to learn more or to request access. -{% endnote %} + +
## Use case @@ -47,9 +48,11 @@ Connections are pooled per config node, so pointing many nodes at the same serve 3. Find the **FlowFuse Hub Certified Nodes** collection. 4. Locate `@flowfuse-certified-nodes/redis` and click **Install**. -{% note %} +

Note

+ Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. -{% endnote %} + +
## Nodes in this package @@ -230,6 +233,8 @@ Custom/module commands (e.g. RedisJSON, RediSearch) can also be issued via `redi ## Sample flow -{% renderFlow 300 %} +::render-flow{:height="300"} +```json [{"id":"2c33ebeb73062ab3","type":"group","z":"FFF0000000000001","name":"Your First Redis Flow","style":{"label":true},"nodes":["e5f3326ef736cef6","8486e88d8623b42a","d1ebde22d0e224a8","22db25c60af16ab2","fe2be532c53402b3","ebc1e8fc0f1ba2d6","adb95c63d206c633"],"x":414,"y":1219,"w":712,"h":162},{"id":"e5f3326ef736cef6","type":"redis-command","z":"FFF0000000000001","g":"2c33ebeb73062ab3","server":"e370dc92b39a7ba4","command":"SET","name":"","topic":"mykey","params":"[\"Hello from Node-RED\"]","paramsType":"json","payloadType":"json","block":false,"x":710,"y":1260,"wires":[["8486e88d8623b42a"]]},{"id":"8486e88d8623b42a","type":"debug","z":"FFF0000000000001","g":"2c33ebeb73062ab3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":870,"y":1260,"wires":[]},{"id":"d1ebde22d0e224a8","type":"redis-command","z":"FFF0000000000001","g":"2c33ebeb73062ab3","server":"e370dc92b39a7ba4","command":"GET","name":"","topic":"","params":"[]","paramsType":"json","payloadType":"json","block":false,"x":900,"y":1340,"wires":[["22db25c60af16ab2"]]},{"id":"22db25c60af16ab2","type":"debug","z":"FFF0000000000001","g":"2c33ebeb73062ab3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1030,"y":1340,"wires":[]},{"id":"fe2be532c53402b3","type":"change","z":"FFF0000000000001","g":"2c33ebeb73062ab3","name":"Set Key for GET","rules":[{"t":"set","p":"topic","pt":"msg","to":"mykey","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1340,"wires":[["d1ebde22d0e224a8"]]},{"id":"ebc1e8fc0f1ba2d6","type":"inject","z":"FFF0000000000001","g":"2c33ebeb73062ab3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1260,"wires":[["e5f3326ef736cef6"]]},{"id":"adb95c63d206c633","type":"inject","z":"FFF0000000000001","g":"2c33ebeb73062ab3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1340,"wires":[["fe2be532c53402b3"]]},{"id":"e370dc92b39a7ba4","type":"redis-config","name":"Local","options":"{\"host\":\"localhost\",\"port\":6379,\"db\":0}","cluster":false,"optionsType":"json"},{"id":"5e2842a4730589c0","type":"group","z":"FFF0000000000001","name":"Working with JSON Data","style":{"label":true},"nodes":["6acc07a393e5ee26","3255e8a9122253b7","a3b0b4a74133778e","4faba929a84fb358","e5c359b934174c8e","abf011c2e81960c4","22a2b54caae5869d","b9598f313e2a9fd2","d0051f637fd1621a","9e59f275496d722d"],"x":414,"y":1399,"w":852,"h":162},{"id":"6acc07a393e5ee26","type":"redis-command","z":"FFF0000000000001","g":"5e2842a4730589c0","server":"e370dc92b39a7ba4","command":"SET","name":"","topic":"","params":"[]","paramsType":"json","payloadType":"json","block":false,"x":1040,"y":1440,"wires":[["3255e8a9122253b7"]]},{"id":"3255e8a9122253b7","type":"debug","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1170,"y":1440,"wires":[]},{"id":"a3b0b4a74133778e","type":"inject","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1440,"wires":[["d0051f637fd1621a"]]},{"id":"4faba929a84fb358","type":"redis-command","z":"FFF0000000000001","g":"5e2842a4730589c0","server":"e370dc92b39a7ba4","command":"GET","name":"","topic":"","params":"[]","paramsType":"json","payloadType":"json","block":false,"x":900,"y":1520,"wires":[["b9598f313e2a9fd2"]]},{"id":"e5c359b934174c8e","type":"debug","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1170,"y":1520,"wires":[]},{"id":"abf011c2e81960c4","type":"change","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"Set Key for GET","rules":[{"t":"set","p":"topic","pt":"msg","to":"sensor:data","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":1520,"wires":[["4faba929a84fb358"]]},{"id":"22a2b54caae5869d","type":"inject","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1520,"wires":[["abf011c2e81960c4"]]},{"id":"b9598f313e2a9fd2","type":"json","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"","property":"payload","action":"","pretty":false,"x":1030,"y":1520,"wires":[["e5c359b934174c8e"]]},{"id":"d0051f637fd1621a","type":"change","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"Set Key","rules":[{"t":"set","p":"topic","pt":"msg","to":"sensor:data","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"{\t \"temperature\": 22.5,\t \"humidity\": 65,\t \"timestamp\": $now()\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":680,"y":1440,"wires":[["9e59f275496d722d"]]},{"id":"9e59f275496d722d","type":"json","z":"FFF0000000000001","g":"5e2842a4730589c0","name":"","property":"payload","action":"","pretty":false,"x":890,"y":1440,"wires":[["6acc07a393e5ee26"]]},{"id":"39de5da95585227f","type":"group","z":"FFF0000000000001","name":"Pub/Sub Messaging","style":{"label":true},"nodes":["7756558fd542d657","bf2dc26107eff967","9856ddb8e7bc28e8","bcd9b22fd67ecf7c","22f0526fab3b589b"],"x":414,"y":1579,"w":632,"h":162},{"id":"7756558fd542d657","type":"inject","z":"FFF0000000000001","g":"39de5da95585227f","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1620,"wires":[["9856ddb8e7bc28e8"]]},{"id":"bf2dc26107eff967","type":"redis-out","z":"FFF0000000000001","g":"39de5da95585227f","server":"e370dc92b39a7ba4","command":"publish","name":"","topic":"alerts:temperature","obj":true,"x":930,"y":1620,"wires":[]},{"id":"9856ddb8e7bc28e8","type":"change","z":"FFF0000000000001","g":"39de5da95585227f","name":"Set Alert Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"ALERT: Temperature critical in Zone A: 85°C - Equipment shutdown initiated","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":1620,"wires":[["bf2dc26107eff967"]]},{"id":"bcd9b22fd67ecf7c","type":"redis-in","z":"FFF0000000000001","g":"39de5da95585227f","server":"e370dc92b39a7ba4","command":"subscribe","name":"","topic":"alerts:temperature","obj":true,"timeout":0,"x":530,"y":1700,"wires":[["22f0526fab3b589b"]]},{"id":"22f0526fab3b589b","type":"debug","z":"FFF0000000000001","g":"39de5da95585227f","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":890,"y":1700,"wires":[]},{"id":"adc4b0df6b0e84e3","type":"group","z":"FFF0000000000001","name":"Using Lua Scripts for Atomic Operations","style":{"label":true},"nodes":["ca8a523710138a11","8804755befa1fbc8","0898e80e28b7d276","0b16690b3c2c10df","9a03ebdea51eefdc","c641d8e54620a73a","7c11a5f1c9716527","a07a025db962be50"],"x":414,"y":1759,"w":1112,"h":142},{"id":"ca8a523710138a11","type":"inject","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1860,"wires":[["8804755befa1fbc8"]]},{"id":"8804755befa1fbc8","type":"function","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","name":"Prepare Lua Script Arguments","func":"// msg.payload format: [keys..., args...]\n// First element(s) are the keys, remaining elements are arguments\nmsg.payload = [\n \"inventory:product:SKU-12345\", // KEYS[1]\n 3 // ARGV[1] - quantity requested\n];\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":1860,"wires":[["0898e80e28b7d276"]]},{"id":"0898e80e28b7d276","type":"redis-lua-script","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","server":"e370dc92b39a7ba4","name":"","keyval":"1","func":"local key = KEYS[1]\nlocal requested = tonumber(ARGV[1])\n\nlocal current = tonumber(redis.call('GET', key) or \"0\")\n\nif current >= requested then\n redis.call('DECRBY', key, requested)\n return {1, current - requested}\nelse\n return {0, current}\nend","stored":false,"block":false,"x":1040,"y":1860,"wires":[["0b16690b3c2c10df"]]},{"id":"0b16690b3c2c10df","type":"function","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","name":"Format Lua Script Response","func":"const result = msg.payload;\nconst success = result[0];\nconst remaining = result[1];\n\nif (success === 1) {\n msg.payload = {\n status: \"success\",\n message: `Order processed. Remaining stock: ${remaining}`,\n remaining: remaining\n };\n} else {\n msg.payload = {\n status: \"failed\",\n message: `Insufficient stock. Available: ${remaining}`,\n available: remaining\n };\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1240,"y":1860,"wires":[["9a03ebdea51eefdc"]]},{"id":"9a03ebdea51eefdc","type":"debug","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1430,"y":1860,"wires":[]},{"id":"c641d8e54620a73a","type":"redis-command","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","server":"e370dc92b39a7ba4","command":"SET","name":"","topic":"inventory:product:SKU-12345","params":"[10]","paramsType":"json","payloadType":"json","block":false,"x":780,"y":1800,"wires":[["7c11a5f1c9716527"]]},{"id":"7c11a5f1c9716527","type":"debug","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1030,"y":1800,"wires":[]},{"id":"a07a025db962be50","type":"inject","z":"FFF0000000000001","g":"adc4b0df6b0e84e3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":1800,"wires":[["c641d8e54620a73a"]]},{"id":"01a96fb2044d723f","type":"group","z":"FFF0000000000001","name":"Direct Redis Client Access with redis-instance","style":{"label":true},"nodes":["745f41f352fa2212","a9ec9a8a5a66daa3","e3a5634ef0ebcb78","5192081309e08db9","4f169053bbd7add9","15f0b0462f795828","4a394700c6914961"],"x":414,"y":1919,"w":732,"h":222},{"id":"745f41f352fa2212","type":"redis-instance","z":"FFF0000000000001","g":"01a96fb2044d723f","server":"e370dc92b39a7ba4","name":"","topic":"redis","location":"flow","x":490,"y":1960,"wires":[]},{"id":"a9ec9a8a5a66daa3","type":"inject","z":"FFF0000000000001","g":"01a96fb2044d723f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":520,"y":2040,"wires":[["e3a5634ef0ebcb78"]]},{"id":"e3a5634ef0ebcb78","type":"function","z":"FFF0000000000001","g":"01a96fb2044d723f","name":"Batch Store Sensor Readings (Pipeline)","func":"const redis = flow.get('redis');\n\n// Create a pipeline\nconst pipeline = redis.pipeline();\n\n// Add multiple sensor readings in one batch\nconst sensors = [\n { id: 'temp-01', value: 23.5, unit: 'C' },\n { id: 'temp-02', value: 24.1, unit: 'C' },\n { id: 'humidity-01', value: 65, unit: '%' },\n { id: 'pressure-01', value: 1013, unit: 'hPa' }\n];\n\nsensors.forEach(sensor => {\n const key = `sensor:${sensor.id}:latest`;\n const data = JSON.stringify({\n value: sensor.value,\n unit: sensor.unit,\n timestamp: Date.now()\n });\n pipeline.set(key, data, 'EX', 3600); // Expire in 1 hour\n});\n\n// Execute all commands at once\npipeline.exec((err, results) => {\n if (err) {\n node.error(err, msg);\n return;\n }\n\n msg.payload = {\n message: `Stored ${results.length} sensor readings`,\n results: results\n };\n node.send(msg);\n});","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":2040,"wires":[["5192081309e08db9"]]},{"id":"5192081309e08db9","type":"debug","z":"FFF0000000000001","g":"01a96fb2044d723f","name":"debug 8","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1040,"y":2040,"wires":[]},{"id":"4f169053bbd7add9","type":"inject","z":"FFF0000000000001","g":"01a96fb2044d723f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":520,"y":2100,"wires":[["15f0b0462f795828"]]},{"id":"15f0b0462f795828","type":"function","z":"FFF0000000000001","g":"01a96fb2044d723f","name":"Scan and List Latest Sensor Keys","func":"const redis = flow.get('redis');\n\nasync function scanKeys() {\n const matchPattern = 'sensor:*:latest';\n const allKeys = [];\n let cursor = '0';\n\n try {\n do {\n // Scan with pattern matching\n const result = await redis.scan(\n cursor,\n 'MATCH', matchPattern,\n 'COUNT', 100\n );\n\n cursor = result[0];\n const keys = result[1];\n allKeys.push(...keys);\n\n } while (cursor !== '0');\n\n msg.payload = {\n pattern: matchPattern,\n count: allKeys.length,\n keys: allKeys\n };\n\n node.send(msg);\n\n } catch (err) {\n node.error(err, msg);\n }\n}\n\nscanKeys();","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":760,"y":2100,"wires":[["4a394700c6914961"]]},{"id":"4a394700c6914961","type":"debug","z":"FFF0000000000001","g":"01a96fb2044d723f","name":"debug 9","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1040,"y":2100,"wires":[]},{"id":"1a2b3c4d5e6f7081","type":"group","z":"FFF0000000000001","name":"Lists and Queues","style":{"label":true},"nodes":["1a2b3c4d5e6f7001","1a2b3c4d5e6f7002","1a2b3c4d5e6f7003","1a2b3c4d5e6f7005","1a2b3c4d5e6f7006","1a2b3c4d5e6f7007"],"x":414,"y":2159,"w":612,"h":162},{"id":"1a2b3c4d5e6f7001","type":"inject","z":"FFF0000000000001","g":"1a2b3c4d5e6f7081","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":2200,"wires":[["1a2b3c4d5e6f7002"]]},{"id":"1a2b3c4d5e6f7002","type":"change","z":"FFF0000000000001","g":"1a2b3c4d5e6f7081","name":"Prepare Order Payload","rules":[{"t":"set","p":"payload","pt":"msg","to":"Order #1042 - 2x Widget, 1x Gadget","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":2200,"wires":[["1a2b3c4d5e6f7003"]]},{"id":"1a2b3c4d5e6f7003","type":"redis-out","z":"FFF0000000000001","g":"1a2b3c4d5e6f7081","server":"e370dc92b39a7ba4","command":"rpush","name":"","topic":"queue:orders","x":930,"y":2200,"wires":[]},{"id":"1a2b3c4d5e6f7005","type":"inject","z":"FFF0000000000001","g":"1a2b3c4d5e6f7081","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":510,"y":2280,"wires":[["1a2b3c4d5e6f7006"]]},{"id":"1a2b3c4d5e6f7006","type":"redis-in","z":"FFF0000000000001","g":"1a2b3c4d5e6f7081","server":"e370dc92b39a7ba4","command":"blpop","name":"","topic":"queue:orders","timeout":0,"x":750,"y":2280,"wires":[["1a2b3c4d5e6f7007"]]},{"id":"1a2b3c4d5e6f7007","type":"debug","z":"FFF0000000000001","g":"1a2b3c4d5e6f7081","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":2280,"wires":[]},{"id":"2a2b3c4d5e6f7082","type":"group","z":"FFF0000000000001","name":"Lua Scripting - JSON and Sorted Sets","style":{"label":true},"nodes":["2a2b3c4d5e6f7001","2a2b3c4d5e6f7002","2a2b3c4d5e6f7003","2a2b3c4d5e6f7004","2a2b3c4d5e6f7005","2a2b3c4d5e6f7006","2a2b3c4d5e6f7007","2a2b3c4d5e6f7008","2a2b3c4d5e6f7009","2a2b3c4d5e6f700a","2a2b3c4d5e6f700b","2a2b3c4d5e6f700c","2a2b3c4d5e6f700d","2a2b3c4d5e6f700e","2a2b3c4d5e6f700f","2a2b3c4d5e6f7010","2a2b3c4d5e6f7011","2a2b3c4d5e6f7012","2a2b3c4d5e6f7013","2a2b3c4d5e6f7014"],"x":404,"y":2339,"w":952,"h":322},{"id":"2a2b3c4d5e6f7001","type":"inject","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Add Reading (ZADD)","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":570,"y":2380,"wires":[["2a2b3c4d5e6f7002"]]},{"id":"2a2b3c4d5e6f7002","type":"function","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Prepare ZADD Args","func":"// msg.payload format: [key, score, JSON-encoded member]\nconst reading = { test: 1, hello: \"world1\", hexstr: \"000102\" };\nmsg.payload = [\n \"device:96a4:1a87:04\", // KEYS[1]\n Date.now(), // ARGV[1] - score\n JSON.stringify(reading) // ARGV[2] - member\n];\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":2380,"wires":[["2a2b3c4d5e6f7003"]]},{"id":"2a2b3c4d5e6f7003","type":"redis-lua-script","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","server":"e370dc92b39a7ba4","name":"ZADD with JSON Member","keyval":"1","func":"local foo = redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2])\nreturn foo","stored":false,"block":false,"x":1020,"y":2380,"wires":[["2a2b3c4d5e6f7004"]]},{"id":"2a2b3c4d5e6f7004","type":"debug","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":2380,"wires":[]},{"id":"2a2b3c4d5e6f7005","type":"inject","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Decode Member","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":570,"y":2440,"wires":[["2a2b3c4d5e6f7006"]]},{"id":"2a2b3c4d5e6f7006","type":"function","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Prepare Decode Args","func":"msg.payload = [\n \"device:96a4:1a87:04\",\n \"{\\\"temperature\\\":22.5,\\\"hello\\\":\\\"world\\\"}\"\n];\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":2440,"wires":[["2a2b3c4d5e6f7007"]]},{"id":"2a2b3c4d5e6f7007","type":"redis-lua-script","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","server":"e370dc92b39a7ba4","name":"cjson.decode Extract Field","keyval":"1","func":"local foo = cjson.decode(ARGV[1])\nreturn foo.temperature","stored":false,"block":false,"x":1020,"y":2440,"wires":[["2a2b3c4d5e6f7008"]]},{"id":"2a2b3c4d5e6f7008","type":"debug","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":2440,"wires":[]},{"id":"2a2b3c4d5e6f7009","type":"inject","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Build JSON (cjson.encode)","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":570,"y":2500,"wires":[["2a2b3c4d5e6f700a"]]},{"id":"2a2b3c4d5e6f700a","type":"redis-lua-script","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","server":"e370dc92b39a7ba4","name":"cjson.encode Build JSON","keyval":"0","func":"local foo = {}\nfoo.field1 = 1\nfoo.field2 = \"hello world\"\nfoo.field3 = {}\nfoo.field3.name = \"paul\"\nreturn cjson.encode(foo)","stored":false,"block":false,"x":840,"y":2500,"wires":[["2a2b3c4d5e6f700b"]]},{"id":"2a2b3c4d5e6f700b","type":"json","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"","property":"payload","action":"","pretty":false,"x":1020,"y":2500,"wires":[["2a2b3c4d5e6f700c"]]},{"id":"2a2b3c4d5e6f700c","type":"debug","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":2500,"wires":[]},{"id":"2a2b3c4d5e6f700d","type":"inject","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Extract Substring","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":570,"y":2560,"wires":[["2a2b3c4d5e6f700e"]]},{"id":"2a2b3c4d5e6f700e","type":"function","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Prepare string.sub Args","func":"msg.payload = [\"hexstr:060708\"];\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":780,"y":2560,"wires":[["2a2b3c4d5e6f700f"]]},{"id":"2a2b3c4d5e6f700f","type":"redis-lua-script","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","server":"e370dc92b39a7ba4","name":"string.sub Extract Substring","keyval":"0","func":"local foo = string.sub(ARGV[1], -6, -1)\nreturn foo","stored":false,"block":false,"x":1020,"y":2560,"wires":[["2a2b3c4d5e6f7010"]]},{"id":"2a2b3c4d5e6f7010","type":"debug","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":2560,"wires":[]},{"id":"2a2b3c4d5e6f7011","type":"inject","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Query Sorted Set (ZRANGE)","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":570,"y":2620,"wires":[["2a2b3c4d5e6f7012"]]},{"id":"2a2b3c4d5e6f7012","type":"function","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Prepare ZRANGE Args","func":"msg.payload = [\"device:96a4:1a87:04\"];\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":800,"y":2620,"wires":[["2a2b3c4d5e6f7013"]]},{"id":"2a2b3c4d5e6f7013","type":"redis-lua-script","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","server":"e370dc92b39a7ba4","name":"ZRANGE Query Members","keyval":"1","func":"return redis.call('ZRANGE', KEYS[1], 0, -1)","stored":false,"block":false,"x":1020,"y":2620,"wires":[["2a2b3c4d5e6f7014"]]},{"id":"2a2b3c4d5e6f7014","type":"debug","z":"FFF0000000000001","g":"2a2b3c4d5e6f7082","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1260,"y":2620,"wires":[]},{"id":"0b0e24520432625b","type":"global-config","env":[],"modules":{"@flowfuse-certified-nodes/redis":"1.0.0"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/flowfuse/images/ff-expert-at-platform-level.png b/nuxt/content-guides/flowfuse-nodes/images/ff-expert-at-platform-level.png similarity index 100% rename from src/node-red/flowfuse/images/ff-expert-at-platform-level.png rename to nuxt/content-guides/flowfuse-nodes/images/ff-expert-at-platform-level.png diff --git a/src/node-red/flowfuse/images/ff-expert-in-editor.png b/nuxt/content-guides/flowfuse-nodes/images/ff-expert-in-editor.png similarity index 100% rename from src/node-red/flowfuse/images/ff-expert-in-editor.png rename to nuxt/content-guides/flowfuse-nodes/images/ff-expert-in-editor.png diff --git a/src/node-red/flowfuse/images/ff-expert-opened.png b/nuxt/content-guides/flowfuse-nodes/images/ff-expert-opened.png similarity index 100% rename from src/node-red/flowfuse/images/ff-expert-opened.png rename to nuxt/content-guides/flowfuse-nodes/images/ff-expert-opened.png diff --git a/src/node-red/flowfuse/images/ff-expert-opned-platform.png b/nuxt/content-guides/flowfuse-nodes/images/ff-expert-opned-platform.png similarity index 100% rename from src/node-red/flowfuse/images/ff-expert-opned-platform.png rename to nuxt/content-guides/flowfuse-nodes/images/ff-expert-opned-platform.png diff --git a/src/node-red/flowfuse/images/mcp-in-flowfuse.png b/nuxt/content-guides/flowfuse-nodes/images/mcp-in-flowfuse.png similarity index 100% rename from src/node-red/flowfuse/images/mcp-in-flowfuse.png rename to nuxt/content-guides/flowfuse-nodes/images/mcp-in-flowfuse.png diff --git a/src/node-red/flowfuse/mcp/index.md b/nuxt/content-guides/flowfuse-nodes/mcp/README.md similarity index 86% rename from src/node-red/flowfuse/mcp/index.md rename to nuxt/content-guides/flowfuse-nodes/mcp/README.md index 9dc8fec3db..ce9ed32de9 100644 --- a/src/node-red/flowfuse/mcp/index.md +++ b/nuxt/content-guides/flowfuse-nodes/mcp/README.md @@ -1,14 +1,13 @@ --- -eleventyNavigation: - key: MCP - parent: FlowFuse Nodes - order: 2 +title: MCP Nodes +metaTitle: "MCP Nodes" +navTitle: MCP +navOrder: 2 meta: - title: MCP Nodes description: A set of nodes that enable the creation of MCP (Model Context Protocol) servers in your Node-RED flows for AI-integration. --- -# {{ meta.title }} +# MCP Nodes This document lists and explains the **MCP nodes** available in FlowFuse. MCP (Model Context Protocol) nodes extend Node-RED to integrate AI models, tools, and resources through the Model Context Protocol framework. Each node helps you connect, configure, and manage AI interactions directly from Node-RED. @@ -119,6 +118,9 @@ FlowFuse Expert enforces access control based on the annotations configured in y This section lists the **MCP nodes** available in FlowFuse: -{% include "navigation-items-list.njk" %} +- [MCP Prompt](/docs/flowfuse-nodes/mcp/mcp-prompt/): The MCP Prompt node allows you to create pre-configured prompt templates that users can easily invoke from their MCP client. +- [MCP Resource](/docs/flowfuse-nodes/mcp/mcp-resource/): The MCP Resource node allows you to expose read-only data that AI assistants can access for context. +- [MCP Response](/docs/flowfuse-nodes/mcp/mcp-response/): Sends responses back to the MCP client for tools and resources. +- [MCP Tool](/docs/flowfuse-nodes/mcp/mcp-tool/): MCP Tool node allows you to create custom tools that FlowFuse Expert can invoke to perform specific tasks. -Each listed node provides a unique capability within the MCP ecosystem, from registering tools and resources to managing prompts and responses. Use these nodes to create MCP Servers, provide tools, resources, and prompts to MCP clients, and standardize AI workflows in your Node-RED projects. \ No newline at end of file +Each listed node provides a unique capability within the MCP ecosystem, from registering tools and resources to managing prompts and responses. Use these nodes to create MCP Servers, provide tools, resources, and prompts to MCP clients, and standardize AI workflows in your Node-RED projects. diff --git a/src/node-red/flowfuse/mcp/mcp-prompt.md b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-prompt.md similarity index 96% rename from src/node-red/flowfuse/mcp/mcp-prompt.md rename to nuxt/content-guides/flowfuse-nodes/mcp/mcp-prompt.md index 49abe13a9b..a3375ee07f 100644 --- a/src/node-red/flowfuse/mcp/mcp-prompt.md +++ b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-prompt.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: MCP Prompt - parent: MCP +title: MCP Prompt +metaTitle: "MCP Prompt" +navTitle: MCP Prompt +navOrder: 50 meta: - title: MCP Prompt description: The MCP Prompt node allows you to create pre-configured prompt templates that users can easily invoke from their MCP client. --- -# {{ meta.title }} +# MCP Prompt The MCP Prompt node allows you to create pre-configured prompt templates that users can easily invoke from their MCP client. These templates can include variable placeholders that users fill in, making it simple to create consistent, well-structured prompts for common tasks. Unlike tools and resources, prompts don't require flows or response nodes - they simply register the template with the MCP server. @@ -54,9 +54,7 @@ Detailed description of what this prompt does and when to use it. `string`, *Required* -{% raw %} Define your prompt text using **double curly braces `{{ }}`** for variables. -{% endraw %} ### Arguments @@ -67,30 +65,25 @@ JSON schema defining the template variables that users can customize. This follo ### Basic Example -{% raw %} ``` Hello {{ name }}! Welcome to {{ location }}. ``` -{% endraw %} **Variables:** `name`, `location` ### Complex Example -{% raw %} ``` You are a holiday planning agent. You should provide information about {{ location }}. You should also provide rough budget ideas for visiting this place in {{ time_of_year }} for {{ duration }} days. Please breakdown rough ideas for hotels and local tourist hot spots to visit. ``` -{% endraw %} **Variables:** `location`, `time_of_year`, `duration` ### Multi-Section Example -{% raw %} ``` # Code Review Request @@ -108,7 +101,6 @@ Focus on: Provide feedback on code quality, potential bugs, and suggestions for improvement. ``` -{% endraw %} **Variables:** `filename`, `language`, `code`, `focus_area_1`, `focus_area_2`, `focus_area_3` @@ -192,6 +184,8 @@ Provide feedback on code quality, potential bugs, and suggestions for improvemen ## Example Flow -{% renderFlow 300 %} +::render-flow{:height="300"} +```json [{"id":"1dd56abc53f50de0","type":"group","z":"FFF0000000000001","name":"MCP Prompts","style":{"label":true},"nodes":["cc2bedb852d8cea4","241e6041470d735c"],"x":714,"y":819,"w":212,"h":142},{"id":"cc2bedb852d8cea4","type":"mcp-prompt","z":"FFF0000000000001","g":"1dd56abc53f50de0","name":"","server":"28907ed9ddcdd4b9","promptId":"uppercase","title":"Uppercase","description":"This prompt will return the provided content in all uppercase characters","template":"Please return the following in uppercase: {{message}}","arguments":"{\n \"type\": \"object\",\n \"properties\": {\n \"message\": {\n \"type\": \"string\",\n \"description\": \"The text provided by the user\"\n }\n },\n \"required\": [\"message\"]\n}","x":800,"y":920,"wires":[]},{"id":"241e6041470d735c","type":"mcp-prompt","z":"FFF0000000000001","g":"1dd56abc53f50de0","name":"","server":"28907ed9ddcdd4b9","promptId":"holiday_planner","title":"Holiday Planning and Budgeting Agent","description":"This prompt can assist users with creating budgets for their desired holiday destinations. ","template":"You are a holiday planning agent.\nYou should provide information about {{ location }}.\nYou should also provide rough budget ideas for visiting this place in {{ time_of_year }} for {{ duration }} days.\nPlease breakdown rough ideas for hotels and local tourist hot spots to visit.","arguments":"{\n \"type\": \"object\",\n \"properties\": {\n \"location\": {\n \"type\": \"string\",\n \"description\": \"A country, city or town somewhere in the world.\",\n \"minLength\": 1\n },\n \"time_of_year\": {\n \"type\": \"string\",\n \"description\": \"This can be a specific date, season or month. Used to provide context to the agent as to when in the year the user wants to take their holiday.\",\n \"minLength\": 1\n },\n \"duration\": {\n \"type\": \"number\",\n \"default\": 7,\n \"description\": \"The number of days\"\n }\n },\n \"required\": [\"location\", \"time_of_year\"]\n}","x":820,"y":860,"wires":[]},{"id":"28907ed9ddcdd4b9","type":"mcp-server","name":"My Node-RED MCP Server","protocol":"http","path":"/mcp"},{"id":"89a436fa564e3d58","type":"global-config","env":[],"modules":{"@flowfuse-nodes/nr-mcp-server-nodes":"0.1.2"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/flowfuse/mcp/mcp-resource.md b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-resource.md similarity index 99% rename from src/node-red/flowfuse/mcp/mcp-resource.md rename to nuxt/content-guides/flowfuse-nodes/mcp/mcp-resource.md index 5006b1d5da..72337b224f 100644 --- a/src/node-red/flowfuse/mcp/mcp-resource.md +++ b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-resource.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: MCP Resource - parent: MCP +title: MCP Resource +metaTitle: "MCP Resource" +navTitle: MCP Resource +navOrder: 50 meta: - title: MCP Resource description: The MCP Resource node allows you to expose read-only data that AI assistants can access for context. --- -# {{ meta.title }} +# MCP Resource The MCP Resource node allows you to expose read-only data that AI assistants can access for context. Resources are designed to provide information without performing actions or causing side effects. Resources allow servers to share data that provides context to language models, such as files, database schemas, or application-specific information @@ -168,6 +168,8 @@ msg.payload = { ## Example Flow -{% renderFlow %} +::render-flow +```json [{"id":"00744c2e2a560c36","type":"group","z":"e1ceeedf31ce1ebd","name":"MCP Resources","style":{"label":true},"nodes":["555a96b221f8e2bf","bb258a21622c01b9","066f6c58bc45d617","70e0b93eb88d63d6","5651b0896965ae6a","6bcaef4ed1ab82ae","3d6e0d7e21b44845","350db73b93a26f8d","fac46d1d55d9ca22","84ebc9b12d948173","9d4edda03c19b553","d3aa7bfa90c9e24e","d6f015cb6debe35b"],"x":274,"y":1739,"w":1002,"h":282},{"id":"555a96b221f8e2bf","type":"mcp-resource","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"","server":"28907ed9ddcdd4b9","resourceUri":"local://books/{genre}","resourceId":"my_books","title":"Books Array","description":"JSON Array of books filtered by genre.","mimeType":"application/json","x":400,"y":1920,"wires":[["70e0b93eb88d63d6"]]},{"id":"bb258a21622c01b9","type":"mcp-response","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"","x":1130,"y":1900,"wires":[]},{"id":"066f6c58bc45d617","type":"json","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"To JSON","property":"payload","action":"str","pretty":false,"x":870,"y":1920,"wires":[["350db73b93a26f8d"]]},{"id":"70e0b93eb88d63d6","type":"template","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"library","field":"library","fieldType":"msg","format":"json","syntax":"mustache","template":"[\n {\n \"author\": \"Harper Lee\",\n \"title\": \"To Kill a Mockingbird\",\n \"genre\": \"Fiction\",\n \"year\": 1960\n },\n {\n \"author\": \"J.K. Rowling\",\n \"title\": \"Harry Potter and the Sorcerer's Stone\",\n \"genre\": \"Fantasy\",\n \"year\": 1997\n },\n {\n \"author\": \"George Orwell\",\n \"title\": \"1984\",\n \"genre\": \"Dystopian\",\n \"year\": 1949\n },\n {\n \"author\": \"Jane Austen\",\n \"title\": \"Pride and Prejudice\",\n \"genre\": \"Romance\",\n \"year\": 1813\n },\n {\n \"author\": \"F. Scott Fitzgerald\",\n \"title\": \"The Great Gatsby\",\n \"genre\": \"Classic\",\n \"year\": 1925\n },\n {\n \"author\": \"Toni Morrison\",\n \"title\": \"Beloved\",\n \"genre\": \"Historical Fiction\",\n \"year\": 1987\n },\n {\n \"author\": \"Stephen King\",\n \"title\": \"The Shining\",\n \"genre\": \"Horror\",\n \"year\": 1977\n },\n {\n \"author\": \"Agatha Christie\",\n \"title\": \"Murder on the Orient Express\",\n \"genre\": \"Mystery\",\n \"year\": 1934\n },\n {\n \"author\": \"Gabriel Garcia Marquez\",\n \"title\": \"One Hundred Years of Solitude\",\n \"genre\": \"Magical Realism\",\n \"year\": 1967\n },\n {\n \"author\": \"Mark Twain\",\n \"title\": \"Adventures of Huckleberry Finn\",\n \"genre\": \"Adventure\",\n \"year\": 1884\n },\n {\n \"author\": \"J.R.R. Tolkien\",\n \"title\": \"The Lord of the Rings\",\n \"genre\": \"Fantasy\",\n \"year\": 1954\n },\n {\n \"author\": \"Ernest Hemingway\",\n \"title\": \"The Old Man and the Sea\",\n \"genre\": \"Literary Fiction\",\n \"year\": 1952\n },\n {\n \"author\": \"Charlotte Bronte\",\n \"title\": \"Jane Eyre\",\n \"genre\": \"Gothic\",\n \"year\": 1847\n },\n {\n \"author\": \"Leo Tolstoy\",\n \"title\": \"War and Peace\",\n \"genre\": \"Historical Fiction\",\n \"year\": 1869\n },\n {\n \"author\": \"Emily Bronte\",\n \"title\": \"Wuthering Heights\",\n \"genre\": \"Gothic Romance\",\n \"year\": 1847\n },\n {\n \"author\": \"Ray Bradbury\",\n \"title\": \"Fahrenheit 451\",\n \"genre\": \"Science Fiction\",\n \"year\": 1953\n },\n {\n \"author\": \"Arthur Conan Doyle\",\n \"title\": \"The Adventures of Sherlock Holmes\",\n \"genre\": \"Mystery\",\n \"year\": 1892\n },\n {\n \"author\": \"Margaret Atwood\",\n \"title\": \"The Handmaid's Tale\",\n \"genre\": \"Dystopian\",\n \"year\": 1985\n },\n {\n \"author\": \"Herman Melville\",\n \"title\": \"Moby Dick\",\n \"genre\": \"Adventure\",\n \"year\": 1851\n },\n {\n \"author\": \"Kazuo Ishiguro\",\n \"title\": \"Never Let Me Go\",\n \"genre\": \"Science Fiction\",\n \"year\": 2005\n }\n]","output":"json","x":580,"y":1920,"wires":[["5651b0896965ae6a"]]},{"id":"5651b0896965ae6a","type":"function","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"filter","func":"const books = msg.library\nconst genre = msg.payload.genre\n\nif (!Array.isArray(books)) {\n throw new Error('Payload is not an array of books')\n}\n\nif (typeof genre !== 'string') {\n throw new Error('Genre must be a string')\n}\n\nfunction looseCompareGenre(genre, search) {\n return genre.toLowerCase().includes(search.toLowerCase())\n}\nconst filteredBooks = books.filter(book => looseCompareGenre(book.genre, genre))\n\nmsg.payload = filteredBooks\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":720,"y":1920,"wires":[["066f6c58bc45d617"]]},{"id":"6bcaef4ed1ab82ae","type":"catch","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"","scope":"group","uncaught":false,"x":860,"y":1980,"wires":[["350db73b93a26f8d"]]},{"id":"3d6e0d7e21b44845","type":"mcp-resource","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"","server":"28907ed9ddcdd4b9","resourceUri":"db://recipes","resourceId":"recipes","title":"Recipes Array","description":"JSON Array of all recipes","mimeType":"application/json","x":380,"y":1820,"wires":[["84ebc9b12d948173"]]},{"id":"350db73b93a26f8d","type":"junction","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","x":1010,"y":1880,"wires":[["bb258a21622c01b9","fac46d1d55d9ca22"]]},{"id":"fac46d1d55d9ca22","type":"debug","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"resource response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1140,"y":1860,"wires":[]},{"id":"84ebc9b12d948173","type":"template","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"db query for recipes","field":"payload","fieldType":"msg","format":"json","syntax":"mustache","template":"{\n \"recipes\": [\n {\n \"name\": \"Spaghetti Aglio e Olio\",\n \"ingredients\": [\n \"Spaghetti\",\n \"Garlic\",\n \"Olive Oil\",\n \"Red Pepper Flakes\",\n \"Parsley\"\n ],\n \"method\": \"Cook spaghetti, sauté garlic in olive oil, add red pepper flakes, toss with cooked spaghetti, garnish with parsley.\",\n \"wine_paring\": \"Pinot Grigio\"\n },\n {\n \"name\": \"Chicken Alfredo Pasta\",\n \"ingredients\": [\n \"Chicken Breast\",\n \"Fettuccine Pasta\",\n \"Heavy Cream\",\n \"Parmesan Cheese\",\n \"Garlic\",\n \"Butter\"\n ],\n \"method\": \"Cook chicken, cook pasta, make alfredo sauce with cream, parmesan, garlic, and butter, combine all.\",\n \"wine_paring\": \"Chardonnay\"\n },\n {\n \"name\": \"Caprese Salad\",\n \"ingredients\": [\n \"Tomatoes\",\n \"Fresh Mozzarella\",\n \"Basil\",\n \"Olive Oil\",\n \"Balsamic Vinegar\",\n \"Salt\",\n \"Pepper\"\n ],\n \"method\": \"Slice tomatoes and mozzarella, layer with basil, drizzle with olive oil and balsamic vinegar, season with salt and pepper.\",\n \"wine_paring\": \"Chianti\"\n },\n {\n \"name\": \"Beef Tacos\",\n \"ingredients\": [\n \"Ground Beef\",\n \"Taco Seasoning\",\n \"Tortillas\",\n \"Lettuce\",\n \"Tomatoes\",\n \"Cheese\",\n \"Sour Cream\"\n ],\n \"method\": \"Cook beef with taco seasoning, assemble tacos with beef, lettuce, tomatoes, cheese, and sour cream.\",\n \"wine_paring\": null\n },\n {\n \"name\": \"Vegetable Stir Fry\",\n \"ingredients\": [\n \"Mixed Vegetables\",\n \"Soy Sauce\",\n \"Garlic\",\n \"Ginger\",\n \"Sesame Oil\",\n \"Rice\"\n ],\n \"method\": \"Stir fry vegetables with soy sauce, garlic, and ginger, finish with sesame oil, serve over rice.\",\n \"wine_paring\": \"Riesling\"\n },\n {\n \"name\": \"Margherita Pizza\",\n \"ingredients\": [\n \"Pizza Dough\",\n \"Tomato Sauce\",\n \"Fresh Mozzarella\",\n \"Basil\",\n \"Olive Oil\"\n ],\n \"method\": \"Top pizza dough with sauce, mozzarella, and basil, drizzle with olive oil, bake until crust is golden.\",\n \"wine_paring\": \"Merlot\"\n },\n {\n \"name\": \"Grilled Salmon\",\n \"ingredients\": [\n \"Salmon Fillet\",\n \"Lemon\",\n \"Garlic\",\n \"Dill\",\n \"Olive Oil\"\n ],\n \"method\": \"Marinate salmon with lemon, garlic, dill, and olive oil, grill until cooked through.\",\n \"wine_paring\": \"Sauvignon Blanc\"\n },\n {\n \"name\": \"Pasta Primavera\",\n \"ingredients\": [\n \"Pasta\",\n \"Assorted Vegetables\",\n \"Cream Sauce\",\n \"Garlic\",\n \"Parmesan Cheese\"\n ],\n \"method\": \"Cook pasta, sauté vegetables, add cream sauce, garlic, and parmesan, toss with cooked pasta.\",\n \"wine_paring\": \"Chardonnay\"\n },\n {\n \"name\": \"Chicken Caesar Salad\",\n \"ingredients\": [\n \"Chicken Breast\",\n \"Romaine Lettuce\",\n \"Caesar Dressing\",\n \"Croutons\",\n \"Parmesan Cheese\"\n ],\n \"method\": \"Grill chicken, chop lettuce, toss with dressing, croutons, and parmesan, top with grilled chicken.\",\n \"wine_paring\": \"Sauvignon Blanc\"\n },\n {\n \"name\": \"Chocolate Chip Cookies\",\n \"ingredients\": [\n \"Flour\",\n \"Butter\",\n \"Sugar\",\n \"Eggs\",\n \"Chocolate Chips\",\n \"Vanilla Extract\",\n \"Baking Soda\"\n ],\n \"method\": \"Cream butter and sugar, add eggs and vanilla, mix in dry ingredients and chocolate chips, bake until golden.\",\n \"wine_paring\": null\n }\n ]\n}","output":"str","x":630,"y":1820,"wires":[["d6f015cb6debe35b"]]},{"id":"9d4edda03c19b553","type":"comment","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"Status Resource Example","info":"","x":420,"y":1780,"wires":[]},{"id":"d3aa7bfa90c9e24e","type":"comment","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","name":"Dynamic Resource Example","info":"","x":430,"y":1880,"wires":[]},{"id":"d6f015cb6debe35b","type":"junction","z":"e1ceeedf31ce1ebd","g":"00744c2e2a560c36","x":930,"y":1820,"wires":[["350db73b93a26f8d"]]},{"id":"28907ed9ddcdd4b9","type":"mcp-server","name":"My Node-RED MCP Server","protocol":"http","path":"/mcp"},{"id":"3d12514448ee3580","type":"global-config","env":[],"modules":{"@flowfuse-nodes/nr-mcp-server-nodes":"0.1.1"}}] -{% endrenderFlow %} \ No newline at end of file +``` +:: diff --git a/src/node-red/flowfuse/mcp/mcp-response.md b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-response.md similarity index 92% rename from src/node-red/flowfuse/mcp/mcp-response.md rename to nuxt/content-guides/flowfuse-nodes/mcp/mcp-response.md index a0d5444238..5d1294462a 100644 --- a/src/node-red/flowfuse/mcp/mcp-response.md +++ b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-response.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: MCP Responses - parent: MCP +title: MCP Response +metaTitle: "MCP Response" +navTitle: MCP Responses +navOrder: 50 meta: - title: MCP Response description: Sends responses back to the MCP client for tools and resources. --- -# {{ meta.title }} +# MCP Response Sends responses back to the MCP client for tools and resources. This node should be the final node in any flow that begins with an MCP Tool or MCP Resource node. @@ -40,4 +40,4 @@ The node accepts `msg.payload` containing the data to return to the MCP client. [MCP Tool/Resource] → [Your Processing Nodes] → [MCP Response] ``` -The MCP Response node completes the request cycle by sending your processed data back to the AI assistant that made the request. \ No newline at end of file +The MCP Response node completes the request cycle by sending your processed data back to the AI assistant that made the request. diff --git a/src/node-red/flowfuse/mcp/mcp-tool.md b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-tool.md similarity index 98% rename from src/node-red/flowfuse/mcp/mcp-tool.md rename to nuxt/content-guides/flowfuse-nodes/mcp/mcp-tool.md index 06af642559..17581e08e3 100644 --- a/src/node-red/flowfuse/mcp/mcp-tool.md +++ b/nuxt/content-guides/flowfuse-nodes/mcp/mcp-tool.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: MCP Tool - parent: MCP +title: MCP Tool +metaTitle: "MCP Tool" +navTitle: MCP Tool +navOrder: 50 meta: - title: MCP Tool description: MCP Tool node allows you to create custom tools that FlowFuse Expert can invoke to perform specific tasks. --- -# {{ meta.title }} +# MCP Tool MCP Tool node allows you to create custom tools that FlowFuse Expert can invoke to perform specific tasks. These tools can do anything a Node-RED flow can do - from querying databases and calling APIs to controlling IoT devices and processing data. The FlowFuse Expert decides when to call your tool based on the description and input schema you provide. @@ -159,6 +159,8 @@ You can then use these values in subsequent nodes to perform your tool's logic. ## Example Flow -{% renderFlow %} +::render-flow +```json [{"id":"4076896ebd9fb8b4","type":"group","z":"e1ceeedf31ce1ebd","name":"MCP Tools","style":{"label":true},"nodes":["670d69227fd02715","d473b3e38011a7d1","8bb8104fab25a772","b4398fb68fb3d363","561f103a1ac605c4","d31cc0acdb813863","3c23f963f4982c1a","644e76704eb56f41","30ead11f52f1bf19","8b5faff4ad12d406","079eacf59ffc7032"],"x":254,"y":1399,"w":892,"h":282},{"id":"670d69227fd02715","type":"mcp-tool","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","server":"28907ed9ddcdd4b9","toolName":"greeting","title":"","description":"Greet person by name","inputSchema":"{\n \"type\": \"object\",\n \"properties\": {\n \"name\": {\n \"type\": \"string\",\n \"description\": \"The name to greet\",\n \"minLength\": 1\n }\n },\n \"required\": [\"name\"]\n}","x":340,"y":1480,"wires":[["8bb8104fab25a772"]]},{"id":"d473b3e38011a7d1","type":"change","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"\"This response is defined in a Node-RED change node. Hi \" & payload.name","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":1480,"wires":[["079eacf59ffc7032"]]},{"id":"8bb8104fab25a772","type":"delay","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","pauseType":"delay","timeout":"2","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":540,"y":1480,"wires":[["d473b3e38011a7d1"]]},{"id":"b4398fb68fb3d363","type":"mcp-tool","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","server":"28907ed9ddcdd4b9","toolName":"get_iss_position","title":"Get ISS Position","description":"Retrieves the latitude and longitude of the Internanational Space Station","inputSchema":"{}","x":360,"y":1580,"wires":[["561f103a1ac605c4"]]},{"id":"561f103a1ac605c4","type":"http request","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"http://api.open-notify.org/iss-now.json","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":730,"y":1580,"wires":[["079eacf59ffc7032"]]},{"id":"d31cc0acdb813863","type":"catch","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","scope":"group","uncaught":false,"x":730,"y":1640,"wires":[["079eacf59ffc7032"]]},{"id":"3c23f963f4982c1a","type":"debug","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"tool response","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1020,"y":1520,"wires":[]},{"id":"644e76704eb56f41","type":"mcp-response","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"","x":1020,"y":1560,"wires":[]},{"id":"30ead11f52f1bf19","type":"comment","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"Simple Greeting Tool","info":"","x":370,"y":1440,"wires":[]},{"id":"8b5faff4ad12d406","type":"comment","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","name":"Get IIS Position Tool","info":"","x":370,"y":1540,"wires":[]},{"id":"079eacf59ffc7032","type":"junction","z":"e1ceeedf31ce1ebd","g":"4076896ebd9fb8b4","x":880,"y":1540,"wires":[["644e76704eb56f41","3c23f963f4982c1a"]]},{"id":"28907ed9ddcdd4b9","type":"mcp-server","name":"My Node-RED MCP Server","protocol":"http","path":"/mcp"},{"id":"c05fb032a62e8357","type":"global-config","env":[],"modules":{"@flowfuse-nodes/nr-mcp-server-nodes":"0.1.1"}}] -{% endrenderFlow %} \ No newline at end of file +``` +:: diff --git a/src/node-red/flowfuse/mqtt/index.md b/nuxt/content-guides/flowfuse-nodes/mqtt/README.md similarity index 63% rename from src/node-red/flowfuse/mqtt/index.md rename to nuxt/content-guides/flowfuse-nodes/mqtt/README.md index db2b9535c8..2e80e7c22b 100644 --- a/src/node-red/flowfuse/mqtt/index.md +++ b/nuxt/content-guides/flowfuse-nodes/mqtt/README.md @@ -1,15 +1,13 @@ --- -eleventyNavigation: - key: FlowFuse MQTT Nodes - parent: FlowFuse Nodes - order: 5 - title: MQTT +title: MQTT Nodes +metaTitle: "MQTT Nodes" +navTitle: FlowFuse MQTT Nodes +navOrder: 5 meta: - title: MQTT Nodes description: MQTT In and Out nodes designed for FlowFuse users with automatic configuration. --- -# {{ meta.title }} +# MQTT Nodes This document lists and explains the **MQTT nodes** available in FlowFuse. These nodes are enhanced versions of the standard **MQTT In** and **MQTT Out** nodes in Node-RED, designed for FlowFuse users. @@ -19,6 +17,7 @@ They are tightly integrated with the [FlowFuse MQTT Broker Service](/docs/user/t This section lists the document of **MQTT nodes** available in FlowFuse: -{% include "navigation-items-list.njk" %} +- [MQTT In](/docs/flowfuse-nodes/mqtt/mqtt-in/): Enhanced MQTT In node for FlowFuse with automatic broker setup, dynamic subscriptions, wildcard topic support, and full MQTT v5 compatibility. +- [MQTT Out](/docs/flowfuse-nodes/mqtt/mqtt-out/): Enhanced MQTT Out node for FlowFuse with automatic broker setup, dynamic topic control, and full MQTT v5 support. -Each node extends standard MQTT functionality with automatic configuration and full MQTT v5 support, making it easier to build reliable and real-time communication flows inside FlowFuse. \ No newline at end of file +Each node extends standard MQTT functionality with automatic configuration and full MQTT v5 support, making it easier to build reliable and real-time communication flows inside FlowFuse. diff --git a/src/node-red/flowfuse/mqtt/mqtt-in.md b/nuxt/content-guides/flowfuse-nodes/mqtt/mqtt-in.md similarity index 96% rename from src/node-red/flowfuse/mqtt/mqtt-in.md rename to nuxt/content-guides/flowfuse-nodes/mqtt/mqtt-in.md index 78938bb8b7..a5776965cb 100644 --- a/src/node-red/flowfuse/mqtt/mqtt-in.md +++ b/nuxt/content-guides/flowfuse-nodes/mqtt/mqtt-in.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: MQTT In - parent: FlowFuse MQTT Nodes +title: MQTT In +metaTitle: "MQTT In" +navTitle: MQTT In +navOrder: 50 meta: - title: MQTT In description: Enhanced MQTT In node for FlowFuse with automatic broker setup, dynamic subscriptions, wildcard topic support, and full MQTT v5 compatibility. --- -# {{ meta.title }} +# MQTT In This is an enhanced version of the standard MQTT In node, designed exclusively for FlowFuse users. The node features automatic configuration upon deployment. The [MQTT broker client](/docs/user/teambroker/) is created automatically alongside the node configuration when added to the canvas. @@ -80,4 +80,4 @@ MQTT supports two wildcard characters for flexible topic matching: ## Version Support -This node fully supports MQTT version 5 features including response topics, correlation data, content types, user properties, message expiry intervals, and topic aliases. It maintains backward compatibility with earlier MQTT versions. \ No newline at end of file +This node fully supports MQTT version 5 features including response topics, correlation data, content types, user properties, message expiry intervals, and topic aliases. It maintains backward compatibility with earlier MQTT versions. diff --git a/src/node-red/flowfuse/mqtt/mqtt-out.md b/nuxt/content-guides/flowfuse-nodes/mqtt/mqtt-out.md similarity index 97% rename from src/node-red/flowfuse/mqtt/mqtt-out.md rename to nuxt/content-guides/flowfuse-nodes/mqtt/mqtt-out.md index 8501166bf3..c8155d177a 100644 --- a/src/node-red/flowfuse/mqtt/mqtt-out.md +++ b/nuxt/content-guides/flowfuse-nodes/mqtt/mqtt-out.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: MQTT Out - parent: FlowFuse MQTT Nodes +title: MQTT Out +metaTitle: "MQTT Out" +navTitle: MQTT Out +navOrder: 50 meta: - title: MQTT Out description: Enhanced MQTT Out node for FlowFuse with automatic broker setup, dynamic topic control, and full MQTT v5 support. --- -# {{ meta.title }} +# MQTT Out This is an enhanced version of the standard MQTT Out node, designed exclusively for FlowFuse users. The node features automatic configuration upon deployment when using within Flowfuse instance. The [MQTT broker](/docs/user/teambroker/) client is created automatically alongside the node configuration when added to the canvas. diff --git a/nuxt/content-guides/node-red/README.md b/nuxt/content-guides/node-red/README.md new file mode 100644 index 0000000000..ecf27df71a --- /dev/null +++ b/nuxt/content-guides/node-red/README.md @@ -0,0 +1,81 @@ +--- +title: "Using Node-RED" +navTitle: "Using Node-RED" +metaTitle: "Node-RED Documentation | Using Node-RED" +navOrder: 1 +navGroup: "Node-RED" +navGroupOrder: 7 +meta: + description: "Reference documentation for using Node-RED with databases, communication protocols, hardware, peripheral devices, notification services and integration technologies." + keywords: node-red, node red, node-red documentation, node-red core nodes, node-red database, node-red protocols, node-red hardware, industrial iot +--- + +# Using Node-RED + +Node-RED is the open-source runtime FlowFuse runs, governs and scales. This section +provides reference documentation for working with Node-RED itself: connecting it to +databases, communication protocols, hardware and notification services, and +understanding what each core node does. + +It is here to answer a question you already have. It is not a path into FlowFuse, and it +does not need to be read in order. If you are setting FlowFuse up, start with +[Using FlowFuse](/docs/user/) instead. + +::callout{icon="i-lucide-book-open"} +**The Node-RED project's own documentation is the primary source.** Installing Node-RED, +the editor, the message model, writing functions and the API reference all live at +[nodered.org/docs](https://nodered.org/docs/). This section covers the ground that +documentation does not: per-node reference on the web, and connecting Node-RED to +specific databases, protocols, hardware and services. +:: + +## Getting started + +Installing Node-RED, finding your way around the editor, and the basics of shaping a +message. [Getting started](/docs/node-red/getting-started/) + +## Core nodes + +Reference documentation for each node in the default Node-RED palette, with a worked +reason to reach for it. [Core nodes](/docs/node-red/core-nodes/) + +## Communication protocols + +Modbus, OPC UA, MQTT, AMQP, WebSocket and LwM2M, for connecting Node-RED to controllers +and edge equipment. [Communication protocols](/docs/node-red/protocol/) + +## Databases + +Reading and writing SQL, NoSQL and time-series databases with Node-RED, one guide per +database. [Databases](/docs/node-red/database/) + +## Integration technologies + +Webhooks, REST APIs and GraphQL, for connecting Node-RED to the rest of your systems. +[Integration technologies](/docs/node-red/integration-technologies/) + +## Notification services + +Sending alerts from Node-RED by email, Telegram and Discord. +[Notification services](/docs/node-red/notification/) + +## Hardware + +Running Node-RED on a Raspberry Pi, a Siemens IoT2050, and other industrial gateways. +[Hardware](/docs/node-red/hardware/) + +## Peripheral devices + +Webcams, barcode scanners and other devices connected to the machine Node-RED runs on. +[Peripheral devices](/docs/node-red/peripheral/) + +## Reference + +[Terminology](/docs/node-red/terminology/) for the words used in Node-RED documentation, +and [keyboard shortcuts](/docs/node-red/keyboard/) for the editor. + +::callout{icon="i-lucide-arrow-right"} +**Running Node-RED for an organisation?** Access control, version history, deployment +across environments and remote instance management are what FlowFuse adds on top. +See [Using FlowFuse](/docs/user/). +:: diff --git a/src/_includes/core-nodes/catch-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/catch.md similarity index 96% rename from src/_includes/core-nodes/catch-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/catch.md index db1b3302b4..e1781dead8 100644 --- a/src/_includes/core-nodes/catch-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/catch.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Catch node" +metaTitle: "Node-RED - Catch Node" +navTitle: "Catch" +navOrder: 4 +--- + +# Catch + Catches and handles errors that occur in flows. ## Where and why do we use the Catch node? @@ -46,9 +55,11 @@ The Catch node handles errors when interacting with APIs, including network issu In this example, the inject node sets a request timeout of 2000 milliseconds. The HTTP request node calls a mock URL with a 3-second delay parameter, simulating a delayed response. This causes a timeout error that the Catch node intercepts. After catching the error, the flow retries the request after a 5-second delay. -{% renderFlow %} +::render-flow +```json [{"id":"aa3e542e2b379d9e","type":"catch","z":"dff6fa938cfda5c8","name":"","scope":null,"uncaught":false,"x":140,"y":420,"wires":[["f52165d6428bf401","7c3d8d927c2b87d9"]]},{"id":"d6d87d75c231118a","type":"inject","z":"dff6fa938cfda5c8","name":"Send request","props":[{"p":"requestTimeout","v":"2000","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":370,"y":220,"wires":[["2a684bfe86bf3597"]]},{"id":"2a684bfe86bf3597","type":"http request","z":"dff6fa938cfda5c8","name":"","method":"GET","ret":"txt","paytoqs":"query","url":"https://reqres.in/api/users?delay=3","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":550,"y":280,"wires":[["df1c4cba60624654"]]},{"id":"df1c4cba60624654","type":"debug","z":"dff6fa938cfda5c8","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":740,"y":280,"wires":[]},{"id":"f52165d6428bf401","type":"debug","z":"dff6fa938cfda5c8","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":360,"y":440,"wires":[]},{"id":"7c3d8d927c2b87d9","type":"delay","z":"dff6fa938cfda5c8","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":360,"y":380,"wires":[["2a684bfe86bf3597"]]},{"id":"c2deb182e2c24e98","type":"comment","z":"dff6fa938cfda5c8","name":"Send request with timeout of 2000 mil seconds","info":"","x":290,"y":160,"wires":[]},{"id":"303cbead6a589517","type":"comment","z":"dff6fa938cfda5c8","name":"Set a delay of 3 seconds for the response.","info":"","x":660,"y":220,"wires":[]},{"id":"828fb1a255aaeb65","type":"comment","z":"dff6fa938cfda5c8","name":"Retry request after 5 seconds if request timeout","info":"","x":240,"y":320,"wires":[]}] -{% endrenderFlow %} +``` +:: ### User input validation @@ -56,6 +67,11 @@ In applications with user input, validation errors can disrupt the flow. The Cat In this example, the function node attempts to sort input data received from an inject node using the sort method, which only works with arrays. Sending other data types causes an error that the Catch node catches, which then sends a validation message to the user. -{% renderFlow %} +::render-flow +```json [{"id":"5b5392bda3519ca5","type":"inject","z":"a2240ea952051e81","name":"Send invalid input","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"This is string","payloadType":"str","x":240,"y":160,"wires":[["2c3f517165f7ee37"]]},{"id":"f63239dbca38b4bd","type":"catch","z":"a2240ea952051e81","name":"","scope":null,"uncaught":false,"x":200,"y":400,"wires":[["222121f5280a68d8","d0f062a664420050"]]},{"id":"222121f5280a68d8","type":"debug","z":"a2240ea952051e81","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":380,"y":380,"wires":[]},{"id":"c79bf5f53f284338","type":"inject","z":"a2240ea952051e81","name":"Send valid input","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1,2,3,4,5,6,7,8,9,10]","payloadType":"json","x":240,"y":240,"wires":[["2c3f517165f7ee37"]]},{"id":"2c3f517165f7ee37","type":"function","z":"a2240ea952051e81","name":"Sort data array","func":"let data = msg.payload;\ndata = data.sort((a,b)=>a+b)\nmsg.payload = data\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":620,"y":200,"wires":[[]]},{"id":"7c0aadd1766c2909","type":"debug","z":"a2240ea952051e81","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":600,"y":440,"wires":[]},{"id":"d0f062a664420050","type":"change","z":"a2240ea952051e81","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"please enter valid input ","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":440,"wires":[["7c0aadd1766c2909"]]},{"id":"51f9a4431f667dd4","type":"comment","z":"a2240ea952051e81","name":"Send alert when invalid input recieved","info":"","x":410,"y":300,"wires":[]},{"id":"a66c2f72744d7b33","type":"comment","z":"a2240ea952051e81","name":"Function sorts data array recived by inject node","info":"","x":440,"y":80,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="common" file="25-catch" node="catch" name="Catch"} +:: diff --git a/src/_includes/core-nodes/comment-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/comment.md similarity index 94% rename from src/_includes/core-nodes/comment-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/comment.md index 052329cd0c..2ae0b1ac93 100644 --- a/src/_includes/core-nodes/comment-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/comment.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Comment node" +metaTitle: "Node-RED - Comment Node" +navTitle: "Comment" +navOrder: 7 +--- + +# Comment + Adds documentation and explanatory notes directly within your flows. ## Where and why do we use the Comment node? @@ -34,6 +43,11 @@ Because Comment nodes don't impact runtime behavior, you can be as detailed and Add comments to explain what groups of nodes accomplish and why they're structured that way. This example shows a comment documenting an input validation section, helping future developers understand the requirements and logic. -{% renderFlow %} +::render-flow +```json [{"id":"comment-validation","type":"comment","z":"a1b2c3d4e5f6g7h8","name":"Input Validation Section","info":"This section validates incoming sensor data before processing.\n\nRequirements:\n- Temperature must be between -50 and 100°C\n- Humidity must be between 0 and 100%\n- Both values must be present\n\nInvalid data is logged and discarded to prevent\ndownstream processing errors.","x":210,"y":140,"wires":[]},{"id":"input-inject","type":"inject","z":"a1b2c3d4e5f6g7h8","name":"Sensor Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"temperature\":22,\"humidity\":65}","payloadType":"json","x":190,"y":200,"wires":[["validate-temp"]]},{"id":"validate-temp","type":"switch","z":"a1b2c3d4e5f6g7h8","name":"Check Temperature","property":"payload.temperature","propertyType":"msg","rules":[{"t":"btwn","v":"-50","vt":"num","v2":"100","vt2":"num"}],"checkall":"true","repair":false,"outputs":1,"x":400,"y":200,"wires":[["validate-humidity"]]},{"id":"validate-humidity","type":"switch","z":"a1b2c3d4e5f6g7h8","name":"Check Humidity","property":"payload.humidity","propertyType":"msg","rules":[{"t":"btwn","v":"0","vt":"num","v2":"100","vt2":"num"}],"checkall":"true","repair":false,"outputs":1,"x":620,"y":200,"wires":[["output-debug"]]},{"id":"output-debug","type":"debug","z":"a1b2c3d4e5f6g7h8","name":"Valid Data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":200,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="common" file="90-comment" node="comment" name="Comment"} +:: diff --git a/src/_includes/core-nodes/complete-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/complete.md similarity index 94% rename from src/_includes/core-nodes/complete-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/complete.md index 5e7c692377..04cfc0cf7e 100644 --- a/src/_includes/core-nodes/complete-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/complete.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Complete node" +metaTitle: "Node-RED - Complete Node" +navTitle: "Complete" +navOrder: 3 +--- + +# Complete + ## What is the Complete Node? The Complete Node in Node-RED is used to trigger a flow after a specified node completes its execution or a certain task. This is achieved by informing the Node-RED runtime about the completion of a task performed by the node itself. @@ -29,24 +38,27 @@ For batch processing tasks, the Complete Node can be used to signal the completi In Node-RED, certain nodes like WebSocket-out and MQTT-out do not have outputs to connect with. The Complete node in Node-RED can be helpful when you need to know when a process is done by those nodes. -# Example +## Example 1. In the example flow below, we have a WebSocket server, the Inject node sends data to the WebSocket server, and upon successful transmission to the WebSocket server, a Complete node handles the event. -{% renderFlow %} - +::render-flow +```json [{"id":"c10651a30bf2d6ab","type":"inject","z":"a2240ea952051e81","name":"send data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":300,"y":220,"wires":[["915f186a0a2b9663"]]},{"id":"915f186a0a2b9663","type":"websocket out","z":"a2240ea952051e81","name":"websocket server","server":"65bb0cfe75e94539","client":"","x":590,"y":220,"wires":[]},{"id":"ad8b360ab6cfeb02","type":"complete","z":"a2240ea952051e81","name":"complete","scope":["915f186a0a2b9663"],"uncaught":false,"x":280,"y":340,"wires":[["3a1e38c46e9e2e47"]]},{"id":"3a1e38c46e9e2e47","type":"debug","z":"a2240ea952051e81","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":640,"y":340,"wires":[]},{"id":"a8bc28796b265405","type":"comment","z":"a2240ea952051e81","name":"Sending data to websocket server","info":"","x":420,"y":160,"wires":[]},{"id":"40320b4bdb0a294d","type":"comment","z":"a2240ea952051e81","name":"Upon successful data transmission to the WebSocket server, the Complete node handles the event.","info":"","x":460,"y":280,"wires":[]},{"id":"65bb0cfe75e94539","type":"websocket-listener","path":"/ws/response","wholemsg":"false"}] - -{% endrenderFlow %} +``` +:: In the example flow below, we have an inject node sending an HTTP GET request to a mock API. After successful completion of the request, the complete node will handle the event. -{% renderFlow %} - +::render-flow +```json [{"id":"1be36b7e2b60d224","type":"inject","z":"a2240ea952051e81","name":"Get Todolist ","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":290,"y":240,"wires":[["9467be1692a1ae7b"]]},{"id":"93eedc09f6c55a7a","type":"complete","z":"a2240ea952051e81","name":"","scope":["9467be1692a1ae7b"],"uncaught":false,"x":290,"y":360,"wires":[["5f1a90e93fad1567"]]},{"id":"5f1a90e93fad1567","type":"debug","z":"a2240ea952051e81","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":360,"wires":[]},{"id":"7412de7c14e16942","type":"comment","z":"a2240ea952051e81","name":"Sending get request to API ","info":"","x":410,"y":180,"wires":[]},{"id":"6668136d53f3c650","type":"comment","z":"a2240ea952051e81","name":"Handle the request completion event.","info":"","x":410,"y":300,"wires":[]},{"id":"9467be1692a1ae7b","type":"http request","z":"a2240ea952051e81","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://jsonplaceholder.typicode.com/todos","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":570,"y":240,"wires":[[]]}] - -{% endrenderFlow %} +``` +:: ## Output Message: When a task is completed by a specified node in the Complete Node, it emits the same message object emitted by that specified node. + +::node-red-help{category="common" file="24-complete" node="complete" name="Complete"} +:: diff --git a/src/_includes/core-nodes/debug-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/debug.md similarity index 85% rename from src/_includes/core-nodes/debug-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/debug.md index 1aae775b9e..5ce33093f3 100644 --- a/src/_includes/core-nodes/debug-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/debug.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Debug node" +metaTitle: "Node-RED - Debug Node" +navTitle: "Debug" +navOrder: 2 +--- + +# Debug + Displays messages in the Debug sidebar for monitoring and troubleshooting flows. ## Where and why do we use the Debug node? @@ -50,22 +59,25 @@ The node can be enabled or disabled without modifying the flow. A disabled Debug The Copy Path feature lets you quickly copy the property path of any value in the debug output. This is a real time saver when building Change or Function nodes, helping you avoid typos and errors. Simply click the small icon next to any property in the debug output to copy its full path. - + ### Copy Value Copy Value gives you an exact copy of any property value to use in Inject, Change, or Function nodes. This is extremely useful when you need to simulate real data or when sharing example flows with others for troubleshooting. Click the value in the debug output and select Copy Value from the menu. - + ### Pin Open When debug output contains many nested properties, Pin Open helps you keep specific items expanded while collapsing others. This makes it easy to focus on the data you care about without losing your place as new messages arrive. - + ## Examples This example demonstrates common ways to use the Debug node for visualizing data and assisting development. It shows outputting to the sidebar, displaying node status, and using JSONata expressions. -![Debug Nodes](./images/debug-examples.png) \ No newline at end of file +![Debug Nodes](/docs/node-red/core-nodes/images/debug-examples.png) + +::node-red-help{category="common" file="21-debug" node="debug" name="Debug"} +:: diff --git a/nuxt/content-guides/node-red/core-nodes/common/index.md b/nuxt/content-guides/node-red/core-nodes/common/index.md new file mode 100644 index 0000000000..51579d0c6d --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/common/index.md @@ -0,0 +1,22 @@ +--- +title: "Node-RED common nodes" +metaTitle: "Node-RED Common Nodes" +navTitle: "Common nodes" +navOrder: 1 +meta: + description: "The Common nodes in Node-RED's default palette, and what each one is for." +--- + +# Common + +The **Common** section of Node-RED's default palette. Each page opens with why you +would reach for that node, then mirrors the node's built-in help. + +- [Inject](/docs/node-red/core-nodes/common/inject/): Guide on the Node-RED core node that injects a message into a flow +- [Debug](/docs/node-red/core-nodes/common/debug/) +- [Complete](/docs/node-red/core-nodes/common/complete/) +- [Catch](/docs/node-red/core-nodes/common/catch/) +- [Status](/docs/node-red/core-nodes/common/status/) +- [Link](/docs/node-red/core-nodes/common/link/) +- [Comment](/docs/node-red/core-nodes/common/comment/) +- [Unknown](/docs/node-red/core-nodes/common/unknown/) diff --git a/src/_includes/core-nodes/inject-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/inject.md similarity index 91% rename from src/_includes/core-nodes/inject-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/inject.md index e8331951c2..606f5849e4 100644 --- a/src/_includes/core-nodes/inject-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/inject.md @@ -1,3 +1,15 @@ +--- +title: "Node-RED Inject node" +metaTitle: "Node-RED - Inject Node" +navTitle: "Inject" +navOrder: 1 +meta: + description: "Guide on the Node-RED core node that injects a message into a flow" + keywords: "inject, message, flow" +--- + +# Inject + Triggers flows by injecting messages manually or automatically. ## Where and why do we use the Inject node? @@ -44,22 +56,31 @@ The node can set multiple message properties at once, allowing you to construct To setup state when starting Node-RED, the inject node can be set to trigger a flow once with minimal delay. This example injects a timestamp immediately after deployment or restart. -{% renderFlow %} +::render-flow +```json [{"id":"73cc510bee68600f","type":"inject","z":"80987f27785245a7","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":"0.1","topic":"","payload":"","payloadType":"date","x":190,"y":200,"wires":[["7f83bf24bdf7bc68"]]},{"id":"7f83bf24bdf7bc68","type":"debug","z":"80987f27785245a7","name":"Output once","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":370,"y":200,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Run a flow daily at midnight By selecting "at a specific time" in the Repeat section, the inject node can generate a message at set times. This example triggers at 23:59 (11:59 PM) every day, useful for daily data processing tasks. -{% renderFlow %} +::render-flow +```json [{"id":"998e844a7e50e275","type":"inject","z":"80987f27785245a7","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"59 23 * * *","once":false,"onceDelay":"0","topic":"","payload":"","payloadType":"date","x":190,"y":320,"wires":[["1e80f5229516e910"]]},{"id":"1e80f5229516e910","type":"debug","z":"80987f27785245a7","name":"Output daily at night","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":400,"y":320,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Inject a static string The Inject node can set the payload to static data like strings, numbers, or JSON objects. This example injects the string "Hello FlowFuse!" when triggered manually. -{% renderFlow %} +::render-flow +```json [{"id":"c0451e14f6b7eff0","type":"inject","z":"80987f27785245a7","name":"Inject a string","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello FlowFuse!","payloadType":"str","x":190,"y":280,"wires":[["9fbd8a0a9d21562a"]]},{"id":"9fbd8a0a9d21562a","type":"debug","z":"80987f27785245a7","name":"Output \"Hello FlowFuse\"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":410,"y":280,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="common" file="20-inject" node="inject" name="Inject"} +:: diff --git a/src/_includes/core-nodes/link-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/link.md similarity index 96% rename from src/_includes/core-nodes/link-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/link.md index 710d8b964e..03bca375d2 100644 --- a/src/_includes/core-nodes/link-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/link.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Link node" +metaTitle: "Node-RED - Link Node" +navTitle: "Link" +navOrder: 6 +--- + +# Link + Creates virtual connections between nodes without visible wires, helping organize complex flows. ## Where and why do we use the Link nodes? @@ -36,14 +45,21 @@ The virtual connections between Link nodes only become visible in the editor whe Link nodes separate different functional areas of a flow without cluttering the canvas with long wires. This example shows input processing separated from output handling, making each section easier to understand and maintain. -{% renderFlow %} +::render-flow +```json [{"id":"22ca6ef9668b5ddd","type":"group","z":"f77f61b90395f588","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["input-inject","process-input","link-out-process","link-in-output","format-output","output-debug"],"x":334,"y":5299,"w":792,"h":202},{"id":"input-inject","type":"inject","z":"f77f61b90395f588","g":"22ca6ef9668b5ddd","name":"Sensor Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"temperature\":22,\"humidity\":65}","payloadType":"json","x":450,"y":5340,"wires":[["process-input"]]},{"id":"process-input","type":"function","z":"f77f61b90395f588","g":"22ca6ef9668b5ddd","name":"Validate Input","func":"if (\n msg.payload &&\n msg.payload.temperature !== undefined &&\n msg.payload.humidity !== undefined\n) {\n return msg;\n}\nreturn null;","outputs":1,"x":760,"y":5340,"wires":[["link-out-process"]]},{"id":"link-out-process","type":"link out","z":"f77f61b90395f588","g":"22ca6ef9668b5ddd","name":"To Output Handler","mode":"link","links":["link-in-output"],"x":935,"y":5340,"wires":[]},{"id":"link-in-output","type":"link in","z":"f77f61b90395f588","g":"22ca6ef9668b5ddd","name":"From Input Process","links":["link-out-process"],"x":515,"y":5460,"wires":[["format-output"]]},{"id":"format-output","type":"function","z":"f77f61b90395f588","g":"22ca6ef9668b5ddd","name":"Format for Display","func":"msg.payload = `Temp: ${msg.payload.temperature}°C, Humidity: ${msg.payload.humidity}%`;\nreturn msg;","outputs":1,"x":770,"y":5460,"wires":[["output-debug"]]},{"id":"output-debug","type":"debug","z":"f77f61b90395f588","g":"22ca6ef9668b5ddd","name":"Display Output","active":true,"tosidebar":true,"complete":"payload","x":1000,"y":5460,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Reusable processing logic Link In nodes enable reusing the same processing logic from multiple sources without duplicating nodes. This example shows multiple inputs feeding into a single processing pipeline through Link nodes, useful for common transformations or validations. -{% renderFlow %} +::render-flow +```json [{"id":"source1-inject","type":"inject","z":"a1b2c3d4e5f6g7h8","name":"Source 1","props":[{"p":"payload"},{"p":"source","v":"sensor-1","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25","payloadType":"num","x":180,"y":140,"wires":[["link-out-1"]]},{"id":"source2-inject","type":"inject","z":"a1b2c3d4e5f6g7h8","name":"Source 2","props":[{"p":"payload"},{"p":"source","v":"sensor-2","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"30","payloadType":"num","x":180,"y":200,"wires":[["link-out-2"]]},{"id":"source3-inject","type":"inject","z":"a1b2c3d4e5f6g7h8","name":"Source 3","props":[{"p":"payload"},{"p":"source","v":"sensor-3","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"28","payloadType":"num","x":180,"y":260,"wires":[["link-out-3"]]},{"id":"link-out-1","type":"link out","z":"a1b2c3d4e5f6g7h8","name":"","mode":"link","links":["link-in-process"],"x":335,"y":140,"wires":[]},{"id":"link-out-2","type":"link out","z":"a1b2c3d4e5f6g7h8","name":"","mode":"link","links":["link-in-process"],"x":335,"y":200,"wires":[]},{"id":"link-out-3","type":"link out","z":"a1b2c3d4e5f6g7h8","name":"","mode":"link","links":["link-in-process"],"x":335,"y":260,"wires":[]},{"id":"link-in-process","type":"link in","z":"a1b2c3d4e5f6g7h8","name":"Common Processor","links":["link-out-1","link-out-2","link-out-3"],"x":195,"y":380,"wires":[["process-data"]]},{"id":"process-data","type":"function","z":"a1b2c3d4e5f6g7h8","name":"Convert to Fahrenheit","func":"msg.payload = (msg.payload * 9/5) + 32;\nmsg.payload = `${msg.source}: ${msg.payload.toFixed(1)}°F`;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":400,"y":380,"wires":[["result-debug"]]},{"id":"result-debug","type":"debug","z":"a1b2c3d4e5f6g7h8","name":"Processed Results","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":620,"y":380,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="common" file="60-link" node="link" name="Link"} +:: diff --git a/src/_includes/core-nodes/status-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/status.md similarity index 95% rename from src/_includes/core-nodes/status-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/status.md index 54bf1a99ad..46a3da97b1 100644 --- a/src/_includes/core-nodes/status-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/status.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Status node" +metaTitle: "Node-RED - Status Node" +navTitle: "Status" +navOrder: 5 +--- + +# Status + Monitors and captures status updates from other nodes in your flows. ## Where and why do we use the Status node? @@ -42,14 +51,21 @@ This information enables you to implement sophisticated monitoring logic, includ The Status node tracks the number of messages queued in a delay node. This example captures status updates from the delay node and processes them to monitor queue depth, useful for detecting backpressure or triggering alerts when the queue grows too large. -{% renderFlow %} +::render-flow +```json [{"id":"delay-inject","type":"inject","z":"f1e2d3c4b5a69788","name":"Send Messages","props":[{"p":"payload"}],"repeat":"0.5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":190,"y":200,"wires":[["delay-node"]]},{"id":"delay-node","type":"delay","z":"f1e2d3c4b5a69788","name":"Rate Limit Queue","pauseType":"rate","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":400,"y":200,"wires":[["delay-output"]]},{"id":"status-monitor","type":"status","z":"f1e2d3c4b5a69788","name":"Monitor Queue","scope":["delay-node"],"x":190,"y":280,"wires":[["status-debug"]]},{"id":"delay-output","type":"debug","z":"f1e2d3c4b5a69788","name":"Rate Limited Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":620,"y":200,"wires":[]},{"id":"status-debug","type":"debug","z":"f1e2d3c4b5a69788","name":"Queue Status","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"status","targetType":"msg","statusVal":"","statusType":"auto","x":390,"y":280,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Connection state monitoring Monitor the connection status of MQTT or other connection-based nodes. This example shows how to capture connection state changes and use them to trigger reconnection logic, send alerts, or update application state based on connectivity. -{% renderFlow %} +::render-flow +```json [{"id":"mqtt-in","type":"mqtt in","z":"a9b8c7d6e5f4g3h2","name":"MQTT Subscriber","topic":"sensor/#","qos":"0","datatype":"auto","broker":"mqtt-broker","nl":false,"rap":false,"inputs":0,"x":200,"y":180,"wires":[["mqtt-debug"]]},{"id":"mqtt-status","type":"status","z":"a9b8c7d6e5f4g3h2","name":"Connection Monitor","scope":["mqtt-in"],"x":210,"y":260,"wires":[["connection-check"]]},{"id":"mqtt-debug","type":"debug","z":"a9b8c7d6e5f4g3h2","name":"MQTT Messages","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":420,"y":180,"wires":[]},{"id":"connection-check","type":"switch","z":"a9b8c7d6e5f4g3h2","name":"Check Connection","property":"status.text","propertyType":"msg","rules":[{"t":"eq","v":"connected","vt":"str"},{"t":"eq","v":"disconnected","vt":"str"}],"checkall":"true","outputs":2,"x":430,"y":260,"wires":[["connected-debug"],["disconnected-debug"]]},{"id":"connected-debug","type":"debug","z":"a9b8c7d6e5f4g3h2","name":"Connected","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"status","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":240,"wires":[]},{"id":"disconnected-debug","type":"debug","z":"a9b8c7d6e5f4g3h2","name":"Disconnected","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"status","targetType":"msg","statusVal":"","statusType":"auto","x":660,"y":280,"wires":[]},{"id":"mqtt-broker","type":"mqtt-broker","name":"","broker":"localhost","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"birthTopic":"","birthQos":"0","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="common" file="25-status" node="status" name="Status"} +:: diff --git a/src/_includes/core-nodes/unknown-use-case.md b/nuxt/content-guides/node-red/core-nodes/common/unknown.md similarity index 80% rename from src/_includes/core-nodes/unknown-use-case.md rename to nuxt/content-guides/node-red/core-nodes/common/unknown.md index c5048c5abe..274d2621c0 100644 --- a/src/_includes/core-nodes/unknown-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/common/unknown.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Unknown node" +metaTitle: "Node-RED - Unknown Node" +navTitle: "Unknown" +navOrder: 8 +--- + +# Unknown + Represents nodes that are not installed in your Node-RED instance. ## Where and why do we see the Unknown node? @@ -18,14 +27,17 @@ Before importing flows, identify which node packages are installed in your sourc Access the Palette Manager through the Node-RED menu to see all installed packages and their versions. -![List of nodes installed, including unused nodes](./images/list-nodes-unused.png) +![List of nodes installed, including unused nodes](/docs/node-red/core-nodes/images/list-nodes-unused.png) ### Using System Info The System Info dialog provides a comprehensive list of installed node packages that you can view and copy for documentation. - + ## Migrating flows to FlowFuse -When migrating flows to FlowFuse, use the [nr-tools plugin](/docs/migration/introduction) for automatic package installation, credential migration, and complete flow transfer. This eliminates manual package identification and installation, making migration faster and less error-prone. \ No newline at end of file +When migrating flows to FlowFuse, use the [nr-tools plugin](/docs/migration/introduction) for automatic package installation, credential migration, and complete flow transfer. This eliminates manual package identification and installation, making migration faster and less error-prone. + +::node-red-help{category="common" file="98-unknown" node="unknown" name="Unknown"} +:: diff --git a/src/_includes/core-nodes/change-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/change.md similarity index 89% rename from src/_includes/core-nodes/change-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/change.md index 527a0c32b7..b09c2b11b0 100644 --- a/src/_includes/core-nodes/change-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/change.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Change node" +metaTitle: "Node-RED - Change Node" +navTitle: "Change" +navOrder: 3 +--- + +# Change + ## What's the Change node in Node-RED used for? The Change node in Node-RED is used for modifying the content of messages within a flow. It allows you to add, remove, modify, or set message properties and payload values, making it a fundamental node for data transformation and manipulation. The Change node is essential for preparing data for further processing, formatting messages for specific outputs, and adapting data to suit the requirements of downstream nodes in a flow. @@ -7,44 +16,59 @@ Use cases for the Change node include: 1. **Data Transformation**: You can use the Change node to transform data from one format to another. For example, you can convert temperature values from Celsius to Fahrenheit, translate textual information, or convert timestamps to a different format. -![Data Transform](./images/change-data-transform.png) +![Data Transform](/docs/node-red/core-nodes/images/change-data-transform.png) -{% renderFlow %} +::render-flow +```json [{"id":"1cd48684.04dbab","type":"tab","label":"Temperature Conversion","disabled":false,"info":""},{"id":"d803c3c9.0761d8","type":"inject","z":"1cd48684.04dbab","name":"Celsius","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"25","payloadType":"num","x":170,"y":100,"wires":[["21b83b07.36564"]]},{"id":"21b83b07.36564","type":"change","z":"1cd48684.04dbab","name":"Convert to Fahrenheit","rules":[{"t":"set","p":"payload","pt":"msg","to":"$round(($number(payload) * 9/5) + 32, 2)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":100,"wires":[["dc92db44.a50c08"]]},{"id":"dc92db44.a50c08","type":"debug","z":"1cd48684.04dbab","name":"Fahrenheit","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":610,"y":100,"wires":[]}] -{% endrenderFlow %} +``` +:: 2. **Message Filtering**: The Change node can filter out messages based on specific conditions. You can use the Change node to route messages to different outputs, discard irrelevant messages, or take specific actions based on message properties. -![Message Filter](./images/change-message-filter.png) -{% renderFlow %} +![Message Filter](/docs/node-red/core-nodes/images/change-message-filter.png) +::render-flow +```json [{"id":"a4569070.48f9d8","type":"inject","z":"d238e48a.85c08","name":"Simulate Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Bonjour","payloadType":"str","x":170,"y":120,"wires":[["53c33235.e5c248"]]}] -{% endrenderFlow %} +``` +:: 3. **Message Enrichment**: The Change node allows you to add or modify properties in a message to enrich its content. For instance, you can add timestamps, add contextual information, or set specific identifiers for tracking purposes. -![Message Enrichment](./images/change-message-enrich.png) -{% renderFlow %} +![Message Enrichment](/docs/node-red/core-nodes/images/change-message-enrich.png) +::render-flow +```json [{"id":"34d49c7f.505d58","type":"tab","label":"Message Enrichment","disabled":false,"info":""},{"id":"f7c09e8f.01af2","type":"inject","z":"34d49c7f.505d58","name":"Simulate Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, world!","payloadType":"str","x":170,"y":120,"wires":[["2ef767d1.3b5f32"]]},{"id":"2ef767d1.3b5f32","type":"change","z":"34d49c7f.505d58","name":"Add Timestamp","rules":[{"t":"set","p":"timestamp","pt":"msg","to":"$now()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":120,"wires":[["e3b0c949.c20ba"]]},{"id":"e3b0c949.c20ba","type":"debug","z":"34d49c7f.505d58","name":"Enriched Message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":120,"wires":[]}] -{% endrenderFlow %} +``` +:: 4. **Renaming Properties**: The Change node allows you to rename message properties, making it easier to understand and work with data at various points in your flow. -![Example](./images/change-rename-property.png) -{% renderFlow %} +![Example](/docs/node-red/core-nodes/images/change-rename-property.png) +::render-flow +```json [{"id":"5e6054b9.8b6a64","type":"tab","label":"Renaming Properties","disabled":false,"info":""},{"id":"ca8a03f3.119d18","type":"inject","z":"5e6054b9.8b6a64","name":"Simulate Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"data\":123}","payloadType":"json","x":170,"y":120,"wires":[["d44de052.a77d"]]},{"id":"d44de052.a77d","type":"change","z":"5e6054b9.8b6a64","name":"Rename Property","rules":[{"t":"set","p":"sensorData","pt":"msg","to":"payload.data","tot":"msg"},{"t":"delete","p":"payload.data","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":380,"y":120,"wires":[["690e6de1.3ad218"]]},{"id":"690e6de1.3ad218","type":"debug","z":"5e6054b9.8b6a64","name":"Renamed Property","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"sensorData","targetType":"msg","statusVal":"","statusType":"auto","x":570,"y":120,"wires":[]}] -{% endrenderFlow %} +``` +:: 5. **Default Values**: If a message lacks certain properties, the Change node can set default values for those properties, ensuring consistency in the data flow. -![Example](./images/change-default.png) -{% renderFlow %} +![Example](/docs/node-red/core-nodes/images/change-default.png) +::render-flow +```json [{"id":"3abbe88b.537ac4","type":"tab","label":"Default Values","disabled":false,"info":""},{"id":"947dcab3.47e8b","type":"inject","z":"3abbe88b.537ac4","name":"Simulate Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Alice\"}","payloadType":"json","x":170,"y":120,"wires":[["a8d3ce5c.7982c"]]},{"id":"a8d3ce5c.7982c","type":"change","z":"3abbe88b.537ac4","name":"Set Default Age","rules":[{"t":"missing","p":"payload.age","pt":"msg"},{"t":"set","p":"payload.age","pt":"msg","to":"25","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":370,"y":120,"wires":[["f5892567.3bbd8"]]},{"id":"f5892567.3bbd8","type":"debug","z":"3abbe88b.537ac4","name":"Enriched Data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":550,"y":120,"wires":[]}] -{% endrenderFlow %} +``` +:: 6. **Message Formatting**: When sending data to external systems or services, the Change node can format the message payload in the required format (e.g., JSON, XML) or adjust data to match specific API requirements. -![Example](./images/change-message-format.png) -{% renderFlow %} +![Example](/docs/node-red/core-nodes/images/change-message-format.png) +::render-flow +```json [{"id":"49e37717.8c3d98","type":"tab","label":"Message Formatting","disabled":false,"info":""},{"id":"4b97a1f2.bf7a0c","type":"inject","z":"49e37717.8c3d98","name":"Simulate Data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"temperature\":28,\"humidity\":50}","payloadType":"json","x":170,"y":120,"wires":[["f303ce36.e3c1f"]]},{"id":"f303ce36.e3c1f","type":"change","z":"49e37717.8c3d98","name":"Format as JSON","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.temperature & \" °C, Humidity: \" & payload.humidity & \"%\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":360,"y":120,"wires":[["d7c1af9f.f1099"]]},{"id":"d7c1af9f.f1099","type":"debug","z":"49e37717.8c3d98","name":"Formatted Message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":580,"y":120,"wires":[]}] -{% endrenderFlow %} +``` +:: + +Overall, the Change node is a crucial tool for data manipulation and orchestration in Node-RED flows. Its flexibility and range of operations make it an essential node for customizing messages according to your specific use cases and the requirements of the nodes within your flow. -Overall, the Change node is a crucial tool for data manipulation and orchestration in Node-RED flows. Its flexibility and range of operations make it an essential node for customizing messages according to your specific use cases and the requirements of the nodes within your flow. \ No newline at end of file +::node-red-help{category="function" file="15-change" node="change" name="Change"} +:: diff --git a/src/_includes/core-nodes/delay-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/delay.md similarity index 90% rename from src/_includes/core-nodes/delay-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/delay.md index ccf5ca8630..6f99710b31 100644 --- a/src/_includes/core-nodes/delay-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/delay.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Delay node" +metaTitle: "Node-RED - Delay Node" +navTitle: "Delay" +navOrder: 6 +--- + +# Delay + ## What's the Delay node in Node-RED used for? The Delay node allows you to introduce a delay in the flow of messages between nodes. It can be useful in various scenarios where you need to control the timing of message processing. For example, the delay node can limit the rate at which messages are processed downstream or throttle the flow of messaging. Both can be useful for interacting with external systems that may have limitations in place. @@ -19,11 +28,13 @@ Here are some other use cases for using the Delay node: An example of using the Delay node to rate limit http request to an external API. -![Delay node properties](./images/delay-node-2.png) +![Delay node properties](/docs/node-red/core-nodes/images/delay-node-2.png) -{% renderFlow %} +::render-flow +```json [{"id":"1f825afc.866efc","type":"delay","z":"e92fb6c3b304fd7c","name":"Rate Limit","pauseType":"rate","timeout":"20","timeoutUnits":"seconds","rate":"10","nbRateUnits":"","rateUnits":"minute","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":620,"y":120,"wires":[["26f1f0e3.65e3c8"]]}] -{% endrenderFlow %} +``` +:: ### Reset the queue for the delay node @@ -32,8 +43,13 @@ There's situations however where the queue needs to be cleared. This is done by When a `reset` property is set, the queue will be empty. This is useful for when the lack of an event might need to send a notification. Schedule the notification -to be sent but allow a positive event to cancel the notification. In the example below; say you want to turn off the lights if no movement was detected for a period of time, you set the delay node to that time. When there's motion detected, you than send a `msg.reset` message to the delay node to cancel them turning of the light. This depends on an inject node set to [send a message on an interval](/node-red/core-nodes/inject/#run-a-flow-daily-at-midnight) to turn the lights off. +to be sent but allow a positive event to cancel the notification. In the example below; say you want to turn off the lights if no movement was detected for a period of time, you set the delay node to that time. When there's motion detected, you than send a `msg.reset` message to the delay node to cancel them turning of the light. This depends on an inject node set to [send a message on an interval](/docs/node-red/core-nodes/common/inject/#run-a-flow-daily-at-midnight) to turn the lights off. -{% renderFlow %} +::render-flow +```json [{"id":"06b93157ff07c9b1","type":"delay","z":"e512003df3c971c7","name":"","pauseType":"delay","timeout":"5","timeoutUnits":"seconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":460,"y":100,"wires":[["f345c902d09aaf76"]]},{"id":"1965e58562943d69","type":"inject","z":"e512003df3c971c7","name":"Motion detected","props":[{"p":"reset","v":"true","vt":"bool"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":250,"y":160,"wires":[["06b93157ff07c9b1"]]},{"id":"f345c902d09aaf76","type":"debug","z":"e512003df3c971c7","name":"Never receive a message","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":670,"y":100,"wires":[]},{"id":"834f0812a01be7a4","type":"inject","z":"e512003df3c971c7","name":"Turn off lights","props":[{"p":"payload"}],"repeat":"10","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":100,"wires":[["06b93157ff07c9b1"]]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="function" file="89-delay" node="delay" name="Delay"} +:: diff --git a/src/_includes/core-nodes/exec-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/exec.md similarity index 87% rename from src/_includes/core-nodes/exec-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/exec.md index 0f4c0cd513..8c7c65540d 100644 --- a/src/_includes/core-nodes/exec-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/exec.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Exec node" +metaTitle: "Node-RED - Exec Node" +navTitle: "Exec" +navOrder: 8 +--- + +# Exec + Node-RED is written in Javascript, as are the custom nodes in the [Flows Library](https://flows.nodered.org/search?type=node). At times it would be great to use programs written in other languages. In this guide we'll explain @@ -11,7 +20,7 @@ outputs. Let's create a basic example and work from there. Let's connect the inject to the `exec` input, and connect the three dots to debug nodes. I've set the command in the exec node to `date`. -!["Wired up exec node"](./images/wired-up-exec-node.png) +!["Wired up exec node"](/docs/node-red/core-nodes/images/wired-up-exec-node.png) When triggered, you'll see two outputs in the debug pane. One from `Standard out`; the date and timestamp of the execution and the `Exit code` will output too. @@ -35,7 +44,7 @@ want. When there's continous output you'll need to select `spawn` mode in the settings windown of the `exec` node: -![spawn mode for exec in Node-RED](./images/spawn-mode-exec.png) +![spawn mode for exec in Node-RED](/docs/node-red/core-nodes/images/spawn-mode-exec.png) This will produce a message per line to the standard out output for further processing. @@ -50,3 +59,6 @@ will not be expanded and Node-RED will try to remove the file or directory calle On FlowFuse Cloud, the Node-RED exec nodes are disabled. The Stacks, that is the containers that Node-RED runs in, don't include any accessible executatables so there would be no benefit to running exec commands. All the data is exposed through Javascript and Node.JS already. + +::node-red-help{category="function" file="90-exec" node="exec" name="Exec"} +:: diff --git a/src/_includes/core-nodes/filter-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/filter.md similarity index 95% rename from src/_includes/core-nodes/filter-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/filter.md index 27369707f7..ce88a6fe36 100644 --- a/src/_includes/core-nodes/filter-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/filter.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Filter node" +metaTitle: "Node-RED - Filter Node" +navTitle: "Filter" +navOrder: 9 +--- + +# Filter + ## What's the Filter node in Node-RED used for? The Filter node, previously called "Report by Exception" (RBE), has two modes of @@ -45,9 +54,11 @@ With 3 messages send to the filter node; 1, 2, 2, the following flow will send through the first messages, `1` and `2` respectively. Then filter out the last `2` message as no changes were observed since it sent on the previous message. -{% renderFlow %} +::render-flow +```json [{"id":"5adf6b757e2a7bb2","type":"rbe","z":"97a2668540e2f3ba","name":"Only report changes","func":"rbe","gap":"","start":"","inout":"out","septopics":true,"property":"payload","topi":"topic","x":420,"y":100,"wires":[["6682a9e8826ad09b"]]},{"id":"f60dc26f8d634312","type":"inject","z":"97a2668540e2f3ba","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1,2,2]","payloadType":"json","x":130,"y":100,"wires":[["5b2b61bb112b23e7"]]},{"id":"6682a9e8826ad09b","type":"debug","z":"97a2668540e2f3ba","name":"Print changes","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":620,"y":100,"wires":[]},{"id":"5b2b61bb112b23e7","type":"split","z":"97a2668540e2f3ba","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":250,"y":100,"wires":[["5adf6b757e2a7bb2"]]}] -{% endrenderFlow %} +``` +:: ### Report changes, ignore the initial value @@ -56,9 +67,11 @@ sentinel value. Each change afterwards is send onwards. In this case printing ju 2 once. The first message; `1` is remembered, and the next message afterwards is the only change in the stream of values. -{% renderFlow %} +::render-flow +```json [{"id":"d5c20441fc01294b","type":"rbe","z":"97a2668540e2f3ba","name":"Ignore first message, only report changes","func":"rbei","gap":"","start":"","inout":"out","septopics":true,"property":"payload","topi":"topic","x":480,"y":180,"wires":[["c5845cc63c81f81d"]]},{"id":"eef24d9b8c3f98c7","type":"inject","z":"97a2668540e2f3ba","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1,2,2]","payloadType":"json","x":130,"y":180,"wires":[["9e8e79801228c3bb"]]},{"id":"c5845cc63c81f81d","type":"debug","z":"97a2668540e2f3ba","name":"Print changes","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":740,"y":180,"wires":[]},{"id":"9e8e79801228c3bb","type":"split","z":"97a2668540e2f3ba","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":250,"y":180,"wires":[["d5c20441fc01294b"]]}] -{% endrenderFlow %} +``` +:: ### Report changes larger than a certain percentage "Deadband" @@ -72,9 +85,11 @@ and sends it on. The third message is equal to the sentinel value and thus filtered. The last value, `1`, is a 50% change compared to the sentinel value of `2` and send forward. -{% renderFlow %} +::render-flow +```json [{"id":"af9390a840a0b28a","type":"rbe","z":"97a2668540e2f3ba","name":"Report changes over ","func":"deadbandEq","gap":"50%","start":"","inout":"out","septopics":true,"property":"payload","topi":"topic","x":420,"y":260,"wires":[["f76f04bfb73ad8db"]]},{"id":"2a28d48e44fd6450","type":"inject","z":"97a2668540e2f3ba","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1,2,2,1]","payloadType":"json","x":130,"y":260,"wires":[["9155ade283e76ab6"]]},{"id":"f76f04bfb73ad8db","type":"debug","z":"97a2668540e2f3ba","name":"Print changes","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":620,"y":260,"wires":[]},{"id":"9155ade283e76ab6","type":"split","z":"97a2668540e2f3ba","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":250,"y":260,"wires":[["af9390a840a0b28a"]]}] -{% endrenderFlow %} +``` +:: ### Report only on one specific value @@ -83,6 +98,11 @@ used to only report a single value when it occurs. For example, to only report integers that are equal to 2, the start value is set to 2 and the change cannot be greater of equal to 1. -{% renderFlow %} +::render-flow +```json [{"id":"6b70bed3e7360f58","type":"rbe","z":"97a2668540e2f3ba","name":"Only send 2's","func":"narrowbandEq","gap":"1","start":"2","inout":"out","septopics":false,"property":"payload","topi":"topic","x":400,"y":340,"wires":[["5f02aa2b43499ca8"]]},{"id":"a5b05fcbe6fdf53e","type":"inject","z":"97a2668540e2f3ba","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1,2,2]","payloadType":"json","x":130,"y":340,"wires":[["e58f98ecb220d7f3"]]},{"id":"5f02aa2b43499ca8","type":"debug","z":"97a2668540e2f3ba","name":"Print the same values","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":600,"y":340,"wires":[]},{"id":"e58f98ecb220d7f3","type":"split","z":"97a2668540e2f3ba","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":250,"y":340,"wires":[["6b70bed3e7360f58"]]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="function" file="rbe" node="rbe" name="Filter"} +:: diff --git a/src/_includes/core-nodes/function-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/function.md similarity index 97% rename from src/_includes/core-nodes/function-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/function.md index 02c7eb5a67..cd117cc30e 100644 --- a/src/_includes/core-nodes/function-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/function.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Function node" +metaTitle: "Node-RED - Function Node" +navTitle: "Function" +navOrder: 1 +--- + +# Function + ## What Is a Function node in Node-RED? In Node-RED, a function node allows you to write custom JavaScript code to process message objects in your flow. It's used for specific tasks that can't be accomplished with the standard built-in nodes alone. When you write custom JavaScript in a function node, this code gets executed every time a message passes through the node. @@ -106,18 +115,21 @@ The following Node-RED objects can be accessed in a function node: In the example flow below, we have a function that converts temperature data, simulated using an inject node with a random number, from Celsius to Fahrenheit. Additionally, it performs other formatting. -{% renderFlow %} - +::render-flow +```json [{"id":"b138b603c8f94cf8","type":"inject","z":"a2240ea952051e81","name":"Temperature sensor","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"$random() * 100","payloadType":"jsonata","x":240,"y":240,"wires":[["ab41b0f6e6fa58aa"]]},{"id":"ab41b0f6e6fa58aa","type":"function","z":"a2240ea952051e81","name":"Convert Celsius to Fahrenheit","func":"// Extract temperature reading from the incoming message\nconst temperatureCelsius = msg.payload;\n\n// Convert Celsius to Fahrenheit\nconst temperatureFahrenheit = (temperatureCelsius * 9 / 5) + 32;\n\n// Round the temperature Fahrenheit to two decimal places and convert it back to a number\nconst roundedTemperatureFahrenheit = parseFloat(temperatureFahrenheit.toFixed(2));\n\n// Update the message payload with the temperature in Fahrenheit\nmsg.payload = roundedTemperatureFahrenheit;\n\n// Return the modified message object\nreturn msg;\n\n\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":240,"wires":[["b7271bd3259bc56d"]]},{"id":"b7271bd3259bc56d","type":"debug","z":"a2240ea952051e81","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":240,"wires":[]},{"id":"8f553a15e4f5a8dd","type":"comment","z":"a2240ea952051e81","name":"Convert received temperature from Celsius to Fahrenheit and format it","info":"","x":510,"y":180,"wires":[]}] - -{% endrenderFlow %} +``` +:: 2. **Conditional Routing:** When dealing with a broad range of conditions that require intricate logic for each case, the switch node may fall short. In such scenarios, using a function node with multiple outputs can be benificial. In the example flow below, we have an inject node generating a random number and sending it to the function node. We've set up the function node to evaluate the received numeric value and perform conditional routing based on predefined ranges, sending the message to different outputs accordingly. -{% renderFlow %} - +::render-flow +```json [{"id":"6a1f8dd5e5037d24","type":"inject","z":"a2240ea952051e81","name":"send random number","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 100","payloadType":"jsonata","x":180,"y":180,"wires":[["c4ef3a83b54cd6eb"]]},{"id":"c4ef3a83b54cd6eb","type":"function","z":"a2240ea952051e81","name":"Check against dynamic ranges","func":"// Extract numeric value from the incoming message\nconst numericValue = msg.payload;\n\n// Define ranges for conditional routing\nconst range1 = { min: 0, max: 10 };\nconst range2 = { min: 11, max: 20 };\nconst range3 = { min: 21, max: 30 };\n\n// Check numeric value against ranges\nif (numericValue >= range1.min && numericValue <= range1.max) {\n // Value falls within range 1\n return [msg, null, null];\n} else if (numericValue >= range2.min && numericValue <= range2.max) {\n // Value falls within range 2\n return [null, msg, null];\n} else if (numericValue >= range3.min && numericValue <= range3.max) {\n // Value falls within range 3\n return [null, null, msg];\n} else {\n // Value falls outside all ranges\n node.warn(\"Value falls outside all defined ranges.\");\n return null;\n}\n","outputs":3,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":470,"y":180,"wires":[["a3af86e82cd2267a"],["35233d68a07ad23a"],["5a99ebc4ee439757"]]},{"id":"a3af86e82cd2267a","type":"debug","z":"a2240ea952051e81","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":140,"wires":[]},{"id":"35233d68a07ad23a","type":"debug","z":"a2240ea952051e81","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":180,"wires":[]},{"id":"5a99ebc4ee439757","type":"debug","z":"a2240ea952051e81","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"b1e8ca2faa3faafc","type":"comment","z":"a2240ea952051e81","name":"The function checks the numeric value against dynamic ranges and sends them to outputs accordingly.","info":"","x":450,"y":80,"wires":[]}] +``` +:: -{% endrenderFlow %} +::node-red-help{category="function" file="10-function" node="function" name="Function"} +:: diff --git a/nuxt/content-guides/node-red/core-nodes/function/index.md b/nuxt/content-guides/node-red/core-nodes/function/index.md new file mode 100644 index 0000000000..47390d121d --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/function/index.md @@ -0,0 +1,23 @@ +--- +title: "Node-RED function nodes" +metaTitle: "Node-RED Function Nodes" +navTitle: "Function nodes" +navOrder: 2 +meta: + description: "The Function nodes in Node-RED's default palette, and what each one is for." +--- + +# Function + +The **Function** section of Node-RED's default palette. Each page opens with why you +would reach for that node, then mirrors the node's built-in help. + +- [Function](/docs/node-red/core-nodes/function/function/) +- [Switch](/docs/node-red/core-nodes/function/switch/) +- [Change](/docs/node-red/core-nodes/function/change/) +- [Range](/docs/node-red/core-nodes/function/range/) +- [Template](/docs/node-red/core-nodes/function/template/) +- [Delay](/docs/node-red/core-nodes/function/delay/) +- [Trigger](/docs/node-red/core-nodes/function/trigger/) +- [Exec](/docs/node-red/core-nodes/function/exec/) +- [Filter](/docs/node-red/core-nodes/function/filter/) diff --git a/src/_includes/core-nodes/range-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/range.md similarity index 82% rename from src/_includes/core-nodes/range-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/range.md index b069115ade..7e9176333c 100644 --- a/src/_includes/core-nodes/range-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/range.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Range node" +metaTitle: "Node-RED - Range Node" +navTitle: "Range" +navOrder: 4 +--- + +# Range + ## What's the Range node in Node-RED used for? The "Range" node in Node-RED allows you to map a numeric value from one range to another. For example, if you wanted to map miles to kilometers, you can specific the input range at 1 to 100 and the target range as 1 to 160. @@ -17,8 +26,13 @@ Besides unit conversion, the range node can be used for: An example of a change node that converts from miles to kilometers. -![Range properties](./images/range-node2.png) +![Range properties](/docs/node-red/core-nodes/images/range-node2.png) -{% renderFlow %} +::render-flow +```json [{"id":"183739aecda7dc43","type":"range","z":"e92fb6c3b304fd7c","minin":"1","maxin":"100","minout":"0","maxout":"160","action":"scale","round":true,"property":"payload","name":"Miles >Km","x":390,"y":220,"wires":[["9c4edf7250c34cdb"]]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="function" file="16-range" node="range" name="Range"} +:: diff --git a/src/_includes/core-nodes/switch-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/switch.md similarity index 90% rename from src/_includes/core-nodes/switch-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/switch.md index 4625e47dd4..44bb248b81 100644 --- a/src/_includes/core-nodes/switch-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/switch.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Switch node" +metaTitle: "Node-RED - Switch Node" +navTitle: "Switch" +navOrder: 2 +--- + +# Switch + ## What is the Switch node used for in Node-RED The Switch node allows you to route messages based on certain conditions. It acts as a decision-making tool within your flow, allowing you to define rules for directing messages to different output branches. @@ -18,8 +27,13 @@ Here are some common use cases for using the Switch node in Node-RED: ## Examples -![Switch Node Example](./images/switch-example-2.png) +![Switch Node Example](/docs/node-red/core-nodes/images/switch-example-2.png) -{% renderFlow %} +::render-flow +```json [{"id":"1401d664616fc956","type":"tab","label":"Flow 8","disabled":false,"info":"","env":[]},{"id":"inject-node","type":"inject","z":"1401d664616fc956","name":"Simulate Temperature Data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"sensorType\": \"temperature\", \"value\": 28}","payloadType":"json","x":190,"y":200,"wires":[["temperature-route-node"]]},{"id":"temperature-route-node","type":"switch","z":"1401d664616fc956","name":"Temperature Routing","property":"payload.value","propertyType":"msg","rules":[{"t":"lt","v":"25","vt":"num"},{"t":"gte","v":"25","vt":"num"}],"checkall":"true","outputs":2,"x":470,"y":200,"wires":[["below-25-node"],["above-25-node"]]},{"id":"below-25-node","type":"debug","z":"1401d664616fc956","name":"Below 25°C","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":680,"y":160,"wires":[]},{"id":"above-25-node","type":"debug","z":"1401d664616fc956","name":"Above 25°C","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":680,"y":240,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="function" file="10-switch" node="switch" name="Switch"} +:: diff --git a/src/_includes/core-nodes/template-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/template.md similarity index 79% rename from src/_includes/core-nodes/template-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/template.md index 5cee418aaa..943ae423e7 100644 --- a/src/_includes/core-nodes/template-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/template.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Template node" +metaTitle: "Node-RED - Template Node" +navTitle: "Template" +navOrder: 5 +--- + +# Template + The "Template" node is used to create and manipulate text templates. For example for generating HTML, configuration files, our other text based strings. The Template node allows you to generate dynamic content by injecting data into predefined templates using the Mustache templating language. When there's no need for dynamic templates, the `Format` can be set to `Plain Text`, which has a slight performance benefit. @@ -13,11 +22,13 @@ You can connect the Template node to a source of data, such as an MQTT input, HT In the Template node configuration, you define the template using text based formatting languages like HTML with the Mustache syntax. Mustache is a simple and "logic-less" templating language that allows you to insert variables and expressions into your template. -For example, you can define a template like this: {% raw %}`

Hello {{payload.name}}!

`{% endraw %}. +For example, you can define a template like this: `

Hello {{payload.name}}!

`. -{% renderFlow %} -[{"id":"97d742eec1cb7dbf","type":"inject","z":"a6b7ede2e13fcbdf","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\": \"FlowFuse\"}","payloadType":"json","x":180,"y":60,"wires":[["c85d70b41f374f02"]]},{"id":"c85d70b41f374f02","type":"template","z":"a6b7ede2e13fcbdf","name":"Template using payload.name","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"

Hello {% raw %}`{{payload.name}}!{% endraw %}

","output":"str","x":450,"y":60,"wires":[["6df7215459dfb240"]]},{"id":"6df7215459dfb240","type":"debug","z":"a6b7ede2e13fcbdf","name":"Print \"

Hello, FlowFuse!

\"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":60,"wires":[]}] -{% endrenderFlow %} +::render-flow +```json +[{"id":"97d742eec1cb7dbf","type":"inject","z":"a6b7ede2e13fcbdf","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\": \"FlowFuse\"}","payloadType":"json","x":180,"y":60,"wires":[["c85d70b41f374f02"]]},{"id":"c85d70b41f374f02","type":"template","z":"a6b7ede2e13fcbdf","name":"Template using payload.name","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"

Hello {{payload.name}}!

","output":"str","x":450,"y":60,"wires":[["6df7215459dfb240"]]},{"id":"6df7215459dfb240","type":"debug","z":"a6b7ede2e13fcbdf","name":"Print \"

Hello, FlowFuse!

\"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":750,"y":60,"wires":[]}] +``` +:: This example will output `

Hello FlowFuse!

`. @@ -33,22 +44,18 @@ as JSON, to further use the message as an object, not as string. #### Input data ```js -{%- raw -%} { payload: ['ACME', 1] } -{% endraw %} ``` #### Template node configuration * Template: ```text - {%- raw -%} { "product": "{{payload.0}}", "version": {{payload.1}} } - {% endraw %} ``` * Format: `Mustache template` * Output as: `Parsed JSON` @@ -56,9 +63,7 @@ as JSON, to further use the message as an object, not as string. #### Output ```json -{%- raw -%} { "product": "ACME", "version": 1 } -{% endraw %} ``` ### Generating JavaScript object from YAML @@ -66,31 +71,25 @@ as JSON, to further use the message as an object, not as string. Node-RED can also parse YAML #### Input data ```js -{%- raw -%} { topic: 'FlowFuse', payload: 3000 } -{% endraw %} ``` #### template node configuration * Template: ```text - {%- raw -%} options: title: {{topic}} port: {{payload}} - {% endraw %} ``` * Format: `Mustache template` * Output as: `Parsed JSON` #### Output ```json -{%- raw -%} { "title": "FlowFuse", "port": 3000 } -{% endraw %} ``` ## Comments @@ -100,10 +99,8 @@ which will not appear in the output. Comments work about the same as the normal syntax, with a `!` after the opening curly brackets: ```mustache -{%- raw -%} {{! this won't show }} This text will be in the output -{% endraw %} ``` Will result in `This text will be in the output`. Note there's no empty line character `\n`. @@ -111,4 +108,7 @@ Will result in `This text will be in the output`. Note there's no empty line cha ### Mustache partials Mustache by default supports partials to include. This is an unsupported feature -in Node-RED. \ No newline at end of file +in Node-RED. + +::node-red-help{category="function" file="80-template" node="template" name="Template"} +:: diff --git a/src/_includes/core-nodes/trigger-use-case.md b/nuxt/content-guides/node-red/core-nodes/function/trigger.md similarity index 96% rename from src/_includes/core-nodes/trigger-use-case.md rename to nuxt/content-guides/node-red/core-nodes/function/trigger.md index 1249c178b2..bf0ad82317 100644 --- a/src/_includes/core-nodes/trigger-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/function/trigger.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Trigger node" +metaTitle: "Node-RED - Trigger Node" +navTitle: "Trigger" +navOrder: 7 +--- + +# Trigger + ## What is the Trigger Node in Node-RED? The Trigger node in Node-RED facilitates the initiation and repetition of messages at customizable intervals, offering precise control over when messages are sent, their recurrence frequency, and optional delays. This functionality is valuable for automating tasks and efficiently managing communication flow within Node-RED flows. @@ -35,18 +44,19 @@ The Inject node lets you send messages at specific intervals but it starts immed 1. In the example flow below, we've simulated a door lock system. We employ an inject node to input a password, which is then verified against a specified password in a switch node. If the input password is correct, a trigger node sends a payload to open the door. After 4 seconds, a second message is sent to close the door. This can also be utilized for scenarios involving turning an LED on and off. -{% renderFlow %} - +::render-flow +```json [{"id":"39333c055828b138","type":"inject","z":"39d029f8bb26b820","name":"swap card","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"pass123","payloadType":"str","x":200,"y":240,"wires":[["7fec2333b929d424"]]},{"id":"6e6bc184119065fc","type":"trigger","z":"39d029f8bb26b820","name":"","op1":"opening door","op2":"closing door","op1type":"str","op2type":"str","duration":"4","extend":false,"overrideDelay":false,"units":"s","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":540,"y":240,"wires":[["d0ccd1e7dd9e313a"]]},{"id":"7fec2333b929d424","type":"switch","z":"39d029f8bb26b820","name":"","property":"payload","propertyType":"msg","rules":[{"t":"eq","v":"pass123","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":350,"y":240,"wires":[["6e6bc184119065fc"]]},{"id":"d0ccd1e7dd9e313a","type":"debug","z":"39d029f8bb26b820","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":240,"wires":[]},{"id":"5662274e316d9317","type":"comment","z":"39d029f8bb26b820","name":"Simulating a door lock system where entering the correct password will open the door for 5 seconds and then will close.","info":"","x":480,"y":140,"wires":[]}] - -{% endrenderFlow %} +``` +:: 2. In the example flow below, we have a trigger node polling data continuously from an API. It polls data at specific interval when a message is received and stops if a message is received with the property of 'reset'. This can also be used in scenarios where you want to read sensor data with custom control. -{% renderFlow %} - +::render-flow +```json [{"id":"e682b48eea38c319","type":"inject","z":"a2240ea952051e81","name":"Start polling","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":170,"y":200,"wires":[["b9ae5465226eae3a"]]},{"id":"5eb6498b447e55cd","type":"inject","z":"a2240ea952051e81","name":"Stop polling","props":[{"p":"reset","v":"","vt":"date"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":170,"y":340,"wires":[["b9ae5465226eae3a"]]},{"id":"80ad06952f6dcae7","type":"http request","z":"a2240ea952051e81","name":"","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://jsonplaceholder.typicode.com/todos/","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":690,"y":280,"wires":[["6182fd9db1a9270a"]]},{"id":"b9ae5465226eae3a","type":"trigger","z":"a2240ea952051e81","name":"","op1":"1","op2":"0","op1type":"str","op2type":"str","duration":"-500","extend":false,"overrideDelay":false,"units":"ms","reset":"","bytopic":"all","topic":"topic","outputs":1,"x":440,"y":280,"wires":[["80ad06952f6dcae7"]]},{"id":"6182fd9db1a9270a","type":"debug","z":"a2240ea952051e81","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":900,"y":280,"wires":[]},{"id":"04893f86fbf06dee","type":"comment","z":"a2240ea952051e81","name":"Polling data from API with control to start and stop","info":"","x":560,"y":200,"wires":[]}] +``` +:: -{% endrenderFlow %} - - +::node-red-help{category="function" file="89-trigger" node="trigger" name="Trigger"} +:: diff --git a/src/node-red/core-nodes/images/batch-example1.png b/nuxt/content-guides/node-red/core-nodes/images/batch-example1.png similarity index 100% rename from src/node-red/core-nodes/images/batch-example1.png rename to nuxt/content-guides/node-red/core-nodes/images/batch-example1.png diff --git a/src/node-red/core-nodes/images/batch-example2.png b/nuxt/content-guides/node-red/core-nodes/images/batch-example2.png similarity index 100% rename from src/node-red/core-nodes/images/batch-example2.png rename to nuxt/content-guides/node-red/core-nodes/images/batch-example2.png diff --git a/src/node-red/core-nodes/images/batch-example3.webm b/nuxt/content-guides/node-red/core-nodes/images/batch-example3.webm similarity index 100% rename from src/node-red/core-nodes/images/batch-example3.webm rename to nuxt/content-guides/node-red/core-nodes/images/batch-example3.webm diff --git a/src/node-red/core-nodes/images/batch-example4.png b/nuxt/content-guides/node-red/core-nodes/images/batch-example4.png similarity index 100% rename from src/node-red/core-nodes/images/batch-example4.png rename to nuxt/content-guides/node-red/core-nodes/images/batch-example4.png diff --git a/src/node-red/core-nodes/images/batch-example5.png b/nuxt/content-guides/node-red/core-nodes/images/batch-example5.png similarity index 100% rename from src/node-red/core-nodes/images/batch-example5.png rename to nuxt/content-guides/node-red/core-nodes/images/batch-example5.png diff --git a/src/node-red/core-nodes/images/change-data-transform.png b/nuxt/content-guides/node-red/core-nodes/images/change-data-transform.png similarity index 100% rename from src/node-red/core-nodes/images/change-data-transform.png rename to nuxt/content-guides/node-red/core-nodes/images/change-data-transform.png diff --git a/src/node-red/core-nodes/images/change-default.png b/nuxt/content-guides/node-red/core-nodes/images/change-default.png similarity index 100% rename from src/node-red/core-nodes/images/change-default.png rename to nuxt/content-guides/node-red/core-nodes/images/change-default.png diff --git a/src/node-red/core-nodes/images/change-message-enrich.png b/nuxt/content-guides/node-red/core-nodes/images/change-message-enrich.png similarity index 100% rename from src/node-red/core-nodes/images/change-message-enrich.png rename to nuxt/content-guides/node-red/core-nodes/images/change-message-enrich.png diff --git a/src/node-red/core-nodes/images/change-message-filter.png b/nuxt/content-guides/node-red/core-nodes/images/change-message-filter.png similarity index 100% rename from src/node-red/core-nodes/images/change-message-filter.png rename to nuxt/content-guides/node-red/core-nodes/images/change-message-filter.png diff --git a/src/node-red/core-nodes/images/change-message-format.png b/nuxt/content-guides/node-red/core-nodes/images/change-message-format.png similarity index 100% rename from src/node-red/core-nodes/images/change-message-format.png rename to nuxt/content-guides/node-red/core-nodes/images/change-message-format.png diff --git a/src/node-red/core-nodes/images/change-rename-property.png b/nuxt/content-guides/node-red/core-nodes/images/change-rename-property.png similarity index 100% rename from src/node-red/core-nodes/images/change-rename-property.png rename to nuxt/content-guides/node-red/core-nodes/images/change-rename-property.png diff --git a/src/node-red/core-nodes/images/comment-node-full-text.png b/nuxt/content-guides/node-red/core-nodes/images/comment-node-full-text.png similarity index 100% rename from src/node-red/core-nodes/images/comment-node-full-text.png rename to nuxt/content-guides/node-red/core-nodes/images/comment-node-full-text.png diff --git a/src/node-red/core-nodes/images/debug-copy-path.webm b/nuxt/content-guides/node-red/core-nodes/images/debug-copy-path.webm similarity index 100% rename from src/node-red/core-nodes/images/debug-copy-path.webm rename to nuxt/content-guides/node-red/core-nodes/images/debug-copy-path.webm diff --git a/src/node-red/core-nodes/images/debug-copy-value.webm b/nuxt/content-guides/node-red/core-nodes/images/debug-copy-value.webm similarity index 100% rename from src/node-red/core-nodes/images/debug-copy-value.webm rename to nuxt/content-guides/node-red/core-nodes/images/debug-copy-value.webm diff --git a/src/node-red/core-nodes/images/debug-examples.png b/nuxt/content-guides/node-red/core-nodes/images/debug-examples.png similarity index 100% rename from src/node-red/core-nodes/images/debug-examples.png rename to nuxt/content-guides/node-red/core-nodes/images/debug-examples.png diff --git a/src/node-red/core-nodes/images/debug-panel.png b/nuxt/content-guides/node-red/core-nodes/images/debug-panel.png similarity index 100% rename from src/node-red/core-nodes/images/debug-panel.png rename to nuxt/content-guides/node-red/core-nodes/images/debug-panel.png diff --git a/src/node-red/core-nodes/images/debug-pin-open.webm b/nuxt/content-guides/node-red/core-nodes/images/debug-pin-open.webm similarity index 100% rename from src/node-red/core-nodes/images/debug-pin-open.webm rename to nuxt/content-guides/node-red/core-nodes/images/debug-pin-open.webm diff --git a/src/node-red/core-nodes/images/delay-node-2.png b/nuxt/content-guides/node-red/core-nodes/images/delay-node-2.png similarity index 100% rename from src/node-red/core-nodes/images/delay-node-2.png rename to nuxt/content-guides/node-red/core-nodes/images/delay-node-2.png diff --git a/src/node-red/core-nodes/images/inject-config-string.png b/nuxt/content-guides/node-red/core-nodes/images/inject-config-string.png similarity index 100% rename from src/node-red/core-nodes/images/inject-config-string.png rename to nuxt/content-guides/node-red/core-nodes/images/inject-config-string.png diff --git a/src/node-red/core-nodes/images/link-node.png b/nuxt/content-guides/node-red/core-nodes/images/link-node.png similarity index 100% rename from src/node-red/core-nodes/images/link-node.png rename to nuxt/content-guides/node-red/core-nodes/images/link-node.png diff --git a/src/node-red/core-nodes/images/list-nodes-unused.png b/nuxt/content-guides/node-red/core-nodes/images/list-nodes-unused.png similarity index 100% rename from src/node-red/core-nodes/images/list-nodes-unused.png rename to nuxt/content-guides/node-red/core-nodes/images/list-nodes-unused.png diff --git a/src/node-red/core-nodes/images/mqtt-config.png b/nuxt/content-guides/node-red/core-nodes/images/mqtt-config.png similarity index 100% rename from src/node-red/core-nodes/images/mqtt-config.png rename to nuxt/content-guides/node-red/core-nodes/images/mqtt-config.png diff --git a/src/node-red/core-nodes/images/range-node2.png b/nuxt/content-guides/node-red/core-nodes/images/range-node2.png similarity index 100% rename from src/node-red/core-nodes/images/range-node2.png rename to nuxt/content-guides/node-red/core-nodes/images/range-node2.png diff --git a/src/node-red/core-nodes/images/spawn-mode-exec.png b/nuxt/content-guides/node-red/core-nodes/images/spawn-mode-exec.png similarity index 100% rename from src/node-red/core-nodes/images/spawn-mode-exec.png rename to nuxt/content-guides/node-red/core-nodes/images/spawn-mode-exec.png diff --git a/src/node-red/core-nodes/images/status-node-output.png b/nuxt/content-guides/node-red/core-nodes/images/status-node-output.png similarity index 100% rename from src/node-red/core-nodes/images/status-node-output.png rename to nuxt/content-guides/node-red/core-nodes/images/status-node-output.png diff --git a/src/node-red/core-nodes/images/status-node.png b/nuxt/content-guides/node-red/core-nodes/images/status-node.png similarity index 100% rename from src/node-red/core-nodes/images/status-node.png rename to nuxt/content-guides/node-red/core-nodes/images/status-node.png diff --git a/src/node-red/core-nodes/images/switch-example-2.png b/nuxt/content-guides/node-red/core-nodes/images/switch-example-2.png similarity index 100% rename from src/node-red/core-nodes/images/switch-example-2.png rename to nuxt/content-guides/node-red/core-nodes/images/switch-example-2.png diff --git a/src/node-red/core-nodes/images/system-info-installed-nodes.webm b/nuxt/content-guides/node-red/core-nodes/images/system-info-installed-nodes.webm similarity index 100% rename from src/node-red/core-nodes/images/system-info-installed-nodes.webm rename to nuxt/content-guides/node-red/core-nodes/images/system-info-installed-nodes.webm diff --git a/src/node-red/core-nodes/images/wired-up-exec-node.png b/nuxt/content-guides/node-red/core-nodes/images/wired-up-exec-node.png similarity index 100% rename from src/node-red/core-nodes/images/wired-up-exec-node.png rename to nuxt/content-guides/node-red/core-nodes/images/wired-up-exec-node.png diff --git a/nuxt/content-guides/node-red/core-nodes/index.md b/nuxt/content-guides/node-red/core-nodes/index.md new file mode 100644 index 0000000000..c97e93c0cd --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/index.md @@ -0,0 +1,70 @@ +--- +title: "Node-RED core nodes" +metaTitle: "Node-RED Core Nodes" +navTitle: "Core nodes" +navOrder: 3 +meta: + description: "What each node in the default Node-RED palette does, and a worked reason to reach for it." +--- + +# Core nodes + +Every node in Node-RED's default palette, grouped into the same sections the editor's +palette uses. Each page opens with why you would reach for that node, then mirrors the +node's built-in help. + +## [Common](/docs/node-red/core-nodes/common/) + +- [Inject](/docs/node-red/core-nodes/common/inject/): Guide on the Node-RED core node that injects a message into a flow +- [Debug](/docs/node-red/core-nodes/common/debug/) +- [Complete](/docs/node-red/core-nodes/common/complete/) +- [Catch](/docs/node-red/core-nodes/common/catch/) +- [Status](/docs/node-red/core-nodes/common/status/) +- [Link](/docs/node-red/core-nodes/common/link/) +- [Comment](/docs/node-red/core-nodes/common/comment/) +- [Unknown](/docs/node-red/core-nodes/common/unknown/) + +## [Function](/docs/node-red/core-nodes/function/) + +- [Function](/docs/node-red/core-nodes/function/function/) +- [Switch](/docs/node-red/core-nodes/function/switch/) +- [Change](/docs/node-red/core-nodes/function/change/) +- [Range](/docs/node-red/core-nodes/function/range/) +- [Template](/docs/node-red/core-nodes/function/template/) +- [Delay](/docs/node-red/core-nodes/function/delay/) +- [Trigger](/docs/node-red/core-nodes/function/trigger/) +- [Exec](/docs/node-red/core-nodes/function/exec/) +- [Filter](/docs/node-red/core-nodes/function/filter/) + +## [Network](/docs/node-red/core-nodes/network/) + +- [TLS](/docs/node-red/core-nodes/network/tls/) +- [HTTP Proxy](/docs/node-red/core-nodes/network/http-proxy/) +- [MQTT In](/docs/node-red/core-nodes/network/mqtt-in/) +- [MQTT Out](/docs/node-red/core-nodes/network/mqtt-out/) +- [HTTP in](/docs/node-red/core-nodes/network/http-in/) +- [HTTP request](/docs/node-red/core-nodes/network/http-request/) +- [WebSocket](/docs/node-red/core-nodes/network/websocket/) +- [TCP in](/docs/node-red/core-nodes/network/tcp-in/) +- [UDP In](/docs/node-red/core-nodes/network/udp-in/) +- [UDP Out](/docs/node-red/core-nodes/network/udp-out/) + +## [Sequence](/docs/node-red/core-nodes/sequence/) + +- [Split](/docs/node-red/core-nodes/sequence/split/) +- [Sort](/docs/node-red/core-nodes/sequence/sort/) +- [Batch](/docs/node-red/core-nodes/sequence/batch/) +- [Join](/docs/node-red/core-nodes/sequence/join/) + +## [Parsers](/docs/node-red/core-nodes/parsers/) + +- [CSV](/docs/node-red/core-nodes/parsers/csv/) +- [HTML](/docs/node-red/core-nodes/parsers/html/) +- [JSON](/docs/node-red/core-nodes/parsers/json/) +- [XML](/docs/node-red/core-nodes/parsers/xml/) +- [YAML](/docs/node-red/core-nodes/parsers/yaml/) + +## [Storage](/docs/node-red/core-nodes/storage/) + +- [Write File](/docs/node-red/core-nodes/storage/write-file/) +- [Read File](/docs/node-red/core-nodes/storage/read-file/) diff --git a/src/_includes/core-nodes/http-in-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/http-in.md similarity index 96% rename from src/_includes/core-nodes/http-in-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/http-in.md index 363f311d01..d9d5ce8eec 100644 --- a/src/_includes/core-nodes/http-in-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/http-in.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED HTTP in node" +metaTitle: "Node-RED - HTTP in Node" +navTitle: "HTTP in" +navOrder: 5 +--- + +# HTTP in + ## What are Http-in nodes used for in Node-RED The "HTTP In" node in Node-RED is a core node that allows you to create an HTTP endpoint within your flow. It essentially sets up an HTTP server that listens for incoming HTTP requests on a specified URL path and HTTP method (e.g., GET, POST). When a request is received at this endpoint, it triggers the flow and allows you to process the request and generate a response using other nodes in the flow. you can send any type of data as a response whether it is html page, JSON, string, etc. @@ -17,21 +26,27 @@ The baseurl will be the URL of the Node-RED instance at which your flow is deplo 1. In the example flow below, we have an HTTP In node configured with the GET method and "/test" as the URL path. This node returns an HTML page as a response when a request is received. -{% renderFlow %} +::render-flow +```json [{"id":"d705b6ca20481a18","type":"http in","z":"a2240ea952051e81","name":"","url":"/test","method":"get","upload":false,"swaggerDoc":"","x":220,"y":220,"wires":[["500bcf5db325f188"]]},{"id":"f74c362610a1f4dd","type":"debug","z":"a2240ea952051e81","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":780,"y":160,"wires":[]},{"id":"102ecacbf029fa61","type":"http response","z":"a2240ea952051e81","name":"","statusCode":"200","headers":{},"x":800,"y":280,"wires":[]},{"id":"500bcf5db325f188","type":"template","z":"a2240ea952051e81","name":"","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"\n\n\n\n \n \n Devs page\n\n\n\n

Hello, Devs

\n\n\n","output":"str","x":480,"y":220,"wires":[["f74c362610a1f4dd","102ecacbf029fa61"]]},{"id":"d0bd0e43011a33b1","type":"comment","z":"a2240ea952051e81","name":"The HTTP In node returns an HTML page as response when a request is received at the specified path.","info":"","x":510,"y":100,"wires":[]}] -{% endrenderFlow %} +``` +:: 2. In the example flow below, we have an HTTP In node configured to return the todo item as a JSON object stored in the global context, associated with the requested ID provided as a request parameter. -{% renderFlow %} +::render-flow +```json [{"id":"d705b6ca20481a18","type":"http in","z":"b152a914653d9fce","name":"","url":"/todo/:id","method":"get","upload":false,"swaggerDoc":"","x":270,"y":360,"wires":[["cc28a4ae08042cd2"]]},{"id":"f74c362610a1f4dd","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":880,"y":300,"wires":[]},{"id":"102ecacbf029fa61","type":"http response","z":"b152a914653d9fce","name":"","statusCode":"200","headers":{},"x":880,"y":420,"wires":[]},{"id":"d0bd0e43011a33b1","type":"comment","z":"b152a914653d9fce","name":"The HTTP In node returns the todo item associated with the requested ID provided as a request parameter.","info":"","x":540,"y":160,"wires":[]},{"id":"255028cacf698a4f","type":"inject","z":"b152a914653d9fce","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"[ { \"id\": 1, \"task\": \"Complete homework\", \"completed\": false }, { \"id\": 2, \"task\": \"Go for a run\", \"completed\": true }, { \"id\": 3, \"task\": \"Buy groceries\", \"completed\": false } ]","payloadType":"json","x":270,"y":240,"wires":[["560484dbf9da9f27"]]},{"id":"560484dbf9da9f27","type":"change","z":"b152a914653d9fce","name":"Store simulated todo JSON in global context","rules":[{"t":"set","p":"todos","pt":"global","to":"payload","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":590,"y":240,"wires":[[]]},{"id":"cc28a4ae08042cd2","type":"function","z":"b152a914653d9fce","name":"Filter data based on recived param i","func":"let id = Number(msg.req.params.id);\nlet todoList = global.get('todos');\nlet todo = todoList.filter((task)=>task.id===id);\nmsg.payload = todo;\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":600,"y":360,"wires":[["f74c362610a1f4dd","102ecacbf029fa61"]]}] -{% endrenderFlow %} +``` +:: 3. In the example flow below, we have an HTTP In node configured with the POST method and "/todo" as the URL path. When a POST request containing a todo JSON object is received, it stores it in the todo list within the global context. -{% renderFlow %} +::render-flow +```json [{"id":"203195252f71d9f4","type":"http in","z":"b152a914653d9fce","name":"","url":"/todo","method":"post","upload":true,"swaggerDoc":"","x":220,"y":280,"wires":[["93df3c07ae4ad228","995da14e2a688758"]]},{"id":"93df3c07ae4ad228","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":500,"y":240,"wires":[]},{"id":"eb447a5a61f6654d","type":"http response","z":"b152a914653d9fce","name":"","statusCode":"201","headers":{},"x":820,"y":320,"wires":[]},{"id":"995da14e2a688758","type":"function","z":"b152a914653d9fce","name":"store todo in todolist ","func":"let todoList = global.get('todos') || [];\nlet newTodo = msg.payload;\n\ntodoList.push(newTodo);\nglobal.set('todos',todoList)\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":320,"wires":[["eb447a5a61f6654d"]]},{"id":"503937c4fc8b7902","type":"comment","z":"b152a914653d9fce","name":"The HTTP In node stores the todo object in the todolist when a POST request with a todo object is received.","info":"","x":520,"y":180,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Output @@ -47,5 +62,5 @@ The baseurl will be the URL of the Node-RED instance at which your flow is deplo - **files:** If enabled within the node, an object containing any files uploaded as part of a POST request. - **res object:** An HTTP response object. This property should not be used directly; - - +::node-red-help{category="network" file="21-httpin" node="http in" name="HTTP in"} +:: diff --git a/src/_includes/core-nodes/http-proxy-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/http-proxy.md similarity index 89% rename from src/_includes/core-nodes/http-proxy-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/http-proxy.md index 734da3f4e9..ba8fc94aac 100644 --- a/src/_includes/core-nodes/http-proxy-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/http-proxy.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED HTTP Proxy node" +metaTitle: "Node-RED - HTTP Proxy Node" +navTitle: "HTTP Proxy" +navOrder: 2 +--- + +# HTTP Proxy + ## What are HTTP Proxy Nodes in Node-RED? The HTTP Proxy Config node in Node-RED allows you to configure settings for an HTTP proxy server. It enables routing outgoing HTTP requests through a specified proxy. This is useful for scenarios where internet access requires passing through a proxy server. However, this node is not directly available in the Node-RED palette; it is accessible within the configuration settings of some Node-RED core nodes and certain custom nodes used for facilitating network communication, such as HTTP request node, etc. @@ -18,3 +27,6 @@ An HTTP proxy is a server that sits between a client and one or more other serve - **Ignore Hosts:** An optional setting that allows you to specify hosts that should bypass the proxy and connect directly to the destination server. This can be useful for accessing local resources or services that don't need to pass through the proxy. *Note: When accessing to the host in the ignored host list, no proxy will be used.* + +::node-red-help{category="network" file="06-httpproxy" node="http proxy" name="HTTP Proxy"} +:: diff --git a/src/_includes/core-nodes/http-request-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/http-request.md similarity index 83% rename from src/_includes/core-nodes/http-request-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/http-request.md index a9eac59df8..be1070e637 100644 --- a/src/_includes/core-nodes/http-request-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/http-request.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED HTTP request node" +metaTitle: "Node-RED - HTTP request Node" +navTitle: "HTTP request" +navOrder: 6 +--- + +# HTTP request + ## What are HTTP request nodes used for in Node-RED In Node-RED, an HTTP Request node allows you to make HTTP requests to external servers or services. This allows you to interact with web services, APIs, or any other HTTP-based endpoints. @@ -9,12 +18,12 @@ When you configure an HTTP Request node, you typically specify the method (GET, Below, you'll find a range of settings to tailor HTTP requests to fit the needs of different APIs or web services. Depending on the service you're working with, some options might be crucial, while others could be optional. - **Method:** Select the HTTP method for the request (e.g., GET, POST, PUT, DELETE). You can dynamically set it using `msg.method`. -- **URL:** Specify the endpoint URL to communicate with. Dynamic URL setting is allowed using `msg.url`. additionally, if you want to construct a URL with the message's property you can utilize Mustache-style tags with double braces `{% raw %}{{ }}{% endraw %}`. For example, `example.com/{% raw %}{{topic}}{% endraw %}`, where the value of `msg.topic` will be automatically inserted. Using double braces `{% raw %}{{ }}{% endraw %}` performs HTML escaping by default, so special characters in the substituted value will be escaped. However, if you want to preserve special characters like `/` and `&` in the constructed URL, you can use triple braces `{% raw %}{{{ }}}{% endraw %}`. +- **URL:** Specify the endpoint URL to communicate with. Dynamic URL setting is allowed using `msg.url`. additionally, if you want to construct a URL with the message's property you can utilize Mustache-style tags with double braces `{{ }}`. For example, `example.com/{{topic}}`, where the value of `msg.topic` will be automatically inserted. Using double braces `{{ }}` performs HTML escaping by default, so special characters in the substituted value will be escaped. However, if you want to preserve special characters like `/` and `&` in the constructed URL, you can use triple braces `{{{ }}}`. - **Payload:** Allows to choose how received payload from the previous node will be sent with the request: - **Ignore:** If enabled Payload will be ignored. - **Append to query-string parameter:** Enabling this option will Allow sending URL query string parameters using `msg.payload`. - **Send as request:** Send payload data as part of the request body. -- **Enable Secure Connection:** Allows to activate SSL/TLS for secure communication. TLS configuration options are available, For more information refer to [TLS config node](/node-red/core-nodes/tls). +- **Enable Secure Connection:** Allows to activate SSL/TLS for secure communication. TLS configuration options are available, For more information refer to [TLS config node](/docs/node-red/core-nodes/network/tls/). - **Use Authentication:** If required, allow to provide credentials for authentication. - **Type:** Select the authentication type. - **basic:** Uses Basic authentication where the username and password are sent in the request headers in Base64-encoded form. @@ -24,7 +33,7 @@ Below, you'll find a range of settings to tailor HTTP requests to fit the needs - **bearer:** Uses Bearer token authentication where a bearer token, typically a JSON Web Token (JWT), is sent in the Authorization header. - **Token:** Provide the bearer token if bearer authentication is selected. - **Enable Connection Keep-Alive:** Enabling this option will allow Maintain persistent connections for efficiency. -- **Use Proxy:** Allows to Route requests through a proxy server if necessary, for more information on the configuration of [HTTP Proxy](/node-red/core-nodes/http-proxy/) config node +- **Use Proxy:** Allows to Route requests through a proxy server if necessary, for more information on the configuration of [HTTP Proxy](/docs/node-red/core-nodes/network/http-proxy/) config node - **Only send non-2xx responses to Catch node:** Enabling this option will send only non-success responses to the Catch node. - **Disable Strict HTTP Parsing:** Enabling this option relaxes how Node-RED interprets HTTP responses. It's handy when dealing with responses that don't perfectly match the standard HTTP format. - **Return:** Allows to Choose the format for response data conversion @@ -37,7 +46,7 @@ Below, you'll find a range of settings to tailor HTTP requests to fit the needs 1. **API Integration:** The HTTP request node allows seamless integration with external APIs. Developers can utilize it to fetch data from APIs using GET requests or send data to APIs using POST/PUT requests. For instance, fetching weather data from a weather API or posting data to a messaging service like Slack are common scenarios. -2. **Webhooks:** With the HTTP request node, users can set up webhooks to trigger actions in response to specific events. This enables real-time communication between different applications. For example, triggering a webhook to notify a third-party service when certain conditions are met or when data is received from a sensor. For more information, refer to [Using webhook with Node-RED](/node-red/integration-technologies/webhook/) +2. **Webhooks:** With the HTTP request node, users can set up webhooks to trigger actions in response to specific events. This enables real-time communication between different applications. For example, triggering a webhook to notify a third-party service when certain conditions are met or when data is received from a sensor. For more information, refer to [Using webhook with Node-RED](/docs/node-red/integration-technologies/webhook/) 3. **Remote Control and Device Management:** In smart home systems, the HTTP request node can be used to facilitates remote device management. It allows users to control various devices such as lights, thermostats, and security cameras via web or mobile interfaces by interacting with device APIs. Actions like toggling devices, adjusting settings, and receiving real-time updates can be achieved through the HTTP request node. @@ -47,33 +56,43 @@ These are a few use cases of the HTTP Request node, but its ability to communica 1. Below is an example showing how you can construct a URL with message properties in the HTTP request node, an example includes sending a GET request. -{% renderFlow %} +::render-flow +```json [{"id":"b152a914653d9fce","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"dce407bcce963567","type":"inject","z":"b152a914653d9fce","name":"Get post ","props":[{"p":"topic","v":"1","vt":"num"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":200,"y":220,"wires":[["ea91769386ca7b48"]]},{"id":"ea91769386ca7b48","type":"http request","z":"b152a914653d9fce","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://jsonplaceholder.typicode.com/posts/{{topic}}","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":220,"wires":[["2f597047663e2964"]]},{"id":"2f597047663e2964","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"9f40509b6642d082","type":"comment","z":"b152a914653d9fce","name":"The HTTP request node retrieves posts from a mock API using the ID passed as a query parameter with `msg.topic` by the inject node.","info":"","x":510,"y":140,"wires":[]}] -{% endrenderFlow %} +``` +:: 2. Below is an example showing how you can send an HTTP POST request to a mock API. This example includes registering a user. -{% renderFlow %} +::render-flow +```json [{"id":"dce407bcce963567","type":"inject","z":"b152a914653d9fce","name":"Register With Mock API","props":[{"p":"payload.email","v":"eve.holt@reqres.in","vt":"str"},{"p":"payload.password","v":"password","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":220,"y":220,"wires":[["ea91769386ca7b48"]]},{"id":"ea91769386ca7b48","type":"http request","z":"b152a914653d9fce","name":"","method":"POST","ret":"txt","paytoqs":"ignore","url":"https://reqres.in/api/login","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":220,"wires":[["2f597047663e2964"]]},{"id":"2f597047663e2964","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"9f40509b6642d082","type":"comment","z":"b152a914653d9fce","name":"The HTTP request node sends a POST request to register a user.","info":"","x":430,"y":140,"wires":[]}] -{% endrenderFlow %} +``` +:: 3. Below is an example demonstrating how you can dynamically set the URL, method, and headers for the HTTP request node. Additionally, this example illustrates sending a GET request to a mock API with an authorization token. -{% renderFlow %} +::render-flow +```json [{"id":"dce407bcce963567","type":"inject","z":"b152a914653d9fce","name":"make request ","props":[{"p":"url","v":"https://postman-echo.com/basic-auth","vt":"str"},{"p":"method","v":"GET","vt":"str"},{"p":"headers.Authorization","v":"Basic cG9zdG1hbjpwYXNzd29yZA","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":210,"y":220,"wires":[["ea91769386ca7b48"]]},{"id":"ea91769386ca7b48","type":"http request","z":"b152a914653d9fce","name":"","method":"use","ret":"obj","paytoqs":"ignore","url":"","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":220,"wires":[["2f597047663e2964"]]},{"id":"2f597047663e2964","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"9f40509b6642d082","type":"comment","z":"b152a914653d9fce","name":"The HTTP request node retrieves posts from a mock API using the ID passed as a query parameter with `msg.topic` by the inject node.","info":"","x":510,"y":140,"wires":[]}] -{% endrenderFlow %} +``` +:: 4. Below is an example showing how you can send a PUT request using the HTTP request node. -{% renderFlow %} - [{"id":"dce407bcce963567","type":"inject","z":"b152a914653d9fce","name":"Update post","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"id\":1,\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}","payloadType":"json","x":210,"y":220,"wires":[["ea91769386ca7b48"]]},{"id":"ea91769386ca7b48","type":"http request","z":"b152a914653d9fce","name":"","method":"PUT","ret":"obj","paytoqs":"ignore","url":"https://jsonplaceholder.typicode.com/posts/1","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":220,"wires":[["2f597047663e2964"]]},{"id":"2f597047663e2964","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"9f40509b6642d082","type":"comment","z":"b152a914653d9fce","name":"The HTTP request node sends a PUT request to update the post.","info":"","x":470,"y":140,"wires":[]}] -{% endrenderFlow %} +::render-flow +```json +[{"id":"dce407bcce963567","type":"inject","z":"b152a914653d9fce","name":"Update post","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"id\":1,\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}","payloadType":"json","x":210,"y":220,"wires":[["ea91769386ca7b48"]]},{"id":"ea91769386ca7b48","type":"http request","z":"b152a914653d9fce","name":"","method":"PUT","ret":"obj","paytoqs":"ignore","url":"https://jsonplaceholder.typicode.com/posts/1","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":220,"wires":[["2f597047663e2964"]]},{"id":"2f597047663e2964","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"9f40509b6642d082","type":"comment","z":"b152a914653d9fce","name":"The HTTP request node sends a PUT request to update the post.","info":"","x":470,"y":140,"wires":[]}] +``` +:: 5. Below is an example showing how you can send a DELETE request using the HTTP request node. -{% renderFlow %} +::render-flow +```json [{"id":"dce407bcce963567","type":"inject","z":"b152a914653d9fce","name":"delete post","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":200,"y":220,"wires":[["ea91769386ca7b48"]]},{"id":"ea91769386ca7b48","type":"http request","z":"b152a914653d9fce","name":"","method":"DELETE","ret":"obj","paytoqs":"ignore","url":"https://jsonplaceholder.typicode.com/posts/1","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":490,"y":220,"wires":[["2f597047663e2964"]]},{"id":"2f597047663e2964","type":"debug","z":"b152a914653d9fce","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":740,"y":220,"wires":[]},{"id":"9f40509b6642d082","type":"comment","z":"b152a914653d9fce","name":"The HTTP request node sends a DELETE request to delete a post.","info":"","x":440,"y":140,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Output @@ -83,3 +102,6 @@ These are a few use cases of the HTTP Request node, but its ability to communica - **responseURL:** Provides the final redirected URL if any redirects occurred during processing; otherwise, it shows the URL of the original request. - **responseCookies:** If the response includes cookies, this property is an object containing name/value pairs for each cookie. - **redirectList:** Accumulated information about redirects, including the next redirect destination (`location`) and cookies returned from the redirect source. + +::node-red-help{category="network" file="21-httprequest" node="http request" name="HTTP request"} +:: diff --git a/nuxt/content-guides/node-red/core-nodes/network/index.md b/nuxt/content-guides/node-red/core-nodes/network/index.md new file mode 100644 index 0000000000..ccaf179087 --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/network/index.md @@ -0,0 +1,24 @@ +--- +title: "Node-RED network nodes" +metaTitle: "Node-RED Network Nodes" +navTitle: "Network nodes" +navOrder: 3 +meta: + description: "The Network nodes in Node-RED's default palette, and what each one is for." +--- + +# Network + +The **Network** section of Node-RED's default palette. Each page opens with why you +would reach for that node, then mirrors the node's built-in help. + +- [TLS](/docs/node-red/core-nodes/network/tls/) +- [HTTP Proxy](/docs/node-red/core-nodes/network/http-proxy/) +- [MQTT In](/docs/node-red/core-nodes/network/mqtt-in/) +- [MQTT Out](/docs/node-red/core-nodes/network/mqtt-out/) +- [HTTP in](/docs/node-red/core-nodes/network/http-in/) +- [HTTP request](/docs/node-red/core-nodes/network/http-request/) +- [WebSocket](/docs/node-red/core-nodes/network/websocket/) +- [TCP in](/docs/node-red/core-nodes/network/tcp-in/) +- [UDP In](/docs/node-red/core-nodes/network/udp-in/) +- [UDP Out](/docs/node-red/core-nodes/network/udp-out/) diff --git a/src/_includes/core-nodes/mqtt-in-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/mqtt-in.md similarity index 95% rename from src/_includes/core-nodes/mqtt-in-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/mqtt-in.md index a33d9caed8..8183f72d67 100644 --- a/src/_includes/core-nodes/mqtt-in-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/mqtt-in.md @@ -1,4 +1,11 @@ -# {{ meta.title }} +--- +title: "Node-RED MQTT In node" +metaTitle: "Node-RED - MQTT In Node" +navTitle: "MQTT In" +navOrder: 3 +--- + +# MQTT In This node connects to an MQTT broker and subscribes to messages from a specified topic. @@ -119,12 +126,17 @@ These inputs only apply when dynamic subscriptions are enabled: This example subscribes to the public topic `cheerlights/coloured/hex` on the Mosquitto test broker. Each time a new color is published, the color code is displayed in the Debug panel. -{% renderFlow %} +::render-flow +```json [{"id":"0e7402215650517c","type":"mqtt in","z":"a149bb66646389a3","name":"Cheerlights","topic":"cheerlights/coloured/hex","qos":"2","datatype":"auto-detect","broker":"037ca6b6ca0d7699","nl":false,"rap":true,"rh":0,"inputs":0,"x":220,"y":260,"wires":[["6173b5c60df9bfee"]]},{"id":"6173b5c60df9bfee","type":"debug","z":"a149bb66646389a3","name":"Cheerlights Debug","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":260,"wires":[]},{"id":"037ca6b6ca0d7699","type":"mqtt-broker","name":"Public Mosquitto Broker","broker":"test.mosquitto.org","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true}] -{% endrenderFlow %} +``` +:: ## Notes - The MQTT In node automatically reconnects if the connection to the broker is lost. - Retained messages are received immediately when subscribing to a topic that has one. - The node can work alongside the MQTT Out node for bi-directional communication. + +::node-red-help{category="network" file="10-mqtt" node="mqtt in" name="MQTT In"} +:: diff --git a/src/_includes/core-nodes/mqtt-out-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/mqtt-out.md similarity index 95% rename from src/_includes/core-nodes/mqtt-out-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/mqtt-out.md index cbbd70f692..0d84233e33 100644 --- a/src/_includes/core-nodes/mqtt-out-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/mqtt-out.md @@ -1,4 +1,11 @@ -# {{ meta.title }} +--- +title: "Node-RED MQTT Out node" +metaTitle: "Node-RED - MQTT Out Node" +navTitle: "MQTT Out" +navOrder: 4 +--- + +# MQTT Out The MQTT Out node connects to an MQTT broker and publishes messages to one or more topics. It is typically used to send sensor data, commands, or event notifications from Node-RED to external systems or devices that subscribe to MQTT topics. @@ -100,9 +107,11 @@ In that case, it will disconnect, apply the new configuration, and reconnect. This example shows how to publish a timestamp to an MQTT topic on the public Mosquitto test broker. -{% renderFlow %} +::render-flow +```json [{"id":"c1b20f45c3c3e77e","type":"tab","label":"MQTT Publish Example","disabled":false,"info":"","env":[]},{"id":"e5b31919a35d7f51","type":"inject","z":"c1b20f45c3c3e77e","name":"Inject Timestamp","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"example/time","payload":"","payloadType":"date","x":180,"y":120,"wires":[["6cd12cecb82889e2"]]},{"id":"6cd12cecb82889e2","type":"mqtt out","z":"c1b20f45c3c3e77e","name":"MQTT Out","topic":"","qos":"1","retain":"false","broker":"da4d8b90.3a89d8","x":430,"y":120,"wires":[]},{"id":"da4d8b90.3a89d8","type":"mqtt-broker","name":"Public Mosquitto Broker","broker":"test.mosquitto.org","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true}] -{% endrenderFlow %} +``` +:: ## Notes @@ -110,3 +119,6 @@ This example shows how to publish a timestamp to an MQTT topic on the public Mos - For large payloads or binary data, use buffers to avoid unnecessary conversion. - Multiple MQTT Out nodes can share the same broker connection. - MQTT v5 users can take advantage of additional properties for richer message metadata. + +::node-red-help{category="network" file="10-mqtt" node="mqtt out" name="MQTT Out"} +:: diff --git a/src/_includes/core-nodes/tcp-in-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/tcp-in.md similarity index 96% rename from src/_includes/core-nodes/tcp-in-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/tcp-in.md index 2c0f0521c8..c388af3a8a 100644 --- a/src/_includes/core-nodes/tcp-in-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/tcp-in.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED TCP in node" +metaTitle: "Node-RED - TCP in Node" +navTitle: "TCP in" +navOrder: 8 +--- + +# TCP in + ## What are TCP-In nodes used for in Node-RED The TCP node in Node-RED allows you to establish connections to remote TCP ports, serving as a TCP client for communication with external services or devices. Additionally, it facilitates the creation of a TCP server that can accept incoming connections. This functionality supports various applications, such as interacting with web servers or receiving data streams from IoT devices. Whether you're working on IoT projects, industrial automation, or networked systems, this node seamlessly integrates TCP/IP communication into your Node-RED workflows. @@ -13,7 +22,7 @@ TCP (Transmission Control Protocol) is one of the core protocols of the Internet - **Connect to:** Enables the TCP node to act as a client, establishing connections to remote servers. - **Hostname:** Specifies the hostname or IP address of the remote server when using the "Connect to" type. - **Port:** Specifies the TCP port number to listen on (when using "Listen on" type) or to connect to (when using "Connect to" type). -- **Enable secure (SSL/TLS) connection:** Enabling this option activates SSL/TLS for secure communication. In Node-RED we have TLS config node which allows to activate TLS secure communication, refer to [TLS config node](/node-red/core-nodes/tls/) for details on configurations. +- **Enable secure (SSL/TLS) connection:** Enabling this option activates SSL/TLS for secure communication. In Node-RED we have TLS config node which allows to activate TLS secure communication, refer to [TLS config node](/docs/node-red/core-nodes/network/tls/) for details on configurations. - **Output:** - **Streams of:** Selecting this option will outputs the data stream received from the TCP connection as a continuous stream of messages. - **Single:** Selecting this option will outputs a single message containing the data received from the TCP connection. @@ -36,12 +45,19 @@ TCP (Transmission Control Protocol) is one of the core protocols of the Internet 1. In the example flow below, we create a basic TCP server using the tcp-in node. -{% renderFlow %} +::render-flow +```json [{"id":"a8c5eab2876f058e","type":"group","z":"5b972161c4e0464e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["e286c8cf0f18b990","279de14c4fcb638c","15e102f80cf28535","c4d2ad15a31bd520","aaea81c136a207e0","64a84ea2a47d4ccf"],"x":194,"y":179,"w":672,"h":222},{"id":"e286c8cf0f18b990","type":"tcp in","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"TCP Server","server":"server","host":"","port":"2000","datamode":"stream","datatype":"utf8","newline":"","topic":"","trim":false,"base64":false,"tls":"","x":330,"y":280,"wires":[["aaea81c136a207e0"]]},{"id":"279de14c4fcb638c","type":"inject","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"Send data to tcp server","props":[{"p":"payload"}],"repeat":"2","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":350,"y":360,"wires":[["15e102f80cf28535"]]},{"id":"15e102f80cf28535","type":"tcp request","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"TCP request","server":"127.0.0.1","port":"2000","out":"sit","ret":"string","splitc":" ","newline":"","trim":false,"tls":"","x":570,"y":360,"wires":[["c4d2ad15a31bd520"]]},{"id":"c4d2ad15a31bd520","type":"debug","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":760,"y":360,"wires":[]},{"id":"aaea81c136a207e0","type":"debug","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":720,"y":280,"wires":[]},{"id":"64a84ea2a47d4ccf","type":"comment","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"Creating TCP server uisng TCP In node","info":"","x":510,"y":220,"wires":[]}] -{% endrenderFlow %} +``` +:: 2. In the example flow below, we demonstrate how to utilize the TCP-In node alongside other TCP nodes to enable bidirectional communication. -{% renderFlow %} +::render-flow +```json [{"id":"a8c5eab2876f058e","type":"group","z":"5b972161c4e0464e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["e286c8cf0f18b990","279de14c4fcb638c","15e102f80cf28535","c4d2ad15a31bd520","aaea81c136a207e0","64a84ea2a47d4ccf","bb1fcbe5804228d7","f0962665b353693f","e9d80655226685f3","0d400b0bcd290e30","dc8d47a4e642899f","4ed5c064fef77a4b"],"x":194,"y":199,"w":672,"h":382},{"id":"e286c8cf0f18b990","type":"tcp in","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"TCP Server","server":"server","host":"","port":"2000","datamode":"stream","datatype":"utf8","newline":"","topic":"","trim":false,"base64":false,"tls":"","x":290,"y":300,"wires":[["aaea81c136a207e0"]]},{"id":"279de14c4fcb638c","type":"inject","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"Send data to tcp server","props":[{"p":"payload"}],"repeat":"2","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":350,"y":420,"wires":[["15e102f80cf28535"]]},{"id":"15e102f80cf28535","type":"tcp request","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"TCP request","server":"127.0.0.1","port":"2000","out":"sit","ret":"string","splitc":" ","newline":"","trim":false,"tls":"","x":570,"y":420,"wires":[["c4d2ad15a31bd520"]]},{"id":"c4d2ad15a31bd520","type":"debug","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":760,"y":420,"wires":[]},{"id":"aaea81c136a207e0","type":"debug","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":740,"y":300,"wires":[]},{"id":"64a84ea2a47d4ccf","type":"comment","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"Creating TCP server uisng TCP In node","info":"","x":530,"y":240,"wires":[]},{"id":"bb1fcbe5804228d7","type":"tcp in","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"TCP Server","server":"server","host":"","port":"2000","datamode":"stream","datatype":"utf8","newline":"","topic":"","trim":false,"base64":false,"tls":"","x":290,"y":440,"wires":[[]]},{"id":"f0962665b353693f","type":"debug","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":680,"y":460,"wires":[]},{"id":"e9d80655226685f3","type":"inject","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":310,"y":540,"wires":[["0d400b0bcd290e30"]]},{"id":"0d400b0bcd290e30","type":"tcp out","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"","host":"","port":"","beserver":"reply","base64":false,"end":false,"tls":"","x":750,"y":540,"wires":[]},{"id":"dc8d47a4e642899f","type":"comment","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"Sending data to tcp server","info":"","x":510,"y":340,"wires":[]},{"id":"4ed5c064fef77a4b","type":"comment","z":"5b972161c4e0464e","g":"a8c5eab2876f058e","name":"Sending data to client ","info":"","x":500,"y":500,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="network" file="31-tcpin" node="tcp in" name="TCP in"} +:: diff --git a/src/_includes/core-nodes/tls-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/tls.md similarity index 93% rename from src/_includes/core-nodes/tls-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/tls.md index 6c7b180716..ca7b956b83 100644 --- a/src/_includes/core-nodes/tls-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/tls.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED TLS node" +metaTitle: "Node-RED - TLS Node" +navTitle: "TLS" +navOrder: 1 +--- + +# TLS + ## What is the TLS config node in Node-RED? In Node-RED, the TLS Config node is used to configure Transport Layer Security (TLS) settings for secure communication over networks. TLS ensures that data transmitted between applications, servers, and devices is encrypted and secure. However, this node is not directly available in the Node-RED palette; it is accessible within the configuration settings of some Node-RED core nodes and certain custom nodes used for facilitating network communication, such as HTTP Request, TCP-In, and custom nodes like Kafka, MongoDB, etc. @@ -22,3 +31,6 @@ The TLS Config node primarily facilitates secure communication over the network. **Web Server Security:** When building web servers using Node-RED, it's essential to secure communication between clients and the server. The TLS Config node can be used to configure TLS settings for nodes such as HTTP, TCP, enabling HTTPS communication and encrypting data exchanged between clients and the server. **Database Connection Security:** Node-RED is frequently used to interact with databases for storing and retrieving data. When connecting to databases, such as MongoDB or MySQL, it's crucial to ensure that data is transmitted securely. The TLS Config node can be used to configure TLS settings for database nodes, securing communication with the database server. + +::node-red-help{category="network" file="05-tls" node="tls" name="TLS"} +:: diff --git a/src/_includes/core-nodes/udp-in-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/udp-in.md similarity index 96% rename from src/_includes/core-nodes/udp-in-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/udp-in.md index 8ce3289e94..2dce6cec19 100644 --- a/src/_includes/core-nodes/udp-in-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/udp-in.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED UDP In node" +metaTitle: "Node-RED - UDP In Node" +navTitle: "UDP In" +navOrder: 9 +--- + +# UDP In + ## What is udp-in node in Node-RED? The UDP-In node in Node-RED enables the reception of UDP messages from remote devices or services. It acts as a listener, waiting for incoming UDP packets on a specified port. This functionality is crucial for real-time applications, such as IoT data ingestion and network communication. Whether you're receiving sensor data from IoT devices or communicating with other networked systems, the UDP-In node seamlessly integrates UDP communication into your Node-RED workflows, providing a lightweight and efficient solution for data reception. @@ -43,9 +52,11 @@ The UDP-In node in Node-RED provides versatile configuration options to tailor U 1. In the example below, we have a UDP-In node configured to receive data sent over localhost and port 90, using a UDP-Out node. -{% renderFlow %} +::render-flow +```json [{"id":"8b65196d8e0682a7","type":"group","z":"5b972161c4e0464e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["c69190416293c2c5","1448bee95281f5bb","4d31d05731f0fa6c","cd0155779b527eb2","9b0fd28ce351cbee","53660b468f150faa"],"x":374,"y":59,"w":472,"h":262},{"id":"c69190416293c2c5","type":"inject","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":490,"y":160,"wires":[["1448bee95281f5bb"]]},{"id":"1448bee95281f5bb","type":"udp out","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"","addr":"127.0.0.1","iface":"","port":"90","ipv":"udp4","outport":"","base64":false,"multicast":"false","x":730,"y":160,"wires":[]},{"id":"4d31d05731f0fa6c","type":"udp in","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"","iface":"","port":"90","ipv":"udp4","multicast":"false","group":"","datatype":"buffer","x":470,"y":280,"wires":[["cd0155779b527eb2"]]},{"id":"cd0155779b527eb2","type":"debug","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":720,"y":280,"wires":[]},{"id":"9b0fd28ce351cbee","type":"comment","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"Sending data to client","info":"","x":600,"y":100,"wires":[]},{"id":"53660b468f150faa","type":"comment","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"Receving data from server ","info":"","x":610,"y":220,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Output @@ -53,3 +64,5 @@ The UDP-In node in Node-RED provides versatile configuration options to tailor U - **msg.fromip:** The IP address and port from which the message was received, formatted as "IP:Port". - **msg.ip and msg.port:** The IP address and port from which the message was received. +::node-red-help{category="network" file="32-udp" node="udp in" name="UDP In"} +:: diff --git a/src/_includes/core-nodes/udp-out-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/udp-out.md similarity index 93% rename from src/_includes/core-nodes/udp-out-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/udp-out.md index 17e6729ace..58a6f4cd6f 100644 --- a/src/_includes/core-nodes/udp-out-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/udp-out.md @@ -1,8 +1,17 @@ +--- +title: "Node-RED UDP Out node" +metaTitle: "Node-RED - UDP Out Node" +navTitle: "UDP Out" +navOrder: 10 +--- + +# UDP Out + ## What is udp-out node in Node-RED? The udp-out node in Node-RED is a node used for sending UDP messages to a specified network destination. When you add a udp-out node to your Node-RED flow, you configure it with the IP address and port of the destination device or service. Then, any messages received by the udp-out node are sent as UDP packets to that destination. This node is particularly useful for applications where real-time communication or lightweight message transmission is required. -For more information on UDP refer to [What is UDP](/node-red/core-nodes/udp-in/#what-is-udp%3F) +For more information on UDP refer to [What is UDP](/docs/node-red/core-nodes/network/udp-in/#what-is-udp%3F) ## Configuring UDP-Out Node @@ -42,6 +51,11 @@ For more information on UDP refer to [What is UDP](/node-red/core-nodes/udp-in/# 1. In the example below, we have a udp-out node configured to send data over localhost on port 90 and receive it with a udp-in node. -{% renderFlow %} +::render-flow +```json [{"id":"8b65196d8e0682a7","type":"group","z":"5b972161c4e0464e","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["c69190416293c2c5","1448bee95281f5bb","4d31d05731f0fa6c","cd0155779b527eb2","9b0fd28ce351cbee","53660b468f150faa"],"x":374,"y":59,"w":472,"h":262},{"id":"c69190416293c2c5","type":"inject","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":490,"y":160,"wires":[["1448bee95281f5bb"]]},{"id":"1448bee95281f5bb","type":"udp out","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"","addr":"127.0.0.1","iface":"","port":"90","ipv":"udp4","outport":"","base64":false,"multicast":"false","x":730,"y":160,"wires":[]},{"id":"4d31d05731f0fa6c","type":"udp in","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"","iface":"","port":"90","ipv":"udp4","multicast":"false","group":"","datatype":"buffer","x":470,"y":280,"wires":[["cd0155779b527eb2"]]},{"id":"cd0155779b527eb2","type":"debug","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":720,"y":280,"wires":[]},{"id":"9b0fd28ce351cbee","type":"comment","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"Sending data to client","info":"","x":600,"y":100,"wires":[]},{"id":"53660b468f150faa","type":"comment","z":"5b972161c4e0464e","g":"8b65196d8e0682a7","name":"Receving data from server ","info":"","x":610,"y":220,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="network" file="32-udp" node="udp out" name="UDP Out"} +:: diff --git a/src/_includes/core-nodes/websocket-use-case.md b/nuxt/content-guides/node-red/core-nodes/network/websocket.md similarity index 94% rename from src/_includes/core-nodes/websocket-use-case.md rename to nuxt/content-guides/node-red/core-nodes/network/websocket.md index c920f987b1..9081ba3349 100644 --- a/src/_includes/core-nodes/websocket-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/network/websocket.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED WebSocket node" +metaTitle: "Node-RED - WebSocket Node" +navTitle: "WebSocket" +navOrder: 7 +--- + +# WebSocket + ## What are WebSocket nodes used for in Node-RED Node-RED provides two WebSocket nodes that serve distinct purposes and can operate in two modes. @@ -50,7 +59,11 @@ Simple Echo test This shows both modes, with one set acting as a WebSocket Echo Server and the other connecting to that server and sending and receiving messages. -{% renderFlow %} +::render-flow +```json [{"id":"43294be738b7699a","type":"tab","label":"WebSockets","disabled":false,"info":"","env":[]},{"id":"e788dbc44428f6b7","type":"websocket in","z":"43294be738b7699a","name":"","server":"","client":"4db78eb653d6d50d","x":290,"y":240,"wires":[["d86baf93795dbb2d"]]},{"id":"8c702971b0292f9b","type":"websocket out","z":"43294be738b7699a","name":"","server":"","client":"4db78eb653d6d50d","x":510,"y":180,"wires":[]},{"id":"dda81a976ccf2a2b","type":"inject","z":"43294be738b7699a","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":220,"y":180,"wires":[["8c702971b0292f9b"]]},{"id":"d86baf93795dbb2d","type":"debug","z":"43294be738b7699a","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":240,"wires":[]},{"id":"d36a13e17814e375","type":"websocket in","z":"43294be738b7699a","name":"","server":"542f751e15337c46","client":"","x":230,"y":320,"wires":[["1660b4da46db22ad"]]},{"id":"1660b4da46db22ad","type":"websocket out","z":"43294be738b7699a","name":"","server":"542f751e15337c46","client":"","x":560,"y":320,"wires":[]},{"id":"4db78eb653d6d50d","type":"websocket-client","path":"ws://localhost:1880/ws/echo","tls":"","wholemsg":"false","hb":"0","subprotocol":""},{"id":"542f751e15337c46","type":"websocket-listener","path":"/ws/echo","wholemsg":"false"}] -{% endrenderFlow %} +``` +:: +::node-red-help{category="network" file="22-websocket" node="websocket" name="WebSocket"} +:: diff --git a/src/_includes/core-nodes/csv-use-case.md b/nuxt/content-guides/node-red/core-nodes/parsers/csv.md similarity index 95% rename from src/_includes/core-nodes/csv-use-case.md rename to nuxt/content-guides/node-red/core-nodes/parsers/csv.md index 05420ac383..f865bccf78 100644 --- a/src/_includes/core-nodes/csv-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/parsers/csv.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED CSV node" +metaTitle: "Node-RED - CSV Node" +navTitle: "CSV" +navOrder: 1 +--- + +# CSV + Converts between CSV formatted text and JavaScript objects. ## Where and why do we use the CSV node? @@ -44,6 +53,11 @@ Tokio,January,3000 This flow reads the CSV data, converts it to objects, adds a calculated PartsProduced column, and converts it back to CSV format. -{% renderFlow %} +::render-flow +```json [{"id":"1","type":"inject","z":"3a74ad88ecc45bcb","name":"Start","repeat":"","crontab":"","once":false,"onceDelay":0.1,"payload":"","payloadType":"date","x":90,"y":60,"wires":[["2"]]},{"id":"2","type":"template","z":"3a74ad88ecc45bcb","name":"CSV Data","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Station,Month,Consumption\nRio,January,4000\nNew York,January,1000\nTokio,January,3000","x":240,"y":60,"wires":[["3"]]},{"id":"3","type":"csv","z":"3a74ad88ecc45bcb","name":"CSV In","sep":",","hdrin":true,"hdrout":"","multi":"one","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":false,"include_null_values":false,"x":410,"y":60,"wires":[["4","5"]]},{"id":"4","type":"debug","z":"3a74ad88ecc45bcb","name":"Debug JSON","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":590,"y":60,"wires":[]},{"id":"5","type":"change","z":"3a74ad88ecc45bcb","name":"Add PartsProduced","rules":[{"t":"set","p":"columns","pt":"msg","to":"msg.columns & \", PartsProduced\"","tot":"jsonata"},{"t":"set","p":"payload.PartsProduced","pt":"msg","to":"payload.Consumption / 10","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":230,"y":140,"wires":[["6"]]},{"id":"6","type":"csv","z":"3a74ad88ecc45bcb","name":"CSV out","sep":",","hdrin":"","hdrout":"all","multi":"one","ret":"\\n","temp":"","skip":"0","strings":true,"include_empty_strings":false,"include_null_values":false,"x":420,"y":140,"wires":[["7"]]},{"id":"7","type":"debug","z":"3a74ad88ecc45bcb","name":"Debug Final CSV","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":610,"y":140,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="parsers" file="70-CSV" node="csv" name="CSV"} +:: diff --git a/src/_includes/core-nodes/html-use-case.md b/nuxt/content-guides/node-red/core-nodes/parsers/html.md similarity index 91% rename from src/_includes/core-nodes/html-use-case.md rename to nuxt/content-guides/node-red/core-nodes/parsers/html.md index 773ed3f98e..62cd848f3b 100644 --- a/src/_includes/core-nodes/html-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/parsers/html.md @@ -1,8 +1,17 @@ +--- +title: "Node-RED HTML node" +metaTitle: "Node-RED - HTML Node" +navTitle: "HTML" +navOrder: 2 +--- + +# HTML + Extracts elements from an HTML document. ## Where and why do we use the HTML node? -The HTML node parses HTML documents and extracts specific elements using CSS selectors. This is essential when you need to scrape data from web pages, extract specific content from HTML responses, or process HTML documents to retrieve structured information. Unlike the [template node](/node-red/core-nodes/template/) which generates HTML, this node is purely for parsing and extraction. +The HTML node parses HTML documents and extracts specific elements using CSS selectors. This is essential when you need to scrape data from web pages, extract specific content from HTML responses, or process HTML documents to retrieve structured information. Unlike the [template node](/docs/node-red/core-nodes/function/template/) which generates HTML, this node is purely for parsing and extraction. ## How it works @@ -48,6 +57,11 @@ The node uses CSS selector syntax with jQuery extensions, so you can use: This example fetches the Node-RED homepage and extracts the text from the `h1` tag. The HTTP Request node retrieves the page, and the HTML node parses it to find the heading. -{% renderFlow %} +::render-flow +```json [{"id":"fe3ffa918ba45f27","type":"html","z":"99a0b45110d553ec","name":"Select H1 element","property":"payload","outproperty":"payload","tag":"h1","ret":"html","as":"single","x":610,"y":40,"wires":[["07dd1efff04d231a"]]},{"id":"339359b6a6793b3d","type":"http request","z":"99a0b45110d553ec","name":"Get Node-RED.org homepage","method":"GET","ret":"txt","paytoqs":"ignore","url":"https://nodered.org/","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":true,"headers":[],"x":350,"y":40,"wires":[["fe3ffa918ba45f27"]]},{"id":"e7dcdcff49c14ab1","type":"inject","z":"99a0b45110d553ec","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":120,"y":40,"wires":[["339359b6a6793b3d"]]},{"id":"07dd1efff04d231a","type":"debug","z":"99a0b45110d553ec","name":"Print H1 content","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":820,"y":40,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="parsers" file="70-HTML" node="html" name="HTML"} +:: diff --git a/nuxt/content-guides/node-red/core-nodes/parsers/index.md b/nuxt/content-guides/node-red/core-nodes/parsers/index.md new file mode 100644 index 0000000000..61b2dc03b2 --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/parsers/index.md @@ -0,0 +1,19 @@ +--- +title: "Node-RED parsers nodes" +metaTitle: "Node-RED Parsers Nodes" +navTitle: "Parser nodes" +navOrder: 5 +meta: + description: "The Parsers nodes in Node-RED's default palette, and what each one is for." +--- + +# Parsers + +The **Parsers** section of Node-RED's default palette. Each page opens with why you +would reach for that node, then mirrors the node's built-in help. + +- [CSV](/docs/node-red/core-nodes/parsers/csv/) +- [HTML](/docs/node-red/core-nodes/parsers/html/) +- [JSON](/docs/node-red/core-nodes/parsers/json/) +- [XML](/docs/node-red/core-nodes/parsers/xml/) +- [YAML](/docs/node-red/core-nodes/parsers/yaml/) diff --git a/src/_includes/core-nodes/json-use-case.md b/nuxt/content-guides/node-red/core-nodes/parsers/json.md similarity index 95% rename from src/_includes/core-nodes/json-use-case.md rename to nuxt/content-guides/node-red/core-nodes/parsers/json.md index 2f70af538e..836ebd0376 100644 --- a/src/_includes/core-nodes/json-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/parsers/json.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED JSON node" +metaTitle: "Node-RED - JSON Node" +navTitle: "JSON" +navOrder: 3 +--- + +# JSON + Converts between JSON strings and JavaScript objects. ## Where and why do we use the JSON node? @@ -45,6 +54,11 @@ Suppose you have a JSON data stream from sensors installed on an assembly line i The flow parses incoming JSON strings, groups messages together, calculates the average efficiency, and converts the result back to JSON format. -{% renderFlow %} +::render-flow +```json [{"id":"609e5eb634beaf5c","type":"tab","label":"Flow 4","disabled":false,"info":"","env":[]},{"id":"a0ce2ea0.b7597","type":"inject","z":"609e5eb634beaf5c","name":"Simulate Data","props":[{"p":"payload"}],"repeat":"0.5","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"equipment\":\"Drill Press\",\"timestamp\":\"2023-09-22T12:34:56Z\",\"efficiency\":89.5}","payloadType":"json","x":140,"y":80,"wires":[["8d32bd8d.6d5cc"]]},{"id":"8d32bd8d.6d5cc","type":"json","z":"609e5eb634beaf5c","name":"Parse JSON","property":"payload","action":"obj","pretty":false,"x":330,"y":80,"wires":[["673dc89e.64ac18"]]},{"id":"673dc89e.64ac18","type":"join","z":"609e5eb634beaf5c","name":"Group Messages","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"10","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":530,"y":80,"wires":[["1780e12a.aa407f"]]},{"id":"1780e12a.aa407f","type":"function","z":"609e5eb634beaf5c","name":"Calculate Efficiency","func":"let arr = msg.payload;\nlet sum = 0;\nlet count = 0;\n\narr.forEach(function(item) {\n sum += item.efficiency;\n count++;\n});\n\nlet averageEfficiency = sum / count;\n\nmsg.payload = {\n equipment: arr[0].equipment,\n averageEfficiency: averageEfficiency\n};\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":80,"wires":[["6285ddd29f8b38c7"]]},{"id":"6a79ba9.44db444","type":"debug","z":"609e5eb634beaf5c","name":"Output","active":false,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","x":1110,"y":80,"wires":[]},{"id":"6285ddd29f8b38c7","type":"json","z":"609e5eb634beaf5c","name":"Parse Object","property":"payload","action":"str","pretty":false,"x":950,"y":80,"wires":[["6a79ba9.44db444"]]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="parsers" file="70-JSON" node="json" name="JSON"} +:: diff --git a/src/_includes/core-nodes/xml-use-case.md b/nuxt/content-guides/node-red/core-nodes/parsers/xml.md similarity index 94% rename from src/_includes/core-nodes/xml-use-case.md rename to nuxt/content-guides/node-red/core-nodes/parsers/xml.md index 250f64b36b..6827cea03c 100644 --- a/src/_includes/core-nodes/xml-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/parsers/xml.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED XML node" +metaTitle: "Node-RED - XML Node" +navTitle: "XML" +navOrder: 4 +--- + +# XML + Converts between XML strings and JavaScript objects. ## Where and why do we use the XML node? @@ -96,12 +105,19 @@ Becomes: ### XML String to Object -{% renderFlow %} +::render-flow +```json [{"id":"227621b279d19ace","type":"xml","z":"b446dfa04d79d359","name":"XML to Object","property":"payload","attr":"","chr":"","x":380,"y":180,"wires":[["953fc1f64f4b892e"]]},{"id":"404972c7c517dedd","type":"inject","z":"b446dfa04d79d359","name":"Inject XML","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":" Laptop 999.99 ","payloadType":"str","x":220,"y":180,"wires":[["227621b279d19ace"]]},{"id":"953fc1f64f4b892e","type":"debug","z":"b446dfa04d79d359","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":180,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Object to XML String -{% renderFlow %} +::render-flow +```json [{"id":"227621b279d19ace","type":"xml","z":"b446dfa04d79d359","name":"Object to XML","property":"payload","attr":"","chr":"","x":440,"y":180,"wires":[["953fc1f64f4b892e"]]},{"id":"404972c7c517dedd","type":"inject","z":"b446dfa04d79d359","name":"Inject Object","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{ \"id\": 10, \"name\": \"Demo Item\", \"category\": \"Sample\", \"price\": 499, \"inStock\": true, \"rating\": 4.5, \"manufacturer\": \"DemoCorp\", \"createdAt\": \"2025-01-01T10:00:00Z\", \"description\": \"A medium-sized JSON object for XML conversion testing.\" }","payloadType":"json","x":270,"y":180,"wires":[["227621b279d19ace"]]},{"id":"953fc1f64f4b892e","type":"debug","z":"b446dfa04d79d359","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":590,"y":180,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="parsers" file="70-XML" node="xml" name="XML"} +:: diff --git a/src/_includes/core-nodes/yaml-use-case.md b/nuxt/content-guides/node-red/core-nodes/parsers/yaml.md similarity index 94% rename from src/_includes/core-nodes/yaml-use-case.md rename to nuxt/content-guides/node-red/core-nodes/parsers/yaml.md index 69ad228207..f5bd46724e 100644 --- a/src/_includes/core-nodes/yaml-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/parsers/yaml.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED YAML node" +metaTitle: "Node-RED - YAML Node" +navTitle: "YAML" +navOrder: 5 +--- + +# YAML + Converts between YAML format and JavaScript objects. ## Where and why do we use the YAML node? @@ -30,22 +39,31 @@ The node validates the structure during parsing and will report errors for malfo The YAML node automatically detects the input format. When it receives JSON, it converts the data to YAML format. In this example, a JSON object `{"foo":"bar"}` is converted to YAML. -{% renderFlow %} +::render-flow +```json [{"id":"4481ea08a9fe27e1","type":"inject","z":"7d38803e3d40ee7e","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"foo\":\"bar\"}","payloadType":"json","x":190,"y":220,"wires":[["b31536833d27aee0"]]},{"id":"b31536833d27aee0","type":"yaml","z":"7d38803e3d40ee7e","property":"payload","name":"Parse JSON to YAML","x":400,"y":220,"wires":[["90fed168a9d1a4b5"]]},{"id":"90fed168a9d1a4b5","type":"debug","z":"7d38803e3d40ee7e","name":"Debug: Output YAML","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":640,"y":220,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Parsing YAML to JSON When the input is YAML format, the node automatically converts it to a JavaScript object (JSON). This makes it easy to work with YAML configuration files in Node-RED flows. -{% renderFlow %} +::render-flow +```json [{"id":"a0dc30d8f5225962","type":"inject","z":"7d38803e3d40ee7e","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"foo: bar","payloadType":"str","x":180,"y":300,"wires":[["0fc216b8cebccc25"]]},{"id":"0fc216b8cebccc25","type":"yaml","z":"7d38803e3d40ee7e","property":"payload","name":"Parse YAML to JSON","x":400,"y":300,"wires":[["c9a3f66e67b41ad4"]]},{"id":"c9a3f66e67b41ad4","type":"debug","z":"7d38803e3d40ee7e","name":"Debug: Output JSON","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":640,"y":300,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Error handling for invalid input When the input is malformed, the YAML node throws an error. This error can be caught using a Catch node, allowing you to handle parsing failures gracefully. In this example, the YAML string is missing a closing quote, which triggers an error. -{% renderFlow %} +::render-flow +```json [{"id":"0fc216b8cebccc25","type":"yaml","z":"7d38803e3d40ee7e","property":"payload","name":"Input invalid","x":370,"y":420,"wires":[[]]},{"id":"a0dc30d8f5225962","type":"inject","z":"7d38803e3d40ee7e","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"foo: \"bar","payloadType":"str","x":180,"y":420,"wires":[["0fc216b8cebccc25"]]},{"id":"c9a3f66e67b41ad4","type":"debug","z":"7d38803e3d40ee7e","name":"Caught error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":370,"y":500,"wires":[]},{"id":"6e3ba1ebc7beaf81","type":"catch","z":"7d38803e3d40ee7e","name":"","scope":["0fc216b8cebccc25"],"uncaught":false,"x":190,"y":500,"wires":[["c9a3f66e67b41ad4"]]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="parsers" file="70-YAML" node="yaml" name="YAML"} +:: diff --git a/src/_includes/core-nodes/batch-use-case.md b/nuxt/content-guides/node-red/core-nodes/sequence/batch.md similarity index 96% rename from src/_includes/core-nodes/batch-use-case.md rename to nuxt/content-guides/node-red/core-nodes/sequence/batch.md index 2a5fda3be4..cfcc338451 100644 --- a/src/_includes/core-nodes/batch-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/sequence/batch.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Batch node" +metaTitle: "Node-RED - Batch Node" +navTitle: "Batch" +navOrder: 3 +--- + +# Batch + Creates sequences of messages based on various rules. ## Where and why do we use the Batch node? @@ -12,17 +21,17 @@ The Batch node operates in three different modes, each suited for different use Groups messages into sequences of a given length. Set the batch size to 5 and the first 5 messages form one batch, the next 5 form another batch, and so on. -![Batching messages into 5 groups](./images/batch-example1.png) +![Batching messages into 5 groups](/docs/node-red/core-nodes/images/batch-example1.png) The overlap option lets you repeat messages between batches. When enabled, messages at the end of one sequence appear at the start of the next. With a batch size of 5 and overlap of 1, you get sequences like 1-5, then 5-9, then 9-13. This creates a sliding window effect that's useful for analysis requiring context from previous data. -![Batching messages into 5 groups with overlap](./images/batch-example2.png) +![Batching messages into 5 groups with overlap](/docs/node-red/core-nodes/images/batch-example2.png) ### Group by Time Interval Groups all messages that arrive within a specified time period. Set it to 2 seconds and every message received in that window gets batched together. When the interval expires, the batch releases and a new window starts. You can optionally configure the node to send an empty message if nothing arrives within the interval. - + ### Concatenate Sequences @@ -30,9 +39,9 @@ Creates a new message sequence by combining incoming sequences in a specified or This mode lets you duplicate sequences for parallel processing or reorder them by topic. For example, you could filter an array of numbers into positive and negative values, assign each group a different topic, then concatenate them in whichever order you need. -![Duplicating a sequence of data](./images/batch-example4.png) +![Duplicating a sequence of data](/docs/node-red/core-nodes/images/batch-example4.png) -![Batch filter and concatenate](./images/batch-example5.png) +![Batch filter and concatenate](/docs/node-red/core-nodes/images/batch-example5.png) ## How the node handles messages @@ -42,6 +51,11 @@ If you send a message with the `msg.reset` property set to true, the node immedi ### Demo flows -{% renderFlow 700 %} +::render-flow{:height="700"} +```json [{"id":"52c7d8c93d68afb5","type":"tab","label":"Batch Node","disabled":false,"info":"","env":[]},{"id":"7aa502dddb9274d2","type":"group","z":"52c7d8c93d68afb5","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["e3a5c066.16333","9223c119.c5268","e4d07fa1.78c16","848f59e5.7528d8","cf1bbb5e.ba3e68","a1e311d5.4dca1","2776c823.77eba8","3c47b863c67393a2","93c3e8c551c02f95","572ede73fc15f038","5043a65dd26e130d"],"x":34,"y":439,"w":592,"h":382},{"id":"2701ed93a0fbc58d","type":"group","z":"52c7d8c93d68afb5","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["cf1fd796.197678","d1c8ddf0.99b4e","f727a5d3.ea1a28","f4d6dba4.7e8ab8","31b81865.611788","356c8654.2ad1aa"],"x":34,"y":899,"w":492,"h":242},{"id":"0b151c343c56c94c","type":"group","z":"52c7d8c93d68afb5","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["74853568.22b87c","8f4f683.99d1998","6c47ccb3.bb0184","49c2ac1.59a9354","311dd6b4.5aeb7a","e27c55b0.18e9c8","9e65f29a.69ca2","817acbfb.452af8"],"x":34,"y":59,"w":552,"h":302},{"id":"b857a13782d7c5d9","type":"group","z":"52c7d8c93d68afb5","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["53645699.a35c48","4cb873e6.f9996c","51089be3.4ecbf4","84c34533.6284a8","c7241026.18245","67d24449.028eec","5d909bfb.6faf44","7b289e4ad723a92a"],"x":614,"y":59,"w":592,"h":302},{"id":"3eda846bc0d54e59","type":"group","z":"52c7d8c93d68afb5","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["7f1ce95c.7ddbc8","3412e439.eda55c","e6f01877.16d558","c11e5c5f.876d6","e99c703b.f404","dbd6e8b8.cbf2b8","408f3032.eafc1","5137b2d0.f4838c","c571b56c.ae63b8","c548f2c.641141","4aa7d5ab1091553e","65261ee2e95176c2"],"x":614,"y":899,"w":632,"h":402},{"id":"5043a65dd26e130d","type":"junction","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","x":100,"y":580,"wires":[["93c3e8c551c02f95","3c47b863c67393a2"]]},{"id":"65261ee2e95176c2","type":"junction","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","x":980,"y":1040,"wires":[["3412e439.eda55c"]]},{"id":"e3a5c066.16333","type":"batch","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"","mode":"interval","count":10,"overlap":0,"interval":"2","allowEmptySequence":false,"topics":[],"x":280,"y":660,"wires":[["9223c119.c5268"]]},{"id":"9223c119.c5268","type":"join","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":150,"y":740,"wires":[["e4d07fa1.78c16"]]},{"id":"e4d07fa1.78c16","type":"debug","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":510,"y":740,"wires":[]},{"id":"848f59e5.7528d8","type":"comment","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"↑ create message sequence received within 2s","info":"","x":270,"y":780,"wires":[]},{"id":"cf1bbb5e.ba3e68","type":"comment","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"← join sequence to array","info":"","x":490,"y":660,"wires":[]},{"id":"a1e311d5.4dca1","type":"inject","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":480,"wires":[["5043a65dd26e130d"]]},{"id":"2776c823.77eba8","type":"delay","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"Rate limit 1msg/0.5s","pauseType":"rate","timeout":"1","timeoutUnits":"seconds","rate":"1","nbRateUnits":"0.5","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":460,"y":580,"wires":[["e3a5c066.16333"]]},{"id":"5842d64f.9fc608","type":"comment","z":"52c7d8c93d68afb5","name":"Example: Time-based Group Mode - Group messages received within 2s","info":"*Time-based Group mode* of batch node can be used to create new message sequences from incoming messages received within specified time range. \n","x":300,"y":420,"wires":[]},{"id":"cf1fd796.197678","type":"inject","z":"52c7d8c93d68afb5","g":"2701ed93a0fbc58d","name":"Array of 3 characters [\"a\", \"b\", \"c\"]","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SEQ","payload":"[\"a\", \"b\", \"c\"]","payloadType":"json","x":210,"y":940,"wires":[["d1c8ddf0.99b4e"]]},{"id":"d1c8ddf0.99b4e","type":"split","z":"52c7d8c93d68afb5","g":"2701ed93a0fbc58d","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":430,"y":940,"wires":[["f727a5d3.ea1a28"]]},{"id":"f727a5d3.ea1a28","type":"batch","z":"52c7d8c93d68afb5","g":"2701ed93a0fbc58d","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"SEQ"},{"topic":"SEQ"}],"x":190,"y":1020,"wires":[["f4d6dba4.7e8ab8"]]},{"id":"f4d6dba4.7e8ab8","type":"join","z":"52c7d8c93d68afb5","g":"2701ed93a0fbc58d","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":190,"y":1100,"wires":[["31b81865.611788"]]},{"id":"31b81865.611788","type":"debug","z":"52c7d8c93d68afb5","g":"2701ed93a0fbc58d","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":410,"y":1100,"wires":[]},{"id":"356c8654.2ad1aa","type":"comment","z":"52c7d8c93d68afb5","g":"2701ed93a0fbc58d","name":"← Duplicate SEQ","info":"","x":400,"y":1020,"wires":[]},{"id":"c851c021.a9688","type":"comment","z":"52c7d8c93d68afb5","name":"Example: Concatenate Mode - Duplicate a sequence of data","info":"*Concatenate mode* of batch node can be used to combine input message sequences to create a new message sequence. Order of the sequences can be specified using message topic assigned to each message in a sequence. Message sequence can be specified multiple times.\n","x":260,"y":880,"wires":[]},{"id":"3c47b863c67393a2","type":"change","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"Reset","rules":[{"t":"set","p":"reset","pt":"msg","to":"true","tot":"bool"},{"t":"delete","p":"payload","pt":"msg"},{"t":"delete","p":"topic","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":130,"y":660,"wires":[["e3a5c066.16333"]]},{"id":"3d208473.f31e1c","type":"comment","z":"52c7d8c93d68afb5","name":"Example: Number-based Group Mode - Group 5 consecutive messages","info":"","x":290,"y":40,"wires":[]},{"id":"74853568.22b87c","type":"batch","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"","mode":"count","count":"5","overlap":0,"interval":"5","allowEmptySequence":false,"topics":[],"x":180,"y":220,"wires":[["8f4f683.99d1998"]]},{"id":"8f4f683.99d1998","type":"join","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":170,"y":280,"wires":[["6c47ccb3.bb0184"]]},{"id":"6c47ccb3.bb0184","type":"debug","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":350,"y":280,"wires":[]},{"id":"49c2ac1.59a9354","type":"comment","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"↑ create message sequence with 5 messages","info":"","x":290,"y":320,"wires":[]},{"id":"311dd6b4.5aeb7a","type":"comment","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"← join sequence to array","info":"","x":390,"y":220,"wires":[]},{"id":"e27c55b0.18e9c8","type":"inject","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":100,"wires":[["9e65f29a.69ca2"]]},{"id":"9e65f29a.69ca2","type":"function","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"send: 1..20","func":"for(var x = 1; x <= 20; x++) {\n node.send({payload: x});\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":170,"y":160,"wires":[["74853568.22b87c"]]},{"id":"817acbfb.452af8","type":"comment","z":"52c7d8c93d68afb5","g":"0b151c343c56c94c","name":"← send 20 msgs with numbers 1..20","info":"","x":420,"y":160,"wires":[]},{"id":"ecff527d.d64cb","type":"comment","z":"52c7d8c93d68afb5","name":"Example: Number-based Group Mode - 5 consecutive messages, overlap 1 msg","info":"","x":900,"y":40,"wires":[]},{"id":"53645699.a35c48","type":"batch","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"","mode":"count","count":"5","overlap":"1","interval":"5","allowEmptySequence":false,"topics":[],"x":760,"y":220,"wires":[["4cb873e6.f9996c"]]},{"id":"4cb873e6.f9996c","type":"join","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":"false","timeout":"","count":"","reduceRight":false,"x":750,"y":280,"wires":[["51089be3.4ecbf4"]]},{"id":"51089be3.4ecbf4","type":"debug","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":930,"y":280,"wires":[]},{"id":"84c34533.6284a8","type":"comment","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"↑ create message sequence with 5 messages with overlap of 1 msg","info":"","x":940,"y":320,"wires":[]},{"id":"c7241026.18245","type":"comment","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"← join sequence to array","info":"","x":970,"y":220,"wires":[]},{"id":"67d24449.028eec","type":"inject","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":720,"y":100,"wires":[["5d909bfb.6faf44"]]},{"id":"5d909bfb.6faf44","type":"function","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"send: 1..20","func":"for (var x = 1; x <= 20; x++) {\n node.send({ payload: x });\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":750,"y":160,"wires":[["53645699.a35c48"]]},{"id":"7b289e4ad723a92a","type":"comment","z":"52c7d8c93d68afb5","g":"b857a13782d7c5d9","name":"← send 20 msgs with numbers 1..20","info":"","x":1000,"y":160,"wires":[]},{"id":"93c3e8c551c02f95","type":"function","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"send: 1..20","func":"for (var x = 1; x <= 20; x++) {\n node.send({ payload: x });\n}","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":250,"y":580,"wires":[["2776c823.77eba8"]]},{"id":"572ede73fc15f038","type":"comment","z":"52c7d8c93d68afb5","g":"7aa502dddb9274d2","name":"↓ send 20 msgs with numbers 1..20","info":"","x":320,"y":540,"wires":[]},{"id":"7f1ce95c.7ddbc8","type":"join","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"","mode":"auto","build":"string","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":770,"y":1220,"wires":[["e6f01877.16d558"]]},{"id":"3412e439.eda55c","type":"batch","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"","mode":"concat","count":10,"overlap":0,"interval":10,"allowEmptySequence":false,"topics":[{"topic":"NEG"},{"topic":"POS"}],"x":770,"y":1140,"wires":[["7f1ce95c.7ddbc8"]]},{"id":"e6f01877.16d558","type":"debug","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","x":1050,"y":1220,"wires":[]},{"id":"c11e5c5f.876d6","type":"change","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"POS","rules":[{"t":"set","p":"topic","pt":"msg","to":"POS","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":887,"y":1020,"wires":[["65261ee2e95176c2"]]},{"id":"e99c703b.f404","type":"change","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"NEG","rules":[{"t":"set","p":"topic","pt":"msg","to":"NEG","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":887,"y":1060,"wires":[["65261ee2e95176c2"]]},{"id":"dbd6e8b8.cbf2b8","type":"switch","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":">= 0? \\n < 0?","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"0","vt":"num"},{"t":"else"}],"checkall":"true","repair":true,"outputs":2,"x":750,"y":1040,"wires":[["c11e5c5f.876d6"],["e99c703b.f404"]]},{"id":"408f3032.eafc1","type":"split","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":930,"y":940,"wires":[["dbd6e8b8.cbf2b8"]]},{"id":"5137b2d0.f4838c","type":"inject","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"","repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"SEQ","payload":"[1,-6,-8,7,2,-3]","payloadType":"json","x":750,"y":940,"wires":[["408f3032.eafc1"]]},{"id":"c571b56c.ae63b8","type":"comment","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"↑ Join the sequence of messages","info":"","x":850,"y":1260,"wires":[]},{"id":"c548f2c.641141","type":"comment","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"← Order sequence of messages: NEG, POS","info":"","x":1050,"y":1140,"wires":[]},{"id":"16341de8ac839049","type":"comment","z":"52c7d8c93d68afb5","name":"Example: Concatenate Mode - Batch Filter & Concat","info":"*Concatenate mode* of batch node can be used to combine input message sequences to create a new message sequence. Order of the sequences can be specified using message topic assigned to each message in a sequence. Message sequence can be specified multiple times.\n","x":810,"y":880,"wires":[]},{"id":"4aa7d5ab1091553e","type":"comment","z":"52c7d8c93d68afb5","g":"3eda846bc0d54e59","name":"Set topic \"POS\" \\n or \"NEG\"","info":"","x":1140,"y":1040,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="sequence" file="19-batch" node="batch" name="Batch"} +:: diff --git a/nuxt/content-guides/node-red/core-nodes/sequence/index.md b/nuxt/content-guides/node-red/core-nodes/sequence/index.md new file mode 100644 index 0000000000..ba7f572e31 --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/sequence/index.md @@ -0,0 +1,18 @@ +--- +title: "Node-RED sequence nodes" +metaTitle: "Node-RED Sequence Nodes" +navTitle: "Sequence nodes" +navOrder: 4 +meta: + description: "The Sequence nodes in Node-RED's default palette, and what each one is for." +--- + +# Sequence + +The **Sequence** section of Node-RED's default palette. Each page opens with why you +would reach for that node, then mirrors the node's built-in help. + +- [Split](/docs/node-red/core-nodes/sequence/split/) +- [Sort](/docs/node-red/core-nodes/sequence/sort/) +- [Batch](/docs/node-red/core-nodes/sequence/batch/) +- [Join](/docs/node-red/core-nodes/sequence/join/) diff --git a/src/_includes/core-nodes/join-use-case.md b/nuxt/content-guides/node-red/core-nodes/sequence/join.md similarity index 96% rename from src/_includes/core-nodes/join-use-case.md rename to nuxt/content-guides/node-red/core-nodes/sequence/join.md index 45a5a38d66..411d793fd0 100644 --- a/src/_includes/core-nodes/join-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/sequence/join.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Join node" +metaTitle: "Node-RED - Join Node" +navTitle: "Join" +navOrder: 4 +--- + +# Join + Joins sequences of messages into a single message. ## Where and why do we use the Join node? @@ -59,22 +68,31 @@ For manual mode, the other properties of the output message come from the last m This example shows automatic mode. The Split node breaks an array into individual messages, then Join automatically reassembles them back into the original array. -{% renderFlow %} +::render-flow +```json [{"id":"b5ea6d2a.6e7bb","type":"tab","label":"openValve","disabled":false,"info":""},{"id":"84ed227552b4e6eb","type":"join","z":"b5ea6d2a.6e7bb","name":"","mode":"auto","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":590,"y":300,"wires":[["f2dba285d7a067cd"]]},{"id":"522b4e247e84ac0e","type":"inject","z":"b5ea6d2a.6e7bb","name":"Send array","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[ { \"id\": 1, \"task\": \"Complete project proposal\", \"completed\": false }, { \"id\": 2, \"task\": \"Review presentation slides\", \"completed\": true }, { \"id\": 3, \"task\": \"Prepare for client meeting\", \"completed\": false } ]","payloadType":"json","x":220,"y":300,"wires":[["351e98a55e5a50c6"]]},{"id":"f2dba285d7a067cd","type":"debug","z":"b5ea6d2a.6e7bb","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":300,"wires":[]},{"id":"d47a5edb2d5d5b70","type":"comment","z":"b5ea6d2a.6e7bb","name":"Joining the messages to reverse the split that was performed.","info":"","x":500,"y":220,"wires":[]},{"id":"351e98a55e5a50c6","type":"split","z":"b5ea6d2a.6e7bb","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":410,"y":300,"wires":[["84ed227552b4e6eb"]]}] -{% endrenderFlow %} +``` +:: ### Manual mode Here manual mode combines three separate sensor readings into one object. Each message has a different `msg.topic` (temperature, humidity, pressure) and those topics become the keys in the output object. -{% renderFlow %} +::render-flow +```json [{"id":"b5ea6d2a.6e7bb","type":"tab","label":"openValve","disabled":false,"info":""},{"id":"84ed227552b4e6eb","type":"join","z":"b5ea6d2a.6e7bb","name":"","mode":"custom","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":490,"y":300,"wires":[["f2dba285d7a067cd"]]},{"id":"522b4e247e84ac0e","type":"inject","z":"b5ea6d2a.6e7bb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"temperature","payload":"40","payloadType":"num","x":260,"y":240,"wires":[["84ed227552b4e6eb"]]},{"id":"12e54e4066bac7a3","type":"inject","z":"b5ea6d2a.6e7bb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"humidity","payload":"33","payloadType":"num","x":270,"y":300,"wires":[["84ed227552b4e6eb"]]},{"id":"b04d4f51f0602607","type":"inject","z":"b5ea6d2a.6e7bb","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"pressure","payload":"1000","payloadType":"num","x":270,"y":360,"wires":[["84ed227552b4e6eb"]]},{"id":"f2dba285d7a067cd","type":"debug","z":"b5ea6d2a.6e7bb","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":720,"y":300,"wires":[]},{"id":"d47a5edb2d5d5b70","type":"comment","z":"b5ea6d2a.6e7bb","name":"Combining three payload into one object ","info":"","x":520,"y":180,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Reduce sequence mode This example uses reduce mode to calculate total inventory. The expression `$A+payload.quantity` adds each item's quantity to the running total, starting from 0. -{% renderFlow %} +::render-flow +```json [{"id":"b5ea6d2a.6e7bb","type":"tab","label":"openValve","disabled":false,"info":""},{"id":"84ed227552b4e6eb","type":"join","z":"b5ea6d2a.6e7bb","name":"","mode":"reduce","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"3","reduceRight":false,"reduceExp":"$A+payload.quantity","reduceInit":"0","reduceInitType":"num","reduceFixup":"$A","x":590,"y":300,"wires":[["f2dba285d7a067cd"]]},{"id":"522b4e247e84ac0e","type":"inject","z":"b5ea6d2a.6e7bb","name":"Send array","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[ { \"id\": 1, \"name\": \"Laptop\", \"quantity\": 15 }, { \"id\": 2, \"name\": \"Printer\", \"quantity\": 5 }, { \"id\": 3, \"name\": \"Monitor\", \"quantity\": 10 } ]","payloadType":"json","x":220,"y":300,"wires":[["351e98a55e5a50c6"]]},{"id":"f2dba285d7a067cd","type":"debug","z":"b5ea6d2a.6e7bb","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":300,"wires":[]},{"id":"d47a5edb2d5d5b70","type":"comment","z":"b5ea6d2a.6e7bb","name":"Calculating total stocks using Join node reduced expression mode","info":"","x":510,"y":220,"wires":[]},{"id":"351e98a55e5a50c6","type":"split","z":"b5ea6d2a.6e7bb","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":410,"y":300,"wires":[["84ed227552b4e6eb"]]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="sequence" file="17-split" node="join" name="Join"} +:: diff --git a/src/_includes/core-nodes/sort-use-case.md b/nuxt/content-guides/node-red/core-nodes/sequence/sort.md similarity index 95% rename from src/_includes/core-nodes/sort-use-case.md rename to nuxt/content-guides/node-red/core-nodes/sequence/sort.md index 425f243354..d2d3b85d3f 100644 --- a/src/_includes/core-nodes/sort-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/sequence/sort.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Sort node" +metaTitle: "Node-RED - Sort Node" +navTitle: "Sort" +navOrder: 2 +--- + +# Sort + Sorts an array or a sequence of messages. ## Where and why do we use the Sort node? @@ -41,14 +50,21 @@ Enable **As numbers** to sort numerically instead of alphabetically. Without thi This example sorts numbers and letters in ascending order. The arrays get arranged from smallest to largest, or A to Z. -{% renderFlow %} +::render-flow +```json [{"id":"b5ea6d2a.6e7bb","type":"tab","label":"openValve","disabled":false,"info":""},{"id":"416d6d32df411abe","type":"sort","z":"b5ea6d2a.6e7bb","name":"","order":"ascending","as_num":false,"target":"payload","targetType":"msg","msgKey":"payload","msgKeyType":"elem","seqKey":"payload.quantity","seqKeyType":"msg","x":570,"y":320,"wires":[["eb923bde78247dc5"]]},{"id":"c8bd64176725f43f","type":"inject","z":"b5ea6d2a.6e7bb","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[7,8,41,90,2,4,2]","payloadType":"json","x":360,"y":320,"wires":[["416d6d32df411abe"]]},{"id":"eb923bde78247dc5","type":"debug","z":"b5ea6d2a.6e7bb","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":320,"wires":[]},{"id":"67f2e62eec9509c5","type":"comment","z":"b5ea6d2a.6e7bb","name":"Ordering numbers in ascending order","info":"","x":530,"y":240,"wires":[]},{"id":"481e382abac7a730","type":"sort","z":"b5ea6d2a.6e7bb","name":"","order":"ascending","as_num":false,"target":"payload","targetType":"msg","msgKey":"payload","msgKeyType":"elem","seqKey":"payload.quantity","seqKeyType":"msg","x":570,"y":440,"wires":[["f43093160e436025"]]},{"id":"21502b212a9c0f80","type":"inject","z":"b5ea6d2a.6e7bb","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"G\", \"F\", \"T\", \"A\", \"R\", \"P\", \"H\", \"W\", \"C\", \"Y\", \"N\", \"B\", \"L\", \"O\", \"X\", \"I\", \"V\", \"E\", \"J\", \"U\", \"K\", \"M\", \"S\", \"Z\", \"D\", \"Q\"]","payloadType":"json","x":330,"y":440,"wires":[["481e382abac7a730"]]},{"id":"f43093160e436025","type":"debug","z":"b5ea6d2a.6e7bb","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":440,"wires":[]},{"id":"dba624ac20a75580","type":"comment","z":"b5ea6d2a.6e7bb","name":"Ordering alphabets in ascending order","info":"","x":510,"y":380,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Sorting message sequences Here the Sort node arranges a sequence of messages in descending order by the quantity property. The Split node breaks the array into individual messages, each gets sorted by its quantity value, and higher quantities come first. -{% renderFlow %} +::render-flow +```json [{"id":"416d6d32df411abe","type":"sort","z":"b5ea6d2a.6e7bb","name":"","order":"descending","as_num":false,"target":"","targetType":"seq","msgKey":"payload","msgKeyType":"elem","seqKey":"payload.quantity","seqKeyType":"msg","x":550,"y":320,"wires":[["eb923bde78247dc5"]]},{"id":"c8bd64176725f43f","type":"inject","z":"b5ea6d2a.6e7bb","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[ { \"id\": 1, \"name\": \"Laptop\", \"quantity\": 15 }, { \"id\": 2, \"name\": \"Printer\", \"quantity\": 5 }, { \"id\": 3, \"name\": \"Monitor\", \"quantity\": 10 } ]","payloadType":"json","x":270,"y":320,"wires":[["05e33079464a9243"]]},{"id":"eb923bde78247dc5","type":"debug","z":"b5ea6d2a.6e7bb","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":720,"y":320,"wires":[]},{"id":"05e33079464a9243","type":"split","z":"b5ea6d2a.6e7bb","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":410,"y":320,"wires":[["416d6d32df411abe"]]},{"id":"67f2e62eec9509c5","type":"comment","z":"b5ea6d2a.6e7bb","name":"Ordering sequence of messages in descending order based on the quantity property of each message.","info":"","x":530,"y":240,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="sequence" file="18-sort" node="sort" name="Sort"} +:: diff --git a/src/_includes/core-nodes/split-use-case.md b/nuxt/content-guides/node-red/core-nodes/sequence/split.md similarity index 95% rename from src/_includes/core-nodes/split-use-case.md rename to nuxt/content-guides/node-red/core-nodes/sequence/split.md index 5cfd362980..f5c1809701 100644 --- a/src/_includes/core-nodes/split-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/sequence/split.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Split node" +metaTitle: "Node-RED - Split Node" +navTitle: "Split" +navOrder: 1 +--- + +# Split + Splits a message into a sequence of messages. ## Where and why do we use the Split node? @@ -46,9 +55,11 @@ Because streaming mode doesn't know how many messages to expect, it doesn't set Arrays are the simplest case. Feed in an array and get one message per element. Here an array `[1, 2, 3, 4]` becomes four messages. -{% renderFlow %} +::render-flow +```json [{"id":"6354daaccf2b2504","type":"inject","z":"2862bf5c278ff5bd","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1, 2, 3, 4]","payloadType":"json","x":140,"y":100,"wires":[["82ab52c7f894f725"]]},{"id":"82ab52c7f894f725","type":"split","z":"2862bf5c278ff5bd","name":"Split Array","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":310,"y":100,"wires":[["80ee79b75e373ba9"]]},{"id":"80ee79b75e373ba9","type":"debug","z":"2862bf5c278ff5bd","name":"Print individual values","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":520,"y":100,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Regrouping elements @@ -56,9 +67,11 @@ Sometimes you need to chunk data into smaller groups. Say an API only accepts 20 With input `[1, 2, 3, 4, 5]` and `Fixed length of` set to 2, you get three messages: `[1, 2]`, `[3, 4]`, and `[5]`. -{% renderFlow %} +::render-flow +```json [{"id":"57087c8029d44fa2","type":"inject","z":"2862bf5c278ff5bd","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1, 2, 3, 4, 5]","payloadType":"json","x":150,"y":160,"wires":[["b8d0aec7f0cba6c5"]]},{"id":"b8d0aec7f0cba6c5","type":"split","z":"2862bf5c278ff5bd","name":"Regroup array","splt":"\\n","spltType":"str","arraySplt":"2","arraySpltType":"len","stream":false,"addname":"","x":340,"y":160,"wires":[["d45d698bae8b575d"]]},{"id":"d45d698bae8b575d","type":"debug","z":"2862bf5c278ff5bd","name":"Print individual values","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":160,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Splitting strings @@ -66,17 +79,21 @@ The default string split uses `\n` (newline) as the delimiter, which splits text Here we split a list of European cities, one per line. -{% renderFlow %} +::render-flow +```json [{"id":"39a0a053a3696cd7","type":"inject","z":"2862bf5c278ff5bd","name":"Trigger","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":220,"wires":[["60bf012438abb4eb"]]},{"id":"4b56a3ed831df59e","type":"split","z":"2862bf5c278ff5bd","name":"Split by line","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":470,"y":220,"wires":[["31f8ca22882b297f"]]},{"id":"31f8ca22882b297f","type":"debug","z":"2862bf5c278ff5bd","name":"Print each line","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":660,"y":220,"wires":[]},{"id":"60bf012438abb4eb","type":"template","z":"2862bf5c278ff5bd","name":"Data in lines","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Amsterdam\nAndorra la Vella\nAthens","output":"str","x":290,"y":220,"wires":[["4b56a3ed831df59e"]]}] -{% endrenderFlow %} +``` +:: ### Splitting by word Change the delimiter to a space and you can split sentences into words. Put a space character in the `Split using` field. It won't be visible in the form but it's there. -{% renderFlow %} +::render-flow +```json [{"id":"619209d6e3f02473","type":"inject","z":"2862bf5c278ff5bd","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"foo bar","payloadType":"str","x":130,"y":280,"wires":[["15b9b3d17a64e2c7"]]},{"id":"15b9b3d17a64e2c7","type":"split","z":"2862bf5c278ff5bd","name":"Split by space","splt":" ","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":300,"y":280,"wires":[["12607e8708ef58f2"]]},{"id":"12607e8708ef58f2","type":"debug","z":"2862bf5c278ff5bd","name":"Print each word","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":500,"y":280,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Splitting objects @@ -84,6 +101,11 @@ When you split an object, you get one message per key-value pair. The key goes i This example splits a simple object mapping words to numbers. -{% renderFlow %} +::render-flow +```json [{"id":"3c4c5535ec3b2138","type":"inject","z":"2862bf5c278ff5bd","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"one\": 1, \"two\": 2}","payloadType":"json","x":170,"y":340,"wires":[["eb3227c954debb95"]]},{"id":"eb3227c954debb95","type":"split","z":"2862bf5c278ff5bd","name":"Split map","splt":"\\n","spltType":"str","arraySplt":"1","arraySpltType":"len","stream":false,"addname":"","x":360,"y":340,"wires":[["8c82877cdaff8f0d"]]},{"id":"8c82877cdaff8f0d","type":"debug","z":"2862bf5c278ff5bd","name":"Print property values","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":340,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: + +::node-red-help{category="sequence" file="17-split" node="split" name="Split"} +:: diff --git a/nuxt/content-guides/node-red/core-nodes/storage/index.md b/nuxt/content-guides/node-red/core-nodes/storage/index.md new file mode 100644 index 0000000000..cae21ff4c7 --- /dev/null +++ b/nuxt/content-guides/node-red/core-nodes/storage/index.md @@ -0,0 +1,16 @@ +--- +title: "Node-RED storage nodes" +metaTitle: "Node-RED Storage Nodes" +navTitle: "Storage nodes" +navOrder: 6 +meta: + description: "The Storage nodes in Node-RED's default palette, and what each one is for." +--- + +# Storage + +The **Storage** section of Node-RED's default palette. Each page opens with why you +would reach for that node, then mirrors the node's built-in help. + +- [Write File](/docs/node-red/core-nodes/storage/write-file/) +- [Read File](/docs/node-red/core-nodes/storage/read-file/) diff --git a/src/_includes/core-nodes/read-file-use-case.md b/nuxt/content-guides/node-red/core-nodes/storage/read-file.md similarity index 96% rename from src/_includes/core-nodes/read-file-use-case.md rename to nuxt/content-guides/node-red/core-nodes/storage/read-file.md index 5fa101c591..c49dc11f0a 100644 --- a/src/_includes/core-nodes/read-file-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/storage/read-file.md @@ -1,3 +1,12 @@ +--- +title: "Node-RED Read File node" +metaTitle: "Node-RED - Read File Node" +navTitle: "Read File" +navOrder: 2 +--- + +# Read File + ## What is the Read File node in Node-RED? The Read File node in Node-RED is used to read the contents of a file from the file system. In FlowFuse Cloud, the Read File node interacts with a cloud-based storage solution, leveraging AWS S3 for file storage. However, in the Node-RED instance running on edge devices using FlowFuse device agent, this node will read the file from the device's local file system. The Read File node can read both string and binary buffer data, making it versatile for various integration needs within Node-RED flows. @@ -46,12 +55,19 @@ The Read File node in Node-RED is used to read the contents of a file from the f 1. In the example flow, we demonstrate how to read the file content using the Read File node and obtain the output as a string. -{% renderFlow %} +::render-flow +```json [{"id":"5098633f9aec714f","type":"file in","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"","filename":"/myfile.txt/","filenameType":"str","format":"utf8","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":520,"y":360,"wires":[["60134a0cf5da9669"]]},{"id":"c4feedf988092418","type":"inject","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Read file","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":280,"y":360,"wires":[["5098633f9aec714f"]]},{"id":"60134a0cf5da9669","type":"debug","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":360,"wires":[]},{"id":"bfb2903869157e1f","type":"file","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"","filename":"/myfile.txt/","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":520,"y":240,"wires":[["2537ebc276403591"]]},{"id":"01503f5a493d03cc","type":"inject","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Write into file","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":300,"y":240,"wires":[["bfb2903869157e1f"]]},{"id":"2537ebc276403591","type":"debug","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":240,"wires":[]},{"id":"42f7744a16a52b9d","type":"comment","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Reading file using read file node","info":"","x":530,"y":300,"wires":[]},{"id":"8155df85eb599235","type":"comment","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Creating a myfile.txt file and writing content to it.","info":"","x":520,"y":180,"wires":[]}] -{% endrenderFlow %} +``` +:: 2. In the example flow, we demonstrate how to read the file content using the Read File node and obtain the output as a buffer object. -{% renderFlow %} +::render-flow +```json [{"id":"ead17e92ffc5c32a","type":"group","z":"b5ea6d2a.6e7bb","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["5098633f9aec714f","c4feedf988092418","60134a0cf5da9669","bfb2903869157e1f","01503f5a493d03cc","2537ebc276403591","42f7744a16a52b9d","8155df85eb599235"],"x":174,"y":139,"w":712,"h":262},{"id":"5098633f9aec714f","type":"file in","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"","filename":"/myfile.txt/","filenameType":"str","format":"","chunk":false,"sendError":false,"encoding":"none","allProps":false,"x":520,"y":360,"wires":[["60134a0cf5da9669"]]},{"id":"c4feedf988092418","type":"inject","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Read file","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":280,"y":360,"wires":[["5098633f9aec714f"]]},{"id":"60134a0cf5da9669","type":"debug","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":360,"wires":[]},{"id":"bfb2903869157e1f","type":"file","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"","filename":"/myfile.txt/","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":520,"y":240,"wires":[["2537ebc276403591"]]},{"id":"01503f5a493d03cc","type":"inject","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Write into file","props":[{"p":"payload"}],"repeat":"5","crontab":"","once":true,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":300,"y":240,"wires":[["bfb2903869157e1f"]]},{"id":"2537ebc276403591","type":"debug","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":240,"wires":[]},{"id":"42f7744a16a52b9d","type":"comment","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Reading file using read file node","info":"","x":530,"y":300,"wires":[]},{"id":"8155df85eb599235","type":"comment","z":"b5ea6d2a.6e7bb","g":"ead17e92ffc5c32a","name":"Creating a myfile.txt file and writing content to it.","info":"","x":520,"y":180,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="storage" file="10-file" node="file in" name="Read File"} +:: diff --git a/src/_includes/core-nodes/write-file-use-case.md b/nuxt/content-guides/node-red/core-nodes/storage/write-file.md similarity index 95% rename from src/_includes/core-nodes/write-file-use-case.md rename to nuxt/content-guides/node-red/core-nodes/storage/write-file.md index 72b5b0a85d..5f9bdcc359 100644 --- a/src/_includes/core-nodes/write-file-use-case.md +++ b/nuxt/content-guides/node-red/core-nodes/storage/write-file.md @@ -1,8 +1,17 @@ +--- +title: "Node-RED Write File node" +metaTitle: "Node-RED - Write File Node" +navTitle: "Write File" +navOrder: 1 +--- + +# Write File + ## What is the Write File Node in Node-RED? The "Write File" node in Node-RED is used to write data to a file on the filesystem. It's commonly employed in flows where you need to save data or logs for later analysis or storage. In FlowFuse Cloud, the Write File node interacts with a cloud-based storage solution, leveraging AWS S3 for file storage. However, in the Node-RED instance running on edge devices using the [ FlowFuse device agent](/platform/device-agent/), this node will interact with the local file system of that device. The content to be written is specified using `msg.payload`. -# Configuring the Write File Node in Node-RED: +## Configuring the Write File Node in Node-RED: - **Filename:** Specify the filename - **Path:** Specify the path to the file where data will be saved. @@ -51,12 +60,19 @@ After the completion of the write operation, the input message passed to the wri 1. In the example flow below, we demonstrate how to create a file and write content to it. -{% renderFlow %} +::render-flow +```json [{"id":"aa247fb1ef163c92","type":"group","z":"b5ea6d2a.6e7bb","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["bfb2903869157e1f","01503f5a493d03cc","2537ebc276403591","8ad766e1d67466f4"],"x":194,"y":179,"w":712,"h":162},{"id":"bfb2903869157e1f","type":"file","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"","filename":"/myfile.txt/","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"false","encoding":"none","x":540,"y":300,"wires":[["2537ebc276403591"]]},{"id":"01503f5a493d03cc","type":"inject","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"Write into file","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":310,"y":300,"wires":[["bfb2903869157e1f"]]},{"id":"2537ebc276403591","type":"debug","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":800,"y":300,"wires":[]},{"id":"8ad766e1d67466f4","type":"comment","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"Write timestamps to the myfile.txt file. If the file doesn't exist, it will be created.","info":"","x":550,"y":220,"wires":[]}] -{% endrenderFlow %} +``` +:: 2. In the example flow below, we demonstrate how to delete a file using the Write File node. -{% renderFlow %} +::render-flow +```json [{"id":"b5ea6d2a.6e7bb","type":"tab","label":"openValve","disabled":false,"info":""},{"id":"aa247fb1ef163c92","type":"group","z":"b5ea6d2a.6e7bb","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["bfb2903869157e1f","01503f5a493d03cc","2537ebc276403591","8ad766e1d67466f4"],"x":194,"y":179,"w":712,"h":162},{"id":"bfb2903869157e1f","type":"file","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"","filename":"/myfile.txt/","filenameType":"str","appendNewline":true,"createDir":false,"overwriteFile":"delete","encoding":"none","x":550,"y":300,"wires":[["2537ebc276403591"]]},{"id":"01503f5a493d03cc","type":"inject","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"Delete file","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":300,"y":300,"wires":[["bfb2903869157e1f"]]},{"id":"2537ebc276403591","type":"debug","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":800,"y":300,"wires":[]},{"id":"8ad766e1d67466f4","type":"comment","z":"b5ea6d2a.6e7bb","g":"aa247fb1ef163c92","name":"Deleting myfile.txt using write file node","info":"","x":550,"y":220,"wires":[]}] -{% endrenderFlow %} +``` +:: + +::node-red-help{category="storage" file="10-file" node="file" name="Write File"} +:: diff --git a/nuxt/content-guides/node-red/database/README.md b/nuxt/content-guides/node-red/database/README.md new file mode 100644 index 0000000000..431fc70ec1 --- /dev/null +++ b/nuxt/content-guides/node-red/database/README.md @@ -0,0 +1,31 @@ +--- +title: Node-RED Database Integration Guides +metaTitle: "Node-RED Database Integration Guides" +navTitle: Databases +navOrder: 5 +meta: + description: Explore database integration guides for Node-RED, including PostgreSQL, MongoDB, InfluxDB, DynamoDB, and TimescaleDB + keywords: node-red, databases, integration, PostgreSQL, MongoDB, InfluxDB, DynamoDB, TimescaleDB +--- + +# Node-RED Database Integration Guides + +Node-RED is highly versatile and can be set up to work with a variety of databases, whether it is SQL (e.g., PostgreSQL, MySQL), NoSQL (e.g., MongoDB), or time-series databases (e.g., InfluxDB). This flexibility allows you to store and manage IoT data effectively, enabling the creation of interactive and data-driven applications for IoT environments. + +## Resources + +Here are some resources to help you get started with Node-RED on diffrent types of databases. Each guide provides step-by-step instructions to help you get started, along with advanced techniques for optimizing performance and handling complex data operations.: + +- [Using DynamoDB with Node-RED (2026 Updated)](/docs/node-red/database/dynamodb/): Get started with AWS' NoSQL database DynamoDB with Node-RED +- [Using Firebase with Node-RED (2026 Updated)](/docs/node-red/database/firebase/): Learn how to integrate Cloud Firestore with Node-RED to build real-time event-driven applications. This guide covers Firestore setup, reading, writing, and listening to data using Node-RED. +- [Using InfluxDB with Node-RED (2026 Updated)](/docs/node-red/database/influxdb/): Node-RED has great support for InfluxDB. In this guide, we'll explain how to get your data flowing into one of the most popular time-series databases. +- [Using MongoDB With Node-RED (2026 Updated)](/docs/node-red/database/mongodb/): Learn how to seamlessly integrate MongoDB, a NoSQL database, into your Node-RED applications with this step-by-step documentation. +- [Using MySQL with Node-RED (2026 Updated)](/docs/node-red/database/mysql/): Learn how to seamlessly integrate MySQL with Node-RED for efficient data management and application development. +- [Using PostgreSQL with Node-RED (2026 Updated)](/docs/node-red/database/postgresql/): Learn how to seamlessly integrate PostgreSQL with Node-RED for efficient data management and application development. +- [Using Redis with Node-RED (2026 Updated)](/docs/node-red/database/redis/): Learn how to integrate Redis with Node-RED for fast data storage, pub/sub messaging, JSON handling, Lua scripting, and advanced Redis operations in Node-RED flows. +- [Using SQLite with Node-RED (2026 Updated)](/docs/node-red/database/sqlite/): Learn how to seamlessly integrate SQLite with Node-RED for efficient data management and application development. +- [Using TimescaleDB with Node-RED (2026 Updated)](/docs/node-red/database/timescaledb/): Learn how to integrate TimescaleDB with Node-RED for storing and managing time-series data efficiently. + +::callout{icon="i-lucide-badge-check"} +**A certified node for these databases.** The guides in this section use community packages. One database here is also covered by a FlowFuse certified node, maintained for production use: [redis](/integrations/?certified=1) +:: diff --git a/src/node-red/database/dynamodb.md b/nuxt/content-guides/node-red/database/dynamodb.md similarity index 98% rename from src/node-red/database/dynamodb.md rename to nuxt/content-guides/node-red/database/dynamodb.md index 297bfd82dc..a7c5369118 100644 --- a/src/node-red/database/dynamodb.md +++ b/nuxt/content-guides/node-red/database/dynamodb.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: DynamoDB - parent: Database +title: Using DynamoDB with Node-RED (2026 Updated) +metaTitle: "Using DynamoDB with Node-RED (2026 Updated)" +navTitle: DynamoDB +navOrder: 50 meta: - title: Using DynamoDB with Node-RED (2026 Updated) description: Get started with AWS' NoSQL database DynamoDB with Node-RED keywords: node-red, flowfuse, integration, dynamodb, aws, nosql -image: /node-red/core-nodes/images/node-red-dynamodb.png +image: /docs/node-red/core-nodes/images/node-red-dynamodb.png --- -# {{ meta.title }} +# Using DynamoDB with Node-RED (2026 Updated) Amazon's DynamoDB is a fully managed NoSQL database service known for its fast and predictable performance and scalable design. This makes it suitable for applications needing low-latency responses. In this documentation, we’ll look at how to set up and use DynamoDB, configure the necessary IAM roles, and apply Node-RED flows to store and retrieve data effectively. @@ -69,9 +69,11 @@ Now, let's dive into some practical examples using Node-RED to manage a customer This first flow will generate data and send it to DynamoDB via the PutItem operation. -{% renderFlow %} +::render-flow +```json [{"id":"10b0b970739d616f","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"","operation":"PutItem","Statements":"","RequestItems":"","TableName":"FlowFuse-Demo","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":820,"y":240,"wires":[["0157a98fa69fe514"],["dc92470ddaa225bf"]]},{"id":"functionNode","type":"function","z":"37b2bc0d1ebdc616","name":"Set Value","func":"msg.Item = {\n \"source\": { \"S\": msg.source },\n \"time\": { \"N\": String(msg.payload) },\n \"temp\": {\"S\": String(msg.datagen.temp) },\n \"email\": {\"S\": msg.datagen.email},\n \"name\": {\"S\": msg.datagen.name},\n \"work\": {\"S\": msg.datagen.work},\n \"address\": {\"S\": msg.datagen.address},\n \"country\": {\"S\": msg.datagen.country},\n};\n\nmsg.TableName = \"FlowFuse-Email\";\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":620,"y":240,"wires":[["10b0b970739d616f"]]},{"id":"0157a98fa69fe514","type":"debug","z":"37b2bc0d1ebdc616","name":"Store Customer Info","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1060,"y":220,"wires":[]},{"id":"dc92470ddaa225bf","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 28","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1020,"y":260,"wires":[]},{"id":"45d46649bae57332","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"source","v":"FF_DEVICE_NAME","vt":"env"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":240,"wires":[["1160ce65b26429f3"]]},{"id":"1160ce65b26429f3","type":"data-generator","z":"37b2bc0d1ebdc616","name":"","field":"datagen","fieldType":"msg","syntax":"json","template":"{\n \"name\": \"{{firstName}} {{lastName}}\",\n \"work\": \"{{company}}\",\n \"email\": \"{{email}}\",\n \"address\": \"{{int 1 100}} {{street}}\",\n \"country\": \"{{country}}\",\n \"temp\": {{float 30 36}}\n}","x":440,"y":240,"wires":[["functionNode"]]}] -{% endrenderFlow %} +``` +:: 1. Configure the AWS DyanamoDB node by **editing** the node. 2. Click the **pencil** to create a new config. @@ -89,9 +91,11 @@ This first flow will generate data and send it to DynamoDB via the PutItem opera The GetItem operation fetches data, which you can then display using debug nodes. To retrieve specific customer information based on their email import this flow: -{% renderFlow %} +::render-flow +```json [{"id":"f7e069b78d2ef5c5","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":340,"wires":[["07caf16318aea2f6"]]},{"id":"07caf16318aea2f6","type":"function","z":"37b2bc0d1ebdc616","name":"Get Specific Customer Info","func":"msg = {\n TableName: \"FlowFuse-Email\",\n Key: {\n \"email\": { \"S\": \"florance.shelly@cirpria.xyz\" } //put email you want to search here\n }\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":340,"wires":[["3485e6cddd28c3eb"]]},{"id":"3485e6cddd28c3eb","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"","operation":"GetItem","Statements":"","RequestItems":"","TableName":"","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":760,"y":340,"wires":[["0c0b39c735d71d77"],["ae673a52c1c7a123"]]},{"id":"0c0b39c735d71d77","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 30","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1000,"y":320,"wires":[]},{"id":"ae673a52c1c7a123","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 31","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1000,"y":360,"wires":[]}] -{% endrenderFlow %} +``` +:: ![Dynamodb Get Item flowfuse](./images/node-red-dynamodb-get-item-flowfuse.png){data-zoomable} @@ -101,9 +105,11 @@ The function node specifies the **TableName** and **Partition key** to search ag When you need to find items under specific criteria, set up your query with partition keys and conditions. The Query operation allows you to efficiently retrieve data without scanning the entire contents of a particular partition key. If a key has significant about of data, but only need one particular value. This would then be the ideal path. -{% renderFlow %} +::render-flow +```json [{"id":"99a876a56a78ae25","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":420,"wires":[["d59f213bc3f6712a"]]},{"id":"d59f213bc3f6712a","type":"function","z":"37b2bc0d1ebdc616","name":"Returns only the attriubutes desired","func":"msg.TableName = \"FlowFuse-Email\";\nmsg.KeyConditionExpression = \"email = :email\";\nmsg.ExpressionAttributeValues = {\n \":email\": { \"S\": \"florance.shelly@cirpria.xyz\" }\n};\nmsg.ProjectionExpression = \"#n, #t\";\nmsg.ExpressionAttributeNames = {\n \"#t\": \"temp\",\n \"#n\": \"name\"\n} // Only retrieve these attributes\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":420,"wires":[["e178ffb9a87226a5"]]},{"id":"e178ffb9a87226a5","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"","operation":"Query","Statements":"","RequestItems":"","TableName":"","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":730,"y":420,"wires":[["b2e6d9d9dcf9dcd3"],["b2595472f1dc6570"]]},{"id":"b2e6d9d9dcf9dcd3","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 32","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":400,"wires":[]},{"id":"b2595472f1dc6570","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 33","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":440,"wires":[]}] -{% endrenderFlow %} +``` +:: In this example we are retrieving only **name** and **temp** from the matching partition key match. @@ -113,14 +119,18 @@ In this example we are retrieving only **name** and **temp** from the matching p While scanning is available, it should be used sparingly due to its high demand on resources, especially in large databases. Use it when necessary prioritize using query for regular operations. -{% renderFlow %} +::render-flow +```json [{"id":"671cf37c1ae941d2","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"TableName","v":"FlowFuse-Email","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":540,"wires":[["8cb14f9530eeb524"]]},{"id":"8cb14f9530eeb524","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"","operation":"Scan","Statements":"","RequestItems":"","TableName":"","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":470,"y":540,"wires":[["86e3a35428218d91"],["5c6e85699cc783fc"]]},{"id":"86e3a35428218d91","type":"debug","z":"37b2bc0d1ebdc616","name":"Get Values From Partition Key","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":520,"wires":[]},{"id":"5c6e85699cc783fc","type":"debug","z":"37b2bc0d1ebdc616","name":"Should be used sparingly for Large databases","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":560,"wires":[]}] -{% endrenderFlow %} +``` +:: ![DynamoDB Scan Node-RED](./images/node-red-dynamodb-scan-flowfuse.png){data-zoomable} ## Full Flow -{% renderFlow 400 %} +::render-flow{:height="400"} +```json [{"id":"10b0b970739d616f","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"75ba91a6ba62cece","operation":"PutItem","Statements":"","RequestItems":"","TableName":"FlowFuse-Email","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":820,"y":220,"wires":[["0157a98fa69fe514"],["dc92470ddaa225bf"]]},{"id":"functionNode","type":"function","z":"37b2bc0d1ebdc616","name":"Set Value","func":"msg.Item = {\n \"source\": { \"S\": msg.source },\n \"time\": { \"N\": String(msg.payload) },\n \"temp\": {\"S\": String(msg.datagen.temp) },\n \"email\": {\"S\": msg.datagen.email},\n \"name\": {\"S\": msg.datagen.name},\n \"work\": {\"S\": msg.datagen.work},\n \"address\": {\"S\": msg.datagen.address},\n \"country\": {\"S\": msg.datagen.country},\n};\n\nmsg.TableName = \"FlowFuse-Email\";\n\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":620,"y":220,"wires":[["10b0b970739d616f"]]},{"id":"0157a98fa69fe514","type":"debug","z":"37b2bc0d1ebdc616","name":"Store Customer Info","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1060,"y":200,"wires":[]},{"id":"dc92470ddaa225bf","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 28","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1020,"y":240,"wires":[]},{"id":"45d46649bae57332","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"source","v":"FF_DEVICE_NAME","vt":"env"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":220,"wires":[["1160ce65b26429f3"]]},{"id":"f7e069b78d2ef5c5","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":320,"wires":[["07caf16318aea2f6"]]},{"id":"07caf16318aea2f6","type":"function","z":"37b2bc0d1ebdc616","name":"Get Specific Customer Info","func":"msg = {\n TableName: \"FlowFuse-Email\",\n Key: {\n \"email\": { \"S\": \"florance.shelly@cirpria.xyz\" } //put email you want to search here\n }\n};\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":320,"wires":[["3485e6cddd28c3eb"]]},{"id":"3485e6cddd28c3eb","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"75ba91a6ba62cece","operation":"GetItem","Statements":"","RequestItems":"","TableName":"","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":760,"y":320,"wires":[["0c0b39c735d71d77"],["ae673a52c1c7a123"]]},{"id":"0c0b39c735d71d77","type":"debug","z":"37b2bc0d1ebdc616","name":"Get Customer Info","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1030,"y":300,"wires":[]},{"id":"ae673a52c1c7a123","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 31","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1000,"y":340,"wires":[]},{"id":"99a876a56a78ae25","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":420,"wires":[["d59f213bc3f6712a"]]},{"id":"d59f213bc3f6712a","type":"function","z":"37b2bc0d1ebdc616","name":"Returns only the attriubutes desired","func":"msg.TableName = \"FlowFuse-Email\";\nmsg.KeyConditionExpression = \"email = :email\";\nmsg.ExpressionAttributeValues = {\n \":email\": { \"S\": \"florance.shelly@cirpria.xyz\" }\n};\nmsg.ProjectionExpression = \"#n, #t\";\nmsg.ExpressionAttributeNames = {\n \"#t\": \"temp\",\n \"#n\": \"name\"\n} // Only retrieve these attributes\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":480,"y":420,"wires":[["e178ffb9a87226a5"]]},{"id":"e178ffb9a87226a5","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"75ba91a6ba62cece","operation":"Query","Statements":"","RequestItems":"","TableName":"","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":730,"y":420,"wires":[["b2e6d9d9dcf9dcd3"],["b2595472f1dc6570"]]},{"id":"b2e6d9d9dcf9dcd3","type":"debug","z":"37b2bc0d1ebdc616","name":"Get Specific Customer Info","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":980,"y":400,"wires":[]},{"id":"b2595472f1dc6570","type":"debug","z":"37b2bc0d1ebdc616","name":"debug 33","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":440,"wires":[]},{"id":"1160ce65b26429f3","type":"data-generator","z":"37b2bc0d1ebdc616","name":"","field":"datagen","fieldType":"msg","syntax":"json","template":"{\n \"name\": \"{{firstName}} {{lastName}}\",\n \"work\": \"{{company}}\",\n \"email\": \"{{email}}\",\n \"address\": \"{{int 1 100}} {{street}}\",\n \"country\": \"{{country}}\",\n \"temp\": {{float 30 36}}\n}","x":440,"y":220,"wires":[["functionNode"]]},{"id":"671cf37c1ae941d2","type":"inject","z":"37b2bc0d1ebdc616","name":"","props":[{"p":"payload"},{"p":"TableName","v":"FlowFuse-Email","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":540,"wires":[["8cb14f9530eeb524"]]},{"id":"8cb14f9530eeb524","type":"AWS DynamoDB","z":"37b2bc0d1ebdc616","aws":"75ba91a6ba62cece","operation":"Scan","Statements":"","RequestItems":"","TableName":"","BackupName":"","GlobalTableName":"","ReplicationGroup":"","AttributeDefinitions":"","KeySchema":"","BackupArn":"","Key":"","ExportArn":"","Statement":"","TransactStatements":"","TableArn":"","S3Bucket":"","ResourceArn":"","Item":"","TargetTableName":"","Tags":"","TransactItems":"","TagKeys":"","PointInTimeRecoverySpecification":"","ContributorInsightsAction":"","ReplicaUpdates":"","TimeToLiveSpecification":"","name":"","x":470,"y":540,"wires":[["86e3a35428218d91"],["5c6e85699cc783fc"]]},{"id":"86e3a35428218d91","type":"debug","z":"37b2bc0d1ebdc616","name":"Get Values From Partition Key","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":520,"wires":[]},{"id":"5c6e85699cc783fc","type":"debug","z":"37b2bc0d1ebdc616","name":"Should be used sparingly for Large databases","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":560,"wires":[]},{"id":"75ba91a6ba62cece","type":"amazon config","name":"AWS-gdziuba","region":"us-east-1","proxyRequired":false,"proxy":""}] -{% endrenderFlow %} \ No newline at end of file +``` +:: diff --git a/src/node-red/database/firebase.md b/nuxt/content-guides/node-red/database/firebase.md similarity index 98% rename from src/node-red/database/firebase.md rename to nuxt/content-guides/node-red/database/firebase.md index 6edd848246..1935546210 100644 --- a/src/node-red/database/firebase.md +++ b/nuxt/content-guides/node-red/database/firebase.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: Firebase - parent: Database +title: Using Firebase with Node-RED (2026 Updated) +metaTitle: "Using Firebase with Node-RED (2026 Updated)" +navTitle: Firebase +navOrder: 50 meta: - title: Using Firebase with Node-RED (2026 Updated) description: Learn how to integrate Cloud Firestore with Node-RED to build real-time event-driven applications. This guide covers Firestore setup, reading, writing, and listening to data using Node-RED. keywords: node-red, flowfuse, integration, firebase, cloud firestore, google firebase --- -# {{meta.title}} +# Using Firebase with Node-RED (2026 Updated) Firebase provides two database options: Realtime Database (RTDB) and Cloud Firestore. This guide focuses on Cloud Firestore, Firebase's newer, more flexible document database with better performance, richer queries, and multi-regional support. @@ -170,9 +170,11 @@ This capability is particularly useful for building live dashboards, sending not The flow below demonstrates all the concepts covered in this guide. You can explore and modify it as needed. -{% renderFlow %} +::render-flow +```json [{"id":"57c1f30f8a825e5c","type":"group","z":"b5ce73e91740e4b2","name":"","style":{"label":true,"stroke":"#7fb7df"},"nodes":["5792767043952f56","15d852f4a29abec1","16c12e22e1f3b257","c4d08c57eec14060","f69b62a66076edf1"],"x":114,"y":279,"w":972,"h":122},{"id":"5792767043952f56","type":"inject","z":"b5ce73e91740e4b2","g":"57c1f30f8a825e5c","name":"Send Timestamp","props":[{"p":"payload.timestamp","v":"","vt":"date"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":240,"y":360,"wires":[["c4d08c57eec14060"]]},{"id":"15d852f4a29abec1","type":"debug","z":"b5ce73e91740e4b2","g":"57c1f30f8a825e5c","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":980,"y":360,"wires":[]},{"id":"16c12e22e1f3b257","type":"comment","z":"b5ce73e91740e4b2","g":"57c1f30f8a825e5c","name":"Set Timestamp to \"timestampOverwritten\"","info":"","x":320,"y":320,"wires":[]},{"id":"c4d08c57eec14060","type":"firestore-out","z":"b5ce73e91740e4b2","g":"57c1f30f8a825e5c","name":"Overwrite Timestamp","database":"e8796a1869e179bc","collection":"demo","collectionType":"str","document":"timestampOverwritten","documentType":"str","queryMethod":"set","queryOptions":{"merge":false},"x":540,"y":360,"wires":[]},{"id":"f69b62a66076edf1","type":"firestore-in","z":"b5ce73e91740e4b2","g":"57c1f30f8a825e5c","name":"Timestamp Changes","database":"e8796a1869e179bc","collection":"","collectionType":"str","collectionGroup":"","collectionGroupType":"str","constraints":{},"document":"demo/timestampOverwritten","documentType":"str","filter":"none","inputs":0,"passThrough":false,"x":770,"y":360,"wires":[["15d852f4a29abec1"]]},{"id":"e8796a1869e179bc","type":"firebase-config","name":"My Database","authType":"email","claims":{},"createUser":false,"status":{"firestore":false,"storage":false},"useClaims":false},{"id":"8f193a9c1fc939fb","type":"group","z":"b5ce73e91740e4b2","name":"","style":{"stroke":"#c8e7a7","label":true},"nodes":["7376db537268899b","bd18e498f7c61507","19355c55dc280ad7","0ef7c0721cf81927","29aaf3383098e09e","735b562a594841f3","6a90881898ed3551","ee3a2b0bc367a47e","ca1a112e5c6cbdb2","9acbf29beeba99c3","ce937eb6b8c8ca65","16d258ae4b97ca34","78d3b0d5f0f884f4","cf5f66733f714098","fe80dd5b71c8eebe","a8a4da4c647877d1","4d5563941ff8ff6e","108e033753b35f5a","6e2869b197f278f5","d8baeef2707a77b5","1b5c69ac26a6eed7","c87c1434f562e22c","770a532dd82c2c5d","1addc1cfbb75e991"],"x":114,"y":439,"w":972,"h":582},{"id":"7376db537268899b","type":"inject","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Add Alan","props":[{"p":"payload"},{"p":"user","v":"alanisawesome","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"date_of_birth\":\"June 23, 1912\",\"full_name\":\"Alan Turing\",\"nickname\":\"Alan The Machine\"}","payloadType":"json","x":220,"y":520,"wires":[["a8a4da4c647877d1"]]},{"id":"bd18e498f7c61507","type":"inject","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Add Steve","props":[{"p":"payload"},{"p":"user","v":"steveisapple","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"full_name\":\"Steve Jobs\",\"nickname\":\"Steve The King\",\"hobby\":\"Computer\"}","payloadType":"json","x":220,"y":580,"wires":[["a8a4da4c647877d1"]]},{"id":"19355c55dc280ad7","type":"inject","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Modify Alan Nickname","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"nickname\":\"Alan is Genius\"}","payloadType":"json","x":260,"y":700,"wires":[["108e033753b35f5a"]]},{"id":"0ef7c0721cf81927","type":"inject","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Remove Steve","props":[{"p":"user","v":"steveisapple","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":240,"y":860,"wires":[["d8baeef2707a77b5"]]},{"id":"29aaf3383098e09e","type":"debug","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.changes","targetType":"msg","statusVal":"","statusType":"auto","x":980,"y":540,"wires":[]},{"id":"735b562a594841f3","type":"debug","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.changes","targetType":"msg","statusVal":"","statusType":"auto","x":980,"y":700,"wires":[]},{"id":"6a90881898ed3551","type":"debug","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.changes","targetType":"msg","statusVal":"","statusType":"auto","x":980,"y":860,"wires":[]},{"id":"ee3a2b0bc367a47e","type":"debug","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"debug 6","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":980,"y":980,"wires":[]},{"id":"ca1a112e5c6cbdb2","type":"inject","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Get All Users","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":230,"y":980,"wires":[["770a532dd82c2c5d"]]},{"id":"9acbf29beeba99c3","type":"debug","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"debug 7","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload.docs","targetType":"msg","statusVal":"","statusType":"auto","x":560,"y":980,"wires":[]},{"id":"ce937eb6b8c8ca65","type":"comment","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Add Alan to \"users\"","info":"","x":250,"y":480,"wires":[]},{"id":"16d258ae4b97ca34","type":"comment","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Modify the Alan's Nickname","info":"","x":280,"y":660,"wires":[]},{"id":"78d3b0d5f0f884f4","type":"comment","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Remove Steve from \"users\"","info":"","x":280,"y":820,"wires":[]},{"id":"cf5f66733f714098","type":"comment","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Get All Users from \"users\"","info":"","x":270,"y":940,"wires":[]},{"id":"fe80dd5b71c8eebe","type":"comment","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Print All Users Changes","info":"","x":780,"y":940,"wires":[]},{"id":"a8a4da4c647877d1","type":"firestore-out","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Add User","database":"e8796a1869e179bc","collection":"users","collectionType":"str","document":"user","documentType":"msg","queryMethod":"set","queryOptions":{"merge":false},"x":400,"y":540,"wires":[]},{"id":"4d5563941ff8ff6e","type":"firestore-in","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"User added","database":"e8796a1869e179bc","collection":"users","collectionType":"str","collectionGroup":"","collectionGroupType":"str","constraints":{},"document":"","documentType":"str","filter":"added","inputs":0,"passThrough":false,"x":750,"y":540,"wires":[["29aaf3383098e09e"]]},{"id":"108e033753b35f5a","type":"firestore-out","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Update User Nickname","database":"e8796a1869e179bc","collection":"users","collectionType":"str","document":"alanisawesome","documentType":"str","queryMethod":"update","queryOptions":{"merge":true},"x":530,"y":700,"wires":[]},{"id":"6e2869b197f278f5","type":"firestore-in","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"User Modified","database":"e8796a1869e179bc","collection":"users","collectionType":"str","collectionGroup":"","collectionGroupType":"str","constraints":{},"document":"","documentType":"str","filter":"modified","inputs":0,"passThrough":false,"x":750,"y":700,"wires":[["735b562a594841f3"]]},{"id":"d8baeef2707a77b5","type":"firestore-out","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Remove User","database":"e8796a1869e179bc","collection":"users","collectionType":"str","document":"user","documentType":"msg","queryMethod":"delete","queryOptions":{"merge":false},"x":440,"y":860,"wires":[]},{"id":"1b5c69ac26a6eed7","type":"firestore-in","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"User Removed","database":"e8796a1869e179bc","collection":"users","collectionType":"str","collectionGroup":"","collectionGroupType":"str","constraints":{},"document":"","documentType":"str","filter":"removed","inputs":0,"passThrough":false,"x":760,"y":860,"wires":[["6a90881898ed3551"]]},{"id":"c87c1434f562e22c","type":"firestore-in","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"All Users Changes","database":"e8796a1869e179bc","collection":"users","collectionType":"str","collectionGroup":"","collectionGroupType":"str","constraints":{},"document":"","documentType":"str","filter":"none","inputs":0,"passThrough":false,"x":770,"y":980,"wires":[["ee3a2b0bc367a47e"]]},{"id":"770a532dd82c2c5d","type":"firestore-get","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Get Users","database":"e8796a1869e179bc","collection":"users","collectionType":"str","collectionGroup":"","collectionGroupType":"str","constraints":{},"document":"","documentType":"str","passThrough":false,"x":400,"y":980,"wires":[["9acbf29beeba99c3"]]},{"id":"1addc1cfbb75e991","type":"inject","z":"b5ce73e91740e4b2","g":"8f193a9c1fc939fb","name":"Remove Alan Nickname","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"nickname\":\"DELETE\"}","payloadType":"json","x":260,"y":740,"wires":[["108e033753b35f5a"]]},{"id":"3b1dbdd5845f591a","type":"global-config","env":[],"modules":{"@gogovega/node-red-contrib-cloud-firestore":"0.2.0"}}] -{% endrenderFlow %} +``` +::
@@ -184,6 +186,6 @@ Even better, the [FlowFuse Expert allows you to query your tables using natural Deploy, manage, scale, and secure your Node-RED applications with FlowFuse, and take full control of your industrial workflows and data. -[**Start with FlowFuse today**](https://app.flowfuse.com/) +[**Start with FlowFuse today**](https://app.flowfuse.com/) -
\ No newline at end of file + diff --git a/src/node-red/database/images/config-connection.png b/nuxt/content-guides/node-red/database/images/config-connection.png similarity index 100% rename from src/node-red/database/images/config-connection.png rename to nuxt/content-guides/node-red/database/images/config-connection.png diff --git a/src/node-red/database/images/config-database.png b/nuxt/content-guides/node-red/database/images/config-database.png similarity index 100% rename from src/node-red/database/images/config-database.png rename to nuxt/content-guides/node-red/database/images/config-database.png diff --git a/src/node-red/database/images/data_in_influx.webm b/nuxt/content-guides/node-red/database/images/data_in_influx.webm similarity index 100% rename from src/node-red/database/images/data_in_influx.webm rename to nuxt/content-guides/node-red/database/images/data_in_influx.webm diff --git a/src/node-red/database/images/dynamodb-data-structure-node-red-flowfuse.png b/nuxt/content-guides/node-red/database/images/dynamodb-data-structure-node-red-flowfuse.png similarity index 100% rename from src/node-red/database/images/dynamodb-data-structure-node-red-flowfuse.png rename to nuxt/content-guides/node-red/database/images/dynamodb-data-structure-node-red-flowfuse.png diff --git a/src/node-red/database/images/dynamodb-flowfuse-iam-node-red.png b/nuxt/content-guides/node-red/database/images/dynamodb-flowfuse-iam-node-red.png similarity index 100% rename from src/node-red/database/images/dynamodb-flowfuse-iam-node-red.png rename to nuxt/content-guides/node-red/database/images/dynamodb-flowfuse-iam-node-red.png diff --git a/src/node-red/database/images/flowfuse-dynamodb-aws-setup-node-red.png b/nuxt/content-guides/node-red/database/images/flowfuse-dynamodb-aws-setup-node-red.png similarity index 100% rename from src/node-red/database/images/flowfuse-dynamodb-aws-setup-node-red.png rename to nuxt/content-guides/node-red/database/images/flowfuse-dynamodb-aws-setup-node-red.png diff --git a/src/node-red/database/images/influxdb-node-red-cta-1.png b/nuxt/content-guides/node-red/database/images/influxdb-node-red-cta-1.png similarity index 100% rename from src/node-red/database/images/influxdb-node-red-cta-1.png rename to nuxt/content-guides/node-red/database/images/influxdb-node-red-cta-1.png diff --git a/src/node-red/database/images/mysql-node-config.png b/nuxt/content-guides/node-red/database/images/mysql-node-config.png similarity index 100% rename from src/node-red/database/images/mysql-node-config.png rename to nuxt/content-guides/node-red/database/images/mysql-node-config.png diff --git a/src/node-red/database/images/mysql-with-node-red.png b/nuxt/content-guides/node-red/database/images/mysql-with-node-red.png similarity index 100% rename from src/node-red/database/images/mysql-with-node-red.png rename to nuxt/content-guides/node-red/database/images/mysql-with-node-red.png diff --git a/src/node-red/database/images/node-red-dynamodb-flow-flowfuse.png b/nuxt/content-guides/node-red/database/images/node-red-dynamodb-flow-flowfuse.png similarity index 100% rename from src/node-red/database/images/node-red-dynamodb-flow-flowfuse.png rename to nuxt/content-guides/node-red/database/images/node-red-dynamodb-flow-flowfuse.png diff --git a/src/node-red/database/images/node-red-dynamodb-get-item-flowfuse.png b/nuxt/content-guides/node-red/database/images/node-red-dynamodb-get-item-flowfuse.png similarity index 100% rename from src/node-red/database/images/node-red-dynamodb-get-item-flowfuse.png rename to nuxt/content-guides/node-red/database/images/node-red-dynamodb-get-item-flowfuse.png diff --git a/src/node-red/database/images/node-red-dynamodb-query-flowfuse.png b/nuxt/content-guides/node-red/database/images/node-red-dynamodb-query-flowfuse.png similarity index 100% rename from src/node-red/database/images/node-red-dynamodb-query-flowfuse.png rename to nuxt/content-guides/node-red/database/images/node-red-dynamodb-query-flowfuse.png diff --git a/src/node-red/database/images/node-red-dynamodb-scan-flowfuse.png b/nuxt/content-guides/node-red/database/images/node-red-dynamodb-scan-flowfuse.png similarity index 100% rename from src/node-red/database/images/node-red-dynamodb-scan-flowfuse.png rename to nuxt/content-guides/node-red/database/images/node-red-dynamodb-scan-flowfuse.png diff --git a/src/node-red/database/images/node-red-dynamodb.png b/nuxt/content-guides/node-red/database/images/node-red-dynamodb.png similarity index 100% rename from src/node-red/database/images/node-red-dynamodb.png rename to nuxt/content-guides/node-red/database/images/node-red-dynamodb.png diff --git a/src/node-red/database/images/node-red-influxdb.png b/nuxt/content-guides/node-red/database/images/node-red-influxdb.png similarity index 100% rename from src/node-red/database/images/node-red-influxdb.png rename to nuxt/content-guides/node-red/database/images/node-red-influxdb.png diff --git a/src/node-red/database/images/postgresql_with_node-red.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red.png diff --git a/src/node-red/database/images/postgresql_with_node-red_Inventory_management_system.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_Inventory_management_system.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_Inventory_management_system.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_Inventory_management_system.png diff --git a/src/node-red/database/images/postgresql_with_node-red_delete_data.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_delete_data.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_delete_data.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_delete_data.png diff --git a/src/node-red/database/images/postgresql_with_node-red_drop_tables.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_drop_tables.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_drop_tables.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_drop_tables.png diff --git a/src/node-red/database/images/postgresql_with_node-red_form1.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_form1.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_form1.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_form1.png diff --git a/src/node-red/database/images/postgresql_with_node-red_form2.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_form2.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_form2.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_form2.png diff --git a/src/node-red/database/images/postgresql_with_node-red_form3.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_form3.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_form3.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_form3.png diff --git a/src/node-red/database/images/postgresql_with_node-red_insert_data.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_insert_data.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_insert_data.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_insert_data.png diff --git a/src/node-red/database/images/postgresql_with_node-red_pgconfig1.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_pgconfig1.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_pgconfig1.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_pgconfig1.png diff --git a/src/node-red/database/images/postgresql_with_node-red_pgconfig2.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_pgconfig2.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_pgconfig2.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_pgconfig2.png diff --git a/src/node-red/database/images/postgresql_with_node-red_retrive_data.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_retrive_data.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_retrive_data.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_retrive_data.png diff --git a/src/node-red/database/images/postgresql_with_node-red_update_data.png b/nuxt/content-guides/node-red/database/images/postgresql_with_node-red_update_data.png similarity index 100% rename from src/node-red/database/images/postgresql_with_node-red_update_data.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_node-red_update_data.png diff --git a/src/node-red/database/images/postgresql_with_nodered_create_table.png b/nuxt/content-guides/node-red/database/images/postgresql_with_nodered_create_table.png similarity index 100% rename from src/node-red/database/images/postgresql_with_nodered_create_table.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_nodered_create_table.png diff --git a/src/node-red/database/images/postgresql_with_nodred_environment_variable.png b/nuxt/content-guides/node-red/database/images/postgresql_with_nodred_environment_variable.png similarity index 100% rename from src/node-red/database/images/postgresql_with_nodred_environment_variable.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_nodred_environment_variable.png diff --git a/src/node-red/database/images/postgresql_with_nodred_environment_variable_ff_editor.png b/nuxt/content-guides/node-red/database/images/postgresql_with_nodred_environment_variable_ff_editor.png similarity index 100% rename from src/node-red/database/images/postgresql_with_nodred_environment_variable_ff_editor.png rename to nuxt/content-guides/node-red/database/images/postgresql_with_nodred_environment_variable_ff_editor.png diff --git a/src/node-red/database/images/timescaledb-with-node-red-hypertables.png b/nuxt/content-guides/node-red/database/images/timescaledb-with-node-red-hypertables.png similarity index 100% rename from src/node-red/database/images/timescaledb-with-node-red-hypertables.png rename to nuxt/content-guides/node-red/database/images/timescaledb-with-node-red-hypertables.png diff --git a/src/node-red/database/images/timescaledb-with-node-red.png b/nuxt/content-guides/node-red/database/images/timescaledb-with-node-red.png similarity index 100% rename from src/node-red/database/images/timescaledb-with-node-red.png rename to nuxt/content-guides/node-red/database/images/timescaledb-with-node-red.png diff --git a/src/node-red/database/images/using-Mongo-with-nr.png b/nuxt/content-guides/node-red/database/images/using-Mongo-with-nr.png similarity index 100% rename from src/node-red/database/images/using-Mongo-with-nr.png rename to nuxt/content-guides/node-red/database/images/using-Mongo-with-nr.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-annotomy-of-mongodb-document.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-annotomy-of-mongodb-document.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-annotomy-of-mongodb-document.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-annotomy-of-mongodb-document.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-change-node-to-delete-data.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-change-node-to-delete-data.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-change-node-to-delete-data.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-change-node-to-delete-data.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-change-node-to-insert-data.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-change-node-to-insert-data.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-change-node-to-insert-data.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-change-node-to-insert-data.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-change-node-to-update-data.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-change-node-to-update-data.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-change-node-to-update-data.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-change-node-to-update-data.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-dashboard-view.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-dashboard-view.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-dashboard-view.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-dashboard-view.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-flow.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-flow.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-flow.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-crm-system-node-red-flow.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-delete-data-form.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-delete-data-form.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-delete-data-form.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-delete-data-form.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-flowfuse-instance-setting.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-flowfuse-instance-setting.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-flowfuse-instance-setting.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-flowfuse-instance-setting.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-friend-id-node.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-friend-id-node.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-friend-id-node.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-friend-id-node.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-insert-data-form.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-insert-data-form.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-insert-data-form.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-insert-data-form.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-flow.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-flow.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-flow.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-flow.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-node-configuration.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-node-configuration.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-node-configuration.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-deleteone-node-configuration.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-flow.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-flow.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-flow.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-flow.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-node-configuration.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-node-configuration.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-node-configuration.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-drop-node-configuration.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-find-flow.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-find-flow.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-find-flow.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-find-flow.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-find-node-configuration.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-find-node-configuration.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-find-node-configuration.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-find-node-configuration.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-flow.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-flow.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-flow.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-flow.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-node-configuration.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-node-configuration.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-node-configuration.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-insertone-node-configuration.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-node-connection-configuration.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-node-connection-configuration.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-node-connection-configuration.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-node-connection-configuration.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-update-node-configuration.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-update-node-configuration.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-update-node-configuration.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-update-node-configuration.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-mongodb-updateone-flow.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-updateone-flow.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-mongodb-updateone-flow.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-mongodb-updateone-flow.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-table-widget.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-table-widget.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-table-widget.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-table-widget.png diff --git a/src/node-red/database/images/using-mongodb-with-node-red-update-data-form.png b/nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-update-data-form.png similarity index 100% rename from src/node-red/database/images/using-mongodb-with-node-red-update-data-form.png rename to nuxt/content-guides/node-red/database/images/using-mongodb-with-node-red-update-data-form.png diff --git a/src/node-red/database/images/using-timescaledb-with-node-red-environment-variables.png b/nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-environment-variables.png similarity index 100% rename from src/node-red/database/images/using-timescaledb-with-node-red-environment-variables.png rename to nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-environment-variables.png diff --git a/src/node-red/database/images/using-timescaledb-with-node-red-inject-node-1.png b/nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-inject-node-1.png similarity index 100% rename from src/node-red/database/images/using-timescaledb-with-node-red-inject-node-1.png rename to nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-inject-node-1.png diff --git a/src/node-red/database/images/using-timescaledb-with-node-red-inject-node-2.png b/nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-inject-node-2.png similarity index 100% rename from src/node-red/database/images/using-timescaledb-with-node-red-inject-node-2.png rename to nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-inject-node-2.png diff --git a/src/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-connection-tab.png b/nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-connection-tab.png similarity index 100% rename from src/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-connection-tab.png rename to nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-connection-tab.png diff --git a/src/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-security-tab.png b/nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-security-tab.png similarity index 100% rename from src/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-security-tab.png rename to nuxt/content-guides/node-red/database/images/using-timescaledb-with-node-red-postgresql-config-node-security-tab.png diff --git a/src/node-red/database/influxdb.md b/nuxt/content-guides/node-red/database/influxdb.md similarity index 89% rename from src/node-red/database/influxdb.md rename to nuxt/content-guides/node-red/database/influxdb.md index cb4fe02795..baa2521730 100644 --- a/src/node-red/database/influxdb.md +++ b/nuxt/content-guides/node-red/database/influxdb.md @@ -1,25 +1,22 @@ --- -eleventyNavigation: - key: InfluxDB - parent: Database +title: Using InfluxDB with Node-RED (2026 Updated) +metaTitle: "Using InfluxDB with Node-RED (2026 Updated)" +navTitle: InfluxDB +navOrder: 50 meta: - title: Using InfluxDB with Node-RED (2026 Updated) description: Node-RED has great support for InfluxDB. In this guide, we'll explain how to get your data flowing into one of the most popular time-series databases. keywords: node-red, databases, integration, influxdb, time-series database -image: /node-red/core-nodes/images/node-red-influxdb.png +image: /docs/node-red/core-nodes/images/node-red-influxdb.png --- -# {{ meta.title }} +# Using InfluxDB with Node-RED (2026 Updated) InfluxDB is a time series database that is commonly used for storing and analysing IoT data. Node-RED is a visual programming tool that makes it easy to connect different data sources and create flows that automate tasks. In this documentation, we will show you how to write data to InfluxDB from a Node-RED flow. We will also provide you with a few tips for writing data to InfluxDB effectively. -
- Write to InfluxDB from every Node-RED instance you run, all from one place - +::cta-image{src="/docs/node-red/database/images/influxdb-node-red-cta-1.png" alt="Write to InfluxDB from every Node-RED instance you run, all from one place" cta="sign-up" reference="Node-RED: Using InfluxDB with Node-RED"} +:: ## Step 1: Install the InfluxDB Node-RED package @@ -53,9 +50,11 @@ The fields are the individual pieces of data that you are writing. The tags are You can import the flow below into Node-RED to see an example of a payload which will write all the required values to create a data point in InfluxDB: -{% renderFlow %} +::render-flow +```json [{"id":"cb3b0ecc762dbf93","type":"inject","z":"4542482476b9c71d","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"time\":1688718546,\"temperature\":24},{\"device\":\"dQBgXeWLRE\",\"deviceType\":\"Pi4\",\"deviceName\":\"demo-pi-rob\"}]","payloadType":"json","x":450,"y":420,"wires":[["87166c0dafdeea33"]]},{"id":"87166c0dafdeea33","type":"debug","z":"4542482476b9c71d","name":"debug 31","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":600,"y":420,"wires":[]}] -{% endrenderFlow %} +``` +:: In this example, the time & temperature fields are hard coded, you will need to overwrite the values stored in ```payload[0].time``` & ```payload[0].temperature``` with real data if you were to connect this flow to a real IOT thermometer. @@ -83,9 +82,11 @@ This is an example valid payload: ``` You can import a demo, including the demo payload flow using the code below: -{% renderFlow %} +::render-flow +```json [{"id":"ecbb02face30cbcd","type":"influxdb out","z":"4542482476b9c71d","influxdb":"1c1a5edef41716e3","name":"InfluxDB","measurement":"temperature","precision":"","retentionPolicy":"","database":"database","precisionV18FluxV20":"s","retentionPolicyV18Flux":"","org":"organization","bucket":"my_data","x":360,"y":220,"wires":[]},{"id":"de83c2b49ba249fd","type":"inject","z":"4542482476b9c71d","name":"","props":[{"p":"measurement","v":"temperature","vt":"str"},{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"time\":1688987984,\"temperature\":24},{\"device\":\"dQBgXeWLRE\",\"deviceType\":\"Pi4\",\"deviceName\":\"demo-pi-rob\"}]","payloadType":"json","x":190,"y":160,"wires":[["aad6353f2f00333e","ecbb02face30cbcd"]]},{"id":"aad6353f2f00333e","type":"debug","z":"4542482476b9c71d","name":"debug 31","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":360,"y":160,"wires":[]},{"id":"1c1a5edef41716e3","type":"influxdb","hostname":"127.0.0.1","port":"8086","protocol":"http","database":"my_data","name":"","usetls":false,"tls":"","influxdbVersion":"2.0","url":"https://localhost","rejectUnauthorized":true}] -{% endrenderFlow %} +``` +:: Bear in mind that you will need to edit the server and database details in your influxdb node for this demo to work. @@ -93,7 +94,7 @@ Bear in mind that you will need to edit the server and database details in your You should now be ready to test your flow is writing data to InfluxDB correctly. There is no output in Node-RED to confirm you data was written, so you will need to check directly on InfluxDB. - + Great, our data has arrived correctly and is ready to be used. @@ -103,4 +104,4 @@ Great, our data has arrived correctly and is ready to be used. 1. Set the correct measurement name. The measurement name is the name of the table in InfluxDB where the data will be stored. It is important to choose a meaningful measurement name that will help you to easily identify the data later. 1. Set the correct tags and fields. Tags are used to identify the data points, while fields are used to store the actual data values. It is important to set the correct tags and fields for your data so that you can easily query and analyse it later. 1. Set the correct timestamp. The timestamp is the time at which the data point was recorded. It is important to set the correct timestamp so that you can track the evolution of your data over time. -1. Use the correct precision. The precision is the number of decimal places that are stored for each data value. It is important to use the correct precision so that your data is easy to use. \ No newline at end of file +1. Use the correct precision. The precision is the number of decimal places that are stored for each data value. It is important to use the correct precision so that your data is easy to use. diff --git a/src/node-red/database/mongodb.md b/nuxt/content-guides/node-red/database/mongodb.md similarity index 98% rename from src/node-red/database/mongodb.md rename to nuxt/content-guides/node-red/database/mongodb.md index 158fbf5c89..90321575d5 100644 --- a/src/node-red/database/mongodb.md +++ b/nuxt/content-guides/node-red/database/mongodb.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: MongoDB - parent: Database +title: Using MongoDB With Node-RED (2026 Updated) +metaTitle: "Using MongoDB With Node-RED (2026 Updated)" +navTitle: MongoDB +navOrder: 50 meta: - title: Using MongoDB With Node-RED (2026 Updated) description: Learn how to seamlessly integrate MongoDB, a NoSQL database, into your Node-RED applications with this step-by-step documentation. keywords: node-red, databases, integration, mongodb, no-sql database -image: /node-red/core-nodes/images/using-Mongo-with-nr.png +image: /docs/node-red/core-nodes/images/using-Mongo-with-nr.png --- -# {{ meta.title }} +# Using MongoDB With Node-RED (2026 Updated) This guide provides implementation procedures for integrating MongoDB with Node-RED. It covers configuration requirements, operational patterns, and a complete implementation example using a customer relationship management system. @@ -290,4 +290,4 @@ Connect a debug node to the output of any MongoDB4 node to monitor operation res 3. Use inject nodes for retrieval and drop operations 4. Use the forms for create, update, and delete operations -!["Screenshot displaying dashboard view of CRM System"](./images/using-mongodb-with-node-red-crm-system-node-red-dashboard-view.png "Screenshot displaying dashboard view of CRM System"){data-zoomable} \ No newline at end of file +!["Screenshot displaying dashboard view of CRM System"](./images/using-mongodb-with-node-red-crm-system-node-red-dashboard-view.png "Screenshot displaying dashboard view of CRM System"){data-zoomable} diff --git a/src/node-red/database/mysql.md b/nuxt/content-guides/node-red/database/mysql.md similarity index 95% rename from src/node-red/database/mysql.md rename to nuxt/content-guides/node-red/database/mysql.md index 569ca8cda1..e37a985ba6 100644 --- a/src/node-red/database/mysql.md +++ b/nuxt/content-guides/node-red/database/mysql.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: MySQL - parent: Database +title: Using MySQL with Node-RED (2026 Updated) +metaTitle: "Using MySQL with Node-RED (2026 Updated)" +navTitle: MySQL +navOrder: 50 meta: - title: Using MySQL with Node-RED (2026 Updated) description: Learn how to seamlessly integrate MySQL with Node-RED for efficient data management and application development. keywords: nodered mysql, node red mysql, node-red-node-mysql, mysql nodered, mysql node red -image: /node-red/database/images/timescaledb-with-node-red.png +image: /docs/node-red/database/images/timescaledb-with-node-red.png --- -# {{ meta.title }} +# Using MySQL with Node-RED (2026 Updated) When discussing popular and widely used databases, MySQL inevitably stands out. This is especially evident within the Node-RED community, where the MySQL contrib node has the highest number of downloads among all database contrib nodes. However, popularity often brings its own set of challenges. We've prepared this comprehensive guide to help our Node-RED community members navigate these challenges. It covers all aspects of using MySQL with Node-RED, including an overview of what MySQL is, the differences between PostgreSQL and MySQL, when to choose one over the other, essential MySQL operations, and more. @@ -31,7 +31,7 @@ When discussing popular and widely used databases, MySQL inevitably stands out. ## Choosing Between PostgreSQL and MySQL -Deciding between [PostgreSQL](/node-red/database/postgresql/) and MySQL depends on your project's needs and what each database system does best. PostgreSQL is ideal for big projects requiring complex data handling, reliability, and often updated data. It works well in environments where keeping data consistent is crucial. PostgreSQL's advanced features, like materialized views and support for writing procedures in different languages beyond SQL make it great for managing sophisticated data needs. +Deciding between [PostgreSQL](/docs/node-red/database/postgresql/) and MySQL depends on your project's needs and what each database system does best. PostgreSQL is ideal for big projects requiring complex data handling, reliability, and often updated data. It works well in environments where keeping data consistent is crucial. PostgreSQL's advanced features, like materialized views and support for writing procedures in different languages beyond SQL make it great for managing sophisticated data needs. On the other hand, MySQL is excellent for projects that prioritize fast data reading and are easy to set up and use. It's commonly used for smaller projects, quick prototypes, or applications where quick deployment is critical. MySQL offers flexibility with different storage options, like InnoDB for transactions and MyISAM for handling lots of reads simultaneously, making it versatile depending on your workload. @@ -185,9 +185,11 @@ DROP TABLE IF EXISTS weather_data; Below is the complete flow covering all the operations discussed throughout this blog. -{% renderFlow %} +::render-flow +```json [{"id":"01aeec8769243693","type":"mysql","z":"a9e5683585deb91e","mydb":"bceed1e54606b872","name":"MySQL","x":980,"y":1660,"wires":[["e9ae7f0a3fdbfbc5"]]},{"id":"e9ae7f0a3fdbfbc5","type":"debug","z":"a9e5683585deb91e","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1160,"y":1660,"wires":[]},{"id":"faf229c38d675b2b","type":"inject","z":"a9e5683585deb91e","name":"Create the table","props":[],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":600,"y":1660,"wires":[["b407dbb1901749bf"]]},{"id":"b407dbb1901749bf","type":"template","z":"a9e5683585deb91e","name":"","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"CREATE TABLE IF NOT EXISTS weather_data (\n id INT AUTO_INCREMENT PRIMARY KEY,\n location VARCHAR(100) NOT NULL,\n date DATETIME NOT NULL,\n tem DECIMAL(5, 2) NOT NULL\n);\n","output":"str","x":820,"y":1660,"wires":[["01aeec8769243693"]]},{"id":"cfc26503f0812a88","type":"mysql","z":"a9e5683585deb91e","mydb":"bceed1e54606b872","name":"MySQL","x":980,"y":1760,"wires":[["236d40b1edb920d2"]]},{"id":"236d40b1edb920d2","type":"debug","z":"a9e5683585deb91e","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1160,"y":1760,"wires":[]},{"id":"25790e7a3aef22bf","type":"inject","z":"a9e5683585deb91e","name":"Insert the data into the table","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"$random() * 100","payloadType":"jsonata","x":560,"y":1760,"wires":[["7a798b3539da9547"]]},{"id":"7a798b3539da9547","type":"template","z":"a9e5683585deb91e","name":"","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"INSERT INTO weather_data (location, date, tem)\nVALUES ('New York', CURTIME(), {{ payload }});","output":"str","x":820,"y":1760,"wires":[["cfc26503f0812a88"]]},{"id":"0bbb4d17defe3067","type":"mysql","z":"a9e5683585deb91e","mydb":"bceed1e54606b872","name":"MySQL","x":980,"y":1860,"wires":[["ef2de8cb0673de47"]]},{"id":"ef2de8cb0673de47","type":"debug","z":"a9e5683585deb91e","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1180,"y":1860,"wires":[]},{"id":"027c2c3801bf71a3","type":"inject","z":"a9e5683585deb91e","name":"Retrieve data from table","props":[],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":580,"y":1860,"wires":[["079031046156b6a0"]]},{"id":"079031046156b6a0","type":"template","z":"a9e5683585deb91e","name":"","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"SELECT * FROM weather_data;\n","output":"str","x":820,"y":1860,"wires":[["0bbb4d17defe3067"]]},{"id":"eb38532de73213cf","type":"mysql","z":"a9e5683585deb91e","mydb":"bceed1e54606b872","name":"MySQL","x":980,"y":2080,"wires":[["281838d687cea172"]]},{"id":"281838d687cea172","type":"debug","z":"a9e5683585deb91e","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1180,"y":2080,"wires":[]},{"id":"d4dfbbac635e8a3f","type":"inject","z":"a9e5683585deb91e","name":"Drop the table if it exist","props":[],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":580,"y":2080,"wires":[["4442ad0bbc50a256"]]},{"id":"4442ad0bbc50a256","type":"template","z":"a9e5683585deb91e","name":"","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"DROP TABLE IF EXISTS weather_data;\n","output":"str","x":820,"y":2080,"wires":[["eb38532de73213cf"]]},{"id":"668ed2021704c543","type":"mysql","z":"a9e5683585deb91e","mydb":"bceed1e54606b872","name":"MySQL","x":980,"y":1940,"wires":[["666152e3362a25d5"]]},{"id":"666152e3362a25d5","type":"debug","z":"a9e5683585deb91e","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1180,"y":1940,"wires":[]},{"id":"fbcbad8039d05e8e","type":"inject","z":"a9e5683585deb91e","name":"Delete data from the table","props":[],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","x":570,"y":1940,"wires":[["aa929706ed813892"]]},{"id":"aa929706ed813892","type":"template","z":"a9e5683585deb91e","name":"","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"DELETE FROM weather_data\nWHERE tem > 15\nAND location = 'New York';","output":"str","x":820,"y":1940,"wires":[["668ed2021704c543"]]},{"id":"9c2befca464db660","type":"mysql","z":"a9e5683585deb91e","mydb":"bceed1e54606b872","name":"MySQL","x":980,"y":2020,"wires":[["d28cb3472c5fc357"]]},{"id":"d28cb3472c5fc357","type":"debug","z":"a9e5683585deb91e","name":"debug 6","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1180,"y":2020,"wires":[]},{"id":"49297922b9784f8e","type":"inject","z":"a9e5683585deb91e","name":"Update data from the table","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"10","payloadType":"num","x":570,"y":2020,"wires":[["bbae98e54824b222"]]},{"id":"bbae98e54824b222","type":"template","z":"a9e5683585deb91e","name":"","field":"topic","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"UPDATE weather_data\nSET tem = {{payload}}\nWHERE id = 3","output":"str","x":820,"y":2020,"wires":[["9c2befca464db660"]]},{"id":"bceed1e54606b872","type":"MySQLdatabase","name":"Mysql config","host":"${HOST}","port":"${PORT}","db":"${DATABASE}","tz":"+5:30","charset":"UTF8"}] -{% endrenderFlow %} +``` +:: ### Deploying the Flow @@ -195,4 +197,4 @@ Below is the complete flow covering all the operations discussed throughout this After deploying the flow, you can test each operation, such as creating, deleting, updating, and executing other queries, by clicking the inject button. For debugging purposes, add debug nodes to the flow. -Additionally, if you want to explore the integration of other databases with Node-RED, you can refer to our [database section](/node-red/database/) in the Node-RED learning resources, where we cover databases such as PostgreSQL, MongoDB, InfluxDB, DynamoDB, and more. \ No newline at end of file +Additionally, if you want to explore the integration of other databases with Node-RED, you can refer to our [database section](/docs/node-red/database/) in the Node-RED learning resources, where we cover databases such as PostgreSQL, MongoDB, InfluxDB, DynamoDB, and more. diff --git a/src/node-red/database/postgresql.md b/nuxt/content-guides/node-red/database/postgresql.md similarity index 98% rename from src/node-red/database/postgresql.md rename to nuxt/content-guides/node-red/database/postgresql.md index b618445b2e..4726ec6b6b 100644 --- a/src/node-red/database/postgresql.md +++ b/nuxt/content-guides/node-red/database/postgresql.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: PostgreSQL - parent: Database +title: Using PostgreSQL with Node-RED (2026 Updated) +metaTitle: "Using PostgreSQL with Node-RED (2026 Updated)" +navTitle: PostgreSQL +navOrder: 50 meta: - title: Using PostgreSQL with Node-RED (2026 Updated) description: Learn how to seamlessly integrate PostgreSQL with Node-RED for efficient data management and application development. keywords: node-red, databases, integration, PostgreSQL, SQL database -image: /node-red/core-nodes/images/postgresql_with_node-red.png +image: /docs/node-red/core-nodes/images/postgresql_with_node-red.png --- -# {{ meta.title }} +# Using PostgreSQL with Node-RED (2026 Updated) PostgreSQL is a highly reliable open-source relational database known for its extensive features. It supports diverse data types, robust SQL, and ACID compliance, allowing high-performance systems. Over the years, it has demonstrated reliability, security, and compatibility, which makes it a popular choice for businesses worldwide. diff --git a/src/node-red/database/redis.md b/nuxt/content-guides/node-red/database/redis.md similarity index 98% rename from src/node-red/database/redis.md rename to nuxt/content-guides/node-red/database/redis.md index 3b27c16948..ef9a24b905 100644 --- a/src/node-red/database/redis.md +++ b/nuxt/content-guides/node-red/database/redis.md @@ -1,22 +1,22 @@ --- -eleventyNavigation: - key: Redis - parent: Database +title: Using Redis with Node-RED (2026 Updated) +metaTitle: "Using Redis with Node-RED (2026 Updated)" +navTitle: Redis +navOrder: 50 meta: - title: Using Redis with Node-RED (2026 Updated) description: Learn how to integrate Redis with Node-RED for fast data storage, pub/sub messaging, JSON handling, Lua scripting, and advanced Redis operations in Node-RED flows. keywords: nodered redis, node red redis, node-red-node-redis, redis nodered, redis node red -certified: - - "@flowfuse-certified-nodes/redis" --- -# {{ meta.title }} +# Using Redis with Node-RED (2026 Updated) Redis is a powerful in-memory data structure store that can be used as a database, cache, message broker, and streaming engine. When combined with Node-RED, Redis provides a fast and efficient way to store and retrieve data, manage session states, implement pub/sub messaging patterns, and share data across multiple Node-RED instances. This documentation will walk you through integrating Redis with Node-RED, from basic setup to advanced use cases. -{% include "components/certified-node-callout.liquid" %} +::callout{icon="i-lucide-badge-check"} +**Certified nodes for this technology.** FlowFuse maintains certified nodes for production use covering this technology. [redis](/integrations/?certified=1) +:: ## Getting Started @@ -366,6 +366,8 @@ For more Redis commands, patterns, and advanced capabilities, refer to the [offi Below is the complete example that we covered in this document. -{% renderFlow %} +::render-flow +```json [{"id":"2c33ebeb73062ab3","type":"group","z":"d4f60c79eff5211d","name":"Your First Redis Flow","style":{"label":true},"nodes":["e5f3326ef736cef6","8486e88d8623b42a","d1ebde22d0e224a8","22db25c60af16ab2","fe2be532c53402b3","ebc1e8fc0f1ba2d6","adb95c63d206c633"],"x":514,"y":359,"w":712,"h":162},{"id":"e5f3326ef736cef6","type":"redis-command","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","server":"e370dc92b39a7ba4","command":"SET","name":"","topic":"mykey","params":"[\"Hello from Node-RED\"]","paramsType":"json","payloadType":"json","block":false,"x":810,"y":400,"wires":[["8486e88d8623b42a"]]},{"id":"8486e88d8623b42a","type":"debug","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":970,"y":400,"wires":[]},{"id":"d1ebde22d0e224a8","type":"redis-command","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","server":"e370dc92b39a7ba4","command":"GET","name":"","topic":"","params":"[]","paramsType":"json","payloadType":"json","block":false,"x":1000,"y":480,"wires":[["22db25c60af16ab2"]]},{"id":"22db25c60af16ab2","type":"debug","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1130,"y":480,"wires":[]},{"id":"fe2be532c53402b3","type":"change","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","name":"Set Key for GET","rules":[{"t":"set","p":"topic","pt":"msg","to":"mykey","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":480,"wires":[["d1ebde22d0e224a8"]]},{"id":"ebc1e8fc0f1ba2d6","type":"inject","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":400,"wires":[["e5f3326ef736cef6"]]},{"id":"adb95c63d206c633","type":"inject","z":"d4f60c79eff5211d","g":"2c33ebeb73062ab3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":480,"wires":[["fe2be532c53402b3"]]},{"id":"e370dc92b39a7ba4","type":"redis-config","name":"Local","options":"{\"host\":\"localhost\",\"port\":6379,\"db\":0}","cluster":false,"optionsType":"json"},{"id":"5e2842a4730589c0","type":"group","z":"d4f60c79eff5211d","name":"Working with JSON Data","style":{"label":true},"nodes":["6acc07a393e5ee26","3255e8a9122253b7","a3b0b4a74133778e","4faba929a84fb358","e5c359b934174c8e","abf011c2e81960c4","22a2b54caae5869d","b9598f313e2a9fd2","d0051f637fd1621a","9e59f275496d722d"],"x":514,"y":539,"w":852,"h":162},{"id":"6acc07a393e5ee26","type":"redis-command","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","server":"e370dc92b39a7ba4","command":"SET","name":"","topic":"","params":"[]","paramsType":"json","payloadType":"json","block":false,"x":1140,"y":580,"wires":[["3255e8a9122253b7"]]},{"id":"3255e8a9122253b7","type":"debug","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1270,"y":580,"wires":[]},{"id":"a3b0b4a74133778e","type":"inject","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":580,"wires":[["d0051f637fd1621a"]]},{"id":"4faba929a84fb358","type":"redis-command","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","server":"e370dc92b39a7ba4","command":"GET","name":"","topic":"","params":"[]","paramsType":"json","payloadType":"json","block":false,"x":1000,"y":660,"wires":[["b9598f313e2a9fd2"]]},{"id":"e5c359b934174c8e","type":"debug","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1270,"y":660,"wires":[]},{"id":"abf011c2e81960c4","type":"change","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"Set Key for GET","rules":[{"t":"set","p":"topic","pt":"msg","to":"sensor:data","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"[]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":800,"y":660,"wires":[["4faba929a84fb358"]]},{"id":"22a2b54caae5869d","type":"inject","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":660,"wires":[["abf011c2e81960c4"]]},{"id":"b9598f313e2a9fd2","type":"json","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"","property":"payload","action":"","pretty":false,"x":1130,"y":660,"wires":[["e5c359b934174c8e"]]},{"id":"d0051f637fd1621a","type":"change","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"Set Key","rules":[{"t":"set","p":"topic","pt":"msg","to":"sensor:data","tot":"str"},{"t":"set","p":"payload","pt":"msg","to":"{\t \"temperature\": 22.5,\t \"humidity\": 65,\t \"timestamp\": $now()\t}","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":780,"y":580,"wires":[["9e59f275496d722d"]]},{"id":"9e59f275496d722d","type":"json","z":"d4f60c79eff5211d","g":"5e2842a4730589c0","name":"","property":"payload","action":"","pretty":false,"x":990,"y":580,"wires":[["6acc07a393e5ee26"]]},{"id":"39de5da95585227f","type":"group","z":"d4f60c79eff5211d","name":"Pub/Sub Messaging","style":{"label":true},"nodes":["7756558fd542d657","bf2dc26107eff967","9856ddb8e7bc28e8","bcd9b22fd67ecf7c","22f0526fab3b589b"],"x":514,"y":719,"w":632,"h":162},{"id":"7756558fd542d657","type":"inject","z":"d4f60c79eff5211d","g":"39de5da95585227f","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":760,"wires":[["9856ddb8e7bc28e8"]]},{"id":"bf2dc26107eff967","type":"redis-out","z":"d4f60c79eff5211d","g":"39de5da95585227f","server":"e370dc92b39a7ba4","command":"publish","name":"","topic":"alerts:temperature","obj":true,"x":1030,"y":760,"wires":[]},{"id":"9856ddb8e7bc28e8","type":"change","z":"d4f60c79eff5211d","g":"39de5da95585227f","name":"Set Alert Message","rules":[{"t":"set","p":"payload","pt":"msg","to":"ALERT: Temperature critical in Zone A: 85°C - Equipment shutdown initiated","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":810,"y":760,"wires":[["bf2dc26107eff967"]]},{"id":"bcd9b22fd67ecf7c","type":"redis-in","z":"d4f60c79eff5211d","g":"39de5da95585227f","server":"e370dc92b39a7ba4","command":"subscribe","name":"","topic":"alerts:temperature","obj":true,"timeout":0,"x":630,"y":840,"wires":[["22f0526fab3b589b"]]},{"id":"22f0526fab3b589b","type":"debug","z":"d4f60c79eff5211d","g":"39de5da95585227f","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":990,"y":840,"wires":[]},{"id":"adc4b0df6b0e84e3","type":"group","z":"d4f60c79eff5211d","name":"Using Lua Scripts for Atomic Operations","style":{"label":true},"nodes":["ca8a523710138a11","8804755befa1fbc8","0898e80e28b7d276","0b16690b3c2c10df","9a03ebdea51eefdc","c641d8e54620a73a","7c11a5f1c9716527","a07a025db962be50"],"x":514,"y":899,"w":1112,"h":142},{"id":"ca8a523710138a11","type":"inject","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":1000,"wires":[["8804755befa1fbc8"]]},{"id":"8804755befa1fbc8","type":"function","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","name":"Prepare Lua Script Arguments","func":"// msg.payload format: [keys..., args...]\n// First element(s) are the keys, remaining elements are arguments\nmsg.payload = [\n \"inventory:product:SKU-12345\", // KEYS[1]\n 3 // ARGV[1] - quantity requested\n];\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":850,"y":1000,"wires":[["0898e80e28b7d276"]]},{"id":"0898e80e28b7d276","type":"redis-lua-script","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","server":"e370dc92b39a7ba4","name":"","keyval":"1","func":"local key = KEYS[1]\nlocal requested = tonumber(ARGV[1])\n\nlocal current = tonumber(redis.call('GET', key) or \"0\")\n\nif current >= requested then\n redis.call('DECRBY', key, requested)\n return {1, current - requested}\nelse\n return {0, current}\nend","stored":false,"block":false,"x":1140,"y":1000,"wires":[["0b16690b3c2c10df"]]},{"id":"0b16690b3c2c10df","type":"function","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","name":"Format Lua Script Response","func":"const result = msg.payload;\nconst success = result[0];\nconst remaining = result[1];\n\nif (success === 1) {\n msg.payload = {\n status: \"success\",\n message: `Order processed. Remaining stock: ${remaining}`,\n remaining: remaining\n };\n} else {\n msg.payload = {\n status: \"failed\",\n message: `Insufficient stock. Available: ${remaining}`,\n available: remaining\n };\n}\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1340,"y":1000,"wires":[["9a03ebdea51eefdc"]]},{"id":"9a03ebdea51eefdc","type":"debug","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1530,"y":1000,"wires":[]},{"id":"c641d8e54620a73a","type":"redis-command","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","server":"e370dc92b39a7ba4","command":"SET","name":"","topic":"inventory:product:SKU-12345","params":"[10]","paramsType":"json","payloadType":"json","block":false,"x":880,"y":940,"wires":[["7c11a5f1c9716527"]]},{"id":"7c11a5f1c9716527","type":"debug","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1130,"y":940,"wires":[]},{"id":"a07a025db962be50","type":"inject","z":"d4f60c79eff5211d","g":"adc4b0df6b0e84e3","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":610,"y":940,"wires":[["c641d8e54620a73a"]]},{"id":"01a96fb2044d723f","type":"group","z":"d4f60c79eff5211d","name":"Direct Redis Client Access with redis-instance","style":{"label":true},"nodes":["745f41f352fa2212","a9ec9a8a5a66daa3","e3a5634ef0ebcb78","5192081309e08db9","4f169053bbd7add9","15f0b0462f795828","4a394700c6914961"],"x":514,"y":1059,"w":732,"h":222},{"id":"745f41f352fa2212","type":"redis-instance","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","server":"e370dc92b39a7ba4","name":"","topic":"redis","location":"flow","x":590,"y":1100,"wires":[]},{"id":"a9ec9a8a5a66daa3","type":"inject","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":620,"y":1180,"wires":[["e3a5634ef0ebcb78"]]},{"id":"e3a5634ef0ebcb78","type":"function","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","name":"Batch Store Sensor Readings (Pipeline)","func":"const redis = flow.get('redis');\n\n// Create a pipeline\nconst pipeline = redis.pipeline();\n\n// Add multiple sensor readings in one batch\nconst sensors = [\n { id: 'temp-01', value: 23.5, unit: 'C' },\n { id: 'temp-02', value: 24.1, unit: 'C' },\n { id: 'humidity-01', value: 65, unit: '%' },\n { id: 'pressure-01', value: 1013, unit: 'hPa' }\n];\n\nsensors.forEach(sensor => {\n const key = `sensor:${sensor.id}:latest`;\n const data = JSON.stringify({\n value: sensor.value,\n unit: sensor.unit,\n timestamp: Date.now()\n });\n pipeline.set(key, data, 'EX', 3600); // Expire in 1 hour\n});\n\n// Execute all commands at once\npipeline.exec((err, results) => {\n if (err) {\n node.error(err, msg);\n return;\n }\n\n msg.payload = {\n message: `Stored ${results.length} sensor readings`,\n results: results\n };\n node.send(msg);\n});","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":880,"y":1180,"wires":[["5192081309e08db9"]]},{"id":"5192081309e08db9","type":"debug","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","name":"debug 8","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1140,"y":1180,"wires":[]},{"id":"4f169053bbd7add9","type":"inject","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":620,"y":1240,"wires":[["15f0b0462f795828"]]},{"id":"15f0b0462f795828","type":"function","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","name":"Scan and List Latest Sensor Keys","func":"const redis = flow.get('redis');\n\nasync function scanKeys() {\n const matchPattern = 'sensor:*:latest';\n const allKeys = [];\n let cursor = '0';\n\n try {\n do {\n // Scan with pattern matching\n const result = await redis.scan(\n cursor,\n 'MATCH', matchPattern,\n 'COUNT', 100\n );\n\n cursor = result[0];\n const keys = result[1];\n allKeys.push(...keys);\n\n } while (cursor !== '0');\n\n msg.payload = {\n pattern: matchPattern,\n count: allKeys.length,\n keys: allKeys\n };\n\n node.send(msg);\n\n } catch (err) {\n node.error(err, msg);\n }\n}\n\nscanKeys();","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":860,"y":1240,"wires":[["4a394700c6914961"]]},{"id":"4a394700c6914961","type":"debug","z":"d4f60c79eff5211d","g":"01a96fb2044d723f","name":"debug 9","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1140,"y":1240,"wires":[]},{"id":"d7c53907f5b90ca5","type":"global-config","env":[],"modules":{"node-red-contrib-redis":"1.4.0"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/database/sqlite.md b/nuxt/content-guides/node-red/database/sqlite.md similarity index 98% rename from src/node-red/database/sqlite.md rename to nuxt/content-guides/node-red/database/sqlite.md index f66ddb2e69..f2991dae83 100644 --- a/src/node-red/database/sqlite.md +++ b/nuxt/content-guides/node-red/database/sqlite.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: SQLite - parent: Database +title: Using SQLite with Node-RED (2026 Updated) +metaTitle: "Using SQLite with Node-RED (2026 Updated)" +navTitle: SQLite +navOrder: 50 meta: - title: Using SQLite with Node-RED (2026 Updated) description: Learn how to seamlessly integrate SQLite with Node-RED for efficient data management and application development. keywords: nodered sqlite, node red sqlite, node-red-node-sqlite, sqlite nodered, sqlite node red --- -# {{ meta.title }} +# Using SQLite with Node-RED (2026 Updated) SQLite is a lightweight, self-contained database that does not require a server. It is ideal for quick setups, small applications, and IoT devices, as it stores data directly in files and is easy to manage. @@ -230,6 +230,8 @@ DELETE FROM devices WHERE device_id = $id; Below is the complete flow created throughout this guide. You can import it into Node-RED and experiment with it. -{% renderFlow %} +::render-flow +```json [{"id":"7397d3636bf9eec9","type":"group","z":"9cf82b68bb89e8ce","name":"Create Table ( fixed statement )","style":{"label":true},"nodes":["843c6bddd1fa88dd","c7134f3054775ed9","462a0643f5f85a85"],"x":1034,"y":4459,"w":732,"h":82},{"id":"843c6bddd1fa88dd","type":"inject","z":"9cf82b68bb89e8ce","g":"7397d3636bf9eec9","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1140,"y":4500,"wires":[["c7134f3054775ed9"]]},{"id":"c7134f3054775ed9","type":"sqlite","z":"9cf82b68bb89e8ce","g":"7397d3636bf9eec9","mydb":"c91139d411507971","sqlquery":"fixed","sql":"CREATE TABLE IF NOT EXISTS devices (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n device_id TEXT NOT NULL UNIQUE,\n name TEXT,\n status TEXT,\n location TEXT,\n last_seen DATETIME DEFAULT CURRENT_TIMESTAMP\n);\n","name":"Create Table","x":1490,"y":4500,"wires":[["462a0643f5f85a85"]]},{"id":"462a0643f5f85a85","type":"debug","z":"9cf82b68bb89e8ce","g":"7397d3636bf9eec9","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1660,"y":4500,"wires":[]},{"id":"c91139d411507971","type":"sqlitedb","db":"/tmp/sqlite","mode":"RWC"},{"id":"191f069422d0f9b4","type":"group","z":"9cf82b68bb89e8ce","name":"Read Records","style":{"label":true},"nodes":["f145276495e6a7bd","b6e61ecb0379b93b","02c7723103391825"],"x":1034,"y":4559,"w":732,"h":82},{"id":"f145276495e6a7bd","type":"inject","z":"9cf82b68bb89e8ce","g":"191f069422d0f9b4","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1140,"y":4600,"wires":[["b6e61ecb0379b93b"]]},{"id":"b6e61ecb0379b93b","type":"sqlite","z":"9cf82b68bb89e8ce","g":"191f069422d0f9b4","mydb":"c91139d411507971","sqlquery":"fixed","sql":"SELECT * FROM devices;\n","name":"Read","x":1470,"y":4600,"wires":[["02c7723103391825"]]},{"id":"02c7723103391825","type":"debug","z":"9cf82b68bb89e8ce","g":"191f069422d0f9b4","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1660,"y":4600,"wires":[]},{"id":"c9bf8c21a73cf4f1","type":"group","z":"9cf82b68bb89e8ce","name":"Batch without response","style":{"label":true},"nodes":["d8d2fd8ff4da64fe","7d7556aa27fa6ca8","d15a64daed322158","ea5920b64362e412"],"x":1034,"y":4659,"w":732,"h":82},{"id":"d8d2fd8ff4da64fe","type":"inject","z":"9cf82b68bb89e8ce","g":"c9bf8c21a73cf4f1","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1140,"y":4700,"wires":[["ea5920b64362e412"]]},{"id":"7d7556aa27fa6ca8","type":"sqlite","z":"9cf82b68bb89e8ce","g":"c9bf8c21a73cf4f1","mydb":"c91139d411507971","sqlquery":"batch","sql":"INSERT INTO devices (device_id, name, status, location)\nVALUES ($id, $name, $status, $location);","name":"Insert","x":1470,"y":4700,"wires":[["d15a64daed322158"]]},{"id":"d15a64daed322158","type":"debug","z":"9cf82b68bb89e8ce","g":"c9bf8c21a73cf4f1","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1660,"y":4700,"wires":[]},{"id":"ea5920b64362e412","type":"function","z":"9cf82b68bb89e8ce","g":"c9bf8c21a73cf4f1","name":"Batch Insert","func":"// Sample 10 devices\nconst devices = [\n { id: 'DEV002', name: 'Pressure Sensor', status: 'offline', location: 'Factory Floor 2' },\n { id: 'DEV003', name: 'Humidity Sensor', status: 'online', location: 'Warehouse 1' },\n { id: 'DEV004', name: 'Vibration Sensor', status: 'online', location: 'Factory Floor 3' },\n { id: 'DEV005', name: 'Flow Sensor', status: 'offline', location: 'Plant 1' },\n { id: 'DEV006', name: 'Level Sensor', status: 'online', location: 'Tank 1' },\n { id: 'DEV007', name: 'Pressure Gauge', status: 'online', location: 'Plant 2' },\n { id: 'DEV008', name: 'Temperature Sensor 2', status: 'offline', location: 'Warehouse 2' },\n { id: 'DEV009', name: 'Humidity Sensor 2', status: 'online', location: 'Plant 3' },\n { id: 'DEV010', name: 'Flow Meter', status: 'online', location: 'Factory Floor 4' }\n];\n\n// Generate batch SQL\nconst batchSQL = devices.map(d =>\n `INSERT INTO devices (device_id, name, status, location) VALUES \n ('${d.id}', '${d.name}', '${d.status}', '${d.location}');`\n).join('\\n');\n\nmsg.topic = batchSQL;\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1310,"y":4700,"wires":[["7d7556aa27fa6ca8"]]},{"id":"e199d35406609471","type":"group","z":"9cf82b68bb89e8ce","name":"Update Record ( via prepared statement with array )","style":{"label":true},"nodes":["9688278c29895ab0","ea71fbc3ef5cc8bd","9208e6ef30218e35","1ad5c81d925a0310"],"x":1034,"y":4759,"w":732,"h":82},{"id":"9688278c29895ab0","type":"inject","z":"9cf82b68bb89e8ce","g":"e199d35406609471","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1140,"y":4800,"wires":[["9208e6ef30218e35"]]},{"id":"ea71fbc3ef5cc8bd","type":"sqlite","z":"9cf82b68bb89e8ce","g":"e199d35406609471","mydb":"c91139d411507971","sqlquery":"prepared","sql":"UPDATE devices SET status = $1 WHERE device_id = $2;","name":"Update","x":1480,"y":4800,"wires":[["1ad5c81d925a0310"]]},{"id":"9208e6ef30218e35","type":"change","z":"9cf82b68bb89e8ce","g":"e199d35406609471","name":"Set Params","rules":[{"t":"set","p":"params","pt":"msg","to":"[\"offline\", \"DEV002\"]","tot":"json"}],"action":"","property":"","from":"","to":"","reg":false,"x":1310,"y":4800,"wires":[["ea71fbc3ef5cc8bd"]]},{"id":"1ad5c81d925a0310","type":"debug","z":"9cf82b68bb89e8ce","g":"e199d35406609471","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1660,"y":4800,"wires":[]},{"id":"67085d9fa9b7b5dd","type":"group","z":"9cf82b68bb89e8ce","name":"Delete Record ( via prepared statement with object )","style":{"label":true},"nodes":["c551a497bf776c15","1767cd99e3b03bd2","b3570250013c8703","9ec536a39e055ffe"],"x":1034,"y":4859,"w":732,"h":82},{"id":"c551a497bf776c15","type":"inject","z":"9cf82b68bb89e8ce","g":"67085d9fa9b7b5dd","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":1140,"y":4900,"wires":[["b3570250013c8703"]]},{"id":"1767cd99e3b03bd2","type":"sqlite","z":"9cf82b68bb89e8ce","g":"67085d9fa9b7b5dd","mydb":"c91139d411507971","sqlquery":"prepared","sql":"DELETE FROM devices WHERE device_id = $device_id;","name":"Delete","x":1470,"y":4900,"wires":[["9ec536a39e055ffe"]]},{"id":"b3570250013c8703","type":"change","z":"9cf82b68bb89e8ce","g":"67085d9fa9b7b5dd","name":"Set Params","rules":[{"t":"set","p":"params","pt":"msg","to":"{}","tot":"json"},{"t":"set","p":"params.$device_id","pt":"msg","to":"DEV006","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1310,"y":4900,"wires":[["1767cd99e3b03bd2"]]},{"id":"9ec536a39e055ffe","type":"debug","z":"9cf82b68bb89e8ce","g":"67085d9fa9b7b5dd","name":"debug 5","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1660,"y":4900,"wires":[]},{"id":"4c464964391e1c1c","type":"global-config","env":[],"modules":{"node-red-node-sqlite":"1.1.1"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/database/timescaledb.md b/nuxt/content-guides/node-red/database/timescaledb.md similarity index 97% rename from src/node-red/database/timescaledb.md rename to nuxt/content-guides/node-red/database/timescaledb.md index aff38d57c4..c7623204c6 100644 --- a/src/node-red/database/timescaledb.md +++ b/nuxt/content-guides/node-red/database/timescaledb.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: TimescaleDB - parent: Database +title: Using TimescaleDB with Node-RED (2026 Updated) +metaTitle: "Using TimescaleDB with Node-RED (2026 Updated)" +navTitle: TimescaleDB +navOrder: 50 meta: - title: Using TimescaleDB with Node-RED (2026 Updated) description: Learn how to integrate TimescaleDB with Node-RED for storing and managing time-series data efficiently. keywords: node-red timescaledb, nodered postgresql, nodered time-series database -image: /node-red/database/images/timescaledb-with-node-red.png +image: /docs/node-red/database/images/timescaledb-with-node-red.png --- -# {{ meta.title }} +# Using TimescaleDB with Node-RED (2026 Updated) In the context of IoT and IIoT applications, time series databases are essential for storing data based on timestamps. While InfluxDB has been a popular choice for a long time, another time series database, TimescaleDB, is gaining popularity. This guide will cover how to use TimescaleDB with Node-RED, how TimescaleDB works, and the queries needed when building IoT applications. @@ -87,9 +87,11 @@ SELECT create_hypertable('sensor_data', 'time'); 2. Drag an Inject node onto the canvas, which we will use to trigger the operation. 3. Connect the Inject node's output to the input of the PostgreSQL node. -{% renderFlow %} +::render-flow +```json [{"id":"d766709f13c8410b","type":"inject","z":"7748186d67ad0a58","name":"Create hypertable","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":210,"y":140,"wires":[["a977cb646de2a30b"]]},{"id":"a977cb646de2a30b","type":"postgresql","z":"7748186d67ad0a58","name":"PostgreSQL","query":"CREATE TABLE sensor_data (\n time TIMESTAMPTZ NOT NULL,\n location TEXT,\n temperature DOUBLE PRECISION\n);\n\nSELECT create_hypertable('sensor_data', 'time');\n","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":490,"y":140,"wires":[["c843732f2088f83e"]]},{"id":"c843732f2088f83e","type":"debug","z":"7748186d67ad0a58","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":740,"y":140,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Inserting Data into the Table @@ -119,9 +121,11 @@ VALUES ( 5. Drag a Debug node onto the canvas. 6. Connect the output of the Inject nodes to the input of the PostgreSQL node and the output of PostgreSQL to the input of the Debug node. -{% renderFlow %} +::render-flow +```json [{"id":"c42dfdaa44a02eda","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"INSERT INTO sensor_data (time, location, temperature)\nVALUES (now(), '{{msg.payload.location}}', '{{msg.payload.temperature}}');\n","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":490,"y":540,"wires":[["f88822fc9fdf90dc"]]},{"id":"f88822fc9fdf90dc","type":"debug","z":"7748186d67ad0a58","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":700,"y":540,"wires":[]},{"id":"b3b5fc6ad830145e","type":"inject","z":"7748186d67ad0a58","name":"Sensor placed in the New York","props":[{"p":"payload.location","v":"New York","vt":"str"},{"p":"payload.temperature","v":"$floor(($random() * 21) + 30)","vt":"jsonata"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":580,"wires":[["c42dfdaa44a02eda"]]},{"id":"2d07d8d73ed43f37","type":"inject","z":"7748186d67ad0a58","name":"Sensor placed in the Chicago","props":[{"p":"payload.location","v":"Chicago","vt":"str"},{"p":"payload.temperature","v":"$floor(($random() * 21) + 30)","vt":"jsonata"}],"repeat":"5","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":520,"wires":[["c42dfdaa44a02eda"]]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Updating data to the table @@ -140,9 +144,11 @@ UPDATE sensor_data 3. Drag a Debug node onto the canvas. 4. Connect the output of the Inject node to the input of the PostgreSQL node and the output of the PostgreSQL node to the input of the Debug node. -{% renderFlow %} +::render-flow +```json [{"id":"702b9169ff00396c","type":"inject","z":"7748186d67ad0a58","name":"Updating data based on condition","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":210,"y":1120,"wires":[["81dcb2d851bb9316"]]},{"id":"81dcb2d851bb9316","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"-- Update temperature data in the sensor_data table\nUPDATE sensor_data\n SET temperature = temperature + 0.1\n WHERE time >= '2024-05-29T11:50:25.859Z' -- Starting timestamp for the update\n AND time < '2024-05-29T12:17:43.305Z'; -- Ending timestamp for the update\n","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":530,"y":1120,"wires":[["29e28595a9f5a0df"]]},{"id":"29e28595a9f5a0df","type":"debug","z":"7748186d67ad0a58","name":"debug 14","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":1120,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Deleting data to the table @@ -157,9 +163,11 @@ WHERE temperature < 35 -- Delete rows where the temperature is less than 35 degr 3. Drag a Debug node onto the canvas. 4. Connect the output of the Inject node to the input of the PostgreSQL node and the output of the PostgreSQL node to the input of the Debug node. -{% renderFlow %} +::render-flow +```json [{"id":"702b9169ff00396c","type":"inject","z":"7748186d67ad0a58","name":"Delete data based on condition","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":210,"y":1120,"wires":[["81dcb2d851bb9316"]]},{"id":"81dcb2d851bb9316","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"-- Delete rows from the sensor_data table where the temperature is below 35 degrees Celsius or humidity is below 60%\nDELETE FROM sensor_data\nWHERE temperature < 35 -- Delete rows where the temperature is less than 35 degrees Celsius","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":530,"y":1120,"wires":[["29e28595a9f5a0df"]]},{"id":"29e28595a9f5a0df","type":"debug","z":"7748186d67ad0a58","name":"debug 14","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":760,"y":1120,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Retrieving all data from the table @@ -173,9 +181,11 @@ SELECT * FROM sensor_data; 3. Drag a Debug node onto the canvas. 4. Connect the output of the Inject node to the input of the PostgreSQL node and the output of the PostgreSQL node to the input of the Debug node. -{% renderFlow %} +::render-flow +```json [{"id":"d551e15f7013e970","type":"inject","z":"7748186d67ad0a58","name":"Retrieve all data","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":180,"y":660,"wires":[["858063c8e7d90f50"]]},{"id":"858063c8e7d90f50","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"SELECT * FROM sensor_data;","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":550,"y":660,"wires":[["79156835d5ad4c4d"]]},{"id":"79156835d5ad4c4d","type":"debug","z":"7748186d67ad0a58","name":"debug 9","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":660,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Retrieve Recent Data @@ -195,9 +205,11 @@ LIMIT 100; -- Limit the results to 100 rows 3. Drag a Debug node onto the canvas. 4. Connect the output of the Inject node to the input of the PostgreSQL node and the output of the PostgreSQL node to the input of the Debug node. -{% renderFlow %} +::render-flow +```json [{"id":"9436c79e9d9e3593","type":"inject","z":"7748186d67ad0a58","name":"Retrieve last 100 data ordered by time","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":720,"wires":[["6d26117dd61d0ecc"]]},{"id":"6d26117dd61d0ecc","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"SELECT * FROM sensor_data ORDER BY time DESC LIMIT 100;","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":550,"y":720,"wires":[["092a08307ae6d63c"]]},{"id":"092a08307ae6d63c","type":"debug","z":"7748186d67ad0a58","name":"debug 10","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":720,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Retrieve Data Based on Time Range @@ -216,9 +228,11 @@ WHERE time > NOW() - INTERVAL '400 SECONDS'; 3. Drag a Debug node onto the canvas. 4. Connect the output of the inject node to the input of the PostgreSQL node, and connect the output of the PostgreSQL node to the input of the debug node. -{% renderFlow %} +::render-flow +```json [{"id":"60e111b1f6e09613","type":"inject","z":"7748186d67ad0a58","name":"Retrieve data based on time range","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":240,"y":880,"wires":[["0b1ec338dbb164f9"]]},{"id":"0b1ec338dbb164f9","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"-- Aggregate data into specific time buckets\nSELECT time_bucket('15 minutes', time) AS fifteen_min, -- Create time buckets of 15 minutes\n location, -- Location of the sensor\n MAX(temperature) AS max_temp -- Calculate the maximum temperature within each time bucket\nFROM sensor_data -- Select data from the conditions table\nWHERE time > NOW() - INTERVAL '3 hours' -- Filter data to include only the last 3 hours\nGROUP BY fifteen_min, location -- Group data by time buckets and location\nORDER BY fifteen_min DESC, max_temp DESC; -- Order the results by time bucket in descending order, and then by maximum temperature in descending order\n","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":550,"y":880,"wires":[["f6f6c4fa651ee351"]]},{"id":"f6f6c4fa651ee351","type":"debug","z":"7748186d67ad0a58","name":"debug 13","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":880,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Aggregating data into specific time bucket @@ -241,9 +255,11 @@ ORDER BY fifteen_min DESC, max_temp DESC; -- Order the results by time bucket in 3. Drag the Debug node onto the canvas. 4. Connect the output of the inject node to the input of the PostgreSQL node, and connect the output of the PostgreSQL node to the input of the debug node. -{% renderFlow %} +::render-flow +```json [{"id":"60e111b1f6e09613","type":"inject","z":"7748186d67ad0a58","name":"Aggregating data into specific time bucket","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":260,"y":880,"wires":[["0b1ec338dbb164f9"]]},{"id":"0b1ec338dbb164f9","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"-- Aggregate data into specific time buckets\nSELECT time_bucket('15 minutes', time) AS fifteen_min, -- Create time buckets of 15 minutes\n location, -- Location of the sensor\n MAX(temperature) AS max_temp -- Calculate the maximum temperature within each time bucket\nFROM sensor_data -- Select data from the conditions table\nWHERE time > NOW() - INTERVAL '3 hours' -- Filter data to include only the last 3 hours\nGROUP BY fifteen_min, location -- Group data by time buckets and location\nORDER BY fifteen_min DESC, max_temp DESC; -- Order the results by time bucket in descending order, and then by maximum temperature in descending order\n","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":550,"y":880,"wires":[["f6f6c4fa651ee351"]]},{"id":"f6f6c4fa651ee351","type":"debug","z":"7748186d67ad0a58","name":"debug 13","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":780,"y":880,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ### Dropping the table @@ -257,12 +273,14 @@ DROP TABLE IF EXISTS sensor_data; 3. Drag the Debug node onto the canvas. 4. Connect the output of the inject node to the input of the PostgreSQL node, and connect the output of the PostgreSQL node to the input of the debug node. -{% renderFlow %} +::render-flow +```json [{"id":"5275332fecd1c715","type":"inject","z":"7748186d67ad0a58","name":"Drop table","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":160,"y":960,"wires":[["382c26de90712487"]]},{"id":"382c26de90712487","type":"postgresql","z":"7748186d67ad0a58","name":"","query":"DROP TABLE IF EXISTS sensor_data;","postgreSQLConfig":"ea1f8e3d9db95245","split":false,"rowsPerMsg":1,"outputs":1,"x":450,"y":960,"wires":[["d7eb0c9020c968c5"]]},{"id":"d7eb0c9020c968c5","type":"debug","z":"7748186d67ad0a58","name":"debug 12","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":720,"y":960,"wires":[]},{"id":"ea1f8e3d9db95245","type":"postgreSQLConfig","name":"TimescaleDB Configurations","host":"${HOST}","hostFieldType":"str","port":"${PORT}","portFieldType":"num","database":"${DATABASE}","databaseFieldType":"str","ssl":"false","sslFieldType":"bool","applicationName":"","applicationNameType":"str","max":"10","maxFieldType":"num","idle":"1000","idleFieldType":"num","connectionTimeout":"10000","connectionTimeoutFieldType":"num","user":"${USERNAME}","userFieldType":"str","password":"${PASSWORD}","passwordFieldType":"str"}] -{% endrenderFlow %} +``` +:: ## Deploying the Flow 1. To test the imported flows, you need to deploy them. To do that, click on the deploy button located in the top right corner. -After deploying the flow, you can test each operation such as creating, deleting, updating, and other queries by clicking on the inject button. Upon successful operation, you will be able to see the results in the debug panel of the sidebar. If you want to learn any additional information about PostgreSQL, you can refer to the [Using PostgreSQL with Node-RED](/node-red/database/postgresql/) where you will also find the section which shows the messages received after a successful operation by the PostgresWQL node. \ No newline at end of file +After deploying the flow, you can test each operation such as creating, deleting, updating, and other queries by clicking on the inject button. Upon successful operation, you will be able to see the results in the debug panel of the sidebar. If you want to learn any additional information about PostgreSQL, you can refer to the [Using PostgreSQL with Node-RED](/docs/node-red/database/postgresql/) where you will also find the section which shows the messages received after a successful operation by the PostgresWQL node. diff --git a/nuxt/content-guides/node-red/getting-started/README.md b/nuxt/content-guides/node-red/getting-started/README.md new file mode 100644 index 0000000000..7e94a94d6f --- /dev/null +++ b/nuxt/content-guides/node-red/getting-started/README.md @@ -0,0 +1,23 @@ +--- +title: Getting Started with Node-RED +metaTitle: "Getting Started with Node-RED" +navTitle: Getting started +navOrder: 2 +meta: + description: Learn the basics of Node-RED, a powerful tool for IoT integration and workflow automation. + keywords: node-red, node-red basics, node-red foundation, node-red editor +--- + +# Getting Started with Node-RED + +This section provides an overview of Node-RED, a robust visual tool designed for integrating IoT devices and automating workflows without needing extensive programming knowledge. + +- [Getting Started with the Node-RED Editor](/docs/node-red/getting-started/editor/): Learn about the powerful features of Node-RED Editor. +- [Node-RED Port (localhost:1880)](/docs/node-red/getting-started/node-red-port/): Learn how to configure Node-RED ports, change default settings, secure your installation, and set up remote access with FlowFuse. +- [How to Update Node-RED](/docs/node-red/getting-started/update-node-red/): Learn how to update Node-RED across different installation methods including npm, Raspberry Pi, Docker, and FlowFuse +- [Node-RED Library – A Curated and Actively Maintained List of Nodes](/docs/node-red/getting-started/library/): Browse the Node-RED Library for community-built nodes and integrations. FlowFuse's curated catalog offers tested, documented, enterprise-ready solutions with professional support for critical deployments. +- [Installing Node-RED on Android](/docs/node-red/getting-started/node-red-android/): Learn how to install and run Node-RED on Android devices using Termux +- [Understanding Node-RED Messages](/docs/node-red/getting-started/node-red-messages/): A comprehensive guide to working with Node-RED messages, ensuring error-free flows and optimized data handling. +- [Strings in Node-RED: Convert String to Number, Split, Concatenate, Trim, and More](/docs/node-red/getting-started/string/): Learn essential string operations in Node-RED including converting between strings and numbers, splitting and concatenating text, parsing JSON, extracting substrings, trimming whitespace, and more. Step-by-step guide with practical examples. +- [Node-RED Programming](/docs/node-red/getting-started/programming/): Master Node-RED programming fundamentals including flows, nodes, messages, conditional logic, and data manipulation. Learn essential concepts for building sophisticated visual programming solutions. +- [Working with Dates and Times in Node-RED](/docs/node-red/getting-started/date-and-time/): Learn how to handle dates and times in Node-RED without coding. Master timestamps, formatting, timezones, calculations, and time-based automation with visual nodes. diff --git a/src/node-red/getting-started/date-and-time.md b/nuxt/content-guides/node-red/getting-started/date-and-time.md similarity index 98% rename from src/node-red/getting-started/date-and-time.md rename to nuxt/content-guides/node-red/getting-started/date-and-time.md index 26f2a6ed0f..31958da125 100644 --- a/src/node-red/getting-started/date-and-time.md +++ b/nuxt/content-guides/node-red/getting-started/date-and-time.md @@ -1,15 +1,14 @@ --- -eleventyNavigation: - key: Date & Time - order: 7 - parent: Getting Started +title: Working with Dates and Times in Node-RED +metaTitle: "Working with Dates and Times in Node-RED" +navTitle: Date & Time +navOrder: 7 meta: - title: Working with Dates and Times in Node-RED - description: Learn how to handle dates and times in Node-RED without coding. Master timestamps, formatting, timezones, calculations, and time-based automation with visual nodes. + description: Learn how to handle dates and times in Node-RED without coding. Master timestamps, formatting, timezones, calculations, and time-based automation with visual nodes. keywords: Node-RED, dates, times, timestamps, JSONata, Moment.js, Date/Time Formatter, timezones, date formatting, time calculations, inject node, change node, human-readable dates, relative time, time-based automation, date math, date difference, ISO 8601, flow automation, Node-RED tutorial, date manipulation, scheduling, event logging, time conversion, time adjustment, visual programming --- -# {{meta.title}} +# Working with Dates and Times in Node-RED Working with dates and times comes up constantly in Node-RED. Whether you're logging events, scheduling tasks, checking business hours, displaying the current time, or pulling historical data, it all relies on handling timestamps correctly. The best part is that you can manage all of this using visual nodes, without writing any code. @@ -48,9 +47,11 @@ In the inject/change node, select **JSONata expression** from the payload type d This JSONata approach works identically in both nodes, use whichever fits your flow better. -{% renderFlow %} +::render-flow +```json [{"id":"9341cec1a1f9e50e","type":"group","z":"d7101f3a4d45deed","name":"Getting the Current Time in Node-RED","style":{"label":true},"nodes":["89cb30afb7fec451","51937bef067f5257","547ec4b9d6a389c9"],"x":108,"y":73,"w":804,"h":694},{"id":"89cb30afb7fec451","type":"group","z":"d7101f3a4d45deed","g":"9341cec1a1f9e50e","name":"Using Change node ( JSONata )","style":{"label":true},"nodes":["b5bdfd1b2a3b9c27","444d17c32b8bd1c1","276e0fcf607c944d","07fbc83f895b7aa4","ac8e5e28a896f52c","8d4143fb8361c469","aff528c64b9568ad"],"x":134,"y":539,"w":752,"h":202},{"id":"b5bdfd1b2a3b9c27","type":"inject","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"Inject","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":640,"wires":[["444d17c32b8bd1c1","276e0fcf607c944d","07fbc83f895b7aa4"]]},{"id":"444d17c32b8bd1c1","type":"change","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"milliseconds since epoch","rules":[{"t":"set","p":"payload","pt":"msg","to":"$millis()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":580,"wires":[["ac8e5e28a896f52c"]]},{"id":"276e0fcf607c944d","type":"change","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"YYYY-MM-DDTHH:mm:ss.sssZ","rules":[{"t":"set","p":"payload","pt":"msg","to":"$now()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":640,"wires":[["8d4143fb8361c469"]]},{"id":"07fbc83f895b7aa4","type":"change","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"JavaScript Date object","rules":[{"t":"set","p":"payload","pt":"msg","to":"$moment()","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":700,"wires":[["aff528c64b9568ad"]]},{"id":"ac8e5e28a896f52c","type":"debug","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":580,"wires":[]},{"id":"8d4143fb8361c469","type":"debug","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":640,"wires":[]},{"id":"aff528c64b9568ad","type":"debug","z":"d7101f3a4d45deed","g":"89cb30afb7fec451","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":700,"wires":[]},{"id":"51937bef067f5257","type":"group","z":"d7101f3a4d45deed","g":"9341cec1a1f9e50e","name":"Using Change node ( Timestamp option )","style":{"label":true},"nodes":["375d143d48695d37","72fcba815fa5cfdd","4e50e0a2e436b658","d41aa61b456bd3a1","f5bec9511bc8f77f","9d3fb7b89adafa8d","7e60fb224e4ff747"],"x":134,"y":319,"w":752,"h":202},{"id":"375d143d48695d37","type":"inject","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"Inject","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":230,"y":420,"wires":[["72fcba815fa5cfdd","4e50e0a2e436b658","d41aa61b456bd3a1"]]},{"id":"72fcba815fa5cfdd","type":"change","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"milliseconds since epoch","rules":[{"t":"set","p":"payload","pt":"msg","to":"","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":360,"wires":[["f5bec9511bc8f77f"]]},{"id":"4e50e0a2e436b658","type":"change","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"YYYY-MM-DDTHH:mm:ss.sssZ","rules":[{"t":"set","p":"payload","pt":"msg","to":"iso","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":420,"wires":[["9d3fb7b89adafa8d"]]},{"id":"d41aa61b456bd3a1","type":"change","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"JavaScript Date object","rules":[{"t":"set","p":"payload","pt":"msg","to":"object","tot":"date"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":480,"wires":[["7e60fb224e4ff747"]]},{"id":"f5bec9511bc8f77f","type":"debug","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":360,"wires":[]},{"id":"9d3fb7b89adafa8d","type":"debug","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":420,"wires":[]},{"id":"7e60fb224e4ff747","type":"debug","z":"d7101f3a4d45deed","g":"51937bef067f5257","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":480,"wires":[]},{"id":"547ec4b9d6a389c9","type":"group","z":"d7101f3a4d45deed","g":"9341cec1a1f9e50e","name":"Using Inject Nodes ( Timestamp Option )","style":{"label":true},"nodes":["499ab581537f5598","b9bd4fdc2f424cbf","b969db92319355f3","ccc969cd1ed85eda","ab178e4e3109f9b0","cdb0cfd879846420"],"x":134,"y":99,"w":752,"h":202},{"id":"499ab581537f5598","type":"inject","z":"d7101f3a4d45deed","g":"547ec4b9d6a389c9","name":"milliseconds since epoch","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":310,"y":140,"wires":[["b9bd4fdc2f424cbf"]]},{"id":"b9bd4fdc2f424cbf","type":"debug","z":"d7101f3a4d45deed","g":"547ec4b9d6a389c9","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":140,"wires":[]},{"id":"b969db92319355f3","type":"inject","z":"d7101f3a4d45deed","g":"547ec4b9d6a389c9","name":"YYYY-MM-DDTHH:mm:ss.sssZ","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"iso","payloadType":"date","x":340,"y":200,"wires":[["ccc969cd1ed85eda"]]},{"id":"ccc969cd1ed85eda","type":"debug","z":"d7101f3a4d45deed","g":"547ec4b9d6a389c9","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":200,"wires":[]},{"id":"ab178e4e3109f9b0","type":"inject","z":"d7101f3a4d45deed","g":"547ec4b9d6a389c9","name":"JavaScript Date object","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"object","payloadType":"date","x":300,"y":260,"wires":[["cdb0cfd879846420"]]},{"id":"cdb0cfd879846420","type":"debug","z":"d7101f3a4d45deed","g":"547ec4b9d6a389c9","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":260,"wires":[]}] -{% endrenderFlow %} +``` +:: > For more advanced date/time operations and formatting, see the [JSONata documentation](https://docs.jsonata.org/date-time-functions). @@ -172,9 +173,11 @@ JSONata uses square brackets, but the codes are different from the **Date/Time F - `$fromMillis(payload, '[M]/[D]/[Y]')` → 12/11/2024 - `$fromMillis(payload, '[h]:[m01] [P]')` → 3:45 PM -{% renderFlow %} +::render-flow +```json [{"id":"1e8a1110d36d3f33","type":"group","z":"d7101f3a4d45deed","name":"Formatting Dates for Display","style":{"label":true},"nodes":["f019c2f5ed9d8104","1699577150167eb5"],"x":108,"y":793,"w":804,"h":414},{"id":"f019c2f5ed9d8104","type":"group","z":"d7101f3a4d45deed","g":"1e8a1110d36d3f33","name":"The Moment Nodes","style":{"label":true},"nodes":["bd06565765141868","898b23fa51f95edf","f734d9e8979b20b2","f3a3a5a9974816d3","2de95300ee070276","d5f3d8aeaaeffa7d","b713b196b48c62d3"],"x":134,"y":819,"w":752,"h":202},{"id":"bd06565765141868","type":"moment","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"MMMM D, YYYY","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":0,"adjType":"days","adjDir":"add","format":"MMMM D, YYYY","locale":"en-US","output":"payload","outputType":"msg","outTz":"Africa/Abidjan","x":460,"y":860,"wires":[["898b23fa51f95edf"]]},{"id":"898b23fa51f95edf","type":"debug","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":860,"wires":[]},{"id":"f734d9e8979b20b2","type":"inject","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":920,"wires":[["f3a3a5a9974816d3","d5f3d8aeaaeffa7d","bd06565765141868"]]},{"id":"f3a3a5a9974816d3","type":"moment","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"MMMM D, YYYY [at] h:mm A","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":0,"adjType":"days","adjDir":"add","format":"MMMM D, YYYY [at] h:mm A","locale":"en-US","output":"payload","outputType":"msg","outTz":"Africa/Abidjan","x":510,"y":920,"wires":[["2de95300ee070276"]]},{"id":"2de95300ee070276","type":"debug","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":920,"wires":[]},{"id":"d5f3d8aeaaeffa7d","type":"moment","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"fromNow","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":0,"adjType":"days","adjDir":"add","format":"fromNow","locale":"en-US","output":"payload","outputType":"msg","outTz":"Africa/Abidjan","x":430,"y":980,"wires":[["b713b196b48c62d3"]]},{"id":"b713b196b48c62d3","type":"debug","z":"d7101f3a4d45deed","g":"f019c2f5ed9d8104","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":980,"wires":[]},{"id":"1699577150167eb5","type":"group","z":"d7101f3a4d45deed","g":"1e8a1110d36d3f33","name":"JSONata Formatting","style":{"label":true},"nodes":["678f13a9fad9c397","cf810cbd301fddb0","9443cb33ea42e25b","255b135032862816","2d3b9aee8b0ebe5b"],"x":134,"y":1039,"w":752,"h":142},{"id":"678f13a9fad9c397","type":"inject","z":"d7101f3a4d45deed","g":"1699577150167eb5","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":1120,"wires":[["cf810cbd301fddb0","2d3b9aee8b0ebe5b"]]},{"id":"cf810cbd301fddb0","type":"change","z":"d7101f3a4d45deed","g":"1699577150167eb5","name":"$fromMillis(payload, '[M]/[D]/[Y]')","rules":[{"t":"set","p":"payload","pt":"msg","to":"$fromMillis(payload, '[M]/[D]/[Y]')","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":1080,"wires":[["9443cb33ea42e25b"]]},{"id":"9443cb33ea42e25b","type":"debug","z":"d7101f3a4d45deed","g":"1699577150167eb5","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1080,"wires":[]},{"id":"255b135032862816","type":"debug","z":"d7101f3a4d45deed","g":"1699577150167eb5","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1140,"wires":[]},{"id":"2d3b9aee8b0ebe5b","type":"change","z":"d7101f3a4d45deed","g":"1699577150167eb5","name":"$fromMillis(payload, '[h]:[m01] [P]')","rules":[{"t":"set","p":"payload","pt":"msg","to":"$fromMillis(payload, '[h]:[m01] [P]')","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":1140,"wires":[["255b135032862816"]]},{"id":"5e97a86213b906bf","type":"global-config","env":[],"modules":{"node-red-contrib-moment":"5.0.0"}}] -{% endrenderFlow %} +``` +:: ## Handling Time Zones @@ -226,9 +229,11 @@ $fromMillis(payload, '[M]/[D]/[Y] [h]:[m01] [P]', '-0500') The offset is a string like `-0500` (5 hours behind UTC). This works, but you have to know the offset and manage daylight saving time yourself. The **Date/Time Formatter** node handles all of that automatically. -{% renderFlow %} +::render-flow +```json [{"id":"894fb9d3ddfa14d7","type":"group","z":"d7101f3a4d45deed","name":"Handling Time Zones","style":{"label":true},"nodes":["4d735b1f71f79189","0c31bb975d166128"],"x":108,"y":1233,"w":804,"h":474},{"id":"4d735b1f71f79189","type":"group","z":"d7101f3a4d45deed","g":"894fb9d3ddfa14d7","name":"Converting to a Different Timezone Using Moment nodes","style":{"label":true},"nodes":["995cf0548ac66af1","963db1d838c1a34d","faefd25e268f68e9","9259ebfd137a16b0","32f2b829e7a6de84","6a4a272401d45e61","48bf3927e175904c"],"x":134,"y":1259,"w":752,"h":202},{"id":"995cf0548ac66af1","type":"inject","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":1360,"wires":[["963db1d838c1a34d","6a4a272401d45e61","48bf3927e175904c"]]},{"id":"963db1d838c1a34d","type":"moment","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"America/New_York","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":0,"adjType":"days","adjDir":"add","format":"MMMM D, YYYY h:mm A z","locale":"en-US","output":"payload","outputType":"msg","outTz":"America/New_York","x":470,"y":1300,"wires":[["faefd25e268f68e9"]]},{"id":"faefd25e268f68e9","type":"debug","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1300,"wires":[]},{"id":"9259ebfd137a16b0","type":"debug","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1360,"wires":[]},{"id":"32f2b829e7a6de84","type":"debug","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1420,"wires":[]},{"id":"6a4a272401d45e61","type":"moment","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"Europe/London","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":0,"adjType":"days","adjDir":"add","format":"MMMM D, YYYY h:mm A z","locale":"en-US","output":"payload","outputType":"msg","outTz":"Europe/London","x":460,"y":1360,"wires":[["9259ebfd137a16b0"]]},{"id":"48bf3927e175904c","type":"moment","z":"d7101f3a4d45deed","g":"4d735b1f71f79189","name":"Asia/Tokyo","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":0,"adjType":"days","adjDir":"add","format":"MMMM D, YYYY h:mm A z","locale":"en-US","output":"payload","outputType":"msg","outTz":"Asia/Tokyo","x":440,"y":1420,"wires":[["32f2b829e7a6de84"]]},{"id":"0c31bb975d166128","type":"group","z":"d7101f3a4d45deed","g":"894fb9d3ddfa14d7","name":"Converting to a Different Timezone Using JSONata","style":{"label":true},"nodes":["ee7822f24a2792cb","0a006c9e87656cfc","31122b7227db8ce7","c62c7e7b3b450e85","6dfde983c76894c3","74e37a7cc2080238","f0c786e6db30ffad"],"x":134,"y":1479,"w":752,"h":202},{"id":"ee7822f24a2792cb","type":"inject","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":1580,"wires":[["6dfde983c76894c3","74e37a7cc2080238","f0c786e6db30ffad"]]},{"id":"0a006c9e87656cfc","type":"debug","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1520,"wires":[]},{"id":"31122b7227db8ce7","type":"debug","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1580,"wires":[]},{"id":"c62c7e7b3b450e85","type":"debug","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":780,"y":1640,"wires":[]},{"id":"6dfde983c76894c3","type":"change","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"America/New_York ( Winter )","rules":[{"t":"set","p":"payload","pt":"msg","to":"$fromMillis(payload, '[M]/[D]/[Y] [h]:[m01] [P]', '-0500')","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":1520,"wires":[["0a006c9e87656cfc"]]},{"id":"74e37a7cc2080238","type":"change","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"Europe/London ( Winter )","rules":[{"t":"set","p":"payload","pt":"msg","to":"$fromMillis(payload, '[M]/[D]/[Y] [h]:[m01] [P]', '+0000')","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":1580,"wires":[["31122b7227db8ce7"]]},{"id":"f0c786e6db30ffad","type":"change","z":"d7101f3a4d45deed","g":"0c31bb975d166128","name":"Asia/Tokyo ( Winter )","rules":[{"t":"set","p":"payload","pt":"msg","to":"$fromMillis(payload, '[M]/[D]/[Y] [h]:[m01] [P]', '+0900')","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":480,"y":1640,"wires":[["c62c7e7b3b450e85"]]},{"id":"dcb2806bdd099173","type":"global-config","env":[],"modules":{"node-red-contrib-moment":"5.0.0"}}] -{% endrenderFlow %} +``` +:: ## Doing Math with Dates @@ -312,6 +317,8 @@ This example calculates the difference between a timestamp seven days ago and th Hit the **Inject** button. The **Debug** tab will show the number of days difference (**7**). -{% renderFlow %} +::render-flow +```json [{"id":"48b48dea5aef0701","type":"group","z":"d7101f3a4d45deed","name":"Doing Math with Dates","style":{"label":true},"nodes":["a2137766a67faf67","bb370162520e95d7"],"x":108,"y":1733,"w":964,"h":254},{"id":"a2137766a67faf67","type":"group","z":"d7101f3a4d45deed","g":"48b48dea5aef0701","name":"Using Moment nodes","style":{"label":true},"nodes":["aa71711160b1b545","f76e05557f807562","bfd33b878fefe852"],"x":134,"y":1759,"w":912,"h":82},{"id":"aa71711160b1b545","type":"inject","z":"d7101f3a4d45deed","g":"a2137766a67faf67","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":1800,"wires":[["f76e05557f807562"]]},{"id":"f76e05557f807562","type":"moment","z":"d7101f3a4d45deed","g":"a2137766a67faf67","name":"+ 1 days","topic":"","input":"payload","inputType":"msg","inTz":"Africa/Abidjan","adjAmount":"1","adjType":"days","adjDir":"add","format":"YYYY-MM-DD","locale":"en-US","output":"payload","outputType":"msg","outTz":"Africa/Abidjan","x":430,"y":1800,"wires":[["bfd33b878fefe852"]]},{"id":"bfd33b878fefe852","type":"debug","z":"d7101f3a4d45deed","g":"a2137766a67faf67","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1800,"wires":[]},{"id":"bb370162520e95d7","type":"group","z":"d7101f3a4d45deed","g":"48b48dea5aef0701","name":"Using JSONata","style":{"label":true},"nodes":["551b26f1bfda2d88","4881ffafeb5da64c","82c0370128a1f80d","29f632a5c68ecc55"],"x":134,"y":1879,"w":912,"h":82},{"id":"551b26f1bfda2d88","type":"inject","z":"d7101f3a4d45deed","g":"bb370162520e95d7","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":250,"y":1920,"wires":[["82c0370128a1f80d"]]},{"id":"4881ffafeb5da64c","type":"debug","z":"d7101f3a4d45deed","g":"bb370162520e95d7","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"days_difference","targetType":"msg","statusVal":"","statusType":"auto","x":940,"y":1920,"wires":[]},{"id":"82c0370128a1f80d","type":"change","z":"d7101f3a4d45deed","g":"bb370162520e95d7","name":"Set Start and End Time","rules":[{"t":"move","p":"payload","pt":"msg","to":"end_time","tot":"msg"},{"t":"set","p":"start_time","pt":"msg","to":"msg.end_time - (7 * 86400000)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":490,"y":1920,"wires":[["29f632a5c68ecc55"]]},{"id":"29f632a5c68ecc55","type":"change","z":"d7101f3a4d45deed","g":"bb370162520e95d7","name":"Difference in Days","rules":[{"t":"set","p":"days_difference","pt":"msg","to":"(msg.end_time - msg.start_time) / 86400000","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":750,"y":1920,"wires":[["4881ffafeb5da64c"]]},{"id":"ccc081e4ccaba879","type":"global-config","env":[],"modules":{"node-red-contrib-moment":"5.0.0"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/getting-started/editor/index.md b/nuxt/content-guides/node-red/getting-started/editor/README.md similarity index 82% rename from src/node-red/getting-started/editor/index.md rename to nuxt/content-guides/node-red/getting-started/editor/README.md index 27862cacb3..e5d2be814f 100644 --- a/src/node-red/getting-started/editor/index.md +++ b/nuxt/content-guides/node-red/getting-started/editor/README.md @@ -1,15 +1,14 @@ --- -eleventyNavigation: - key: Editor - order: 1 - parent: Getting Started +title: Getting Started with the Node-RED Editor +metaTitle: "Getting Started with the Node-RED Editor" +navTitle: Editor +navOrder: 1 meta: - title: Getting Started with the Node-RED Editor - description: Learn about the powerful features of Node-RED Editor. + description: Learn about the powerful features of Node-RED Editor. keywords: node red editor, node-red editor, nodered flow, node-red flows, nodered workspace --- -# {{meta.title}} +# Getting Started with the Node-RED Editor The Node-RED Editor is one of the most essential components of Node-RED. As the main focus of Node-RED is to enable visual programming, the editor provides a graphical interface that allows users to create, configure, and manage flows easily. diff --git a/src/node-red/getting-started/editor/header.md b/nuxt/content-guides/node-red/getting-started/editor/header.md similarity index 97% rename from src/node-red/getting-started/editor/header.md rename to nuxt/content-guides/node-red/getting-started/editor/header.md index 0a0301f9e7..bfab10e44f 100644 --- a/src/node-red/getting-started/editor/header.md +++ b/nuxt/content-guides/node-red/getting-started/editor/header.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: Header - parent: Editor +title: Node-RED Editor Header component +metaTitle: "Node-RED Editor Header component" +navTitle: Header +navOrder: 50 meta: - title: Node-RED Editor Header component - description: Explore the features available in the Node-RED Editor header component, essential for navigation and configuration. + description: Explore the features available in the Node-RED Editor header component, essential for navigation and configuration. keywords: node-red, node-red editor --- -# {{meta.title}} +# Node-RED Editor Header component The Node-RED editor header is a central component that facilitates navigation to Node-RED main settings, provides a deploy button for executing flows, and access to the user profile. @@ -160,7 +160,7 @@ Clicking on this option will open the sidebars config tab that will allow you to ![Flows option of the main menu's options](./images/main-menu-flows.png "Flows option of the main menu's options"){data-zoomable} -This option allows you to manage the [flow](/node-red/terminology/#flow) tabs. +This option allows you to manage the [flow](/docs/node-red/terminology/#flow) tabs. - **Add**: Adds a new flow tab. - **Edit**: Edits the current flow tab. @@ -170,7 +170,7 @@ This option allows you to manage the [flow](/node-red/terminology/#flow) tabs. ![Subflows option of the main menu's options](./images/main-menu-subflows.png "Subflows option of the main menu's options"){data-zoomable} -This option allows you to create the [subflow](/node-red/terminology/#subflow). +This option allows you to create the [subflow](/docs/node-red/terminology/#subflow). - **Create Subflow**: Creates a new subflow tab. - **Selection to Subflow**: Converts the selected nodes into a subflow. diff --git a/src/node-red/getting-started/editor/images/adding-nodes-to-group.webm b/nuxt/content-guides/node-red/getting-started/editor/images/adding-nodes-to-group.webm similarity index 100% rename from src/node-red/getting-started/editor/images/adding-nodes-to-group.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/adding-nodes-to-group.webm diff --git a/src/node-red/getting-started/editor/images/creating-group.webm b/nuxt/content-guides/node-red/getting-started/editor/images/creating-group.webm similarity index 100% rename from src/node-red/getting-started/editor/images/creating-group.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/creating-group.webm diff --git a/src/node-red/getting-started/editor/images/deleting-wires.webm b/nuxt/content-guides/node-red/getting-started/editor/images/deleting-wires.webm similarity index 100% rename from src/node-red/getting-started/editor/images/deleting-wires.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/deleting-wires.webm diff --git a/src/node-red/getting-started/editor/images/deploy-button-active.png b/nuxt/content-guides/node-red/getting-started/editor/images/deploy-button-active.png similarity index 100% rename from src/node-red/getting-started/editor/images/deploy-button-active.png rename to nuxt/content-guides/node-red/getting-started/editor/images/deploy-button-active.png diff --git a/src/node-red/getting-started/editor/images/deploy-button-inactive.png b/nuxt/content-guides/node-red/getting-started/editor/images/deploy-button-inactive.png similarity index 100% rename from src/node-red/getting-started/editor/images/deploy-button-inactive.png rename to nuxt/content-guides/node-red/getting-started/editor/images/deploy-button-inactive.png diff --git a/src/node-red/getting-started/editor/images/deploy-button-options.png b/nuxt/content-guides/node-red/getting-started/editor/images/deploy-button-options.png similarity index 100% rename from src/node-red/getting-started/editor/images/deploy-button-options.png rename to nuxt/content-guides/node-red/getting-started/editor/images/deploy-button-options.png diff --git a/src/node-red/getting-started/editor/images/detaching-nodes-while-keeping-wires.webm b/nuxt/content-guides/node-red/getting-started/editor/images/detaching-nodes-while-keeping-wires.webm similarity index 100% rename from src/node-red/getting-started/editor/images/detaching-nodes-while-keeping-wires.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/detaching-nodes-while-keeping-wires.webm diff --git a/src/node-red/getting-started/editor/images/editor-flow-tabs.png b/nuxt/content-guides/node-red/getting-started/editor/images/editor-flow-tabs.png similarity index 100% rename from src/node-red/getting-started/editor/images/editor-flow-tabs.png rename to nuxt/content-guides/node-red/getting-started/editor/images/editor-flow-tabs.png diff --git a/src/node-red/getting-started/editor/images/editor-workspace.png b/nuxt/content-guides/node-red/getting-started/editor/images/editor-workspace.png similarity index 100% rename from src/node-red/getting-started/editor/images/editor-workspace.png rename to nuxt/content-guides/node-red/getting-started/editor/images/editor-workspace.png diff --git a/src/node-red/getting-started/editor/images/environment-variables-options.png b/nuxt/content-guides/node-red/getting-started/editor/images/environment-variables-options.png similarity index 100% rename from src/node-red/getting-started/editor/images/environment-variables-options.png rename to nuxt/content-guides/node-red/getting-started/editor/images/environment-variables-options.png diff --git a/src/node-red/getting-started/editor/images/group-description.png b/nuxt/content-guides/node-red/getting-started/editor/images/group-description.png similarity index 100% rename from src/node-red/getting-started/editor/images/group-description.png rename to nuxt/content-guides/node-red/getting-started/editor/images/group-description.png diff --git a/src/node-red/getting-started/editor/images/group-level-env.png b/nuxt/content-guides/node-red/getting-started/editor/images/group-level-env.png similarity index 100% rename from src/node-red/getting-started/editor/images/group-level-env.png rename to nuxt/content-guides/node-red/getting-started/editor/images/group-level-env.png diff --git a/src/node-red/getting-started/editor/images/group-merging-groups.webm b/nuxt/content-guides/node-red/getting-started/editor/images/group-merging-groups.webm similarity index 100% rename from src/node-red/getting-started/editor/images/group-merging-groups.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/group-merging-groups.webm diff --git a/src/node-red/getting-started/editor/images/group-properties-edit.png b/nuxt/content-guides/node-red/getting-started/editor/images/group-properties-edit.png similarity index 100% rename from src/node-red/getting-started/editor/images/group-properties-edit.png rename to nuxt/content-guides/node-red/getting-started/editor/images/group-properties-edit.png diff --git a/src/node-red/getting-started/editor/images/group-properties.png b/nuxt/content-guides/node-red/getting-started/editor/images/group-properties.png similarity index 100% rename from src/node-red/getting-started/editor/images/group-properties.png rename to nuxt/content-guides/node-red/getting-started/editor/images/group-properties.png diff --git a/src/node-red/getting-started/editor/images/group.png b/nuxt/content-guides/node-red/getting-started/editor/images/group.png similarity index 100% rename from src/node-red/getting-started/editor/images/group.png rename to nuxt/content-guides/node-red/getting-started/editor/images/group.png diff --git a/src/node-red/getting-started/editor/images/header-flowfuse-instance.png b/nuxt/content-guides/node-red/getting-started/editor/images/header-flowfuse-instance.png similarity index 100% rename from src/node-red/getting-started/editor/images/header-flowfuse-instance.png rename to nuxt/content-guides/node-red/getting-started/editor/images/header-flowfuse-instance.png diff --git a/src/node-red/getting-started/editor/images/header.png b/nuxt/content-guides/node-red/getting-started/editor/images/header.png similarity index 100% rename from src/node-red/getting-started/editor/images/header.png rename to nuxt/content-guides/node-red/getting-started/editor/images/header.png diff --git a/src/node-red/getting-started/editor/images/lasso-tool.webm b/nuxt/content-guides/node-red/getting-started/editor/images/lasso-tool.webm similarity index 100% rename from src/node-red/getting-started/editor/images/lasso-tool.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/lasso-tool.webm diff --git a/src/node-red/getting-started/editor/images/main-menu-arrange-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-arrange-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-arrange-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-arrange-option.png diff --git a/src/node-red/getting-started/editor/images/main-menu-configuration-nodes.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-configuration-nodes.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-configuration-nodes.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-configuration-nodes.png diff --git a/src/node-red/getting-started/editor/images/main-menu-edit-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-edit-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-edit-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-edit-option.png diff --git a/src/node-red/getting-started/editor/images/main-menu-export-local.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-export-local.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-export-local.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-export-local.png diff --git a/src/node-red/getting-started/editor/images/main-menu-export-team.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-export-team.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-export-team.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-export-team.png diff --git a/src/node-red/getting-started/editor/images/main-menu-export.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-export.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-export.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-export.png diff --git a/src/node-red/getting-started/editor/images/main-menu-flows.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-flows.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-flows.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-flows.png diff --git a/src/node-red/getting-started/editor/images/main-menu-groups.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-groups.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-groups.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-groups.png diff --git a/src/node-red/getting-started/editor/images/main-menu-import.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-import.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-import.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-import.png diff --git a/src/node-red/getting-started/editor/images/main-menu-search-flow-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-search-flow-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-search-flow-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-search-flow-option.png diff --git a/src/node-red/getting-started/editor/images/main-menu-search-tab.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-search-tab.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-search-tab.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-search-tab.png diff --git a/src/node-red/getting-started/editor/images/main-menu-settings.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-settings.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-settings.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-settings.png diff --git a/src/node-red/getting-started/editor/images/main-menu-subflows.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-subflows.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-subflows.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-subflows.png diff --git a/src/node-red/getting-started/editor/images/main-menu-view-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/main-menu-view-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/main-menu-view-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/main-menu-view-option.png diff --git a/src/node-red/getting-started/editor/images/moving-multiple-wires.webm b/nuxt/content-guides/node-red/getting-started/editor/images/moving-multiple-wires.webm similarity index 100% rename from src/node-red/getting-started/editor/images/moving-multiple-wires.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/moving-multiple-wires.webm diff --git a/src/node-red/getting-started/editor/images/moving-wire.webm b/nuxt/content-guides/node-red/getting-started/editor/images/moving-wire.webm similarity index 100% rename from src/node-red/getting-started/editor/images/moving-wire.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/moving-wire.webm diff --git a/src/node-red/getting-started/editor/images/mqtt-in-node-status.png b/nuxt/content-guides/node-red/getting-started/editor/images/mqtt-in-node-status.png similarity index 100% rename from src/node-red/getting-started/editor/images/mqtt-in-node-status.png rename to nuxt/content-guides/node-red/getting-started/editor/images/mqtt-in-node-status.png diff --git a/src/node-red/getting-started/editor/images/node-add-config-node.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-add-config-node.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-add-config-node.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-add-config-node.png diff --git a/src/node-red/getting-started/editor/images/node-appearance-tab.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-appearance-tab.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-appearance-tab.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-appearance-tab.png diff --git a/src/node-red/getting-started/editor/images/node-buttons.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-buttons.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-buttons.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-buttons.png diff --git a/src/node-red/getting-started/editor/images/node-description-tab.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-description-tab.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-description-tab.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-description-tab.png diff --git a/src/node-red/getting-started/editor/images/node-edit-properties-tab.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-edit-properties-tab.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-edit-properties-tab.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-edit-properties-tab.png diff --git a/src/node-red/getting-started/editor/images/node-enable-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-enable-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-enable-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-enable-option.png diff --git a/src/node-red/getting-started/editor/images/node-help.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-help.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-help.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-help.png diff --git a/src/node-red/getting-started/editor/images/node-indicating-error.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-indicating-error.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-indicating-error.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-indicating-error.png diff --git a/src/node-red/getting-started/editor/images/node-indicating-how-many-nodes-using-config-node.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-indicating-how-many-nodes-using-config-node.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-indicating-how-many-nodes-using-config-node.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-indicating-how-many-nodes-using-config-node.png diff --git a/src/node-red/getting-started/editor/images/node-indicating-undeployed-changes.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-indicating-undeployed-changes.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-indicating-undeployed-changes.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-indicating-undeployed-changes.png diff --git a/src/node-red/getting-started/editor/images/node-input-ouput-port.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-input-ouput-port.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-input-ouput-port.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-input-ouput-port.png diff --git a/src/node-red/getting-started/editor/images/node-quick-add-dialog.webm b/nuxt/content-guides/node-red/getting-started/editor/images/node-quick-add-dialog.webm similarity index 100% rename from src/node-red/getting-started/editor/images/node-quick-add-dialog.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/node-quick-add-dialog.webm diff --git a/src/node-red/getting-started/editor/images/node-red-editor-palette.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-editor-palette.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-editor-palette.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-editor-palette.png diff --git a/src/node-red/getting-started/editor/images/node-red-editor-window.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-editor-window.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-editor-window.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-editor-window.png diff --git a/src/node-red/getting-started/editor/images/node-red-export-local-library.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-export-local-library.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-export-local-library.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-export-local-library.png diff --git a/src/node-red/getting-started/editor/images/node-red-node.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-node.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-node.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-node.png diff --git a/src/node-red/getting-started/editor/images/node-red-palette-category.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-category.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-palette-category.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-category.png diff --git a/src/node-red/getting-started/editor/images/node-red-palette-collapse-expand.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-collapse-expand.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-palette-collapse-expand.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-collapse-expand.png diff --git a/src/node-red/getting-started/editor/images/node-red-palette-export-scope.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-export-scope.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-palette-export-scope.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-export-scope.png diff --git a/src/node-red/getting-started/editor/images/node-red-palette-json.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-json.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-palette-json.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-json.png diff --git a/src/node-red/getting-started/editor/images/node-red-palette-search.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-search.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-palette-search.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-search.png diff --git a/src/node-red/getting-started/editor/images/node-red-palette-toggle.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-toggle.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-palette-toggle.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-palette-toggle.png diff --git a/src/node-red/getting-started/editor/images/node-red-pallete-manger-nodes-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-pallete-manger-nodes-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-pallete-manger-nodes-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-pallete-manger-nodes-option.png diff --git a/src/node-red/getting-started/editor/images/node-red-sidebar.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-red-sidebar.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-red-sidebar.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-red-sidebar.png diff --git a/src/node-red/getting-started/editor/images/node-wire.png b/nuxt/content-guides/node-red/getting-started/editor/images/node-wire.png similarity index 100% rename from src/node-red/getting-started/editor/images/node-wire.png rename to nuxt/content-guides/node-red/getting-started/editor/images/node-wire.png diff --git a/src/node-red/getting-started/editor/images/pallete-manger-filter-and-sort-options.png b/nuxt/content-guides/node-red/getting-started/editor/images/pallete-manger-filter-and-sort-options.png similarity index 100% rename from src/node-red/getting-started/editor/images/pallete-manger-filter-and-sort-options.png rename to nuxt/content-guides/node-red/getting-started/editor/images/pallete-manger-filter-and-sort-options.png diff --git a/src/node-red/getting-started/editor/images/removing-node-from-group.webm b/nuxt/content-guides/node-red/getting-started/editor/images/removing-node-from-group.webm similarity index 100% rename from src/node-red/getting-started/editor/images/removing-node-from-group.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/removing-node-from-group.webm diff --git a/src/node-red/getting-started/editor/images/removing-nodes-from-group.webm b/nuxt/content-guides/node-red/getting-started/editor/images/removing-nodes-from-group.webm similarity index 100% rename from src/node-red/getting-started/editor/images/removing-nodes-from-group.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/removing-nodes-from-group.webm diff --git a/src/node-red/getting-started/editor/images/removing-nodes-while-keeping-wires.webm b/nuxt/content-guides/node-red/getting-started/editor/images/removing-nodes-while-keeping-wires.webm similarity index 100% rename from src/node-red/getting-started/editor/images/removing-nodes-while-keeping-wires.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/removing-nodes-while-keeping-wires.webm diff --git a/src/node-red/getting-started/editor/images/selecting-connected-nodes.webm b/nuxt/content-guides/node-red/getting-started/editor/images/selecting-connected-nodes.webm similarity index 100% rename from src/node-red/getting-started/editor/images/selecting-connected-nodes.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/selecting-connected-nodes.webm diff --git a/src/node-red/getting-started/editor/images/selecting-downward-connected-nodes.webm b/nuxt/content-guides/node-red/getting-started/editor/images/selecting-downward-connected-nodes.webm similarity index 100% rename from src/node-red/getting-started/editor/images/selecting-downward-connected-nodes.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/selecting-downward-connected-nodes.webm diff --git a/src/node-red/getting-started/editor/images/selecting-flows.webm b/nuxt/content-guides/node-red/getting-started/editor/images/selecting-flows.webm similarity index 100% rename from src/node-red/getting-started/editor/images/selecting-flows.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/selecting-flows.webm diff --git a/src/node-red/getting-started/editor/images/selecting-upward-connected-nodes.webm b/nuxt/content-guides/node-red/getting-started/editor/images/selecting-upward-connected-nodes.webm similarity index 100% rename from src/node-red/getting-started/editor/images/selecting-upward-connected-nodes.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/selecting-upward-connected-nodes.webm diff --git a/src/node-red/getting-started/editor/images/slicing-wires.webm b/nuxt/content-guides/node-red/getting-started/editor/images/slicing-wires.webm similarity index 100% rename from src/node-red/getting-started/editor/images/slicing-wires.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/slicing-wires.webm diff --git a/src/node-red/getting-started/editor/images/subflow-appearance-tab.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-appearance-tab.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-appearance-tab.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-appearance-tab.png diff --git a/src/node-red/getting-started/editor/images/subflow-converting-nodes-to-subflow.webm b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-converting-nodes-to-subflow.webm similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-converting-nodes-to-subflow.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-converting-nodes-to-subflow.webm diff --git a/src/node-red/getting-started/editor/images/subflow-custom-properties.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-custom-properties.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-custom-properties.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-custom-properties.png diff --git a/src/node-red/getting-started/editor/images/subflow-delete-option.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-delete-option.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-delete-option.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-delete-option.png diff --git a/src/node-red/getting-started/editor/images/subflow-editing-properties.webm b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-editing-properties.webm similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-editing-properties.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-editing-properties.webm diff --git a/src/node-red/getting-started/editor/images/subflow-input-output.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-input-output.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-input-output.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-input-output.png diff --git a/src/node-red/getting-started/editor/images/subflow-module-tab.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-module-tab.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-module-tab.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-module-tab.png diff --git a/src/node-red/getting-started/editor/images/subflow-node.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-node.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-node.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-node.png diff --git a/src/node-red/getting-started/editor/images/subflow-property-description.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-property-description.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-property-description.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-property-description.png diff --git a/src/node-red/getting-started/editor/images/subflow-status-node.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-status-node.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-status-node.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-status-node.png diff --git a/src/node-red/getting-started/editor/images/subflow-ui-preview.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-ui-preview.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-ui-preview.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-ui-preview.png diff --git a/src/node-red/getting-started/editor/images/subflow-window.png b/nuxt/content-guides/node-red/getting-started/editor/images/subflow-window.png similarity index 100% rename from src/node-red/getting-started/editor/images/subflow-window.png rename to nuxt/content-guides/node-red/getting-started/editor/images/subflow-window.png diff --git a/src/node-red/getting-started/editor/images/wiring-nodes-together.webm b/nuxt/content-guides/node-red/getting-started/editor/images/wiring-nodes-together.webm similarity index 100% rename from src/node-red/getting-started/editor/images/wiring-nodes-together.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/wiring-nodes-together.webm diff --git a/src/node-red/getting-started/editor/images/workspace-add-flow.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-add-flow.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-add-flow.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-add-flow.png diff --git a/src/node-red/getting-started/editor/images/workspace-delete-flow-dialog.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-delete-flow-dialog.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-delete-flow-dialog.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-delete-flow-dialog.png diff --git a/src/node-red/getting-started/editor/images/workspace-delete-flow.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-delete-flow.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-delete-flow.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-delete-flow.png diff --git a/src/node-red/getting-started/editor/images/workspace-disable-enable-flow.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-disable-enable-flow.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-disable-enable-flow.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-disable-enable-flow.png diff --git a/src/node-red/getting-started/editor/images/workspace-enable-disable-flow.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-enable-disable-flow.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-enable-disable-flow.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-enable-disable-flow.png diff --git a/src/node-red/getting-started/editor/images/workspace-flow-edit.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-flow-edit.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-flow-edit.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-flow-edit.png diff --git a/src/node-red/getting-started/editor/images/workspace-navigator-tool.webm b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-navigator-tool.webm similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-navigator-tool.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-navigator-tool.webm diff --git a/src/node-red/getting-started/editor/images/workspace-reordering-flow.webm b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-reordering-flow.webm similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-reordering-flow.webm rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-reordering-flow.webm diff --git a/src/node-red/getting-started/editor/images/workspace-search-tool.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-search-tool.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-search-tool.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-search-tool.png diff --git a/src/node-red/getting-started/editor/images/workspace-view-tool.png b/nuxt/content-guides/node-red/getting-started/editor/images/workspace-view-tool.png similarity index 100% rename from src/node-red/getting-started/editor/images/workspace-view-tool.png rename to nuxt/content-guides/node-red/getting-started/editor/images/workspace-view-tool.png diff --git a/src/node-red/getting-started/editor/palette.md b/nuxt/content-guides/node-red/getting-started/editor/palette.md similarity index 91% rename from src/node-red/getting-started/editor/palette.md rename to nuxt/content-guides/node-red/getting-started/editor/palette.md index e3983cd54f..56c40bb7b4 100644 --- a/src/node-red/getting-started/editor/palette.md +++ b/nuxt/content-guides/node-red/getting-started/editor/palette.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: Palette - parent: Editor +title: Node-RED Editor Palette +metaTitle: "Node-RED Editor Palette" +navTitle: Palette +navOrder: 50 meta: - title: Node-RED Editor Palette description: Explore the features available in the Node-RED Editor palette, essential for navigation and configuration. keywords: node-red, node-red editor --- -# {{meta.title}} +# Node-RED Editor Palette The Palette is the left sidebar that contains all available nodes, including core nodes and third-party nodes that are installed. @@ -40,4 +40,4 @@ Clicking on the down arrow icon will expand all categories back as default. ![Image showing Node-RED Palette toggle palette button](./images/node-red-palette-toggle.png "Image showing Node-RED Palette toggle palette button"){data-zoomable} -Clicking this button hides the palette sidebar. To show it again, click on the button once more. \ No newline at end of file +Clicking this button hides the palette sidebar. To show it again, click on the button once more. diff --git a/src/node-red/getting-started/editor/sidebar.md b/nuxt/content-guides/node-red/getting-started/editor/sidebar.md similarity index 98% rename from src/node-red/getting-started/editor/sidebar.md rename to nuxt/content-guides/node-red/getting-started/editor/sidebar.md index e28746cd33..b2b83c1165 100644 --- a/src/node-red/getting-started/editor/sidebar.md +++ b/nuxt/content-guides/node-red/getting-started/editor/sidebar.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: Sidebar - parent: Editor +title: Node-RED Editor Sidebar component +metaTitle: "Node-RED Editor Sidebar component" +navTitle: Sidebar +navOrder: 50 meta: - title: Node-RED Editor Sidebar component description: Explore the features available in the Node-RED Editor sidebar component. keywords: node-red, node-red editor, sidebar --- -# {{meta.title}} +# Node-RED Editor Sidebar component The sidebar is located on the right side of your Node-RED Editor. It contains a collection of different tools that make Node-RED easier to use, such as managing nodes, configuration, context storage, and more. @@ -160,4 +160,4 @@ To resize the sidebar, hover the mouse over the sidebar's border until the curso To switch between panels, click the expand icon in the top-right corner. This will open a menu with all the available panels. -Alternatively, you can click on the small boxes with different icons at the top. \ No newline at end of file +Alternatively, you can click on the small boxes with different icons at the top. diff --git a/src/node-red/getting-started/editor/workspace.md b/nuxt/content-guides/node-red/getting-started/editor/workspace.md similarity index 89% rename from src/node-red/getting-started/editor/workspace.md rename to nuxt/content-guides/node-red/getting-started/editor/workspace.md index ba6a0c58d8..958c15f26f 100644 --- a/src/node-red/getting-started/editor/workspace.md +++ b/nuxt/content-guides/node-red/getting-started/editor/workspace.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: Workspace - parent: Editor +title: Node-RED Editor Workspace +metaTitle: "Node-RED Editor Workspace" +navTitle: Workspace +navOrder: 50 meta: - title: Node-RED Editor Workspace description: Explore the features available in the Node-RED Editor workspace component keywords: node-red, node-red editor, workspace --- -# {{meta.title}} +# Node-RED Editor Workspace The workspace is the main area in the editor where you build application flows by dropping nodes from the palette. @@ -20,7 +20,7 @@ The workspace is the main area in the editor where you build application flows b The workspace provides view tools at the footer in the right corner. This includes zoom in (`Ctrl` + `+`) and zoom out (`Ctrl` + `-`) buttons to control the view of the workspace and reset the zoom level to its default. - + Additionally, it provides a view navigator that allows you to see a scaled-down view of the entire workspace. In this view, you can also see the currently visible area of the workspace in the editor. To jump to a specific workspace area, click on that area in the view navigator. @@ -74,7 +74,7 @@ Alternatively, right-click on the flow tab and select "Disable/Enable" from the ### Reordering Flows - + Flows can be reordered by clicking and dragging the flow tab to the desired position. More options for the flow tab menu can be accessed by clicking on the top-right dropdown arrow icon. @@ -98,7 +98,7 @@ It will create the subflow window like a flow tab for you. ### Editing a Subflow - + To open the subflow edit dialog, double-click on the subflow node, then click on the "edit template properties". You can give the name for that subflow, add the description by clicking on the top-right @@ -130,7 +130,7 @@ To delete the subflow, click on the "delete subflow" button at the top of the su ### Converting Nodes into a Subflow - + If you have nodes on the workspace and you want to create a subflow of them, you can select them by pressing the left mouse key and drawing a rectangle around them. Then click on subflow -> selection to subflow in the main menu. @@ -184,7 +184,7 @@ Nodes can be added from the [Node-RED palette](./palette.md) by dragging them on Node-RED Editor provides a quick and easy way to add nodes via the palette: - + - Press `Ctrl` or `Command` and click on the workspace. - Select the desired node from the dialog, which contains all available nodes from the main node palette. @@ -253,7 +253,7 @@ The "wires" refer to the connections that link nodes together to define the flow ### Wiring Nodes Together - + To connect the nodes using the wires, left-click on the node's output port and drag the wire to the destination input port. Additionally, if you press the `Ctrl`/`Command` and mouse left key on the input or output port you will not need to hold the left mouse key or any other button to drag the wire. To connect it to the destination port, press the left mouse key on destination port. If the `Ctrl`/`Command` key remains pressed after connecting to the destination port, and if that port's node has an output port, a new wire will be dragged. @@ -261,23 +261,23 @@ The wires can be connected from the input port to the output port, not from the ### Deleting Wires - + To delete wires, click the left mouse button to select the first wire. To select multiple wires, press and hold the `Ctrl\Command` key while clicking each wire with the left mouse button. If you use only the left mouse button, you can select only one wire at a time. After selecting the wires, press the 'delete' or 'backspace' key to delete them. ### Moving Wires - + To disconnect the wire from the port, select the wire by clicking on it. Then press and hold the `Shift` key while the left mouse key is pressed on the port. When the mouse is dragged you'll see the wire disconnects from the port and can be connected to another port. - + If a port has multiple wires connected to it, if none of them are selected when the button is pressed with the Shift key held, all of the wires will move. ### Slicing Wires - + Wires can also be removed by slicing through them. You can do this by holding the `Alt`/`Option` key and then drawing the line for slicing by holding the left mouse key. @@ -285,13 +285,13 @@ Wires can also be removed by slicing through them. You can do this by holding th #### Keeping Wire While Deleting Node - + To do that, press and hold the `Ctrl`/`Command` key, select the node by clicking the left mouse key, and then press the "delete" or "backspace" button. #### Detaching Node from Wires - + To use this option, you have to set the [keyboard shortcut](./header.md#keyboard-shortcuts) for the "detach-selected-nodes" action. @@ -303,7 +303,7 @@ In Node-RED, groups can be created for better organization, containing a single ### Creating a Flow Group - + To create a flow group, select nodes (by holding the Ctrl key or drawing a rectangle around them). Navigate to `Groups -> Group selection` in the main menu or press `Ctrl + Shift + G`. @@ -329,25 +329,25 @@ To add a description to the group, double-click on it. Select the third option f ### Adding Nodes to a Group - + To add nodes to an existing group, drag and drop them into the group. This can be done one node at a time. Groups can also be nested within each other in the same manner. ### Removing Nodes from a Group - + To remove nodes from a group, select the nodes and navigate to `Groups -> Remove selection` in the main menu. Similarly, remove a group from another group. Alternatively, click on a node, hold the 'Alt' key, and drag it outside of the group. ### Merging Groups - + To merge multiple groups into a single group, select the groups. Go to `Main Menu -> Groups -> Merge selection`. ### Ungrouping Selected Nodes - + To ungroup nodes from a group, select the nodes. Go to `Main Menu -> Groups -> Ungroup selection`. @@ -359,30 +359,30 @@ A node can be selected or deselected by clicking on it. To select multiple nodes ### Lasso Tool - + Node-RED provides a lasso tool to make selection faster. To use the lasso tool, press the left mouse key and drag the cursor, then you can select multiple nodes by drawing a rectangle around them. ### Selecting Connected Nodes - + To select all connected nodes to a specific node, press the `Shift` button and click on the middle of that node. ### Selecting All Upstream Nodes - + To select all of the connected nodes that are before that specific node, press the `Shift` button and while holding it click on the left part of that node. ### Selecting All Downstream Nodes - + To select all of the connected nodes that are after that specific node, press the `Shift` button and while holding it click on the right part of that node. ### Selecting Flows - + To select the flow tabs, press the `Ctrl/Command` key and while holding it click on the flow tab you want to select. Now you can then delete, export, or copy them collectively. diff --git a/src/node-red/getting-started/images/adding-breakpoints.webm b/nuxt/content-guides/node-red/getting-started/images/adding-breakpoints.webm similarity index 100% rename from src/node-red/getting-started/images/adding-breakpoints.webm rename to nuxt/content-guides/node-red/getting-started/images/adding-breakpoints.webm diff --git a/src/node-red/getting-started/images/breakpoint-debugging.webm b/nuxt/content-guides/node-red/getting-started/images/breakpoint-debugging.webm similarity index 100% rename from src/node-red/getting-started/images/breakpoint-debugging.webm rename to nuxt/content-guides/node-red/getting-started/images/breakpoint-debugging.webm diff --git a/src/node-red/getting-started/images/breakpoints-section.png b/nuxt/content-guides/node-red/getting-started/images/breakpoints-section.png similarity index 100% rename from src/node-red/getting-started/images/breakpoints-section.png rename to nuxt/content-guides/node-red/getting-started/images/breakpoints-section.png diff --git a/src/node-red/getting-started/images/debugger-controls.png b/nuxt/content-guides/node-red/getting-started/images/debugger-controls.png similarity index 100% rename from src/node-red/getting-started/images/debugger-controls.png rename to nuxt/content-guides/node-red/getting-started/images/debugger-controls.png diff --git a/src/node-red/getting-started/images/disable-enable-button.png b/nuxt/content-guides/node-red/getting-started/images/disable-enable-button.png similarity index 100% rename from src/node-red/getting-started/images/disable-enable-button.png rename to nuxt/content-guides/node-red/getting-started/images/disable-enable-button.png diff --git a/src/node-red/getting-started/images/disabling-breakpoints.webm b/nuxt/content-guides/node-red/getting-started/images/disabling-breakpoints.webm similarity index 100% rename from src/node-red/getting-started/images/disabling-breakpoints.webm rename to nuxt/content-guides/node-red/getting-started/images/disabling-breakpoints.webm diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-change-node.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-change-node.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-change-node.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-change-node.png diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-calculating-avg.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-calculating-avg.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-calculating-avg.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-calculating-avg.png diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-combining-node.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-combining-node.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-combining-node.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-combining-node.png diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-sort-node.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-sort-node.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-sort-node.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-sort-node.png diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-switch-node.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-switch-node.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-switch-node.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-data-with-node-red-switch-node.png diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-switch-node.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-switch-node.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-switch-node.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-switch-node.png diff --git a/src/node-red/getting-started/images/filtering-mapping-sorting-reducing-with-node-red.png b/nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-with-node-red.png similarity index 100% rename from src/node-red/getting-started/images/filtering-mapping-sorting-reducing-with-node-red.png rename to nuxt/content-guides/node-red/getting-started/images/filtering-mapping-sorting-reducing-with-node-red.png diff --git a/src/node-red/getting-started/images/if-else-with-node-red.png b/nuxt/content-guides/node-red/getting-started/images/if-else-with-node-red.png similarity index 100% rename from src/node-red/getting-started/images/if-else-with-node-red.png rename to nuxt/content-guides/node-red/getting-started/images/if-else-with-node-red.png diff --git a/src/node-red/getting-started/images/message-section.png b/nuxt/content-guides/node-red/getting-started/images/message-section.png similarity index 100% rename from src/node-red/getting-started/images/message-section.png rename to nuxt/content-guides/node-red/getting-started/images/message-section.png diff --git a/src/node-red/getting-started/images/mistake-1-solution.webm b/nuxt/content-guides/node-red/getting-started/images/mistake-1-solution.webm similarity index 100% rename from src/node-red/getting-started/images/mistake-1-solution.webm rename to nuxt/content-guides/node-red/getting-started/images/mistake-1-solution.webm diff --git a/src/node-red/getting-started/images/mistake1.webm b/nuxt/content-guides/node-red/getting-started/images/mistake1.webm similarity index 100% rename from src/node-red/getting-started/images/mistake1.webm rename to nuxt/content-guides/node-red/getting-started/images/mistake1.webm diff --git a/src/node-red/getting-started/images/node-red-data-types.webm b/nuxt/content-guides/node-red/getting-started/images/node-red-data-types.webm similarity index 100% rename from src/node-red/getting-started/images/node-red-data-types.webm rename to nuxt/content-guides/node-red/getting-started/images/node-red-data-types.webm diff --git a/src/node-red/getting-started/images/node-red-instance-update.png b/nuxt/content-guides/node-red/getting-started/images/node-red-instance-update.png similarity index 100% rename from src/node-red/getting-started/images/node-red-instance-update.png rename to nuxt/content-guides/node-red/getting-started/images/node-red-instance-update.png diff --git a/src/node-red/getting-started/images/node-red-message-passing.webm b/nuxt/content-guides/node-red/getting-started/images/node-red-message-passing.webm similarity index 100% rename from src/node-red/getting-started/images/node-red-message-passing.webm rename to nuxt/content-guides/node-red/getting-started/images/node-red-message-passing.webm diff --git a/src/node-red/getting-started/images/proceeding-further-execution.webm b/nuxt/content-guides/node-red/getting-started/images/proceeding-further-execution.webm similarity index 100% rename from src/node-red/getting-started/images/proceeding-further-execution.webm rename to nuxt/content-guides/node-red/getting-started/images/proceeding-further-execution.webm diff --git a/src/node-red/getting-started/images/removing-breakpoints.webm b/nuxt/content-guides/node-red/getting-started/images/removing-breakpoints.webm similarity index 100% rename from src/node-red/getting-started/images/removing-breakpoints.webm rename to nuxt/content-guides/node-red/getting-started/images/removing-breakpoints.webm diff --git a/src/node-red/getting-started/images/schedule-update.png b/nuxt/content-guides/node-red/getting-started/images/schedule-update.png similarity index 100% rename from src/node-red/getting-started/images/schedule-update.png rename to nuxt/content-guides/node-red/getting-started/images/schedule-update.png diff --git a/src/node-red/getting-started/images/update-node-red-cta-1.png b/nuxt/content-guides/node-red/getting-started/images/update-node-red-cta-1.png similarity index 100% rename from src/node-red/getting-started/images/update-node-red-cta-1.png rename to nuxt/content-guides/node-red/getting-started/images/update-node-red-cta-1.png diff --git a/src/node-red/getting-started/library/index.md b/nuxt/content-guides/node-red/getting-started/library/README.md similarity index 83% rename from src/node-red/getting-started/library/index.md rename to nuxt/content-guides/node-red/getting-started/library/README.md index 2189eb298a..68e0f4fe04 100644 --- a/src/node-red/getting-started/library/index.md +++ b/nuxt/content-guides/node-red/getting-started/library/README.md @@ -1,13 +1,11 @@ --- +title: Node-RED Library – A Curated and Actively Maintained List of Nodes metaTitle: "Node-RED Library: Curated List of Nodes" -eleventyNavigation: - key: Node-RED Library - parent: Getting Started - order: 3 +navTitle: Node-RED Library +navOrder: 3 meta: - title: Node-RED Library – A Curated and Actively Maintained List of Nodes - description: Browse the Node-RED Library for community-built nodes and integrations. FlowFuse's curated catalog offers tested, documented, enterprise-ready solutions with professional support for critical deployments. - keywords: node-red library, node-red nodes, node-red integrations, community nodes, node-red packages, custom nodes, node-red flows + description: Browse the Node-RED Library for community-built nodes and integrations. FlowFuse's curated catalog offers tested, documented, enterprise-ready solutions with professional support for critical deployments. + keywords: node-red library, node-red nodes, node-red integrations, community nodes, node-red packages, custom nodes, node-red flows --- # Node-RED Library @@ -47,7 +45,7 @@ To install nodes through the Node-RED editor: 5. Search for the required node 6. Click **Install** next to the package - + Installed nodes appear in your palette immediately and are ready for use in your flows. diff --git a/src/node-red/getting-started/library/images/installing-node-red-node.webm b/nuxt/content-guides/node-red/getting-started/library/images/installing-node-red-node.webm similarity index 100% rename from src/node-red/getting-started/library/images/installing-node-red-node.webm rename to nuxt/content-guides/node-red/getting-started/library/images/installing-node-red-node.webm diff --git a/src/node-red/getting-started/node-red-android.md b/nuxt/content-guides/node-red/getting-started/node-red-android.md similarity index 96% rename from src/node-red/getting-started/node-red-android.md rename to nuxt/content-guides/node-red/getting-started/node-red-android.md index a1e90b5bfb..f6920142ac 100644 --- a/src/node-red/getting-started/node-red-android.md +++ b/nuxt/content-guides/node-red/getting-started/node-red-android.md @@ -1,15 +1,14 @@ --- -eleventyNavigation: - key: Node-RED On Android - order: 4 - parent: Getting Started +title: Installing Node-RED on Android +metaTitle: "Installing Node-RED on Android" +navTitle: Node-RED On Android +navOrder: 4 meta: - title: Installing Node-RED on Android description: Learn how to install and run Node-RED on Android devices using Termux keywords: node-red android, termux, android installation, mobile node-red --- -# {{ meta.title }} +# Installing Node-RED on Android You can run Node-RED on Android devices using Termux, a terminal emulator and Linux environment app. This guide walks you through the installation process and helps you get Node-RED running on your Android phone or tablet. diff --git a/src/node-red/getting-started/node-red-messages.md b/nuxt/content-guides/node-red/getting-started/node-red-messages.md similarity index 95% rename from src/node-red/getting-started/node-red-messages.md rename to nuxt/content-guides/node-red/getting-started/node-red-messages.md index 9eab49a2de..1bc7377930 100644 --- a/src/node-red/getting-started/node-red-messages.md +++ b/nuxt/content-guides/node-red/getting-started/node-red-messages.md @@ -1,15 +1,14 @@ --- -eleventyNavigation: - key: Working with Messages - order: 5 - parent: Getting Started +title: Understanding Node-RED Messages +metaTitle: "Understanding Node-RED Messages" +navTitle: Working with Messages +navOrder: 5 meta: - title: Understanding Node-RED Messages description: A comprehensive guide to working with Node-RED messages, ensuring error-free flows and optimized data handling. keywords: msg.payload node red, node red message structure, node-red messages, node red msg.payload, msg.payload, node red msg.payload array --- -# {{ meta.title }} +# Understanding Node-RED Messages Node-RED operates by passing messages between nodes to create dynamic IoT, automation, and data-processing workflows. Each message transports data that nodes read, modify, process, or analyze. Understanding message structure and handling is essential for building reliable flows. Poor message management can cause subtle bugs like data overwrites, infinite loops, or system crashes. @@ -19,7 +18,7 @@ This guide explores Node-RED message mechanics, common pitfalls, and best practi Messages in Node-RED are data packets that flow between nodes in your workflow. Node-RED follows an event-driven architecture where nodes act as both event emitters and listeners, with messages serving as the communication medium between them. - + _Node-RED message passing visualization_ Messages carry the data that powers your workflows, sensor readings, user inputs, API responses, and more. Fundamentally, Node-RED messages are JavaScript objects, providing a flexible structure for managing and transferring data throughout your flows. @@ -69,7 +68,7 @@ Message property values can be any JavaScript-supported data type: - [Buffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) - Other complex data types - + _Various data types supported by Node-RED_ ## How to Clone Messages and Properties @@ -245,7 +244,7 @@ Steps: 3. Set the action to "Always convert to JavaScript Object" and click Done. 4. Connect the JSON node between the source node (sending the JSON string) and the destination node (requiring the parsed object). -The JSON node automatically converts incoming JSON strings into JavaScript objects. For more details, see the [JSON node documentation](/node-red/core-nodes/json/). +The JSON node automatically converts incoming JSON strings into JavaScript objects. For more details, see the [JSON node documentation](/docs/node-red/core-nodes/parsers/json/). ## Common Mistakes to Avoid @@ -271,10 +270,10 @@ return msg; This commonly occurs when an Inject node sends `msg.payload` as a string or number, then a Change node attempts to add properties to it. - + _Incorrect: Adding properties to a non-object type_ - + _Correct: Initialize as object before adding properties_ ### 2. Overwriting the Entire Message Object @@ -324,4 +323,4 @@ msg.payload = msg.payload * 2; return msg; // Flow continues ``` -By mastering Node-RED message handling and avoiding common mistakes, you can build robust, efficient workflows. Understanding JSON conversion, message cloning, and proper property management ensures smooth data flow between nodes and maintainable, error-free applications. \ No newline at end of file +By mastering Node-RED message handling and avoiding common mistakes, you can build robust, efficient workflows. Understanding JSON conversion, message cloning, and proper property management ensures smooth data flow between nodes and maintainable, error-free applications. diff --git a/src/node-red/getting-started/node-red-port.md b/nuxt/content-guides/node-red/getting-started/node-red-port.md similarity index 96% rename from src/node-red/getting-started/node-red-port.md rename to nuxt/content-guides/node-red/getting-started/node-red-port.md index d42a2394ab..9787a510ea 100644 --- a/src/node-red/getting-started/node-red-port.md +++ b/nuxt/content-guides/node-red/getting-started/node-red-port.md @@ -1,15 +1,14 @@ --- -eleventyNavigation: - key: Port Configuration - order: 2 - parent: Getting Started +title: Node-RED Port (localhost:1880) +metaTitle: "Node-RED Port (localhost:1880)" +navTitle: Port Configuration +navOrder: 2 meta: - title: Node-RED Port (localhost:1880) description: Learn how to configure Node-RED ports, change default settings, secure your installation, and set up remote access with FlowFuse. keywords: Node-RED port, port 1880, change Node-RED port, Node-RED security, remote access, FlowFuse, port configuration --- -# {{ meta.title }} +# Node-RED Port (localhost:1880) Node-RED runs on **port 1880** by default. This is the network port where Node-RED listens for connections. When you start Node-RED, you access the editor by opening `http://localhost:1880` in your browser. All your HTTP endpoints and the Node-RED interface are served through this port. diff --git a/nuxt/content-guides/node-red/getting-started/programming/README.md b/nuxt/content-guides/node-red/getting-started/programming/README.md new file mode 100644 index 0000000000..6b4fe635c8 --- /dev/null +++ b/nuxt/content-guides/node-red/getting-started/programming/README.md @@ -0,0 +1,22 @@ +--- +title: Node-RED Programming +metaTitle: "Node-RED Programming" +navTitle: Programming +navOrder: 8 +meta: + description: Master Node-RED programming fundamentals including flows, nodes, messages, conditional logic, and data manipulation. Learn essential concepts for building sophisticated visual programming solutions. + keywords: Node-RED programming, visual programming, Node-RED flows, Node-RED nodes, message passing, conditional logic, data manipulation, flow-based programming, Node-RED tutorial, IoT programming +--- + +# Node-RED Programming + +Learn the core programming concepts you'll use every day in Node-RED. This section covers the fundamental building blocks that will help you create more sophisticated and reliable flows. + +## What's Covered + +Programming in Node-RED means working with flows, nodes, and messages. Even though you're working visually, you'll still need to understand key programming concepts like conditional logic, loops, and data manipulation. This section teaches you how to implement these concepts using Node-RED's visual approach. + +- [How to Debug Node-RED Flows Using Debugger](/docs/node-red/getting-started/programming/debugging-flows/): Debug Node-RED flows using the Debugger. Learn to set breakpoints, step through execution, and inspect messages for efficient troubleshooting. +- [How to Filter, Map, Sort, and Reduce Data in Node-RED](/docs/node-red/getting-started/programming/data-tranformation/): Learn how to perform data transformation in Node-RED with a low-code approach. +- [How to implement loops in Node-RED flows](/docs/node-red/getting-started/programming/loop/): Learn how to implement while, for, and for...of loops in Node-RED with core and custom nodes for efficient data processing and automation. +- [How to Use If-Else Logic in Node-RED](/docs/node-red/getting-started/programming/if-else/): Learn how to implement If-Else logic in Node-RED with our step-by-step guide. Use Function and Switch nodes for dynamic, conditional flows. diff --git a/src/node-red/getting-started/programming/data-tranformation.md b/nuxt/content-guides/node-red/getting-started/programming/data-tranformation.md similarity index 97% rename from src/node-red/getting-started/programming/data-tranformation.md rename to nuxt/content-guides/node-red/getting-started/programming/data-tranformation.md index 8ef74b5a7d..3e16217393 100644 --- a/src/node-red/getting-started/programming/data-tranformation.md +++ b/nuxt/content-guides/node-red/getting-started/programming/data-tranformation.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: Data Tranformation - parent: Programming +title: How to Filter, Map, Sort, and Reduce Data in Node-RED +metaTitle: "How to Filter, Map, Sort, and Reduce Data in Node-RED" +navTitle: Data Tranformation +navOrder: 50 meta: - title: How to Filter, Map, Sort, and Reduce Data in Node-RED - description: Learn how to perform data transformation in Node-RED with a low-code approach. + description: Learn how to perform data transformation in Node-RED with a low-code approach. keywords: how to filter data in node-red, how to map array in node-red, how to map data in node-red, how to sort data in node-red, how to reduce data in node-red, data transformation in node-red -image: /node-red/getting-started/images/filtering-mapping-sorting-reducing-with-node-red.png +image: /docs/node-red/getting-started/images/filtering-mapping-sorting-reducing-with-node-red.png --- -# {{meta.title}} +# How to Filter, Map, Sort, and Reduce Data in Node-RED Data transformation is at the heart of most Node-RED applications, whether you're processing IoT sensor readings, cleaning API responses, or preparing data for visualization. While you could write JavaScript functions to handle these operations, Node-RED's visual, low-code approach offers a more maintainable and accessible alternative that anyone on your team can understand and modify. @@ -92,9 +92,11 @@ Filter, map, sort, and reduce are essential functions in data processing because However, you've noticed that the temperature data is in Kelvin, but you need it in Celsius. Additionally, the data is not correctly ordered by timestamp, and you only need the data of June 17th. Finally, you want to calculate the average temperature for that day. Users who are not familiar with Node-RED basics can use a JavaScript function node to achieve this, as shown below: -{% renderFlow %} +::render-flow +```json [{"id":"306d455509a3747e","type":"inject","z":"977143edb097b685","name":"Inject the sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"timestamp\":\"2024-06-17T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-17T11:00:00Z\",\"temperature\":299.15},{\"timestamp\":\"2024-06-17T10:30:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-17T10:15:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-17T10:45:00Z\",\"temperature\":303.15},{\"timestamp\":\"2024-06-18T09:00:00Z\",\"temperature\":297.15},{\"timestamp\":\"2024-06-18T10:00:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-18T11:00:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-18T12:00:00Z\",\"temperature\":302.15},{\"timestamp\":\"2024-06-19T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-19T11:00:00Z\",\"temperature\":299.15}]","payloadType":"json","x":260,"y":200,"wires":[["c58e1653fe5511eb"]]},{"id":"c58e1653fe5511eb","type":"function","z":"977143edb097b685","name":"Filtering, mapping, reducing and sorting data with traditional coding","func":"let sensorData = msg.payload;\n\nconst filteredData = sensorData\n .filter(item => item.timestamp.startsWith(\"2024-06-17\"))\n .map(item => ({\n timestamp: item.timestamp,\n temperature: item.temperature - 273.15\n }));\n\nfilteredData.sort((a, b) => (a.timestamp > b.timestamp) ? 1 : ((b.timestamp > a.timestamp) ? -1 : 0));\n\nconst totalTemperature = filteredData.reduce((acc, entry) => acc + entry.temperature, 0);\nconst averageTemperature = totalTemperature / filteredData.length;\n\nmsg.payload = {\n sensorData: filteredData,\n averageTemperature: averageTemperature\n};\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":200,"wires":[["827c7d2009eeb046"]]},{"id":"827c7d2009eeb046","type":"debug","z":"977143edb097b685","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1060,"y":200,"wires":[]}] -{% endrenderFlow %} +``` +:: Using function nodes isn't wrong, but it adds complexity to your applications, for more information refer to the [Drawbacks of using Fuction nodes](/blog/2023/03/why-should-you-use-node-red-function-nodes/#_5-benefits-of-avoiding-function-nodes) Article. Since not everyone on the team may be familiar with JavaScript, it can limit who can solve business problems. To keep the application flow simple, using a low-code approach to perform these operations is crucial. @@ -113,9 +115,11 @@ Mapping often refers to the process of applying a function to each item in a lis !["Screenshot join node creating new array by combining message sequnce"](../images/filtering-mapping-sorting-reducing-data-with-node-red-join-node-combining-node.png "Screenshot join node creating new array by combining message sequnce"){data-zoomable} -{% renderFlow %} +::render-flow +```json [{"id":"306d455509a3747e","type":"inject","z":"977143edb097b685","name":"Inject the sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[ {\"timestamp\":\"2024-06-17T10:00:00Z\",\"temperature\":298.15}, {\"timestamp\":\"2024-06-17T11:00:00Z\",\"temperature\":299.15}, {\"timestamp\":\"2024-06-17T10:30:00Z\",\"temperature\":300.15}, {\"timestamp\":\"2024-06-17T10:15:00Z\",\"temperature\":301.15}, {\"timestamp\":\"2024-06-17T10:45:00Z\",\"temperature\":303.15}, {\"timestamp\":\"2024-06-18T09:00:00Z\",\"temperature\":297.15}, {\"timestamp\":\"2024-06-18T10:00:00Z\",\"temperature\":300.15}, {\"timestamp\":\"2024-06-18T11:00:00Z\",\"temperature\":301.15}, {\"timestamp\":\"2024-06-18T12:00:00Z\",\"temperature\":302.15}, {\"timestamp\":\"2024-06-19T10:00:00Z\",\"temperature\":298.15}, {\"timestamp\":\"2024-06-19T11:00:00Z\",\"temperature\":299.15} ]","payloadType":"json","x":500,"y":540,"wires":[["9d9c0688468e1aae"]]},{"id":"9d9c0688468e1aae","type":"split","z":"977143edb097b685","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":860,"y":540,"wires":[["785125a70fbdc554"]]},{"id":"785125a70fbdc554","type":"change","z":"977143edb097b685","name":"Converting the temperature data from kelvin to celsius","rules":[{"t":"set","p":"payload.temperature","pt":"msg","to":"payload.temperature - 273.15","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1340,"y":540,"wires":[["9cd6e05b88ec26fd"]]},{"id":"9cd6e05b88ec26fd","type":"join","z":"977143edb097b685","name":"Creating new array by combining message sequence","mode":"auto","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":1820,"y":540,"wires":[["244660d81bf5e5b2"]]},{"id":"244660d81bf5e5b2","type":"debug","z":"977143edb097b685","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":2140,"y":540,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Filtering @@ -127,9 +131,11 @@ Filtering is the process of selecting specific items from an array to create a n !["Screenshot of switch node filtering data bases on timestamp"](../images/filtering-mapping-sorting-reducing-switch-node.png "Screenshot switch node filtering data bases on timestamp"){data-zoomable} -{% renderFlow %} +::render-flow +```json [{"id":"4e76a2328451b4c3","type":"inject","z":"977143edb097b685","name":"Inject the sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"timestamp\":\"2024-06-17T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-17T11:00:00Z\",\"temperature\":299.15},{\"timestamp\":\"2024-06-17T10:30:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-17T10:15:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-17T10:45:00Z\",\"temperature\":303.15},{\"timestamp\":\"2024-06-18T09:00:00Z\",\"temperature\":297.15},{\"timestamp\":\"2024-06-18T10:00:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-18T11:00:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-18T12:00:00Z\",\"temperature\":302.15},{\"timestamp\":\"2024-06-19T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-19T11:00:00Z\",\"temperature\":299.15}]","payloadType":"json","x":380,"y":480,"wires":[["f4e07a31f505a50c"]]},{"id":"f4e07a31f505a50c","type":"split","z":"977143edb097b685","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":680,"y":480,"wires":[["fcd6a0a1497203a9"]]},{"id":"bed1a7d861fa9e3d","type":"join","z":"977143edb097b685","name":"Creating new array by combining message sequence","mode":"auto","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"0","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":1940,"y":480,"wires":[["2449811ec79bc220"]]},{"id":"2449811ec79bc220","type":"debug","z":"977143edb097b685","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2240,"y":480,"wires":[]},{"id":"7797594a508cfb46","type":"switch","z":"977143edb097b685","name":"Routing message sequence based on condition","property":"payload.timestamp","propertyType":"msg","rules":[{"t":"cont","v":"2024-06-17","vt":"str"}],"checkall":"true","repair":true,"outputs":1,"x":1520,"y":480,"wires":[["bed1a7d861fa9e3d"]]},{"id":"fcd6a0a1497203a9","type":"change","z":"977143edb097b685","name":"Converting the temperature data from kelvin to celsius","rules":[{"t":"set","p":"payload.temperature","pt":"msg","to":"payload.temperature - 273.15","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1100,"y":480,"wires":[["7797594a508cfb46"]]}] -{% endrenderFlow %} +``` +:: ## Sorting @@ -141,9 +147,11 @@ To perform sorting, we have to use the Node-RED Sort Node. !["Screenshot of sort node sorting data in ascending order based on timestamp"](../images/filtering-mapping-sorting-reducing-data-with-node-red-sort-node.png "Screenshot of sort node sorting data in ascending order based on timestamp"){data-zoomable} -{% renderFlow %} +::render-flow +```json [{"id":"8b66990baca45f2d","type":"inject","z":"977143edb097b685","name":"Inject the sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"timestamp\":\"2024-06-17T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-17T11:00:00Z\",\"temperature\":299.15},{\"timestamp\":\"2024-06-17T10:30:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-17T10:15:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-17T10:45:00Z\",\"temperature\":303.15},{\"timestamp\":\"2024-06-18T09:00:00Z\",\"temperature\":297.15},{\"timestamp\":\"2024-06-18T10:00:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-18T11:00:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-18T12:00:00Z\",\"temperature\":302.15},{\"timestamp\":\"2024-06-19T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-19T11:00:00Z\",\"temperature\":299.15}]","payloadType":"json","x":400,"y":540,"wires":[["f0d48b57cbd10fdd"]]},{"id":"f0d48b57cbd10fdd","type":"split","z":"977143edb097b685","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":740,"y":540,"wires":[["fdc21a267f7583e7"]]},{"id":"fdc21a267f7583e7","type":"change","z":"977143edb097b685","name":"Correcting the temperature property","rules":[{"t":"set","p":"payload.temperature","pt":"msg","to":"payload.temperature - 273.15","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1080,"y":540,"wires":[["7d4ce2d4e21cd914"]]},{"id":"c273215c8c9cebee","type":"join","z":"977143edb097b685","name":"Creating new array by combining message sequence","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":1860,"y":540,"wires":[["c423efbd0581367a"]]},{"id":"e70d15ced7405755","type":"debug","z":"977143edb097b685","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2480,"y":540,"wires":[]},{"id":"7d4ce2d4e21cd914","type":"switch","z":"977143edb097b685","name":"Routing message sequence based on condition","property":"payload.timestamp","propertyType":"msg","rules":[{"t":"cont","v":"2024-06-17","vt":"str"}],"checkall":"true","repair":true,"outputs":1,"x":1440,"y":540,"wires":[["c273215c8c9cebee"]]},{"id":"c423efbd0581367a","type":"sort","z":"977143edb097b685","name":"Sorting data based on timestamp","order":"ascending","as_num":false,"target":"payload","targetType":"msg","msgKey":"timestamp","msgKeyType":"jsonata","seqKey":"payload.timestamp","seqKeyType":"jsonata","x":2240,"y":540,"wires":[["e70d15ced7405755"]]}] -{% endrenderFlow %} +``` +:: ## Reducing @@ -157,6 +165,8 @@ Reducing refers to the process of combining elements of a data structure (such a In this configuration, the Join node is set to reduce sequence mode. The initial value of the accumulator ($A) is initialized to 0. As each message is processed, the current temperature (payload.temperature) is added to $A. Once all messages have been processed, the accumulated sum $A is divided by the total number of messages ($N) to compute the average temperature. -{% renderFlow %} +::render-flow +```json [{"id":"5ff0902202c21e85","type":"inject","z":"977143edb097b685","name":"Inject the sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"timestamp\":\"2024-06-17T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-17T11:00:00Z\",\"temperature\":299.15},{\"timestamp\":\"2024-06-17T10:30:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-17T10:15:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-17T10:45:00Z\",\"temperature\":303.15},{\"timestamp\":\"2024-06-18T09:00:00Z\",\"temperature\":297.15},{\"timestamp\":\"2024-06-18T10:00:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-18T11:00:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-18T12:00:00Z\",\"temperature\":302.15},{\"timestamp\":\"2024-06-19T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-19T11:00:00Z\",\"temperature\":299.15}]","payloadType":"json","x":580,"y":620,"wires":[["993ffc096c3e8089"]]},{"id":"993ffc096c3e8089","type":"split","z":"977143edb097b685","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":900,"y":620,"wires":[["3bbb68c2dc2a0f5c"]]},{"id":"3bbb68c2dc2a0f5c","type":"change","z":"977143edb097b685","name":"Correcting the temperature property","rules":[{"t":"set","p":"payload.temperature","pt":"msg","to":"payload.temperature - 273.15","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":1280,"y":620,"wires":[["580210c585730f97"]]},{"id":"01e7066b3ff012e7","type":"join","z":"977143edb097b685","name":"Creating new array by combining message sequence","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":2060,"y":620,"wires":[["27cc5d5e90f7facd","6116c1efc3f7f682"]]},{"id":"580210c585730f97","type":"switch","z":"977143edb097b685","name":"Routing message sequence based on condition","property":"payload.timestamp","propertyType":"msg","rules":[{"t":"cont","v":"2024-06-17","vt":"str"}],"checkall":"true","repair":true,"outputs":1,"x":1640,"y":620,"wires":[["01e7066b3ff012e7"]]},{"id":"27cc5d5e90f7facd","type":"sort","z":"977143edb097b685","name":"Sorting data based on timestamp","order":"ascending","as_num":false,"target":"payload","targetType":"msg","msgKey":"timestamp","msgKeyType":"jsonata","seqKey":"payload.timestamp","seqKeyType":"jsonata","x":2440,"y":620,"wires":[["f1f93a7b4575daf1"]]},{"id":"362ec9c482688cf6","type":"debug","z":"977143edb097b685","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":3140,"y":740,"wires":[]},{"id":"b9f2f83a330140ca","type":"join","z":"977143edb097b685","name":"Calculating the the average of temperature","mode":"reduce","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":true,"timeout":"","count":"","reduceRight":false,"reduceExp":"$A+ payload.temperature","reduceInit":"0","reduceInitType":"num","reduceFixup":"$A/$N","x":2890,"y":740,"wires":[["362ec9c482688cf6"]]},{"id":"f1f93a7b4575daf1","type":"debug","z":"977143edb097b685","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":2720,"y":620,"wires":[]},{"id":"6116c1efc3f7f682","type":"split","z":"977143edb097b685","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":2460,"y":740,"wires":[["b9f2f83a330140ca"]]}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/getting-started/programming/debugging-flows.md b/nuxt/content-guides/node-red/getting-started/programming/debugging-flows.md similarity index 90% rename from src/node-red/getting-started/programming/debugging-flows.md rename to nuxt/content-guides/node-red/getting-started/programming/debugging-flows.md index da2f030725..fa70f4aeaa 100644 --- a/src/node-red/getting-started/programming/debugging-flows.md +++ b/nuxt/content-guides/node-red/getting-started/programming/debugging-flows.md @@ -1,16 +1,16 @@ --- -eleventyNavigation: - key: Debugging - parent: Programming +title: How to Debug Node-RED Flows Using Debugger +metaTitle: "How to Debug Node-RED Flows Using Debugger" +navTitle: Debugging +navOrder: 50 meta: - title: How to Debug Node-RED Flows Using Debugger description: Debug Node-RED flows using the Debugger. Learn to set breakpoints, step through execution, and inspect messages for efficient troubleshooting. keywords: node-red-debugger, node red debug, nodered debug, node-red debug, how to debug nodered flows, how to debug node-red flows --- -# {{meta.title}} +# How to Debug Node-RED Flows Using Debugger -When it comes to debugging application flows in Node-RED, the tool most Node-RED developers often reach for is the [Debug](/node-red/core-nodes/debug/) node. It provides a simple way to output message payloads or other data to the debug sidebar, helping you gain insights into how your flow is working. But what if you needed more control and visibility over the flow’s execution? What if you wanted to step through each node in detail, inspect variables, or pause the flow at specific points to understand what’s happening? +When it comes to debugging application flows in Node-RED, the tool most Node-RED developers often reach for is the [Debug](/docs/node-red/core-nodes/common/debug/) node. It provides a simple way to output message payloads or other data to the debug sidebar, helping you gain insights into how your flow is working. But what if you needed more control and visibility over the flow’s execution? What if you wanted to step through each node in detail, inspect variables, or pause the flow at specific points to understand what’s happening? In these cases, using the **Node-RED Debugger** becomes invaluable. The debugger allows you to trace the execution of your flows interactively, set breakpoints, and gain deeper insights beyond what the Debug node offers. This Documentation will show you how to effectively use the Node-RED Debugger to pinpoint issues and fine-tune your applications. @@ -57,9 +57,11 @@ To illustrate how to use the Node-RED Debugger effectively, let’s consider a f 3. Create a new array from the filtered results. 4. Split the array and calculate the average temperature. -{% renderFlow %} +::render-flow +```json [{"id":"3c012808d6b397e2","type":"group","z":"9cf82b68bb89e8ce","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["5ff0902202c21e85","993ffc096c3e8089","3bbb68c2dc2a0f5c","580210c585730f97","362ec9c482688cf6","b9f2f83a330140ca","6116c1efc3f7f682","01e7066b3ff012e7"],"x":394,"y":1899,"w":532,"h":642},{"id":"5ff0902202c21e85","type":"inject","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Inject the sample data","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[{\"timestamp\":\"2024-06-17T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-17T11:00:00Z\",\"temperature\":299.15},{\"timestamp\":\"2024-06-17T10:30:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-17T10:15:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-17T10:45:00Z\",\"temperature\":303.15},{\"timestamp\":\"2024-06-18T09:00:00Z\",\"temperature\":297.15},{\"timestamp\":\"2024-06-18T10:00:00Z\",\"temperature\":300.15},{\"timestamp\":\"2024-06-18T11:00:00Z\",\"temperature\":301.15},{\"timestamp\":\"2024-06-18T12:00:00Z\",\"temperature\":302.15},{\"timestamp\":\"2024-06-19T10:00:00Z\",\"temperature\":298.15},{\"timestamp\":\"2024-06-19T11:00:00Z\",\"temperature\":299.15}]","payloadType":"json","x":540,"y":1940,"wires":[["993ffc096c3e8089"]]},{"id":"993ffc096c3e8089","type":"split","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":640,"y":2020,"wires":[["3bbb68c2dc2a0f5c"]]},{"id":"3bbb68c2dc2a0f5c","type":"change","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Kelvin to celcius","rules":[{"t":"set","p":"payload.temperature","pt":"msg","to":"payload.temperature - 273.15","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":560,"y":2100,"wires":[["580210c585730f97"]]},{"id":"580210c585730f97","type":"switch","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Routing message sequence based on condition","property":"payload.timestamp","propertyType":"msg","rules":[{"t":"cont","v":"2024-06-17","vt":"str"}],"checkall":"true","repair":false,"outputs":1,"x":660,"y":2180,"wires":[["01e7066b3ff012e7"]]},{"id":"362ec9c482688cf6","type":"debug","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":650,"y":2500,"wires":[]},{"id":"b9f2f83a330140ca","type":"join","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Calculating the  the average of temperature","mode":"reduce","build":"object","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","useparts":true,"accumulate":true,"timeout":"","count":"","reduceRight":false,"reduceExp":"$A+ payload.temperature","reduceInit":"0","reduceInitType":"num","reduceFixup":"$A/$N","x":690,"y":2400,"wires":[["362ec9c482688cf6"]]},{"id":"6116c1efc3f7f682","type":"split","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Splits a message into a sequence of messages.","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":700,"y":2340,"wires":[["b9f2f83a330140ca"]]},{"id":"01e7066b3ff012e7","type":"join","z":"9cf82b68bb89e8ce","g":"3c012808d6b397e2","name":"Creating new array by combining message sequence","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","useparts":true,"accumulate":false,"timeout":"","count":"","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"num","reduceFixup":"","x":700,"y":2260,"wires":[["6116c1efc3f7f682"]]}] -{% endrenderFlow %} +``` +:: However, clicking the **Inject** node once does not produce the expected results; instead, it requires clicking again to get the output. This indicates that there might be a timing issue or a logic flaw in the flow that prevents it from processing correctly on the first click. Let's debug the flow with a debugger now. @@ -87,7 +89,7 @@ At the top of the Debugger tab, you will find controls to stop the runtime manua Now, let's diagnose the flow. We’ll manually pause the runtime, then step through each part of the flow using the debugger controls, observing the changes at each step. - + _Video showing the execution of flow while debugger enabled and how to proceed to subsequent execution_ Follow these steps: @@ -103,13 +105,13 @@ Manually stepping through the flow is useful for understanding how the flow oper ## Adding Breakpoints for Debugging Flows -Now that we've pinpointed the problem to be somewhere between the[Switch](/node-red/core-nodes/switch/) node and the [Join](/node-red/core-nodes/join/) node, it’s time to leverage breakpoints for a more efficient debugging experience. These breakpoints allow you to pause the flow automatically allowing you to inspect messages and context without having to step through each node manually. This is especially useful for larger or more intricate flows. +Now that we've pinpointed the problem to be somewhere between the[Switch](/docs/node-red/core-nodes/function/switch/) node and the [Join](/docs/node-red/core-nodes/sequence/join/) node, it’s time to leverage breakpoints for a more efficient debugging experience. These breakpoints allow you to pause the flow automatically allowing you to inspect messages and context without having to step through each node manually. This is especially useful for larger or more intricate flows. First, let’s discuss where exactly we should add breakpoints. Our previous debugging shows that all 11 messages are correctly reaching the input of the Switch node. However, we need to check how many messages pass through the Switch node's condition and whether they contain the required part object for the Join node to create a single value (array). To do this, we should add breakpoints at the output of the Switch node to monitor how many messages pass through, as well as at the input and output of the Join node. This will help us determine how many messages are reaching the input of the Join node and whether they contain the part object necessary for the Join node to automatically convert them into an array of those objects. - + _Video showing how to add breakpoints_ To add a breakpoint: @@ -121,7 +123,7 @@ To add a breakpoint: ### Debugging: Pinpointing the Exact Problem and Solving the Issue in the Flow - + _Video showing the execution of the flow with added breakpoints, indicating the number of each input/output being sent and received for debugging._ Start by clicking the inject node to trigger execution, which will pause at the output of the switch node. Check the blue rectangle to see how many messages have passed through; it shows only a few, not 11, indicating that only those messages met the condition. As you proceed, you will see those messages also reaching the input of the join node correctly. @@ -136,7 +138,7 @@ Now that you’ve learned how to add breakpoints and pinpoint problems, lets loo ### Disabling Breakpoints - + _Video showing two ways of disabling breakpoints_ To disable a breakpoint without removing it: @@ -149,7 +151,7 @@ To disable a breakpoint without removing it: ### Removing Breakpoints - + _Video showing two ways of removing breakpoints_ To remove a breakpoint: diff --git a/src/node-red/getting-started/programming/if-else.md b/nuxt/content-guides/node-red/getting-started/programming/if-else.md similarity index 93% rename from src/node-red/getting-started/programming/if-else.md rename to nuxt/content-guides/node-red/getting-started/programming/if-else.md index 46a809beed..289e2227d5 100644 --- a/src/node-red/getting-started/programming/if-else.md +++ b/nuxt/content-guides/node-red/getting-started/programming/if-else.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: If-Else - parent: Programming +title: How to Use If-Else Logic in Node-RED +metaTitle: "How to Use If-Else Logic in Node-RED" +navTitle: If-Else +navOrder: 50 meta: - title: How to Use If-Else Logic in Node-RED - description: Learn how to implement If-Else logic in Node-RED with our step-by-step guide. Use Function and Switch nodes for dynamic, conditional flows. + description: Learn how to implement If-Else logic in Node-RED with our step-by-step guide. Use Function and Switch nodes for dynamic, conditional flows. keywords: node red if else, nodered switch, node-red conditional flow, node red function if else, node red if then else example, node red switch example, node red switch node example, node red function multiple outputs example, node red function example,node red function if, node red if, node red if node, node red if then -image: /node-red/getting-started/images/if-else-with-node-red.png +image: /docs/node-red/getting-started/images/if-else-with-node-red.png --- -# {{meta.title}} +# How to Use If-Else Logic in Node-RED Human decision-making is often guided by a series of "if this, then that" choices, whether it's deciding what to wear based on the weather or determining the quickest route to work depending on traffic. This kind of logic is equally crucial in systems, especially those built in Node-RED. Just as we make decisions based on various factors, systems must evaluate conditions and choose the appropriate course of action. @@ -36,7 +36,7 @@ To implement If-Else logic in Node-RED, you can use the Switch node, which align ### Using Switch Node -The [Switch](/node-red/core-nodes/switch/) node in Node-RED is used for routing messages based on specific conditions, offering a straightforward, low-code approach to implementing conditional logic in your flows. The Switch node allows you to set up rules using a visual interface, making it ideal for users who prefer a more intuitive method for handling conditions. However, it’s important to note that the Switch node represents a different, independent concept known as the "[switch statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch)." While it serves a similar purpose to If-Else logic by building conditional flows, it operates under its own programming paradigm. +The [Switch](/docs/node-red/core-nodes/function/switch/) node in Node-RED is used for routing messages based on specific conditions, offering a straightforward, low-code approach to implementing conditional logic in your flows. The Switch node allows you to set up rules using a visual interface, making it ideal for users who prefer a more intuitive method for handling conditions. However, it’s important to note that the Switch node represents a different, independent concept known as the "[switch statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/switch)." While it serves a similar purpose to If-Else logic by building conditional flows, it operates under its own programming paradigm. To demonstrate the Switch node, we'll set up a flow to make decisions based on the temperature value. We will route messages through different outputs based on temperature thresholds. @@ -73,13 +73,15 @@ graph TD ``` _Node-RED flow using the Switch node to route messages based on temperature thresholds._ -{% renderFlow %} +::render-flow +```json [{"id":"b90722a28f81c014","type":"group","z":"9cf82b68bb89e8ce","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["dd0d3432c348e1f2","21bd53568877f3b5","5277e184faad375a","7fbf37916236a960","aac1f46169e431ab","3eb24a6129d8fa8e","36e51d42d34c9587"],"x":194,"y":1219,"w":892,"h":322},{"id":"dd0d3432c348e1f2","type":"debug","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"high temperature","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":1260,"wires":[]},{"id":"21bd53568877f3b5","type":"inject","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"Temperature","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random()*100","payloadType":"jsonata","x":310,"y":1380,"wires":[["5277e184faad375a"]]},{"id":"5277e184faad375a","type":"switch","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"30","vt":"num"},{"t":"lte","v":"30","vt":"num"},{"t":"lte","v":"20","vt":"num"},{"t":"lte","v":"10","vt":"num"}],"checkall":"true","repair":false,"outputs":4,"x":530,"y":1380,"wires":[["dd0d3432c348e1f2"],["7fbf37916236a960"],["7fbf37916236a960"],["3eb24a6129d8fa8e"]]},{"id":"7fbf37916236a960","type":"switch","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"","property":"payload","propertyType":"msg","rules":[{"t":"gt","v":"20","vt":"num"},{"t":"gt","v":"10","vt":"num"}],"checkall":"false","repair":false,"outputs":2,"x":670,"y":1380,"wires":[["aac1f46169e431ab"],["36e51d42d34c9587"]]},{"id":"aac1f46169e431ab","type":"debug","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"for medium temperature","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":930,"y":1340,"wires":[]},{"id":"3eb24a6129d8fa8e","type":"debug","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"for very low temperature","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":810,"y":1500,"wires":[]},{"id":"36e51d42d34c9587","type":"debug","z":"9cf82b68bb89e8ce","g":"b90722a28f81c014","name":"for low temperature","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":910,"y":1420,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Using Function Node -The [Function](/node-red/core-nodes/function/) node allows for more complex logic by writing JavaScript. It's suitable when you need more control, or multiple values must be checked together. +The [Function](/docs/node-red/core-nodes/function/function/) node allows for more complex logic by writing JavaScript. It's suitable when you need more control, or multiple values must be checked together. For demonstration purposes, let's use the temperature example where we determine whether to turn the air conditioner on or off based on the temperature: @@ -100,7 +102,7 @@ Before moving further, let's pause and understand what’s happening in the code In Node-RED, `msg.payload` is used to carry data through the flow. Initially, it holds the temperature value injected by the Inject node. The Function node then processes this value using If-Else logic. If the temperature exceeds 30°C, `msg.payload` is set to `"Turn on the air conditioner"`, indicating that the air conditioner should be turned on. If the temperature is 30°C or lower, `msg.payload` is set to `"No action required"`, signaling that the air conditioner should remain off. This updated `msg.payload` is then passed on to the next node, ensuring the system responds appropriately based on the temperature input. -Many people need clarification on the messaging system in Node-RED. For a deeper understanding of how messaging works in Node-RED, I recommend going through this document: [Node-RED Messaging Guide](/node-red/getting-started/node-red-messages/). +Many people need clarification on the messaging system in Node-RED. For a deeper understanding of how messaging works in Node-RED, I recommend going through this document: [Node-RED Messaging Guide](/docs/node-red/getting-started/node-red-messages/). 3. Next, drag the Debug node onto the canvas and connect it to the output of the Function node. This will allow you to see the results of your conditional logic in the Node-RED debug window. 4. Deploy the flow by clicking the "Deploy" button in the top-right corner of the Node-RED editor. @@ -115,9 +117,11 @@ flowchart TD D --> E[End] ``` -{% renderFlow %} +::render-flow +```json [{"id":"51ffa77e55eb7f63","type":"group","z":"9cf82b68bb89e8ce","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["19fa09374c1be7c4","ff41ef215d860c1a","773175cbe8014372"],"x":154,"y":2059,"w":672,"h":82},{"id":"19fa09374c1be7c4","type":"inject","z":"9cf82b68bb89e8ce","g":"51ffa77e55eb7f63","name":"Temperature","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random()*100","payloadType":"jsonata","x":270,"y":2100,"wires":[["ff41ef215d860c1a"]]},{"id":"ff41ef215d860c1a","type":"function","z":"9cf82b68bb89e8ce","g":"51ffa77e55eb7f63","name":"Temperature Threshold Check","func":"let Temperature = msg.payload;\n if (Temperature > 30) {\n msg.payload = \"Turn on the air conditioner\";\n } else {\n msg.payload = \"No action required\";\n }\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":510,"y":2100,"wires":[["773175cbe8014372"]]},{"id":"773175cbe8014372","type":"debug","z":"9cf82b68bb89e8ce","g":"51ffa77e55eb7f63","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":730,"y":2100,"wires":[]}] -{% endrenderFlow %} +``` +:: _Node-RED flow using the Function node to implement simple If-Else logic for temperature control._ @@ -182,9 +186,11 @@ flowchart TD ``` _Node-RED flow using the Function node with multiple outputs for handling various conditions like temperature and humidity._ -{% renderFlow %} +::render-flow +```json [{"id":"301a31b0972b0b20","type":"group","z":"9cf82b68bb89e8ce","style":{"stroke":"#b2b3bd","stroke-opacity":"1","fill":"#f2f3fb","fill-opacity":"0.5","label":true,"label-position":"nw","color":"#32333b"},"nodes":["dbd085ed607e41de","31d2e8f0c871143d","b169bf385ca85f6c","aa7ab6452f4a7791","cc3e6e2c70643f6f","800508e428d74f5f"],"x":114,"y":719,"w":832,"h":202},{"id":"dbd085ed607e41de","type":"inject","z":"9cf82b68bb89e8ce","g":"301a31b0972b0b20","name":"Temperature && Humidity","props":[{"p":"payload.temperature","v":"$random() * 100","vt":"jsonata"},{"p":"payload.humidity","v":"$random() * 100","vt":"jsonata"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":820,"wires":[["31d2e8f0c871143d"]]},{"id":"31d2e8f0c871143d","type":"function","z":"9cf82b68bb89e8ce","g":"301a31b0972b0b20","name":"Temp/Humidity Decision Engine","func":"let Temperature = msg.payload.temperature;\nlet Humidity = msg.payload.humidity;\n\n// Initialize output array\nlet outputs = [null, null, null, null];\n\nif (Temperature > 30 && Humidity < 40) {\n // High temperature and low humidity\n outputs[0] = { payload: \"High temperature and low humidity: Turn on the air conditioner and use a humidifier\" };\n} else if (Temperature > 30 && Humidity >= 40) {\n // High temperature and high humidity\n outputs[1] = { payload: \"High temperature and high humidity: Turn on the air conditioner\" };\n} else if (Temperature < 15 && Humidity < 40) {\n // Low temperature and low humidity\n outputs[2] = { payload: \"Low temperature and low humidity: Turn on the heater and use a humidifier\" };\n} else if (Temperature < 15 && Humidity >= 40) {\n // Low temperature and high humidity\n outputs[3] = { payload: \"Low temperature and high humidity: Turn on the heater\" };\n}\n\nreturn outputs;","outputs":4,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":570,"y":820,"wires":[["b169bf385ca85f6c"],["aa7ab6452f4a7791"],["cc3e6e2c70643f6f"],["800508e428d74f5f"]]},{"id":"b169bf385ca85f6c","type":"debug","z":"9cf82b68bb89e8ce","g":"301a31b0972b0b20","name":"Output 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":840,"y":760,"wires":[]},{"id":"aa7ab6452f4a7791","type":"debug","z":"9cf82b68bb89e8ce","g":"301a31b0972b0b20","name":"Output 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":840,"y":800,"wires":[]},{"id":"cc3e6e2c70643f6f","type":"debug","z":"9cf82b68bb89e8ce","g":"301a31b0972b0b20","name":"Output 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":840,"y":840,"wires":[]},{"id":"800508e428d74f5f","type":"debug","z":"9cf82b68bb89e8ce","g":"301a31b0972b0b20","name":"Output 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":840,"y":880,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Choosing Between the Function Node and Switch Node @@ -194,4 +200,4 @@ The Function node excels in scenarios where complex logic and detailed message p In contrast, the Switch node is designed for simpler, value-based routing. It is ideal for straightforward scenarios where you need to route messages based on a single value with multiple possible outputs. This node enables you to create rules based on specific values or conditions without the need for complex logic or extensive message modifications. If your routing logic involves basic comparisons and does not require advanced processing or calculations, the Switch node provides a more streamlined and intuitive approach. -In summary, choose the Function node for intricate decision-making and detailed message processing, while the Switch node is better suited for scenarios where simple value-based routing is sufficient. \ No newline at end of file +In summary, choose the Function node for intricate decision-making and detailed message processing, while the Switch node is better suited for scenarios where simple value-based routing is sufficient. diff --git a/src/node-red/getting-started/programming/loop.md b/nuxt/content-guides/node-red/getting-started/programming/loop.md similarity index 96% rename from src/node-red/getting-started/programming/loop.md rename to nuxt/content-guides/node-red/getting-started/programming/loop.md index e92552f5e9..837b2fc8bf 100644 --- a/src/node-red/getting-started/programming/loop.md +++ b/nuxt/content-guides/node-red/getting-started/programming/loop.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: Loop - parent: Programming +title: How to implement loops in Node-RED flows +metaTitle: "How to implement loops in Node-RED flows" +navTitle: Loop +navOrder: 50 meta: - title: How to implement loops in Node-RED flows description: Learn how to implement while, for, and for...of loops in Node-RED with core and custom nodes for efficient data processing and automation. keywords: node red loop, nodered loop, node red for loop, node-red loop, nodered for of, node-red while loop, node-red do while loop --- -# {{meta.title}} +# How to implement loops in Node-RED flows Handling repetitive tasks is a common challenge in automation and data processing. Whether you need to iterate over large datasets, perform calculations, or execute operations based on conditions multiple times, using loops can significantly enhance efficiency and scalability. @@ -61,9 +61,11 @@ graph TD F --> G["Debug Node
Display Current String"] ``` -{% renderFlow %} +::render-flow +```json [{"id":"e90dc2e50e40896c","type":"group","z":"a3aa840957f658c6","name":"While Loop","style":{"label":true},"nodes":["cf6ebf02d00eaca6","696d89aa4050cd13","0e2d9f0447cf6226","bb6074cdf4398cb6","e19304ab31199315","c467509c666ee400","06bb08e3f0d7888a"],"x":34,"y":139,"w":1092,"h":242},{"id":"cf6ebf02d00eaca6","type":"switch","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"Does the msg.i contains Z","property":"i","propertyType":"msg","rules":[{"t":"cont","v":"Z","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":590,"y":240,"wires":[["c467509c666ee400"],["0e2d9f0447cf6226","06bb08e3f0d7888a"]]},{"id":"696d89aa4050cd13","type":"debug","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"End","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1030,"y":180,"wires":[]},{"id":"0e2d9f0447cf6226","type":"debug","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"Output \"i\"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"i","targetType":"msg","statusVal":"","statusType":"auto","x":900,"y":260,"wires":[]},{"id":"bb6074cdf4398cb6","type":"inject","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"Start","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"[\"hello\",\"run\",\"why\"]","payloadType":"json","x":130,"y":240,"wires":[["e19304ab31199315"]]},{"id":"e19304ab31199315","type":"change","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"initilized i with empty string","rules":[{"t":"set","p":"i","pt":"msg","to":"","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":320,"y":240,"wires":[["cf6ebf02d00eaca6"]]},{"id":"c467509c666ee400","type":"change","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"if not Set payload to \"completed\"","rules":[{"t":"set","p":"payload","pt":"msg","to":"Completed","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":790,"y":180,"wires":[["696d89aa4050cd13"]]},{"id":"06bb08e3f0d7888a","type":"function","z":"a3aa840957f658c6","g":"e90dc2e50e40896c","name":"Append Random Letter","func":"// add ASCII char from 32 to 126\nmsg.i += String.fromCharCode(Math.random()*26 + 65);\nreturn msg;","outputs":1,"timeout":"","noerr":0,"initialize":"","finalize":"","libs":[],"x":590,"y":340,"wires":[["cf6ebf02d00eaca6"]]}] -{% endrenderFlow %} +``` +:: #### For Loop @@ -94,13 +96,15 @@ graph TD G --> H["Debug Node
Display Completion Message"] ``` -{% renderFlow %} +::render-flow +```json [{"id":"3b546e0612673478","type":"group","z":"a3aa840957f658c6","name":"ForLoop","style":{"label":true},"nodes":["8ee16c1d06fca9fe","64a4a7329631d7ce","e67a34285a7e6979","e2074cf01ed451fd","811cedbe7cb89415","8bbccc2d10c36bfe","826d819a06810184","e7ebf7f66cda1370"],"x":14,"y":159,"w":1292,"h":242},{"id":"8ee16c1d06fca9fe","type":"switch","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"if msg.i == msg.payload.length","property":"i","propertyType":"msg","rules":[{"t":"eq","v":"payload.length","vt":"msg"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":550,"y":280,"wires":[["8bbccc2d10c36bfe"],["e7ebf7f66cda1370","826d819a06810184"]]},{"id":"64a4a7329631d7ce","type":"debug","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"End","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1210,"y":200,"wires":[]},{"id":"e67a34285a7e6979","type":"debug","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"Output \"i\"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1080,"y":340,"wires":[]},{"id":"e2074cf01ed451fd","type":"inject","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"Start","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":"","topic":"","payload":"[\"hello\",\"run\",\"why\"]","payloadType":"json","x":110,"y":280,"wires":[["811cedbe7cb89415"]]},{"id":"811cedbe7cb89415","type":"change","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"initilized i with 0","rules":[{"t":"set","p":"i","pt":"msg","to":"0","tot":"num"}],"action":"","property":"","from":"","to":"","reg":false,"x":280,"y":280,"wires":[["8ee16c1d06fca9fe"]]},{"id":"8bbccc2d10c36bfe","type":"change","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"If 'i' equals array length, set payload to \"completed\".","rules":[{"t":"set","p":"payload","pt":"msg","to":"Completed","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":200,"wires":[["64a4a7329631d7ce"]]},{"id":"826d819a06810184","type":"change","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"Access array element with i","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[msg.i]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":860,"y":340,"wires":[["e67a34285a7e6979"]]},{"id":"e7ebf7f66cda1370","type":"change","z":"a3aa840957f658c6","g":"3b546e0612673478","name":"increment i by 1","rules":[{"t":"set","p":"i","pt":"msg","to":"i+1","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":360,"wires":[["8ee16c1d06fca9fe"]]}] -{% endrenderFlow %} +``` +:: #### For...of / ForEach Loop -In traditional programming, `for...of` and `forEach` loops are commonly used to iterate through arrays or object properties, allowing for individual element processing. Since Node-RED doesn’t include these specific constructs, you can replicate their functionality by using a combination of nodes, particularly the **[Split](/node-red/core-nodes/split/)** and **[Join](/node-red/core-nodes/join/)** nodes. +In traditional programming, `for...of` and `forEach` loops are commonly used to iterate through arrays or object properties, allowing for individual element processing. Since Node-RED doesn’t include these specific constructs, you can replicate their functionality by using a combination of nodes, particularly the **[Split](/docs/node-red/core-nodes/sequence/split/)** and **[Join](/docs/node-red/core-nodes/sequence/join/)** nodes. Here’s how you can replicate this functionality in Node-RED: @@ -111,7 +115,7 @@ Here’s how you can replicate this functionality in Node-RED: When you click the **Inject** button, the **Split** node will process each element of the array individually, and the **Debug** node will display each one in the debug window. -To explore how you can map, sort, filter, and reduce data using this approach, check out our guide: [How to Filter, Map, Sort, and Reduce Data in Node-RED](/node-red/getting-started/programming/data-tranformation/). +To explore how you can map, sort, filter, and reduce data using this approach, check out our guide: [How to Filter, Map, Sort, and Reduce Data in Node-RED](/docs/node-red/getting-started/programming/data-tranformation/). ```mermaid graph TD @@ -123,13 +127,15 @@ graph TD C["Debug Node\nDisplays each element"] ``` -{% renderFlow %} +::render-flow +```json [{"id":"2e67b2739f364a71","type":"group","z":"a3aa840957f658c6","style":{"stroke":"#999999","stroke-opacity":"1","fill":"none","fill-opacity":"1","label":true,"label-position":"nw","color":"#a4a4a4"},"nodes":["2add2705d262010d","40333c736844a0c1","3337ba024a363a15","2f7d2aab12b3b4e9"],"x":254,"y":219,"w":532,"h":162},{"id":"2add2705d262010d","type":"inject","z":"a3aa840957f658c6","g":"2e67b2739f364a71","name":"Array","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"foo\",\"bar\",\"foobar\"]","payloadType":"json","x":350,"y":340,"wires":[["40333c736844a0c1"]]},{"id":"40333c736844a0c1","type":"split","z":"a3aa840957f658c6","g":"2e67b2739f364a71","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":530,"y":320,"wires":[["3337ba024a363a15"]]},{"id":"3337ba024a363a15","type":"debug","z":"a3aa840957f658c6","g":"2e67b2739f364a71","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":690,"y":320,"wires":[]},{"id":"2f7d2aab12b3b4e9","type":"inject","z":"a3aa840957f658c6","g":"2e67b2739f364a71","name":"Object","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"first\":\"Hello World\",\"second\":8,\"third\":true}","payloadType":"json","x":350,"y":260,"wires":[["40333c736844a0c1"]]}] -{% endrenderFlow %} +``` +:: #### Implementing Loops with the Function Node -Implementing loops with the **[Function](/node-red/core-nodes/function/)** node is straightforward if you're familiar with JavaScript, as it allows you to write custom code. However, a common issue is figuring out how to send a message on each iteration without ending the loop after the first iteration. In this section, we’ll show you how to implement loops in the `Function` node correctly, ensuring that each iteration is processed and sent out properly without prematurely breaking the loop. +Implementing loops with the **[Function](/docs/node-red/core-nodes/function/function/)** node is straightforward if you're familiar with JavaScript, as it allows you to write custom code. However, a common issue is figuring out how to send a message on each iteration without ending the loop after the first iteration. In this section, we’ll show you how to implement loops in the `Function` node correctly, ensuring that each iteration is processed and sent out properly without prematurely breaking the loop. For demonstration purposes, we will implement a `for` loop. @@ -149,9 +155,11 @@ When you deploy the flow and click the **Inject** button, each item in the array By using `node.send()` inside the loop, you ensure that each iteration produces a separate message, and the Function node can handle multiple messages efficiently. For more information on on this, refer to [Documentation on Sending messages asynchronously](https://nodered.org/docs/user-guide/writing-functions#sending-messages-asynchronously). -{% renderFlow %} +::render-flow +```json [{"id":"50be2bac3b058be5","type":"group","z":"a3aa840957f658c6","name":"","style":{"label":true},"nodes":["97c288f32955c6ab","7a8404c44ac0749b","0d7b9cb51669ad9b"],"x":414,"y":499,"w":612,"h":82},{"id":"97c288f32955c6ab","type":"inject","z":"a3aa840957f658c6","g":"50be2bac3b058be5","name":"Array","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[1, \"hello\", \"%\", true]","payloadType":"json","x":510,"y":540,"wires":[["7a8404c44ac0749b"]]},{"id":"7a8404c44ac0749b","type":"function","z":"a3aa840957f658c6","g":"50be2bac3b058be5","name":"For Loop","func":"for (let i = 0; i < msg.payload.length; i++) {\n // Create a new message for each item\n let newMsg = { ...msg }; // Copy the original msg object\n newMsg.payload = msg.payload[i]; // Set payload to the current item\n node.send(newMsg)\n}","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":700,"y":540,"wires":[["0d7b9cb51669ad9b"]]},{"id":"0d7b9cb51669ad9b","type":"debug","z":"a3aa840957f658c6","g":"50be2bac3b058be5","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":920,"y":540,"wires":[]}] -{% endrenderFlow %} +``` +:: ### Implementing Loops in Node-RED with Custom node @@ -177,9 +185,11 @@ Throughtout this section we will show you how you can implement loops in Node-RE 6. Drag a **Debug** node onto the canvas and connect its input to the output of the **Function** node. This will print the current `msg.payload` after each iteration. 7. Drag another **Debug** node onto the canvas and connect its input to the first output of the **Loop** node. This will print when the loop exits, indicating that the condition has been met. -{% renderFlow %} +::render-flow +```json [{"id":"7ef4d41cf74f75c3","type":"group","z":"a3aa840957f658c6","name":"While Loop","style":{"label":true},"nodes":["3ba931b1.fb48d6","2d297dfa.6e660a","5aef28e.0f9e7d8","90c49008.053a58","9a725668.330148","e88b0e8c.d39858","9c48f336.471ea","66144af4.d1ec9c","75c6b326.e934d4","77ac6763.8e54b8","99b265b4.e2d3c8"],"x":174,"y":139,"w":672,"h":322},{"id":"3ba931b1.fb48d6","type":"loop","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"","kind":"cond","count":"10","initial":"1","step":"1","condition":"msg.payload.includes(\"Z\") != true","conditionType":"js","when":"after","enumeration":"enum","enumerationType":"msg","limit":"","loopPayload":"loop-keep","finalPayload":"final-last","x":500,"y":280,"wires":[["5aef28e.0f9e7d8"],["e88b0e8c.d39858"]]},{"id":"2d297dfa.6e660a","type":"inject","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"str","x":270,"y":280,"wires":[["3ba931b1.fb48d6"]]},{"id":"5aef28e.0f9e7d8","type":"debug","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"loop","targetType":"msg","statusVal":"","statusType":"auto","x":720,"y":280,"wires":[]},{"id":"90c49008.053a58","type":"comment","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"Example: Conditional loop","info":"","x":310,"y":180,"wires":[]},{"id":"9a725668.330148","type":"comment","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"Show final status","info":"","x":740,"y":240,"wires":[]},{"id":"e88b0e8c.d39858","type":"function","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"","func":"// add ASCII char from 32 to 126\nmsg.payload += String.fromCharCode(Math.random()*26 + 65);\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","x":340,"y":380,"wires":[["66144af4.d1ec9c","9c48f336.471ea"]]},{"id":"9c48f336.471ea","type":"debug","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":730,"y":380,"wires":[]},{"id":"66144af4.d1ec9c","type":"delay","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"","pauseType":"delay","timeout":"0.5","timeoutUnits":"milliseconds","rate":"1","nbRateUnits":"1","rateUnits":"second","randomFirst":"1","randomLast":"5","randomUnits":"seconds","drop":false,"allowrate":false,"outputs":1,"x":530,"y":355,"wires":[["3ba931b1.fb48d6"]]},{"id":"75c6b326.e934d4","type":"comment","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"Show string","info":"","x":730,"y":420,"wires":[]},{"id":"77ac6763.8e54b8","type":"comment","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"Add char to string","info":"","x":370,"y":420,"wires":[]},{"id":"99b265b4.e2d3c8","type":"comment","z":"a3aa840957f658c6","g":"7ef4d41cf74f75c3","name":"Repeat until string doesn't finish with \"Z\"","info":"","x":480,"y":240,"wires":[]}] -{% endrenderFlow %} +``` +:: #### For Loop @@ -190,9 +200,11 @@ Throughtout this section we will show you how you can implement loops in Node-RE 5. Then, drag another **Change** node onto the canvas. Configure this node to set `msg.payload` to `msg.payload[msg.loop.value]`, which extracts the current array element using the loop’s counter (`msg.loop.value`). The **Loop** node generates properties like `value`, which is the counter we are incrementing. 6. Finally, drag a **Debug** node onto the canvas and connect it to the output of the previous **Change** node to print the current array element in each iteration. -{% renderFlow %} +::render-flow +```json [{"id":"ac4bfead30be7380","type":"group","z":"a3aa840957f658c6","name":"For Loop","style":{"label":true},"nodes":["2ffc3d07.23e6fa","6e1118b1.449db8","ea852667d724cfb1","880a2501f7d153ba","bd58a956.c036b","60a8f138.0909a","44f1a29403157bba"],"x":74,"y":199,"w":1132,"h":202},{"id":"2ffc3d07.23e6fa","type":"inject","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"foo\",\"bar\",\"foobar\"]","payloadType":"json","x":210,"y":260,"wires":[["880a2501f7d153ba"]]},{"id":"6e1118b1.449db8","type":"loop","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"","kind":"fcnt","count":"","initial":"0","step":"1","condition":"","conditionType":"js","when":"before","enumeration":"enum","enumerationType":"msg","limit":"","loopPayload":"loop-orig","finalPayload":"final-last","x":710,"y":260,"wires":[["44f1a29403157bba"],["bd58a956.c036b"]]},{"id":"ea852667d724cfb1","type":"change","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"Access array element with loop counter","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[msg.loop.value]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":840,"y":360,"wires":[["60a8f138.0909a"]]},{"id":"880a2501f7d153ba","type":"change","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"Set Pass Count","rules":[{"t":"set","p":"count","pt":"msg","to":"payload.length","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":440,"y":260,"wires":[["6e1118b1.449db8"]]},{"id":"bd58a956.c036b","type":"change","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"Repeat","rules":[],"action":"","property":"","from":"","to":"","reg":false,"x":520,"y":360,"wires":[["6e1118b1.449db8","ea852667d724cfb1"]]},{"id":"60a8f138.0909a","type":"debug","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1090,"y":360,"wires":[]},{"id":"44f1a29403157bba","type":"debug","z":"a3aa840957f658c6","g":"ac4bfead30be7380","name":"End Loop","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":1000,"y":240,"wires":[]}] -{% endrenderFlow %} +``` +:: #### For...of / ForEach Loop @@ -203,6 +215,8 @@ Throughtout this section we will show you how you can implement loops in Node-RE With the **Enumeration** kind, you can iterate through different types of data such as arrays, strings, objects, and more, making this loop versatile for handling various data structures. -{% renderFlow %} +::render-flow +```json [{"id":"65d854c9098393e8","type":"group","z":"a3aa840957f658c6","name":"For of/ for each","style":{"label":true},"nodes":["38630ff1.21721","f009e0e9.24576","cf52b6dd.5febb8","33031c0e.1b115c","b28b06f1.38eb48","6e593ff2.dc94c"],"x":334,"y":219,"w":722,"h":222},{"id":"38630ff1.21721","type":"inject","z":"a3aa840957f658c6","g":"65d854c9098393e8","name":"Object","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"first\":\"Hello World\",\"second\":8,\"third\":true}","payloadType":"json","x":430,"y":260,"wires":[["f009e0e9.24576"]]},{"id":"f009e0e9.24576","type":"loop","z":"a3aa840957f658c6","g":"65d854c9098393e8","name":"","kind":"enum","count":"","initial":"","step":"","condition":"","conditionType":"js","when":"before","enumeration":"payload","enumerationType":"msg","limit":"","loopPayload":"loop-val","finalPayload":"final-orig","x":740,"y":300,"wires":[["cf52b6dd.5febb8"],["33031c0e.1b115c","6e593ff2.dc94c"]]},{"id":"cf52b6dd.5febb8","type":"debug","z":"a3aa840957f658c6","g":"65d854c9098393e8","name":"Loop End","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"loop","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":280,"wires":[]},{"id":"33031c0e.1b115c","type":"change","z":"a3aa840957f658c6","g":"65d854c9098393e8","name":"Repeat","rules":[],"action":"","property":"","from":"","to":"","reg":false,"x":730,"y":400,"wires":[["f009e0e9.24576"]]},{"id":"b28b06f1.38eb48","type":"inject","z":"a3aa840957f658c6","g":"65d854c9098393e8","name":"Array","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"[\"foo\",\"bar\",\"foobar\"]","payloadType":"json","x":430,"y":320,"wires":[["f009e0e9.24576"]]},{"id":"6e593ff2.dc94c","type":"debug","z":"a3aa840957f658c6","g":"65d854c9098393e8","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":940,"y":400,"wires":[]}] -{% endrenderFlow %} \ No newline at end of file +``` +:: diff --git a/src/node-red/getting-started/string.md b/nuxt/content-guides/node-red/getting-started/string.md similarity index 94% rename from src/node-red/getting-started/string.md rename to nuxt/content-guides/node-red/getting-started/string.md index 399fb01cf3..3f3348e49d 100644 --- a/src/node-red/getting-started/string.md +++ b/nuxt/content-guides/node-red/getting-started/string.md @@ -1,16 +1,13 @@ --- +title: 'Strings in Node-RED: Convert String to Number, Split, Concatenate, Trim, and More' metaTitle: "Strings in Node-RED: Convert, Split & Trim" -eleventyNavigation: - key: String - order: 6 - parent: Getting Started +navTitle: String +navOrder: 6 meta: - title: "Strings in Node-RED: Convert String to Number, Split, Concatenate, Trim, and More" description: Learn essential string operations in Node-RED including converting between strings and numbers, splitting and concatenating text, parsing JSON, extracting substrings, trimming whitespace, and more. Step-by-step guide with practical examples. keywords: node-red strings, working with strings in node-red, node-red string operations, node-red string manipulation, node-red text processing, node-red change node strings, node-red jsonata string functions, node-red template node strings, node-red function node strings, node-red parsing strings, node-red string conversion, node-red split string, node-red concatenate strings --- - -# {{ meta.title }} +# Strings in Node-RED: Convert String to Number, Split, Concatenate, Trim, and More Strings are one of the most common data types in Node-RED. Whether you're converting sensor values, parsing API responses, or building dynamic messages, understanding string operations is essential for building reliable flows. @@ -26,9 +23,11 @@ One of the most frequent operations is converting string values to numbers for m If your payload contains the string `"42"`, it becomes the number `42`. You can now use this in calculations or comparisons. -{% renderFlow %} +::render-flow +```json [{"id":"2ac68ed1d9a7b380","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"42","payloadType":"str","x":530,"y":200,"wires":[["a14a67ac6574ae82"]]},{"id":"a14a67ac6574ae82","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"String to Number","rules":[{"t":"set","p":"payload","pt":"msg","to":"$number(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":200,"wires":[["225f4c6a47df2d3c"]]},{"id":"225f4c6a47df2d3c","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":200,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Converting Number to String @@ -42,9 +41,11 @@ Converting numbers to strings is useful for displaying values, building messages A number like `42` becomes the string `"42"`, ready for text operations. -{% renderFlow %} +::render-flow +```json [{"id":"38b9110190ddba53","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"42","payloadType":"num","x":530,"y":260,"wires":[["06a4b6591a791f8d"]]},{"id":"06a4b6591a791f8d","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Number to String","rules":[{"t":"set","p":"payload","pt":"msg","to":"$string(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":710,"y":260,"wires":[["c560426627a56137"]]},{"id":"c560426627a56137","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":260,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Splitting Strings @@ -64,9 +65,11 @@ Splitting strings is essential when parsing CSV data, breaking apart delimited v The Split node creates separate messages for each segment. For example, if you receive a log entry `"2024-12-15 14:30:45 ERROR Database connection failed"` and you split using spaces, it produces separate messages: first `"2024-12-15"`, then `"14:30:45"`, then `"ERROR"`, then `"Database"`, and so on. Each piece flows through your subsequent nodes one at a time, allowing you to extract the timestamp, severity level, and message separately. -{% renderFlow %} +::render-flow +```json [{"id":"6a5c13d2e5bf152f","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"2024-12-15 14:30:45 ERROR Database connection failed","payloadType":"str","x":530,"y":320,"wires":[["4d3e4c715e94572b"]]},{"id":"9e90b663c08067fc","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":320,"wires":[]},{"id":"4d3e4c715e94572b","type":"split","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Split String","splt":" ","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","property":"payload","x":690,"y":320,"wires":[["9e90b663c08067fc"]]}] -{% endrenderFlow %} +``` +:: ## Concatenating Strings @@ -74,15 +77,17 @@ Combining strings is common when building messages, URLs, or formatted output. 1. Add a **Template** node after the nodes containing your data 2. Double-click to open the Template configuration -3. Write your text and insert variables using {% raw %}`{{variableName}}`{% endraw %} syntax +3. Write your text and insert variables using `{{variableName}}` syntax 4. Click **Done** 5. Connect to where you need the processed data -Each {% raw %}`{{variableName}}`{% endraw %} is replaced with actual data. For example, the template {% raw %}`Hello {{payload.name}}, your order #{{payload.orderId}} has shipped to {{payload.city}}.`{% endraw %} with data containing name "Sarah", orderId "12345", and city "Portland" produces: `Hello Sarah, your order #12345 has shipped to Portland.` +Each `{{variableName}}` is replaced with actual data. For example, the template `Hello {{payload.name}}, your order #{{payload.orderId}} has shipped to {{payload.city}}.` with data containing name "Sarah", orderId "12345", and city "Portland" produces: `Hello Sarah, your order #12345 has shipped to Portland.` -{% renderFlow %} +::render-flow +```json [{"id":"60cb0a1d79b095a3","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"name\":\"Sarah\",\"orderId\":\"12345\",\"city\":\"Portland\"}","payloadType":"json","x":530,"y":380,"wires":[["88d113db79d20417"]]},{"id":"9ed0bd64daa0cf35","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":380,"wires":[]},{"id":"88d113db79d20417","type":"template","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Concatenating Strings","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"Hello {{payload.name}}, your order #{{payload.orderId}} has shipped to {{payload.city}}.","output":"str","x":720,"y":380,"wires":[["9ed0bd64daa0cf35"]]}] -{% endrenderFlow %} +``` +:: ## Parsing JSON Strings @@ -96,9 +101,11 @@ API responses and stored data often arrive as JSON strings, text that looks like The JSON node detects your data type automatically. String `'{"temperature":22,"humidity":65}'` becomes an object `{temperature: 22, humidity: 65}` so you can access `msg.payload.temperature`. -{% renderFlow %} +::render-flow +```json [{"id":"987d6b5ce96f863e","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"temperature\":22,\"humidity\":65}","payloadType":"str","x":530,"y":440,"wires":[["b92eb17c8dac49d8"]]},{"id":"b92eb17c8dac49d8","type":"json","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Parsing JSON Strings","property":"payload","action":"","pretty":false,"x":720,"y":440,"wires":[["a34030e2d9a085fe"]]},{"id":"a34030e2d9a085fe","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":440,"wires":[]}] -{% endrenderFlow %} +``` +:: ## Extracting Substrings @@ -117,9 +124,11 @@ Getting specific parts of a string is useful for parsing fixed-format data, extr - `$substring(payload, 6, 5)` on `"Hello World"` gives `"World"` - `$substring(payload, 6)` (no length) on `"Hello World"` gives `"World"` (all remaining characters) -{% renderFlow %} +::render-flow +```json [{"id":"1505bbcdda1ef70f","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello World","payloadType":"str","x":510,"y":500,"wires":[["7e13b90c9b8e48de"]]},{"id":"7b985161f48aaf74","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":500,"wires":[]},{"id":"7e13b90c9b8e48de","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Extracting Substrings","rules":[{"t":"set","p":"payload","pt":"msg","to":"$substring(payload, 0, 5)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":500,"wires":[["7b985161f48aaf74"]]}] -{% endrenderFlow %} +``` +:: ## Trimming Whitespace @@ -133,9 +142,11 @@ Removing unwanted spaces, tabs, or line breaks from strings prevents comparison Whitespace from both ends is removed. `" Hello World "` becomes `"Hello World"`. The space between words stays, only edge spaces are removed. -{% renderFlow %} +::render-flow +```json [{"id":"f43fff4b7a32185d","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":" Hello World ","payloadType":"str","x":510,"y":560,"wires":[["9ed2f66a591cefab"]]},{"id":"603d7024f06979bc","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":560,"wires":[]},{"id":"9ed2f66a591cefab","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Trimming Whitespace","rules":[{"t":"set","p":"payload","pt":"msg","to":"$trim(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":720,"y":560,"wires":[["603d7024f06979bc"]]}] -{% endrenderFlow %} +``` +:: ## Changing Case @@ -149,13 +160,17 @@ Converting string case helps with standardization and comparison since computers Using `$uppercase(payload)`, the string `"hello world"` becomes `"HELLO WORLD"`. Using `$lowercase(payload)`, the string `"Hello World"` becomes `"hello world"`. -{% renderFlow %} +::render-flow +```json [{"id":"9b39fb9d0a75e5c2","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello World","payloadType":"str","x":510,"y":620,"wires":[["04389bee4b520eb3"]]},{"id":"3390eac6826821a7","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":620,"wires":[]},{"id":"04389bee4b520eb3","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Changing Case : Lowercase","rules":[{"t":"set","p":"payload","pt":"msg","to":"$lowercase(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":620,"wires":[["3390eac6826821a7"]]}] -{% endrenderFlow %} +``` +:: -{% renderFlow %} +::render-flow +```json [{"id":"2bee50ed8273a7d7","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":680,"wires":[]},{"id":"b2d16a456b083325","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Changing Case : Uppercase","rules":[{"t":"set","p":"payload","pt":"msg","to":"$uppercase(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":740,"y":680,"wires":[["2bee50ed8273a7d7"]]},{"id":"2f20f09b8146b397","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello World","payloadType":"str","x":510,"y":680,"wires":[["b2d16a456b083325"]]}] -{% endrenderFlow %} +``` +:: ## Replacing Text @@ -169,9 +184,11 @@ Finding and replacing text within strings lets you correct values, standardize f All occurrences are replaced. `"I love apples and apples are great"` with `$replace(payload, "apples", "oranges")` gives `"I love oranges and oranges are great"`. -{% renderFlow %} +::render-flow +```json [{"id":"dfbb9d1ce41c8672","type":"debug","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":950,"y":740,"wires":[]},{"id":"33d81d6a0d2d0f0c","type":"change","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"Replacing Text","rules":[{"t":"set","p":"payload","pt":"msg","to":"$replace(payload, \"apples\", \"oranges\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":700,"y":740,"wires":[["dfbb9d1ce41c8672"]]},{"id":"a8e1a932d619fa33","type":"inject","z":"c16e1fb8932e7e73","g":"09a33e651efa47a8","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"I love apples and apples are great","payloadType":"str","x":530,"y":740,"wires":[["33d81d6a0d2d0f0c"]]}] -{% endrenderFlow %} +``` +:: ## Checking String Length @@ -185,9 +202,11 @@ Determining string length helps with validation or conditional processing. The string `"Hello"` returns `5`. Use this value in conditions or validation logic. -{% renderFlow %} +::render-flow +```json [{"id":"3791a3fac7be4833","type":"debug","z":"b446dfa04d79d359","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"length","targetType":"msg","statusVal":"","statusType":"auto","x":1110,"y":1260,"wires":[]},{"id":"fca2213c932b1fea","type":"change","z":"b446dfa04d79d359","name":"Checking String Length","rules":[{"t":"set","p":"length","pt":"msg","to":"$length(payload)","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":910,"y":1260,"wires":[["3791a3fac7be4833"]]},{"id":"706d864345a0c62c","type":"inject","z":"b446dfa04d79d359","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello","payloadType":"str","x":710,"y":1260,"wires":[["fca2213c932b1fea"]]}] -{% endrenderFlow %} +``` +:: ## Checking if String Contains Text @@ -201,9 +220,11 @@ Testing whether a string contains specific text helps with filtering and conditi Returns `true` if found, `false` if not. `"The quick brown fox"` with `$contains(payload, "quick")` returns `true`. Use in Switch nodes to route messages differently based on content. -{% renderFlow %} +::render-flow +```json [{"id":"eb5fe8fe6e00e603","type":"debug","z":"b446dfa04d79d359","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":710,"y":1740,"wires":[]},{"id":"daba355f3ca9693f","type":"change","z":"b446dfa04d79d359","name":"Checking if String Contains Text","rules":[{"t":"set","p":"contains","pt":"msg","to":"$contains(payload, \"quick\")","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":1740,"wires":[["eb5fe8fe6e00e603"]]},{"id":"ca2ab085f8edec8c","type":"inject","z":"b446dfa04d79d359","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"The quick brown fox","payloadType":"str","x":270,"y":1740,"wires":[["daba355f3ca9693f"]]}] -{% endrenderFlow %} +``` +:: ## Complex String Operations diff --git a/src/node-red/getting-started/update-node-red.md b/nuxt/content-guides/node-red/getting-started/update-node-red.md similarity index 88% rename from src/node-red/getting-started/update-node-red.md rename to nuxt/content-guides/node-red/getting-started/update-node-red.md index 260c1d9302..bd16b4bc70 100644 --- a/src/node-red/getting-started/update-node-red.md +++ b/nuxt/content-guides/node-red/getting-started/update-node-red.md @@ -1,23 +1,19 @@ --- -eleventyNavigation: - key: Update Node-RED - order: 3 - parent: Getting Started +title: How to Update Node-RED +metaTitle: "How to Update Node-RED" +navTitle: Update Node-RED +navOrder: 3 meta: - title: How to Update Node-RED description: Learn how to update Node-RED across different installation methods including npm, Raspberry Pi, Docker, and FlowFuse keywords: Node-RED update, upgrade Node-RED, Node-RED version, update npm Node-RED --- -# {{meta.title}} +# How to Update Node-RED Regular updates keep your Node-RED installation running smoothly with the latest features, improvements, and bug fixes. Each new release brings enhancements that expand what you can build and improve your development experience. Whether you installed Node-RED through npm, used the Raspberry Pi script, or are running it in Docker, this guide walks you through the update process step by step. We'll also cover how to check your version, update your installation, and handle updates in FlowFuse. - - Update hundreds of Node-RED instances in one click, not one at a time - +::cta-image{src="/docs/node-red/getting-started/images/update-node-red-cta-1.png" alt="Update hundreds of Node-RED instances in one click, not one at a time" cta="sign-up" reference="Node-RED: How to Update Node-RED"} +:: ## Checking Your Current Version @@ -133,4 +129,4 @@ Instead of updating instances manually, you can schedule automatic updates in fl ![FlowFuse scheduled update configuration](./images/schedule-update.png){data-zoomable} -FlowFuse will then automatically update your Node-RED instances within the defined maintenance window, ensuring they stay up to date without manual intervention. \ No newline at end of file +FlowFuse will then automatically update your Node-RED instances within the defined maintenance window, ensuring they stay up to date without manual intervention. diff --git a/nuxt/content-guides/node-red/hardware/README.md b/nuxt/content-guides/node-red/hardware/README.md new file mode 100644 index 0000000000..42cecdf2ac --- /dev/null +++ b/nuxt/content-guides/node-red/hardware/README.md @@ -0,0 +1,26 @@ +--- +title: Setting Up Node-RED on Different Hardware +metaTitle: "Setting Up Node-RED on Different Hardware" +navTitle: Hardware +navOrder: 8 +meta: + description: Learn how to set up Node-RED on various hardware platforms, such as Raspberry Pi, Arduino, and more. + keywords: node-red, node-red hardware setup, raspberry pi, arduino, siemens iot2050, +--- + +# Setting Up Node-RED on Different Hardware + +Node-RED is highly versatile and can be set up on a wide range of hardware devices, including popular choices like Raspberry Pi, Ardiuno, Siemens 2050, and more. This flexibility allows you to connect Node-RED to diverse devices and sensors, which enables the creation of interactive and automated systems. + +By setting up Node-RED on different hardware, you can easily integrate physical inputs with digital systems. This capability is essential for IoT (Internet of Things) applications, where data from sensors and devices can trigger actions or responses in real-time. + +## Resources + +Here are some resources to help you get started with Node-RED on diffrent hardware devices: + +- [Run Node-RED on Siemens IoT2050](/docs/node-red/hardware/siemens-iot-2050/): In this guide, we will discuss how to install FlowFuse Device agent on Siemens IoT2050. +- [Setting Node-RED on BLIIOT ARMxy BL340](/docs/node-red/hardware/armxy-bl340/): Guide to setting up Node-RED on BLIIOT ARMxy BL340, including installation and configuration steps. +- [Setting Node-RED on Raspberry Pi 4](/docs/node-red/hardware/raspberry-pi-4/): Learn how to install the FlowFuse Edge Agent on the Raspberry Pi 4 effortlessly. Manage your device with Node-RED through FlowFuse with ease. +- [Setting Node-RED on Robustel EG5120](/docs/node-red/hardware/robustel-eg5120/): In this guide, we will discuss how to install FlowFuse Device agent on Robustel EG5120. +- [Setting up Node-RED on Opto-22 Groov Rio R7](/docs/node-red/hardware/opto-22-groove-rio-7-mm2001-10/): Learn how to install and configure Node-RED on the Opto-22 Groov Rio R7, a rugged edge I/O module for industrial applications. +- [Setting Up Node-RED on Raspberry Pi 5](/docs/node-red/hardware/raspberry-pi-5/): Learn how to install the FlowFuse Edge Agent on the Raspberry Pi 5 effortlessly. Manage your device with Node-RED through FlowFuse with ease. diff --git a/nuxt/content-guides/node-red/hardware/armxy-bl340.md b/nuxt/content-guides/node-red/hardware/armxy-bl340.md new file mode 100644 index 0000000000..41fd339b94 --- /dev/null +++ b/nuxt/content-guides/node-red/hardware/armxy-bl340.md @@ -0,0 +1,117 @@ +--- +title: Setting Node-RED on BLIIOT ARMxy BL340 +metaTitle: "Setting Node-RED on BLIIOT ARMxy BL340" +navTitle: BLIIOT ARMxy BL340 +navOrder: 50 +meta: + description: Guide to setting up Node-RED on BLIIOT ARMxy BL340, including installation and configuration steps. + keywords: node-red, flowfuse, BLIIOT ARMxy BL340 +image: /docs/node-red/hardware/images/armxy-bl340.jpg +--- + +# Setting Node-RED on BLIIOT ARMxy BL340 + +## Specifications + +| | | +|---|---| +| Model | BLIIoT ARMxy BL340 Series | +| RAM MB | 2048 | +| Processor | ARM Cortex-A53 | +| GPU | G31 MP2 | +| IO Points | Optional (GPIO, RS485,CAN,RS232,DI/DO/AI/AO etc.,) | +| Connectivity | Dual-band Wi-Fi 6, Bluetooth 5.2, Gigabit Ethernet, 4x USB 3.0, 1x USB-C | +| Clock Speed | Up to 1.4 GHz | +| Storage | SD, SDHC and SDXC(UHS-I) card | +| Display Output | HDMI 2.0, DisplayPort | +| Power Supply | 9~36V DC | + +The BLIIOT ARMxy BL340 is a high-performance single-board computer designed for demanding applications, including edge computing, automation, and embedded systems. Featuring an octa-core ARM processor, advanced connectivity options, and support for high-speed storage, it provides a powerful platform for developers and engineers. + +Integrating this powerful hardware with FlowFuse not only enhances its capabilities but also simplifies the management and deployment process. + +

Note

+ +Exciting Update! FlowFuse is now available as three different products: [FlowFuse Edge for OT teams](/product/edge/), [FlowFuse Hub for IT teams](/product/hub/), and [FlowFuse Fleet](/product/fleet/) for managing devices at scale. Visit the [FlowFuse Product Page](/product/) to learn how the platform enables you to build and manage industrial apps at scale. + +
+ +## Prerequisites + +Before proceeding with the installation, ensure you have the following: + +- **BLIIOT ARMxy BL340** – A functioning device with internet access. +- **FlowFuse Account** - Ensure you have a FlowFuse account. If not, you can create a free account that allows you to manage up to two edge devices for free. For more information, refer to [FlowFuse Free Tier](/blog/2024/12/flowfuse-release-2-12/) +- **Sudo Privileges** – Administrator access to install required packages. + +## Getting Started + +This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the BLIIOT ARMxy BL340, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. + +### Installing FlowFuse Device Agent + +Before we start, it is recommended to update and upgrade your system to ensure all your packages are up to date: + +```bash +sudo apt update && sudo apt upgrade -y +``` + +Next, let's install the FlowFuse device agent with the following script. + +```bash +bash <(curl -sL https://raw.githubusercontent.com/FlowFuse/device-agent/main/service/raspbian-install-device-agent.sh) +``` + +This script installs the Node.js runtime (if not already installed), sets up the FlowFuse device agent, and configures the device to automatically run the FlowFuse agent on boot and restart it in case of a crash. + +To verify that the service is running, use the following command: + +```bash +sudo systemctl status flowfuse-device-agent.service +``` + +If running, you should see a result similar to the one shown in the image below: + +!["Status of the FlowFuse Device Agent systemd service"](./images/systemctl-status.png "Status of the FlowFuse Device Agent systemd service"){data-zoomable} + +### Registering the Device to Connect to FlowFuse + +Once you have installed the FlowFuse Device Agent, you need to register the hardware to connect it to your FlowFuse team. + +For instructions on how to register the hardware with your FlowFuse team, follow the documentation: [Register your Remote Instance](/docs/device-agent/register/). + +When registering your hardware, you will be presented with a dialog containing a one-time passcode command that the Device Agent uses to retrieve its configuration. **Make sure to copy it.** + +!["Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"](./images/configuration-dailog-with-one-time-code.png "Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"){data-zoomable} + +### Connecting Device + +Execute the command you have copied with sudo as shown below + +```bash +sudo flowfuse-device-agent -o https://app.flowfuse.com +``` + +Once executed, you should see an output similar to the one below, indicating that the FlowFuse Device Agent has been successfully configured: + +```bash +[AGENT] 3/21/2025 7:09:25 PM [info] Entering Device setup... +[AGENT] 3/21/2025 7:09:27 PM [info] Device setup was successful +[AGENT] 3/21/2025 7:09:27 PM [info] To start the Device Agent with the new configuration, run the following command: +[AGENT] 3/21/2025 7:09:27 PM [info] flowfuse-device-agent +``` + +Now, you can check the remote instance in the FlowFuse platform, where its status should be displayed as **"running."**. + +!["Status of the BLIIOT ARMxy BL340 remote instance in FlowFuse, showing its connection and operational state"](./images/status-flowfuse.png "Status of the BLIIOT ARMxy BL340 remote instance in FlowFuse, showing its connection and operational state"){data-zoomable} + +Now, when your device reboots, the FlowFuse Device Agent will automatically start, ensuring that your BLIIOT ARMxy BL340 remains connected to the FlowFuse platform. + +## Accessing Node-RED Editor. + +1. Login into your FlowFuse account. +2. Click on the remote instances option in the left sidebar. +3. Click on the device and enable the developer mode by clicking on the top right-corner switch. +4. Once Developer Mode is enabled, click on the Open Editor option located next to the that switch. + +For more information refer to [FlowFuse documentation](/docs/user/introduction/#working-with-devices) diff --git a/src/node-red/hardware/images/GRV-R7-MM1001-10.jpeg b/nuxt/content-guides/node-red/hardware/images/GRV-R7-MM1001-10.jpeg similarity index 100% rename from src/node-red/hardware/images/GRV-R7-MM1001-10.jpeg rename to nuxt/content-guides/node-red/hardware/images/GRV-R7-MM1001-10.jpeg diff --git a/src/node-red/hardware/images/armxy-bl340.jpg b/nuxt/content-guides/node-red/hardware/images/armxy-bl340.jpg similarity index 100% rename from src/node-red/hardware/images/armxy-bl340.jpg rename to nuxt/content-guides/node-red/hardware/images/armxy-bl340.jpg diff --git a/src/node-red/hardware/images/configuration-dailog-with-one-time-code.png b/nuxt/content-guides/node-red/hardware/images/configuration-dailog-with-one-time-code.png similarity index 100% rename from src/node-red/hardware/images/configuration-dailog-with-one-time-code.png rename to nuxt/content-guides/node-red/hardware/images/configuration-dailog-with-one-time-code.png diff --git a/src/node-red/hardware/images/flowfuse-on-robustel-eg5120-installation-complete.png b/nuxt/content-guides/node-red/hardware/images/flowfuse-on-robustel-eg5120-installation-complete.png similarity index 100% rename from src/node-red/hardware/images/flowfuse-on-robustel-eg5120-installation-complete.png rename to nuxt/content-guides/node-red/hardware/images/flowfuse-on-robustel-eg5120-installation-complete.png diff --git a/src/node-red/hardware/images/flowfuse-on-robustel-eg5120-node-red.png b/nuxt/content-guides/node-red/hardware/images/flowfuse-on-robustel-eg5120-node-red.png similarity index 100% rename from src/node-red/hardware/images/flowfuse-on-robustel-eg5120-node-red.png rename to nuxt/content-guides/node-red/hardware/images/flowfuse-on-robustel-eg5120-node-red.png diff --git a/src/node-red/hardware/images/flowfuse-on-siemens-iot2050.png b/nuxt/content-guides/node-red/hardware/images/flowfuse-on-siemens-iot2050.png similarity index 100% rename from src/node-red/hardware/images/flowfuse-on-siemens-iot2050.png rename to nuxt/content-guides/node-red/hardware/images/flowfuse-on-siemens-iot2050.png diff --git a/src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-debug-node.png b/nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-debug-node.png similarity index 100% rename from src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-debug-node.png rename to nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-debug-node.png diff --git a/src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-device-configuration-dialog.png b/nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-device-configuration-dialog.png similarity index 100% rename from src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-device-configuration-dialog.png rename to nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-device-configuration-dialog.png diff --git a/src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-dht-sensor.png b/nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-dht-sensor.png similarity index 100% rename from src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-dht-sensor.png rename to nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-dht-sensor.png diff --git a/src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-inject-node.png b/nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-inject-node.png similarity index 100% rename from src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-inject-node.png rename to nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-inject-node.png diff --git a/src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-pins.png b/nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-pins.png similarity index 100% rename from src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-pins.png rename to nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi-pins.png diff --git a/src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi.png b/nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi.png similarity index 100% rename from src/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi.png rename to nuxt/content-guides/node-red/hardware/images/how-to-setup-node-red-on-raspberry-pi.png diff --git a/src/node-red/hardware/images/raspberry-4-editor.png b/nuxt/content-guides/node-red/hardware/images/raspberry-4-editor.png similarity index 100% rename from src/node-red/hardware/images/raspberry-4-editor.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-4-editor.png diff --git a/src/node-red/hardware/images/raspberry-pi-4-b.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-4-b.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-4-b.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-4-b.png diff --git a/src/node-red/hardware/images/raspberry-pi-4.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-4.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-4.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-4.png diff --git a/src/node-red/hardware/images/raspberry-pi-5-config-before-flash.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-config-before-flash.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-5-config-before-flash.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-config-before-flash.png diff --git a/src/node-red/hardware/images/raspberry-pi-5-device-agent.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-device-agent.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-5-device-agent.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-device-agent.png diff --git a/src/node-red/hardware/images/raspberry-pi-5-editor.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-editor.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-5-editor.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-editor.png diff --git a/src/node-red/hardware/images/raspberry-pi-5-flash-os.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-flash-os.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-5-flash-os.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-5-flash-os.png diff --git a/src/node-red/hardware/images/raspberry-pi-5.png b/nuxt/content-guides/node-red/hardware/images/raspberry-pi-5.png similarity index 100% rename from src/node-red/hardware/images/raspberry-pi-5.png rename to nuxt/content-guides/node-red/hardware/images/raspberry-pi-5.png diff --git a/src/node-red/hardware/images/raspberrypi-5.png b/nuxt/content-guides/node-red/hardware/images/raspberrypi-5.png similarity index 100% rename from src/node-red/hardware/images/raspberrypi-5.png rename to nuxt/content-guides/node-red/hardware/images/raspberrypi-5.png diff --git a/src/node-red/hardware/images/robustel-eg5120.png b/nuxt/content-guides/node-red/hardware/images/robustel-eg5120.png similarity index 100% rename from src/node-red/hardware/images/robustel-eg5120.png rename to nuxt/content-guides/node-red/hardware/images/robustel-eg5120.png diff --git a/src/node-red/hardware/images/siemens-iot-2050.jpg b/nuxt/content-guides/node-red/hardware/images/siemens-iot-2050.jpg similarity index 100% rename from src/node-red/hardware/images/siemens-iot-2050.jpg rename to nuxt/content-guides/node-red/hardware/images/siemens-iot-2050.jpg diff --git a/src/node-red/hardware/images/status-flowfuse.png b/nuxt/content-guides/node-red/hardware/images/status-flowfuse.png similarity index 100% rename from src/node-red/hardware/images/status-flowfuse.png rename to nuxt/content-guides/node-red/hardware/images/status-flowfuse.png diff --git a/src/node-red/hardware/images/systemctl-status.png b/nuxt/content-guides/node-red/hardware/images/systemctl-status.png similarity index 100% rename from src/node-red/hardware/images/systemctl-status.png rename to nuxt/content-guides/node-red/hardware/images/systemctl-status.png diff --git a/src/node-red/hardware/opto-22-groove-rio-7-mm2001-10.md b/nuxt/content-guides/node-red/hardware/opto-22-groove-rio-7-mm2001-10.md similarity index 67% rename from src/node-red/hardware/opto-22-groove-rio-7-mm2001-10.md rename to nuxt/content-guides/node-red/hardware/opto-22-groove-rio-7-mm2001-10.md index 613198ae38..3f0932daa1 100644 --- a/src/node-red/hardware/opto-22-groove-rio-7-mm2001-10.md +++ b/nuxt/content-guides/node-red/hardware/opto-22-groove-rio-7-mm2001-10.md @@ -1,29 +1,28 @@ --- -eleventyNavigation: - key: Opto-22 Groov Epic - parent: Hardware +title: Setting up Node-RED on Opto-22 Groov Rio R7 +metaTitle: "Setting up Node-RED on Opto-22 Groov Rio R7" +navTitle: Opto-22 Groov Epic +navOrder: 50 meta: - title: Setting up Node-RED on Opto-22 Groov Rio R7 description: Learn how to install and configure Node-RED on the Opto-22 Groov Rio R7, a rugged edge I/O module for industrial applications. keywords: node-red, flowfuse, Opto-22 Groov Rio, groov RIO, industrial IoT, edge computing -image: "/node-red/hardware/images/GRV-R7-MM1001-10.jpeg" -specifications: - Model: GRV-R7-MM2001-10 - RAM: 1024 MB - Processor: ARM Cortex-A8, 1 GHz - I/O Channels: 10 multi-signal, multifunction channels (analog I/O, temperature, discrete I/O, mechanical relays) - Connectivity: - - Dual switched Gigabit Ethernet - - USB 2.0 (host) - - Power over Ethernet (PoE) - - 10–32 VDC power input - Clock Speed: 1 GHz - Storage: - - 4 GB eMMC (internal) - - USB memory stick support (up to 32 GB) -layout: layouts/hardware.njk +image: /docs/node-red/hardware/images/GRV-R7-MM1001-10.jpeg --- +# Setting up Node-RED on Opto-22 Groov Rio R7 + +## Specifications + +| | | +|---|---| +| Model | GRV-R7-MM2001-10 | +| RAM | 1024 MB | +| Processor | ARM Cortex-A8, 1 GHz | +| I/O Channels | 10 multi-signal, multifunction channels (analog I/O, temperature, discrete I/O, mechanical relays) | +| Connectivity | Dual switched Gigabit Ethernet,USB 2.0 (host),Power over Ethernet (PoE),10–32 VDC power input | +| Clock Speed | 1 GHz | +| Storage | 4 GB eMMC (internal),USB memory stick support (up to 32 GB) | + The Opto-22 Groov Rio R7 is a rugged edge I/O module designed for industrial applications. Equipped with a powerful ARM Cortex-A8 processor, versatile I/O channels, and various connectivity options, it’s an ideal solution for edge computing and industrial IoT. ## Prerequisites @@ -31,14 +30,23 @@ The Opto-22 Groov Rio R7 is a rugged edge I/O module designed for industrial app Before proceeding with the installation, ensure you have the following: - **Opto-22 Groov Rio R7** – A functioning device with internet access. -- **FlowFuse Account** - You need an active FlowFuse account to access the platform and configure your instance. If you do not have one, please visit the FlowFuse website and [sign up]({% include "sign-up-url.njk" %}) for a new account before proceeding. +- **FlowFuse Account** - You need an active FlowFuse account to access the platform and configure your instance. If you do not have one, please visit the FlowFuse website and [sign up](https://app.flowfuse.com/account/create) for a new account before proceeding. - **Sudo Privileges** – Administrator access to install required packages. ## Getting Started This guide will walk you through setting up Node-RED on the Groov Rio R7 using the FlowFuse Device Agent, allowing you to manage, scale, and secure your remote instances effectively. -{% include "hardware/system/opto-22-groove-rio.md" %} +### Installing FlowFuse Device Agent + +Before starting the installation, it is recommended to update your system to +ensure that all your packages are up to date. You can use groov manage, which +acts as the command central for your groov RIO devices. For detailed +instructions on how to update the system, [watch this video](https://www.opto22.com/support/resources-tools/videos/playlist-what-is-groov-epic?wchannelid=61lkudfc8c&wmediaid=mxzzp2kudx). + +This guide is written for the firmware version of: `4.0.2-b.194`. Node.JS 20 is +available on the device, and you should be good to go to register the edge device +on FlowFuse. ### Registering the Device to Connect to FlowFuse diff --git a/nuxt/content-guides/node-red/hardware/raspberry-pi-4.md b/nuxt/content-guides/node-red/hardware/raspberry-pi-4.md new file mode 100644 index 0000000000..c43584b8dc --- /dev/null +++ b/nuxt/content-guides/node-red/hardware/raspberry-pi-4.md @@ -0,0 +1,118 @@ +--- +title: Setting Node-RED on Raspberry Pi 4 +metaTitle: "Setting Node-RED on Raspberry Pi 4" +navTitle: Raspberry Pi 4 +navOrder: 50 +meta: + description: Learn how to install the FlowFuse Edge Agent on the Raspberry Pi 4 effortlessly. Manage your device with Node-RED through FlowFuse with ease. + keywords: node-red, flowfuse, raspberry pi, raspberry pi 4 +image: /docs/node-red/hardware/images/raspberry-pi-4-b.png +--- + +# Setting Node-RED on Raspberry Pi 4 + +## Specifications + +| | | +|---|---| +| Model | Raspberry Pi 4 B 8GB | +| RAM MB | 8192 | +| Processor | Broadcom BCM2711, ARM Cortex-A72 (ARMv8-A), 4 (Quad-core) | +| GPIO | (Fully backwar ds-compatible with previous boards), Standard 40-pin GPIO Header | +| Connectivity | Dual-band Wi-Fi, Bluetooth 5.0, Gigabit Ethernet, 2x USB 3.0, 2x USB 2.0 | +| Clock Speed | 1.5 GHz | +| Storage | microSD | + +## Raspberry Pi OS Installation + +To set up your Raspberry Pi 4 for use with Node-RED and FlowFuse, follow these steps: + +### Flashing Raspberry Pi OS + +1. Use the [official Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the 64-bit version of Raspberry Pi OS to an SD card. + +![Flash Raspberry Pi OS on an SD-card](./images/raspberry-pi-5-flash-os.png) + +2. Before writing to the SD card, configure the OS for headless mode, including Wi-Fi, SSH, and authentication settings. + +![Configure RPi OS before flashing](./images/raspberry-pi-5-config-before-flash.png) + +3. Write the OS and configuration to the SD card. This process takes about 10 minutes. + +4. Insert the SD card into the Raspberry Pi 4 and power it on. The device should appear on your network after a minute or so. + +5. Connect to the Raspberry Pi using SSH: + + ```sh + ssh pi@raspberrypi.local + ``` + +## Getting Started + +This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the Raspberry Pi 4, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. + +### Installing FlowFuse Device Agent + +Before we start, it is recommended to update and upgrade your system to ensure all your packages are up to date: + +```bash +sudo apt update && sudo apt upgrade -y +``` + +Next, let's install the FlowFuse device agent with the following script. + +```bash +bash <(curl -sL https://raw.githubusercontent.com/FlowFuse/device-agent/main/service/raspbian-install-device-agent.sh) +``` + +This script installs the Node.js runtime (if not already installed), sets up the FlowFuse device agent, and configures the device to automatically run the FlowFuse agent on boot and restart it in case of a crash. + +To verify that the service is running, use the following command: + +```bash +sudo systemctl status flowfuse-device-agent.service +``` + +If running, you should see a result similar to the one shown in the image below: + +!["Status of the FlowFuse Device Agent systemd service"](./images/systemctl-status.png "Status of the FlowFuse Device Agent systemd service"){data-zoomable} + +### Registering the Device to Connect to FlowFuse + +Once you have installed the FlowFuse Device Agent, you need to register the hardware to connect it to your FlowFuse team. + +For instructions on how to register the hardware with your FlowFuse team, follow the documentation: [Register your Remote Instance](/docs/device-agent/register/). + +When registering your hardware, you will be presented with a dialog containing a one-time passcode command that the Device Agent uses to retrieve its configuration. **Make sure to copy it.** + +!["Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"](./images/configuration-dailog-with-one-time-code.png "Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"){data-zoomable} + +### Connecting Device + +Execute the command you have copied with sudo as shown below + +```bash +sudo flowfuse-device-agent -o https://app.flowfuse.com +``` + +Once executed, you should see an output similar to the one below, indicating that the FlowFuse Device Agent has been successfully configured: + +```bash +[AGENT] 3/21/2025 7:09:25 PM [info] Entering Device setup... +[AGENT] 3/21/2025 7:09:27 PM [info] Device setup was successful +[AGENT] 3/21/2025 7:09:27 PM [info] To start the Device Agent with the new configuration, run the following command: +[AGENT] 3/21/2025 7:09:27 PM [info] flowfuse-device-agent +``` + +Now, you can check the remote instance in the FlowFuse platform, where its status should be displayed as **"running."**. + +![Status of the remote instance in FlowFuse, showing its connection and operational state](./images/raspberry-pi-4.png "Status of the remote instance in FlowFuse, showing its connection and operational state"){data-zoomable} + +## Accessing Node-RED Editor. + +1. Login into your FlowFuse account. +2. Click on the remote instances option in the left sidebar. +3. Click on the device and enable the developer mode by clicking on the top right-corner switch. +4. Once Developer Mode is enabled, click on the Open Editor option located next to the that switch. + +For more information refer to [FlowFuse documentation](/docs/user/introduction/#working-with-devices) diff --git a/nuxt/content-guides/node-red/hardware/raspberry-pi-5.md b/nuxt/content-guides/node-red/hardware/raspberry-pi-5.md new file mode 100644 index 0000000000..a523bf0c82 --- /dev/null +++ b/nuxt/content-guides/node-red/hardware/raspberry-pi-5.md @@ -0,0 +1,118 @@ +--- +title: Setting Up Node-RED on Raspberry Pi 5 +metaTitle: "Setting Up Node-RED on Raspberry Pi 5" +navTitle: Raspberry Pi 5 +navOrder: 50 +meta: + description: Learn how to install the FlowFuse Edge Agent on the Raspberry Pi 5 effortlessly. Manage your device with Node-RED through FlowFuse with ease. + keywords: node-red, flowfuse, raspberry pi, raspberry pi 5 +image: /docs/node-red/hardware/images/raspberrypi-5.png +--- + +# Setting Up Node-RED on Raspberry Pi 5 + +## Specifications + +| | | +|---|---| +| Model | Raspberry Pi 5 Model 8GB | +| RAM MB | 8192 | +| Processor | Broadcom BCM2712, ARM Cortex-A76 (ARMv8.2-A), 4 (Quad-core) | +| GPIO | Standard 40-pin GPIO Header | +| Connectivity | 2 × USB 2.0 Ports, 2 × USB 3.0 Ports, Bluetooth 5.0, USB-C, Wi-Fi + Bluetooth® Low Energy | +| Clock Speed | 2.4 GHz | +| Storage | microSD | + +## Raspberry Pi OS Installation + +To set up your Raspberry Pi 5 for use with Node-RED and FlowFuse, follow these steps: + +### Flashing Raspberry Pi OS + +1. Use the [official Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the 64-bit version of Raspberry Pi OS to an SD card. + +![Flash Raspberry Pi OS on an SD-card](./images/raspberry-pi-5-flash-os.png) + +2. Before writing to the SD card, configure the OS for headless mode, including Wi-Fi, SSH, and authentication settings. + +![Configure RPi OS before flashing](./images/raspberry-pi-5-config-before-flash.png) + +3. Write the OS and configuration to the SD card. This process takes about 10 minutes. + +4. Insert the SD card into the Raspberry Pi 5 and power it on. The device should appear on your network after a minute or so. + +5. Connect to the Raspberry Pi using SSH: + + ```sh + ssh pi@raspberrypi.local + ``` + +## Getting Started + +This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the Raspberry Pi 5, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. + +### Installing FlowFuse Device Agent + +Before we start, it is recommended to update and upgrade your system to ensure all your packages are up to date: + +```bash +sudo apt update && sudo apt upgrade -y +``` + +Next, let's install the FlowFuse device agent with the following script. + +```bash +bash <(curl -sL https://raw.githubusercontent.com/FlowFuse/device-agent/main/service/raspbian-install-device-agent.sh) +``` + +This script installs the Node.js runtime (if not already installed), sets up the FlowFuse device agent, and configures the device to automatically run the FlowFuse agent on boot and restart it in case of a crash. + +To verify that the service is running, use the following command: + +```bash +sudo systemctl status flowfuse-device-agent.service +``` + +If running, you should see a result similar to the one shown in the image below: + +!["Status of the FlowFuse Device Agent systemd service"](./images/systemctl-status.png "Status of the FlowFuse Device Agent systemd service"){data-zoomable} + +### Registering the Device to Connect to FlowFuse + +Once you have installed the FlowFuse Device Agent, you need to register the hardware to connect it to your FlowFuse team. + +For instructions on how to register the hardware with your FlowFuse team, follow the documentation: [Register your Remote Instance](/docs/device-agent/register/). + +When registering your hardware, you will be presented with a dialog containing a one-time passcode command that the Device Agent uses to retrieve its configuration. **Make sure to copy it.** + +!["Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"](./images/configuration-dailog-with-one-time-code.png "Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"){data-zoomable} + +### Connecting Device + +Execute the command you have copied with sudo as shown below + +```bash +sudo flowfuse-device-agent -o https://app.flowfuse.com +``` + +Once executed, you should see an output similar to the one below, indicating that the FlowFuse Device Agent has been successfully configured: + +```bash +[AGENT] 3/21/2025 7:09:25 PM [info] Entering Device setup... +[AGENT] 3/21/2025 7:09:27 PM [info] Device setup was successful +[AGENT] 3/21/2025 7:09:27 PM [info] To start the Device Agent with the new configuration, run the following command: +[AGENT] 3/21/2025 7:09:27 PM [info] flowfuse-device-agent +``` + +Now, you can check the remote instance in the FlowFuse platform, where its status should be displayed as **"running."**. + +![Status of the remote instance in FlowFuse, showing its connection and operational state](./images/raspberry-pi-5.png "Status of the remote instance in FlowFuse, showing its connection and operational state."){data-zoomable} + +## Accessing Node-RED Editor. + +1. Login into your FlowFuse account. +2. Click on the remote instances option in the left sidebar. +3. Click on the device and enable the developer mode by clicking on the top right-corner switch. +4. Once Developer Mode is enabled, click on the Open Editor option located next to the that switch. + +For more information refer to [FlowFuse documentation](/docs/user/introduction/#working-with-devices) diff --git a/nuxt/content-guides/node-red/hardware/robustel-eg5120.md b/nuxt/content-guides/node-red/hardware/robustel-eg5120.md new file mode 100644 index 0000000000..4864b1475a --- /dev/null +++ b/nuxt/content-guides/node-red/hardware/robustel-eg5120.md @@ -0,0 +1,98 @@ +--- +title: Setting Node-RED on Robustel EG5120 +metaTitle: "Setting Node-RED on Robustel EG5120" +navTitle: Robustel EG5120 +navOrder: 50 +meta: + description: In this guide, we will discuss how to install FlowFuse Device agent on Robustel EG5120. + keywords: node-red, flowfuse, robustel eg5120 +image: /docs/node-red/hardware/images/robustel-eg5120.png +--- + +# Setting Node-RED on Robustel EG5120 + +## Specifications + +| | | +|---|---| +| Model | Robustel EG5120 | +| RAM MB | 2048 | +| Processor | Broadcom BCM2711, ARM Cortex-A72 (ARMv8-A), 4 (Quad-core) | +| GPIO | Standard 40-pin GPIO Header | +| Connectivity | Dual-band Wi-Fi, Bluetooth 5.0, Gigabit Ethernet, 2x USB 3.0, 2x USB 2.0 | +| Clock Speed | 1.5 GHz | +| Storage | microSD | + +The [Robustel EG5120](https://www.robustel.com/product/eg5120-industrial-edge-computing-gateway/) is a versatile gateway that facilitates robust connectivity for industrial IoT applications. Integrating this powerful hardware with FlowFuse not only enhances its capabilities but also simplifies the management and deployment process. In this documentation, we’ll walk through the steps to integrate the Robustel EG5120 with FlowFuse. + +The [Robustel EG5120](https://www.robustel.com/product/eg5120-industrial-edge-computing-gateway/), equipped with Linux-based Debian 11 supporting a wide variety of programming languages including Node.js, offers robust connectivity options. When combined with FlowFuse, this gateway becomes even more powerful, enabling seamless device management and deployment. + +The Robustel EG5120 supports multiple connectivity options including Ethernet, Wi-Fi, and cellular networks, which are essential for flexible deployments in various industrial scenarios. Its built-in support for Bluetooth, cellular connectivity, RS232, RS485, and Modbus facilitates seamless integration with a wide array of IoT devices and services. This blog will guide you through using FlowFuse to effectively manage your Node-RED instance, enhancing both the security and scalability of your IoT applications. + +## Getting Started + +This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the Robustel EG5120, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. + +### Installing FlowFuse Device Agent + +Before we start, it is recommended to update and upgrade your system to ensure all your packages are up to date: + +```bash +sudo apt update && sudo apt upgrade -y +``` + +Next, let's install the FlowFuse device agent with the following script. + +```bash +bash <(curl -sL https://raw.githubusercontent.com/FlowFuse/device-agent/main/service/raspbian-install-device-agent.sh) +``` + +This script installs the Node.js runtime (if not already installed), sets up the FlowFuse device agent, and configures the device to automatically run the FlowFuse agent on boot and restart it in case of a crash. + +To verify that the service is running, use the following command: + +```bash +sudo systemctl status flowfuse-device-agent.service +``` + +If running, you should see a result similar to the one shown in the image below: + +!["Status of the FlowFuse Device Agent systemd service"](./images/systemctl-status.png "Status of the FlowFuse Device Agent systemd service"){data-zoomable} + +### Registering the Device to Connect to FlowFuse + +Once you have installed the FlowFuse Device Agent, you need to register the hardware to connect it to your FlowFuse team. + +For instructions on how to register the hardware with your FlowFuse team, follow the documentation: [Register your Remote Instance](/docs/device-agent/register/). + +When registering your hardware, you will be presented with a dialog containing a one-time passcode command that the Device Agent uses to retrieve its configuration. **Make sure to copy it.** + +!["Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"](./images/configuration-dailog-with-one-time-code.png "Dialog containing a one-time passcode command that the Device Agent can use to retrieve its configuration"){data-zoomable} + +### Connecting Device + +Execute the command you have copied with sudo as shown below + +```bash +sudo flowfuse-device-agent -o https://app.flowfuse.com +``` + +Once executed, you should see an output similar to the one below, indicating that the FlowFuse Device Agent has been successfully configured: + +```bash +[AGENT] 3/21/2025 7:09:25 PM [info] Entering Device setup... +[AGENT] 3/21/2025 7:09:27 PM [info] Device setup was successful +[AGENT] 3/21/2025 7:09:27 PM [info] To start the Device Agent with the new configuration, run the following command: +[AGENT] 3/21/2025 7:09:27 PM [info] flowfuse-device-agent +``` + +Now, you can check the remote instance in the FlowFuse platform, where its status should be displayed as **"running."**. + +## Accessing Node-RED Editor. + +1. Login into your FlowFuse account. +2. Click on the remote instances option in the left sidebar. +3. Click on the device and enable the developer mode by clicking on the top right-corner switch. +4. Once Developer Mode is enabled, click on the Open Editor option located next to the that switch. + +For more information refer to [FlowFuse documentation](/docs/user/introduction/#working-with-devices) diff --git a/src/node-red/hardware/siemens-iot-2050.md b/nuxt/content-guides/node-red/hardware/siemens-iot-2050.md similarity index 90% rename from src/node-red/hardware/siemens-iot-2050.md rename to nuxt/content-guides/node-red/hardware/siemens-iot-2050.md index 686a870d47..66eda7a402 100644 --- a/src/node-red/hardware/siemens-iot-2050.md +++ b/nuxt/content-guides/node-red/hardware/siemens-iot-2050.md @@ -1,23 +1,28 @@ --- -eleventyNavigation: - key: Siemens IoT2050 - parent: Hardware +title: Run Node-RED on Siemens IoT2050 +metaTitle: "Run Node-RED on Siemens IoT2050" +navTitle: Siemens IoT2050 +navOrder: 50 meta: - title: Run Node-RED on Siemens IoT2050 description: In this guide, we will discuss how to install FlowFuse Device agent on Siemens IoT2050. keywords: node-red, flowfuse, siemens iot2050 flowfuse -image: "/node-red/hardware/images/siemens-iot-2050.jpg" -specifications: - Model: IOT2050 Basic - RAM_MB: 1024 - Processor: ARM TI AM6528 GP - GPIO: x20 Digital I/O - Connectivity: 1x RS 232 / 422 / 485, Ethernet, USB 2.0, Arduino, mPCIe - Clock Speed: 1 GHz - Storage: SD Card -layout: layouts/hardware.njk +image: /docs/node-red/hardware/images/siemens-iot-2050.jpg --- +# Run Node-RED on Siemens IoT2050 + +## Specifications + +| | | +|---|---| +| Model | IOT2050 Basic | +| RAM MB | 1024 | +| Processor | ARM TI AM6528 GP | +| GPIO | x20 Digital I/O | +| Connectivity | 1x RS 232 / 422 / 485, Ethernet, USB 2.0, Arduino, mPCIe | +| Clock Speed | 1 GHz | +| Storage | SD Card | + Siemens [announced](https://press.siemens.com/global/en/pressrelease/new-siemens-gateway-between-cloud-company-it-and-production) the IoT2000 series in March of 2020. With this tool many have been using it to function as a gateway between their plant operations and cloud infrastructure. Onboard it came with Node-RED pre-installed. To manage Node-RED as an organization the FlowFuse agent is recommended, this documentation shows you how to do so.
Warning: Later in the documentation we will be updating Node.js. This will break MRAA library. This will prevent communication to the GPIO of the device.
diff --git a/nuxt/content-guides/node-red/integration-technologies/README.md b/nuxt/content-guides/node-red/integration-technologies/README.md new file mode 100644 index 0000000000..c5006825ff --- /dev/null +++ b/nuxt/content-guides/node-red/integration-technologies/README.md @@ -0,0 +1,23 @@ +--- +title: Using Different Technologies for Building Applications with Node-RED. +metaTitle: "Building Applications with Node-RED" +navTitle: Integration technologies +navOrder: 6 +meta: + description: Learn how to leverage various integration technologies with Node-RED for building robust and interconnected applications. + keywords: node-red, flowfuse, node-red integration, integration technologies, webhook, rest api +--- + +# Using Different Technologies for Building Applications with Node-RED. + +Developing powerful and scalable applications frequently necessitates the integration of various technologies. This integration is crucial for creating seamless and efficient systems that can handle complex tasks and large volumes of data. Whether you're working with REST APIs to enable communication between different services or implementing GraphQL for more efficient data querying, the ability to blend these technologies effectively is essential for modern application development. + +Node-RED offers a versatile and robust platform to meet these needs. Its intuitive flow-based interface allows developers to easily design and deploy sophisticated workflows, which makes it an invaluable tool for integrating REST APIs, GraphQL, webhooks, and more. + +## Resources + +Here are some resources to help you integrate Node-RED with various different technologies: + +- [Creating REST API's with Node-RED](/docs/node-red/integration-technologies/rest/): Learn how to create REST APIs in Node-RED and fetch data from an API. +- [Integrating GraphQL APIs in Node-RED](/docs/node-red/integration-technologies/graphql/): Learn how to integrate GraphQL APIs in Node-RED. This guide covers setting up endpoints, executing queries, handling variables, and using mutations for dynamic data. +- [Using Webhook with Node-RED](/docs/node-red/integration-technologies/webhook/): Learn how to seamlessly integrate webhooks into your Node-RED applications for automating tasks and enhancing communication. diff --git a/src/node-red/integration-technologies/graphql.md b/nuxt/content-guides/node-red/integration-technologies/graphql.md similarity index 98% rename from src/node-red/integration-technologies/graphql.md rename to nuxt/content-guides/node-red/integration-technologies/graphql.md index 2e11f318c7..c5e4ee18d9 100644 --- a/src/node-red/integration-technologies/graphql.md +++ b/nuxt/content-guides/node-red/integration-technologies/graphql.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: GraphQL API - parent: "Integration Technologies" +title: Integrating GraphQL APIs in Node-RED +metaTitle: "Integrating GraphQL APIs in Node-RED" +navTitle: GraphQL API +navOrder: 50 meta: - title: Integrating GraphQL APIs in Node-RED - description: Learn how to integrate GraphQL APIs in Node-RED. This guide covers setting up endpoints, executing queries, handling variables, and using mutations for dynamic data. - keywords: node-red, flowfuse, graphql apis, queries, mutations + description: Learn how to integrate GraphQL APIs in Node-RED. This guide covers setting up endpoints, executing queries, handling variables, and using mutations for dynamic data. + keywords: node-red, flowfuse, graphql apis, queries, mutations --- -# {{meta.title}} +# Integrating GraphQL APIs in Node-RED GraphQL is transforming the way APIs are designed. Unlike traditional REST APIs, which often require multiple requests to different endpoints, GraphQL provides a single, flexible endpoint that allows you to fetch exactly the data you need, nothing more, nothing less. In this article, you will learn how to integrate GraphQL with Node-RED and build APIs that efficiently serve your application's data requirements. @@ -507,6 +507,8 @@ Notice how the response includes all fields from `DeviceBasicInfo` (id, name, ty The following example flow demonstrates creating, reading, updating, and deleting data using GraphQL, including performing queries with fragments for reusable field selections. This flow and the GraphQL node are for demonstration purposes only and do not include a demo API. -{% renderFlow %} +::render-flow +```json [{"id":"7cb18349cfb7f014","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Get Single Device By ID","graphql":"fd163a325aa21cdb","format":"text","template":"query GetDevice($id: ID!) {\n device(id: $id) {\n id\n name\n type\n model\n location\n lastSeenStatus\n maintenanceDue\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":490,"y":240,"wires":[["1515cbad98355c5f"],["0c4068bd1821923b"]]},{"id":"6ac3b625b7e6e954","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{\"id\":\"1\"}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":240,"wires":[["7cb18349cfb7f014"]]},{"id":"66d017b14ed8c60d","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Get Devices","graphql":"fd163a325aa21cdb","format":"text","template":"query GetDevices {\n devices {\n id\n name\n type\n location\n lastSeenStatus\n createdAt\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":450,"y":140,"wires":[["fdcc15f68872b4d0"],["beb63abd92f155ce"]]},{"id":"572aa9e659f5e27e","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":140,"wires":[["66d017b14ed8c60d"]]},{"id":"6d4f9a2cb159d8e5","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Create Device","graphql":"fd163a325aa21cdb","format":"text","template":"mutation CreateDevice($input: DeviceInput!) {\n createDevice(input: $input) {\n id\n name\n type\n model\n location\n createdAt\n success\n errors {\n field\n message\n }\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":460,"y":340,"wires":[["f5527e179e1c8911"],["9fc70e8f8715f994"]]},{"id":"e0b89bf0a47dbae3","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{\"input\":{\"name\":\"Smart Thermostat\",\"type\":\"Controller\",\"model\":\"Nest V3\",\"location\":\"Office Area\"}}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":340,"wires":[["6d4f9a2cb159d8e5"]]},{"id":"fdcc15f68872b4d0","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":120,"wires":[]},{"id":"beb63abd92f155ce","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":160,"wires":[]},{"id":"1515cbad98355c5f","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":220,"wires":[]},{"id":"0c4068bd1821923b","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":260,"wires":[]},{"id":"f5527e179e1c8911","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":320,"wires":[]},{"id":"9fc70e8f8715f994","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":360,"wires":[]},{"id":"e0d2739c55386644","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Create Device","graphql":"fd163a325aa21cdb","format":"text","template":"mutation CreateDevice($input: DeviceInput!) {\n createDevice(input: $input) {\n id\n name\n type\n model\n location\n createdAt\n success\n errors {\n field\n message\n }\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":460,"y":440,"wires":[["f5c2e0346b4979aa"],["597bac49938cd7bc"]]},{"id":"b45422c8bffaa52d","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{\"input\":{\"name\":\"Smart Thermostat\",\"type\":\"Controller\",\"model\":\"Nest V3\",\"location\":\"Office Area\"}}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":440,"wires":[["e0d2739c55386644"]]},{"id":"f5c2e0346b4979aa","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":420,"wires":[]},{"id":"597bac49938cd7bc","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":460,"wires":[]},{"id":"dae18c118b5a7d25","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Update Device","graphql":"fd163a325aa21cdb","format":"text","template":"mutation UpdateDevice($id: ID!, $input: DeviceUpdateInput!) {\n updateDevice(id: $id, input: $input) {\n id\n name\n location\n lastSeenStatus\n updatedAt\n success\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":460,"y":540,"wires":[["8372c53fd374ca0c"],["81ee27d5d002cdf7"]]},{"id":"06e715476d4a05a1","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{ \"id\": \"1\", \"input\": { \"location\": \"Factory Floor 3\", \"lastSeenStatus\": \"Maintenance\" } }","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":540,"wires":[["dae18c118b5a7d25"]]},{"id":"8372c53fd374ca0c","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":520,"wires":[]},{"id":"81ee27d5d002cdf7","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":560,"wires":[]},{"id":"b59476221e135579","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Deactivate Device","graphql":"fd163a325aa21cdb","format":"text","template":"mutation DeactivateDevice($id: ID!) {\n deactivateDevice(id: $id) {\n id\n isActive\n deactivatedAt\n success\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":470,"y":640,"wires":[["bc0b5067c9b64b4a"],["9bcd78c44b2d304a"]]},{"id":"2f45ba641ea8d272","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{ \"id\": \"4\" }","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":640,"wires":[["b59476221e135579"]]},{"id":"bc0b5067c9b64b4a","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":620,"wires":[]},{"id":"9bcd78c44b2d304a","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":660,"wires":[]},{"id":"7d7e6689f0c42fac","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"fragment","graphql":"fd163a325aa21cdb","format":"text","template":"fragment DeviceBasicInfo on Device {\n id\n name\n type\n location\n createdAt\n}\n\nfragment DeviceOperationalInfo on Device {\n ...DeviceBasicInfo\n lastSeenStatus\n lastSeenAt\n maintenanceDue\n}\n\nquery GetDeviceProfile($deviceId: ID!) {\n device(id: $deviceId) {\n ...DeviceOperationalInfo\n }\n}","syntax":"mustache","token":"","showDebug":false,"x":440,"y":740,"wires":[["e818060c00087930"],["621b732245671f2f"]]},{"id":"ac37b7540e536255","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{ \"deviceId\": \"4\" }","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":740,"wires":[["7d7e6689f0c42fac"]]},{"id":"e818060c00087930","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":720,"wires":[]},{"id":"621b732245671f2f","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":760,"wires":[]},{"id":"f99baa69918d2a9a","type":"graphql","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Pagination","graphql":"fd163a325aa21cdb","format":"text","template":"query GetDevicesPaginated($limit: Int = 10, $offset: Int = 0, $searchTerm: String) {\n devices(limit: $limit, offset: $offset, search: $searchTerm) {\n id\n name\n type\n location\n lastSeenStatus\n createdAt\n lastSeenAt\n }\n deviceCount(search: $searchTerm)\n}","syntax":"mustache","token":"","showDebug":false,"x":450,"y":840,"wires":[["368fbd3d4f756b6a"],["cc1ef9d881065082"]]},{"id":"509af177c480ed03","type":"inject","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"","props":[{"p":"variables","v":"{ \"deviceId\": \"4\" }","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":270,"y":840,"wires":[["f99baa69918d2a9a"]]},{"id":"368fbd3d4f756b6a","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Result","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":820,"wires":[]},{"id":"cc1ef9d881065082","type":"debug","z":"98a60b6dd0896e47","g":"9a0c28902989b739","name":"Error","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":690,"y":860,"wires":[]},{"id":"fd163a325aa21cdb","type":"graphql-server","name":"","endpoint":"none","token":""},{"id":"574504396881aa85","type":"global-config","env":[],"modules":{"node-red-contrib-graphql":"2.2.0"}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/integration-technologies/images/change-node-prepare-data-chart.png b/nuxt/content-guides/node-red/integration-technologies/images/change-node-prepare-data-chart.png similarity index 100% rename from src/node-red/integration-technologies/images/change-node-prepare-data-chart.png rename to nuxt/content-guides/node-red/integration-technologies/images/change-node-prepare-data-chart.png diff --git a/src/node-red/integration-technologies/images/change-node-set-downloads-payload.png b/nuxt/content-guides/node-red/integration-technologies/images/change-node-set-downloads-payload.png similarity index 100% rename from src/node-red/integration-technologies/images/change-node-set-downloads-payload.png rename to nuxt/content-guides/node-red/integration-technologies/images/change-node-set-downloads-payload.png diff --git a/src/node-red/integration-technologies/images/chart-with-data.png b/nuxt/content-guides/node-red/integration-technologies/images/chart-with-data.png similarity index 100% rename from src/node-red/integration-technologies/images/chart-with-data.png rename to nuxt/content-guides/node-red/integration-technologies/images/chart-with-data.png diff --git a/src/node-red/integration-technologies/images/dashboard-config-chart.png b/nuxt/content-guides/node-red/integration-technologies/images/dashboard-config-chart.png similarity index 100% rename from src/node-red/integration-technologies/images/dashboard-config-chart.png rename to nuxt/content-guides/node-red/integration-technologies/images/dashboard-config-chart.png diff --git a/src/node-red/integration-technologies/images/dashboard-config-ui-base.png b/nuxt/content-guides/node-red/integration-technologies/images/dashboard-config-ui-base.png similarity index 100% rename from src/node-red/integration-technologies/images/dashboard-config-ui-base.png rename to nuxt/content-guides/node-red/integration-technologies/images/dashboard-config-ui-base.png diff --git a/src/node-red/integration-technologies/images/dashboard-config-ui-group.png b/nuxt/content-guides/node-red/integration-technologies/images/dashboard-config-ui-group.png similarity index 100% rename from src/node-red/integration-technologies/images/dashboard-config-ui-group.png rename to nuxt/content-guides/node-red/integration-technologies/images/dashboard-config-ui-group.png diff --git a/src/node-red/integration-technologies/images/http-get-npmapi.png b/nuxt/content-guides/node-red/integration-technologies/images/http-get-npmapi.png similarity index 100% rename from src/node-red/integration-technologies/images/http-get-npmapi.png rename to nuxt/content-guides/node-red/integration-technologies/images/http-get-npmapi.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-change-node-formating-sensor-data.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change-node-formating-sensor-data.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-change-node-formating-sensor-data.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change-node-formating-sensor-data.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-change-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-change-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-change1-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change1-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-change1-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change1-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-change2-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change2-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-change2-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-change2-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-dht-sensor-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-dht-sensor-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-dht-sensor-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-dht-sensor-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-diagram.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-diagram.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-diagram.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-diagram.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-flow.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-flow.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-flow.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-flow.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-function-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-function-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-function-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-function-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-how-webhook-works.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-how-webhook-works.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-how-webhook-works.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-how-webhook-works.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-http-in-node-endpoint-for-receiving-data-from-server-2.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-http-in-node-endpoint-for-receiving-data-from-server-2.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-http-in-node-endpoint-for-receiving-data-from-server-2.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-http-in-node-endpoint-for-receiving-data-from-server-2.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-request-node-sending-request-to-server3.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-request-node-sending-request-to-server3.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-request-node-sending-request-to-server3.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-request-node-sending-request-to-server3.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-scheduled-maintenance-table-dashboard-view.webm b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-scheduled-maintenance-table-dashboard-view.webm similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-scheduled-maintenance-table-dashboard-view.webm rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-scheduled-maintenance-table-dashboard-view.webm diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-sensor2-inject-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-sensor2-inject-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-sensor2-inject-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-sensor2-inject-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-server-1-instance.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-server-1-instance.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-server-1-instance.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-server-1-instance.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-server-2-instance.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-server-2-instance.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-server-2-instance.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-server-2-instance.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-server-3-instance.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-server-3-instance.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-server-3-instance.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-server-3-instance.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-switch-node.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-switch-node.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-switch-node.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-switch-node.png diff --git a/src/node-red/integration-technologies/images/using-webhook-with-node-red-webhook-trigger.png b/nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-webhook-trigger.png similarity index 100% rename from src/node-red/integration-technologies/images/using-webhook-with-node-red-webhook-trigger.png rename to nuxt/content-guides/node-red/integration-technologies/images/using-webhook-with-node-red-webhook-trigger.png diff --git a/src/node-red/integration-technologies/images/webhooks-node-red.png b/nuxt/content-guides/node-red/integration-technologies/images/webhooks-node-red.png similarity index 100% rename from src/node-red/integration-technologies/images/webhooks-node-red.png rename to nuxt/content-guides/node-red/integration-technologies/images/webhooks-node-red.png diff --git a/src/node-red/integration-technologies/rest.md b/nuxt/content-guides/node-red/integration-technologies/rest.md similarity index 95% rename from src/node-red/integration-technologies/rest.md rename to nuxt/content-guides/node-red/integration-technologies/rest.md index ed358518b4..a25e51f494 100644 --- a/src/node-red/integration-technologies/rest.md +++ b/nuxt/content-guides/node-red/integration-technologies/rest.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: REST API - parent: "Integration Technologies" +title: Creating REST API's with Node-RED +metaTitle: "Creating REST API's with Node-RED" +navTitle: REST API +navOrder: 50 meta: - title: Creating REST API's with Node-RED - description: Learn how to create REST APIs in Node-RED and fetch data from an API. - keywords: node-red, flowfuse, rest api + description: Learn how to create REST APIs in Node-RED and fetch data from an API. + keywords: node-red, flowfuse, rest api --- -# {{meta.title}} +# Creating REST API's with Node-RED REST APIs are how applications talk to each other over the web. They use standard HTTP methods (GET, POST, PUT, DELETE) to send and receive data, usually in JSON format. This guide shows you how to build your own REST APIs in Node-RED and how to pull data from existing APIs. @@ -19,9 +19,11 @@ REST APIs are how applications talk to each other over the web. They use standar 3. Then Drag an http response node, in it and set the status code if want. 4. Connect the "http-in" node's output to the input of the function node and the function node's output to the input of the http response node. -{% renderFlow %} +::render-flow +```json [{"id":"27333f67794bdc72","type":"http in","z":"977143edb097b685","name":"","url":"/test","method":"get","upload":false,"swaggerDoc":"","x":320,"y":220,"wires":[["f351033226953150"]]},{"id":"a7ee48616541a36a","type":"http response","z":"977143edb097b685","name":"","statusCode":"200","headers":{},"x":760,"y":220,"wires":[]},{"id":"dcfc8d1126f139d5","type":"comment","z":"977143edb097b685","name":"Http-in node created API sending todo list as response","info":"","x":540,"y":140,"wires":[]},{"id":"f351033226953150","type":"change","z":"977143edb097b685","name":"","rules":[{"t":"set","p":"payload","pt":"msg","to":"todos","tot":"global"}],"action":"","property":"","from":"","to":"","reg":false,"x":540,"y":220,"wires":[["a7ee48616541a36a"]]}] -{% endrenderFlow %} +``` +:: ## Creating a POST, PUT, and DELETE API @@ -30,17 +32,23 @@ REST APIs are how applications talk to each other over the web. They use standar 3. Drag an "HTTP Response" node onto the workspace. Configure it and set the status code if needed. 4. Connect the output of the "HTTP In" node to the input of the node handling your application logic (e.g., Change node for DELETE operation). Then, connect the output of this node to the input of the HTTP Response node. -{% renderFlow %} +::render-flow +```json [{"id":"8893fc84b3391b34","type":"http in","z":"977143edb097b685","name":"","url":"/todo/delete","method":"delete","upload":true,"swaggerDoc":"","x":250,"y":720,"wires":[["088808484586fdc1"]]},{"id":"04a60e7af4d6d522","type":"http response","z":"977143edb097b685","name":"","statusCode":"204","headers":{},"x":740,"y":720,"wires":[]},{"id":"088808484586fdc1","type":"function","z":"977143edb097b685","name":"Delete the todo item","func":"let todoList = global.get('todos') || [];\nlet id = msg.payload.id;\n\n// Find the index of the item to delete\nlet index = todoList.findIndex(item => item.id === id);\n\nif (index !== -1) {\n // Remove the item from the todoList array\n todoList.splice(index, 1);\n global.set('todos', todoList);\n msg.payload = \"Item deleted successfully.\";\n msg.statusCode = 204; // No Content\n} else {\n msg.payload = \"Item not found.\";\n msg.statusCode = 404; // Not Found\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":520,"y":720,"wires":[["04a60e7af4d6d522"]]}] -{% endrenderFlow %} +``` +:: -{% renderFlow %} +::render-flow +```json [{"id":"825a6296456b7c27","type":"http in","z":"977143edb097b685","name":"","url":"/todo","method":"post","upload":true,"swaggerDoc":"","x":260,"y":1460,"wires":[["0133494821cf99ca","9fe48410514631f8"]]},{"id":"0133494821cf99ca","type":"debug","z":"977143edb097b685","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":540,"y":1420,"wires":[]},{"id":"5284a06365a7f8f7","type":"http response","z":"977143edb097b685","name":"","statusCode":"201","headers":{},"x":860,"y":1500,"wires":[]},{"id":"9fe48410514631f8","type":"function","z":"977143edb097b685","name":"store todo in todolist ","func":"let todoList = global.get('todos') || [];\nlet newTodo = msg.payload;\n\ntodoList.push(newTodo);\nglobal.set('todos',todoList)\nreturn msg;","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":560,"y":1500,"wires":[["5284a06365a7f8f7"]]}] -{% endrenderFlow %} +``` +:: -{% renderFlow %} +::render-flow +```json [{"id":"8893fc84b3391b34","type":"http in","z":"977143edb097b685","name":"","url":"/todo/update","method":"put","upload":true,"swaggerDoc":"","x":400,"y":280,"wires":[["088808484586fdc1"]]},{"id":"04a60e7af4d6d522","type":"http response","z":"977143edb097b685","name":"","statusCode":"200","headers":{},"x":900,"y":280,"wires":[]},{"id":"088808484586fdc1","type":"function","z":"977143edb097b685","name":"update the todo item","func":"let todoList = global.get('todos') || [];\nlet id = msg.payload.id;\nlet newTodo = msg.payload.newtodo;\n\n// Find the index of the item to update\nlet index = todoList.findIndex(item => item.id === id);\n\nif (index !== -1) {\n // Update the todo item\n todoList[index].task = newTodo;\n global.set('todos', todoList);\n msg.payload = \"Item updated successfully.\";\n msg.statusCode = 200; // OK\n} else {\n msg.payload = \"Item not found.\";\n msg.statusCode = 404; // Not Found\n}\n\nreturn msg;\n","outputs":1,"timeout":0,"noerr":0,"initialize":"","finalize":"","libs":[],"x":660,"y":280,"wires":[["04a60e7af4d6d522"]]},{"id":"ea3e7e96bbccf530","type":"comment","z":"977143edb097b685","name":"Http in node created api for updating the todo item","info":"","x":650,"y":200,"wires":[]}] -{% endrenderFlow %} +``` +:: For more details, refer to the [CRUD API Blueprint](/blueprints/getting-started/crud/), where we have created CRUD APIs to store, retrieve, delete, and update the data from MongoDB database. @@ -112,9 +120,11 @@ For the example we will fetch the data of [Node-RED Dashboard 2.0](/platform/das A simple flow to fetch data from npm registry would be: -{% renderFlow %} +::render-flow +```json [{"id":"32b083d0ca67265f","type":"inject","z":"977143edb097b685","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":240,"y":1100,"wires":[["53db14b9a848d5ce"]]},{"id":"53db14b9a848d5ce","type":"http request","z":"977143edb097b685","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.npmjs.org/downloads/range/last-month/@flowforge/node-red-dashboard","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":410,"y":1100,"wires":[["9e08fa8d25a19f24"]]},{"id":"9e08fa8d25a19f24","type":"debug","z":"977143edb097b685","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":580,"y":1100,"wires":[]}] -{% endrenderFlow %} +``` +:: Where we paste the API URL into the settings panel: @@ -123,13 +133,13 @@ Where we paste the API URL into the settings panel: When running this flow you'll see a blob of text in the `Debug` pane. This is a great first start, but a blob isn't useful for the rest of the flow. -We need to parse the data as JSON. While the [JSON node](/node-red/core-nodes/json) +We need to parse the data as JSON. While the [JSON node](/docs/node-red/core-nodes/parsers/json/) would work, the HTTP request node can do this natively. Let `a parsed JSON object` the `Return` settings of the HTTP request node. So now we got the data, and a little more than we need, so let's change the message output to keep only what we're interested in; `payload.downloads`. To -do this, we'll use the [change node](/node-red/core-nodes/change). +do this, we'll use the [change node](/docs/node-red/core-nodes/function/change/). ![Change node to set the payload with downloads](./images/change-node-set-downloads-payload.png "Change node to set the payload") @@ -154,7 +164,7 @@ The default theme is great, so just accept that, and save all dialogs to continu #### Normalizing the data The data for the chart needs to be changed before we can show it. The messages should have a `x` and `y` key. So let's prepare the data with -a combination of the [Split](/node-red/core-nodes/split) and change node. +a combination of the [Split](/docs/node-red/core-nodes/sequence/split/) and change node. The Split node with the default configuration allows to 30 elements of the array to be mapped individually. The change node will set the `payload.x` and `payload.y` @@ -170,7 +180,7 @@ Connect the change node output to a new chart node, and voila: While we created a chart and it has some data, there's one more thing to explain. How can the data be kept up-to-date? It's straight forward to have the `Inject` -node [run every night](/node-red/core-nodes/inject/), +node [run every night](/docs/node-red/core-nodes/common/inject/), but the chart would now have multiple data points for the same day. This paints multiple lines on top of each other. While that works, the hover of the chart will display the duplication and it's wastefull. @@ -180,6 +190,8 @@ So before we update the chart we need to send a message to the chart where the That way the chart is emptied first, and right afterwards it will receive the new data to write. -{% renderFlow %} +::render-flow +```json [{"id":"da9a67e8c3ea7742","type":"inject","z":"977143edb097b685","name":"","props":[{"p":"payload"}],"repeat":"","crontab":"00 12 * * *","once":false,"onceDelay":0.1,"topic":"","payload":"[]","payloadType":"json","x":250,"y":960,"wires":[["efd22a89abc3c06f","a6851a41dba2c39b"]]},{"id":"a6851a41dba2c39b","type":"http request","z":"977143edb097b685","name":"","method":"GET","ret":"obj","paytoqs":"ignore","url":"https://api.npmjs.org/downloads/range/last-month/@flowforge/node-red-dashboard","tls":"","persist":false,"proxy":"","insecureHTTPParser":false,"authType":"","senderr":false,"headers":[],"x":430,"y":960,"wires":[["7931f7457880f7c3"]]},{"id":"7931f7457880f7c3","type":"change","z":"977143edb097b685","name":"Only get the Downloads","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.downloads","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":650,"y":960,"wires":[["74e3b15c7b09726a"]]},{"id":"74e3b15c7b09726a","type":"link out","z":"977143edb097b685","name":"link out 1","mode":"link","links":["43e4aff34b989e83"],"x":815,"y":960,"wires":[]},{"id":"43e4aff34b989e83","type":"link in","z":"977143edb097b685","name":"Normalize daily data","links":["74e3b15c7b09726a"],"x":195,"y":1040,"wires":[["ca8c62bfbdf75715"]]},{"id":"ca8c62bfbdf75715","type":"split","z":"977143edb097b685","name":"","splt":"\\n","spltType":"str","arraySplt":1,"arraySpltType":"len","stream":false,"addname":"","x":310,"y":1040,"wires":[["3f462d2c7e3bca50"]]},{"id":"3f462d2c7e3bca50","type":"change","z":"977143edb097b685","name":"Prepare data for the chart","rules":[{"t":"set","p":"payload.x","pt":"msg","to":"$toMillis(payload.day)","tot":"jsonata"},{"t":"set","p":"payload.y","pt":"msg","to":"payload.downloads","tot":"msg"},{"t":"delete","p":"payload.day","pt":"msg"},{"t":"delete","p":"payload.downloads","pt":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":510,"y":1040,"wires":[["f4e6a85b8cb8dac0"]]},{"id":"4b71fc28c2da66e7","type":"ui-chart","z":"977143edb097b685","group":"bac8effac57694e1","name":"","label":"Daily Downloads","order":9007199254740991,"chartType":"line","xAxisType":"time","removeOlder":1,"removeOlderUnit":"3600","removeOlderPoints":"","colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"width":0,"height":0,"className":"","x":330,"y":1120,"wires":[[]]},{"id":"f4e6a85b8cb8dac0","type":"link out","z":"977143edb097b685","name":"link out 2","mode":"link","links":["6f7068445bfe4311"],"x":675,"y":1040,"wires":[]},{"id":"6f7068445bfe4311","type":"link in","z":"977143edb097b685","name":"Update the chart","links":["f4e6a85b8cb8dac0","79067215ee592ec9","efd22a89abc3c06f"],"x":195,"y":1120,"wires":[["4b71fc28c2da66e7"]]},{"id":"efd22a89abc3c06f","type":"link out","z":"977143edb097b685","name":"link out 3","mode":"link","links":["6f7068445bfe4311"],"x":415,"y":920,"wires":[]},{"id":"bac8effac57694e1","type":"ui-group","name":"NPM Downloads","page":"f10b4d0259e43aeb","width":"6","height":"1","order":-1},{"id":"f10b4d0259e43aeb","type":"ui-page","name":"Main","ui":"cb79bc4520925e32","path":"/","layout":"grid","theme":"2c5d702b11de7dd1","order":-1},{"id":"cb79bc4520925e32","type":"ui-base","name":"My UI","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false},{"id":"2c5d702b11de7dd1","type":"ui-theme","name":"Theme Name","colors":{"surface":"#ffffff","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"}}] -{% endrenderFlow %} \ No newline at end of file +``` +:: diff --git a/src/node-red/integration-technologies/webhook.md b/nuxt/content-guides/node-red/integration-technologies/webhook.md similarity index 96% rename from src/node-red/integration-technologies/webhook.md rename to nuxt/content-guides/node-red/integration-technologies/webhook.md index 41cb0e98f4..e6e8432d05 100644 --- a/src/node-red/integration-technologies/webhook.md +++ b/nuxt/content-guides/node-red/integration-technologies/webhook.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: Webhook - parent: "Integration Technologies" +title: Using Webhook with Node-RED +metaTitle: "Using Webhook with Node-RED" +navTitle: Webhook +navOrder: 50 meta: - title: Using Webhook with Node-RED - description: Learn how to seamlessly integrate webhooks into your Node-RED applications for automating tasks and enhancing communication. - keywords: node-red, flowfuse, webhooks, automation, event-driven -image: /node-red/hardware/images/webhooks-node-red.png + description: Learn how to seamlessly integrate webhooks into your Node-RED applications for automating tasks and enhancing communication. + keywords: node-red, flowfuse, webhooks, automation, event-driven +image: /docs/node-red/hardware/images/webhooks-node-red.png --- -# {{meta.title}} +# Using Webhook with Node-RED Webhooks let different systems talk to each other automatically when something happens. Instead of constantly asking "anything new?", one system just tells the other "hey, this just happened." This guide shows you how to set up webhooks in Node-RED, using a real manufacturing example where temperature sensors trigger maintenance alerts. @@ -173,4 +173,4 @@ return msg; 1. With your flow updated to include the above, click the "Deploy" button in the top-right corner of the Node-RED Editor in each Node-RED instance. 2. In server 3 Node-RED instance (Maintenance scheduling system), Locate the 'Open Dashboard' button at the top-right corner of the Dashboard 2.0 sidebar and click on it to navigate to the dashboard. - \ No newline at end of file + diff --git a/src/node-red/keyboard/index.md b/nuxt/content-guides/node-red/keyboard/README.md similarity index 94% rename from src/node-red/keyboard/index.md rename to nuxt/content-guides/node-red/keyboard/README.md index 5063be5d68..ffd65ee868 100644 --- a/src/node-red/keyboard/index.md +++ b/nuxt/content-guides/node-red/keyboard/README.md @@ -1,11 +1,11 @@ --- -eleventyNavigation: - key: Keyboard Shortcuts - order: 3 +title: Node-RED Keyboard Shortcuts +metaTitle: "Node-RED Keyboard Shortcuts" +navTitle: Keyboard shortcuts +navOrder: 11 meta: - title: Node-RED Keyboard Shortcuts - description: A comprehensive list of keyboard shortcuts for Node-RED to enhance productivity and streamline workflow. - keywords: node-red, node-red keyboard shortcuts + description: A comprehensive list of keyboard shortcuts for Node-RED to enhance productivity and streamline workflow. + keywords: node-red, node-red keyboard shortcuts --- # Node-RED Keyboard Shortcuts @@ -110,4 +110,4 @@ Node-RED lets you customize keyboard shortcuts to fit your workflow, making it e 1. To set custom keyboard shortcuts, go to the keyboard settings in the user settings. Click `Shift + ?` or click the top-right menu icon and select "Settings." In the settings menu, switch to "Keyboard Settings." 2. In the Keyboard Settings, you will see actions with assigned shortcuts as well as those that are unassigned. To change or set shortcuts, click on "Unassigned" or the existing shortcut next to the action you want to modify. -3. Enter your preferred key combination for the action. Then, select the appropriate scope and click the check icon to save your changes. Your new shortcuts will now be active. \ No newline at end of file +3. Enter your preferred key combination for the action. Then, select the appropriate scope and click the check icon to save your changes. Your new shortcuts will now be active. diff --git a/nuxt/content-guides/node-red/notification/README.md b/nuxt/content-guides/node-red/notification/README.md new file mode 100644 index 0000000000..d8c4b5216c --- /dev/null +++ b/nuxt/content-guides/node-red/notification/README.md @@ -0,0 +1,21 @@ +--- +title: Notification Services in Node-RED +metaTitle: "Notification Services in Node-RED" +navTitle: Notification services +navOrder: 7 +meta: + description: Learn how to integrate various notification services with Node-RED for real-time alerts and messaging. + keywords: node-red, notification services, email notifications, Telegram notifications, Slack notifications, WhatsApp notifications +--- + +# Notification Services + +Real-time notifications are essential in our automation world, helping to keep us informed, responsive, and efficient. Whether it's a critical system alert, a customer inquiry, or a simple reminder, timely notifications can significantly enhance productivity and efficiency. However, managing and integrating such services can be challenging in traditional development environments. Node-RED simplifies this process by supporting a wide range of notification services, including email, Telegram, Slack, WhatsApp, and more. + +## Resources + +Here are some resources to help you get started with Node-RED on diffrent notification services: + +- [Sending and receiving Discord messages with Node-RED](/docs/node-red/notification/discord/): Learn how to send and receive Discord messages with Node-RED. +- [Sending and receiving emails with Node-RED](/docs/node-red/notification/email/): Learn how to send and receive emails using Node-RED, along with best practices for sending email notifications. +- [Sending and receiving Telegram messages with Node-RED](/docs/node-red/notification/telegram/): Learn to seamlessly integrate Telegram with Node-RED for messaging. Create bots, obtain chat IDs, and send/receive messages, including group messaging. diff --git a/src/node-red/notification/discord.md b/nuxt/content-guides/node-red/notification/discord.md similarity index 91% rename from src/node-red/notification/discord.md rename to nuxt/content-guides/node-red/notification/discord.md index a95f21645e..b8ddc77ca1 100644 --- a/src/node-red/notification/discord.md +++ b/nuxt/content-guides/node-red/notification/discord.md @@ -1,21 +1,21 @@ --- -eleventyNavigation: - key: Discord - parent: "Notification Services" +title: Sending and receiving Discord messages with Node-RED +metaTitle: "Sending and receiving Discord messages with Node-RED" +navTitle: Discord +navOrder: 50 meta: - title: Sending and receiving Discord messages with Node-RED - description: Learn how to send and receive Discord messages with Node-RED. - keywords: nodered discord, node red discord -image: /node-red/notification/images/sending-and-receiving-discord-message-with-node-red.png + description: Learn how to send and receive Discord messages with Node-RED. + keywords: nodered discord, node red discord +image: /docs/node-red/notification/images/sending-and-receiving-discord-message-with-node-red.png --- -# {{meta.title}} +# Sending and receiving Discord messages with Node-RED This guide explains how to integrate Discord with Node-RED to send and receive messages. You'll learn how to configure a Discord bot, send messages to users and channels, and handle incoming messages. Discord is commonly used for notifications in IoT applications. This document covers the setup process and includes troubleshooting steps for common integration issues. -For information on integrating other notification services, see the guides on [Email](/node-red/notification/email/) and [Telegram](/node-red/notification/telegram/). +For information on integrating other notification services, see the guides on [Email](/docs/node-red/notification/email/) and [Telegram](/docs/node-red/notification/telegram/). @@ -69,7 +69,7 @@ To send a message to a user, you will need the ID of that user. Before copying t 3. Set the `msg.payload` to the message you want to send and the `msg.user` to the user ID of the user you want to send the message to. 4. Connect the Inject node's output to the input of the DiscordMessageManager node. - + ## Sending messages to the Discord server @@ -98,7 +98,7 @@ To send a message to the Discord server, you have to make sure that your bot is 3. Connect the Inject node's output to the input of the DiscordMessageManager node. - + ## Receiving messages from Discord @@ -108,13 +108,15 @@ To send a message to the Discord server, you have to make sure that your bot is After deploying the flow, you will start receiving messages sent to your bot. In the debug panel in the sidebar, you will see the message object printed for each message, which contains different objects. Each object shows different details; for example, the author object contains details about the sender, and the channel object includes information of the channel if the message was sent in a channel. - + Below, I have provided the complete flow we built throughout the guide. Make sure to replace the environment variable 'BOT_TOKEN' with your actual bot token. -{% renderFlow %} +::render-flow +```json [{"id":"7a8dd49f9614608e","type":"inject","z":"4674ed668685adf6","name":"Sending mesasge to Discord server 's channel","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, This is from Node-RED","payloadType":"str","x":570,"y":420,"wires":[["3984c20a52db033f"]]},{"id":"3984c20a52db033f","type":"discordMessageManager","z":"4674ed668685adf6","name":"","channel":"56454645657765656","token":"","x":930,"y":420,"wires":[["b99ae75425047b6b"]]},{"id":"b99ae75425047b6b","type":"debug","z":"4674ed668685adf6","name":"debug 3","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1200,"y":420,"wires":[]},{"id":"6af740e11aba6ca2","type":"inject","z":"4674ed668685adf6","name":"Sending message to Discord user","props":[{"p":"payload"},{"p":"user","v":"65454534534345365","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Hello, Sumit","payloadType":"str","x":550,"y":320,"wires":[["f69363f341b62333"]]},{"id":"f69363f341b62333","type":"discordMessageManager","z":"4674ed668685adf6","name":"","channel":"","token":"","x":930,"y":320,"wires":[["c5001a783992ae01"]]},{"id":"c5001a783992ae01","type":"debug","z":"4674ed668685adf6","name":"debug 4","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":1220,"y":320,"wires":[]},{"id":"820b70826545a401","type":"debug","z":"4674ed668685adf6","name":"debug 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":1020,"y":540,"wires":[]},{"id":"f044d3784ef8c74a","type":"discordMessage","z":"4674ed668685adf6","name":"","channelIdFilter":"","token":"","x":720,"y":540,"wires":[["820b70826545a401"]]}] -{% endrenderFlow %} +``` +:: ## Debugging and Troubleshooting diff --git a/src/node-red/notification/email.md b/nuxt/content-guides/node-red/notification/email.md similarity index 95% rename from src/node-red/notification/email.md rename to nuxt/content-guides/node-red/notification/email.md index 82bc9d1ee0..158394c086 100644 --- a/src/node-red/notification/email.md +++ b/nuxt/content-guides/node-red/notification/email.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: Email - parent: "Notification Services" +title: Sending and receiving emails with Node-RED +metaTitle: "Sending and receiving emails with Node-RED" +navTitle: Email +navOrder: 50 meta: - title: Sending and receiving emails with Node-RED - description: Learn how to send and receive emails using Node-RED, along with best practices for sending email notifications. - keywords: node-red, gmail, flowfuse device agent, anti-spam measures, email] -image: /node-red/notification/images/email_node-red.png + description: Learn how to send and receive emails using Node-RED, along with best practices for sending email notifications. + keywords: node-red, gmail, flowfuse device agent, anti-spam measures, email] +image: /docs/node-red/notification/images/email_node-red.png --- -# {{meta.title}} +# Sending and receiving emails with Node-RED This guide shows you how to integrate email with Node-RED for sending and receiving messages. You'll learn how to configure email nodes, set up Gmail integration, and follow best practices to ensure your notifications reach their destination. @@ -157,4 +157,4 @@ Even legitimate emails can sometimes trigger spam filters. Follow these practice - **Authenticate your emails** - Implement SPF, DKIM, and DMARC protocols to verify your email's legitimacy and improve deliverability. - **Manage your sending frequency** - Avoid sending too many emails in a short period. Maintain a consistent schedule and ensure each message provides value. - **Keep your email list clean** - Regularly remove invalid or inactive addresses. High bounce rates and spam complaints damage your sender reputation. -- **Monitor your reputation** - Use tools like SenderScore or Google Postmaster Tools to track your sender reputation and identify potential issues before they affect delivery. \ No newline at end of file +- **Monitor your reputation** - Use tools like SenderScore or Google Postmaster Tools to track your sender reputation and identify potential issues before they affect delivery. diff --git a/src/node-red/notification/images/discord-with-node-red-bot-reset-token.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-bot-reset-token.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-bot-reset-token.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-bot-reset-token.png diff --git a/src/node-red/notification/images/discord-with-node-red-channel-id.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-channel-id.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-channel-id.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-channel-id.png diff --git a/src/node-red/notification/images/discord-with-node-red-conform-add-to-server.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-conform-add-to-server.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-conform-add-to-server.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-conform-add-to-server.png diff --git a/src/node-red/notification/images/discord-with-node-red-create-app.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-create-app.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-create-app.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-create-app.png diff --git a/src/node-red/notification/images/discord-with-node-red-developer-mode.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-developer-mode.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-developer-mode.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-developer-mode.png diff --git a/src/node-red/notification/images/discord-with-node-red-error-1.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-error-1.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-error-1.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-error-1.png diff --git a/src/node-red/notification/images/discord-with-node-red-error-2.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-error-2.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-error-2.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-error-2.png diff --git a/src/node-red/notification/images/discord-with-node-red-error-3.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-error-3.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-error-3.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-error-3.png diff --git a/src/node-red/notification/images/discord-with-node-red-new-application-button.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-new-application-button.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-new-application-button.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-new-application-button.png diff --git a/src/node-red/notification/images/discord-with-node-red-oauth-copy-url.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-oauth-copy-url.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-oauth-copy-url.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-oauth-copy-url.png diff --git a/src/node-red/notification/images/discord-with-node-red-oauth-scope.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-oauth-scope.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-oauth-scope.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-oauth-scope.png diff --git a/src/node-red/notification/images/discord-with-node-red-outh-permission.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-outh-permission.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-outh-permission.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-outh-permission.png diff --git a/src/node-red/notification/images/discord-with-node-red-privillage-itents.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-privillage-itents.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-privillage-itents.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-privillage-itents.png diff --git a/src/node-red/notification/images/discord-with-node-red-receiving-messages.webm b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-receiving-messages.webm similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-receiving-messages.webm rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-receiving-messages.webm diff --git a/src/node-red/notification/images/discord-with-node-red-select-the-server.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-select-the-server.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-select-the-server.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-select-the-server.png diff --git a/src/node-red/notification/images/discord-with-node-red-sending-msg-to-server.webm b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-sending-msg-to-server.webm similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-sending-msg-to-server.webm rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-sending-msg-to-server.webm diff --git a/src/node-red/notification/images/discord-with-node-red-sending-msg-to-user.webm b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-sending-msg-to-user.webm similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-sending-msg-to-user.webm rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-sending-msg-to-user.webm diff --git a/src/node-red/notification/images/discord-with-node-red-user-id.png b/nuxt/content-guides/node-red/notification/images/discord-with-node-red-user-id.png similarity index 100% rename from src/node-red/notification/images/discord-with-node-red-user-id.png rename to nuxt/content-guides/node-red/notification/images/discord-with-node-red-user-id.png diff --git a/src/node-red/notification/images/email_node-red.png b/nuxt/content-guides/node-red/notification/images/email_node-red.png similarity index 100% rename from src/node-red/notification/images/email_node-red.png rename to nuxt/content-guides/node-red/notification/images/email_node-red.png diff --git a/src/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-in-node-configuration.png b/nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-in-node-configuration.png similarity index 100% rename from src/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-in-node-configuration.png rename to nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-in-node-configuration.png diff --git a/src/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-node-configuration.png b/nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-node-configuration.png similarity index 100% rename from src/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-node-configuration.png rename to nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-e-mail-node-configuration.png diff --git a/src/node-red/notification/images/sending-and-receiving-email-with-node-red-gmail-inbox.png b/nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-gmail-inbox.png similarity index 100% rename from src/node-red/notification/images/sending-and-receiving-email-with-node-red-gmail-inbox.png rename to nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-gmail-inbox.png diff --git a/src/node-red/notification/images/sending-and-receiving-email-with-node-red-inject-node.png b/nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-inject-node.png similarity index 100% rename from src/node-red/notification/images/sending-and-receiving-email-with-node-red-inject-node.png rename to nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-inject-node.png diff --git a/src/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red-flow.png b/nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red-flow.png similarity index 100% rename from src/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red-flow.png rename to nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red-flow.png diff --git a/src/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red_setting_environment_variables.png b/nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red_setting_environment_variables.png similarity index 100% rename from src/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red_setting_environment_variables.png rename to nuxt/content-guides/node-red/notification/images/sending-and-receiving-email-with-node-red-node-red_setting_environment_variables.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-activating-bot.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-activating-bot.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-activating-bot.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-activating-bot.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-botfather.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-botfather.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-botfather.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-botfather.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-change-node.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-change-node.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-change-node.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-change-node.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-enabling-send-error-to-second-option.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-enabling-send-error-to-second-option.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-enabling-send-error-to-second-option.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-enabling-send-error-to-second-option.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-flow.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-flow.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-flow.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-flow.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-flowfue-instance-settings.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-flowfue-instance-settings.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-flowfue-instance-settings.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-flowfue-instance-settings.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-getmyid.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-getmyid.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-getmyid.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-getmyid.png diff --git a/src/node-red/notification/images/sending-telegram-with-node-red-telegram-node-configuration.png b/nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-telegram-node-configuration.png similarity index 100% rename from src/node-red/notification/images/sending-telegram-with-node-red-telegram-node-configuration.png rename to nuxt/content-guides/node-red/notification/images/sending-telegram-with-node-red-telegram-node-configuration.png diff --git a/src/node-red/notification/images/telegram_node-red.png b/nuxt/content-guides/node-red/notification/images/telegram_node-red.png similarity index 100% rename from src/node-red/notification/images/telegram_node-red.png rename to nuxt/content-guides/node-red/notification/images/telegram_node-red.png diff --git a/src/node-red/notification/telegram.md b/nuxt/content-guides/node-red/notification/telegram.md similarity index 92% rename from src/node-red/notification/telegram.md rename to nuxt/content-guides/node-red/notification/telegram.md index 5915a23aa7..abdfb63ea2 100644 --- a/src/node-red/notification/telegram.md +++ b/nuxt/content-guides/node-red/notification/telegram.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: Telegram - parent: "Notification Services" +title: Sending and receiving Telegram messages with Node-RED +metaTitle: "Sending and receiving Telegram messages with Node-RED" +navTitle: Telegram +navOrder: 50 meta: - title: Sending and receiving Telegram messages with Node-RED - description: Learn to seamlessly integrate Telegram with Node-RED for messaging. Create bots, obtain chat IDs, and send/receive messages, including group messaging. - keywords: node-red, telegram, bot -image: /node-red/notification/images/telegram_node-red.png + description: Learn to seamlessly integrate Telegram with Node-RED for messaging. Create bots, obtain chat IDs, and send/receive messages, including group messaging. + keywords: node-red, telegram, bot +image: /docs/node-red/notification/images/telegram_node-red.png --- -# {{meta.title}} +# Sending and receiving Telegram messages with Node-RED Telegram has become a popular choice for messaging in home automation applications. This guide shows you how to integrate Telegram with Node-RED, covering bot creation, chat ID retrieval, and both sending and receiving messages. @@ -102,4 +102,4 @@ Environment variables keep your sensitive information secure by preventing it fr 1. Deploy the flow by clicking the Deploy button in the top-right corner. -Your Telegram bot is now ready to use. Click the Inject button to send a message, and you'll receive a notification in Telegram. You can also check your bot's chat to see messages sent via Node-RED. To test receiving messages, send a message to your bot and watch the Debug panel display the message object containing the message content and additional information. \ No newline at end of file +Your Telegram bot is now ready to use. Click the Inject button to send a message, and you'll receive a notification in Telegram. You can also check your bot's chat to see messages sent via Node-RED. To test receiving messages, send a message to your bot and watch the Debug panel display the message object containing the message content and additional information. diff --git a/src/node-red/peripheral/index.md b/nuxt/content-guides/node-red/peripheral/README.md similarity index 53% rename from src/node-red/peripheral/index.md rename to nuxt/content-guides/node-red/peripheral/README.md index 56fe7db2a9..cb8ade0103 100644 --- a/src/node-red/peripheral/index.md +++ b/nuxt/content-guides/node-red/peripheral/README.md @@ -1,13 +1,13 @@ --- -eleventyNavigation: - key: "Peripheral Devices" - order: 7 +title: Integrating Node-RED with Peripheral Devices +metaTitle: "Integrating Node-RED with Peripheral Devices" +navTitle: Peripheral devices +navOrder: 9 meta: - title: Integrating Node-RED with Peripheral Devices - description: Learn how to integrate Node-RED with various peripheral devices, including keyboards, mice, printers, and more. - keywords: node-red, flowfuse, peripheral devices, webcam, barcode scanner + description: Learn how to integrate Node-RED with various peripheral devices, including keyboards, mice, printers, and more. + keywords: node-red, flowfuse, peripheral devices, webcam, barcode scanner --- # Peripheral Devices -Node-RED supports a wide range of peripheral devices, allowing users to connect Node-RED to many external inputs and outputs. This enables the creation of interactive and automated systems where data from peripheral devices can trigger actions or responses in the digital realm. By bridging the gap between software and hardware, Node-RED expands its applicability to a wide range of use cases. \ No newline at end of file +Node-RED supports a wide range of peripheral devices, allowing users to connect Node-RED to many external inputs and outputs. This enables the creation of interactive and automated systems where data from peripheral devices can trigger actions or responses in the digital realm. By bridging the gap between software and hardware, Node-RED expands its applicability to a wide range of use cases. diff --git a/src/node-red/peripheral/ardiuno.md b/nuxt/content-guides/node-red/peripheral/ardiuno.md similarity index 90% rename from src/node-red/peripheral/ardiuno.md rename to nuxt/content-guides/node-red/peripheral/ardiuno.md index ab42f332fa..c2386fce16 100644 --- a/src/node-red/peripheral/ardiuno.md +++ b/nuxt/content-guides/node-red/peripheral/ardiuno.md @@ -1,15 +1,15 @@ --- -eleventyNavigation: - key: "Arduino" - parent: "Peripheral Devices" +title: Connecting Arduino to Node-RED +metaTitle: "Connecting Arduino to Node-RED" +navTitle: Arduino +navOrder: 50 meta: - title: Connecting Arduino to Node-RED - description: Learn how to control and monitor Arduino hardware using Node-RED - keywords: node-red, flowfuse, ardiuno -image: /node-red/peripheral/images/barcode-scanner.png + description: Learn how to control and monitor Arduino hardware using Node-RED + keywords: node-red, flowfuse, ardiuno +image: /docs/node-red/peripheral/images/barcode-scanner.png --- -# {{meta.title}} +# Connecting Arduino to Node-RED This documentation explains how to use Node-RED to interact with an Arduino board via serial communication using the Firmata protocol. It covers how to write to and read from digital and analog pins using the `node-red-node-arduino` package. diff --git a/src/node-red/peripheral/barcodescanner.md b/nuxt/content-guides/node-red/peripheral/barcodescanner.md similarity index 87% rename from src/node-red/peripheral/barcodescanner.md rename to nuxt/content-guides/node-red/peripheral/barcodescanner.md index 0b39e07010..127a2f64dd 100644 --- a/src/node-red/peripheral/barcodescanner.md +++ b/nuxt/content-guides/node-red/peripheral/barcodescanner.md @@ -1,16 +1,15 @@ --- +title: How to connect a barcode scanner to your Node-RED application metaTitle: "How to Connect a Barcode Scanner to Node-RED" -eleventyNavigation: - key: "Barcode Scanner" - parent: "Peripheral Devices" +navTitle: Barcode Scanner +navOrder: 50 meta: - title: How to connect a barcode scanner to your Node-RED application - description: Learn to seamlessly connect a barcode scanner to Node-RED for efficient data capture and automation. - keywords: node-red, flowfuse, barcodescanner -image: /node-red/peripheral/images/barcode-scanner.png + description: Learn to seamlessly connect a barcode scanner to Node-RED for efficient data capture and automation. + keywords: node-red, flowfuse, barcodescanner +image: /docs/node-red/peripheral/images/barcode-scanner.png --- -# {{meta.title}} +# How to connect a barcode scanner to your Node-RED application Barcode scanners, functioning as Human Interface Devices (HID) similar to keyboards, offer versatile programming options. Variations of barcode scanners can be seen used from anything from checkout counters, logistics, and to manufacturing erp systems. In our case, we kept it basic and we used one to trigger a Node-RED flow, keeping the process straightforward and efficient. Don't let that limit your imagination though, with QR codes, you can store just about anything including recipes in a JSON structure. @@ -34,4 +33,4 @@ You could even take it a step further and create a [QR code](https://smalldev.to ## Linux Setup -Linux users might face a slightly more complicated setup, as access to communication ports isn't always granted by default, and specific drivers are needed for optimal node functionality. This is due to the security around applications having access to specific devices connected to the system. For this, we recommend following the detailed instructions available in the project's [GitHub](https://github.com/gdziuba/node-red-contrib-usbhid) repository. \ No newline at end of file +Linux users might face a slightly more complicated setup, as access to communication ports isn't always granted by default, and specific drivers are needed for optimal node functionality. This is due to the security around applications having access to specific devices connected to the system. For this, we recommend following the detailed instructions available in the project's [GitHub](https://github.com/gdziuba/node-red-contrib-usbhid) repository. diff --git a/src/node-red/peripheral/esp32.md b/nuxt/content-guides/node-red/peripheral/esp32.md similarity index 97% rename from src/node-red/peripheral/esp32.md rename to nuxt/content-guides/node-red/peripheral/esp32.md index 6741765876..39276356a7 100644 --- a/src/node-red/peripheral/esp32.md +++ b/nuxt/content-guides/node-red/peripheral/esp32.md @@ -1,14 +1,14 @@ --- -eleventyNavigation: - key: "ESP32" - parent: "Peripheral Devices" +title: Connect ESP32 with Node-RED using MQTT +metaTitle: "Connect ESP32 with Node-RED using MQTT" +navTitle: ESP32 +navOrder: 50 meta: - title: Connect ESP32 with Node-RED using MQTT description: Learn how to send and receive MQTT messages between ESP32 and Node-RED using FlowFuse. keywords: node-red, flowfuse, esp32, mqtt, esp32 node-red --- -# {{meta.title}} +# Connect ESP32 with Node-RED using MQTT This document outlines the procedure for establishing MQTT communication between an ESP32 microcontroller and a Node-RED instance. @@ -58,9 +58,11 @@ This section provides a practical demonstration with an importable Node-RED flow Import the following JSON into your Node-RED editor. This flow creates a simple dashboard with ON/OFF buttons that publish to the `/esp32/led` topic. You must configure the **`mqtt out`** node with your specific broker credentials. -{% renderFlow 300 %} +::render-flow{:height="300"} +```json [{"id":"59887a8115c95eae","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"02c25e8a30f9379d","type":"ui-base","name":"My Dashboard","path":"/dashboard","appIcon":"","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"showPageTitle":true,"navigationStyle":"default","titleBarStyle":"default"},{"id":"cfb2ab9ff30660fc","type":"ui-theme","name":"Default Theme","colors":{"surface":"#ffffff","primary":"#0094CE","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"density":"default","pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}},{"id":"d263574af6876c7a","type":"ui-page","name":"ESP32","ui":"02c25e8a30f9379d","path":"/page1","icon":"home","layout":"grid","theme":"cfb2ab9ff30660fc","breakpoints":[{"name":"Default","px":"0","cols":"3"},{"name":"Tablet","px":"576","cols":"6"},{"name":"Small Desktop","px":"768","cols":"9"},{"name":"Desktop","px":"1024","cols":"12"}],"order":1,"className":"","visible":"true","disabled":"false"},{"id":"3ae115ea7ede6827","type":"ui-group","name":"Group 1","page":"d263574af6876c7a","width":"6","height":"1","order":1,"showTitle":false,"className":"","visible":"true","disabled":"false","groupType":"default"},{"id":"def97b29f5f7baab","type":"mqtt-broker","name":"","broker":"broker.flowfuse.cloud","port":"1883","clientid":"","autoConnect":true,"usetls":false,"protocolVersion":"4","keepalive":"60","cleansession":true,"autoUnsubscribe":true,"birthTopic":"","birthQos":"0","birthRetain":"false","birthPayload":"","birthMsg":{},"closeTopic":"","closeQos":"0","closeRetain":"false","closePayload":"","closeMsg":{},"willTopic":"","willQos":"0","willRetain":"false","willPayload":"","willMsg":{},"userProps":"","sessionExpiry":""},{"id":"5a9162986a34a4d6","type":"ui-button","z":"59887a8115c95eae","group":"3ae115ea7ede6827","name":"","label":"ON","order":1,"width":"3","height":"2","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"1","payloadType":"num","topic":"topic","topicType":"msg","buttonColor":"green","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":190,"y":120,"wires":[["9239f8a7cca5c858"]]},{"id":"f9c194994d9491a8","type":"ui-button","z":"59887a8115c95eae","group":"3ae115ea7ede6827","name":"","label":"OFF","order":2,"width":"3","height":"2","emulateClick":false,"tooltip":"","color":"","bgcolor":"","className":"","icon":"","iconPosition":"left","payload":"2","payloadType":"num","topic":"topic","topicType":"msg","buttonColor":"red","textColor":"","iconColor":"","enableClick":true,"enablePointerdown":false,"pointerdownPayload":"","pointerdownPayloadType":"str","enablePointerup":false,"pointerupPayload":"","pointerupPayloadType":"str","x":190,"y":160,"wires":[["9239f8a7cca5c858"]]},{"id":"9239f8a7cca5c858","type":"mqtt out","z":"59887a8115c95eae","name":"","topic":"/LedControl","qos":"","retain":"","respTopic":"","contentType":"","userProps":"","correl":"","expiry":"","broker":"def97b29f5f7baab","x":390,"y":140,"wires":[]}] -{% endrenderFlow %} +``` +:: ### 2. ESP32 Demo Code diff --git a/src/node-red/peripheral/images/barcode-scanner.png b/nuxt/content-guides/node-red/peripheral/images/barcode-scanner.png similarity index 100% rename from src/node-red/peripheral/images/barcode-scanner.png rename to nuxt/content-guides/node-red/peripheral/images/barcode-scanner.png diff --git a/src/node-red/peripheral/images/usbhid-barcode-node-red.png b/nuxt/content-guides/node-red/peripheral/images/usbhid-barcode-node-red.png similarity index 100% rename from src/node-red/peripheral/images/usbhid-barcode-node-red.png rename to nuxt/content-guides/node-red/peripheral/images/usbhid-barcode-node-red.png diff --git a/src/node-red/peripheral/images/usbhid-config-node-red.png b/nuxt/content-guides/node-red/peripheral/images/usbhid-config-node-red.png similarity index 100% rename from src/node-red/peripheral/images/usbhid-config-node-red.png rename to nuxt/content-guides/node-red/peripheral/images/usbhid-config-node-red.png diff --git a/src/node-red/peripheral/images/usbhid-qr-pizza-order.png b/nuxt/content-guides/node-red/peripheral/images/usbhid-qr-pizza-order.png similarity index 100% rename from src/node-red/peripheral/images/usbhid-qr-pizza-order.png rename to nuxt/content-guides/node-red/peripheral/images/usbhid-qr-pizza-order.png diff --git a/src/node-red/peripheral/images/usbhid-scanned-barcode.png b/nuxt/content-guides/node-red/peripheral/images/usbhid-scanned-barcode.png similarity index 100% rename from src/node-red/peripheral/images/usbhid-scanned-barcode.png rename to nuxt/content-guides/node-red/peripheral/images/usbhid-scanned-barcode.png diff --git a/src/node-red/peripheral/images/using_webcam_with_node-red.png b/nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red.png similarity index 100% rename from src/node-red/peripheral/images/using_webcam_with_node-red.png rename to nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red.png diff --git a/src/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_control.webm b/nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_control.webm similarity index 100% rename from src/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_control.webm rename to nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_control.webm diff --git a/src/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_passing_payload.webm b/nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_passing_payload.webm similarity index 100% rename from src/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_passing_payload.webm rename to nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red_capturing_images_by_passing_payload.webm diff --git a/src/node-red/peripheral/images/using_webcam_with_node-red_selecting_different_camera.webm b/nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red_selecting_different_camera.webm similarity index 100% rename from src/node-red/peripheral/images/using_webcam_with_node-red_selecting_different_camera.webm rename to nuxt/content-guides/node-red/peripheral/images/using_webcam_with_node-red_selecting_different_camera.webm diff --git a/src/node-red/peripheral/webcam.md b/nuxt/content-guides/node-red/peripheral/webcam.md similarity index 82% rename from src/node-red/peripheral/webcam.md rename to nuxt/content-guides/node-red/peripheral/webcam.md index e06e72b38d..96ee07bc4f 100644 --- a/src/node-red/peripheral/webcam.md +++ b/nuxt/content-guides/node-red/peripheral/webcam.md @@ -1,21 +1,15 @@ --- -eleventyNavigation: - key: Webcam - parent: "Peripheral Devices" +title: Using webcam with Node-RED +metaTitle: "Using webcam with Node-RED" +navTitle: Webcam +navOrder: 50 meta: - title: Using webcam with Node-RED - description: Learn how to seamlessly connect webcam with Node-RED - keywords: node-red, flowfuse, webcam, capturing image -image: /node-red/peripheral/images/using_webcam_with_node-red.png -certified: - - "@flowfuse-certified-nodes/rtsp" -certifiedIntro: > - This page covers browser webcams through the Dashboard widget. For network - cameras that stream over RTSP, FlowFuse certifies and maintains a separate - node: + description: Learn how to seamlessly connect webcam with Node-RED + keywords: node-red, flowfuse, webcam, capturing image +image: /docs/node-red/peripheral/images/using_webcam_with_node-red.png --- -# {{meta.title}} +# Using webcam with Node-RED Dashboard 2.0 has introduced its first third-party webcam widget, simplifying the integration of webcam features with Node-RED applications. In this documentation, you will learn how to utilize the ui-webcam widget in your Node-RED applications. @@ -23,7 +17,9 @@ Additionally, if you are willing to develop your own third-party widget, we have Install Node-RED Dashboard 2.0. Follow these [instructions](/blog/2024/03/dashboard-getting-started/) to get started. -{% include "components/certified-node-callout.liquid" %} +::callout{icon="i-lucide-badge-check"} +**Certified nodes for this technology.** This page covers browser webcams through the Dashboard widget. For network cameras that stream over RTSP, FlowFuse certifies and maintains a separate node: [rtsp](/integrations/?certified=1) +:: ## Using a webcam custom widget @@ -46,7 +42,7 @@ This webcam widget offers nice interactive controls that will allow you to inter 3. Once the webcam is active, you can capture images by clicking the button with the camera icon located at the bottom center of the webcam interface. 4. The widget returns a Base64 string containing the captured image in PNG format. - + ## Capturing images by passing payload @@ -57,7 +53,7 @@ This webcam widget offers nice interactive controls that will allow you to inter By passing the "capture" string as payload, the webcam widget will activate (if it's off) and capture images automatically, without requiring user interaction. This method is commonly used in industrial applications which depend on automated actions. - + ## Selecting different camera devices @@ -66,11 +62,11 @@ The webcam widget also allows you to select different camera devices connected t 1. Click on the ellipsis icon located at the top-right corner of the webcam interface. 2. A dropdown menu will display the connected cameras. Select your preferred camera to use. Additionally, you can turn off the camera by selecting the "Turn camera off" option. - + ## Browser support and privacy - Browser Compatibility: The webcam widget is compatible with all modern browsers, except Internet Explorer. Whether you're using Chrome, Firefox, Safari, or Edge, you can seamlessly integrate webcam features into your Node-RED applications. - Control Limitation: It's important to note that this widget is designed to interact with webcams directly accessible to the system running Node-RED. For example, if the webcam is connected to a different device or network and not directly accessible to the Node-RED running system, the video stream from that webcam won't be displayed on a dashboard using this widget. - HTTPS Requirement: When accessing Dashboard 2.0 remotely (not via `localhost`), it's crucial to use HTTPS. Failure to do so may result in the browser blocking access to the webcam. -- User Permission: Before the webcam can be activated, the browser will prompt the user for permission to access the webcam device. This ensures user privacy and consent before any image capture occurs. The widget cannot capture images until the user has given their permission. \ No newline at end of file +- User Permission: Before the webcam can be activated, the browser will prompt the user for permission to access the webcam device. This ensures user privacy and consent before any image capture occurs. The widget cannot capture images until the user has given their permission. diff --git a/nuxt/content-guides/node-red/protocol/README.md b/nuxt/content-guides/node-red/protocol/README.md new file mode 100644 index 0000000000..b5a89a7a71 --- /dev/null +++ b/nuxt/content-guides/node-red/protocol/README.md @@ -0,0 +1,30 @@ +--- +title: Using Different Protocols for Building Applications with Node-RED +metaTitle: "Using Different Protocols with Node-RED" +navTitle: Communication protocols +navOrder: 4 +meta: + description: Learn how to leverage various communication protocols with Node-RED for building robust and interconnected applications. + keywords: node-red, iot, mqtt, http, coap, websockets, diffrent protocols with node-red +--- + +# Using Different Protocols for Building Applications with Node-RED + +In IoT development, effective communication between devices is essential. This communication is facilitated by various protocols like MQTT, HTTP, CoAP, and WebSockets. Each protocol brings its own set of strengths and is suited for different IoT scenarios. However, understanding which protocol is suited for what scenario and utilizing it can be quite difficult. That's why we have created this section of resources where you will find documentation on using different communication protocols with Node-RED. + +Node-RED, with its intuitive visual programming interface, simplifies the integration of these protocols. Whether you're publishing sensor data over MQTT, triggering HTTP requests, querying CoAP endpoints, or enabling real-time communication with WebSockets, Node-RED provides a flexible and powerful platform. + +## Resources + +Here are some resources to help you get started with integrating Node-RED with various communication protocols: + +- [Building Secure OPC-UA Server in Node-RED.](/docs/node-red/protocol/opc-ua/): Learn how to build Build and Deploy a custom OPC UA Server in Node-RED +- [Using AMQP with Node-RED](/docs/node-red/protocol/amqp/): Learn how to integrate AMQP with Node-RED for reliable message delivery, advanced routing, and improved data management in your flows. +- [Using LwM2M with Node-RED](/docs/node-red/protocol/lwm2m/): Learn how to integrate LwM2M with Node-RED for effective IoT device management. This guide covers setup, data handling, and remote commands. +- [Using Modbus with Node-RED](/docs/node-red/protocol/modbus/): Learn to use Modbus with Node-RED, including how to build a Modbus server and how to send and read data to and from that server. +- [Using MQTT with Node-RED](/docs/node-red/protocol/mqtt/): Learn how to use MQTT with Node-RED. +- [Using Websocket with Node-RED](/docs/node-red/protocol/websocket/) + +::callout{icon="i-lucide-badge-check"} +**Certified nodes for these protocols.** Two protocols are also covered by a FlowFuse certified node, maintained for production use. EtherNet/IP, for Rockwell and Allen-Bradley controllers, has no page of its own in this section, and is documented with its certified node: [cip-suite](/integrations/?certified=1), [opcua](/integrations/?certified=1) +:: diff --git a/src/node-red/protocol/amqp.md b/nuxt/content-guides/node-red/protocol/amqp.md similarity index 97% rename from src/node-red/protocol/amqp.md rename to nuxt/content-guides/node-red/protocol/amqp.md index 6cdca8a1b1..abc014545c 100644 --- a/src/node-red/protocol/amqp.md +++ b/nuxt/content-guides/node-red/protocol/amqp.md @@ -1,17 +1,17 @@ --- -eleventyNavigation: - key: AMQP - parent: "Communication Protocols" +title: Using AMQP with Node-RED +metaTitle: "Using AMQP with Node-RED" +navTitle: AMQP +navOrder: 50 meta: - title: Using AMQP with Node-RED description: Learn how to integrate AMQP with Node-RED for reliable message delivery, advanced routing, and improved data management in your flows. keywords: node-red amqp, node-red rabbitmq, how to use amqp with node-red, how to use rabbitmq with node-red, rabbitmq node red, amqp node red -image: /node-red/protocol/images/amqp-with-node-red.png +image: /docs/node-red/protocol/images/amqp-with-node-red.png --- -# {{meta.title}} +# Using AMQP with Node-RED -Imagine your Node-RED flow working well, handling data from different sources, until suddenly, messages start disappearing or arriving out of order. [MQTT](/node-red/protocol/mqtt/) works fine for basic messaging, but it can struggle in more complex situations where you need delivery guarantees and advanced routing. +Imagine your Node-RED flow working well, handling data from different sources, until suddenly, messages start disappearing or arriving out of order. [MQTT](/docs/node-red/protocol/mqtt/) works fine for basic messaging, but it can struggle in more complex situations where you need delivery guarantees and advanced routing. That’s where AMQP comes in. AMQP solves these issues with features that MQTT doesn’t have. In this guide, we'll explain what AMQP is and how to use it with Node-RED. @@ -108,12 +108,14 @@ Configure the node by dragging an AMQP node onto the canvas. Double-click the no 1. Add two `amqp-in` nodes on to the canvas. Configure one to listen for messages with the `routingKey` of `"zone1"` and the other with `"zone2"`. Both nodes should be set to the `"irrigation_control"` exchange. 2. Connect each `amqp-in` node to a `debug` node to see the received commands for each zone. - + _Video showing the flow that uses the Direct exchange type to send messages and receive messages_ -{% renderFlow %} +::render-flow +```json [{"id":"efe7a260307e6202","type":"amqp-out","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","reconnectOnError":true,"exchangeName":"irrigation_control","exchangeType":"direct","exchangeRoutingKey":"","exchangeRoutingKeyType":"str","exchangeDurable":true,"amqpProperties":"{ \"headers\": {} }","rpcTimeoutMilliseconds":3000,"outputs":0,"x":470,"y":160,"wires":[]},{"id":"538de33f548833ac","type":"inject","z":"807758ec576fbfd8","name":"Send command to zone 1","props":[{"p":"payload"},{"p":"routingKey","v":"zone1","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{ \"command\": \"start\" }","payloadType":"json","x":230,"y":100,"wires":[["efe7a260307e6202"]]},{"id":"20cfe04fab562ea9","type":"inject","z":"807758ec576fbfd8","name":"Send command to zone 2","props":[{"p":"payload"},{"p":"routingKey","v":"zone2","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{ \"command\": \"stop\" }","payloadType":"str","x":230,"y":240,"wires":[["efe7a260307e6202"]]},{"id":"7a5e0f3a66dc1ccc","type":"amqp-in","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","prefetch":0,"reconnectOnError":true,"noAck":true,"exchangeName":"irrigation_control","exchangeType":"direct","exchangeRoutingKey":"zone1","exchangeDurable":true,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{}","x":230,"y":360,"wires":[["63c5671d6f4efd07"]]},{"id":"4bf1b44b656c35c2","type":"amqp-in","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","prefetch":0,"reconnectOnError":true,"noAck":true,"exchangeName":"irrigation_control","exchangeType":"direct","exchangeRoutingKey":"zone2","exchangeDurable":true,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{}","x":230,"y":440,"wires":[["e7d4fabe9ef668fe"]]},{"id":"63c5671d6f4efd07","type":"debug","z":"807758ec576fbfd8","name":"Zone 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":500,"y":360,"wires":[]},{"id":"e7d4fabe9ef668fe","type":"debug","z":"807758ec576fbfd8","name":"Zone 2","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":500,"y":440,"wires":[]},{"id":"bfb1e7e97eef5e04","type":"amqp-broker","name":"AMQP Config","host":"localhost","port":"5672","vhost":"","tls":false,"credsFromSettings":false}] -{% endrenderFlow %} +``` +:: We configured a Direct type exchange in Node-RED to route messages to specific queues based on the routing key. We demonstrated how to send and receive commands in a smart irrigation system, ensuring that messages for different zones are delivered correctly. This setup is proper when you need precise message delivery based on an exact match with the routing key. @@ -133,12 +135,14 @@ We configured a Direct type exchange in Node-RED to route messages to specific q 1. Add two `amqp-in` nodes on to the canvas. Configure one to listen for messages with the `routingKey` of `"temperature.*"` and the other with `"humidity.*"`. Both nodes should be set to the `"weather_data"` exchange. 2. Connect each `amqp-in` node to a `debug` node to view the sensor data received. - + _Video showing the flow that uses the Topic exchange type to send messages and receive messages_ -{% renderFlow %} +::render-flow +```json [{"id":"06ca737a23c93c75","type":"inject","z":"807758ec576fbfd8","name":"Temp sensor 1","props":[{"p":"payload"},{"p":"routingKey","v":"temperature.sensor1","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 100\t","payloadType":"jsonata","x":180,"y":600,"wires":[["653aec372ecb68a3"]]},{"id":"653aec372ecb68a3","type":"amqp-out","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","reconnectOnError":false,"exchangeName":"weather_data","exchangeType":"topic","exchangeRoutingKey":"","exchangeRoutingKeyType":"str","exchangeDurable":true,"amqpProperties":"{ \"headers\": {} }","rpcTimeoutMilliseconds":3000,"outputs":0,"x":420,"y":660,"wires":[]},{"id":"39546cb6c75044e2","type":"inject","z":"807758ec576fbfd8","name":"Temp sensor 2","props":[{"p":"payload"},{"p":"routingKey","v":"temperature.sensor2","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 100\t","payloadType":"jsonata","x":180,"y":660,"wires":[["653aec372ecb68a3"]]},{"id":"04d4056a8719343d","type":"inject","z":"807758ec576fbfd8","name":"Temp sensor 3","props":[{"p":"payload"},{"p":"routingKey","v":"temperature.sensor3","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 100\t","payloadType":"jsonata","x":180,"y":720,"wires":[["653aec372ecb68a3"]]},{"id":"a7c55eb38bb5f828","type":"amqp-in","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","prefetch":0,"reconnectOnError":false,"noAck":true,"exchangeName":"weather_data","exchangeType":"topic","exchangeRoutingKey":"temperature.*","exchangeDurable":true,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{}","x":460,"y":840,"wires":[["0052a9fab812002f"]]},{"id":"4ac0d82df137f4be","type":"amqp-in","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","prefetch":0,"reconnectOnError":false,"noAck":true,"exchangeName":"weather_data","exchangeType":"topic","exchangeRoutingKey":"humidity.*","exchangeDurable":true,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{}","x":450,"y":920,"wires":[["58f86db958f9c32d"]]},{"id":"a367734d77bd5dcd","type":"inject","z":"807758ec576fbfd8","name":"Hum sensor 1","props":[{"p":"payload"},{"p":"routingKey","v":"humidity.sensor1","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 200\t","payloadType":"jsonata","x":690,"y":580,"wires":[["04248194cccf8c7a"]]},{"id":"04248194cccf8c7a","type":"amqp-out","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","reconnectOnError":false,"exchangeName":"weather_data","exchangeType":"topic","exchangeRoutingKey":"","exchangeRoutingKeyType":"str","exchangeDurable":true,"amqpProperties":"{ \"headers\": {} }","rpcTimeoutMilliseconds":3000,"outputs":0,"x":940,"y":640,"wires":[]},{"id":"3cd4806e1bb64564","type":"inject","z":"807758ec576fbfd8","name":"Hum sensor 2","props":[{"p":"payload"},{"p":"routingKey","v":"humidity.sensor2","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 100\t","payloadType":"jsonata","x":690,"y":640,"wires":[["04248194cccf8c7a"]]},{"id":"35725dfe285e0db1","type":"inject","z":"807758ec576fbfd8","name":"Hum sensor 3","props":[{"p":"payload"},{"p":"routingKey","v":"humidity.sensor3","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 100\t","payloadType":"jsonata","x":690,"y":700,"wires":[["04248194cccf8c7a"]]},{"id":"0052a9fab812002f","type":"debug","z":"807758ec576fbfd8","name":"Temperature sensors data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":770,"y":840,"wires":[]},{"id":"58f86db958f9c32d","type":"debug","z":"807758ec576fbfd8","name":"Humidity sensors data","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":760,"y":920,"wires":[]},{"id":"bfb1e7e97eef5e04","type":"amqp-broker","name":"AMQP Config","host":"localhost","port":"5672","vhost":"","tls":false,"credsFromSettings":false}] -{% endrenderFlow %} +``` +:: We explored the Topic type exchange, which allows for more flexible routing using wildcard patterns in the routing key. The example involved a smart weather station where data from various sensors is published and handled based on sensor types. This setup is ideal for situations where you need to route messages based on partial matches or patterns, offering more granular control over message delivery. @@ -157,12 +161,14 @@ Scenario: You have a smart home system with various components, such as lights, 1. Drag `amqp-in` nodes onto the canvas. Configure one to listen for messages from the `"weather_data"` exchange. 2. Connect the `amqp-in` node to a `debug` node to see the update received from all your components' data. - + _Video showing the flow that uses the Fanout exchange type to send and receive messages._ -{% renderFlow %} +::render-flow +```json [{"id":"7beb4237ba09010b","type":"inject","z":"807758ec576fbfd8","name":"Light update","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Light turned on","payloadType":"str","x":170,"y":1220,"wires":[["d699cd735cc8a0ae"]]},{"id":"8818f122c8937e58","type":"inject","z":"807758ec576fbfd8","name":"thermostats update","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"A new firmware update is available for your thermostat","payloadType":"str","x":190,"y":1280,"wires":[["d699cd735cc8a0ae"]]},{"id":"517aad7b163d5bde","type":"inject","z":"807758ec576fbfd8","name":"Camera update","props":[{"p":"payload"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Movement detected","payloadType":"str","x":180,"y":1340,"wires":[["d699cd735cc8a0ae"]]},{"id":"d699cd735cc8a0ae","type":"amqp-out","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","reconnectOnError":false,"exchangeName":"system_updates","exchangeType":"fanout","exchangeRoutingKey":"","exchangeRoutingKeyType":"str","exchangeDurable":true,"amqpProperties":"{ \"headers\": {} }","rpcTimeoutMilliseconds":3000,"outputs":0,"x":460,"y":1280,"wires":[]},{"id":"1db4056b4c66cb22","type":"amqp-in","z":"807758ec576fbfd8","name":"","broker":"bfb1e7e97eef5e04","prefetch":0,"reconnectOnError":true,"noAck":false,"exchangeName":"system_updates","exchangeType":"fanout","exchangeRoutingKey":"","exchangeDurable":true,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{}","x":180,"y":1520,"wires":[["2392e7d9139813a3"]]},{"id":"2392e7d9139813a3","type":"debug","z":"807758ec576fbfd8","name":"debug 1","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"false","statusVal":"","statusType":"auto","x":420,"y":1520,"wires":[]},{"id":"bfb1e7e97eef5e04","type":"amqp-broker","name":"AMQP Config","host":"localhost","port":"5672","vhost":"","tls":false,"credsFromSettings":false}] -{% endrenderFlow %} +``` +:: We used a Fanout type exchange to broadcast messages to all queues connected to the exchange. We illustrated this with a smart home system where status updates from different components are sent to all devices simultaneously. This type of exchange is perfect for scenarios where you need to send the same message to multiple recipients without concern for routing keys. @@ -181,11 +187,13 @@ We used a Fanout type exchange to broadcast messages to all queues connected to 1. Drag two `amqp-in` nodes on to the canvas. Configure one to listen for messages with the `headers` of `{ "x-match": "all," "machine-type": "CNC," "status": "error," "priority": "high"}` and the other with `{ "x-match": "any," machine-type": "A," "status": "idle," "priority": "high"}.` Both nodes should be set to the `system_update` exchange. 2. Connect each `amqp-in` node to a `debug` node to see the updates received for each component. - + _Video showing the flow that uses the Headers exchange type to send messages and receive messages_ -{% renderFlow %} +::render-flow +```json [{"id":"12c8048f.4eaefb","type":"amqp-in","z":"e4fe9c44.6dee1","name":"","broker":"83e9bf71fbe099c8","prefetch":0,"reconnectOnError":true,"noAck":true,"exchangeName":"machines_update","exchangeType":"headers","exchangeRoutingKey":"","exchangeDurable":false,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{\"x-match\":\"all\",\"machine-type\":\"CNC\",\"status\":\"error\",\"priority\":\"high\"}","x":170,"y":1000,"wires":[["8ec3fa87.70c338"]]},{"id":"6eccc4f.c6a2a3c","type":"amqp-out","z":"e4fe9c44.6dee1","name":"","broker":"83e9bf71fbe099c8","reconnectOnError":true,"exchangeName":"machines_update","exchangeType":"headers","exchangeRoutingKey":"","exchangeRoutingKeyType":"str","exchangeDurable":false,"amqpProperties":"{}","rpcTimeoutMilliseconds":"","outputs":0,"x":530,"y":780,"wires":[]},{"id":"cb5093bf.1d524","type":"inject","z":"e4fe9c44.6dee1","name":"CNC machine: error occured","props":[{"p":"payload"},{"p":"properties","v":"{\"headers\":{\"machine-type\":\"CNC\",\"status\":\"error\",\"priority\":\"high\"}}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Errror occured in the CNC machine","payloadType":"str","x":200,"y":780,"wires":[["6eccc4f.c6a2a3c"]]},{"id":"8ec3fa87.70c338","type":"debug","z":"e4fe9c44.6dee1","name":"Only from CNC machines that has status error and priority high","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":610,"y":1000,"wires":[]},{"id":"d4c2f84b51d2d3cb","type":"amqp-out","z":"e4fe9c44.6dee1","name":"","broker":"83e9bf71fbe099c8","reconnectOnError":true,"exchangeName":"machines_update","exchangeType":"headers","exchangeRoutingKey":"","exchangeRoutingKeyType":"str","exchangeDurable":false,"amqpProperties":"{}","rpcTimeoutMilliseconds":"","outputs":0,"x":530,"y":840,"wires":[]},{"id":"81c21e9941e06a58","type":"inject","z":"e4fe9c44.6dee1","name":"Update from Machine A","props":[{"p":"payload"},{"p":"properties","v":"{\"headers\":{\"machine-type\":\"A\",\"status\":\"idle\",\"priority\":\"high\"}}","vt":"json"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"Machine A is currently idle, awaiting next operation.","payloadType":"str","x":180,"y":840,"wires":[["d4c2f84b51d2d3cb"]]},{"id":"abe55aafb5f65eac","type":"debug","z":"e4fe9c44.6dee1","name":"From all of the machines having status idle or priority high","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":570,"y":1120,"wires":[]},{"id":"adfcaa95122a3556","type":"amqp-in","z":"e4fe9c44.6dee1","name":"","broker":"83e9bf71fbe099c8","prefetch":0,"reconnectOnError":true,"noAck":true,"exchangeName":"machines_update","exchangeType":"headers","exchangeRoutingKey":"","exchangeDurable":false,"queueName":"","queueExclusive":true,"queueDurable":false,"queueAutoDelete":true,"headers":"{\"x-match\":\"any\",\"machine-type\":\"A\",\"status\":\"idle\",\"priority\":\"high\"}","x":170,"y":1120,"wires":[["abe55aafb5f65eac"]]},{"id":"83e9bf71fbe099c8","type":"amqp-broker","name":"","host":"localhost","port":"5672","vhost":"","tls":false,"credsFromSettings":false}] -{% endrenderFlow %} +``` +:: Finally, we configured a Headers type exchange, which routes messages based on attributes in the message headers. The example focused on a factory monitoring system, where updates from machines are routed based on criteria like machine type, status, and priority. This exchange type is powerful for complex routing scenarios where decisions are based on multiple attributes rather than just the routing key. diff --git a/src/node-red/protocol/images/PLC-Information-Model-1.png b/nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-1.png similarity index 100% rename from src/node-red/protocol/images/PLC-Information-Model-1.png rename to nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-1.png diff --git a/src/node-red/protocol/images/PLC-Information-Model-2-of-6-1.png b/nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-2-of-6-1.png similarity index 100% rename from src/node-red/protocol/images/PLC-Information-Model-2-of-6-1.png rename to nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-2-of-6-1.png diff --git a/src/node-red/protocol/images/PLC-Information-Model-3-of-6-1.png b/nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-3-of-6-1.png similarity index 100% rename from src/node-red/protocol/images/PLC-Information-Model-3-of-6-1.png rename to nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-3-of-6-1.png diff --git a/src/node-red/protocol/images/PLC-Information-Model-4-of-6-1.png b/nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-4-of-6-1.png similarity index 100% rename from src/node-red/protocol/images/PLC-Information-Model-4-of-6-1.png rename to nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-4-of-6-1.png diff --git a/src/node-red/protocol/images/PLC-Information-Model-5-of-6-1.png b/nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-5-of-6-1.png similarity index 100% rename from src/node-red/protocol/images/PLC-Information-Model-5-of-6-1.png rename to nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-5-of-6-1.png diff --git a/src/node-red/protocol/images/PLC-Information-Model-6-of-6-1.png b/nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-6-of-6-1.png similarity index 100% rename from src/node-red/protocol/images/PLC-Information-Model-6-of-6-1.png rename to nuxt/content-guides/node-red/protocol/images/PLC-Information-Model-6-of-6-1.png diff --git a/src/node-red/protocol/images/amqp-with-node-red.png b/nuxt/content-guides/node-red/protocol/images/amqp-with-node-red.png similarity index 100% rename from src/node-red/protocol/images/amqp-with-node-red.png rename to nuxt/content-guides/node-red/protocol/images/amqp-with-node-red.png diff --git a/src/node-red/protocol/images/cert-list.png b/nuxt/content-guides/node-red/protocol/images/cert-list.png similarity index 100% rename from src/node-red/protocol/images/cert-list.png rename to nuxt/content-guides/node-red/protocol/images/cert-list.png diff --git a/src/node-red/protocol/images/cert-tab-filled.png b/nuxt/content-guides/node-red/protocol/images/cert-tab-filled.png similarity index 100% rename from src/node-red/protocol/images/cert-tab-filled.png rename to nuxt/content-guides/node-red/protocol/images/cert-tab-filled.png diff --git a/src/node-red/protocol/images/change-node-palette.png b/nuxt/content-guides/node-red/protocol/images/change-node-palette.png similarity index 100% rename from src/node-red/protocol/images/change-node-palette.png rename to nuxt/content-guides/node-red/protocol/images/change-node-palette.png diff --git a/src/node-red/protocol/images/change-node-properties.png b/nuxt/content-guides/node-red/protocol/images/change-node-properties.png similarity index 100% rename from src/node-red/protocol/images/change-node-properties.png rename to nuxt/content-guides/node-red/protocol/images/change-node-properties.png diff --git a/src/node-red/protocol/images/client-cert-path-filled.png b/nuxt/content-guides/node-red/protocol/images/client-cert-path-filled.png similarity index 100% rename from src/node-red/protocol/images/client-cert-path-filled.png rename to nuxt/content-guides/node-red/protocol/images/client-cert-path-filled.png diff --git a/src/node-red/protocol/images/client-cert-path.png b/nuxt/content-guides/node-red/protocol/images/client-cert-path.png similarity index 100% rename from src/node-red/protocol/images/client-cert-path.png rename to nuxt/content-guides/node-red/protocol/images/client-cert-path.png diff --git a/src/node-red/protocol/images/client-testing.webm b/nuxt/content-guides/node-red/protocol/images/client-testing.webm similarity index 100% rename from src/node-red/protocol/images/client-testing.webm rename to nuxt/content-guides/node-red/protocol/images/client-testing.webm diff --git a/src/node-red/protocol/images/compact-server-active.png b/nuxt/content-guides/node-red/protocol/images/compact-server-active.png similarity index 100% rename from src/node-red/protocol/images/compact-server-active.png rename to nuxt/content-guides/node-red/protocol/images/compact-server-active.png diff --git a/src/node-red/protocol/images/compact-server-node-address-space.png b/nuxt/content-guides/node-red/protocol/images/compact-server-node-address-space.png similarity index 100% rename from src/node-red/protocol/images/compact-server-node-address-space.png rename to nuxt/content-guides/node-red/protocol/images/compact-server-node-address-space.png diff --git a/src/node-red/protocol/images/connect-rejected.png b/nuxt/content-guides/node-red/protocol/images/connect-rejected.png similarity index 100% rename from src/node-red/protocol/images/connect-rejected.png rename to nuxt/content-guides/node-red/protocol/images/connect-rejected.png diff --git a/src/node-red/protocol/images/connected-mqtt-node.png b/nuxt/content-guides/node-red/protocol/images/connected-mqtt-node.png similarity index 100% rename from src/node-red/protocol/images/connected-mqtt-node.png rename to nuxt/content-guides/node-red/protocol/images/connected-mqtt-node.png diff --git a/src/node-red/protocol/images/context-data-1.png b/nuxt/content-guides/node-red/protocol/images/context-data-1.png similarity index 100% rename from src/node-red/protocol/images/context-data-1.png rename to nuxt/content-guides/node-red/protocol/images/context-data-1.png diff --git a/src/node-red/protocol/images/context-refresh.png b/nuxt/content-guides/node-red/protocol/images/context-refresh.png similarity index 100% rename from src/node-red/protocol/images/context-refresh.png rename to nuxt/content-guides/node-red/protocol/images/context-refresh.png diff --git a/src/node-red/protocol/images/conveyor-context-function.png b/nuxt/content-guides/node-red/protocol/images/conveyor-context-function.png similarity index 100% rename from src/node-red/protocol/images/conveyor-context-function.png rename to nuxt/content-guides/node-red/protocol/images/conveyor-context-function.png diff --git a/src/node-red/protocol/images/copied-certs-with-pubkey.png b/nuxt/content-guides/node-red/protocol/images/copied-certs-with-pubkey.png similarity index 100% rename from src/node-red/protocol/images/copied-certs-with-pubkey.png rename to nuxt/content-guides/node-red/protocol/images/copied-certs-with-pubkey.png diff --git a/src/node-red/protocol/images/copied-certs.png b/nuxt/content-guides/node-red/protocol/images/copied-certs.png similarity index 100% rename from src/node-red/protocol/images/copied-certs.png rename to nuxt/content-guides/node-red/protocol/images/copied-certs.png diff --git a/src/node-red/protocol/images/copy-certs.png b/nuxt/content-guides/node-red/protocol/images/copy-certs.png similarity index 100% rename from src/node-red/protocol/images/copy-certs.png rename to nuxt/content-guides/node-red/protocol/images/copy-certs.png diff --git a/src/node-red/protocol/images/custom-nodes-installed.png b/nuxt/content-guides/node-red/protocol/images/custom-nodes-installed.png similarity index 100% rename from src/node-red/protocol/images/custom-nodes-installed.png rename to nuxt/content-guides/node-red/protocol/images/custom-nodes-installed.png diff --git a/src/node-red/protocol/images/debug-data.png b/nuxt/content-guides/node-red/protocol/images/debug-data.png similarity index 100% rename from src/node-red/protocol/images/debug-data.png rename to nuxt/content-guides/node-red/protocol/images/debug-data.png diff --git a/src/node-red/protocol/images/deploy-flow.png b/nuxt/content-guides/node-red/protocol/images/deploy-flow.png similarity index 100% rename from src/node-red/protocol/images/deploy-flow.png rename to nuxt/content-guides/node-red/protocol/images/deploy-flow.png diff --git a/src/node-red/protocol/images/der-file.png b/nuxt/content-guides/node-red/protocol/images/der-file.png similarity index 100% rename from src/node-red/protocol/images/der-file.png rename to nuxt/content-guides/node-red/protocol/images/der-file.png diff --git a/src/node-red/protocol/images/direct.webm b/nuxt/content-guides/node-red/protocol/images/direct.webm similarity index 100% rename from src/node-red/protocol/images/direct.webm rename to nuxt/content-guides/node-red/protocol/images/direct.webm diff --git a/src/node-red/protocol/images/eth-ip-debug.png b/nuxt/content-guides/node-red/protocol/images/eth-ip-debug.png similarity index 100% rename from src/node-red/protocol/images/eth-ip-debug.png rename to nuxt/content-guides/node-red/protocol/images/eth-ip-debug.png diff --git a/src/node-red/protocol/images/eth-ip-endpoint-tags.png b/nuxt/content-guides/node-red/protocol/images/eth-ip-endpoint-tags.png similarity index 100% rename from src/node-red/protocol/images/eth-ip-endpoint-tags.png rename to nuxt/content-guides/node-red/protocol/images/eth-ip-endpoint-tags.png diff --git a/src/node-red/protocol/images/eth-ip-in-palette.png b/nuxt/content-guides/node-red/protocol/images/eth-ip-in-palette.png similarity index 100% rename from src/node-red/protocol/images/eth-ip-in-palette.png rename to nuxt/content-guides/node-red/protocol/images/eth-ip-in-palette.png diff --git a/src/node-red/protocol/images/eth-ip-in-properties.png b/nuxt/content-guides/node-red/protocol/images/eth-ip-in-properties.png similarity index 100% rename from src/node-red/protocol/images/eth-ip-in-properties.png rename to nuxt/content-guides/node-red/protocol/images/eth-ip-in-properties.png diff --git a/src/node-red/protocol/images/ethip-node-connection.png b/nuxt/content-guides/node-red/protocol/images/ethip-node-connection.png similarity index 100% rename from src/node-red/protocol/images/ethip-node-connection.png rename to nuxt/content-guides/node-red/protocol/images/ethip-node-connection.png diff --git a/src/node-red/protocol/images/example-flow.png b/nuxt/content-guides/node-red/protocol/images/example-flow.png similarity index 100% rename from src/node-red/protocol/images/example-flow.png rename to nuxt/content-guides/node-red/protocol/images/example-flow.png diff --git a/src/node-red/protocol/images/executing-command-from-server.webm b/nuxt/content-guides/node-red/protocol/images/executing-command-from-server.webm similarity index 100% rename from src/node-red/protocol/images/executing-command-from-server.webm rename to nuxt/content-guides/node-red/protocol/images/executing-command-from-server.webm diff --git a/src/node-red/protocol/images/fanout.webm b/nuxt/content-guides/node-red/protocol/images/fanout.webm similarity index 100% rename from src/node-red/protocol/images/fanout.webm rename to nuxt/content-guides/node-red/protocol/images/fanout.webm diff --git a/src/node-red/protocol/images/flow-manage-palette.png b/nuxt/content-guides/node-red/protocol/images/flow-manage-palette.png similarity index 100% rename from src/node-red/protocol/images/flow-manage-palette.png rename to nuxt/content-guides/node-red/protocol/images/flow-manage-palette.png diff --git a/src/node-red/protocol/images/flow-with-change-palette.png b/nuxt/content-guides/node-red/protocol/images/flow-with-change-palette.png similarity index 100% rename from src/node-red/protocol/images/flow-with-change-palette.png rename to nuxt/content-guides/node-red/protocol/images/flow-with-change-palette.png diff --git a/src/node-red/protocol/images/flow-with-compact-server.png b/nuxt/content-guides/node-red/protocol/images/flow-with-compact-server.png similarity index 100% rename from src/node-red/protocol/images/flow-with-compact-server.png rename to nuxt/content-guides/node-red/protocol/images/flow-with-compact-server.png diff --git a/src/node-red/protocol/images/flowforge-nodered-mqtt-hivemq.png b/nuxt/content-guides/node-red/protocol/images/flowforge-nodered-mqtt-hivemq.png similarity index 100% rename from src/node-red/protocol/images/flowforge-nodered-mqtt-hivemq.png rename to nuxt/content-guides/node-red/protocol/images/flowforge-nodered-mqtt-hivemq.png diff --git a/src/node-red/protocol/images/header.webm b/nuxt/content-guides/node-red/protocol/images/header.webm similarity index 100% rename from src/node-red/protocol/images/header.webm rename to nuxt/content-guides/node-red/protocol/images/header.webm diff --git a/src/node-red/protocol/images/image-20230717-212515.png b/nuxt/content-guides/node-red/protocol/images/image-20230717-212515.png similarity index 100% rename from src/node-red/protocol/images/image-20230717-212515.png rename to nuxt/content-guides/node-red/protocol/images/image-20230717-212515.png diff --git a/src/node-red/protocol/images/image-20230718-155245.png b/nuxt/content-guides/node-red/protocol/images/image-20230718-155245.png similarity index 100% rename from src/node-red/protocol/images/image-20230718-155245.png rename to nuxt/content-guides/node-red/protocol/images/image-20230718-155245.png diff --git a/src/node-red/protocol/images/image-20230718-164326.png b/nuxt/content-guides/node-red/protocol/images/image-20230718-164326.png similarity index 100% rename from src/node-red/protocol/images/image-20230718-164326.png rename to nuxt/content-guides/node-red/protocol/images/image-20230718-164326.png diff --git a/src/node-red/protocol/images/import-context.png b/nuxt/content-guides/node-red/protocol/images/import-context.png similarity index 100% rename from src/node-red/protocol/images/import-context.png rename to nuxt/content-guides/node-red/protocol/images/import-context.png diff --git a/src/node-red/protocol/images/import.png b/nuxt/content-guides/node-red/protocol/images/import.png similarity index 100% rename from src/node-red/protocol/images/import.png rename to nuxt/content-guides/node-red/protocol/images/import.png diff --git a/src/node-red/protocol/images/individual-cert-file-option.png b/nuxt/content-guides/node-red/protocol/images/individual-cert-file-option.png similarity index 100% rename from src/node-red/protocol/images/individual-cert-file-option.png rename to nuxt/content-guides/node-red/protocol/images/individual-cert-file-option.png diff --git a/src/node-red/protocol/images/install-eth-ip-node.png b/nuxt/content-guides/node-red/protocol/images/install-eth-ip-node.png similarity index 100% rename from src/node-red/protocol/images/install-eth-ip-node.png rename to nuxt/content-guides/node-red/protocol/images/install-eth-ip-node.png diff --git a/src/node-red/protocol/images/lwm2m-server-reading.webm b/nuxt/content-guides/node-red/protocol/images/lwm2m-server-reading.webm similarity index 100% rename from src/node-red/protocol/images/lwm2m-server-reading.webm rename to nuxt/content-guides/node-red/protocol/images/lwm2m-server-reading.webm diff --git a/src/node-red/protocol/images/modbus-1-1.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-1.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-1.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-1.png diff --git a/src/node-red/protocol/images/modbus-1-10.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-10.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-10.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-10.png diff --git a/src/node-red/protocol/images/modbus-1-11.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-11.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-11.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-11.png diff --git a/src/node-red/protocol/images/modbus-1-12.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-12.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-12.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-12.png diff --git a/src/node-red/protocol/images/modbus-1-13.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-13.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-13.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-13.png diff --git a/src/node-red/protocol/images/modbus-1-14.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-14.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-14.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-14.png diff --git a/src/node-red/protocol/images/modbus-1-15.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-15.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-15.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-15.png diff --git a/src/node-red/protocol/images/modbus-1-16.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-16.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-16.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-16.png diff --git a/src/node-red/protocol/images/modbus-1-2.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-2.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-2.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-2.png diff --git a/src/node-red/protocol/images/modbus-1-3.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-3.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-3.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-3.png diff --git a/src/node-red/protocol/images/modbus-1-4.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-4.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-4.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-4.png diff --git a/src/node-red/protocol/images/modbus-1-5.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-5.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-5.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-5.png diff --git a/src/node-red/protocol/images/modbus-1-6.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-6.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-6.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-6.png diff --git a/src/node-red/protocol/images/modbus-1-7.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-7.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-7.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-7.png diff --git a/src/node-red/protocol/images/modbus-1-8.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-8.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-8.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-8.png diff --git a/src/node-red/protocol/images/modbus-1-9.png b/nuxt/content-guides/node-red/protocol/images/modbus-1-9.png similarity index 100% rename from src/node-red/protocol/images/modbus-1-9.png rename to nuxt/content-guides/node-red/protocol/images/modbus-1-9.png diff --git a/src/node-red/protocol/images/modbus-1.jpg b/nuxt/content-guides/node-red/protocol/images/modbus-1.jpg similarity index 100% rename from src/node-red/protocol/images/modbus-1.jpg rename to nuxt/content-guides/node-red/protocol/images/modbus-1.jpg diff --git a/src/node-red/protocol/images/modbus-node-red-cta-1.png b/nuxt/content-guides/node-red/protocol/images/modbus-node-red-cta-1.png similarity index 100% rename from src/node-red/protocol/images/modbus-node-red-cta-1.png rename to nuxt/content-guides/node-red/protocol/images/modbus-node-red-cta-1.png diff --git a/src/node-red/protocol/images/modbus-server.png b/nuxt/content-guides/node-red/protocol/images/modbus-server.png similarity index 100% rename from src/node-red/protocol/images/modbus-server.png rename to nuxt/content-guides/node-red/protocol/images/modbus-server.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-change-node.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-change-node.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-change-node.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-change-node.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-combine-holding-register-data.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-combine-holding-register-data.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-combine-holding-register-data.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-combine-holding-register-data.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-combine-output-coils-data.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-combine-output-coils-data.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-combine-output-coils-data.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-combine-output-coils-data.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-dashboard.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-dashboard.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-dashboard.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-dashboard.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-send-holding-registers.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-send-holding-registers.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-send-holding-registers.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-send-holding-registers.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-send-output-coil.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-send-output-coil.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-send-output-coil.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-send-output-coil.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-template-widget-stylesheet.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-template-widget-stylesheet.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-template-widget-stylesheet.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-template-widget-stylesheet.png diff --git a/src/node-red/protocol/images/modbus-with-node-red-text-node.png b/nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-text-node.png similarity index 100% rename from src/node-red/protocol/images/modbus-with-node-red-text-node.png rename to nuxt/content-guides/node-red/protocol/images/modbus-with-node-red-text-node.png diff --git a/src/node-red/protocol/images/mqtt-broker-config.png b/nuxt/content-guides/node-red/protocol/images/mqtt-broker-config.png similarity index 100% rename from src/node-red/protocol/images/mqtt-broker-config.png rename to nuxt/content-guides/node-red/protocol/images/mqtt-broker-config.png diff --git a/src/node-red/protocol/images/mqtt-in-config-node-red.png b/nuxt/content-guides/node-red/protocol/images/mqtt-in-config-node-red.png similarity index 100% rename from src/node-red/protocol/images/mqtt-in-config-node-red.png rename to nuxt/content-guides/node-red/protocol/images/mqtt-in-config-node-red.png diff --git a/src/node-red/protocol/images/mqtt-in-config.png b/nuxt/content-guides/node-red/protocol/images/mqtt-in-config.png similarity index 100% rename from src/node-red/protocol/images/mqtt-in-config.png rename to nuxt/content-guides/node-red/protocol/images/mqtt-in-config.png diff --git a/src/node-red/protocol/images/mqtt-out.png b/nuxt/content-guides/node-red/protocol/images/mqtt-out.png similarity index 100% rename from src/node-red/protocol/images/mqtt-out.png rename to nuxt/content-guides/node-red/protocol/images/mqtt-out.png diff --git a/src/node-red/protocol/images/msg-payload.png b/nuxt/content-guides/node-red/protocol/images/msg-payload.png similarity index 100% rename from src/node-red/protocol/images/msg-payload.png rename to nuxt/content-guides/node-red/protocol/images/msg-payload.png diff --git a/src/node-red/protocol/images/node-red-config-mqtt-server.png b/nuxt/content-guides/node-red/protocol/images/node-red-config-mqtt-server.png similarity index 100% rename from src/node-red/protocol/images/node-red-config-mqtt-server.png rename to nuxt/content-guides/node-red/protocol/images/node-red-config-mqtt-server.png diff --git a/src/node-red/protocol/images/opc-client-accept-cert.png b/nuxt/content-guides/node-red/protocol/images/opc-client-accept-cert.png similarity index 100% rename from src/node-red/protocol/images/opc-client-accept-cert.png rename to nuxt/content-guides/node-red/protocol/images/opc-client-accept-cert.png diff --git a/src/node-red/protocol/images/opc-client-connect.png b/nuxt/content-guides/node-red/protocol/images/opc-client-connect.png similarity index 100% rename from src/node-red/protocol/images/opc-client-connect.png rename to nuxt/content-guides/node-red/protocol/images/opc-client-connect.png diff --git a/src/node-red/protocol/images/opc-folder-structure.png b/nuxt/content-guides/node-red/protocol/images/opc-folder-structure.png similarity index 100% rename from src/node-red/protocol/images/opc-folder-structure.png rename to nuxt/content-guides/node-red/protocol/images/opc-folder-structure.png diff --git a/src/node-red/protocol/images/opc-nodes-install.png b/nuxt/content-guides/node-red/protocol/images/opc-nodes-install.png similarity index 100% rename from src/node-red/protocol/images/opc-nodes-install.png rename to nuxt/content-guides/node-red/protocol/images/opc-nodes-install.png diff --git a/src/node-red/protocol/images/opc-nodes.png b/nuxt/content-guides/node-red/protocol/images/opc-nodes.png similarity index 100% rename from src/node-red/protocol/images/opc-nodes.png rename to nuxt/content-guides/node-red/protocol/images/opc-nodes.png diff --git a/src/node-red/protocol/images/opc-ua-2-title-image.png b/nuxt/content-guides/node-red/protocol/images/opc-ua-2-title-image.png similarity index 100% rename from src/node-red/protocol/images/opc-ua-2-title-image.png rename to nuxt/content-guides/node-red/protocol/images/opc-ua-2-title-image.png diff --git a/src/node-red/protocol/images/reading-data-from-lwm2m-from-server.webm b/nuxt/content-guides/node-red/protocol/images/reading-data-from-lwm2m-from-server.webm similarity index 100% rename from src/node-red/protocol/images/reading-data-from-lwm2m-from-server.webm rename to nuxt/content-guides/node-red/protocol/images/reading-data-from-lwm2m-from-server.webm diff --git a/src/node-red/protocol/images/reading-data-from-server-in-nr.webm b/nuxt/content-guides/node-red/protocol/images/reading-data-from-server-in-nr.webm similarity index 100% rename from src/node-red/protocol/images/reading-data-from-server-in-nr.webm rename to nuxt/content-guides/node-red/protocol/images/reading-data-from-server-in-nr.webm diff --git a/src/node-red/protocol/images/security-tab-default.png b/nuxt/content-guides/node-red/protocol/images/security-tab-default.png similarity index 100% rename from src/node-red/protocol/images/security-tab-default.png rename to nuxt/content-guides/node-red/protocol/images/security-tab-default.png diff --git a/src/node-red/protocol/images/server-testing.webm b/nuxt/content-guides/node-red/protocol/images/server-testing.webm similarity index 100% rename from src/node-red/protocol/images/server-testing.webm rename to nuxt/content-guides/node-red/protocol/images/server-testing.webm diff --git a/src/node-red/protocol/images/sign&encrypt.png b/nuxt/content-guides/node-red/protocol/images/sign&encrypt.png similarity index 100% rename from src/node-red/protocol/images/sign&encrypt.png rename to nuxt/content-guides/node-red/protocol/images/sign&encrypt.png diff --git a/src/node-red/protocol/images/sine-wave-gen.png b/nuxt/content-guides/node-red/protocol/images/sine-wave-gen.png similarity index 100% rename from src/node-red/protocol/images/sine-wave-gen.png rename to nuxt/content-guides/node-red/protocol/images/sine-wave-gen.png diff --git a/src/node-red/protocol/images/ssl-pub-keygen.png b/nuxt/content-guides/node-red/protocol/images/ssl-pub-keygen.png similarity index 100% rename from src/node-red/protocol/images/ssl-pub-keygen.png rename to nuxt/content-guides/node-red/protocol/images/ssl-pub-keygen.png diff --git a/src/node-red/protocol/images/topic.webm b/nuxt/content-guides/node-red/protocol/images/topic.webm similarity index 100% rename from src/node-red/protocol/images/topic.webm rename to nuxt/content-guides/node-red/protocol/images/topic.webm diff --git a/src/node-red/protocol/images/using-lwm2m-with-node-red.png b/nuxt/content-guides/node-red/protocol/images/using-lwm2m-with-node-red.png similarity index 100% rename from src/node-red/protocol/images/using-lwm2m-with-node-red.png rename to nuxt/content-guides/node-red/protocol/images/using-lwm2m-with-node-red.png diff --git a/src/node-red/protocol/images/websocket-handshake.png b/nuxt/content-guides/node-red/protocol/images/websocket-handshake.png similarity index 100% rename from src/node-red/protocol/images/websocket-handshake.png rename to nuxt/content-guides/node-red/protocol/images/websocket-handshake.png diff --git a/src/node-red/protocol/images/writing-data-to-server-from-nr.webm b/nuxt/content-guides/node-red/protocol/images/writing-data-to-server-from-nr.webm similarity index 100% rename from src/node-red/protocol/images/writing-data-to-server-from-nr.webm rename to nuxt/content-guides/node-red/protocol/images/writing-data-to-server-from-nr.webm diff --git a/src/node-red/protocol/images/writing-in-server.webm b/nuxt/content-guides/node-red/protocol/images/writing-in-server.webm similarity index 100% rename from src/node-red/protocol/images/writing-in-server.webm rename to nuxt/content-guides/node-red/protocol/images/writing-in-server.webm diff --git a/src/node-red/protocol/lwm2m.md b/nuxt/content-guides/node-red/protocol/lwm2m.md similarity index 89% rename from src/node-red/protocol/lwm2m.md rename to nuxt/content-guides/node-red/protocol/lwm2m.md index e0b94b5113..0e520d8f86 100644 --- a/src/node-red/protocol/lwm2m.md +++ b/nuxt/content-guides/node-red/protocol/lwm2m.md @@ -1,16 +1,15 @@ --- -eleventyNavigation: - key: LwM2M - parent: "Communication Protocols" +title: Using LwM2M with Node-RED +metaTitle: "Using LwM2M with Node-RED" +navTitle: LwM2M +navOrder: 50 meta: - title: Using LwM2M with Node-RED description: Learn how to integrate LwM2M with Node-RED for effective IoT device management. This guide covers setup, data handling, and remote commands. - keywords: how to use lwm2m with node red, lwm2m protocol, node-red modbus, lightweight m2m with node red - modbus -image: /node-red/protocol/images/using-lwm2m-with-node-red.png + keywords: how to use lwm2m with node red, lwm2m protocol, node-red modbus, lightweight m2m with node red modbus +image: /docs/node-red/protocol/images/using-lwm2m-with-node-red.png --- -# {{meta.title}} +# Using LwM2M with Node-RED IoT devices, especially those designed for low-power operation, can be difficult to manage due to their limited resources and the need for efficient communication and control. This is where LwM2M (Lightweight Machine-to-Machine) comes in. LwM2M is designed to help you monitor, update, and control your devices with minimal overhead, making it ideal for everything from smart sensors to industrial equipment. In this post, we'll explore how you can use LwM2M with Node-RED. It is ideal for anyone starting their journey with LwM2M or Node-RED. @@ -82,7 +81,7 @@ Once you've configured the LwM2M node with the server details, you can confirm t 3. Below this, you'll find the available objects that you can control for your devices and server. 4. Click on the "Device" object option to read device realted information. In the instance 0 section click on the "R" for each object you want. Alternatively, you can click on the top "R" next to instance 0 or "Device-v1.0" to read all values at once. Note that this may not work if any values are unavailable for your device, it will return 404 not found. - + _Video showing LwM2M Server reading the device details_ You can now read information such as device battery level, available memory, device manufacturer, timezone, device type, and a lot. @@ -95,7 +94,7 @@ You can now read information such as device battery level, available memory, dev 4. In the form that opens, enter the new value in the correct format. 5. Click on "Write" to update the value for that resource. - + *Video showing how to perform write operation in the LwM2M server* 6. Drag the **lwm2m client** node onto the canvas, select the correct configuration, and enable the "Subscribe LwM2M object events" option. This setting will trigger and send an event object when commands are executed on the server. @@ -103,7 +102,7 @@ You can now read information such as device battery level, available memory, dev 8. Connect the output of the **lwm2m client** node to the input of the **exec** node. 9. To execute the commands, click on the 'exec' option next to resources such as Reboot. - + *Video showing the LwM2M server executing reboot command for device* ### Reading Data and Configuration from the LwM2M Server in Node-RED @@ -114,7 +113,7 @@ You can now read information such as device battery level, available memory, dev 4. Connect the output of the **inject** node to the input of the LwM2M client node, and connect the output of the **lwm2m client** node to the input of the **debug** node. 5. Deploy the flow by clicking on the top-right "deploy" button. - + _Video showing Node-RED flow that is reading data from LwM2M Server_ ### Writing data and configuration to the LwM2M Server from Node-RED @@ -126,7 +125,7 @@ _Video showing Node-RED flow that is reading data from LwM2M Server_ 5. Connect the **inject** node's output to the input of **lwm2m client out** node. 6. Deploy the flow and click the inject button to perform the write operation. - + _Video showing Node-RED flow that is writing data to LwM2M Server_ -In the same way, you can execute commands from node-red. You have to replace the notion and end that notion with `execute`, like `0/0/4/execute.` When executing the command, you will not have to specify the `msg.payload`. \ No newline at end of file +In the same way, you can execute commands from node-red. You have to replace the notion and end that notion with `execute`, like `0/0/4/execute.` When executing the command, you will not have to specify the `msg.payload`. diff --git a/src/node-red/protocol/modbus.md b/nuxt/content-guides/node-red/protocol/modbus.md similarity index 97% rename from src/node-red/protocol/modbus.md rename to nuxt/content-guides/node-red/protocol/modbus.md index 9bf7a3e122..504c2d64b6 100644 --- a/src/node-red/protocol/modbus.md +++ b/nuxt/content-guides/node-red/protocol/modbus.md @@ -1,25 +1,21 @@ --- -eleventyNavigation: - key: Modbus - parent: "Communication Protocols" +title: Using Modbus with Node-RED +metaTitle: "Using Modbus with Node-RED" +navTitle: Modbus +navOrder: 50 meta: - title: Using Modbus with Node-RED description: Learn to use Modbus with Node-RED, including how to build a Modbus server and how to send and read data to and from that server. - keywords: node red modbus, node-red-contrib-modbus, node-red modbus, modbus node red, modbus nodered, node red modbus rtu, modbus failure on state sending get more about it by logging, node red modbus server - modbus + keywords: node red modbus, node-red-contrib-modbus, node-red modbus, modbus node red, modbus nodered, node red modbus rtu, modbus failure on state sending get more about it by logging, node red modbus server modbus --- -# {{meta.title}} +# Using Modbus with Node-RED In manufacturing companies there is often a small set of production data, currently only available to an equipment operator through the HMI, which would be enormously valuable to a greater audience if there were some way to easily display and share it. Node-RED, along with Modbus and Dashboard modules, can easily create a web-based dashboard, shareable with a weblink and viewable on any web browser on the network. Imagine the advantages of digital signage in the breakroom spurring healthy competition or a manager being able to check daily totals and live process values from the phone in their pocket. - - Read Modbus data from every device on your floor, not just one instance - +::cta-image{src="/docs/node-red/protocol/images/modbus-node-red-cta-1.png" alt="Read Modbus data from every device on your floor, not just one instance" cta="sign-up" reference="Node-RED: Using Modbus with Node-RED"} +:: ## What is Modbus @@ -141,9 +137,11 @@ The output coil data is displayed on the dashboard using a text widget. When the Final flow is given below: -{% renderFlow 300 %} +::render-flow{:height="300"} +```json [{"id":"4cf44f4cbc592b99","type":"tab","label":"Flow 1","disabled":false,"info":"","env":[]},{"id":"b48b43414657ca4e","type":"modbus-read","z":"4cf44f4cbc592b99","name":"","topic":"text","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"1","dataType":"Coil","adr":"0","quantity":"5","rate":"1","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"145bc96e15c34554","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":130,"y":540,"wires":[["f6fb47f18928d28f","3bf66c619694a1a4","35bc45f58dc5c661","5b3a8b0c7cd968fa","f87af242b2287b7c"],[]]},{"id":"e28ae04240af481e","type":"modbus-read","z":"4cf44f4cbc592b99","name":"","topic":"","showStatusActivities":false,"logIOActivities":false,"showErrors":false,"showWarnings":true,"unitid":"1","dataType":"HoldingRegister","adr":"0","quantity":"4","rate":"1","rateUnit":"s","delayOnStart":false,"startDelayTime":"","server":"145bc96e15c34554","useIOFile":false,"ioFile":"","useIOForPayload":false,"emptyMsgOnFail":false,"x":130,"y":800,"wires":[["9dd39216e9bbd6fd","c4389848cebd29c7","dffa222847521883","286d69415ef78e7c"],[]]},{"id":"c302edd625716894","type":"modbus-server","z":"4cf44f4cbc592b99","name":"Modbus server","logEnabled":false,"hostname":"127.0.0.1","serverPort":"10502","responseDelay":100,"delayUnit":"ms","coilsBufferSize":10000,"holdingBufferSize":10000,"inputBufferSize":10000,"discreteBufferSize":10000,"showErrors":false,"x":520,"y":260,"wires":[[],[],[],[],[]]},{"id":"7f20fcaea773cb1b","type":"inject","z":"4cf44f4cbc592b99","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"","payloadType":"date","x":140,"y":260,"wires":[["c302edd625716894"]]},{"id":"bc7bdfd9d81369af","type":"inject","z":"4cf44f4cbc592b99","name":"","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"$random() * 150","payloadType":"jsonata","x":110,"y":1020,"wires":[["2c019b52b54a2e0d"]]},{"id":"2c019b52b54a2e0d","type":"join","z":"4cf44f4cbc592b99","name":"combine holiding register data","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"4","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":350,"y":1020,"wires":[["8bf7d588de60fb33"]]},{"id":"6c14cb9ab888c12d","type":"inject","z":"4cf44f4cbc592b99","name":"","props":[{"p":"payload"}],"repeat":"1","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"true","payloadType":"bool","x":110,"y":1140,"wires":[["1a59e443e0258194"]]},{"id":"1a59e443e0258194","type":"join","z":"4cf44f4cbc592b99","name":"combine coil output data","mode":"custom","build":"array","property":"payload","propertyType":"msg","key":"topic","joiner":"\\n","joinerType":"str","accumulate":false,"timeout":"","count":"5","reduceRight":false,"reduceExp":"","reduceInit":"","reduceInitType":"","reduceFixup":"","x":330,"y":1140,"wires":[["cdb7e5d53210cb6d"]]},{"id":"62338bd2af1d3c0d","type":"modbus-response","z":"4cf44f4cbc592b99","name":"","registerShowMax":20,"x":870,"y":1020,"wires":[]},{"id":"d1eff51eac0af203","type":"modbus-response","z":"4cf44f4cbc592b99","name":"","registerShowMax":20,"x":870,"y":1120,"wires":[]},{"id":"8bf7d588de60fb33","type":"modbus-write","z":"4cf44f4cbc592b99","name":"send holding registers","showStatusActivities":false,"showErrors":false,"showWarnings":true,"unitid":"1","dataType":"MHoldingRegisters","adr":"0","quantity":"4","server":"145bc96e15c34554","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":640,"y":1020,"wires":[["62338bd2af1d3c0d"],[]]},{"id":"cdb7e5d53210cb6d","type":"modbus-write","z":"4cf44f4cbc592b99","name":"send output coils ","showStatusActivities":false,"showErrors":false,"showWarnings":true,"unitid":"1","dataType":"MCoils","adr":"0","quantity":"5","server":"145bc96e15c34554","emptyMsgOnFail":false,"keepMsgProperties":false,"delayOnStart":false,"startDelayTime":"","x":630,"y":1140,"wires":[["d1eff51eac0af203"],[]]},{"id":"405acf6300a072d6","type":"ui-text","z":"4cf44f4cbc592b99","group":"a00b86fb96b216a6","order":1,"width":0,"height":0,"name":"","label":"isEStopReleased","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":690,"y":460,"wires":[]},{"id":"52cb610e23488ba2","type":"ui-text","z":"4cf44f4cbc592b99","group":"a00b86fb96b216a6","order":3,"width":0,"height":0,"name":"","label":"isMotorSwitchedOn","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":690,"y":500,"wires":[]},{"id":"224a2c2948d7d9a5","type":"ui-text","z":"4cf44f4cbc592b99","group":"a00b86fb96b216a6","order":4,"width":0,"height":0,"name":"","label":"isMotorRunning","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":680.111083984375,"y":537.7777709960938,"wires":[]},{"id":"82fce135993ad1ab","type":"ui-text","z":"4cf44f4cbc592b99","group":"a00b86fb96b216a6","order":5,"width":0,"height":0,"name":"","label":"isTailswitchPulsing","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":690,"y":580,"wires":[]},{"id":"f6fb47f18928d28f","type":"change","z":"4cf44f4cbc592b99","name":"isEStopReleased","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[0]","tot":"msg"},{"t":"set","p":"class","pt":"msg","to":"msg.payload ? \"green\":\"red\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":460,"wires":[["405acf6300a072d6"]]},{"id":"3bf66c619694a1a4","type":"change","z":"4cf44f4cbc592b99","name":"isMotorSwitchedOn","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[1]","tot":"msg"},{"t":"set","p":"class","pt":"msg","to":"msg.payload ? \"green\":\"red\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":500,"wires":[["52cb610e23488ba2"]]},{"id":"35bc45f58dc5c661","type":"change","z":"4cf44f4cbc592b99","name":"isMotorRunning","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[2]","tot":"msg"},{"t":"set","p":"class","pt":"msg","to":"msg.payload ? \"green\":\"red\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":540,"wires":[["224a2c2948d7d9a5"]]},{"id":"5b3a8b0c7cd968fa","type":"change","z":"4cf44f4cbc592b99","name":"isTailswitchPulsing","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[3]","tot":"msg"},{"t":"set","p":"class","pt":"msg","to":"msg.payload ? \"green\":\"red\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":580,"wires":[["82fce135993ad1ab"]]},{"id":"f87af242b2287b7c","type":"change","z":"4cf44f4cbc592b99","name":"isMaterialOnBelt","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[4]","tot":"msg"},{"t":"set","p":"class","pt":"msg","to":"msg.payload ? \"green\":\"red\"","tot":"jsonata"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":620,"wires":[["7ab41bd5d19d5f7f"]]},{"id":"7ab41bd5d19d5f7f","type":"ui-text","z":"4cf44f4cbc592b99","group":"a00b86fb96b216a6","order":2,"width":0,"height":0,"name":"","label":"isMaterialOnBelt","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":680,"y":620,"wires":[]},{"id":"cc8b32c267214e28","type":"ui-template","z":"4cf44f4cbc592b99","group":"","page":"","ui":"5ff82627d2caf841","name":"stylesheet","order":0,"width":0,"height":0,"head":"","format":".green span {\n color: green;\n}\n\n.red span {\n color: red;\n}\n","storeOutMessages":true,"passthru":true,"resendOnRefresh":true,"templateScope":"site:style","className":"","x":940,"y":560,"wires":[[]]},{"id":"c4f0157670882335","type":"ui-gauge","z":"4cf44f4cbc592b99","name":"motorAmps","group":"ef2cffe3b927bbb6","order":1,"width":"0","height":"0","gtype":"gauge-half","gstyle":"needle","title":"gauge","units":"units","icon":"","prefix":"","suffix":"","segments":[{"from":"0","color":"#ba6e26"},{"from":"100","color":"#ba6e26"}],"min":0,"max":"150","sizeThickness":16,"sizeGap":4,"sizeKeyThickness":8,"styleRounded":true,"styleGlow":false,"className":"","x":710,"y":760,"wires":[]},{"id":"df73d7c58bbf6069","type":"ui-text","z":"4cf44f4cbc592b99","group":"ef2cffe3b927bbb6","order":2,"width":0,"height":0,"name":"","label":"motorHourMeter","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":720,"y":800,"wires":[]},{"id":"4d14a50a008a4606","type":"ui-text","z":"4cf44f4cbc592b99","group":"98ab0c19d66959eb","order":2,"width":0,"height":0,"name":"","label":"beltTotalTons","format":"{{msg.payload}}","layout":"row-spread","style":false,"font":"","fontSize":16,"color":"#717171","className":"","x":710,"y":880,"wires":[]},{"id":"9dd39216e9bbd6fd","type":"change","z":"4cf44f4cbc592b99","name":"motorAmps","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[0]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":760,"wires":[["c4f0157670882335"]]},{"id":"c4389848cebd29c7","type":"change","z":"4cf44f4cbc592b99","name":"motorHourMeter","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[1]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":400,"y":800,"wires":[["df73d7c58bbf6069"]]},{"id":"dffa222847521883","type":"change","z":"4cf44f4cbc592b99","name":"beltTonsPerHour","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[2]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":410,"y":840,"wires":[["b45120d07ed4a8bb"]]},{"id":"286d69415ef78e7c","type":"change","z":"4cf44f4cbc592b99","name":"beltTotalTons","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload[3]","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":390,"y":880,"wires":[["4d14a50a008a4606"]]},{"id":"b45120d07ed4a8bb","type":"ui-chart","z":"4cf44f4cbc592b99","group":"98ab0c19d66959eb","name":"beltTonsPerHour","label":"chart","order":1,"chartType":"line","category":"beltTonsPerHour","categoryType":"str","xAxisLabel":"","xAxisProperty":"","xAxisPropertyType":"msg","xAxisType":"time","yAxisLabel":"","yAxisProperty":"","ymin":"","ymax":"","action":"append","pointShape":"false","pointRadius":4,"showLegend":true,"removeOlder":1,"removeOlderUnit":"3600","removeOlderPoints":"","colors":["#1f77b4","#aec7e8","#ff7f0e","#2ca02c","#98df8a","#d62728","#ff9896","#9467bd","#c5b0d5"],"width":"6","height":"5","className":"","x":730,"y":840,"wires":[[]]},{"id":"af0626137803b160","type":"comment","z":"4cf44f4cbc592b99","name":"Reading holding register data from modbus server","info":"","x":510,"y":700,"wires":[]},{"id":"bf74f24e625763ff","type":"comment","z":"4cf44f4cbc592b99","name":"Sending output coil data to modbus server","info":"","x":480,"y":1080,"wires":[]},{"id":"c5ab6f8dcb96b79b","type":"comment","z":"4cf44f4cbc592b99","name":"Sending holding register data to modbus server","info":"","x":500,"y":960,"wires":[]},{"id":"8e1387959a33f353","type":"comment","z":"4cf44f4cbc592b99","name":"Reading output coil data from modbus server","info":"","x":490,"y":400,"wires":[]},{"id":"b85a4192ee8a8c38","type":"comment","z":"4cf44f4cbc592b99","name":"Modbus server ","info":"","x":300,"y":180,"wires":[]},{"id":"145bc96e15c34554","type":"modbus-client","name":"Modbus server","clienttype":"tcp","bufferCommands":true,"stateLogEnabled":false,"queueLogEnabled":false,"failureLogEnabled":true,"tcpHost":"127.0.0.1","tcpPort":"10502","tcpType":"DEFAULT","serialPort":"/dev/ttyUSB","serialType":"RTU-BUFFERD","serialBaudrate":"9600","serialDatabits":"8","serialStopbits":"1","serialParity":"none","serialConnectionDelay":"100","serialAsciiResponseStartDelimiter":"0x3A","unit_id":"1","commandDelay":"1","clientTimeout":"1000","reconnectOnTimeout":true,"reconnectTimeout":"2000","parallelUnitIdsAllowed":true,"showErrors":false,"showWarnings":true,"showLogs":true},{"id":"a00b86fb96b216a6","type":"ui-group","name":"Output coils","page":"3876c3cdc50d68a5","width":"3","height":"1","order":1,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"5ff82627d2caf841","type":"ui-base","name":"My Dashboard","path":"/dashboard","includeClientData":true,"acceptsClientConfig":["ui-notification","ui-control"],"showPathInSidebar":false,"navigationStyle":"default","titleBarStyle":"default"},{"id":"ef2cffe3b927bbb6","type":"ui-group","name":"Holding registers","page":"3876c3cdc50d68a5","width":"3","height":"3","order":2,"showTitle":true,"className":"","visible":"true","disabled":"false"},{"id":"98ab0c19d66959eb","type":"ui-group","name":"holding register ","page":"3876c3cdc50d68a5","width":"6","height":"1","order":3,"showTitle":false,"className":"","visible":"true","disabled":"false"},{"id":"3876c3cdc50d68a5","type":"ui-page","name":"Home","ui":"5ff82627d2caf841","path":"/","icon":"home","layout":"grid","theme":"36efce4ebdf1ae91","order":1,"className":"","visible":false,"disabled":"false"},{"id":"36efce4ebdf1ae91","type":"ui-theme","name":"Default Theme","colors":{"surface":"#0094ce","primary":"#0094ce","bgPage":"#eeeeee","groupBg":"#ffffff","groupOutline":"#cccccc"},"sizes":{"pagePadding":"12px","groupGap":"12px","groupBorderRadius":"4px","widgetGap":"12px"}}] -{% endrenderFlow %} +``` +:: The way that we set up this example worked well for the small sample size and quickly getting content on the dashboard, but as you work through your own needs, think about the data structures that will be the most conducive to efficiently working with your data. diff --git a/src/node-red/protocol/mqtt.md b/nuxt/content-guides/node-red/protocol/mqtt.md similarity index 97% rename from src/node-red/protocol/mqtt.md rename to nuxt/content-guides/node-red/protocol/mqtt.md index eec5f60105..08982507e2 100644 --- a/src/node-red/protocol/mqtt.md +++ b/nuxt/content-guides/node-red/protocol/mqtt.md @@ -1,15 +1,14 @@ --- -eleventyNavigation: - key: MQTT - parent: "Communication Protocols" - title: MQTT +title: Using MQTT with Node-RED +metaTitle: "Using MQTT with Node-RED" +navTitle: MQTT +navOrder: 50 meta: - title: Using MQTT with Node-RED description: Learn how to use MQTT with Node-RED. keywords: node red mqtt in, nodered mqtt, mqtt node red, node-red mqtt, node-red mqtt broker, node red mqtt dynamic subscription, mqtt broker node red, nodered mqtt broker, node red mqtt out, mqtt in node red --- -# {{meta.title}} +# Using MQTT with Node-RED Getting devices to talk to each other in industrial environments isn't trivial. You're dealing with spotty networks, power constraints, and devices that need to share data without constant back-and-forth polling. MQTT solves these problems by keeping communication lightweight and flexible. This guide walks you through what MQTT is, why it works well for IIoT, and how to get it running with Node-RED. diff --git a/src/node-red/protocol/opc-ua.md b/nuxt/content-guides/node-red/protocol/opc-ua.md similarity index 98% rename from src/node-red/protocol/opc-ua.md rename to nuxt/content-guides/node-red/protocol/opc-ua.md index d180ae0d28..36cbe748ba 100644 --- a/src/node-red/protocol/opc-ua.md +++ b/nuxt/content-guides/node-red/protocol/opc-ua.md @@ -1,22 +1,15 @@ --- -eleventyNavigation: - key: OPC-UA - parent: "Communication Protocols" +title: Building Secure OPC-UA Server in Node-RED. +metaTitle: "Building Secure OPC-UA Server in Node-RED." +navTitle: OPC-UA +navOrder: 50 meta: - title: Building Secure OPC-UA Server in Node-RED. - description: Learn how to build Build and Deploy a custom OPC UA Server in Node-RED - keywords: node-red,flowfuse, opc-ua, -image: /node-red/protocol/images/opc-ua-2-title-image.png -certified: - - "@flowfuse-certified-nodes/opcua" - - "@flowfuse-certified-nodes/cip-suite" -certifiedIntro: > - This guide builds the server from community packages. Both halves of it, OPC UA - and the Allen Bradley Ethernet/IP link, also have a FlowFuse certified node - maintained for production use: + description: Learn how to build Build and Deploy a custom OPC UA Server in Node-RED + keywords: node-red,flowfuse, opc-ua, +image: /docs/node-red/protocol/images/opc-ua-2-title-image.png --- -# {{meta.title}} +# Building Secure OPC-UA Server in Node-RED. OPC-UA (OPC Unified Architecture) is a communication protocol designed for industrial automation. It enables seamless data exchange and interoperability between various devices, systems, and software applications in the industrial domain. OPC-UA offers secure and reliable communication, making it a preferred choice for building robust industrial solutions. In this document, we will delve into the creation of a fully custom secure OPC-UA Server for PLCs in Node-RED. @@ -62,7 +55,9 @@ Now that we have laid out a concept for our application, let’s build it. ## Install Custom Nodes -{% include "components/certified-node-callout.liquid" %} +::callout{icon="i-lucide-badge-check"} +**Certified nodes for this technology.** This guide builds the server from community packages. Both halves of it, OPC UA and the Allen Bradley Ethernet/IP link, also have a FlowFuse certified node maintained for production use: [opcua](/integrations/?certified=1), [cip-suite](/integrations/?certified=1) +:: First, we need to add three custom nodes that will allow Node-RED to read Ethernet/IP data and add OPC UA Server functionality. @@ -631,6 +626,8 @@ OPC UA is one of several industrial protocols FlowFuse uses to connect PLCs to t Source code for flow used in this documentation - -{% renderFlow %} +::render-flow +```json [{"id":"2e8c7f5c.ab73d","type":"tab","label":"OPC-UA Custom Context Server","disabled":false,"info":""},{"id":"38ce10de.7d8c","type":"opcua-compact-server","z":"2e8c7f5c.ab73d","port":"54845","endpoint":"","productUri":"","acceptExternalCommands":true,"maxAllowedSessionNumber":"10","maxConnectionsPerEndpoint":"10","maxAllowedSubscriptionNumber":"100","alternateHostname":"","name":"","showStatusActivities":false,"showErrors":true,"allowAnonymous":false,"individualCerts":true,"isAuditing":false,"serverDiscovery":true,"users":[],"xmlsetsOPCUA":[],"publicCertificateFile":"/root/.node-red/node_modules/node-red-contrib-opcua-server/certificates/server_selfsigned_cert_2048.pem","privateCertificateFile":"/root/.node-red/node_modules/node-red-contrib-opcua-server/certificates/server_key_2048.pem","registerServerMethod":"1","discoveryServerEndpointUrl":"opc.tcp://192.168.0.114:54845","capabilitiesForMDNS":"","maxNodesPerRead":1000,"maxNodesPerWrite":1000,"maxNodesPerHistoryReadData":100,"maxNodesPerBrowse":3000,"maxBrowseContinuationPoints":"10","maxHistoryContinuationPoints":"10","delayToInit":"1000","delayToClose":"200","serverShutdownTimeout":"100","addressSpaceScript":"function constructAlarmAddressSpace(server, addressSpace, eventObjects, done) {\n // server = the created node-opcua server\n // addressSpace = address space of the node-opcua server\n // eventObjects = add event variables here to hold them in memory from this script\n\n // internal sandbox objects are:\n // node = the compact server node,\n // coreServer = core compact server object for debug and access to NodeOPCUA\n // this.sandboxNodeContext = node context node-red\n // this.sandboxFlowContext = flow context node-red\n // this.sandboxGlobalContext = global context node-red\n // this.sandboxEnv = env variables\n // timeout and interval functions as expected from nodejs\n\n const opcua = coreServer.choreCompact.opcua;\n const LocalizedText = opcua.LocalizedText;\n const namespace = addressSpace.getOwnNamespace();\n\n const Variant = opcua.Variant;\n const DataType = opcua.DataType;\n const DataValue = opcua.DataValue;\n\n var flexServerInternals = this;\n\n this.sandboxFlowContext.set(\"conveyorData.Conveyor_RTS\", false);\n this.sandboxFlowContext.set(\"conveyorData.Robot_RTS\", false);\n this.sandboxFlowContext.set(\"conveyorData.Robot_Position\", 0);\n this.sandboxFlowContext.set(\"conveyorData.Conveyor_Running\", false);\n this.sandboxFlowContext.set(\"conveyorData.Line4_State\", 0);\n this.sandboxFlowContext.set(\"conveyorData.Line4_Fault\", false);\n\n // this.sandboxFlowContext.set(\"isoInput1\", 0);\n // this.setInterval(() => {\n // flexServerInternals.sandboxFlowContext.set(\n // \"isoInput1\",\n // Math.random() + 50.0\n // );\n // }, 500);\n // this.sandboxFlowContext.set(\"isoInput2\", 0);\n // this.sandboxFlowContext.set(\"isoInput3\", 0);\n // this.sandboxFlowContext.set(\"isoInput4\", 0);\n // this.sandboxFlowContext.set(\"isoInput5\", 0);\n // this.sandboxFlowContext.set(\"isoInput6\", 0);\n // this.sandboxFlowContext.set(\"isoInput7\", 0);\n // this.sandboxFlowContext.set(\"isoInput8\", 0);\n // this.sandboxFlowContext.set(\"isoOutput1\", 0);\n // this.setInterval(() => {\n // flexServerInternals.sandboxFlowContext.set(\n // \"isoOutput1\",\n // Math.random() + 10.0\n // );\n // }, 500);\n\n // this.sandboxFlowContext.set(\"isoOutput2\", 0);\n // this.sandboxFlowContext.set(\"isoOutput3\", 0);\n // this.sandboxFlowContext.set(\"isoOutput4\", 0);\n // this.sandboxFlowContext.set(\"isoOutput5\", 0);\n // this.sandboxFlowContext.set(\"isoOutput6\", 0);\n // this.sandboxFlowContext.set(\"isoOutput7\", 0);\n // this.sandboxFlowContext.set(\"isoOutput8\", 0);\n\n coreServer.debugLog(\"init dynamic address space\");\n const rootFolder = addressSpace.findNode(\"RootFolder\");\n\n node.warn(\"construct new address space for OPC UA\");\n\n const myDevice = namespace.addFolder(rootFolder.objects, {\n \"browseName\": \"Line 4 PLC\"\n });\n const conveyorFolder = namespace.addFolder(myDevice, { \"browseName\": \"Conveyor\" });\n const conveyorBools = namespace.addFolder(conveyorFolder, {\n \"browseName\": \"Bools\"\n });\n const conveyorDINTs = namespace.addFolder(conveyorFolder, {\n \"browseName\": \"DINTs\"\n });\n const conveyorFloats = namespace.addFolder(conveyorFolder, {\n \"browseName\": \"Floats\"\n });\n\n // Construct Nodes\n const Conveyor_RTS = namespace.addVariable({\n \"organizedBy\": conveyorBools,\n \"browseName\": \"Conveyor Ready to Start\",\n \"nodeId\": \"ns=1;s=Conveyor_RTS\",\n \"dataType\": \"Boolean\",\n \"value\": {\n \"get\": function () {\n return new Variant({\n \"dataType\": DataType.Boolean,\n \"value\": flexServerInternals.sandboxFlowContext.get(\"conveyorData.Conveyor_RTS\")\n });\n },\n \"set\": function (variant) {\n flexServerInternals.sandboxFlowContext.set(\n \"conveyorData.Conveyor_RTS\",\n variant.value\n );\n return opcua.StatusCodes.Good;\n }\n }\n });\n\n const Robot_RTS = namespace.addVariable({\n \"organizedBy\": conveyorBools,\n \"browseName\": \"Robot Ready to Start\",\n \"nodeId\": \"ns=1;s=Robot_RTS\",\n \"dataType\": \"Boolean\",\n \"value\": {\n \"get\": function () {\n return new Variant({\n \"dataType\": DataType.Boolean,\n \"value\": flexServerInternals.sandboxFlowContext.get(\"conveyorData.Robot_RTS\")\n });\n },\n \"set\": function (variant) {\n flexServerInternals.sandboxFlowContext.set(\n \"conveyorData.Robot_RTS\",\n variant.value\n );\n return opcua.StatusCodes.Good;\n }\n }\n });\n\n const Conveyor_Running = namespace.addVariable({\n \"organizedBy\": conveyorBools,\n \"browseName\": \"Conveyor Running\",\n \"nodeId\": \"ns=1;s=Conveyor_Running\",\n \"dataType\": \"Boolean\",\n \"value\": {\n \"get\": function () {\n return new Variant({\n \"dataType\": DataType.Boolean,\n \"value\": flexServerInternals.sandboxFlowContext.get(\"conveyorData.Conveyor_Running\")\n });\n },\n \"set\": function (variant) {\n flexServerInternals.sandboxFlowContext.set(\n \"conveyorData.Conveyor_Running\",\n variant.value\n );\n return opcua.StatusCodes.Good;\n }\n }\n });\n\n const Line4_Fault = namespace.addVariable({\n \"organizedBy\": conveyorBools,\n \"browseName\": \"Line 4 Faulted\",\n \"nodeId\": \"ns=1;s=Line4_Fault\",\n \"dataType\": \"Boolean\",\n \"value\": {\n \"get\": function () {\n return new Variant({\n \"dataType\": DataType.Boolean,\n \"value\": flexServerInternals.sandboxFlowContext.get(\"conveyorData.Line4_Fault\")\n });\n },\n \"set\": function (variant) {\n flexServerInternals.sandboxFlowContext.set(\n \"conveyorData.Line4_Fault\",\n variant.value\n );\n return opcua.StatusCodes.Good;\n }\n }\n });\n\n const Line4_State = namespace.addVariable({\n \"organizedBy\": conveyorDINTs,\n \"browseName\": \"Line 4 State\",\n \"nodeId\": \"ns=1;s=Line4_State\",\n \"dataType\": \"Int32\",\n \"value\": {\n \"get\": function () {\n return new Variant({\n \"dataType\": DataType.Int32,\n \"value\": flexServerInternals.sandboxFlowContext.get(\"conveyorData.Line4_State\")\n });\n },\n \"set\": function (variant) {\n flexServerInternals.sandboxFlowContext.set(\n \"conveyorData.Line4_State\",\n variant.value\n );\n return opcua.StatusCodes.Good;\n }\n }\n });\n\n const Robot_Position = namespace.addVariable({\n \"organizedBy\": conveyorFloats,\n \"browseName\": \"Robot Axis A1 Position\",\n \"nodeId\": \"ns=1;s=Robot_Position\",\n \"dataType\": \"Float\",\n \"value\": {\n \"get\": function () {\n return new Variant({\n \"dataType\": DataType.Float,\n \"value\": flexServerInternals.sandboxFlowContext.get(\"conveyorData.Robot_Position\")\n });\n },\n \"set\": function (variant) {\n flexServerInternals.sandboxFlowContext.set(\n \"conveyorData.Robot_Position\",\n parseFloat(variant.value)\n );\n return opcua.StatusCodes.Good;\n }\n }\n });\n\n //------------------------------------------------------------------------------\n // Add a view\n //------------------------------------------------------------------------------\n const viewBools = namespace.addView({\n \"organizedBy\": rootFolder.views,\n \"browseName\": \"Line 4 Conveyor Bools\"\n });\n\n const viewDINTs = namespace.addView({\n \"organizedBy\": rootFolder.views,\n \"browseName\": \"Line4 Conveyor DINTs\"\n });\n\n const viewFloats = namespace.addView({\n \"organizedBy\": rootFolder.views,\n \"browseName\": \"Line4 Conveyor Floats\"\n });\n\n viewBools.addReference({\n \"referenceType\": \"Organizes\",\n \"nodeId\": Conveyor_RTS.nodeId\n });\n\n viewBools.addReference({\n \"referenceType\": \"Organizes\",\n \"nodeId\": Robot_RTS.nodeId\n });\n\n viewBools.addReference({\n \"referenceType\": \"Organizes\",\n \"nodeId\": Conveyor_Running.nodeId\n });\n\n viewBools.addReference({\n \"referenceType\": \"Organizes\",\n \"nodeId\": Line4_Fault.nodeId\n });\n\n\n viewDINTs.addReference({\n \"referenceType\": \"Organizes\",\n \"nodeId\": Line4_State.nodeId\n });\n\n viewFloats.addReference({\n \"referenceType\": \"Organizes\",\n \"nodeId\": Robot_Position.nodeId\n });\n coreServer.debugLog(\"create dynamic address space done\");\n node.warn(\"construction of new address space for OPC UA done\");\n\n done();\n}\n","x":960,"y":600,"wires":[]},{"id":"7ae89f134415c51e","type":"eth-ip in","z":"2e8c7f5c.ab73d","endpoint":"f012042b75173b77","mode":"all","variable":"","program":"","name":"Read Line4 Conveyor tags","x":150,"y":600,"wires":[["0c51f44baa08a3b2"]]},{"id":"a0592280baea975b","type":"comment","z":"2e8c7f5c.ab73d","name":"read data from PLC & store in conveyorData context flow memory","info":"","x":350,"y":540,"wires":[]},{"id":"b411a5ce4749fa61","type":"comment","z":"2e8c7f5c.ab73d","name":"Secure OPC UA Server Publishing PLC conveyorData","info":"","x":980,"y":540,"wires":[]},{"id":"0c51f44baa08a3b2","type":"change","z":"2e8c7f5c.ab73d","name":"store PLC Data in flow context memory","rules":[{"t":"set","p":"conveyorData.Conveyor_RTS","pt":"flow","to":"payload.Conveyor_RTS","tot":"msg"},{"t":"set","p":"conveyorData.Conveyor_Running","pt":"flow","to":"payload.Conveyor_Running","tot":"msg"},{"t":"set","p":"conveyorData.Line4_Fault","pt":"flow","to":"payload.Line4_Fault","tot":"msg"},{"t":"set","p":"conveyorData.Line4_State","pt":"flow","to":"payload.Line4_State","tot":"msg"},{"t":"set","p":"conveyorData.Robot_Position","pt":"flow","to":"payload.Robot_Position","tot":"msg"},{"t":"set","p":"conveyorData.Robot_RTS","pt":"flow","to":"payload.Robot_RTS","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":530,"y":600,"wires":[[]]},{"id":"f012042b75173b77","type":"eth-ip endpoint","address":"192.168.0.5","slot":"0","cycletime":"1000","name":"Line 4 PLC","vartable":{"":{"Conveyor_RTS":{"type":"BOOL"},"Conveyor_Running":{"type":"BOOL"},"Line4_Fault":{"type":"BOOL"},"Line4_State":{"type":"DINT"},"Robot_Position":{"type":"REAL"},"Robot_RTS":{"type":"BOOL"}}}}] -{% endrenderFlow %} +``` +:: diff --git a/src/node-red/protocol/websocket.md b/nuxt/content-guides/node-red/protocol/websocket.md similarity index 93% rename from src/node-red/protocol/websocket.md rename to nuxt/content-guides/node-red/protocol/websocket.md index 5f16a47e93..5830bbb5c9 100644 --- a/src/node-red/protocol/websocket.md +++ b/nuxt/content-guides/node-red/protocol/websocket.md @@ -1,15 +1,13 @@ --- -eleventyNavigation: - key: Websocket - parent: "Communication Protocols" +title: Using Websocket with Node-RED +metaTitle: "Using Websocket with Node-RED" +navTitle: Websocket +navOrder: 50 meta: - title: Using Websocket with Node-RED - description: - keywords: node-red websocket, websocket in node-red, websocket node-red -image: + keywords: node-red websocket, websocket in node-red, websocket node-red --- -# {{meta.title}} +# Using Websocket with Node-RED This guide covers WebSocket communication in Node-RED. You'll learn how to connect as a client and set up your own WebSocket server. @@ -64,7 +62,7 @@ Before creating the server, it's important to understand that we will need to li ### Testing the WebSocket Server - + _Testing the WebSocket server with a Websocket client_ Now that you have deployed the Node-RED flow, it is acting as a server that can both send and receive data. To test the server, you can use the [Simple WebSocket Client](https://chromewebstore.google.com/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo?hl=en) extension in your browser. Make sure to install this extension if you want to test the server. @@ -104,9 +102,9 @@ Now, as the section states, we are going to see how you can connect to the WebSo ### Testing the WebSocket Client - + _Testing Websocket Client with Websocket Server_ Now, to test the client, you can send messages from the server and see the debug window for that message in the client instance. Similarly, you can send messages from the client Instance to the server and observe the responses in the debug window of server instance. -For more information on the advaced websocket node configuration refer to the [Websocket Node Documentation](/node-red/core-nodes/websocket/) \ No newline at end of file +For more information on the advaced websocket node configuration refer to the [Websocket Node Documentation](/docs/node-red/core-nodes/network/websocket/) diff --git a/src/node-red/terminology/index.md b/nuxt/content-guides/node-red/terminology/README.md similarity index 95% rename from src/node-red/terminology/index.md rename to nuxt/content-guides/node-red/terminology/README.md index 40a00f8b79..97eae7cd8c 100644 --- a/src/node-red/terminology/index.md +++ b/nuxt/content-guides/node-red/terminology/README.md @@ -1,11 +1,11 @@ --- -eleventyNavigation: - key: Terminology - order: 2 +title: Node-RED Terminology +metaTitle: "Node-RED Terminology" +navTitle: Terminology +navOrder: 10 meta: - title: Node-RED Terminology - description: Explore key terms and concepts used within the Node-RED community, including flows, nodes, subflows, workspace, and more. - keywords: node-red, node-red terminology, node-red concepts, node-red key terms + description: Explore key terms and concepts used within the Node-RED community, including flows, nodes, subflows, workspace, and more. + keywords: node-red, node-red terminology, node-red concepts, node-red key terms --- # Node-RED Terminology @@ -33,7 +33,7 @@ A Node is a fundamental building block used to create flows. Each node represent ![Image displaying node](./images/node-red-node.png "Image displaying node"){data-zoomable} ## Core-Node -The core nodes are the set of nodes that are included with the Node-RED runtime by default, without the need for a node installation procedure. For more information on core nodes, refer to the [Core node docs](/node-red/core-nodes/). +The core nodes are the set of nodes that are included with the Node-RED runtime by default, without the need for a node installation procedure. For more information on core nodes, refer to the [Core node docs](/docs/node-red/core-nodes/). ## Wires The "wires" refer to the connections that link nodes together to define the flow of data. These wires visually represent the direction and flow of information from one node to another within a Node-RED flow. diff --git a/src/node-red/terminology/images/editor-flow-tabs.png b/nuxt/content-guides/node-red/terminology/images/editor-flow-tabs.png similarity index 100% rename from src/node-red/terminology/images/editor-flow-tabs.png rename to nuxt/content-guides/node-red/terminology/images/editor-flow-tabs.png diff --git a/src/node-red/terminology/images/editor-workspace.png b/nuxt/content-guides/node-red/terminology/images/editor-workspace.png similarity index 100% rename from src/node-red/terminology/images/editor-workspace.png rename to nuxt/content-guides/node-red/terminology/images/editor-workspace.png diff --git a/src/node-red/terminology/images/node-input-ouput-port.png b/nuxt/content-guides/node-red/terminology/images/node-input-ouput-port.png similarity index 100% rename from src/node-red/terminology/images/node-input-ouput-port.png rename to nuxt/content-guides/node-red/terminology/images/node-input-ouput-port.png diff --git a/src/node-red/terminology/images/node-palette.png b/nuxt/content-guides/node-red/terminology/images/node-palette.png similarity index 100% rename from src/node-red/terminology/images/node-palette.png rename to nuxt/content-guides/node-red/terminology/images/node-palette.png diff --git a/src/node-red/terminology/images/node-red-editor-deploy-button.png b/nuxt/content-guides/node-red/terminology/images/node-red-editor-deploy-button.png similarity index 100% rename from src/node-red/terminology/images/node-red-editor-deploy-button.png rename to nuxt/content-guides/node-red/terminology/images/node-red-editor-deploy-button.png diff --git a/src/node-red/terminology/images/node-red-editor.png b/nuxt/content-guides/node-red/terminology/images/node-red-editor.png similarity index 100% rename from src/node-red/terminology/images/node-red-editor.png rename to nuxt/content-guides/node-red/terminology/images/node-red-editor.png diff --git a/src/node-red/terminology/images/node-red-message-object.png b/nuxt/content-guides/node-red/terminology/images/node-red-message-object.png similarity index 100% rename from src/node-red/terminology/images/node-red-message-object.png rename to nuxt/content-guides/node-red/terminology/images/node-red-message-object.png diff --git a/src/node-red/terminology/images/node-red-node.png b/nuxt/content-guides/node-red/terminology/images/node-red-node.png similarity index 100% rename from src/node-red/terminology/images/node-red-node.png rename to nuxt/content-guides/node-red/terminology/images/node-red-node.png diff --git a/src/node-red/terminology/images/node-red-palette-manager.png b/nuxt/content-guides/node-red/terminology/images/node-red-palette-manager.png similarity index 100% rename from src/node-red/terminology/images/node-red-palette-manager.png rename to nuxt/content-guides/node-red/terminology/images/node-red-palette-manager.png diff --git a/src/node-red/terminology/images/node-red-subflow.png b/nuxt/content-guides/node-red/terminology/images/node-red-subflow.png similarity index 100% rename from src/node-red/terminology/images/node-red-subflow.png rename to nuxt/content-guides/node-red/terminology/images/node-red-subflow.png diff --git a/src/node-red/terminology/images/node-wire.png b/nuxt/content-guides/node-red/terminology/images/node-wire.png similarity index 100% rename from src/node-red/terminology/images/node-wire.png rename to nuxt/content-guides/node-red/terminology/images/node-wire.png diff --git a/nuxt/content.config.ts b/nuxt/content.config.ts index 2980ad4135..3212253a2d 100644 --- a/nuxt/content.config.ts +++ b/nuxt/content.config.ts @@ -29,6 +29,11 @@ export default defineContentConfig({ source: 'docs/**/*.md', schema: z.object({ navTitle: z.string().optional(), + // The browser/search-result title, when the sidebar label is too short to + // serve as one. Read by nuxt/pages/docs/[...slug].vue; like navGroupOrder + // below, an undeclared key is stripped from frontmatter, which would make + // that read silently undefined. + metaTitle: z.string().optional(), navGroup: z.string().optional(), // Read by useDocsNav to rank the sidebar group headings; without it // declared here @nuxt/content strips the key from frontmatter. diff --git a/nuxt/content/certified-nodes.yml b/nuxt/content/certified-nodes.yml index da18573170..d566ec04a9 100644 --- a/nuxt/content/certified-nodes.yml +++ b/nuxt/content/certified-nodes.yml @@ -19,27 +19,27 @@ bundles: - name: Redis abbr: RE description: In-memory cache and fast key/value store. - url: /node-red/flowfuse/hub/redis/ + url: /docs/flowfuse-nodes/hub/redis/ - name: MQTT abbr: MQ description: Lightweight pub/sub messaging for IoT and event-driven flows. - url: /node-red/flowfuse/mqtt/ + url: /docs/flowfuse-nodes/mqtt/ - name: HTTP Request abbr: HT description: Call REST APIs and web services. - url: /node-red/core-nodes/http-request/ + url: /docs/node-red/core-nodes/network/http-request/ - label: AI nodes: - name: AI nodes abbr: AI description: LLM-powered flows with Gemini, Claude, ChatGPT, and Ollama. both: true - url: /node-red/flowfuse/ai/llm-nodes/ + url: /docs/flowfuse-nodes/ai/llm-nodes/ - name: MCP Server abbr: MCP description: Turn your flows into a Model Context Protocol server so AI agents can call your logic as tools. both: true - url: /node-red/flowfuse/mcp/ + url: /docs/flowfuse-nodes/mcp/ - id: ot label: Certified Nodes — OT @@ -52,28 +52,28 @@ bundles: - name: OPC-UA abbr: OP description: The standard for industrial machine interoperability. - url: /node-red/flowfuse/edge/opcua/ + url: /docs/flowfuse-nodes/edge/opcua/ - name: Modbus TCP & RTU abbr: MO description: Ubiquitous protocol for PLCs and field devices. - url: /node-red/flowfuse/edge/modbus/ + url: /docs/flowfuse-nodes/edge/modbus/ - name: RTSP abbr: RT description: Stream video from IP cameras. - url: /node-red/flowfuse/edge/rtsp/ + url: /docs/flowfuse-nodes/edge/rtsp/ - name: EtherNet/IP abbr: EI description: Rockwell / Allen-Bradley industrial Ethernet protocol. - url: /node-red/flowfuse/edge/cip-suite/ + url: /docs/flowfuse-nodes/edge/cip-suite/ - label: AI nodes: - name: AI nodes abbr: AI description: LLM-powered flows with Gemini, Claude, ChatGPT, and Ollama. both: true - url: /node-red/flowfuse/ai/llm-nodes/ + url: /docs/flowfuse-nodes/ai/llm-nodes/ - name: MCP Server abbr: MCP description: Turn your flows into a Model Context Protocol server so AI agents can call your logic as tools. both: true - url: /node-red/flowfuse/mcp/ + url: /docs/flowfuse-nodes/mcp/ diff --git a/nuxt/content/feature-catalog.yml b/nuxt/content/feature-catalog.yml index c8d39316ef..b871670852 100644 --- a/nuxt/content/feature-catalog.yml +++ b/nuxt/content/feature-catalog.yml @@ -70,7 +70,7 @@ sections: - id: mcp-servers title: Agentic Operations description: "Expose your Node-RED flows as tools an AI agent can call directly, so agents can query the state of the factory or trigger actions without custom integration work." - docsLink: /node-red/flowfuse/mcp/ + docsLink: /docs/flowfuse-nodes/mcp/ tiers: { edge: true, hub: true, fleet: true } - id: onnx-integration title: ONNX Integration diff --git a/nuxt/content/products/edge.yml b/nuxt/content/products/edge.yml index bf9db16721..71c70324a6 100644 --- a/nuxt/content/products/edge.yml +++ b/nuxt/content/products/edge.yml @@ -29,7 +29,7 @@ included: - label: "MQTT Broker" href: "/docs/user/teambroker/" - label: "HTTP Request" - href: "/node-red/core-nodes/http-request/" + href: "/docs/node-red/core-nodes/network/http-request/" - title: "Develop & deploy at the edge" chips: - label: "Edge Development (remote editor proxy)" diff --git a/nuxt/content/products/hub.yml b/nuxt/content/products/hub.yml index 544ca7d072..dd8dbff788 100644 --- a/nuxt/content/products/hub.yml +++ b/nuxt/content/products/hub.yml @@ -37,14 +37,14 @@ included: - title: "Certified for IT systems" chips: - label: "MQTT" - href: "/node-red/flowfuse/mqtt/" + href: "/docs/flowfuse-nodes/mqtt/" - label: "HTTP Request" - href: "/node-red/core-nodes/http-request/" + href: "/docs/node-red/core-nodes/network/http-request/" - "Kafka" - label: "Redis" - href: "/node-red/flowfuse/hub/redis/" + href: "/docs/flowfuse-nodes/hub/redis/" - label: "AI nodes (Gemini, Claude, ChatGPT, Ollama)" - href: "/node-red/flowfuse/ai/llm-nodes/" + href: "/docs/flowfuse-nodes/ai/llm-nodes/" - title: "Governed by default" chips: - label: "Application-Level RBAC" diff --git a/nuxt/lib/chrome.test.mjs b/nuxt/lib/chrome.test.mjs index 5a4cac65d4..ffe5ac947a 100644 --- a/nuxt/lib/chrome.test.mjs +++ b/nuxt/lib/chrome.test.mjs @@ -42,9 +42,17 @@ test('no nav or footer link points at a redirected path', () => { // accept it. So a page that was removed and redirected can sit in the nav // indefinitely, still labelled, pointing somewhere it was never meant to. // Advertise the destination, not the redirect. + // Both maps, because they are written in different shapes. nuxt/redirects.ts uses the + // full Nitro rule object; nuxt/redirects-node-red.ts is a plain `from: to` record fed + // through a builder. Reading only the first left every /node-red/** redirect invisible + // here, which is exactly the set most likely to be left behind in the nav. const redirectsSrc = readFileSync(join(repo, 'nuxt/redirects.ts'), 'utf8') + const nodeRedSrc = readFileSync(join(repo, 'nuxt/redirects-node-red.ts'), 'utf8') const sources = new Set( - [...redirectsSrc.matchAll(/^\s*'([^']+)':\s*{\s*redirect:\s*{\s*to:\s*'([^']+)'/gm)] + [ + ...redirectsSrc.matchAll(/^\s*'([^']+)':\s*{\s*redirect:\s*{\s*to:\s*'([^']+)'/gm), + ...nodeRedSrc.matchAll(/^\s*'([^']+)':\s*'([^']+)',/gm), + ] // A rule whose destination is its own source sends the link nowhere // else, so it is not the drift this test is looking for. There is one // in the file today (/docs/user/expert/), which is its own bug. diff --git a/nuxt/lib/custom-cta-destinations.ts b/nuxt/lib/custom-cta-destinations.ts index c215211d6d..6a251d91d2 100644 --- a/nuxt/lib/custom-cta-destinations.ts +++ b/nuxt/lib/custom-cta-destinations.ts @@ -64,7 +64,7 @@ export const CUSTOM_CTA_DESTINATIONS = { // Event name preserved from the hand-written capture() call this replaces // on the OPC UA integration page (see the note above on migrated events). opcuaCertifiedNodeDocs: { - href: '/node-red/flowfuse/edge/opcua/', + href: '/docs/flowfuse-nodes/edge/opcua/', event: 'cta-certified-opcua-node', }, } as const diff --git a/nuxt/lib/docs-page-title.mjs b/nuxt/lib/docs-page-title.mjs new file mode 100644 index 0000000000..1eab7742aa --- /dev/null +++ b/nuxt/lib/docs-page-title.mjs @@ -0,0 +1,20 @@ +// Which of a docs page's several titles becomes the browser and search-result title. +// +// Lives here rather than inline in nuxt/pages/docs/[...slug].vue so `node --test` can +// assert the order. It is one expression, but it is the expression that decides what +// Google shows for every page under /docs, and getting it wrong is invisible: the page +// still renders, still builds, still passes the link checker, and simply has the wrong +// title. That is exactly how the Node-RED library's pages lost theirs. + +/** + * @param {{metaTitle?: string|null, navTitle?: string|null, title?: string|null}|null} page + * @param {string[]} slugParts the docs path segments, used only as a last resort + * @returns {string} + */ +export function docsPageTitle (page, slugParts = []) { + // metaTitle first: navTitle is the sidebar label, kept short enough to fit the nav + // column, so a page whose full title is longer needs a way to say which one Google + // gets. Pages migrated off Eleventy carry it, and nothing in FlowFuse/flowfuse's docs + // sets it, so their titles are unaffected by its presence here. + return page?.metaTitle || page?.navTitle || page?.title || slugParts.at(-1) || 'Documentation' +} diff --git a/nuxt/lib/docs-page-title.test.mjs b/nuxt/lib/docs-page-title.test.mjs new file mode 100644 index 0000000000..3cf5e075b7 --- /dev/null +++ b/nuxt/lib/docs-page-title.test.mjs @@ -0,0 +1,37 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' + +import { docsPageTitle } from './docs-page-title.mjs' + +test('metaTitle wins, because navTitle is only the sidebar label', () => { + // The defect this pins: with navTitle first, every migrated library page's title + // collapsed to its nav label and "Using MySQL with Node-RED (2026 Updated)" shipped + // as "MySQL". Nothing failed; the pages just had the wrong title. + assert.equal( + docsPageTitle({ + metaTitle: 'Using MySQL with Node-RED (2026 Updated)', + navTitle: 'MySQL', + title: 'Using MySQL with Node-RED (2026 Updated)', + }), + 'Using MySQL with Node-RED (2026 Updated)' + ) +}) + +test('a page from FlowFuse/flowfuse still titles by its nav label', () => { + // Nothing in that repo sets metaTitle, so adding the field must not move their titles. + assert.equal(docsPageTitle({ navTitle: 'Getting Started', title: 'Getting Started' }), 'Getting Started') +}) + +test('title carries the page when there is no nav label', () => { + assert.equal(docsPageTitle({ title: 'Concepts' }), 'Concepts') +}) + +test('the last path segment is the last resort, then a generic name', () => { + assert.equal(docsPageTitle(null, ['user', 'concepts']), 'concepts') + assert.equal(docsPageTitle(null, []), 'Documentation') + assert.equal(docsPageTitle(undefined), 'Documentation') +}) + +test('an empty string is skipped rather than winning', () => { + assert.equal(docsPageTitle({ metaTitle: '', navTitle: 'MySQL' }), 'MySQL') +}) diff --git a/nuxt/lib/guides-frontmatter.mjs b/nuxt/lib/guides-frontmatter.mjs new file mode 100644 index 0000000000..66f2a2fea2 --- /dev/null +++ b/nuxt/lib/guides-frontmatter.mjs @@ -0,0 +1,106 @@ +// Frontmatter checks over the guides tree, for the two ways a page's titles go wrong +// silently. Both of these shipped in the Node-RED library migration and neither produced +// a build error, a broken link or a failing test - the only symptom was the rendered page +// being wrong, which is why they are asserted here rather than left to review. +// +// Test-only, so unlike guides-sync.mjs this may depend on node_modules. + +import { join } from 'node:path' + +import { listGuideFiles } from './guides-sync.mjs' +import { readFrontmatter } from './meta-title-length.mjs' + +/** Every markdown page under the guides tree, with its parsed frontmatter. */ +export function readGuidePages (guidesDir) { + return listGuideFiles(guidesDir) + .filter(relPath => relPath.endsWith('.md')) + .map(relPath => ({ relPath, frontmatter: readFrontmatter(join(guidesDir, relPath)) || {} })) +} + +// A directory index, under either name. A guide's section index is a README.md in the +// authored tree and an index.md in the content tree, and where that rename happens has +// already moved once, so both spellings have to count. Recognising only one leaves every +// nested index alone in its own bucket, and the duplicate-label check below then passes +// for the wrong reason instead of failing. +const INDEX_NAMES = new Set(['README.md', 'index.md']) + +/** + * The nav node a page labels: an index titles the directory it sits in, any other page + * titles itself. `node-red/getting-started/README.md` and `node-red/getting-started.md` + * would both label `node-red/getting-started`. + */ +function navNodeFor (relPath) { + const segments = relPath.split('/') + if (INDEX_NAMES.has(segments.at(-1))) return segments.slice(0, -1).join('/') + return [...segments.slice(0, -1), segments.at(-1).replace(/\.md$/, '')].join('/') +} + +/** + * Groups of nav entries a reader cannot tell apart, because they share both a label and a + * position in the sidebar. + * + * The sidebar nests by directory and labels each entry with `navTitle`, so a label is only + * useful if it is unique among the entries it appears next to: its siblings, and the + * parent it is indented under. nuxt/lib/docs-nav.mjs cannot catch this, because as far as + * it is concerned the titles it was handed are the titles. + * + * The migration hit this by giving every nested directory index the nav key of its + * *parent* Eleventy nav entry instead of its own, so getting-started/{editor,library, + * programming}/README.md all came out as "Getting started" nested underneath a "Getting + * started" that was already there. + */ +export function findDuplicateSiblingNavTitles (guidesDir) { + const labelled = readGuidePages(guidesDir) + .filter(({ frontmatter }) => typeof frontmatter.navTitle === 'string') + .map(({ relPath, frontmatter }) => ({ + relPath, + node: navNodeFor(relPath), + navTitle: frontmatter.navTitle, + })) + + const fileFor = new Map(labelled.map(p => [p.node, p.relPath])) + const labelFor = new Map(labelled.map(p => [p.node, p.navTitle])) + const groups = new Map() + + for (const { relPath, node, navTitle } of labelled) { + const parent = node.split('/').slice(0, -1).join('/') + const key = `${parent} ${navTitle}` + + if (!groups.has(key)) groups.set(key, { under: parent || '.', navTitle, files: new Set() }) + groups.get(key).files.add(relPath) + + // A parent indented directly above its children is as ambiguous as two siblings, + // and it is the shape the screenshot on #5750 showed. + if (labelFor.get(parent) === navTitle) groups.get(key).files.add(fileFor.get(parent)) + } + + return [...groups.values()] + .filter(group => group.files.size > 1) + .map(({ under, navTitle, files }) => ({ under, navTitle, files: [...files].sort() })) +} + +/** + * Pages whose full title can never reach a browser tab or a search result. + * + * nuxt/pages/docs/[...slug].vue titles a page `metaTitle || navTitle || title`. `navTitle` + * is deliberately short, because it has to fit the sidebar column, so a page that gives + * `title` the whole phrase and `navTitle` an abbreviation of it needs `metaTitle` to say + * which one Google gets. Without it the abbreviation wins and the phrase is only ever + * visible as the on-page heading. + * + * This is how 121 moved library pages lost their titles: "Using MySQL with Node-RED (2026 + * Updated)" became "MySQL", with nothing anywhere reporting a problem. + */ +export function findPagesWithUnreachableTitle (guidesDir) { + return readGuidePages(guidesDir) + .filter(({ frontmatter: fm }) => + typeof fm.title === 'string' && + typeof fm.navTitle === 'string' && + fm.navTitle !== fm.title && + typeof fm.metaTitle !== 'string') + .map(({ relPath, frontmatter }) => ({ + file: relPath, + title: frontmatter.title, + navTitle: frontmatter.navTitle, + })) +} diff --git a/nuxt/lib/guides-frontmatter.test.mjs b/nuxt/lib/guides-frontmatter.test.mjs new file mode 100644 index 0000000000..0ae0ca2b5a --- /dev/null +++ b/nuxt/lib/guides-frontmatter.test.mjs @@ -0,0 +1,127 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' +import { tmpdir } from 'node:os' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { findDuplicateSiblingNavTitles, findPagesWithUnreachableTitle } from './guides-frontmatter.mjs' +import { GUIDES_SOURCE } from './guides-sync.mjs' +import { isDirectory } from './meta-title-length.mjs' + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '../..') +const guidesDir = join(repoRoot, GUIDES_SOURCE) + +function scratch (files) { + const root = mkdtempSync(join(tmpdir(), 'guides-frontmatter-')) + for (const [relPath, content] of Object.entries(files)) { + const path = join(root, relPath) + mkdirSync(dirname(path), { recursive: true }) + writeFileSync(path, content, 'utf8') + } + return root +} + +function page (fm) { + const lines = Object.entries(fm).map(([k, v]) => `${k}: "${v}"`) + return `---\n${lines.join('\n')}\n---\n\n# Heading\n` +} + +// Guards the real-tree tests below against a silent no-op if the source layout moves. +test('the guides tree exists where these tests expect it', () => { + assert.ok(isDirectory(guidesDir), `expected ${guidesDir} to exist`) +}) + +test('every sidebar entry is distinguishable from the ones beside it', () => { + const duplicates = findDuplicateSiblingNavTitles(guidesDir) + assert.deepEqual(duplicates, [], duplicates + .map(d => `under ${d.under}/: ${d.files.length} nav entries all labelled "${d.navTitle}" (${d.files.join(', ')})`) + .join('\n')) +}) + +test('every page whose title is longer than its nav label says which one Google gets', () => { + const unreachable = findPagesWithUnreachableTitle(guidesDir) + assert.deepEqual(unreachable, [], unreachable + .map(p => `${p.file}: title "${p.title}" can never render, navTitle "${p.navTitle}" wins; set metaTitle`) + .join('\n')) +}) + +test('a directory index competes with its parent\'s children, not with its own', () => { + // The exact shape the migration got wrong: three nested indexes carrying the nav label + // of the section they sit under, which is already used by that section's own index. + const root = scratch({ + 'node-red/README.md': page({ navTitle: 'Using Node-RED' }), + 'node-red/getting-started/README.md': page({ navTitle: 'Getting started' }), + 'node-red/getting-started/editor/README.md': page({ navTitle: 'Getting started' }), + 'node-red/getting-started/library/README.md': page({ navTitle: 'Getting started' }), + }) + + try { + const [group, ...rest] = findDuplicateSiblingNavTitles(root) + assert.deepEqual(rest, []) + assert.equal(group.navTitle, 'Getting started') + // The section's own index is named alongside its children: the reader sees one + // "Getting started" indented under another, which is what the reviewer screenshotted. + assert.deepEqual(group.files, [ + 'node-red/getting-started/README.md', + 'node-red/getting-started/editor/README.md', + 'node-red/getting-started/library/README.md', + ].sort()) + } finally { + rmSync(root, { recursive: true, force: true }) + } +}) + +test('a directory index is recognised under either name, so the rename cannot blind this', () => { + // #5752 renames every guide README.md to index.md. Keying only off README.md put each + // nested index in a bucket of its own, and this check went green with the duplicate + // labels still in place. + for (const indexName of ['README.md', 'index.md']) { + const root = scratch({ + [`node-red/getting-started/${indexName}`]: page({ navTitle: 'Getting started' }), + [`node-red/getting-started/editor/${indexName}`]: page({ navTitle: 'Getting started' }), + }) + + try { + const [group, ...rest] = findDuplicateSiblingNavTitles(root) + assert.deepEqual(rest, [], `${indexName}: expected exactly one clash`) + assert.deepEqual(group.files, [ + `node-red/getting-started/${indexName}`, + `node-red/getting-started/editor/${indexName}`, + ].sort()) + } finally { + rmSync(root, { recursive: true, force: true }) + } + } +}) + +test('the same nav label in two different sections is fine', () => { + const root = scratch({ + 'application-guide/worked-examples/README.md': page({ navTitle: 'Worked examples' }), + 'node-red-guide/worked-examples/README.md': page({ navTitle: 'Worked examples' }), + }) + + try { + assert.deepEqual(findDuplicateSiblingNavTitles(root), []) + } finally { + rmSync(root, { recursive: true, force: true }) + } +}) + +test('a short nav label beside a long title needs a metaTitle, an identical one does not', () => { + const root = scratch({ + 'node-red/database/mysql.md': page({ title: 'Using MySQL with Node-RED (2026 Updated)', navTitle: 'MySQL' }), + 'node-red/database/redis.md': page({ title: 'Using Redis with Node-RED', navTitle: 'Redis', metaTitle: 'Using Redis with Node-RED' }), + 'application-guide/data-plane.md': page({ title: 'Data plane', navTitle: 'Data plane' }), + }) + + try { + assert.deepEqual(findPagesWithUnreachableTitle(root), [{ + file: 'node-red/database/mysql.md', + title: 'Using MySQL with Node-RED (2026 Updated)', + navTitle: 'MySQL', + }]) + } finally { + rmSync(root, { recursive: true, force: true }) + } +}) diff --git a/nuxt/lib/guides-sync.mjs b/nuxt/lib/guides-sync.mjs index 012476681e..be5faeab5e 100644 --- a/nuxt/lib/guides-sync.mjs +++ b/nuxt/lib/guides-sync.mjs @@ -58,7 +58,14 @@ export function destinationFor (relPath, contentDocsDir, publicDocsDir) { * repo's history rather than flowfuse's. */ export function injectFrontmatter (content, { editUrl, updated }) { - const injected = `editUrl: ${editUrl}\nupdated: ${updated}\n` + // `updated` is omitted rather than emitted empty when git could not answer. YAML reads + // a valueless key as null, which the collection schema (`z.string().optional()`) + // accepts as absent but not as null, so an empty value turns a missing timestamp into + // a parse complaint on every guide page at once. + const injected = [ + `editUrl: ${editUrl}`, + updated ? `updated: ${updated}` : null, + ].filter(Boolean).join('\n') + '\n' return /^---/.test(content) ? content.replace(/^---\n/, `---\n${injected}`) diff --git a/nuxt/lib/guides-sync.test.mjs b/nuxt/lib/guides-sync.test.mjs index 88c006f307..b3971b90a1 100644 --- a/nuxt/lib/guides-sync.test.mjs +++ b/nuxt/lib/guides-sync.test.mjs @@ -30,6 +30,19 @@ function write (path, content) { writeFileSync(path, content, 'utf8') } +test('a timestamp git could not supply is left out, not emitted empty', () => { + // `updated:` with no value is YAML null. The collection schema takes the key as + // optional but not nullable, so an empty value turns "git was unavailable" into a + // parse complaint on every guide page at once. Reachable whenever .git is absent or + // the commit touching the file falls outside a shallow fetch. + const withTimestamp = injectFrontmatter('---\ntitle: T\n---\n', { editUrl: 'u', updated: '2026-09-08' }) + assert.match(withTimestamp, /^---\neditUrl: u\nupdated: 2026-09-08\ntitle: T\n/) + + const without = injectFrontmatter('---\ntitle: T\n---\n', { editUrl: 'u', updated: '' }) + assert.match(without, /^---\neditUrl: u\ntitle: T\n/) + assert.ok(!/updated:/.test(without), 'an empty timestamp must not leave a valueless key') +}) + test('a README becomes its section index, so a directory of guides nests like a directory of docs', () => { assert.equal( destinationFor('application-guide/README.md', '/content/docs', '/public/docs'), diff --git a/nuxt/lib/mdc-bindings.mjs b/nuxt/lib/mdc-bindings.mjs new file mode 100644 index 0000000000..01cb08e78e --- /dev/null +++ b/nuxt/lib/mdc-bindings.mjs @@ -0,0 +1,107 @@ +// Finding markdown that MDC will read as a Vue binding rather than as text. +// +// @nuxt/content compiles a content file with MDC, and MDC treats `{{ ... }}` in prose the +// way a Vue template does: it becomes a binding node against a scope that holds the page, +// not the words that were typed. Nothing errors. `{{ msg.payload }}` in a paragraph simply +// renders as nothing, and a page can lose a sentence with no sign anywhere that it did. +// +// The Node-RED pages are full of mustache examples - it is the Template node's own +// templating syntax - so this is not a theoretical risk on this tree. +// +// The scan is deliberately over-broad: it reports every `{{` that is not inside code, +// whether or not a `}}` closes it, because the failure modes are not all the same shape. +// +// `{{ msg.payload }}` one line, closed -> binds, renders empty +// `{{{triple}}}` triple brace -> binds (value `{triple`), renders empty +// `{{` .. newline .. `}}` spanning lines -> crashes the markdown parser outright +// `{{` with no `}}` unclosed -> crashes the markdown parser outright +// +// A scan that only looked for a closed, single-line pair would miss the last two, which are +// the ones that break a build rather than quietly emptying a sentence, and would miss the +// triple-brace form too (the inner `{` is not what a `[^{}]*` body matches). +// +// Where a mustache is safe, and why this scan skips exactly those places: +// +// fenced code blocks, indented code blocks, inline code spans +// MDC leaves the text alone. This is how every Node-RED page shows mustache syntax, +// and it is the fix for anything this scan reports. +// +// Block-level raw HTML is NOT skipped. MDC does leave `{{ }}` alone inside a raw HTML +// *block*, but not inside *inline* HTML: `{{ msg.payload }}` in a paragraph +// binds exactly like bare prose does. Telling those two apart needs the parser, so this +// reports both and the writer moves the example into a code span. + +/** Strips fenced code blocks, leaving line count intact so reported lines stay right. */ +function blankFencedBlocks (lines) { + const out = [...lines] + let fence = null + for (let i = 0; i < out.length; i++) { + const opening = out[i].match(/^\s{0,3}(`{3,}|~{3,})/) + if (!fence && opening) { + fence = opening[1][0].repeat(opening[1].length) + out[i] = '' + continue + } + if (fence) { + const closing = out[i].match(/^\s{0,3}(`{3,}|~{3,})\s*$/) + out[i] = '' + if (closing && closing[1][0] === fence[0] && closing[1].length >= fence.length) fence = null + } + } + return out +} + +/** Strips inline code spans and indented code blocks from one line. */ +function blankCode (line) { + if (/^(\s{4,}|\t)/.test(line)) return '' + return line.replace(/(`+)(?:(?!\1)[\s\S])*?\1/g, '') +} + +/** + * Every place in `markdown` where MDC could read a mustache as a binding. + * + * Returns `{ line, text }` per hit, one-based lines, so a failure message can point at the + * file and line rather than just naming the file. + */ +export function bindableMustaches (markdown) { + const lines = blankFencedBlocks(String(markdown).split('\n')) + const hits = [] + lines.forEach((line, i) => { + if (blankCode(line).includes('{{')) hits.push({ line: i + 1, text: line.trim() }) + }) + return hits +} + +/** + * MDC component blocks that are opened and never closed. + * + * `::component{...}` opens a container, and its closing `::` is not optional: without it + * the container swallows the rest of the file, so every heading after it stops being a + * heading. Nothing errors and the component still renders, which is why this is worth a + * check. It shipped once: three guides had a raw `` CTA replaced with + * `::cta-image{...}` and no closing `::`, and the only symptom was 23 anchors in those + * pages' own tables of contents pointing at headings that no longer existed. + * + * Counts openers against closers rather than parsing: a `::` on its own line closes the + * innermost open block, so the two must balance over a file. + * + * @param {string} markdown + * @returns {Array<{line: number, component: string}>} unclosed openers, outermost first + */ +export function unclosedMdcBlocks (markdown) { + const lines = blankFencedBlocks(String(markdown).split('\n')) + const open = [] + + lines.forEach((line, index) => { + // An opener names a component; a bare `::` closes one. A single-colon + // `:component{...}` is the inline form and needs no close. + const opener = line.match(/^\s*::([a-z][a-z0-9-]*)/i) + if (opener) { + open.push({ line: index + 1, component: opener[1] }) + return + } + if (/^\s*::\s*$/.test(line)) open.pop() + }) + + return open +} diff --git a/nuxt/lib/mdc-bindings.test.mjs b/nuxt/lib/mdc-bindings.test.mjs new file mode 100644 index 0000000000..4492d40a77 --- /dev/null +++ b/nuxt/lib/mdc-bindings.test.mjs @@ -0,0 +1,126 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { dirname, join, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { findFiles } from './find-files.mjs' +import { GUIDES_SOURCE } from './guides-sync.mjs' +import { isDirectory } from './meta-title-length.mjs' +import { bindableMustaches, unclosedMdcBlocks } from './mdc-bindings.mjs' + +const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), '../..') + +// The trees this repository owns and always has in a checkout. FlowFuse/flowfuse's own +// docs go through the same MDC pipeline and the same function checks them, but they are +// only on disk after a docs sync (nuxt/content/docs is build-generated and gitignored), and +// whether that has run by the time `npm test` does is decided in another repository's +// workflow - so requiring them here would make this test's coverage depend on something +// this repository cannot see. They are scanned below if they happen to be present. +const REQUIRED_TREES = [GUIDES_SOURCE, 'nuxt/content/handbook'] +const OPTIONAL_TREES = ['nuxt/content/docs'] + +test('a mustache in prose is reported', () => { + // MDC compiles `{{ ... }}` in prose into a Vue binding against the page scope, so the + // text renders as nothing at all. Confirmed against this branch's dev server: the + // parsed AST holds `{ type: 'element', tag: 'binding', props: { value: 'msg.payload' } }` + // and the served HTML has an empty gap where the words were. + assert.deepEqual(bindableMustaches('P {{ msg.payload }} Q'), [{ line: 1, text: 'P {{ msg.payload }} Q' }]) + assert.equal(bindableMustaches('tight {{payload.name}} here').length, 1) +}) + +test('a triple-brace mustache is reported, which a {{ ... }} pattern misses', () => { + // `{{{triple}}}` binds too - MDC reads it as the expression `{triple` - but a pattern + // whose body is `[^{}]*` cannot match it, because the body starts with a brace. This is + // the Template node's own escape hatch for unescaped substitution, so it appears in + // exactly the prose most at risk of carrying it. + assert.equal(bindableMustaches('P {{{triple}}} Q').length, 1) +}) + +test('a mustache spanning two lines is reported, and it does not merely render empty', () => { + // A `{{` with its `}}` on a later line, and a `{{` with no `}}` at all, both crash the + // markdown parser outright rather than binding: micromark throws + // "Cannot access index N in a splice buffer of size N". So the shapes a single-line, + // must-be-closed pattern misses are the ones that break a build, not just a sentence. + assert.deepEqual(bindableMustaches('P {{\nmsg.payload\n}} Q').map(h => h.line), [1]) + assert.deepEqual(bindableMustaches('P {{ msg.payload with no close').map(h => h.line), [1]) +}) + +test('a mustache inside code is not reported, because that is the fix', () => { + // Inline code spans, fenced blocks and indented blocks all reach the page as typed. + // Every Node-RED page that shows mustache syntax does it this way. + assert.deepEqual(bindableMustaches('P `{{ msg.payload }}` Q'), []) + assert.deepEqual(bindableMustaches('```\n{{ msg.payload }}\n```'), []) + assert.deepEqual(bindableMustaches('```json\n{"a": "{{ b }}"}\n```'), []) + assert.deepEqual(bindableMustaches('~~~\n{{ msg.payload }}\n~~~'), []) + assert.deepEqual(bindableMustaches(' {{ msg.payload }}'), []) + // A fence inside an MDC component block still counts as a fence. + assert.deepEqual(bindableMustaches('::render-flow\n```json\n{{ x }}\n```\n::'), []) +}) + +test('a mustache in inline HTML is reported, unlike one in an HTML block', () => { + // MDC leaves `{{ }}` alone inside a raw HTML *block* but binds it inside *inline* HTML: + // `{{ msg.payload }}` in a paragraph binds exactly like bare prose. Both + // are reported, deliberately - telling them apart needs the parser, and the fix for + // either is the same. + assert.equal(bindableMustaches('P {{ msg.payload }} Q').length, 1) + assert.equal(bindableMustaches('
\n{{ msg.payload }}\n
').length, 1) +}) + +test('the line number points at the offending line, not the file', () => { + const md = ['# T', '', 'fine', '', 'P {{ a }} Q', '', '`{{ b }}`', '', 'R {{ c }} S'].join('\n') + assert.deepEqual(bindableMustaches(md).map(h => h.line), [5, 9]) +}) + +for (const tree of REQUIRED_TREES) { + // Guards the scan below against passing because it found nothing to scan. + test(`${tree} exists where this test expects it`, () => { + assert.ok(isDirectory(join(REPO_ROOT, tree)), `expected ${tree} to exist`) + }) +} + +test('no content file carries a mustache MDC would read as a binding', () => { + // The Node-RED pages document the Template node, whose whole subject is mustache + // syntax, so this tree is the one where a stray `{{ }}` in prose is likeliest - and + // losing a sentence to it leaves no trace anywhere: no warning, no failed build, no + // empty element, just missing words. + const hits = [] + for (const tree of [...REQUIRED_TREES, ...OPTIONAL_TREES]) { + const dir = join(REPO_ROOT, tree) + if (!isDirectory(dir)) continue + for (const path of findFiles(dir, ['.md'])) { + for (const hit of bindableMustaches(readFileSync(path, 'utf8'))) { + hits.push(`${relative(REPO_ROOT, path)}:${hit.line}: ${hit.text}`) + } + } + } + assert.deepEqual(hits, [], `put the mustache in a code span or a fenced block:\n${hits.join('\n')}`) +}) + +test('an MDC component block that is never closed is reported', () => { + // `::component{...}` opens a container and the closing `::` is not optional. Without + // it the container swallows the rest of the file, so every heading after it stops + // being a heading, and nothing errors: the component still renders. This shipped once, + // on three guides whose CTA was converted from a raw `
`, and the only + // symptom was 23 anchors in those pages' own tables of contents pointing at headings + // that had quietly stopped existing. + assert.deepEqual( + unclosedMdcBlocks('# T\n\n::cta-image{src="a" cta="sign-up"}\n\n## Heading\n'), + [{ line: 3, component: 'cta-image' }] + ) + + // Closed, nested, and inside a fence are all fine. + assert.deepEqual(unclosedMdcBlocks('::cta-image{src="a"}\n::\n\n## Heading\n'), []) + assert.deepEqual(unclosedMdcBlocks('::callout{icon="x"}\ntext\n::\n\n::cta-image{src="a"}\n::\n'), []) + assert.deepEqual(unclosedMdcBlocks('```\n::cta-image{src="a"}\n```\n'), []) +}) + +test('every MDC component block in the guides is closed', () => { + const offenders = [] + for (const path of findFiles(join(REPO_ROOT, GUIDES_SOURCE), ['.md'])) { + for (const { line, component } of unclosedMdcBlocks(readFileSync(path, 'utf8'))) { + offenders.push(`${relative(REPO_ROOT, path)}:${line}: ::${component} is never closed`) + } + } + assert.deepEqual(offenders, [], offenders.join('\n')) +}) diff --git a/nuxt/lib/node-red-help.mjs b/nuxt/lib/node-red-help.mjs new file mode 100644 index 0000000000..9985076a50 --- /dev/null +++ b/nuxt/lib/node-red-help.mjs @@ -0,0 +1,170 @@ +// Selecting one node's built-in help out of the Node-RED project's locale files. +// +// The core-node pages are ordinary content files that ask for their help by name, via the +// ::node-red-help component; this is the part that knows how to find it. Kept in nuxt/lib +// as plain JS so `node --test` can exercise the selection rules without the network. + +import sanitizeHtml from 'sanitize-html' + +export const UPSTREAM = 'https://raw.githubusercontent.com/node-red/node-red/master/packages/node_modules/%40node-red/nodes/locales/en-US' + +/** + * Hyphen-versus-space is not cosmetic here. The old catalogue asked for `mqtt-in` while + * upstream's file says `mqtt in`; the Eleventy version matched on the literal string, + * found nothing, and rendered an empty help section, which is how the live mqtt-in and + * mqtt-out pages came to ship no help at all. + */ +const normalise = (s) => String(s).toLowerCase().replace(/[\s-]+/g, ' ').trim() + +/** Every `` + +test('help is selected by name out of a file holding several nodes', () => { + const html = [ + block('mqtt in', 'IN'), + block('mqtt out', 'OUT'), + block('mqtt-broker', 'BROKER'), + ].join('\n') + + assert.equal(extractHelp(html, 'mqtt out'), 'OUT') +}) + +test('a hyphenated request still finds a space-separated help name', () => { + // The catalogue asked for `mqtt-in` while upstream says `mqtt in`. Eleventy matched on + // the literal string, found nothing, and shipped two pages with no help at all. + assert.equal(extractHelp(block('mqtt in', 'IN'), 'mqtt-in'), 'IN') +}) + +test('an exact name does not drag in its longer siblings', () => { + // `file` is Write File and `file in` is Read File. Prefix-matching alone would put both + // nodes' help on the Write File page. + const html = [block('file', 'WRITE'), block('file in', 'READ')].join('\n') + + assert.equal(extractHelp(html, 'file'), 'WRITE') + assert.equal(extractHelp(html, 'file in'), 'READ') +}) + +test('a name covering a family returns the whole family, in document order', () => { + // /docs/node-red/core-nodes/common/link/ documents link in, link out and link call, as + // the Eleventy page did: its xpath was a starts-with() over a node set, joined. Taking + // only the first match dropped most of the page while still captioning it as the + // node's complete built-in help. + const html = [ + block('link in', 'IN'), + block('link out', 'OUT'), + block('link call', 'CALL'), + ].join('\n') + + assert.equal(extractHelp(html, 'link'), 'IN\n\nOUT\n\nCALL') +}) + +test('a name that matches nothing returns empty rather than the wrong node', () => { + assert.equal(extractHelp(block('tcp in', 'TCP'), 'udp in'), '') +}) + +test('help upstream offers that nothing asked for is reported', () => { + // The only check that catches being quietly *wrong* rather than quietly stale. Asking + // for `watch` in 23-watch.html succeeds, so nothing errors, while `file in` - Read + // File's actual help - sits unclaimed. That shipped the Watch node's help under the + // Read File title for years. + const html = [block('file', 'WRITE'), block('file in', 'READ')].join('\n') + + assert.deepEqual(unclaimedHelpNames(html, ['file']), []) + assert.deepEqual(unclaimedHelpNames(html, ['file in']), ['file']) + assert.deepEqual(unclaimedHelpNames(html, ['nothing']), ['file', 'file in']) +}) + +test('the upstream URL cannot be steered outside the locale tree', () => { + // category and file reach this from a request's query string, and they are pasted into + // a URL, so a path segment is all they may be. + assert.ok(isSafeHelpRef({ category: 'network', file: '10-mqtt' })) + assert.ok(!isSafeHelpRef({ category: '../../etc', file: '10-mqtt' })) + assert.ok(!isSafeHelpRef({ category: 'network', file: '10-mqtt?x=1' })) + assert.ok(!isSafeHelpRef({ category: 'net work', file: '10-mqtt' })) + + assert.match(helpUrlFor({ category: 'network', file: '10-mqtt' }), /\/network\/10-mqtt\.html$/) + assert.throws(() => helpUrlFor({ category: '..', file: 'x' }), /Unsafe Node-RED help reference/) +}) + +// Guards the real-tree tests below against passing because they found nothing to check. +test('the guides tree exists where these tests expect it', () => { + assert.ok(isDirectory(GUIDES_DIR), `expected ${GUIDES_DIR} to exist`) +}) + +test('an upper-case locale file name is a valid reference', () => { + // Five of the six parser locale files are upper case upstream. The guard was + // lower-case-only, so isSafeHelpRef rejected all five before the fetch, the route + // answered 400 rather than the help, and the component swapped the whole Node help + // section for its "could not be read from the Node-RED project" notice. Nothing failed: + // the pages prerendered, and the CSV, HTML, JSON, XML and YAML pages each shipped with + // that notice where their built-in help should have been. + for (const file of ['70-CSV', '70-HTML', '70-JSON', '70-XML', '70-YAML']) { + assert.ok(isSafeHelpRef({ category: 'parsers', file }), `${file} should be accepted`) + } + assert.match(helpUrlFor({ category: 'parsers', file: '70-JSON' }), /\/parsers\/70-JSON\.html$/) + + // Still one path segment, still nothing that can steer the URL. + for (const file of ['../etc', '70-JSON?x=1', '70 JSON', '70/JSON', '70.JSON', '']) { + assert.ok(!isSafeHelpRef({ category: 'parsers', file }), `${file} should be rejected`) + } +}) + +test('every help reference the guides make is one the route will accept', () => { + // The check that would have caught the upper-case guard offline. A reference the route + // rejects does not fail a build - the component renders its error notice and the page + // prerenders fine - so nothing else in the pipeline notices a page losing its help. + const bad = [] + for (const path of findFiles(GUIDES_DIR, ['.md'])) { + for (const { attrs, raw } of helpRefsIn(readFileSync(path, 'utf8'))) { + const where = relative(REPO_ROOT, path) + for (const key of ['category', 'file', 'node']) { + if (!attrs[key]) bad.push(`${where}: ::node-red-help is missing ${key}= (${raw})`) + } + if (attrs.category && attrs.file && !isSafeHelpRef(attrs)) { + bad.push(`${where}: the route rejects category="${attrs.category}" file="${attrs.file}"`) + } + } + } + assert.deepEqual(bad, [], bad.join('\n')) +}) + +test('the sanitiser keeps the classes that carry meaning rather than styling', () => { + // The allowlist let no `class` through at all, which cost more than styling: upstream + // marks an OPTIONAL message property with `
` and a type (rather + // than more prose) with ``. Both render as plain text + // without the class, so an optional property became indistinguishable from a required + // one. The live Eleventy pages keep both, so dropping them was a regression. + const html = sanitiseHelp( + '
' + + '
template string
' + + '
A template.
' + ) + + assert.match(html, /
/) + assert.match(html, /
/) + assert.match(html, //) +}) + +test('the sanitiser keeps the tags and attributes upstream actually uses', () => { + // Enumerated from all 36 en-US locale files rather than guessed. `center` is the Range + // node's table cells (12 uses) and `target` is on every one of upstream's 7 outbound + // links; dropping a tag keeps its text, so both losses read as a styling glitch. + assert.match(sanitiseHelp('
scale
'), /
scale<\/center>/) + assert.match(sanitiseHelp('mode'), /width="80px"/) + assert.match(sanitiseHelp('
  1. one
'), /
    /) + + const link = sanitiseHelp('mustache') + assert.match(link, /target="_blank"/) + // `rel` has to be in the allowlist as well as produced by the transform: sanitize-html + // filters the transform's output afterwards, so an attribute added there and missing + // here is added and then dropped again, with nothing said. + assert.match(link, /rel="noreferrer noopener"/) +}) + +test('the sanitiser still drops what can execute', () => { + assert.doesNotMatch(sanitiseHelp('

    hi

    '), /onclick/) + assert.doesNotMatch(sanitiseHelp('

    hi

    '), /alert/) + assert.doesNotMatch(sanitiseHelp('x'), /javascript:/) + // Deliberately not allowed, even though upstream has one (malformed) use of it on the + // Range node's table: no text depends on it, and it is third-party CSS. + assert.doesNotMatch(sanitiseHelp(''), /style=/) +}) diff --git a/nuxt/lib/redirects-node-red.test.mjs b/nuxt/lib/redirects-node-red.test.mjs new file mode 100644 index 0000000000..3ab42580e5 --- /dev/null +++ b/nuxt/lib/redirects-node-red.test.mjs @@ -0,0 +1,112 @@ +// The /node-red/** -> /docs/** map is 126 hand-written lines whose correctness nothing +// else checks. Every entry is a promise that an indexed URL still lands somewhere, and +// every way of breaking one is silent: a 301 into a 404 is a normal-looking response, and +// the build has no idea a redirect target does not exist. +// +// The core-node half maps an old flat URL onto a page that now sits under its palette +// category, so it goes stale the moment a page moves between categories: the map keeps +// pointing at the old path and nothing fails. + +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { readFileSync } from 'node:fs' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' + +import { GUIDES_SOURCE, listGuideFiles } from './guides-sync.mjs' + +const repoRoot = join(dirname(fileURLToPath(import.meta.url)), '../..') +const read = rel => readFileSync(join(repoRoot, rel), 'utf8') + +const withSlashes = path => '/' + path.split('/').filter(Boolean).join('/') + '/' + +/** The `from: to` pairs out of nuxt/redirects-node-red.ts. */ +function nodeRedRedirects () { + return Object.fromEntries( + [...read('nuxt/redirects-node-red.ts').matchAll(/'(\/[^']+)':\s*'(\/[^']+)'/g)] + .map(([, from, to]) => [from, to]) + ) +} + +/** The `from` keys of the site-wide map, which the node-red targets must not land on. */ +function siteRedirectSources () { + return [...read('nuxt/redirects.ts').matchAll(/'(\/[^']+)':\s*\{\s*redirect:/g)].map(([, from]) => from) +} + +/** Every /docs/ URL the authored guides produce. */ +function docsUrlsThatWillExist () { + const urls = new Set() + + for (const relPath of listGuideFiles(join(repoRoot, GUIDES_SOURCE))) { + if (!relPath.endsWith('.md')) continue + urls.add(withSlashes('/docs/' + relPath.replace(/(README|index)?\.md$/, ''))) + } + + return urls +} + +/** slug -> category, read off the core-node pages themselves. */ +function coreNodeCategories () { + const prefix = 'node-red/core-nodes/' + const bySlug = new Map() + + for (const relPath of listGuideFiles(join(repoRoot, GUIDES_SOURCE))) { + if (!relPath.startsWith(prefix) || !relPath.endsWith('.md')) continue + const rest = relPath.slice(prefix.length) + const [category, file] = rest.split('/') + if (!file || file === 'index.md') continue + bySlug.set(file.replace(/\.md$/, ''), category) + } + + return bySlug +} + +test('every redirect lands on a page that will exist', () => { + const existing = docsUrlsThatWillExist() + // Pages from FlowFuse/flowfuse are not on disk at test time, so only this repo's own + // prefixes can be checked. Those are the ones this map actually moves. + const ours = /^\/docs\/(node-red|flowfuse-nodes|application-guide|node-red-guide)\// + + const dangling = Object.entries(nodeRedRedirects()) + .filter(([, to]) => ours.test(to) && !existing.has(to)) + .map(([from, to]) => `${from} -> ${to}`) + + assert.deepEqual(dangling, [], dangling.join('\n')) +}) + +test('no redirect target is itself a redirect source', () => { + const map = nodeRedRedirects() + const sources = new Set([...Object.keys(map), ...siteRedirectSources()]) + + const hops = Object.entries(map) + .filter(([, to]) => sources.has(to)) + .map(([from, to]) => `${from} -> ${to} -> (redirects again)`) + + assert.deepEqual(hops, [], hops.join('\n')) +}) + +test('the core-node entries point at where the pages actually are', () => { + // The one half of the map that is a function of something else: a node page's URL is + // its category directory plus its filename, so moving a page between categories + // strands the redirect on a path nothing serves. + const categories = coreNodeCategories() + const expected = { '/node-red/core-nodes/': '/docs/node-red/core-nodes/' } + + for (const [slug, category] of categories) { + expected[`/node-red/core-nodes/${slug}/`] = `/docs/node-red/core-nodes/${category}/${slug}/` + } + + // A bare category path was never a page under Eleventy, but it is one now, so it is + // mapped too - except where a node already owns that URL. /node-red/core-nodes/function/ + // was the Function NODE's own URL and keeps pointing at the node, one level down. + for (const category of new Set(categories.values())) { + if (categories.has(category)) continue + expected[`/node-red/core-nodes/${category}/`] = `/docs/node-red/core-nodes/${category}/` + } + + const actual = Object.fromEntries( + Object.entries(nodeRedRedirects()).filter(([from]) => from.startsWith('/node-red/core-nodes/')) + ) + + assert.deepEqual(actual, expected) +}) diff --git a/nuxt/modules/docs-source.ts b/nuxt/modules/docs-source.ts index c70e2b82df..6dc322d003 100644 --- a/nuxt/modules/docs-source.ts +++ b/nuxt/modules/docs-source.ts @@ -8,6 +8,7 @@ import { join, basename, dirname } from 'node:path' import { syncDocs } from '../lib/docs-sync.mjs' // @ts-ignore same import { syncGuides } from '../lib/guides-sync.mjs' +// @ts-ignore same const logger = useLogger('docs-source') @@ -33,8 +34,8 @@ export default defineNuxtModule({ const repoRoot = dirname(nuxtRoot) - // Order matters: syncDocs wipes content/docs before writing, so the guides - // authored in this repo have to be overlaid after it, not before. + // Order matters: syncDocs wipes content/docs before writing, so the guides overlaid + // from this repo have to land after it, not before. await syncDocs({ repoRoot, nuxtRoot, logger }) syncGuides({ repoRoot, nuxtRoot, logger }) @@ -43,6 +44,15 @@ export default defineNuxtModule({ // Collected after the overlay, so the guide pages get prerendered with the rest // of /docs and need no route list of their own in nuxt.config. const docsRoutes = collectRoutes(contentDocsDir, '/docs') + // A floor rather than a log line, following the same call in nuxt.config.ts for the + // integrations. Collecting nothing means the tree above did not land, and on the + // netlify preset unprerendered routes still SSR, so the only symptom would be a + // thinner static site and slow cold pages - nothing that looks like a failure. + if (docsRoutes.length === 0) { + throw new Error( + '[docs-source] collected 0 docs routes to prerender - refusing to build /docs with no pages' + ) + } nuxt.options.nitro.prerender ??= {} const existing = (nuxt.options.nitro.prerender.routes as string[] | undefined) ?? [] nuxt.options.nitro.prerender.routes = [...existing, ...docsRoutes] diff --git a/nuxt/pages/ai/index.vue b/nuxt/pages/ai/index.vue index ef64f8d9c2..d669c09e2f 100644 --- a/nuxt/pages/ai/index.vue +++ b/nuxt/pages/ai/index.vue @@ -98,8 +98,8 @@ const CAPABILITY_GROUPS = [ items: [ { name: 'Governed autonomous operations', diagram: 'governed-operations', externalNote: 'For external agents, approval cards do not apply. The access you granted is the control, enforced on every call.', description: 'Platform Automations let AI act on live systems, with every write behind an approval card, session-scoped and fully audited. A person approves, edits or rejects each proposed change before it reaches a machine. Nothing runs outside the permissions and RBAC that already govern your teams.' }, { name: 'Ask your plant anything', diagram: 'ask-your-plant', externalNote: 'For external agents, whether they can reach the MCP servers you build in your own flows depends on the agent.', description: 'In Insights mode, ask questions in natural language and get answers grounded in live machine state, alarms and logs. Operators and engineers can check what is happening on the floor without building a report or querying a database by hand. Table and MQTT-broker reading are coming soon.' }, - { name: 'Automated visual inspection', diagram: 'visual-inspection', description: `Run ONNX vision models inside flows next to the machine, with camera ingest over RTSP, for inference that works offline and keeps data on your network. Detection results flow into the same logic as any other signal, so you can trigger alerts or actions from what the model sees. Running at the edge means no round trip to the cloud and no image data leaving the plant.` }, - { name: 'Use any model within flows', diagram: 'any-model', description: `Certified LLM nodes bring OpenAI, Anthropic, Gemini or local models via Ollama into any flow with your own keys. Choose the provider that fits each task, or keep everything on local models when data cannot leave your network. Because you supply the keys, model access and spend stay under your control.` }, + { name: 'Automated visual inspection', diagram: 'visual-inspection', description: `Run ONNX vision models inside flows next to the machine, with camera ingest over RTSP, for inference that works offline and keeps data on your network. Detection results flow into the same logic as any other signal, so you can trigger alerts or actions from what the model sees. Running at the edge means no round trip to the cloud and no image data leaving the plant.` }, + { name: 'Use any model within flows', diagram: 'any-model', description: `Certified LLM nodes bring OpenAI, Anthropic, Gemini or local models via Ollama into any flow with your own keys. Choose the provider that fits each task, or keep everything on local models when data cannot leave your network. Because you supply the keys, model access and spend stay under your control.` }, ], }, { @@ -118,7 +118,7 @@ const CAPABILITY_GROUPS = [ eyebrow: 'Expose', subtitle: 'The other direction. Your flows become tools that an agent can call as part of its work.', items: [ - { name: 'AI that uses your tools', diagram: 'your-tools', externalNote: 'Anyone can build them. For external agents, whether they can call them depends on the agent.', description: `Build your own MCP servers and let Insights-mode agents call your tools and services as part of a workflow. Wrap an internal API or system as a tool once, then let agents use it wherever it fits. The agent stays inside the workflow you designed, calling only the tools you register.` }, + { name: 'AI that uses your tools', diagram: 'your-tools', externalNote: 'Anyone can build them. For external agents, whether they can call them depends on the agent.', description: `Build your own MCP servers and let Insights-mode agents call your tools and services as part of a workflow. Wrap an internal API or system as a tool once, then let agents use it wherever it fits. The agent stays inside the workflow you designed, calling only the tools you register.` }, ], }, ] diff --git a/nuxt/pages/docs/[...slug].vue b/nuxt/pages/docs/[...slug].vue index a7bb180beb..18f13a2747 100644 --- a/nuxt/pages/docs/[...slug].vue +++ b/nuxt/pages/docs/[...slug].vue @@ -1,5 +1,6 @@ \ No newline at end of file diff --git a/src/_includes/layouts/learning-resources-nav.njk b/src/_includes/layouts/learning-resources-nav.njk deleted file mode 100644 index a0ffac394d..0000000000 --- a/src/_includes/layouts/learning-resources-nav.njk +++ /dev/null @@ -1,77 +0,0 @@ - -
    - {% set navPages = collections.learningResources | eleventyNavigation %} - {% macro renderNavListItem(entry) -%} - {% set isChildUrl = false %} - {% for child in entry.children %} - {% if child.url == page.url %} - {% set isChildUrl = true %} - {% endif %} - {% endfor %} - -
  1. - {%- if entry.children.length -%} - -
      - {%- for child in entry.children %} - {{ renderNavListItem(child) }} - {%- endfor -%} -
    - {%- else -%} - {{ entry.title }} - {%- endif %} -
  2. - {%- endmacro %} -
      - {%- for entry in navPages %} - {{ renderNavListItem(entry) }} - {%- endfor -%} -
    -
    - - - \ No newline at end of file diff --git a/src/_includes/layouts/left-nav.njk b/src/_includes/layouts/left-nav.njk deleted file mode 100644 index 93361a999c..0000000000 --- a/src/_includes/layouts/left-nav.njk +++ /dev/null @@ -1,92 +0,0 @@ - - - -
    -
      - {% set path = (["node-red/", nav] | join) if nav == "core-nodes" else nav %} - -
    • - {{ nav }} -
    • - {% for group in collections - .nav[nav] - .groups %} - {% if group.children.length > 0 %} -
    • {{ group.name }}
    • - {% for entry in group.children %} -
    • - - {{ entry.name }} - - {% if entry.children %} - - {% endif %} -
    • - {% if entry.children %} -
    • -
        - {% for child in entry.children %} -
      • - - {{ child.name }} - - {% if child.children %} - - {% endif %} -
      • - {% if child.children %} -
      • - -
      • - {% endif %} - {% endfor %} -
      -
    • - {% endif %} - {% endfor %} - {% endif %} - {% endfor %} -
    -
    \ No newline at end of file diff --git a/src/blog/2023/06/community-news-06.md b/src/blog/2023/06/community-news-06.md index 0a3a1c47ed..a5bdaee0c3 100644 --- a/src/blog/2023/06/community-news-06.md +++ b/src/blog/2023/06/community-news-06.md @@ -37,7 +37,7 @@ On June 27, FlowFuse is doing a webinar with our friends at InfluxDB. A great op - [Bringing High Availability to Node-RED](/blog/2023/05/bringing-high-availability-to-node-red/) - FlowFuse CTO discusses the strategy for delivering high availability in the FlowFuse platform. - Two articles featuring how to connect Modbus data with Node-RED: - - [Using Node-RED to Visualize Industrial Production Data via Modbus](/node-red/protocol/modbus/) + - [Using Node-RED to Visualize Industrial Production Data via Modbus](/docs/node-red/protocol/modbus/) - [Best Practices Integrating a Modbus Device With Node-RED](/blog/2023/05/integrating modbus with node-red/) - [Node-RED Tips - Dashboard Edition](/blog/2023/06/3-quick-node-red-tips-7/) - A new set of Node-RED quick tips that are focused on using Node-RED Dashboard. diff --git a/src/blog/2023/06/node-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter.md b/src/blog/2023/06/node-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter.md index 9749dbe1f3..45ad0ca59f 100644 --- a/src/blog/2023/06/node-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter.md +++ b/src/blog/2023/06/node-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter.md @@ -197,7 +197,7 @@ Depending on how noisy the REAL data is, which is common with unfiltered 4-20mA !["Screenshot showing the Filter node Configuration"](./images/ethip-to-S7/e-to-p-23.png "Screenshot showing the Filter node Configuration") -In the example above, we arbitrarily applied a 3% [deadband](/node-red/core-nodes/filter/) +In the example above, we arbitrarily applied a 3% [deadband](/docs/node-red/core-nodes/function/filter/) to the `Robot_Position` value, which means that the value must change by greater than or equal to 3% compared to the last input value, or else the data will be discarded before being sent to the stacklight PLC. You can adjust the deadband to find the right balance for your particular application. diff --git a/src/blog/2023/07/community-news-07.md b/src/blog/2023/07/community-news-07.md index d50bff5d4a..df7c08971f 100644 --- a/src/blog/2023/07/community-news-07.md +++ b/src/blog/2023/07/community-news-07.md @@ -35,11 +35,11 @@ Our next webinar will be focused on the device management capabilities in the Fl - [Node-RED as a No-Code Ethernet/IP to S7 Protocol Converter](/blog/2023/06/node-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter/) - A guide to using Node-RED for converting ethernet IP data to Siemens S7. Also a [video version](https://youtu.be/dteXgcBXUnk) of the same content. -- [MQTT and its Role in IoT and Industrial IoT](/node-red/protocol/mqtt/) - A practical explainer on the role of MQTT in IoT use cases and how to connect with an MQTT broker in Node-RED. +- [MQTT and its Role in IoT and Industrial IoT](/docs/node-red/protocol/mqtt/) - A practical explainer on the role of MQTT in IoT use cases and how to connect with an MQTT broker in Node-RED. - Two new Node-RED Nodes Explained articles - - [Nodes explained: Split](/node-red/core-nodes/split/) - - [Nodes explained: Filter](/node-red/core-nodes/filter/) + - [Nodes explained: Split](/docs/node-red/core-nodes/sequence/split/) + - [Nodes explained: Filter](/docs/node-red/core-nodes/function/filter/) ## From the Community diff --git a/src/blog/2023/07/how-to-build-a-opc-client-dashboard-in-node-red.md b/src/blog/2023/07/how-to-build-a-opc-client-dashboard-in-node-red.md index f48fca5552..ed8f90cf8a 100644 --- a/src/blog/2023/07/how-to-build-a-opc-client-dashboard-in-node-red.md +++ b/src/blog/2023/07/how-to-build-a-opc-client-dashboard-in-node-red.md @@ -67,7 +67,7 @@ meta: answer: "The complete flow source code for all four objectives, browsing, reading, writing, and events, is included inline within the article, ready to import directly into Node-RED." tldr: "This part of the OPC UA series shows how to build an interactive Node-RED dashboard that connects to a third-party OPC UA server to browse the address space, read and write values, and display alarms and events. Complete flow source code is provided, making it a practical starting point for production OPC UA client applications." --- -This article is the third and final part of our OPC UA content series. In the [first article](/blog/2023/07/how-to-deploy-a-basic-opc-ua-server-in-node-red/), we cover some OPC UA fundamentals and walk through an example OPC UA Server flow. In the [second article](/node-red/protocol/opc-ua/), we built a SSL-secured OPC UA server using data from an Allen Bradley PLC as a source. +This article is the third and final part of our OPC UA content series. In the [first article](/blog/2023/07/how-to-deploy-a-basic-opc-ua-server-in-node-red/), we cover some OPC UA fundamentals and walk through an example OPC UA Server flow. In the [second article](/docs/node-red/protocol/opc-ua/), we built a SSL-secured OPC UA server using data from an Allen Bradley PLC as a source. In this article, we show how to build an OPC Client in Node-RED that communicates with a 3rd party OPC UA Server and utilizes an interactive dashboard. @@ -288,7 +288,7 @@ So, you've successfully built your Node-RED application, congratulations! But no Enter [FlowFuse](/), a cloud-based platform designed to add production-grade features to your Node-RED applications. With FlowFuse, you can seamlessly integrate advanced security measures, scale your application as needed, and collaborate effortlessly with your team. It simplifies management and deployment, turning your Node-RED project into a robust, scalable solution. -If you're interested in learning how to use Node-RED for professional use cases, check out our eBook: [Ultimate Beginner's Guide to Professionals](/ebooks/beginner-guide-to-a-professional-nodered/). For additional resources, visit our [Node-RED Learning Resources section](/node-red/core-nodes/), where you can explore integrations with different protocols, messaging services, databases, hardware, and much more. +If you're interested in learning how to use Node-RED for professional use cases, check out our eBook: [Ultimate Beginner's Guide to Professionals](/ebooks/beginner-guide-to-a-professional-nodered/). For additional resources, visit our [Node-RED Learning Resources section](/docs/node-red/core-nodes/), where you can explore integrations with different protocols, messaging services, databases, hardware, and much more. ## Conclusion diff --git a/src/blog/2023/08/community-news-08.md b/src/blog/2023/08/community-news-08.md index 726de76302..72866c6051 100644 --- a/src/blog/2023/08/community-news-08.md +++ b/src/blog/2023/08/community-news-08.md @@ -31,7 +31,7 @@ OPC-UA is a popular communication protocol used to communicate industrial data b - Our developer advocate, Richard Meyer, published a series of articles on OPC-UA: - Part 1: [How to Deploy a Basic OPC-UA Server in Node-RED](/blog/2023/07/how-to-deploy-a-basic-opc-ua-server-in-node-red/) - - Part 2: [How to Build a Secure OPC-UA Server for PLCs in Node-RED](/node-red/protocol/opc-ua/) + - Part 2: [How to Build a Secure OPC-UA Server for PLCs in Node-RED](/docs/node-red/protocol/opc-ua/) - Part 3: [How to Build an OPC UA Client Dashboard in Node-RED](/blog/2023/07/how-to-build-a-opc-client-dashboard-in-node-red/) diff --git a/src/blog/2023/10/community-news-10.md b/src/blog/2023/10/community-news-10.md index d1fad8e9eb..7485b3aeb7 100644 --- a/src/blog/2023/10/community-news-10.md +++ b/src/blog/2023/10/community-news-10.md @@ -49,7 +49,7 @@ The next generation of the Node-RED dashboard is starting to mature. Development * [Share & Preview Flows on flows.nodered.org](/blog/2023/09/flow-viewer/) - A new way to visualize flows in web pages. -* [Charting REST API Data in a Dashboard](/node-red/integration-technologies/rest/) - A short tutorial on how to gather data from a REST API for a dashboard. +* [Charting REST API Data in a Dashboard](/docs/node-red/integration-technologies/rest/) - A short tutorial on how to gather data from a REST API for a dashboard. * Modernize your legacy industrial data - Two part series on making sense of your industrial data. - [Part 1](/blog/2023/09/modernize-your-legacy-industrial-data/) diff --git a/src/blog/2023/10/custom-vuetify-components-dashboard.md b/src/blog/2023/10/custom-vuetify-components-dashboard.md index ba1039f0b0..0b014771d6 100644 --- a/src/blog/2023/10/custom-vuetify-components-dashboard.md +++ b/src/blog/2023/10/custom-vuetify-components-dashboard.md @@ -40,7 +40,7 @@ generated for you, in my case it's: ### Using the template node -Like the [template core node](/node-red/core-nodes/template), the dashboard package +Like the [template core node](/docs/node-red/core-nodes/function/template/), the dashboard package comes with [a template node of its own](https://dashboard.flowfuse.com/nodes/widgets/ui-template.html). If we take the HTML from the Vuetify docs pages and copy it in a template node the spinner will show up on the dashboard. @@ -52,7 +52,7 @@ the spinner will show up on the dashboard. While a custom element on a page is cool, and shows you can inject arbitrary HTML on a Dashboard, it's even better if we could make the element dynamic. So let's start with a first dynamic element. The quickest way to get that done is have -an [`Inject`](/node-red/core-nodes/inject) node output a random number every second. +an [`Inject`](/docs/node-red/core-nodes/common/inject/) node output a random number every second. So let's hook up an Inject, with `msg.payload`'s output being a JSONata expression `$round($random() * 100)` to generate a random number. And let's make sure it @@ -82,7 +82,7 @@ This is mostly a programmers job, but it's not hard, so let's get to it. A butto would be great to reset the timer, and for the sake of this post we can hardcode the deadline to 1m from the button press. -When dragging in a button node, connect it to a [change](/node-red/core-nodes/change) +When dragging in a button node, connect it to a [change](/docs/node-red/core-nodes/function/change/) node. In the change node set the flow variable `flow.deadline` to the timestamp. The Inject node from earlier needs updating to inject the `flow.deadline`. All that's left is calculating how many seconds passed, and normalizing 60 seconds to the diff --git a/src/blog/2023/11/community-news-11.md b/src/blog/2023/11/community-news-11.md index e06ced8fe3..830502cc54 100644 --- a/src/blog/2023/11/community-news-11.md +++ b/src/blog/2023/11/community-news-11.md @@ -59,7 +59,7 @@ Explore building manufacturing applications with FlowFuse Blueprints in our upco * [Meet FlowFuse at SPS Nuremberg](/blog/2023/11/meet-us-at-sps-nuremberg/) - Talk about Node-RED and how FlowFuse can help you operationalize your flows! -* [Install the FlowFuse Edge Agent on the Raspberry Pi 5](/node-red/hardware/raspberry-pi-5) - Managing your Raspberry Pi 5 with Node-RED through FlowFuse is easy to set up +* [Install the FlowFuse Edge Agent on the Raspberry Pi 5](/docs/node-red/hardware/raspberry-pi-5) - Managing your Raspberry Pi 5 with Node-RED through FlowFuse is easy to set up * [Innovate from within - Why manufacturing must embrace Citizen Developers](/blog/2023/10/citizen-development/) - Empower your Operational Technology teams as Citizen Developers diff --git a/src/blog/2024/01/how-to-deploy-node-red-with-flowfuse-to-balenacloud.md b/src/blog/2024/01/how-to-deploy-node-red-with-flowfuse-to-balenacloud.md index 2d43a66f50..0fa5b2a1da 100644 --- a/src/blog/2024/01/how-to-deploy-node-red-with-flowfuse-to-balenacloud.md +++ b/src/blog/2024/01/how-to-deploy-node-red-with-flowfuse-to-balenacloud.md @@ -59,7 +59,7 @@ Integrating InfluxDB enables effective data storage and management: ### Data Generation and Management For testing, we have created a flow to get you started. Follow this [link](https://flows.nodered.org/flow/66f37bb739b6cdb0c7ad3a4e2edd68ef) and import it. There are four sets of flows for you to begin with. The first is for data generation. The second is a manual data generation flow. The third is key as it initiates the creation of a database, in this case, **mydb**. The last flow is a simple query that pulls data from InfluxDB. -1. Import the flows into your FlowFuse instance of Node-RED and deploy. Follow these [instructions](/blog/2023/03/3-quick-node-red-tips-5/#2.-import-helpful-example-flows-provided-with-custom-nodes) for importing and exporting. +1. Import the flows into your FlowFuse instance of Node-RED and deploy. Follow these [instructions](/blog/2023/03/3-quick-node-red-tips-5/#_2-import-helpful-example-flows-provided-with-custom-nodes) for importing and exporting. 2. Return to Flowfuse, go to your instance, and create another [snapshot](/docs/user/snapshots/#create-a-snapshot). 3. Ensure that you **Set Target Snapshot**. diff --git a/src/blog/2024/01/import-a-file.md b/src/blog/2024/01/import-a-file.md index 6567e8a124..edba5b1a08 100644 --- a/src/blog/2024/01/import-a-file.md +++ b/src/blog/2024/01/import-a-file.md @@ -30,7 +30,7 @@ This simple flow allows the user to visualize data from a CSV in the [Node-RED D ### Instructions ### 1. Install Node-RED Dashboard 2.0. Follow these [instructions](/blog/2024/03/dashboard-getting-started/) to install. -2. Import Flow - to import the flow into your Node-RED instance follow these [instructions](/blog/2023/03/3-quick-node-red-tips-5/#1.-copy-and-share-your-flows-using-export-and-import). +2. Import Flow - to import the flow into your Node-RED instance follow these [instructions](/blog/2023/03/3-quick-node-red-tips-5/#_1-copy-and-share-your-flows-using-export-and-import). 3. Access Dashboard - To access the dashboard, navigate to the `https://.flowfuse.cloud/dashboard`. This dashboard is currently configured to take in CSV files and transform them into a single message that is sent to the table for visualization. Simultaneously the data from the import is stored locally in the flow context. From there, the button can be used to trigger the sending of the data from the flow context to the next destination. In this case, it is a simple debug node. diff --git a/src/blog/2024/01/send-a-file.md b/src/blog/2024/01/send-a-file.md index d1bb878604..56e17c5beb 100644 --- a/src/blog/2024/01/send-a-file.md +++ b/src/blog/2024/01/send-a-file.md @@ -132,7 +132,7 @@ Once we have a file ready to be sent, we now need to configure the receiving sid A link to the flow can be found [here](https://flows.nodered.org/flow/effb53752e5d6f767b3c7e5d41a4a6e8). -To import the flow, follow these [instructions](/blog/2023/03/3-quick-node-red-tips-5/#1.-copy-and-share-your-flows-using-export-and-import). +To import the flow, follow these [instructions](/blog/2023/03/3-quick-node-red-tips-5/#_1-copy-and-share-your-flows-using-export-and-import). A Simple HTTP In node can be used in the form of a Post, ensuring the configuration allows for a file. diff --git a/src/blog/2024/01/unified-namespace-what-broker.md b/src/blog/2024/01/unified-namespace-what-broker.md index 0970214521..141e3608ec 100644 --- a/src/blog/2024/01/unified-namespace-what-broker.md +++ b/src/blog/2024/01/unified-namespace-what-broker.md @@ -39,13 +39,13 @@ In addition to vendor lock-in, cloud message queue brokers also introduce relian ### Exotic options -RabbitMQ is a widely used open-source message broker that’s mostly used as an event message bus for web applications. It can also function as a hub in a unified namespace. The broker primarily supports the [AMQP](/node-red/protocol/amqp/) (Advanced Message Queuing Protocol), considered the industry standard for high-performance messaging systems. It also supports STOMP (Streaming Text Oriented Messaging Protocol) and MQTT (MQ Telemetry Transport), catering to various messaging needs. +RabbitMQ is a widely used open-source message broker that’s mostly used as an event message bus for web applications. It can also function as a hub in a unified namespace. The broker primarily supports the [AMQP](/docs/node-red/protocol/amqp/) (Advanced Message Queuing Protocol), considered the industry standard for high-performance messaging systems. It also supports STOMP (Streaming Text Oriented Messaging Protocol) and MQTT (MQ Telemetry Transport), catering to various messaging needs. NATS, short for Network Agnostic Messaging System, is another open-source message broker that is designed for simplicity and reliability. NATS implements its own protocols, making it harder to be interoperable with hardware and software previously purchased. NATS has requirements on message structure too, which creates another barrier to adoption for IoT use cases. ## How Node-RED Helps -Node-RED provides a powerful and flexible way to integrate with various brokers, supporting protocols such as [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), [Kafka](/blog/2024/03/using-kafka-with-node-red/), and [AMQP](/node-red/protocol/amqp/). It allows you to build and manage workflows that interact with your chosen broker, seamlessly connecting different data sources and systems. +Node-RED provides a powerful and flexible way to integrate with various brokers, supporting protocols such as [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), [Kafka](/blog/2024/03/using-kafka-with-node-red/), and [AMQP](/docs/node-red/protocol/amqp/). It allows you to build and manage workflows that interact with your chosen broker, seamlessly connecting different data sources and systems. However, using Node-RED alone in production environments requires additional considerations, such as server deployment, instance management, security implementation, and scalability. This is where FlowFuse enhances Node-RED's capabilities by adding production-ready features. FlowFuse simplifies managing and deploying Node-RED applications, providing essential functionalities like scalability, robust security, and efficient collaboration tools. diff --git a/src/blog/2024/05/exploring-node-red-dashboard-2-widgets.md b/src/blog/2024/05/exploring-node-red-dashboard-2-widgets.md index 47c859b511..ec7e69d7a2 100644 --- a/src/blog/2024/05/exploring-node-red-dashboard-2-widgets.md +++ b/src/blog/2024/05/exploring-node-red-dashboard-2-widgets.md @@ -148,7 +148,7 @@ _Screenshot displaying the change node retrieving expense data from global conte !["Screenshot displaying the change node converting amount to number"](./images/exploring-dashboard-2-widgets-chart-widget-convert-amount-to-number-change-node.png "Screenshot displaying the change node converting amount to number"){data-zoomable} _Screenshot displaying the change node converting amount to number_ -5. Drag a **Join** node onto the canvas, select mode as **reduced expression**, and set the **Reduce exp** to `$A + payload.amount`. Set Initial value to `0`, and **Fix-up exp** to `$A`. Give this **join** node the name "Calculate total". This function operates similarly to using the javascript reduce method on an array to calculate the sum of its values. `$A` stores the accumulated value, and with every incoming message payload, it adds the `payload.amount` value to it, for more details on this refer to the [core node docs on join node](/node-red/core-nodes/join/). +5. Drag a **Join** node onto the canvas, select mode as **reduced expression**, and set the **Reduce exp** to `$A + payload.amount`. Set Initial value to `0`, and **Fix-up exp** to `$A`. Give this **join** node the name "Calculate total". This function operates similarly to using the javascript reduce method on an array to calculate the sum of its values. `$A` stores the accumulated value, and with every incoming message payload, it adds the `payload.amount` value to it, for more details on this refer to the [core node docs on join node](/docs/node-red/core-nodes/sequence/join/). !["Screenshot displaying the join node calculating the total income and expense data"](./images/exploring-dashboard-2-widgets-chart-widget-calculate-total-join-node.png "Screenshot displaying the join node calculating the total income and expense data"){data-zoomable} _Screenshot displaying the join node calculating the total income and expense data_ diff --git a/src/blog/2024/05/understanding-node-flow-global-environment-variables-in-node-red.md b/src/blog/2024/05/understanding-node-flow-global-environment-variables-in-node-red.md index 381c12ac4c..39e726a23d 100644 --- a/src/blog/2024/05/understanding-node-flow-global-environment-variables-in-node-red.md +++ b/src/blog/2024/05/understanding-node-flow-global-environment-variables-in-node-red.md @@ -49,7 +49,7 @@ Variables in Node-RED serve as containers for storing and managing data througho Node-RED offers three primary variable categories: -**Message variables** travel with the message object as it flows through your nodes. The most common example is `msg.payload`, which carries the primary data between nodes. For a deeper dive into message handling, see the [Understanding Node-RED Messages](/node-red/getting-started/node-red-messages/) guide. +**Message variables** travel with the message object as it flows through your nodes. The most common example is `msg.payload`, which carries the primary data between nodes. For a deeper dive into message handling, see the [Understanding Node-RED Messages](/docs/node-red/getting-started/node-red-messages/) guide. **Context variables** store application state at different levels, node, flow, or global scope. They persist data that needs to be accessed across multiple message events, making them ideal for tracking counters, storing configuration, or maintaining state. diff --git a/src/blog/2024/06/how-to-use-mqtt-in-node-red.md b/src/blog/2024/06/how-to-use-mqtt-in-node-red.md index af62446b49..721b9f2784 100644 --- a/src/blog/2024/06/how-to-use-mqtt-in-node-red.md +++ b/src/blog/2024/06/how-to-use-mqtt-in-node-red.md @@ -77,7 +77,7 @@ Before you start, make sure you have: Node-RED ships with MQTT nodes already installed. Open your Node-RED editor and look in the palette, you'll find **mqtt-in** and **mqtt-out** under the network section. -> If you're using FlowFuse's managed MQTT broker, this is straightforward. Use the [ff-mqtt-in](/node-red/flowfuse/mqtt/mqtt-in/) and [ff-mqtt-out](/node-red/flowfuse/mqtt/mqtt-out/) nodes instead of the standard MQTT nodes. Simply drag one onto the canvas, and the connection to FlowFuse's broker will be configured automatically. +> If you're using FlowFuse's managed MQTT broker, this is straightforward. Use the [ff-mqtt-in](/docs/flowfuse-nodes/mqtt/mqtt-in/) and [ff-mqtt-out](/docs/flowfuse-nodes/mqtt/mqtt-out/) nodes instead of the standard MQTT nodes. Simply drag one onto the canvas, and the connection to FlowFuse's broker will be configured automatically. For any other broker, drag an **mqtt-in** or **mqtt-out** node onto your workspace. Double-click it to open the configuration panel. diff --git a/src/blog/2024/07/calling-python-script-from-node-red.md b/src/blog/2024/07/calling-python-script-from-node-red.md index c9759b6843..540bbb2b2e 100644 --- a/src/blog/2024/07/calling-python-script-from-node-red.md +++ b/src/blog/2024/07/calling-python-script-from-node-red.md @@ -103,7 +103,7 @@ Let's now see how to call a Python script from Node-RED. First, we'll create a b ``` :: -Now, let's execute this Python script from Node-RED. To do that, we will use Node-RED's [Exec](/node-red/core-nodes/exec/) node, which allows running commands on your system. +Now, let's execute this Python script from Node-RED. To do that, we will use Node-RED's [Exec](/docs/node-red/core-nodes/function/exec/) node, which allows running commands on your system. 1. Drag an Inject node onto the canvas. 2. Drag an Exec node onto the canvas and Configure the command to `python ./example.py -u`. The -u flag prevents potential output buffering issues when executing Python scripts via exec. @@ -125,7 +125,7 @@ Now, when you deploy this flow and click on the inject node to execute the file, Having explored how to run a Python script within Node-RED with the basic practical example, let's move to a real-world scenario. We'll demonstrate how to read sensor data using Python, despite Node-RED providing numerous community-built nodes for this purpose. This approach provides deeper insights into integrating external scripts, showcasing the flexibility of Node-RED for custom solutions. -Before proceeding, ensure that Node-RED is running on a device connected to a temperature sensor. For detailed instructions, refer to [Setting Up Node-RED on Different Hardware](/node-red/hardware/), In this case, we are running Node-RED on a Raspberry Pi 5 with a DHT11 sensor connected to it. +Before proceeding, ensure that Node-RED is running on a device connected to a temperature sensor. For detailed instructions, refer to [Setting Up Node-RED on Different Hardware](/docs/node-red/hardware/), In this case, we are running Node-RED on a Raspberry Pi 5 with a DHT11 sensor connected to it. 1. Drag an Inject node onto the canvas, and set repeat to 1 seconds of interval. 2. Drag an Exec node and set the path to `python .py`, replace the filename with the name of the file which reads the sensor data, and make sure the python file doesn't contain the loop. diff --git a/src/blog/2024/08/opc-ua-to-mqtt-with-node-red.md b/src/blog/2024/08/opc-ua-to-mqtt-with-node-red.md index a4e166badd..b8509e2063 100644 --- a/src/blog/2024/08/opc-ua-to-mqtt-with-node-red.md +++ b/src/blog/2024/08/opc-ua-to-mqtt-with-node-red.md @@ -190,13 +190,13 @@ _Video showing the successful bridging of OPC UA data to MQTT_ - [OPC UA Client and Server on FlowFuse](/integrations/opcua/) See FlowFuse's full OPC UA client and server capabilities, certified node, and security model in one place. -- [Using MQTT with Node-RED](/node-red/protocol/mqtt/) +- [Using MQTT with Node-RED](/docs/node-red/protocol/mqtt/) Learn how to integrate MQTT with Node-RED to enhance your IoT solutions with real-time data messaging. - [How to Build an OPC UA Client Dashboard in Node-RED](/blog/2023/07/how-to-build-a-opc-client-dashboard-in-node-red/) Follow a step-by-step guide to create a comprehensive OPC UA client dashboard in Node-RED for effective monitoring and control. -- [Building a Secure OPC UA Server in Node-RED](/node-red/protocol/opc-ua/) +- [Building a Secure OPC UA Server in Node-RED](/docs/node-red/protocol/opc-ua/) Explore best practices for configuring a secure OPC UA server in Node-RED to ensure safe and reliable data exchange. - [How to Deploy a Basic OPC UA Server in Node-RED](/blog/2023/07/how-to-deploy-a-basic-opc-ua-server-in-node-red/) diff --git a/src/blog/2024/10/exploring-flowfuse-project-nodes.md b/src/blog/2024/10/exploring-flowfuse-project-nodes.md index a8df1675f5..491151b560 100644 --- a/src/blog/2024/10/exploring-flowfuse-project-nodes.md +++ b/src/blog/2024/10/exploring-flowfuse-project-nodes.md @@ -137,7 +137,7 @@ This example showcases one of many powerful use cases for FlowFuse project nodes ## Using Project Call Nodes -**Project Call** nodes are ideal for triggering flows deployed on another Node-RED instance and retrieving the final result as a response. While they function similarly to [webhooks](/node-red/integration-technologies/webhook/), they utilize [MQTT](/node-red/protocol/mqtt/) as their underlying mechanism instead of HTTP. In this section, we will demonstrate the use of a **Project Call** node through an example of making an on-demand temperature request. +**Project Call** nodes are ideal for triggering flows deployed on another Node-RED instance and retrieving the final result as a response. While they function similarly to [webhooks](/docs/node-red/integration-technologies/webhook/), they utilize [MQTT](/docs/node-red/protocol/mqtt/) as their underlying mechanism instead of HTTP. In this section, we will demonstrate the use of a **Project Call** node through an example of making an on-demand temperature request. ### How Project Call Nodes Work @@ -175,7 +175,7 @@ Throughout this section, we will explore how to utilize the **Project Call** nod ``` :: -1. Copy/download the flow above and import/upload it into your Raspberry Pi Node-RED instance. Ensure you have correctly interfaced the DHT11 sensor with your Raspberry Pi. For more information, refer to our guide on [Setting Up Node-RED on Raspberry Pi 4](/node-red/hardware/raspberry-pi-4/), which explains how to install the device agent on the Raspberry Pi and read temperature data from the DHT11 sensor. +1. Copy/download the flow above and import/upload it into your Raspberry Pi Node-RED instance. Ensure you have correctly interfaced the DHT11 sensor with your Raspberry Pi. For more information, refer to our guide on [Setting Up Node-RED on Raspberry Pi 4](/docs/node-red/hardware/raspberry-pi-4/), which explains how to install the device agent on the Raspberry Pi and read temperature data from the DHT11 sensor. 2. Deploy the flow. Once you deploy the flow, you will see the temperature data displayed on the Debug panel if everything was done correctly. Now, let’s add the project nodes to trigger the temperature reading flow on demand and retrieve the data accordingly. diff --git a/src/blog/2024/10/quick-ways-to-write-functions-in-node-red.md b/src/blog/2024/10/quick-ways-to-write-functions-in-node-red.md index ac0b722c85..6fa7ad4e99 100644 --- a/src/blog/2024/10/quick-ways-to-write-functions-in-node-red.md +++ b/src/blog/2024/10/quick-ways-to-write-functions-in-node-red.md @@ -28,7 +28,7 @@ _Image showing the function node_ ## What Are Function Nodes and Common Challenges? -[Function Nodes](/node-red/core-nodes/function/) in Node-RED allow you to write custom JavaScript for processing messages. While they provide flexibility, many users find it challenging to turn complex business rules into code and manage variables. For more details on the benefits and drawbacks of using Function Nodes, refer to this [Article](/blog/2023/03/why-should-you-use-node-red-function-nodes/). +[Function Nodes](/docs/node-red/core-nodes/function/) in Node-RED allow you to write custom JavaScript for processing messages. While they provide flexibility, many users find it challenging to turn complex business rules into code and manage variables. For more details on the benefits and drawbacks of using Function Nodes, refer to this [Article](/blog/2023/03/why-should-you-use-node-red-function-nodes/). Before using Function Nodes, consider if existing low-code nodes can fulfill your needs. Using standard low-code nodes can simplify your approach and enhance collaboration and clarity. If you still feel the need to use Function Nodes, don’t worry, In the following section, we’ll explore straightforward strategies to make working with Function Nodes easier. diff --git a/src/blog/2024/11/building-uns-with-flowfuse.md b/src/blog/2024/11/building-uns-with-flowfuse.md index 1ca9810104..54d5278414 100644 --- a/src/blog/2024/11/building-uns-with-flowfuse.md +++ b/src/blog/2024/11/building-uns-with-flowfuse.md @@ -40,7 +40,7 @@ Fortunately, Node-RED provides support for a wide range of industrial protocols, By leveraging Node-RED, you can collect data from these legacy systems, process and transform the data using low-code workflows, and then seamlessly send it to the cloud via modern protocols such as MQTT, Kafka, AMQP, and more. -In our example, Node-RED can directly collect metrics from sensor using [I2C](https://flows.nodered.org/node/node-red-contrib-i2c) on the Raspberry Pi. This approach simplifies the process by eliminating the need for additional communication layers. To run Node-RED on the Raspberry Pi, we use [FlowFuse Device Agent](/platform/device-agent/), This agent enables you to remotely monitor, manage, and build Node-RED flows securely through the FlowFuse platform remotely. [See here](/node-red/hardware/) for more details on how to set up and run FlowFuse Device Agent on different devices. +In our example, Node-RED can directly collect metrics from sensor using [I2C](https://flows.nodered.org/node/node-red-contrib-i2c) on the Raspberry Pi. This approach simplifies the process by eliminating the need for additional communication layers. To run Node-RED on the Raspberry Pi, we use [FlowFuse Device Agent](/platform/device-agent/), This agent enables you to remotely monitor, manage, and build Node-RED flows securely through the FlowFuse platform remotely. [See here](/docs/node-red/hardware/) for more details on how to set up and run FlowFuse Device Agent on different devices. ### Step 2: Transform and Process the Collected metrics @@ -60,7 +60,7 @@ Raw data from the ADXL345 sensor might look like this: [26,0,244,255,37,255] ``` -Using a [Function node](/node-red/core-nodes/function/) in Node-RED, we can convert this into a human-readable format. +Using a [Function node](/docs/node-red/core-nodes/function/) in Node-RED, we can convert this into a human-readable format. ![Function node: Transforming Raw Data into Readable Format](./images/function-node.png){data-zoomable} _Function node: Transforming Raw Data into Readable Format_ @@ -80,7 +80,7 @@ While this is more readable, it can still be challenging to monitor changes in v ![Change node: Calculating Magnitude](./images/change-node-calculating-magnitude.png){data-zoomable} _Change node: Calculating Magnitude_ -After calculating the Magnitude using a [Change node](/node-red/core-nodes/change/), the data might look like this: +After calculating the Magnitude using a [Change node](/docs/node-red/core-nodes/function/change/), the data might look like this: ```json { @@ -160,7 +160,7 @@ While Sparkplug B provides a standardized topic model, its hierarchy may not alw ### Step 5: Sending Collected metrics to UNS -With your topic naming convention chosen, it’s time to send the data to the UNS. In Node-RED, we will use the [MQTT Out](/node-red/core-nodes/mqtt-in/) node to send the transformed data to the broker. +With your topic naming convention chosen, it’s time to send the data to the UNS. In Node-RED, we will use the [MQTT Out](/docs/node-red/core-nodes/network/mqtt-in/) node to send the transformed data to the broker. 1. Drag an MQTT out node into your flow. 2. Configure the node to connect to the FlowFuse MQTT Broker using the client credentials generated earlier. diff --git a/src/blog/2024/11/why-pub-sub-in-uns.md b/src/blog/2024/11/why-pub-sub-in-uns.md index 817e63e04d..c189f8f6f6 100644 --- a/src/blog/2024/11/why-pub-sub-in-uns.md +++ b/src/blog/2024/11/why-pub-sub-in-uns.md @@ -32,7 +32,7 @@ Before discussing why Publish/Subscribe (Pub/Sub) is essential for a UNS, let's ![Publish Subscribe Model](./images/pub-sub.png){data-zoomable} _Publish Subscribe Model_ -The Pub/Sub model is a way for systems to communicate where one component, called the publisher, sends messages to a central system ( Broker such as [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), [RabitMQ](/node-red/protocol/amqp/), and [Kafka](/blog/2024/03/using-kafka-with-node-red/) ), and other components, called subscribers, receive those messages. The publisher doesn’t need to know who the subscribers are, and the subscribers don’t know who the publishers are. The central system, or broker, ensures the right messages go to the right subscribers based on their interests. +The Pub/Sub model is a way for systems to communicate where one component, called the publisher, sends messages to a central system ( Broker such as [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), [RabitMQ](/docs/node-red/protocol/amqp/), and [Kafka](/blog/2024/03/using-kafka-with-node-red/) ), and other components, called subscribers, receive those messages. The publisher doesn’t need to know who the subscribers are, and the subscribers don’t know who the publishers are. The central system, or broker, ensures the right messages go to the right subscribers based on their interests. Additionally, it’s important to note that the roles of publisher and subscriber are not mutually exclusive. A component can act as a publisher in one context, sending messages to the broker, and as a subscriber in another, receiving messages from the broker. diff --git a/src/blog/2024/12/publishing-modbus-data-to-uns.md b/src/blog/2024/12/publishing-modbus-data-to-uns.md index a471bba9d5..c794c8e14a 100644 --- a/src/blog/2024/12/publishing-modbus-data-to-uns.md +++ b/src/blog/2024/12/publishing-modbus-data-to-uns.md @@ -130,7 +130,7 @@ After configuring the connection, it's time to test the data collection. If no data appears, check the connection settings (IP address, Unit ID, port, etc.) and ensure your Modbus device is correctly configured and accessible. If you use a simulator like ModSim, ensure it’s running and properly configured to send data. -For more information on using Modbus with Node-RED, please read our tutorial on [Using Modbus with Node-RED](/node-red/protocol/modbus/). +For more information on using Modbus with Node-RED, please read our tutorial on [Using Modbus with Node-RED](/docs/node-red/protocol/modbus/). If you prefer a video explanation, [Kurt Braun](https://www.linkedin.com/in/wago-kurt-braun/) from WAGO demonstrates how to collect Modbus data using Node-RED in FlowFuse: @@ -161,7 +161,7 @@ We must convert these raw register values into human-readable formats for cloud To determine how to process raw Modbus data, such as dividing by a specific value, concatenating, or applying other transformation formulas, refer to the manual of the sensor you use for specific instructions. -In Node-RED, you can use various nodes for transformation. You can choose the [Function node](/node-red/core-nodes/function/) for advanced processing, the [Change node](/node-red/core-nodes/change/) for simpler operations, or the [Template node](/node-red/core-nodes/template/) for defining schemas. For more complex data parsing scenarios, such as handling multiple data types (floats, 32-bit integers, strings), dealing with big-endian/little-endian conversions, or performing byte swapping, consider using the `node-red-contrib-buffer-parser` node. This specialized node simplifies parsing Modbus buffers into various data formats without writing custom code. Learn more in our guide on [using Buffer Parser for industrial data](/blog/2025/12/node-red-buffer-parser-industrial-data/). In this article, I will demonstrate a low-code approach using the Change node to process the data cleanly. +In Node-RED, you can use various nodes for transformation. You can choose the [Function node](/docs/node-red/core-nodes/function/) for advanced processing, the [Change node](/docs/node-red/core-nodes/function/change/) for simpler operations, or the [Template node](/docs/node-red/core-nodes/function/template/) for defining schemas. For more complex data parsing scenarios, such as handling multiple data types (floats, 32-bit integers, strings), dealing with big-endian/little-endian conversions, or performing byte swapping, consider using the `node-red-contrib-buffer-parser` node. This specialized node simplifies parsing Modbus buffers into various data formats without writing custom code. Learn more in our guide on [using Buffer Parser for industrial data](/blog/2025/12/node-red-buffer-parser-industrial-data/). In this article, I will demonstrate a low-code approach using the Change node to process the data cleanly. Additionally, for better organization and accessibility, I will send each metric separately and include additional metadata such as the `timestamp` and `unit`. @@ -288,7 +288,7 @@ _MQTT Out node configuration for temperature data_ **Using FlowFuse MQTT Nodes** -If you're using the FlowFuse MQTT broker, FlowFuse provides specialized MQTT nodes that simplify configuration. These nodes automatically configure the broker connection details when you drop them onto the canvas, no manual setup of server address, Client ID, username, or password required. You also won't need to manually create clients in the broker; they're automatically created when you use these nodes. This streamlines the development process and reduces configuration errors. Learn more about [FlowFuse MQTT nodes](/node-red/flowfuse/mqtt/). +If you're using the FlowFuse MQTT broker, FlowFuse provides specialized MQTT nodes that simplify configuration. These nodes automatically configure the broker connection details when you drop them onto the canvas, no manual setup of server address, Client ID, username, or password required. You also won't need to manually create clients in the broker; they're automatically created when you use these nodes. This streamlines the development process and reduces configuration errors. Learn more about [FlowFuse MQTT nodes](/docs/flowfuse-nodes/mqtt/). **Step 3.3: Configure MQTT Nodes for Remaining Metrics** diff --git a/src/blog/2025/01/mqtt-frontrunner-for-uns.md b/src/blog/2025/01/mqtt-frontrunner-for-uns.md index 538c790ca0..32755595e4 100644 --- a/src/blog/2025/01/mqtt-frontrunner-for-uns.md +++ b/src/blog/2025/01/mqtt-frontrunner-for-uns.md @@ -21,7 +21,7 @@ In this first part of our series, we’ll explain why MQTT is the standout choic The [Unified Namespace (UNS)](/use-cases/uns/) is a data architecture (not just a tool or new technology) that centralizes and organizes data from various sources into a single, unified structure. It eliminates data silos by providing a standardized way to represent, access, and share information across different devices, systems, and services. For more information on what is UNS, read our article: [Introduction to the Unified Namespace](/blog/2023/12/introduction-to-unified-namespace/). -When choosing a broker for your UNS, it's crucial to consider how well the selected broker fits the specific requirements of your IIoT environment, including the types of devices and systems involved, as well as factors like scalability, reliability, and ease of integration. Several options are available alongside MQTT, including [AMQP](/node-red/protocol/amqp/), [Kafka](/blog/2024/03/using-kafka-with-node-red/), and cloud message brokers like AWS Kinesis and GCP Pub/Sub. While these alternatives offer unique features, MQTT stands out, and we’ll explain why later in this article. But If you’re interested in a brief overview of why these alternatives are not the best fit for UNS, check out our article: [Unified Namespace: What Broker to Use?](/blog/2024/01/unified-namespace-what-broker/) +When choosing a broker for your UNS, it's crucial to consider how well the selected broker fits the specific requirements of your IIoT environment, including the types of devices and systems involved, as well as factors like scalability, reliability, and ease of integration. Several options are available alongside MQTT, including [AMQP](/docs/node-red/protocol/amqp/), [Kafka](/blog/2024/03/using-kafka-with-node-red/), and cloud message brokers like AWS Kinesis and GCP Pub/Sub. While these alternatives offer unique features, MQTT stands out, and we’ll explain why later in this article. But If you’re interested in a brief overview of why these alternatives are not the best fit for UNS, check out our article: [Unified Namespace: What Broker to Use?](/blog/2024/01/unified-namespace-what-broker/) There is also an ongoing debate regarding the use of OPC-UA and other protocols for implementing UNS. While I won’t dive into this in detail here as i havent explored it much yet, I encourage you to start with the approach we call reverse engineering. First, understand why MQTT is the preferred choice for implementing UNS, and then explore how its features align with UNS needs. From there, you can evaluate whether other protocols offer similar capabilities. By following this process, you will find the right answer of your questions. @@ -32,7 +32,7 @@ There is also an ongoing debate regarding the use of OPC-UA and other protocols Before diving into its specific advantages for UNS, let’s take a brief look at the history of **MQTT** and how it became the backbone of modern IoT communication. -[MQTT](/node-red/protocol/mqtt/) was developed in the late 1990s by Andy Stanford-Clark at IBM and Arlen Nipper at Eurotech to address communication challenges in low-bandwidth, unreliable networks. This early focus on lightweight messaging paved the way for MQTT to become a pioneering solution for the rapidly expanding IoT space. +[MQTT](/docs/node-red/protocol/mqtt/) was developed in the late 1990s by Andy Stanford-Clark at IBM and Arlen Nipper at Eurotech to address communication challenges in low-bandwidth, unreliable networks. This early focus on lightweight messaging paved the way for MQTT to become a pioneering solution for the rapidly expanding IoT space. Since then, MQTT has evolved significantly. The protocol has gone through several iterations, from MQTT 3.1.1 to the more feature-rich MQTT 5.0, each version enhancing the protocol's capabilities to meet the demands of an increasingly connected world. Today, more than 25 years later, MQTT remains the **de facto protocol** for IoT applications, and its simplicity, scalability, and reliability continue to make it an ideal choice for industrial systems. diff --git a/src/blog/2025/04/build-manufacturing-oee-dashboard.md b/src/blog/2025/04/build-manufacturing-oee-dashboard.md index 853b068407..f071104869 100644 --- a/src/blog/2025/04/build-manufacturing-oee-dashboard.md +++ b/src/blog/2025/04/build-manufacturing-oee-dashboard.md @@ -79,7 +79,7 @@ Before you begin building the OEE Dashboard with FlowFuse, make sure you have th - **Running FlowFuse Instance:** Make sure you have a FlowFuse instance set up and running. If you don't have an account, check out our [free trial](https://app.flowfuse.com/account/create) and learn how to create an instance in FlowFuse. - **FlowFuse Dashboard:** Ensure you have [FlowFuse Dashboard](https://flows.nodered.org/node/@flowfuse/node-red-dashboard) (also known as Node-RED Dashboard 2.0 in the community) installed and properly configured on your instance. -- **SQLite Contrib Node:** Ensure you have [node-red-contrib-sqlite](https://flows.nodered.org/node/node-red-node-sqlite) installed. We use SQLite here because it needs no setup, but the flow works against any database - [FlowFuse Tables](/docs/user/ff-tables/) gives you managed PostgreSQL if you want something production-grade from the start, and our [Database](/node-red/database/) guides cover MongoDB, InfluxDB, TimescaleDB, and DynamoDB. +- **SQLite Contrib Node:** Ensure you have [node-red-contrib-sqlite](https://flows.nodered.org/node/node-red-node-sqlite) installed. We use SQLite here because it needs no setup, but the flow works against any database - [FlowFuse Tables](/docs/user/ff-tables/) gives you managed PostgreSQL if you want something production-grade from the start, and our [Database](/docs/node-red/database/) guides cover MongoDB, InfluxDB, TimescaleDB, and DynamoDB. ### Preparing Simulated Data diff --git a/src/blog/2025/04/design-and-scale-oee-dashboard.md b/src/blog/2025/04/design-and-scale-oee-dashboard.md index 25311c0600..c87815ff59 100644 --- a/src/blog/2025/04/design-and-scale-oee-dashboard.md +++ b/src/blog/2025/04/design-and-scale-oee-dashboard.md @@ -271,7 +271,7 @@ To make the dashboard truly useful in a live setting, you must understand how to 2. Replace the existing `sqlite` nodes in your flow with the nodes for the database you are using. 3. If using SQL based database, queries may remain unchanged. For NoSQL or time-series DBs, rewrite the queries as needed. -For help, refer to our [Database](/node-red/database/) section, which includes guides for MongoDB, PostgreSQL, InfluxDB, TimescaleDB, and DynamoDB. If you do not already have a database to point at, [FlowFuse Tables](/docs/user/ff-tables/) gives you a managed PostgreSQL instance inside the platform - we walk through that pattern end to end in [Building a historical data dashboard with FlowFuse Tables](/blog/2025/08/time-series-dashboard-flowfuse-postgresql/). +For help, refer to our [Database](/docs/node-red/database/) section, which includes guides for MongoDB, PostgreSQL, InfluxDB, TimescaleDB, and DynamoDB. If you do not already have a database to point at, [FlowFuse Tables](/docs/user/ff-tables/) gives you a managed PostgreSQL instance inside the platform - we walk through that pattern end to end in [Building a historical data dashboard with FlowFuse Tables](/blog/2025/08/time-series-dashboard-flowfuse-postgresql/). When connecting to your real data source, you may notice that the field names used in your database differ from those used in the our oee dashboard sqlite node queries. While this seems like a lot of manual work, the dashboard is designed with flexibility in mind. You only need to make two changes to adapt the queries to your schema. diff --git a/src/blog/2025/06/data-acquisition-for-mes.md b/src/blog/2025/06/data-acquisition-for-mes.md index dba90e0af7..07b70b7c0e 100644 --- a/src/blog/2025/06/data-acquisition-for-mes.md +++ b/src/blog/2025/06/data-acquisition-for-mes.md @@ -64,14 +64,14 @@ Following are some of the most commonly used protocol nodes: - **Modbus:** - **OPC UA:** - **OPC DA:** -- **MQTT:** [/node-red/core-nodes/mqtt-in/](/node-red/core-nodes/mqtt-in/) +- **MQTT:** [/docs/node-red/core-nodes/network/mqtt-in/](/docs/node-red/core-nodes/network/mqtt-in/) - **Ethernet/IP:** - **Siemens S7:** - **MITSUBISHI MC:** - **OMRON FINS:** -- **HTTP:** [/node-red/core-nodes/http-in/](/node-red/core-nodes/http-in/) +- **HTTP:** [/docs/node-red/core-nodes/network/http-in/](/docs/node-red/core-nodes/network/http-in/) - **LwM2M:** -- **AMQP:** [/node-red/protocol/amqp/](/node-red/protocol/amqp/) +- **AMQP:** [/docs/node-red/protocol/amqp/](/docs/node-red/protocol/amqp/) - **Serialport:** - **GPIO:** - **Lorawan:** diff --git a/src/blog/2025/06/structuring-storing-data-mes-integration.md b/src/blog/2025/06/structuring-storing-data-mes-integration.md index 64a97c6431..32a817a2dc 100644 --- a/src/blog/2025/06/structuring-storing-data-mes-integration.md +++ b/src/blog/2025/06/structuring-storing-data-mes-integration.md @@ -162,7 +162,7 @@ It has tons of nodes for almost every database and cloud storage system you'll f This includes direct **connections** to normal databases like [MySQL](https://flows.nodered.org/node/node-red-node-mysql) and [PostgreSQL](https://flows.nodered.org/node/node-red-contrib-postgresql) for your organized production data. -It also has special nodes for time-series databases like [InfluxDB](https://flows.nodered.org/node/node-red-contrib-influxdb) and [TimescaleDB](/node-red/database/timescaledb/) to handle fast sensor and machine data. +It also has special nodes for time-series databases like [InfluxDB](https://flows.nodered.org/node/node-red-contrib-influxdb) and [TimescaleDB](/docs/node-red/database/timescaledb/) to handle fast sensor and machine data. Plus, FlowFuse **connects** to big Data Lakes and Cloud Storage services like [Amazon S3](https://flows.nodered.org/node/node-red-node-aws), [Google Cloud Storage](https://flows.nodered.org/node/node-red-contrib-google-cloud), and [Microsoft Azure](https://flows.nodered.org/node/node-red-contrib-azure-storage). diff --git a/src/blog/2025/06/what-is-mes.md b/src/blog/2025/06/what-is-mes.md index a84c785d80..7230e10d94 100644 --- a/src/blog/2025/06/what-is-mes.md +++ b/src/blog/2025/06/what-is-mes.md @@ -50,7 +50,7 @@ A Manufacturing Execution System (MES) connects live shop-floor activity with th An MES pulls live data from machines, sensors, and operators, then uses it to guide and confirm each step of production. As a work order runs, it can track which machine is handling it, how much material is used, and whether output is meeting specification in real time. -The hard part is rarely the concept. It is [getting data out of equipment that was never built to share it](/blog/2025/06/data-acquisition-for-mes/). A decade-old PLC may expose raw, uncontextualized tags over a vendor-specific protocol, while the SCADA system reading that data was designed to present it to an operator, not publish it upward. [Modbus](/node-red/protocol/modbus/), EtherNet/IP, Profinet, [S7comm](/blog/2025/01/integrating-siemens-s7-plcs-with-node-red-guide/), and [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/) can coexist on the same floor, and none inherently carry the order context an ERP system needs. +The hard part is rarely the concept. It is [getting data out of equipment that was never built to share it](/blog/2025/06/data-acquisition-for-mes/). A decade-old PLC may expose raw, uncontextualized tags over a vendor-specific protocol, while the SCADA system reading that data was designed to present it to an operator, not publish it upward. [Modbus](/docs/node-red/protocol/modbus/), EtherNet/IP, Profinet, [S7comm](/blog/2025/01/integrating-siemens-s7-plcs-with-node-red-guide/), and [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/) can coexist on the same floor, and none inherently carry the order context an ERP system needs. In a brownfield environment spanning several sites, that becomes a standing integration problem rather than a one-time project. [IT/OT connectivity](/use-cases/it-ot-middleware/) moves production events and quality checks between [PLCs](/landing/plc/), [SCADA](/use-cases/scada/), and [enterprise systems](/landing/enterprise-integration/) without relying on brittle point-to-point integrations. diff --git a/src/blog/2025/07/quality-control-automation-spc-charts.md b/src/blog/2025/07/quality-control-automation-spc-charts.md index a456af3d9b..10cc4b542b 100644 --- a/src/blog/2025/07/quality-control-automation-spc-charts.md +++ b/src/blog/2025/07/quality-control-automation-spc-charts.md @@ -314,7 +314,7 @@ Time to connect your actual machines. The approach depends on what equipment you For modern PLCs - anything from the last decade like Siemens S7-1200/1500, Allen-Bradley ControlLogix, or Omron NX - you'll use OPC UA. It's already built into these PLCs. Enable it in the configuration, install `node-red-contrib-opcua` from the FlowFuse palette, and point it at your PLC. The endpoint looks like `opc.tcp://192.168.1.100:4840`. Browse for your measurement tags and connect them to your SPC flow. [Full OPC UA guide here](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/). -Older equipment speaks Modbus TCP. Check your manual's appendix for the register map. Install `node-red-contrib-modbus`, configure it with your device's IP address and the register holding your measurement (like 40001 for holding registers). Almost every industrial device from the last 30 years supports this. [Modbus tutorial here](/node-red/protocol/modbus/). +Older equipment speaks Modbus TCP. Check your manual's appendix for the register map. Install `node-red-contrib-modbus`, configure it with your device's IP address and the register holding your measurement (like 40001 for holding registers). Almost every industrial device from the last 30 years supports this. [Modbus tutorial here](/docs/node-red/protocol/modbus/). For everything else, get creative. Old gauges with RS-232 ports work fine with a USB adapter and the serial node - [see our serial port guide](/blog/2025/07/connect-legacy-equipment-serial-flowfuse/). Machines that dump CSV files can be monitored with the watch node. Manual measurements need just a simple dashboard form, one input field, one submit button. Don't overcomplicate it. diff --git a/src/blog/2025/09/creating-pareto-chart.md b/src/blog/2025/09/creating-pareto-chart.md index df3aaf8a15..62ec4bd46c 100644 --- a/src/blog/2025/09/creating-pareto-chart.md +++ b/src/blog/2025/09/creating-pareto-chart.md @@ -27,7 +27,7 @@ To create a Pareto Chart for manufacturing data, you'll need access to industria ### Step 1: Connect to Your Data Source -The first step to create a Pareto Chart is accessing the data you want to analyze. In industrial environments, machine or process data is commonly collected via [industrial protocols](/node-red/protocol/) such as [OPC-UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/), [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), or direct [database](/node-red/database/) queries. Modern industrial platforms support nearly all industrial protocols and databases, making it easy to connect to your existing systems. +The first step to create a Pareto Chart is accessing the data you want to analyze. In industrial environments, machine or process data is commonly collected via [industrial protocols](/docs/node-red/protocol/) such as [OPC-UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/), [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), or direct [database](/docs/node-red/database/) queries. Modern industrial platforms support nearly all industrial protocols and databases, making it easy to connect to your existing systems. To connect your data: diff --git a/src/blog/2025/09/it-vs-ot-difference-between-information-technology-and-operational-technology.md b/src/blog/2025/09/it-vs-ot-difference-between-information-technology-and-operational-technology.md index 313e745081..d7f5f49d64 100644 --- a/src/blog/2025/09/it-vs-ot-difference-between-information-technology-and-operational-technology.md +++ b/src/blog/2025/09/it-vs-ot-difference-between-information-technology-and-operational-technology.md @@ -64,11 +64,11 @@ Connecting the two (IT/OT convergence) is central to Industry 4.0, but it only w OT is the umbrella term for the systems that directly run production: [Programmable Logic Controllers (PLCs)](/blog/2025/10/plc-to-mqtt-using-flowfuse/) that execute real-time control logic, [SCADA](/use-cases/scada/) systems that provide centralized monitoring across distributed sites, Distributed Control Systems (DCS) for complex continuous processes, and [Human-Machine Interfaces (HMI)](/blog/2025/11/building-hmi-for-equipment-control/) that give operators visualization and control. Safety Instrumented Systems, Building Management Systems, and [Manufacturing Execution Systems (MES)](/use-cases/mes/) round out the OT landscape. Collectively, these are known as Industrial Control Systems (ICS). -OT prioritizes safety, availability, and reliability above everything else. These systems were historically air-gapped, receive updates rarely (often only during planned shutdowns), and communicate over industrial protocols like [Modbus](/node-red/protocol/modbus/), Profibus, [OPC-UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/), and [EtherNet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/) rather than standard internet protocols. Equipment stays in service for 15-25+ years, runs on millisecond timing, and has essentially zero tolerance for downtime: every stopped minute costs money. +OT prioritizes safety, availability, and reliability above everything else. These systems were historically air-gapped, receive updates rarely (often only during planned shutdowns), and communicate over industrial protocols like [Modbus](/docs/node-red/protocol/modbus/), Profibus, [OPC-UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/), and [EtherNet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/) rather than standard internet protocols. Equipment stays in service for 15-25+ years, runs on millisecond timing, and has essentially zero tolerance for downtime: every stopped minute costs money. ## What is IT (Information Technology)? -IT covers the systems, software, and infrastructure that manage business data and enable enterprise operations: [email](/node-red/notification/email/) and [databases](/node-red/database/), ERP and CRM platforms, business intelligence, and increasingly cloud and SaaS applications. +IT covers the systems, software, and infrastructure that manage business data and enable enterprise operations: [email](/docs/node-red/notification/email/) and [databases](/docs/node-red/database/), ERP and CRM platforms, business intelligence, and increasingly cloud and SaaS applications. IT prioritizes data confidentiality and integrity, stays connected to the internet by default, and receives patches on monthly or quarterly cycles over standardized protocols like TCP/IP, HTTP, and HTTPS. Equipment typically cycles out every 3-5 years, and businesses can usually tolerate minutes-to-hours of downtime for maintenance. diff --git a/src/blog/2025/09/what-is-takt-time.md b/src/blog/2025/09/what-is-takt-time.md index 5c54b4f9b2..ed159a0279 100644 --- a/src/blog/2025/09/what-is-takt-time.md +++ b/src/blog/2025/09/what-is-takt-time.md @@ -140,11 +140,11 @@ In an ideal lean environment: **Cycle Time should sit at roughly 90–95% of Tak ## Implementing Takt Time Monitoring with FlowFuse -Understanding takt time in theory is one thing; putting it into practice requires the right tools. [FlowFuse](/) connects to your existing systems, whether [PLCs](/blog/2025/10/plc-to-mqtt-using-flowfuse/), [databases](/node-red/database/), or ERP software, to calculate takt time automatically in real time instead of on static spreadsheets. Before starting, [create a FlowFuse account](https://app.flowfuse.com/account/create) if you don't already have one. +Understanding takt time in theory is one thing; putting it into practice requires the right tools. [FlowFuse](/) connects to your existing systems, whether [PLCs](/blog/2025/10/plc-to-mqtt-using-flowfuse/), [databases](/docs/node-red/database/), or ERP software, to calculate takt time automatically in real time instead of on static spreadsheets. Before starting, [create a FlowFuse account](https://app.flowfuse.com/account/create) if you don't already have one. ### Step 1: Connect to Your Data Sources -FlowFuse supports connections to industrial systems through its library of [protocol](/node-red/protocol/) and [database](/node-red/database/) nodes, pulling customer orders from your [ERP system](/blog/2025/06/connect-shop-floor-to-odoo-erp-flowfuse/), production schedules from MES, and real-time counts from PLCs. +FlowFuse supports connections to industrial systems through its library of [protocol](/docs/node-red/protocol/) and [database](/docs/node-red/database/) nodes, pulling customer orders from your [ERP system](/blog/2025/06/connect-shop-floor-to-odoo-erp-flowfuse/), production schedules from MES, and real-time counts from PLCs. For this demo, simulate customer orders with an Inject node: diff --git a/src/blog/2025/10/building-mcp-server-using-flowfuse.md b/src/blog/2025/10/building-mcp-server-using-flowfuse.md index dc294b3e27..b8aef0c872 100644 --- a/src/blog/2025/10/building-mcp-server-using-flowfuse.md +++ b/src/blog/2025/10/building-mcp-server-using-flowfuse.md @@ -41,7 +41,7 @@ Before you begin, ensure you have the following: * **A running FlowFuse Enterprise instance.** If you do not have one, [contact us](/contact-us/) to discuss Enterprise options and get started. -* **Ensure the `@flowfuse-nodes/nr-mcp-server-nodes` package is installed**. This will add the [MCP nodes](/node-red/flowfuse/mcp/) to your Node palette in your instance editor. +* **Ensure the `@flowfuse-nodes/nr-mcp-server-nodes` package is installed**. This will add the [MCP nodes](/docs/flowfuse-nodes/mcp/) to your Node palette in your instance editor. > **Note:** The MCP nodes (@flowfuse-nodes/nr-mcp-server-nodes) are only available on the Enterprise tier. diff --git a/src/blog/2025/10/how-to-log-plc-data-csv-files.md b/src/blog/2025/10/how-to-log-plc-data-csv-files.md index db7b69fa3d..f2d8823245 100644 --- a/src/blog/2025/10/how-to-log-plc-data-csv-files.md +++ b/src/blog/2025/10/how-to-log-plc-data-csv-files.md @@ -197,7 +197,7 @@ try { } ``` -3. Connect it to your notification system to alert when space is critical, for notification you can use [email](/node-red/notification/email/), [telegram](/node-red/notification/telegram/), discord with [FlowFuse](/node-red/notification/discord/). +3. Connect it to your notification system to alert when space is critical, for notification you can use [email](/docs/node-red/notification/email/), [telegram](/docs/node-red/notification/telegram/), discord with [FlowFuse](/docs/node-red/notification/discord/). 4. Deploy the flow Now you'll get warnings before disk space becomes critical, giving you time to archive old data or expand storage. diff --git a/src/blog/2025/11/building-hmi-for-equipment-control.md b/src/blog/2025/11/building-hmi-for-equipment-control.md index 6f1e4a91a5..9c0a37e049 100644 --- a/src/blog/2025/11/building-hmi-for-equipment-control.md +++ b/src/blog/2025/11/building-hmi-for-equipment-control.md @@ -101,7 +101,7 @@ Your HMI needs to talk to your PLC to read equipment status and send control com Choose the node that matches your PLC: -- **[node-red-contrib-modbus](/node-red/protocol/modbus/)** – Modbus RTU/TCP PLCs and devices +- **[node-red-contrib-modbus](/docs/node-red/protocol/modbus/)** – Modbus RTU/TCP PLCs and devices - **[node-red-contrib-s7](/blog/2025/01/integrating-siemens-s7-plcs-with-node-red-guide/)** – Siemens S7-300/400/1200/1500 - **[node-red-contrib-opcua](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/)** – OPC UA servers (universal industrial standard) - **[node-red-contrib-cip-ethernet-ip](/blog/2025/10/using-ethernet-ip-with-flowfuse/)** – Allen-Bradley/Rockwell PLCs diff --git a/src/blog/2025/11/flowfuse+llm+mcp-equals-text-driven-operations.md b/src/blog/2025/11/flowfuse+llm+mcp-equals-text-driven-operations.md index 40ddfd2ba7..efc47596be 100644 --- a/src/blog/2025/11/flowfuse+llm+mcp-equals-text-driven-operations.md +++ b/src/blog/2025/11/flowfuse+llm+mcp-equals-text-driven-operations.md @@ -31,7 +31,7 @@ the right questions. ## The Context Challenge Data capture involves integrating various machine protocols (like -[OPC-UA](/node-red/protocol/opc-ua/) or [Modbus](/node-red/protocol/modbus/)), +[OPC-UA](/docs/node-red/protocol/opc-ua/) or [Modbus](/docs/node-red/protocol/modbus/)), transporting, combining, and visualizing the information. While low-code tools like Node-RED have decreased the implementation time to mere hours, the full problem isn't solved: what happens *after* the data is collected? @@ -66,7 +66,7 @@ functionality (tools) they want to expose to the LLM. * **Resources** are read-only, like sensor readings, employee staff lists, vacation calendars, or specification sheets (e.g., upper and lower temperature limits). * **Tools** are functions that allow the LLM to perform an action or change a state in the physical world. -By feeding this context into an MCP server (such as the official [FlowFuse MCP node](/node-red/flowfuse/mcp/)), +By feeding this context into an MCP server (such as the official [FlowFuse MCP node](/docs/flowfuse-nodes/mcp/)), the LLM transforms into a powerful operational partner. For example, an operator can ask: "Can you show me the last five temp sensor readings recorded?". diff --git a/src/blog/2025/11/industrial-data-validation-guide.md b/src/blog/2025/11/industrial-data-validation-guide.md index 61daea7295..d52de5adfa 100644 --- a/src/blog/2025/11/industrial-data-validation-guide.md +++ b/src/blog/2025/11/industrial-data-validation-guide.md @@ -306,7 +306,7 @@ Once installed, you'll find the "telegram sender" and "telegram receiver" nodes ### Creating Your Telegram Bot and Getting Your Chat ID -Before you can send alerts, you need to create a Telegram bot and get your Chat ID. We have a detailed guide that walks you through the entire process: [How to Create a Telegram Bot and Find Your Chat ID](/node-red/notification/telegram/#creating-a-bot-in-telegram) +Before you can send alerts, you need to create a Telegram bot and get your Chat ID. We have a detailed guide that walks you through the entire process: [How to Create a Telegram Bot and Find Your Chat ID](/docs/node-red/notification/telegram/#creating-a-bot-in-telegram) Once you have your **bot token** and **Chat ID**, come back here to continue with the alert setup. diff --git a/src/blog/2025/12/flowfuse-release-2-25.md b/src/blog/2025/12/flowfuse-release-2-25.md index 2d6cfdba98..7782afd729 100644 --- a/src/blog/2025/12/flowfuse-release-2-25.md +++ b/src/blog/2025/12/flowfuse-release-2-25.md @@ -21,7 +21,7 @@ This release comes with some big updates for the FlowFuse Expert. ![Image of MCP in FlowFuse](./images/mcp-in-flowfuse.png) _[FlowFuse Expert Interface]_ -Our [MCP nodes](/node-red/flowfuse/mcp/) make is super easy to extend AI Agents with tools and resources in your environment. With this release, we're introducing the ability to interact your MCP servers directly within the FlowFuse Expert. +Our [MCP nodes](/docs/flowfuse-nodes/mcp/) make is super easy to extend AI Agents with tools and resources in your environment. With this release, we're introducing the ability to interact your MCP servers directly within the FlowFuse Expert. Previously, when setting up an MCP server in FlowFuse, you would first designate your tools, resources, and servers in side of Node-RED using the MCP nodes. Then, to interact with them, you needed a separate tool like VSCode to query and perform operations. diff --git a/src/blog/2025/12/mttf-vs-mtbf-vs-mttr.md b/src/blog/2025/12/mttf-vs-mtbf-vs-mttr.md index 8d7de3cd8f..155c726826 100644 --- a/src/blog/2025/12/mttf-vs-mtbf-vs-mttr.md +++ b/src/blog/2025/12/mttf-vs-mtbf-vs-mttr.md @@ -187,4 +187,4 @@ Stop guessing when parts will fail. Track installation dates and failure times, Use that data to schedule replacements during planned downtime instead of waiting for a 2am breakdown. Unplanned failures typically cost 3-5x more than scheduled replacements once overtime labor, rush shipping, and lost production are factored in, so the only MTTF numbers that really matter are the ones you measure yourself. -As your equipment base grows, manual tracking becomes cumbersome. [FlowFuse](/) automates this by connecting to [PLCs](/blog/2025/12/what-is-plc/), [SCADA](/use-cases/scada/) systems, [MES](/use-cases/mes/) platforms, and your CMMS, pulling operating hours directly from equipment through industrial protocols like [Modbus](/node-red/protocol/modbus/), [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/), and [EtherNet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/), then capturing failure events to recalculate MTTF in real-time across your entire facility. +As your equipment base grows, manual tracking becomes cumbersome. [FlowFuse](/) automates this by connecting to [PLCs](/blog/2025/12/what-is-plc/), [SCADA](/use-cases/scada/) systems, [MES](/use-cases/mes/) platforms, and your CMMS, pulling operating hours directly from equipment through industrial protocols like [Modbus](/docs/node-red/protocol/modbus/), [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/), and [EtherNet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/), then capturing failure events to recalculate MTTF in real-time across your entire facility. diff --git a/src/blog/2026/01/why-modbus-still-exist.md b/src/blog/2026/01/why-modbus-still-exist.md index b20c089fcc..4f1f43a9b6 100644 --- a/src/blog/2026/01/why-modbus-still-exist.md +++ b/src/blog/2026/01/why-modbus-still-exist.md @@ -120,7 +120,7 @@ For decades, industrial networks lived behind air gaps and Modbus's lack of auth The moment you connect to external networks, Modbus's plaintext communication and zero authentication become indefensible. An attacker with network access can send arbitrary commands to any Modbus device. No password, no certificate, no audit trail. -[OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/) provides TLS encryption, certificate-based authentication, role-based access control, and detailed audit logging. [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/) with [Sparkplug B](/blog/2024/08/using-mqtt-sparkplugb-with-node-red/) adds lightweight security for IIoT deployments. These aren't nice-to-have features when you're connecting critical infrastructure to the internet: they're requirements. Companies implementing Industry 4.0 initiatives, remote operations, or cloud-based analytics cannot build on [Modbus](/node-red/protocol/modbus/). The protocol has no security model to extend. +[OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/) provides TLS encryption, certificate-based authentication, role-based access control, and detailed audit logging. [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/) with [Sparkplug B](/blog/2024/08/using-mqtt-sparkplugb-with-node-red/) adds lightweight security for IIoT deployments. These aren't nice-to-have features when you're connecting critical infrastructure to the internet: they're requirements. Companies implementing Industry 4.0 initiatives, remote operations, or cloud-based analytics cannot build on [Modbus](/docs/node-red/protocol/modbus/). The protocol has no security model to extend. Modbus RTU at 115.2 kbaud can theoretically handle around 80-100 transactions per second under ideal conditions, but real-world installations see more like 40-60 due to device processing time and network overhead. That's fine for a few dozen devices with slow-changing process variables. diff --git a/src/blog/2026/02/edge-ai-is-80-percent-pipeline-and-20-percent-ai.md b/src/blog/2026/02/edge-ai-is-80-percent-pipeline-and-20-percent-ai.md index 7460503941..cdf2302c5f 100644 --- a/src/blog/2026/02/edge-ai-is-80-percent-pipeline-and-20-percent-ai.md +++ b/src/blog/2026/02/edge-ai-is-80-percent-pipeline-and-20-percent-ai.md @@ -54,7 +54,7 @@ Take what happens when you want to put a model in production. You have a data sc In most deployments, nobody has a good answer to any of those questions. The model ends up in a container somewhere that only the data scientist understands, connected to the plant by a fragile handshake that no one wants to touch. The OT team treats it like a black box because it is a black box. -[FlowFuse's ONNX nodes](/node-red/flowfuse/ai/onxx/) change that by putting the model where OT engineers already work. You train, you export, you deploy it as a node in a flow, alongside the Modbus reads, the historian writes, the MQTT publishes. The inference runs locally, on the edge device, no cloud round trip, no latency the line cannot afford. When we [deployed a motor anomaly detector this way](/blog/2026/02/motor-anomaly-detector-ai/), the thing that changed was not the model's accuracy. It was that the people running the line could see what the model was looking at, wire its output to the control logic themselves, and update it through the same pipeline they use for everything else. That is not a convenience improvement. That is the difference between a model that gets maintained and a model that gets abandoned. +[FlowFuse's ONNX nodes](/docs/flowfuse-nodes/ai/onxx/) change that by putting the model where OT engineers already work. You train, you export, you deploy it as a node in a flow, alongside the Modbus reads, the historian writes, the MQTT publishes. The inference runs locally, on the edge device, no cloud round trip, no latency the line cannot afford. When we [deployed a motor anomaly detector this way](/blog/2026/02/motor-anomaly-detector-ai/), the thing that changed was not the model's accuracy. It was that the people running the line could see what the model was looking at, wire its output to the control logic themselves, and update it through the same pipeline they use for everything else. That is not a convenience improvement. That is the difference between a model that gets maintained and a model that gets abandoned. The same logic applies to the [FlowFuse Expert](/docs/user/expert/). OT engineers are not waiting for JavaScript fluency. They know the equipment; they know what they need the flow to do; they just get slowed down in the translation between that knowledge and working code. The Expert handles the boilerplate, autocompletes flows, generates function node logic from a plain-language description, explains what a set of nodes does in terms that make sense. It is not a general-purpose chatbot bolted onto an IDE. It was trained on Node-RED and FlowFuse specifically, which means it gives answers that work in industrial contexts rather than answers that look plausible until you try to run them. For teams where the backlog of integration work is longer than the list of people who can do it, that matters. diff --git a/src/blog/2026/02/mapping-mtconnect-streams.md b/src/blog/2026/02/mapping-mtconnect-streams.md index 6ae476c224..f2089f7377 100644 --- a/src/blog/2026/02/mapping-mtconnect-streams.md +++ b/src/blog/2026/02/mapping-mtconnect-streams.md @@ -75,7 +75,7 @@ Bringing MTConnect data into FlowFuse is the first step toward a live dashboard. The Solution Engine node (`node-red-contrib-solution-engine`) makes this straightforward because it lets you access any data point directly by its dataItemId, without having to worry about parsing XML or navigating nested structures. -You can install the node by following the instructions in the FlowFuse documentation: [Using the Palette Manager](/node-red/getting-started/library/#using-the-palette-manager). +You can install the node by following the instructions in the FlowFuse documentation: [Using the Palette Manager](/docs/node-red/getting-started/library/#using-the-palette-manager). Once installed, follow these steps to start collecting data: diff --git a/src/blog/2026/02/motor-anomaly-detector-ai.md b/src/blog/2026/02/motor-anomaly-detector-ai.md index 09fe3ecc34..85cb178f2f 100644 --- a/src/blog/2026/02/motor-anomaly-detector-ai.md +++ b/src/blog/2026/02/motor-anomaly-detector-ai.md @@ -429,7 +429,7 @@ The flow has five stages: receive the payload, extract features, scale and prepa Add an **mqtt-in** node and configure it to connect to the same broker and topic used during training. Set the output to auto-detect so the JSON payload is parsed automatically. -If you are using the built-in FlowFuse MQTT broker, use the [FlowFuse MQTT nodes](/node-red/flowfuse/mqtt/) , these connect automatically when dragged into the flow. +If you are using the built-in FlowFuse MQTT broker, use the [FlowFuse MQTT nodes](/docs/flowfuse-nodes/mqtt/) , these connect automatically when dragged into the flow. **2. Extract Features** @@ -548,7 +548,7 @@ Once deployed, the flow should look like this: **5. Act on the Result** -Connect the scoring output to whatever suits your operation. For testing, a debug node shows results in real time. For production, an mqtt-out node can publish anomaly alerts downstream, the [FlowFuse Dashboard](/platform/dashboard/) package can visualise the anomaly score over time with a clear motor state indicator, and alerting nodes can notify your team directly, via [Telegram](/node-red/notification/email/), [email](/node-red/notification/telegram/), or [SIGNL4](/blueprints/other/mobile-alerting/) for structured mobile alerts with on-call scheduling and acknowledgement tracking. +Connect the scoring output to whatever suits your operation. For testing, a debug node shows results in real time. For production, an mqtt-out node can publish anomaly alerts downstream, the [FlowFuse Dashboard](/platform/dashboard/) package can visualise the anomaly score over time with a clear motor state indicator, and alerting nodes can notify your team directly, via [Telegram](/docs/node-red/notification/email/), [email](/docs/node-red/notification/telegram/), or [SIGNL4](/blueprints/other/mobile-alerting/) for structured mobile alerts with on-call scheduling and acknowledgement tracking. ### What the Output Looks Like diff --git a/src/blog/2026/02/mqtt-influxdb-tutorial.md b/src/blog/2026/02/mqtt-influxdb-tutorial.md index 0278fe6d08..2c2de67ab8 100644 --- a/src/blog/2026/02/mqtt-influxdb-tutorial.md +++ b/src/blog/2026/02/mqtt-influxdb-tutorial.md @@ -80,7 +80,7 @@ This article takes a different approach. Using FlowFuse (the enterprise platform Before you start, make sure you have the following in place. - A running FlowFuse instance. If you don't have one yet, [sign up](https://app.flowfuse.com/account/create) to get started. -- An MQTT broker. FlowFuse Pro and Enterprise include a [built-in broker](/docs/user/teambroker/), and the [MQTT nodes](/node-red/flowfuse/mqtt/) configure themselves automatically. If you are using an external broker, keep your host, port, and credentials handy. +- An MQTT broker. FlowFuse Pro and Enterprise include a [built-in broker](/docs/user/teambroker/), and the [MQTT nodes](/docs/flowfuse-nodes/mqtt/) configure themselves automatically. If you are using an external broker, keep your host, port, and credentials handy. - A running InfluxDB instance, either local or on InfluxDB Cloud. Keep your URL, token, organization, and bucket name handy. ## Building the Pipeline diff --git a/src/blog/2026/02/shop-floor-to-ai-signals-context-decisions.md b/src/blog/2026/02/shop-floor-to-ai-signals-context-decisions.md index de02b91fc9..948bdf92f0 100644 --- a/src/blog/2026/02/shop-floor-to-ai-signals-context-decisions.md +++ b/src/blog/2026/02/shop-floor-to-ai-signals-context-decisions.md @@ -129,7 +129,7 @@ Crucially, in a Unified Namespace, context is added at the moment data enters th That enriched information is then published into a shared MQTT-based Namespace. One location. One structure. One source of truth. Dashboards, analytics, and AI systems all subscribe to the same contextualized view of reality. -Through [FlowFuse MCP nodes](/node-red/flowfuse/mcp/), AI systems connect directly to the namespace, querying live operational context instead of pulling raw time-series data from isolated historians and attempting to reconstruct meaning after the fact. +Through [FlowFuse MCP nodes](/docs/flowfuse-nodes/mcp/), AI systems connect directly to the namespace, querying live operational context instead of pulling raw time-series data from isolated historians and attempting to reconstruct meaning after the fact. [FlowFuse AI Expert](/ai/) operates on the same MCP-backed context layer. Operators and engineers can ask questions in natural language (*"Is Line 3 behaving normally?"*, *"Have we seen this vibration pattern before?"*, *"What changed before the last failure?"*) and receive answers grounded in the live Unified Namespace. diff --git a/src/blog/2026/04/diagnosing-modbus-degradation.md b/src/blog/2026/04/diagnosing-modbus-degradation.md index dddbd9319e..a444834930 100644 --- a/src/blog/2026/04/diagnosing-modbus-degradation.md +++ b/src/blog/2026/04/diagnosing-modbus-degradation.md @@ -144,7 +144,7 @@ The four metrics worth tracking continuously are transaction success rate, respo ### Putting it into practice -Instrument your polling layer to write a timestamped record on every transaction: device address, function code, success or failure, response time in milliseconds, exception code on failures, and error type (timeout vs. CRC vs. exception response). Write those records to a [time-series store](/node-red/database/). [InfluxDB](/node-red/database/influxdb/) is a common choice for industrial deployments; a [PostgreSQL](/blog/2025/08/getting-started-with-flowfuse-tables/) table with a timestamp index works fine too. +Instrument your polling layer to write a timestamped record on every transaction: device address, function code, success or failure, response time in milliseconds, exception code on failures, and error type (timeout vs. CRC vs. exception response). Write those records to a [time-series store](/docs/node-red/database/). [InfluxDB](/docs/node-red/database/influxdb/) is a common choice for industrial deployments; a [PostgreSQL](/blog/2025/08/getting-started-with-flowfuse-tables/) table with a timestamp index works fine too. A reasonable retention strategy is to keep raw transaction records for 7 days and roll up to hourly aggregates (per-device success rate, median response time, CRC error count) for 90 days of trend data. diff --git a/src/blog/2026/06/event-driven-downtime-escalation-workflow.md b/src/blog/2026/06/event-driven-downtime-escalation-workflow.md index f5a4ac0a0b..10df193f88 100644 --- a/src/blog/2026/06/event-driven-downtime-escalation-workflow.md +++ b/src/blog/2026/06/event-driven-downtime-escalation-workflow.md @@ -81,11 +81,11 @@ flowchart TD Here's what that looks like built in [FlowFuse](/): -- **Catch the stop signal:** Most PLCs expose machine state through [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/) or [Modbus](/node-red/protocol/modbus/), and FlowFuse has nodes for both. Point one at the right tag, and the moment the state flips to stopped, it lands in your flow as a timestamped message. +- **Catch the stop signal:** Most PLCs expose machine state through [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua/) or [Modbus](/docs/node-red/protocol/modbus/), and FlowFuse has nodes for both. Point one at the right tag, and the moment the state flips to stopped, it lands in your flow as a timestamped message. - **Track how long it's been down:** A couple of [timer nodes](/blog/2025/12/node-red-timer/) mark the passage of time: past two minutes, past ten, whatever windows fit your line. -- **Branch by severity:** A switch node routes the message: short stops go straight to a [database](/blog/2025/08/getting-started-with-flowfuse-tables/); longer ones also notify the supervisor over [email](/node-red/notification/email/) or [Telegram](/node-red/notification/telegram/); and if it's still unresolved at the next threshold, maintenance gets the same message, escalating further up if needed. +- **Branch by severity:** A switch node routes the message: short stops go straight to a [database](/blog/2025/08/getting-started-with-flowfuse-tables/); longer ones also notify the supervisor over [email](/docs/node-red/notification/email/) or [Telegram](/docs/node-red/notification/telegram/); and if it's still unresolved at the next threshold, maintenance gets the same message, escalating further up if needed. - **Close the record:** When the "running" message comes through, a final node writes the resolution time back into that record: start, every escalation, and end, all filled in by the flow itself. diff --git a/src/blog/2026/06/flowfuse-release-2-31.md b/src/blog/2026/06/flowfuse-release-2-31.md index c253ba0e4c..5d164686af 100644 --- a/src/blog/2026/06/flowfuse-release-2-31.md +++ b/src/blog/2026/06/flowfuse-release-2-31.md @@ -51,7 +51,7 @@ FlowFuse now offers certified nodes: vetted, FlowFuse-supported nodes you can ad - Gemini - Ollama, for models you run yourself - These first LLM nodes handle single-shot, text-in/text-out calls; see the [LLM nodes documentation](/node-red/flowfuse/ai/llm-nodes/) for details. Multimodal input, conversation history, and tool calling are planned for follow-up iterations. They join the FlowFuse AI Nodes package we [shipped in 2.23](/changelog/2025/10/onnx-nodes/), which already includes the ONNX Inference, Image Classification, Object Detection, and Image Depth Estimation nodes for running vision and custom models on-device, with no external API calls. + These first LLM nodes handle single-shot, text-in/text-out calls; see the [LLM nodes documentation](/docs/flowfuse-nodes/ai/llm-nodes/) for details. Multimodal input, conversation history, and tool calling are planned for follow-up iterations. They join the FlowFuse AI Nodes package we [shipped in 2.23](/changelog/2025/10/onnx-nodes/), which already includes the ONNX Inference, Image Classification, Object Detection, and Image Depth Estimation nodes for running vision and custom models on-device, with no external API calls. Certified Nodes are available to FlowFuse Cloud customers as of this release, with Self-Hosted Enterprise support arriving in the next release (2.32). [Get in touch with sales](/contact-us/?subject=Certified%20Nodes) or your customer success manager, and we'll enable the ones your team needs. diff --git a/src/blog/2026/06/opcua-to-influxdb.md b/src/blog/2026/06/opcua-to-influxdb.md index 94db9b0f6d..fd23281160 100644 --- a/src/blog/2026/06/opcua-to-influxdb.md +++ b/src/blog/2026/06/opcua-to-influxdb.md @@ -90,7 +90,7 @@ You'll install two packages: the InfluxDB nodes and the FlowFuse Edge Certified 2. From the menu (top right), select **Manage palette**, then switch to the **Install** tab. 3. Search for **`node-red-contrib-influxdb`** and click **Install**. These nodes write to and query InfluxDB. 4. Switch the catalog using the top dropdown to **FlowFuse Edge Certified Nodes**. Once sales enables FlowFuse Edge Certified Nodes for your team, this catalog shows up here; restart any existing instance so it picks up the updated catalogue. -5. Search for **`@flowfuse-certified-nodes/opcua`** and click **Install**. See the [OPC UA node documentation](/node-red/flowfuse/edge/opcua/) for the full node set and configuration options. +5. Search for **`@flowfuse-certified-nodes/opcua`** and click **Install**. See the [OPC UA node documentation](/docs/flowfuse-nodes/edge/opcua/) for the full node set and configuration options. ![Manage palette Install tab showing the OPC UA certified package in search results](./images/opcua-manage-pallete.png) _Install the OPC UA certified package from the FlowFuse Edge Certified Nodes catalog._ diff --git a/src/blog/2026/06/process-rtsp-camera-feeds-at-the-edge.md b/src/blog/2026/06/process-rtsp-camera-feeds-at-the-edge.md index cb779b559d..0f0e668d5d 100644 --- a/src/blog/2026/06/process-rtsp-camera-feeds-at-the-edge.md +++ b/src/blog/2026/06/process-rtsp-camera-feeds-at-the-edge.md @@ -155,7 +155,7 @@ That's a live line view anyone can pull up in a browser, with no NVR login and n A live view is a real win, but notice what you have now: the camera's output is a PNG buffer moving through your flow, one message per frame. Once a frame is just another message, you can do more than display it. You can ask what's in it. -That's what the [**FlowFuse AI** nodes](/node-red/flowfuse/ai/) are for. They run vision models locally, inside your flow, with nothing sent to an outside service. The **Object Detection** node takes a PNG buffer as its input, which is exactly what the camera node outputs, so you wire the camera straight into it, no conversion step in between. From there the flow stops watching and starts acting: counting material on the conveyor, flagging a person near the flotation cells, or catching a stopped belt before the line backs up. Each detection comes back as structured data, a label, a confidence score, and a position, which you handle like any other signal in FlowFuse. +That's what the [**FlowFuse AI** nodes](/docs/flowfuse-nodes/ai/) are for. They run vision models locally, inside your flow, with nothing sent to an outside service. The **Object Detection** node takes a PNG buffer as its input, which is exactly what the camera node outputs, so you wire the camera straight into it, no conversion step in between. From there the flow stops watching and starts acting: counting material on the conveyor, flagging a person near the flotation cells, or catching a stopped belt before the line backs up. Each detection comes back as structured data, a label, a confidence score, and a position, which you handle like any other signal in FlowFuse. ## Recording frames to disk diff --git a/src/blog/2026/07/calibration-management-dashboard.md b/src/blog/2026/07/calibration-management-dashboard.md index 08ace58802..0b3a2a9c4c 100644 --- a/src/blog/2026/07/calibration-management-dashboard.md +++ b/src/blog/2026/07/calibration-management-dashboard.md @@ -380,7 +380,7 @@ A dashboard in the quality office doesn't reach the person about to pick up a to 4. Add a `simple-ocr` node, then a `link out` / `link in` pair to carry the result down to the next row of the canvas. -> **Tip:** OCR keeps this tutorial hardware-free, but if your labels have a barcode or QR code, scanning one is faster and far more reliable than reading printed text. Swap the camera and OCR nodes for a [barcode scanner](/node-red/peripheral/barcodescanner/) and feed the decoded ID straight into "Set Tool ID," and everything downstream will keep working. You can also drop "Extract Tool ID" and "Scan Valid?", since a scanner either returns an ID or nothing. +> **Tip:** OCR keeps this tutorial hardware-free, but if your labels have a barcode or QR code, scanning one is faster and far more reliable than reading printed text. Swap the camera and OCR nodes for a [barcode scanner](/docs/node-red/peripheral/barcodescanner/) and feed the decoded ID straight into "Set Tool ID," and everything downstream will keep working. You can also drop "Extract Tool ID" and "Scan Valid?", since a scanner either returns an ID or nothing. 5. Add a `function` node named "Extract Tool ID": @@ -547,4 +547,4 @@ Deploy and open `/tool-check` on a device with a camera. Hold a label reading `T You've built a calibration application that answers two different questions from the same data. The office screen tells a quality engineer how the plant stands and what needs collecting this week. The station screen tells an operator whether the tool in their hand is fit to use. Both read the due date, so neither can drift from the other. -Right now it runs on generated data. To go live, remove the simulator flow and point the queries at your own register. Just alias your column names in the `SELECT` lists, and everything downstream keeps working. If your records sit in a CMMS or ERP behind an API, swap the query nodes for `http request` nodes and nothing else changes. From there, the obvious next steps are ones the register already supports: email the overdue list to the calibration lab each Monday, or [notify](/node-red/notification/) someone the moment a critical gauge goes overdue instead of waiting for a screen to be looked at. The same approach works for [defect tracking](/blog/2026/07/defect-and-quality-monitoring/) and [downtime](/blog/2026/07/build-downtime-logger/) too, and for the other [manufacturing dashboards](/blog/2026/08/manufacturing-dashboard-examples/) teams build on the same data. It also pairs with floor-level checks such as a [layered process audit](/blog/2026/08/layered-process-audit/), where a tool's calibration status is exactly the kind of item this dashboard can hand the auditor an answer for instead of a lookup. See how manufacturers are using it on our [automotive solutions page](/industries/automotive/). +Right now it runs on generated data. To go live, remove the simulator flow and point the queries at your own register. Just alias your column names in the `SELECT` lists, and everything downstream keeps working. If your records sit in a CMMS or ERP behind an API, swap the query nodes for `http request` nodes and nothing else changes. From there, the obvious next steps are ones the register already supports: email the overdue list to the calibration lab each Monday, or [notify](/docs/node-red/notification/) someone the moment a critical gauge goes overdue instead of waiting for a screen to be looked at. The same approach works for [defect tracking](/blog/2026/07/defect-and-quality-monitoring/) and [downtime](/blog/2026/07/build-downtime-logger/) too, and for the other [manufacturing dashboards](/blog/2026/08/manufacturing-dashboard-examples/) teams build on the same data. It also pairs with floor-level checks such as a [layered process audit](/blog/2026/08/layered-process-audit/), where a tool's calibration status is exactly the kind of item this dashboard can hand the auditor an answer for instead of a lookup. See how manufacturers are using it on our [automotive solutions page](/industries/automotive/). diff --git a/src/blog/2026/07/defect-and-quality-monitoring.md b/src/blog/2026/07/defect-and-quality-monitoring.md index 7d41229ba2..c62799eb98 100644 --- a/src/blog/2026/07/defect-and-quality-monitoring.md +++ b/src/blog/2026/07/defect-and-quality-monitoring.md @@ -409,6 +409,6 @@ Deploy and open the dashboard. Every widget populates from the seeded data at on You've built a working quality dashboard: a `defects` table, a single query that computes every KPI in one pass, and a page of stat cards, a Pareto chart, trend and breakdown charts, an SLA table, and a status funnel, all filtering live by line, shift, and date. -Right now it runs on the simulator, but that was only ever a stand-in for your real data. To go live, remove the simulator flow and point the query at your own `defects` table. Everything downstream keeps working, because the dashboard only ever reads from that one query. Your defects don't live in PostgreSQL? That's fine too. FlowFuse connects to MySQL, MongoDB, InfluxDB, and more, as our [database integration guides](/node-red/database/) show. +Right now it runs on the simulator, but that was only ever a stand-in for your real data. To go live, remove the simulator flow and point the query at your own `defects` table. Everything downstream keeps working, because the dashboard only ever reads from that one query. Your defects don't live in PostgreSQL? That's fine too. FlowFuse connects to MySQL, MongoDB, InfluxDB, and more, as our [database integration guides](/docs/node-red/database/) show. That's the real point. FlowFuse lets you build the exact application your floor needs quickly, without deep engineering knowledge or writing code, wired to the systems you already run instead of forcing your process to fit a fixed tool. This tutorial happened to build defect tracking, but the same approach covers the full range of [manufacturing dashboards](/blog/2026/08/manufacturing-dashboard-examples/), production monitoring, OEE, and the wider quality picture. See how manufacturers are already putting it to work on our [automotive solutions page](/industries/automotive/). diff --git a/src/blog/2026/08/layered-process-audit.md b/src/blog/2026/08/layered-process-audit.md index 51b2dae741..f3ed643d23 100644 --- a/src/blog/2026/08/layered-process-audit.md +++ b/src/blog/2026/08/layered-process-audit.md @@ -141,7 +141,7 @@ Manufacturers can start an LPA program with paper forms or spreadsheets, but man A digital LPA can help schedule audits, record findings, notify responsible people, and track actions through closure. -Connecting the audit with production data provides another benefit. With FlowFuse, manufacturers can connect an LPA application to machines, [PLCs](/landing/plc/), sensors, [databases](/node-red/database/), and other industrial systems. +Connecting the audit with production data provides another benefit. With FlowFuse, manufacturers can connect an LPA application to machines, [PLCs](/landing/plc/), sensors, [databases](/docs/node-red/database/), and other industrial systems. This allows audit results to be viewed alongside production information such as downtime, machine states, or process parameters. Instead of only recording that a process failed, teams can investigate what was happening around the failure. diff --git a/src/blog/2026/08/what-is-scada.md b/src/blog/2026/08/what-is-scada.md index 3afa47f34d..4d3112adff 100644 --- a/src/blog/2026/08/what-is-scada.md +++ b/src/blog/2026/08/what-is-scada.md @@ -62,7 +62,7 @@ Programmable Logic Controllers and Remote Terminal Units take the raw signals fr ### Communication Networks -Communication networks carry data between field devices, controllers, and the central SCADA software, using protocols like [Modbus](/node-red/protocol/modbus/), [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua), [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), or [Ethernet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/) depending on the equipment and environment. This layer determines how reliably and securely data moves across a facility, and it becomes especially important when connecting older equipment to newer IT/OT platforms. +Communication networks carry data between field devices, controllers, and the central SCADA software, using protocols like [Modbus](/docs/node-red/protocol/modbus/), [OPC UA](/blog/2025/07/reading-and-writing-plc-data-using-opc-ua), [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/), or [Ethernet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/) depending on the equipment and environment. This layer determines how reliably and securely data moves across a facility, and it becomes especially important when connecting older equipment to newer IT/OT platforms. ### HMI And Central Monitoring Software diff --git a/src/blog/2026/09/industrial-ai-agent.md b/src/blog/2026/09/industrial-ai-agent.md index 495a79aedd..2005ea3f49 100644 --- a/src/blog/2026/09/industrial-ai-agent.md +++ b/src/blog/2026/09/industrial-ai-agent.md @@ -73,7 +73,7 @@ Ask why they don't use the FlowFuse Expert built into the platform, and the answ FlowFuse now acts as an MCP server. Your own AI agent connects to it as a client and because the AI agent is yours, so is the choice of model it uses. -That means the AI agent your company already approved can work the platform directly: look at your teams and applications, check what your instances are doing, read runtime logs, query your [FlowFuse Tables](/node-red/flowfuse/flowfuse-tables/) data, create applications and instances, and build and edit the flows inside your instances. +That means the AI agent your company already approved can work the platform directly: look at your teams and applications, check what your instances are doing, read runtime logs, query your [FlowFuse Tables](/docs/flowfuse-nodes/flowfuse-tables/) data, create applications and instances, and build and edit the flows inside your instances. ## How an AI agent connects to FlowFuse diff --git a/src/changelog/2026/01/mcp-rbacs.md b/src/changelog/2026/01/mcp-rbacs.md index f19a69a4c8..236cecf0af 100644 --- a/src/changelog/2026/01/mcp-rbacs.md +++ b/src/changelog/2026/01/mcp-rbacs.md @@ -1,34 +1,34 @@ ---- -title: "FlowFuse Expert: MCP-Powered Insights with RBACs" -description: "You can now limit which user roles see which Model Context Protocol (MCP) tools when querying your MCP servers with the FlowFuse Expert." +--- +title: "FlowFuse Expert: MCP-Powered Insights with RBACs" +description: "You can now limit which user roles see which Model Context Protocol (MCP) tools when querying your MCP servers with the FlowFuse Expert." date: 2026-01-14 13:00:00.0 -release: "2.26" -authors: ['steve-mclaughlin'] -tags: - - changelog ---- - -Following on the the introduction of [FlowFuse Expert MCP-Powered Insights](/changelog/2025/12/ff-expert-mcp-insights/) we have added annotations to the FlowFuse MCP nodes and linked them up with the FlowFuse roles. -This permits a level of control over who can access what. This is just a first step, we will be working in the area over the next few iterations. - -### MCP Tools Node - -To facilitate a level of role based access control in the Expert, we have leveraged the new Granular RBACs and MCP annotations to introduce 2 means of filtering and control: -1. By [application](/docs/user/concepts/#application) - as introduced in FlowFuse 1.5 -2. By pre-defined role level mapping to the annotation hints. For instance, users with a 'Viewer' role will only be exposed to tools marked with the Read Only hint, while Destructive tools are reserved for 'Owners'. The screenshot below indicates more clearly what this mapping is. - -To facilitate this, we have the next version of our [MCP Server Nodes](/node-red/flowfuse/mcp/) will introduce the 4 MCP standard annotations: - -- **Read Only Hint**: Lets the MCP Client know that the tool is read-only and does not modify any data or state. This hint can be used by LLMs to understand that invoking this tool will not have side effects, making it safe for exploratory queries or information retrieval. -- **Destructive Hint**: Lets the MCP Client know that the tool performs destructive actions that may modify or delete data. This hint can be used by LLMs to exercise caution when invoking this tool, as it may have irreversible effects. -- **Idempotent Hint**: Lets the MCP Client know that the tool is idempotent, meaning that multiple invocations with the same parameters will have the same effect as a single invocation. This hint can be used by LLMs to understand that it is safe to retry or repeat calls to this tool without causing unintended side effects. -- **Open World Hint**: Lets the MCP Client know that the tool operates in an open-world context, meaning it may interact with external systems or data sources beyond the immediate environment. This hint can be used by LLMs to understand that invoking this tool may involve uncertainties or dependencies on external factors. - -These annotations are not exclusive to FlowFuse or FlowFuse Roles, they also are useful for your own Agents and LLMs. - -_Note: Hints do not enforce behavior. The actual behavior of the tool depends on how it is used in your flows_ - -![MCP Server Tool Node with new annotations](./images/mcp-annotations.png){data-zoomable} -_MCP Server Tool Node with new annotations_ - -_Note: This feature is currently in Beta. We are actively expanding these capabilities and would love to hear how you are using MCP to extend your systems._ +release: "2.26" +authors: ['steve-mclaughlin'] +tags: + - changelog +--- + +Following on the the introduction of [FlowFuse Expert MCP-Powered Insights](/changelog/2025/12/ff-expert-mcp-insights/) we have added annotations to the FlowFuse MCP nodes and linked them up with the FlowFuse roles. +This permits a level of control over who can access what. This is just a first step, we will be working in the area over the next few iterations. + +### MCP Tools Node + +To facilitate a level of role based access control in the Expert, we have leveraged the new Granular RBACs and MCP annotations to introduce 2 means of filtering and control: +1. By [application](/docs/user/concepts/#application) - as introduced in FlowFuse 1.5 +2. By pre-defined role level mapping to the annotation hints. For instance, users with a 'Viewer' role will only be exposed to tools marked with the Read Only hint, while Destructive tools are reserved for 'Owners'. The screenshot below indicates more clearly what this mapping is. + +To facilitate this, we have the next version of our [MCP Server Nodes](/docs/flowfuse-nodes/mcp/) will introduce the 4 MCP standard annotations: + +- **Read Only Hint**: Lets the MCP Client know that the tool is read-only and does not modify any data or state. This hint can be used by LLMs to understand that invoking this tool will not have side effects, making it safe for exploratory queries or information retrieval. +- **Destructive Hint**: Lets the MCP Client know that the tool performs destructive actions that may modify or delete data. This hint can be used by LLMs to exercise caution when invoking this tool, as it may have irreversible effects. +- **Idempotent Hint**: Lets the MCP Client know that the tool is idempotent, meaning that multiple invocations with the same parameters will have the same effect as a single invocation. This hint can be used by LLMs to understand that it is safe to retry or repeat calls to this tool without causing unintended side effects. +- **Open World Hint**: Lets the MCP Client know that the tool operates in an open-world context, meaning it may interact with external systems or data sources beyond the immediate environment. This hint can be used by LLMs to understand that invoking this tool may involve uncertainties or dependencies on external factors. + +These annotations are not exclusive to FlowFuse or FlowFuse Roles, they also are useful for your own Agents and LLMs. + +_Note: Hints do not enforce behavior. The actual behavior of the tool depends on how it is used in your flows_ + +![MCP Server Tool Node with new annotations](./images/mcp-annotations.png){data-zoomable} +_MCP Server Tool Node with new annotations_ + +_Note: This feature is currently in Beta. We are actively expanding these capabilities and would love to hear how you are using MCP to extend your systems._ diff --git a/src/customer-stories/aperia-technologies-plc-replacement.md b/src/customer-stories/aperia-technologies-plc-replacement.md index 00ea20c094..e3e27c01b9 100644 --- a/src/customer-stories/aperia-technologies-plc-replacement.md +++ b/src/customer-stories/aperia-technologies-plc-replacement.md @@ -54,7 +54,7 @@ This manual overhead was limiting Aperia's ability to scale manufacturing operat ## The FlowFuse Solution -Aperia's answer was to remove the proprietary PLC from the center of the architecture entirely. Instead, smart fixtures run Node-RED on an edge Windows instance, managed by FlowFuse. At the hardware layer, Node-RED talks directly to sensors, actuators, scanners, and motors using standard industrial protocols: [Modbus TCP](/node-red/protocol/modbus/)/[RTU](/blog/2025/09/using-modbus-with-flowfuse/), [RS232](/blog/2025/07/connect-legacy-equipment-serial-flowfuse/), [raw TCP](/node-red/core-nodes/tcp-in/), and [EtherNet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/). At the software layer, it triggers label printing, logs serialized telemetry to [MongoDB](/node-red/database/mongodb/), syncs maintenance events to Limble, and pulls data from NetSuite ERP. [FlowFuse functions as the orchestration layer](/use-cases/it-ot-middleware/) that ties hardware protocols and enterprise APIs into one canvas, through two strategic implementations: +Aperia's answer was to remove the proprietary PLC from the center of the architecture entirely. Instead, smart fixtures run Node-RED on an edge Windows instance, managed by FlowFuse. At the hardware layer, Node-RED talks directly to sensors, actuators, scanners, and motors using standard industrial protocols: [Modbus TCP](/docs/node-red/protocol/modbus/)/[RTU](/blog/2025/09/using-modbus-with-flowfuse/), [RS232](/blog/2025/07/connect-legacy-equipment-serial-flowfuse/), [raw TCP](/docs/node-red/core-nodes/network/tcp-in/), and [EtherNet/IP](/blog/2025/10/using-ethernet-ip-with-flowfuse/). At the software layer, it triggers label printing, logs serialized telemetry to [MongoDB](/docs/node-red/database/mongodb/), syncs maintenance events to Limble, and pulls data from NetSuite ERP. [FlowFuse functions as the orchestration layer](/use-cases/it-ot-middleware/) that ties hardware protocols and enterprise APIs into one canvas, through two strategic implementations: ### Turning Idle Test Equipment into R&D Capacity @@ -88,7 +88,7 @@ Aperia's solution was a vision-integrated screw assembly station connected to th The solution architecture includes: - **Edge Control Layer:** Node-RED running on edge Windows instances, managed by FlowFuse, in place of proprietary PLC logic - - [**Industrial Protocol Support**](/node-red/protocol/)**:** Modbus TCP/RTU, RS232, raw TCP, and EtherNet/IP for direct communication with sensors, actuators, scanners, and motors + - [**Industrial Protocol Support**](/docs/node-red/protocol/)**:** Modbus TCP/RTU, RS232, raw TCP, and EtherNet/IP for direct communication with sensors, actuators, scanners, and motors - **Enterprise System Integration:** MongoDB for serialized telemetry, DronaHQ for dashboards, NetSuite for ERP data, and Limble for maintenance event syncing - **Alerting:** Slack API integration for instant anomaly and downtime alerts - **Deployment Management:** FlowFuse pipelines for pushing device configuration updates to full device groups across the factory floor diff --git a/src/customer-stories/energinet-streamlining-critical-infrastructure-data-management.md b/src/customer-stories/energinet-streamlining-critical-infrastructure-data-management.md index 87d967918e..0451c57ee2 100644 --- a/src/customer-stories/energinet-streamlining-critical-infrastructure-data-management.md +++ b/src/customer-stories/energinet-streamlining-critical-infrastructure-data-management.md @@ -70,8 +70,8 @@ FlowFuse serves as a production infrastructure connecting Energinet's operationa ### Operational System Delivery: -- Time-Series Analytics: Governed delivery to [InfluxDB](/node-red/database/influxdb/) databases supporting trend analysis, predictive maintenance, and renewable integration planning -- Operational Databases: Structured routing to [MySQL](/node-red/database/mysql/) systems for compliance reporting and operational record-keeping +- Time-Series Analytics: Governed delivery to [InfluxDB](/docs/node-red/database/influxdb/) databases supporting trend analysis, predictive maintenance, and renewable integration planning +- Operational Databases: Structured routing to [MySQL](/docs/node-red/database/mysql/) systems for compliance reporting and operational record-keeping - Real-Time Monitoring: Live data feeds to Grafana visualization platforms enabling 24/7 grid surveillance and incident response ### Enterprise Governance Meeting TSO Standards diff --git a/src/customer-stories/leveraging-node-red-and-flowfuse-to-automate-precision-manufacturing.md b/src/customer-stories/leveraging-node-red-and-flowfuse-to-automate-precision-manufacturing.md index 72cd575098..f015853e96 100644 --- a/src/customer-stories/leveraging-node-red-and-flowfuse-to-automate-precision-manufacturing.md +++ b/src/customer-stories/leveraging-node-red-and-flowfuse-to-automate-precision-manufacturing.md @@ -50,7 +50,7 @@ Manufacturing environments, particularly those involving electro-coating process #### Hardware Challenges and Solutions -Abrasive Technology initially utilized [Raspberry Pis](/node-red/hardware/raspberry-pi-4/) for hosting Node-RED instances, linking them to industrialized touch screens for [Human-Machine Interfaces (HMIs)](/blog/2025/11/building-hmi-for-equipment-control/). While Raspberry Pis proved reliable for many applications, certain projects pushed hardware capabilities. Dudley highlights: "I have found them to be very reliable, but on the current project, I'm absolutely bumping up against its hardware capabilities." The anticipation of the [Pi5](/node-red/hardware/raspberry-pi-5/), with enhanced capabilities, holds promise, and Dudley is eagerly awaiting its arrival. +Abrasive Technology initially utilized [Raspberry Pis](/docs/node-red/hardware/raspberry-pi-4/) for hosting Node-RED instances, linking them to industrialized touch screens for [Human-Machine Interfaces (HMIs)](/blog/2025/11/building-hmi-for-equipment-control/). While Raspberry Pis proved reliable for many applications, certain projects pushed hardware capabilities. Dudley highlights: "I have found them to be very reliable, but on the current project, I'm absolutely bumping up against its hardware capabilities." The anticipation of the [Pi5](/docs/node-red/hardware/raspberry-pi-5/), with enhanced capabilities, holds promise, and Dudley is eagerly awaiting its arrival. In addition to Raspberry Pis, Abrasive Technology currently operates a server running Node-RED locally alongside instances on FlowFuse. The company is actively transitioning its Node-RED instances from locally maintained servers to FlowFuse Cloud to make it easier to manage them. Dudley notes, "We also currently have a server Running Node-RED locally as well as instances on FlowFuse. Our search for a better way to host and manage our Node-RED instances is what started our transition to using FlowFuse." diff --git a/src/customer-stories/leveraging-node-red-and-flowfuse-to-revolutionize-irrigation.md b/src/customer-stories/leveraging-node-red-and-flowfuse-to-revolutionize-irrigation.md index 88898788d2..6f18882080 100644 --- a/src/customer-stories/leveraging-node-red-and-flowfuse-to-revolutionize-irrigation.md +++ b/src/customer-stories/leveraging-node-red-and-flowfuse-to-revolutionize-irrigation.md @@ -55,7 +55,7 @@ Recognizing the need for a technological overhaul, PIDD embraced Node-RED, a flo #### Evolving Hardware Landscape -Initially relying on [Raspberry Pi](/node-red/hardware/raspberry-pi-4/) devices, PIDD faced challenges related to heat, especially in Arizona's high-temperature environment. To address this, they decided to transition to [PLCs](/blog/2025/12/what-is-plc/), with the Groov EPIC system being the preferred choice for improved stability in industrial use. +Initially relying on [Raspberry Pi](/docs/node-red/hardware/raspberry-pi-4/) devices, PIDD faced challenges related to heat, especially in Arizona's high-temperature environment. To address this, they decided to transition to [PLCs](/blog/2025/12/what-is-plc/), with the Groov EPIC system being the preferred choice for improved stability in industrial use. #### Impact on Operations @@ -69,7 +69,7 @@ The adoption of Node-RED and FlowFuse had a profound impact on PIDD's operations PIDD's forward-thinking approach extends to their future plans: - Transition to PLCs: The organization is moving towards PLCs, specifically the Groov EPIC system used by [Opto 22](/customer-stories/opto22-embraces-node-red/), for enhanced stability in challenging environmental conditions. - Command and Control with Ignition: Consideration is given to using [Ignition](/vs/ignition/) for command and control, complemented by [MQTT](/blog/2024/06/how-to-use-mqtt-in-node-red/) for data communication. -- Data Retention Strategy: PIDD aims to implement a robust data retention strategy, possibly involving relational databases like [MySQL](/node-red/database/mysql/) or [Postgres](/node-red/database/postgresql/), to store and retrieve historical data. +- Data Retention Strategy: PIDD aims to implement a robust data retention strategy, possibly involving relational databases like [MySQL](/docs/node-red/database/mysql/) or [Postgres](/docs/node-red/database/postgresql/), to store and retrieve historical data. #### Conclusion diff --git a/src/customer-stories/opto22-embraces-node-red.md b/src/customer-stories/opto22-embraces-node-red.md index 5c1f86d36a..355c7dc384 100644 --- a/src/customer-stories/opto22-embraces-node-red.md +++ b/src/customer-stories/opto22-embraces-node-red.md @@ -30,7 +30,7 @@ Opto 22 is a US-based industrial automation hardware and software manufacturer b -Opto 22 has a history of innovation in the industrial hardware industry, being the first vendor to standardize on Ethernet and TCP/IP connections, being one of the original vendors behind the [OPC standard](/node-red/flowfuse/edge/opcua/), and participating in the steering committee of the more recent [Sparkplug standard](/blog/2024/08/using-mqtt-sparkplugb-with-node-red/). +Opto 22 has a history of innovation in the industrial hardware industry, being the first vendor to standardize on Ethernet and TCP/IP connections, being one of the original vendors behind the [OPC standard](/docs/flowfuse-nodes/edge/opcua/), and participating in the steering committee of the more recent [Sparkplug standard](/blog/2024/08/using-mqtt-sparkplugb-with-node-red/). In 2014, Opto 22 was introducing its first Linux-based hardware called the Opto groov Appliance. At this time, Benson Hougland, VP of Marketing and Product Strategy at Opto 22, and Ben Orchard, Application Engineer at Opto 22 saw a need to make it easier to connect Opto 22 hardware with other devices, sensors, and actuators, in addition to connecting with emerging data services in the cloud. Through lots of research and experimentation, they discovered Node-RED and found it a natural fit for process control engineers who were used to ‘programming’ with flowcharts and diagrams. When Opto 22 introduced its groov EPIC edge programmable industrial controller platform to market in 2018, Node-RED came pre-installed on the device. @@ -42,9 +42,9 @@ Node-RED is being used by Opto 22 customers in a range of use cases. Some of the 1. **Collecting operational data and storing this data in a [SQL database](/use-cases/data-integration/)**. According to Hougland, ‘The ability to collect sensor data, control variables, etc., and store this data in a SQL database is very easy with Node-RED. There are other middleware solutions available but Node-RED makes it dead simple.’ 2. **Accessing external data from the control system to make control decisions**. It is often the case that industrial equipment needs access to external data to make control system decisions. For example, weather data is used in lots of applications to determine the efficiency of machinery. Opto 22 has a customer that manages wind turbines in California. The electrical utility will charge a negative tariff if the turbines generate power for the grid when the grid is over-saturated. Therefore, the Opto 22 customer has deployed Opto groov hardware at each turbine that uses Node-RED to access, every 5 minutes, the spot price for the grid, to determine if the turbine should be generating power or not. The customer realized immediate ROI by reducing their negative tariff charges. -3. **Creating alerts and notifications for industrial events**. Manufacturers often need to send alerts or notifications when a piece of equipment fails or needs servicing. Node-RED makes it very easy to send an alert or notification to any communication channel. Node-RED nodes are available for sending notifications via [email](/node-red/notification/email/), text, text-to-speech, Slack, changing stack lights, etc. -4. **Reading [Modbus](/node-red/flowfuse/edge/modbus/) data.** Modbus is a widely used protocol for industrial devices that is supported on groov EPIC. Opto 22 customers are using Node-RED to transform Modbus registry data into human-readable, contextual data that can be used by other applications on the EPIC or sent to the [cloud for processing](/use-cases/data-integration/). -5. **Bridging new equipment with legacy equipment.** Manufacturing plants have significant investments in legacy equipment that sometimes isn’t easy to access. This makes it difficult to introduce new technologies, like vision analytics, into a [manufacturing line](/use-cases/shop-floor-communication/). Opto 22 customer, [Intrinsics](https://opto22.com/products/groov-case-studies/case-study-intrinsics) wanted to improve their quality control using cameras and vision analytics. They added a [camera](/node-red/flowfuse/edge/rtsp/) to the manufacturing line that sent images directly to a cloud vision analytics service that evaluates a pass or fail. However, the [PLC](/blog/2025/12/what-is-plc/) controlling the line could not receive the pass/fall notification. Therefore, they added a groov Rio, running Node-RED that received the pass/fail message from the analytics service and was able to connect with the legacy PLC to forward the control decision. +3. **Creating alerts and notifications for industrial events**. Manufacturers often need to send alerts or notifications when a piece of equipment fails or needs servicing. Node-RED makes it very easy to send an alert or notification to any communication channel. Node-RED nodes are available for sending notifications via [email](/docs/node-red/notification/email/), text, text-to-speech, Slack, changing stack lights, etc. +4. **Reading [Modbus](/docs/flowfuse-nodes/edge/modbus/) data.** Modbus is a widely used protocol for industrial devices that is supported on groov EPIC. Opto 22 customers are using Node-RED to transform Modbus registry data into human-readable, contextual data that can be used by other applications on the EPIC or sent to the [cloud for processing](/use-cases/data-integration/). +5. **Bridging new equipment with legacy equipment.** Manufacturing plants have significant investments in legacy equipment that sometimes isn’t easy to access. This makes it difficult to introduce new technologies, like vision analytics, into a [manufacturing line](/use-cases/shop-floor-communication/). Opto 22 customer, [Intrinsics](https://opto22.com/products/groov-case-studies/case-study-intrinsics) wanted to improve their quality control using cameras and vision analytics. They added a [camera](/docs/flowfuse-nodes/edge/rtsp/) to the manufacturing line that sent images directly to a cloud vision analytics service that evaluates a pass or fail. However, the [PLC](/blog/2025/12/what-is-plc/) controlling the line could not receive the pass/fall notification. Therefore, they added a groov Rio, running Node-RED that received the pass/fail message from the analytics service and was able to connect with the legacy PLC to forward the control decision. ‘Node-RED makes it so easy to extend and innovate on a hardware platform,’ comments Ben Orchard. ‘The industrial automation industry has a history of offering proprietary drivers to add functionality to a hardware platform. People are tired of this approach. Using Node-RED only requires that you have a web browser to get access to your data and put the data anywhere. It really promotes a lot of freedom and innovation. Node-RED’s open approach is definitely the present and the future for our industry.’ diff --git a/src/customer-stories/scaling-industrial-iot-operations-while-maintaining-competitive-edge.md b/src/customer-stories/scaling-industrial-iot-operations-while-maintaining-competitive-edge.md index aa1db81743..bb83ce6205 100644 --- a/src/customer-stories/scaling-industrial-iot-operations-while-maintaining-competitive-edge.md +++ b/src/customer-stories/scaling-industrial-iot-operations-while-maintaining-competitive-edge.md @@ -55,7 +55,7 @@ FlowFuse processes and routes production data to multiple systems including: - **[MES](/use-cases/mes/) Integration:** Providing real-time machine status, runtime data, and downtime analysis - **SAP Connectivity:** Enabling bidirectional [data integration](/use-cases/data-integration/) for production planning and resource management -- **[InfluxDB](/node-red/database/influxdb/) Storage:** Long-term historical data storage for trend analysis and optimization +- **[InfluxDB](/docs/node-red/database/influxdb/) Storage:** Long-term historical data storage for trend analysis and optimization - **Live Dashboards:** Real-time production visualization and monitoring for operators and management - **Custom HMI Systems:** User interfaces for machine operators to input process parameters and quality data - **SaaS Energy Management System:** Optenda collects power consumption of some machines directly from the FlowFuse Broker @@ -91,7 +91,7 @@ FlowFuse processes and routes production data to multiple systems including: The solution architecture includes: -- **Device Agent Deployment:** Shop floor-level agents connecting directly to manufacturing equipment via [OPC-UA](/node-red/flowfuse/edge/opcua/) and other industrial protocols +- **Device Agent Deployment:** Shop floor-level agents connecting directly to manufacturing equipment via [OPC-UA](/docs/flowfuse-nodes/edge/opcua/) and other industrial protocols - **Encrypted Cloud Connectivity:** Secure communication between edge devices and FlowFuse cloud platform - **Data Processing Pipelines:** Node-RED flows handling data transformation, validation, and routing to multiple downstream systems - **Multi-System Integration:** Connections to MES, SAP, InfluxDB, custom dashboards, and HMI systems diff --git a/src/customer-stories/stfi-future-of-textile-powered-by-node-red.md b/src/customer-stories/stfi-future-of-textile-powered-by-node-red.md index dbacba60fd..d80648278f 100644 --- a/src/customer-stories/stfi-future-of-textile-powered-by-node-red.md +++ b/src/customer-stories/stfi-future-of-textile-powered-by-node-red.md @@ -32,7 +32,7 @@ The Model Factory consists of a number of different manufacturing applications, ![State-Machine and OPC-UA-Connection to the machines](./images/stories/stfi-node-red-flow-opcua.png "State-Machine and OPC-UA-Connection to the machines"){data-zoomable} _Figure 2: [Job Control](https://youtu.be/cgtHO1OVkV8?si=oTpLigbmfqlZ-8Bi&t=98) with State-Machine and OPC-UA-Connection to the machines like a [laser cutter](https://youtu.be/eUkZ8R1tNM4?si=uOwL-XGf0uKkXdVL) (ⒸSTFI)_ -The [job control system](https://youtu.be/cgtHO1OVkV8?si=oTpLigbmfqlZ-8Bi&t=98) is a set of state machines built in Node-RED, see figure 2. Each state machine represents a different machine like a [mobile robot](https://youtu.be/Z_e6EcT2mQs?si=DfxQS0K16bcrpixi) and includes different communication nodes to the different pieces of equipment on the [factory floor](/use-cases/shop-floor-communication/). Node-RED was well suited for developing these state machines since state machines are well represented in flow based programming and also Node-RED provides support for the different communication protocols like [OPC UA](/node-red/flowfuse/edge/opcua/). Node-RED was also used to control the automated guided vehicle and the collaborative robot UR10e within the mobile robot, see figure 3. +The [job control system](https://youtu.be/cgtHO1OVkV8?si=oTpLigbmfqlZ-8Bi&t=98) is a set of state machines built in Node-RED, see figure 2. Each state machine represents a different machine like a [mobile robot](https://youtu.be/Z_e6EcT2mQs?si=DfxQS0K16bcrpixi) and includes different communication nodes to the different pieces of equipment on the [factory floor](/use-cases/shop-floor-communication/). Node-RED was well suited for developing these state machines since state machines are well represented in flow based programming and also Node-RED provides support for the different communication protocols like [OPC UA](/docs/flowfuse-nodes/edge/opcua/). Node-RED was also used to control the automated guided vehicle and the collaborative robot UR10e within the mobile robot, see figure 3. !["Control the Automated Guided Vehicle and the Universal Robot via OPC UA and Real-Time Data Exchange"](./images/stories/stfi-mobile-robot.png "Control the Automated Guided Vehicle and the Universal Robot via OPC UA and Real-Time Data Exchange"){data-zoomable} _Figure 3: Within the [mobile Robot](https://youtu.be/Z_e6EcT2mQs?si=DfxQS0K16bcrpixi), control the Automated Guided Vehicle and the Universal Robot via OPC UA and Real-Time Data Exchange (ⒸSTFI)_ diff --git a/src/node-red/core-nodes/common.njk b/src/node-red/core-nodes/common.njk deleted file mode 100644 index e6d2d49af4..0000000000 --- a/src/node-red/core-nodes/common.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -pagination: - data: coreNodes.common - size: 1 - alias: node - addAllPagesToCollections: true -coreNodeCategory: "common" -permalink: "node-red/core-nodes/{{ node.name | lower | replace(' ', '-') }}/" -eleventyComputed: - eleventyNavigation: - key: "{{ node.name }}" - parent: "{{ coreNodeCategory }}" - title: "Node-RED - {{ node.name }} Node" - description: "{{ node.description }}" - keywords: "{{ node.keywords }}" ---- - -{% include "core-node-docs.njk" %} \ No newline at end of file diff --git a/src/node-red/core-nodes/function.njk b/src/node-red/core-nodes/function.njk deleted file mode 100644 index 228d7cc2ef..0000000000 --- a/src/node-red/core-nodes/function.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -pagination: - data: coreNodes.function - size: 1 - alias: node - addAllPagesToCollections: true -coreNodeCategory: "function" -permalink: "node-red/core-nodes/{{ node.name | lower | replace(' ', '-') }}/" -eleventyComputed: - eleventyNavigation: - key: "{{ node.name }}" - parent: "{{ coreNodeCategory }}" - title: "Node-RED - {{ node.name }} Node" - description: "{{ node.description }}" - keywords: "{{ node.keywords }}" ---- - -{% include "core-node-docs.njk" %} diff --git a/src/node-red/core-nodes/index.njk b/src/node-red/core-nodes/index.njk deleted file mode 100644 index dae5f835ba..0000000000 --- a/src/node-red/core-nodes/index.njk +++ /dev/null @@ -1,25 +0,0 @@ ---- -eleventyNavigation: - key: Core Nodes - order: 4 ---- -

    Node-RED Core Nodes

    - -

    In Node-RED, the core nodes are the set of nodes that are included with the -Node-RED runtime by default without the node install procedure. These nodes are -maintained and supported by the Node-RED development team and are intended to -provide the basic building blocks for creating Node-RED flows.

    - -

    Core nodes include nodes for basic functionality like input/output, processing, -and control flow. They are the foundation upon which more complex workflows can -be built, and they are essential to the operation of Node-RED.

    - -

    The nodes are grouped in a few sections in the nodes sidebar: -

      -
    1. common - miscellaneous nodes
    2. -
    3. function - Control flow, blocking or sending additional messages.
    4. -
    5. network - Receive and send messages over the network on different protocols.
    6. -
    7. sequence - Working with collections of messages, or creating them.
    8. -
    9. parser - transforming data from one format to another.
    10. -
    11. storage - for reading or writing files.
    12. -
    diff --git a/src/node-red/core-nodes/nav.njk b/src/node-red/core-nodes/nav.njk deleted file mode 100644 index b0f9f74bfb..0000000000 --- a/src/node-red/core-nodes/nav.njk +++ /dev/null @@ -1,15 +0,0 @@ ---- -pagination: - data: coreNodesArray - size: 1 - alias: category - addAllPagesToCollections: true -permalink: "node-red/core-nodes/{{ category.key | lower | replace(' ', '-') }}-nav/" -eleventyComputed: - eleventyNavigation: - key: "{{ category.key }}" - title: "{{ category.key | capitalize }}" - parent: "Core Nodes" - url: "/node-red/core-nodes/{{ category.nodes[0].name | lower | replace(' ', '-') }}/" - permalink: false ---- \ No newline at end of file diff --git a/src/node-red/core-nodes/network.njk b/src/node-red/core-nodes/network.njk deleted file mode 100644 index f1aa4d1962..0000000000 --- a/src/node-red/core-nodes/network.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -pagination: - data: coreNodes.network - size: 1 - alias: node - addAllPagesToCollections: true -coreNodeCategory: "network" -permalink: "node-red/core-nodes/{{ node.name | lower | replace(' ', '-') }}/" -eleventyComputed: - eleventyNavigation: - key: "{{ node.name }}" - parent: "{{ coreNodeCategory }}" - title: "Node-RED - {{ node.name }} Node" - description: "{{ node.description }}" - keywords: "{{ node.keywords }}" ---- - -{% include "core-node-docs.njk" %} diff --git a/src/node-red/core-nodes/parsers.njk b/src/node-red/core-nodes/parsers.njk deleted file mode 100644 index a3d95fbada..0000000000 --- a/src/node-red/core-nodes/parsers.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -pagination: - data: coreNodes.parsers - size: 1 - alias: node - addAllPagesToCollections: true -coreNodeCategory: "parsers" -permalink: "node-red/core-nodes/{{ node.name | lower | replace(' ', '-') }}/" -eleventyComputed: - eleventyNavigation: - key: "{{ node.name }}" - parent: "{{ coreNodeCategory }}" - title: "Node-RED - {{ node.name }} Node" - description: "{{ node.description }}" - keywords: "{{ node.keywords }}" ---- - -{% include "core-node-docs.njk" %} diff --git a/src/node-red/core-nodes/sequence.njk b/src/node-red/core-nodes/sequence.njk deleted file mode 100644 index a53a0353ae..0000000000 --- a/src/node-red/core-nodes/sequence.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -pagination: - data: coreNodes.sequence - size: 1 - alias: node - addAllPagesToCollections: true -coreNodeCategory: "sequence" -permalink: "node-red/core-nodes/{{ node.name | lower | replace(' ', '-') }}/" -eleventyComputed: - eleventyNavigation: - key: "{{ node.name }}" - parent: "{{ coreNodeCategory }}" - title: "Node-RED - {{ node.name }} Node" - description: "{{ node.description }}" - keywords: "{{ node.keywords }}" ---- - -{% include "core-node-docs.njk" %} diff --git a/src/node-red/core-nodes/storage.njk b/src/node-red/core-nodes/storage.njk deleted file mode 100644 index ce6fc25b14..0000000000 --- a/src/node-red/core-nodes/storage.njk +++ /dev/null @@ -1,18 +0,0 @@ ---- -pagination: - data: coreNodes.storage - size: 1 - alias: node - addAllPagesToCollections: true -coreNodeCategory: "storage" -permalink: "node-red/core-nodes/{{ node.name | lower | replace(' ', '-') }}/" -eleventyComputed: - eleventyNavigation: - key: "{{ node.name }}" - parent: "{{ coreNodeCategory }}" - title: "Node-RED - {{ node.name }} Node" - description: "{{ node.description }}" - keywords: "{{ node.keywords }}" ---- - -{% include "core-node-docs.njk" %} diff --git a/src/node-red/database/index.md b/src/node-red/database/index.md deleted file mode 100644 index 979ff96745..0000000000 --- a/src/node-red/database/index.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -eleventyNavigation: - key: Database - order: 7 -meta: - title: Node-RED Database Integration Guides - description: Explore database integration guides for Node-RED, including PostgreSQL, MongoDB, InfluxDB, DynamoDB, and TimescaleDB - keywords: node-red, databases, integration, PostgreSQL, MongoDB, InfluxDB, DynamoDB, TimescaleDB -certified: - - "@flowfuse-certified-nodes/redis" -certifiedTitle: "A certified node for these databases" -certifiedIntro: > - The guides in this section use community packages. One database here is also - covered by a FlowFuse certified node, maintained for production use: ---- - -# {{ meta.title }} - -Node-RED is highly versatile and can be set up to work with a variety of databases, whether it is SQL (e.g., PostgreSQL, MySQL), NoSQL (e.g., MongoDB), or time-series databases (e.g., InfluxDB). This flexibility allows you to store and manage IoT data effectively, enabling the creation of interactive and data-driven applications for IoT environments. - -## Resources - -Here are some resources to help you get started with Node-RED on diffrent types of databases. Each guide provides step-by-step instructions to help you get started, along with advanced techniques for optimizing performance and handling complex data operations.: - -{% include "navigation-items-list.njk" %} - -{% include "components/certified-node-callout.liquid" %} diff --git a/src/node-red/flowfuse/ai/index.md b/src/node-red/flowfuse/ai/index.md deleted file mode 100644 index 6aaefb68d1..0000000000 --- a/src/node-red/flowfuse/ai/index.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -eleventyNavigation: - key: AI - parent: FlowFuse Nodes - order: 1 -meta: - title: FlowFuse AI Nodes - description: A set of Node-RED nodes for AI and machine learning, including ONNX model inference and LLM nodes for OpenAI, Anthropic, Google Gemini, and Ollama. ---- - -# {{ meta.title }} - -The **FlowFuse AI** Nodes package adds AI capabilities to Node-RED. It includes nodes for running local ONNX models for image classification, object detection, depth estimation, and custom inference, as well as LLM nodes for sending text prompts to hosted and local large language models from OpenAI, Anthropic, Google Gemini, and Ollama. - -## Nodes - -{% include "navigation-items-list.njk" %} - diff --git a/src/node-red/flowfuse/edge/index.md b/src/node-red/flowfuse/edge/index.md deleted file mode 100644 index 53788dd161..0000000000 --- a/src/node-red/flowfuse/edge/index.md +++ /dev/null @@ -1,34 +0,0 @@ ---- -eleventyNavigation: - key: Edge - parent: FlowFuse Nodes - order: 4 - title: Edge -meta: - title: Edge Certified Nodes - description: Documentation for FlowFuse Edge Certified Nodes, including nodes for connecting FlowFuse to industrial protocols, PLCs, SCADA systems, and factory-floor equipment. ---- - -# {{ meta.title }} - -This section contains documentation for **FlowFuse Edge Certified Nodes** that connect your FlowFuse instances to industrial protocols, PLCs, SCADA systems, and factory-floor equipment. - -FlowFuse Certified Nodes are packages that FlowFuse has vetted for quality, security, and support, and maintains on an ongoing basis. To learn more about what certification means and how these nodes are delivered, [read the FlowFuse Certified Nodes blog post](/blog/2025/07/certified-nodes-v2/). - -{% note %} -The FlowFuse Edge Certified Nodes catalogue is part of the **FlowFuse Edge** offering. [Contact us](/contact-us/) to get access or to learn more. -{% endnote %} - -{% note %} -This section is expanding. We are actively working to bring more Edge Certified Nodes to FlowFuse, and additional documentation will be added here over time. -{% endnote %} - -## Nodes - -This section lists the **Edge Certified Nodes** documented in FlowFuse: - -{% include "navigation-items-list.njk" %} - -{% note %} -Newly installed nodes are picked up automatically, no restart needed. Restart is only required when you update a node that's already installed: restart any remote instance or hosted instance running the previous version. -{% endnote %} diff --git a/src/node-red/flowfuse/index.md b/src/node-red/flowfuse/index.md deleted file mode 100644 index f615d3bc13..0000000000 --- a/src/node-red/flowfuse/index.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -eleventyNavigation: - key: FlowFuse Nodes - order: 5 -meta: - title: FlowFuse Nodes - description: An overview of the Node-RED nodes provided by FlowFuse, explaining their purpose, usage, and benefits for building integrations and automations. ---- - -# {{ meta.title }} - -In Node-RED on the FlowFuse platform, you have access to additional Node-RED nodes that are provided by FlowFuse. These are documented in the below sections as a reference you can use when building integrations and automations. - -## Nodes - -The following documents provide details about the FlowFuse nodes: - -{% include "navigation-items-list.njk" %} diff --git a/src/node-red/getting-started/index.md b/src/node-red/getting-started/index.md deleted file mode 100644 index c43ecd44b8..0000000000 --- a/src/node-red/getting-started/index.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -eleventyNavigation: - key: Getting Started - order: 1 -meta: - title: Getting Started with Node-RED - description: Learn the basics of Node-RED, a powerful tool for IoT integration and workflow automation. - keywords: node-red, node-red basics, node-red foundation, node-red editor ---- - -# {{ meta.title }} - -This section provides an overview of Node-RED, a robust visual tool designed for integrating IoT devices and automating workflows without needing extensive programming knowledge. - -{% include "navigation-items-list.njk" %} \ No newline at end of file diff --git a/src/node-red/getting-started/programming/index.md b/src/node-red/getting-started/programming/index.md deleted file mode 100644 index cd2cd2be1a..0000000000 --- a/src/node-red/getting-started/programming/index.md +++ /dev/null @@ -1,20 +0,0 @@ ---- -eleventyNavigation: - key: Programming - order: 7 - parent: Getting Started -meta: - title: Node-RED Programming - description: Master Node-RED programming fundamentals including flows, nodes, messages, conditional logic, and data manipulation. Learn essential concepts for building sophisticated visual programming solutions. - keywords: Node-RED programming, visual programming, Node-RED flows, Node-RED nodes, message passing, conditional logic, data manipulation, flow-based programming, Node-RED tutorial, IoT programming ---- - -# {{ meta.title }} - -Learn the core programming concepts you'll use every day in Node-RED. This section covers the fundamental building blocks that will help you create more sophisticated and reliable flows. - -## What's Covered - -Programming in Node-RED means working with flows, nodes, and messages. Even though you're working visually, you'll still need to understand key programming concepts like conditional logic, loops, and data manipulation. This section teaches you how to implement these concepts using Node-RED's visual approach. - -{% include "navigation-items-list.njk" %} diff --git a/src/node-red/hardware/armxy-bl340.md b/src/node-red/hardware/armxy-bl340.md deleted file mode 100644 index 540c7b3739..0000000000 --- a/src/node-red/hardware/armxy-bl340.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -eleventyNavigation: - key: BLIIOT ARMxy BL340 - parent: Hardware -meta: - title: Setting Node-RED on BLIIOT ARMxy BL340 - description: Guide to setting up Node-RED on BLIIOT ARMxy BL340, including installation and configuration steps. - keywords: node-red, flowfuse, BLIIOT ARMxy BL340 -image: "/node-red/hardware/images/armxy-bl340.jpg" -specifications: - Model: BLIIoT ARMxy BL340 Series - RAM_MB: 2048 - Processor: ARM Cortex-A53 - GPU: G31 MP2 - IO Points: Optional (GPIO, RS485,CAN,RS232,DI/DO/AI/AO etc.,) - Connectivity: Dual-band Wi-Fi 6, Bluetooth 5.2, Gigabit Ethernet, 4x USB 3.0, 1x USB-C - Clock Speed: Up to 1.4 GHz - Storage: SD, SDHC and SDXC(UHS-I) card - Display Output: HDMI 2.0, DisplayPort - Power Supply: 9~36V DC -layout: layouts/hardware.njk ---- - -The BLIIOT ARMxy BL340 is a high-performance single-board computer designed for demanding applications, including edge computing, automation, and embedded systems. Featuring an octa-core ARM processor, advanced connectivity options, and support for high-speed storage, it provides a powerful platform for developers and engineers. - -Integrating this powerful hardware with FlowFuse not only enhances its capabilities but also simplifies the management and deployment process. - -{% note %} -Exciting Update! FlowFuse is now available as three different products: [FlowFuse Edge for OT teams](/product/edge/), [FlowFuse Hub for IT teams](/product/hub/), and [FlowFuse Fleet](/product/fleet/) for managing devices at scale. Visit the [FlowFuse Product Page](/product/) to learn how the platform enables you to build and manage industrial apps at scale. -{% endnote %} - -## Prerequisites - -Before proceeding with the installation, ensure you have the following: - -- **BLIIOT ARMxy BL340** – A functioning device with internet access. -- **FlowFuse Account** - Ensure you have a FlowFuse account. If not, you can create a free account that allows you to manage up to two edge devices for free. For more information, refer to [FlowFuse Free Tier](/blog/2024/12/flowfuse-release-2-12/) -- **Sudo Privileges** – Administrator access to install required packages. - -## Getting Started - -This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the BLIIOT ARMxy BL340, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. - -{% include "hardware/system/debian-ff-install.md" %} - -{% include "hardware/device-registration.md" %} - -!["Status of the BLIIOT ARMxy BL340 remote instance in FlowFuse, showing its connection and operational state"](./images/status-flowfuse.png "Status of the BLIIOT ARMxy BL340 remote instance in FlowFuse, showing its connection and operational state"){data-zoomable} - -Now, when your device reboots, the FlowFuse Device Agent will automatically start, ensuring that your BLIIOT ARMxy BL340 remains connected to the FlowFuse platform. - -{% include "hardware/accessing-node-red-editor.md" %} diff --git a/src/node-red/hardware/index.md b/src/node-red/hardware/index.md deleted file mode 100644 index e0f9335c52..0000000000 --- a/src/node-red/hardware/index.md +++ /dev/null @@ -1,21 +0,0 @@ ---- -eleventyNavigation: - key: Hardware - order: 6 -meta: - title: Setting Up Node-RED on Different Hardware - description: Learn how to set up Node-RED on various hardware platforms, such as Raspberry Pi, Arduino, and more. - keywords: node-red, node-red hardware setup, raspberry pi, arduino, siemens iot2050, ---- - -# {{ meta.title }} - -Node-RED is highly versatile and can be set up on a wide range of hardware devices, including popular choices like Raspberry Pi, Ardiuno, Siemens 2050, and more. This flexibility allows you to connect Node-RED to diverse devices and sensors, which enables the creation of interactive and automated systems. - -By setting up Node-RED on different hardware, you can easily integrate physical inputs with digital systems. This capability is essential for IoT (Internet of Things) applications, where data from sensors and devices can trigger actions or responses in real-time. - -## Resources - -Here are some resources to help you get started with Node-RED on diffrent hardware devices: - -{% include "navigation-items-list.njk" %} diff --git a/src/node-red/hardware/raspberry-pi-4.md b/src/node-red/hardware/raspberry-pi-4.md deleted file mode 100644 index ddebd20bfc..0000000000 --- a/src/node-red/hardware/raspberry-pi-4.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -eleventyNavigation: - key: Raspberry Pi 4 - parent: Hardware -meta: - title: Setting Node-RED on Raspberry Pi 4 - description: Learn how to install the FlowFuse Edge Agent on the Raspberry Pi 4 effortlessly. Manage your device with Node-RED through FlowFuse with ease. - keywords: node-red, flowfuse, raspberry pi, raspberry pi 4 -image: "/node-red/hardware/images/raspberry-pi-4-b.png" -specifications: - Model: Raspberry Pi 4 B 8GB - RAM_MB: 8192 - Processor: Broadcom BCM2711, ARM Cortex-A72 (ARMv8-A), 4 (Quad-core) - GPIO: (Fully backwar ds-compatible with previous boards), Standard 40-pin GPIO Header - Connectivity: Dual-band Wi-Fi, Bluetooth 5.0, Gigabit Ethernet, 2x USB 3.0, 2x USB 2.0 - Clock Speed: 1.5 GHz - Storage: microSD -layout: layouts/hardware.njk ---- - -## Raspberry Pi OS Installation - -To set up your Raspberry Pi 4 for use with Node-RED and FlowFuse, follow these steps: - -### Flashing Raspberry Pi OS - -1. Use the [official Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the 64-bit version of Raspberry Pi OS to an SD card. - -![Flash Raspberry Pi OS on an SD-card](./images/raspberry-pi-5-flash-os.png) - -2. Before writing to the SD card, configure the OS for headless mode, including Wi-Fi, SSH, and authentication settings. - -![Configure RPi OS before flashing](./images/raspberry-pi-5-config-before-flash.png) - -3. Write the OS and configuration to the SD card. This process takes about 10 minutes. - -4. Insert the SD card into the Raspberry Pi 4 and power it on. The device should appear on your network after a minute or so. - -5. Connect to the Raspberry Pi using SSH: - - ```sh - ssh pi@raspberrypi.local - ``` - -## Getting Started - -This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the Raspberry Pi 4, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. - -{% include "hardware/system/debian-ff-install.md" %} - -{% include "hardware/device-registration.md" %} - -![Status of the remote instance in FlowFuse, showing its connection and operational state](./images/raspberry-pi-4.png "Status of the remote instance in FlowFuse, showing its connection and operational state"){data-zoomable} - -{% include "hardware/accessing-node-red-editor.md" %} \ No newline at end of file diff --git a/src/node-red/hardware/raspberry-pi-5.md b/src/node-red/hardware/raspberry-pi-5.md deleted file mode 100644 index 0a532a4a0e..0000000000 --- a/src/node-red/hardware/raspberry-pi-5.md +++ /dev/null @@ -1,55 +0,0 @@ ---- -eleventyNavigation: - key: Raspberry Pi 5 - parent: Hardware -meta: - title: Setting Up Node-RED on Raspberry Pi 5 - description: Learn how to install the FlowFuse Edge Agent on the Raspberry Pi 5 effortlessly. Manage your device with Node-RED through FlowFuse with ease. - keywords: node-red, flowfuse, raspberry pi, raspberry pi 5 -image: "/node-red/hardware/images/raspberrypi-5.png" -specifications: - Model: Raspberry Pi 5 Model 8GB - RAM_MB: 8192 - Processor: Broadcom BCM2712, ARM Cortex-A76 (ARMv8.2-A), 4 (Quad-core) - GPIO: Standard 40-pin GPIO Header - Connectivity: 2 × USB 2.0 Ports, 2 × USB 3.0 Ports, Bluetooth 5.0, USB-C, Wi-Fi + Bluetooth® Low Energy - Clock Speed: 2.4 GHz - Storage: microSD -layout: layouts/hardware.njk ---- - -## Raspberry Pi OS Installation - -To set up your Raspberry Pi 5 for use with Node-RED and FlowFuse, follow these steps: - -### Flashing Raspberry Pi OS - -1. Use the [official Raspberry Pi Imager](https://www.raspberrypi.com/software/) to flash the 64-bit version of Raspberry Pi OS to an SD card. - -![Flash Raspberry Pi OS on an SD-card](./images/raspberry-pi-5-flash-os.png) - -2. Before writing to the SD card, configure the OS for headless mode, including Wi-Fi, SSH, and authentication settings. - -![Configure RPi OS before flashing](./images/raspberry-pi-5-config-before-flash.png) - -3. Write the OS and configuration to the SD card. This process takes about 10 minutes. - -4. Insert the SD card into the Raspberry Pi 5 and power it on. The device should appear on your network after a minute or so. - -5. Connect to the Raspberry Pi using SSH: - - ```sh - ssh pi@raspberrypi.local - ``` - -## Getting Started - -This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the Raspberry Pi 5, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. - -{% include "hardware/system/debian-ff-install.md" %} - -{% include "hardware/device-registration.md" %} - -![Status of the remote instance in FlowFuse, showing its connection and operational state](./images/raspberry-pi-5.png "Status of the remote instance in FlowFuse, showing its connection and operational state."){data-zoomable} - -{% include "hardware/accessing-node-red-editor.md" %} \ No newline at end of file diff --git a/src/node-red/hardware/robustel-eg5120.md b/src/node-red/hardware/robustel-eg5120.md deleted file mode 100644 index 64bbb7a35c..0000000000 --- a/src/node-red/hardware/robustel-eg5120.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -eleventyNavigation: - key: Robustel EG5120 - parent: Hardware -meta: - title: Setting Node-RED on Robustel EG5120 - description: In this guide, we will discuss how to install FlowFuse Device agent on Robustel EG5120. - keywords: node-red, flowfuse, robustel eg5120 -image: "/node-red/hardware/images/robustel-eg5120.png" -specifications: - Model: Robustel EG5120 - RAM_MB: 2048 - Processor: Broadcom BCM2711, ARM Cortex-A72 (ARMv8-A), 4 (Quad-core) - GPIO: Standard 40-pin GPIO Header - Connectivity: Dual-band Wi-Fi, Bluetooth 5.0, Gigabit Ethernet, 2x USB 3.0, 2x USB 2.0 - Clock Speed: 1.5 GHz - Storage: microSD -layout: layouts/hardware.njk ---- - -The [Robustel EG5120](https://www.robustel.com/product/eg5120-industrial-edge-computing-gateway/) is a versatile gateway that facilitates robust connectivity for industrial IoT applications. Integrating this powerful hardware with FlowFuse not only enhances its capabilities but also simplifies the management and deployment process. In this documentation, we’ll walk through the steps to integrate the Robustel EG5120 with FlowFuse. - -The [Robustel EG5120](https://www.robustel.com/product/eg5120-industrial-edge-computing-gateway/), equipped with Linux-based Debian 11 supporting a wide variety of programming languages including Node.js, offers robust connectivity options. When combined with FlowFuse, this gateway becomes even more powerful, enabling seamless device management and deployment. - -The Robustel EG5120 supports multiple connectivity options including Ethernet, Wi-Fi, and cellular networks, which are essential for flexible deployments in various industrial scenarios. Its built-in support for Bluetooth, cellular connectivity, RS232, RS485, and Modbus facilitates seamless integration with a wide array of IoT devices and services. This blog will guide you through using FlowFuse to effectively manage your Node-RED instance, enhancing both the security and scalability of your IoT applications. - -## Getting Started - -This guide explores how to install and run Node-RED through the FlowFuse Device Agent on the Robustel EG5120, enabling you to build, manage, and scale Node-RED flows efficiently from a remote location. - -{% include "hardware/system/debian-ff-install.md" %} - -{% include "hardware/device-registration.md" %} - -{% include "hardware/accessing-node-red-editor.md" %} \ No newline at end of file diff --git a/src/node-red/index.njk b/src/node-red/index.njk index 431edba5dc..6c0f024e98 100644 --- a/src/node-red/index.njk +++ b/src/node-red/index.njk @@ -310,13 +310,13 @@ sitemapPriority: 0.8

    Node-RED Academy | - Core Nodes + Core Nodes | Variables | Quick Tips

    - SEE MORE + SEE MORE GET IMPLEMENTATION SUPPORT diff --git a/src/node-red/integration-technologies/index.md b/src/node-red/integration-technologies/index.md deleted file mode 100644 index a42dba234b..0000000000 --- a/src/node-red/integration-technologies/index.md +++ /dev/null @@ -1,22 +0,0 @@ ---- -metaTitle: "Building Applications with Node-RED" -eleventyNavigation: - key: "Integration Technologies" - order: 10 -meta: - title: Using Different Technologies for Building Applications with Node-RED. - description: Learn how to leverage various integration technologies with Node-RED for building robust and interconnected applications. - keywords: node-red, flowfuse, node-red integration, integration technologies, webhook, rest api ---- - -# {{meta.title}} - -Developing powerful and scalable applications frequently necessitates the integration of various technologies. This integration is crucial for creating seamless and efficient systems that can handle complex tasks and large volumes of data. Whether you're working with REST APIs to enable communication between different services or implementing GraphQL for more efficient data querying, the ability to blend these technologies effectively is essential for modern application development. - -Node-RED offers a versatile and robust platform to meet these needs. Its intuitive flow-based interface allows developers to easily design and deploy sophisticated workflows, which makes it an invaluable tool for integrating REST APIs, GraphQL, webhooks, and more. - -## Resources - -Here are some resources to help you integrate Node-RED with various different technologies: - -{% include "navigation-items-list.njk" %} \ No newline at end of file diff --git a/src/node-red/learn.md b/src/node-red/learn.md deleted file mode 100644 index 664ab80f21..0000000000 --- a/src/node-red/learn.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -eleventyNavigation: - key: Node-RED Documentation - order: 0 -meta: - title: "Node-RED Documentation: 100+ Tutorials (2026)" - description: "Master Node-RED visual programming with comprehensive documentation covering core nodes, database integration, hardware setup, communication protocols, and real-world industrial IoT examples." - keywords: node-red, node red, documentation, guide, core nodes, database integration, hardware setup, protocols, industrial iot ---- - -# Node-RED Documentation: 100+ Tutorials (2026) - -[Node-RED](/node-red/), the low-code visual programming platform, is becoming increasingly popular for optimizing and automating industrial processes. Enabling the integration of different hardware devices, API services, and technologies, it's exceptionally suited for a variety of industrial IoT applications. - -This documentation provides comprehensive guidance for working with Node-RED, from foundational concepts to advanced implementations. You'll find detailed explanations of core nodes, terminology references, integration guides for databases and communication protocols, and platform-specific setup instructions for various hardware devices. - -## Common Terminology - -A good starting point is understanding the common terms used in Node-RED. You should be able to confidently navigate through most Node-RED-related resources and participate in Node-RED forum discussions. - -[Node-RED Common Terminologies](/node-red/terminology/) - -## Core Nodes - -This section includes detailed documentation for each Node-RED core node, from configuration to use in real-world applications with examples. - -[Core Nodes](/node-red/core-nodes/) - -## Hardware Devices - -In this section, you will find tutorials on setting up Node-RED on different hardware devices. Each tutorial covers configuration, practical demos and troubleshooting tips. - -[Setting Up Node-RED on Different Hardware](/node-red/hardware/) - -## Peripheral Devices - -This section includes step-by-step documentation on integrating Node-RED with various peripheral devices such as webcams, barcode scanners, printer, mic and more. - -[Integrating Node-RED with Peripheral Devices](/node-red/peripheral/) - -## Databases - -This section includes tutorials on how to use different databases with Node-RED. Each tutorial provides step-by-step instructions along with practical examples. We cover a variety of databases such as SQL, NoSQL, and time-series databases. - -[Node-RED Database Integration Guides](/node-red/database/) - -## Communication Protocols - -This section includes comprehensive tutorials on how to use different protocols with Node-RED for building applications, communicating with edge devices, and more. Each tutorial provides step-by-step instructions along with practical demos. - -[Using Different Protocols for Building Applications with Node-RED](/node-red/protocol/) - -## Integration Technologies - -In this section you will find tutorials on how to utilize various technologies with Node-RED, such as implementing webhooks for task automation, creating REST APIs, GraphQL integrations, and more. - -[Using Different Technologies for Building Applications with Node-RED](/node-red/integration-technologies/) - -## Notification Services - -In this section, we cover how to integrate various notification services with Node-RED to send real-time alerts and notifications. It includes step-by-step guides along with troubleshooting tips. - -[Notification Services in Node-RED](/node-red/notification/) diff --git a/src/node-red/node-red.json b/src/node-red/node-red.json deleted file mode 100644 index 5dfbbb1e3e..0000000000 --- a/src/node-red/node-red.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "layout": "layouts/documentation.njk", - "hideTitle": "true", - "nav": "node-red", - "searchTitle": "Node-RED", - "tags": [ - "learningResources" - ] -} diff --git a/src/node-red/notification/index.md b/src/node-red/notification/index.md deleted file mode 100644 index b54da184c0..0000000000 --- a/src/node-red/notification/index.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -eleventyNavigation: - key: "Notification Services" - order: 11 -meta: - title: Notification Services in Node-RED - description: Learn how to integrate various notification services with Node-RED for real-time alerts and messaging. - keywords: node-red, notification services, email notifications, Telegram notifications, Slack notifications, WhatsApp notifications ---- - -# Notification Services - -Real-time notifications are essential in our automation world, helping to keep us informed, responsive, and efficient. Whether it's a critical system alert, a customer inquiry, or a simple reminder, timely notifications can significantly enhance productivity and efficiency. However, managing and integrating such services can be challenging in traditional development environments. Node-RED simplifies this process by supporting a wide range of notification services, including email, Telegram, Slack, WhatsApp, and more. - -## Resources - -Here are some resources to help you get started with Node-RED on diffrent notification services: - -{% include "navigation-items-list.njk" %} \ No newline at end of file diff --git a/src/node-red/protocol/index.md b/src/node-red/protocol/index.md deleted file mode 100644 index 592ca23281..0000000000 --- a/src/node-red/protocol/index.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -metaTitle: "Using Different Protocols with Node-RED" -eleventyNavigation: - key: "Communication Protocols" - order: 9 -meta: - title: Using Different Protocols for Building Applications with Node-RED - description: Learn how to leverage various communication protocols with Node-RED for building robust and interconnected applications. - keywords: node-red, iot, mqtt, http, coap, websockets, diffrent protocols with node-red -certified: - - "@flowfuse-certified-nodes/cip-suite" - - "@flowfuse-certified-nodes/opcua" -certifiedTitle: "Certified nodes for these protocols" -certifiedIntro: > - Two protocols are also covered by a FlowFuse certified node, maintained for - production use. EtherNet/IP, for Rockwell and Allen-Bradley controllers, has no - page of its own in this section, and is documented with its certified node: ---- - -# {{meta.title}} - -In IoT development, effective communication between devices is essential. This communication is facilitated by various protocols like MQTT, HTTP, CoAP, and WebSockets. Each protocol brings its own set of strengths and is suited for different IoT scenarios. However, understanding which protocol is suited for what scenario and utilizing it can be quite difficult. That's why we have created this section of resources where you will find documentation on using different communication protocols with Node-RED. - -Node-RED, with its intuitive visual programming interface, simplifies the integration of these protocols. Whether you're publishing sensor data over MQTT, triggering HTTP requests, querying CoAP endpoints, or enabling real-time communication with WebSockets, Node-RED provides a flexible and powerful platform. - -## Resources - -Here are some resources to help you get started with integrating Node-RED with various communication protocols: - -{% include "navigation-items-list.njk" %} - -{% include "components/certified-node-callout.liquid" %} \ No newline at end of file diff --git a/src/webinars/2026/industrial-iot-solutions.md b/src/webinars/2026/industrial-iot-solutions.md index fa29e2fd4a..ea5875fd05 100644 --- a/src/webinars/2026/industrial-iot-solutions.md +++ b/src/webinars/2026/industrial-iot-solutions.md @@ -10,7 +10,7 @@ meta: Industrial IoT (IIoT) is the use of connected sensors and devices to collect data from industrial operations and feed it into software for monitoring and real-time action. FlowFuse is the platform that runs on top of that data: the flows that connect it, the dashboards that display it, and the governance that keeps it under control. - question: "What's the difference between FlowFuse Hub, Edge, and Fleet?" answer: > - Hub integrates enterprise systems and APIs for IT teams. Edge connects PLCs and machines across sites for OT teams. Fleet manages Node-RED across thousands of distributed devices. All three run on the same platform; the difference is which deployment shape each one is built for. + Hub integrates enterprise systems and APIs for IT teams. Edge connects PLCs and machines across sites for OT teams. Fleet manages Node-RED across thousands of distributed devices. All three run on the same platform; the difference is which deployment shape each one is built for. - question: "How do I know which industrial IoT solution is right for my setup?" answer: > It comes down to where your applications live: a data center (Hub), a plant floor (Edge), or thousands of field devices (Fleet). We'll walk through all three live in the webinar and help map your specific setup to one of them. Bring your architecture and we'll sort it live in the Q&A. @@ -44,7 +44,7 @@ Join Kristopher Sandoval (Product Marketing), Jamie Strusz (Engineering), Drew G ## Which industrial IoT solution fits my setup? -- FlowFuse Hub integrates [enterprise systems](/landing/enterprise-integration/) and [APIs](/node-red/core-nodes/http-request/), FlowFuse Edge connects [PLCs](/landing/plc/) and machines across sites, FlowFuse Fleet manages [Node-RED](/node-red/) across thousands of devices. We'll map your setup to one of the three, live. +- FlowFuse Hub integrates [enterprise systems](/landing/enterprise-integration/) and [APIs](/docs/node-red/core-nodes/network/http-request/), FlowFuse Edge connects [PLCs](/landing/plc/) and machines across sites, FlowFuse Fleet manages [Node-RED](/node-red/) across thousands of devices. We'll map your setup to one of the three, live. - Build faster with Industrial AI: [FlowFuse Expert](/docs/user/expert/) opens its [MCP server](/blog/2025/10/building-mcp-server-using-flowfuse/) to 3rd party agents, such as Cursor, Claude Code, Gemini CLI, and even in-house agents. Any MCP-speaking agent works. Point your AI agent at your FlowFuse instance and it builds, debugs, and fixes flows directly. - See what's running, everywhere: [role-based access control](/docs/user/role-based-access-control/), [audit logging](/docs/user/logs/#audit-log), and [one place to manage every instance and device](/blog/2024/10/managing-node-red-instances-in-centralize-platfrom/), no matter which product you run. - Read a [dashboard](/platform/dashboard/) at a glance: new [built-in themes](https://dashboard.flowfuse.com/nodes/config/ui-theme.html) with WCAG AA contrast, [charts](https://dashboard.flowfuse.com/nodes/widgets/ui-chart) that render instead of leaving empty boxes, and readable dates instead of raw timestamps. diff --git a/src/webinars/2026/modbus-communications-modernization.md b/src/webinars/2026/modbus-communications-modernization.md index d258127976..f9f28a80c8 100644 --- a/src/webinars/2026/modbus-communications-modernization.md +++ b/src/webinars/2026/modbus-communications-modernization.md @@ -44,7 +44,7 @@ This is the core of the session: a practical framework for deciding when to leav Around the halfway mark, Kristopher switches over to a live demo in FlowFuse and Node-RED: raw Modbus registers going in one end, and a named, typed, dashboarded data stream coming out the other. If you'd rather follow along with a written walkthrough afterward, our [Using Modbus with FlowFuse](/blog/2025/09/using-modbus-with-flowfuse/) guide covers the same setup step by step. -Also worth a look once you're in the recording: the live Q&A, where attendees bring in real issues, from Modbus TCP connections that quietly go blank to how much visibility you can actually get into what's on the bus. If any of that sounds familiar, our posts on [Modbus polling best practices](/blog/2026/04/modbus-polling-best-practices/) and [diagnosing Modbus degradation](/blog/2026/04/diagnosing-modbus-degradation/) dig further into the same failure modes. And if you're still weighing Modbus against the alternatives, [why OPC UA isn't replacing Modbus yet](/blog/2026/03/why-opcua-is-not-replacing-modbus-yet/) and [Modbus TCP vs. Modbus RTU](/blog/2026/02/modbus-tcp-vs-modbus-rtu/) are good next reads, and the [Modbus protocol page](/node-red/protocol/modbus/) has the full Node-RED node documentation whenever you're ready to build. +Also worth a look once you're in the recording: the live Q&A, where attendees bring in real issues, from Modbus TCP connections that quietly go blank to how much visibility you can actually get into what's on the bus. If any of that sounds familiar, our posts on [Modbus polling best practices](/blog/2026/04/modbus-polling-best-practices/) and [diagnosing Modbus degradation](/blog/2026/04/diagnosing-modbus-degradation/) dig further into the same failure modes. And if you're still weighing Modbus against the alternatives, [why OPC UA isn't replacing Modbus yet](/blog/2026/03/why-opcua-is-not-replacing-modbus-yet/) and [Modbus TCP vs. Modbus RTU](/blog/2026/02/modbus-tcp-vs-modbus-rtu/) are good next reads, and the [Modbus protocol page](/docs/node-red/protocol/modbus/) has the full Node-RED node documentation whenever you're ready to build. ## Who This Session Is For diff --git a/src/webinars/2026/the-agile-factory-on-the-connected-edge.md b/src/webinars/2026/the-agile-factory-on-the-connected-edge.md index bee86babe4..f93dd1dd80 100644 --- a/src/webinars/2026/the-agile-factory-on-the-connected-edge.md +++ b/src/webinars/2026/the-agile-factory-on-the-connected-edge.md @@ -25,8 +25,8 @@ In this session, Rhythm Agarwal (Senior Manufacturing Test Engineer at Aperia) a ## How Aperia Replaced Traditional PLCs with Edge Applications - **Extends the shop floor, doesn't replace it.** Smart fixtures run on FlowFuse instead of a PLC, so existing equipment keeps working, just connected. -- **Speaks every protocol.** Sensors, scanners, and actuators connect over [MODBUS-TCP](/node-red/protocol/modbus/), [RS-232](/blog/2025/07/connect-legacy-equipment-serial-flowfuse/), and [raw TCP](/node-red/core-nodes/tcp-in/), all feeding into one data flow. -- **Logs data the moment it happens.** Every station writes to a shared [MongoDB](/node-red/database/mongodb/) backend in real time, not on a delay. +- **Speaks every protocol.** Sensors, scanners, and actuators connect over [MODBUS-TCP](/docs/node-red/protocol/modbus/), [RS-232](/blog/2025/07/connect-legacy-equipment-serial-flowfuse/), and [raw TCP](/docs/node-red/core-nodes/network/tcp-in/), all feeding into one data flow. +- **Logs data the moment it happens.** Every station writes to a shared [MongoDB](/docs/node-red/database/mongodb/) backend in real time, not on a delay. - **One fix, applied everywhere.** Code changes roll out to entire device groups from a single pipeline, with full version control, not machine by machine. ## Three Manufacturing Applications Built on the Edge