[{"data":1,"prerenderedAt":29043},["ShallowReactive",2],{"blog-\u002Fblog\u002Fhow-to\u002F2":3,"blog-all-for-related":4,"blog-all-how-to":5},null,[],[6,969,2058,5316,6567,7447,7891,8493,9076,9327,9835,11074,11466,11934,12583,13098,13633,14964,16476,16742,17337,17587,17897,18043,18142,18547,19609,19908,21332,22208,24052,24732,24940,25205,25531,25765,26102,26268,26435,26771,26960,27106,27214,27292,27481,27741,28118,28224,28538,28663],{"id":7,"title":8,"authors":9,"body":11,"cta":940,"date":944,"description":945,"extension":946,"image":947,"lastUpdated":948,"meta":949,"navigation":253,"path":959,"seo":960,"sitemap":961,"stem":962,"subtitle":963,"tags":964,"tldr":967,"video":3,"__hash__":968},"blog\u002Fblog\u002F2025\u002F12\u002Fnode-red-buffer-parser-industrial-data.md","Node-RED Buffer Parser Guide: Decode Modbus and Industrial Device Data (2026)",[10],"sumit-shinde",{"type":12,"value":13,"toc":927},"minimark",[14,27,30,33,38,41,51,54,57,68,75,78,81,84,88,91,96,99,121,124,127,135,138,294,297,300,314,318,325,358,362,365,389,399,424,428,431,439,445,448,457,463,472,478,481,485,488,501,504,577,582,590,594,597,656,664,672,682,686,689,703,706,755,758,780,795,818,821,824,834,838,841,851,865,874,878,881,884,890,900,903,907,910,913,923],[15,16,17,18,22,23,26],"p",{},"Legacy industrial devices communicate in bytes. Your temperature sensor doesn't send you ",[19,20,21],"code",{},"{\"temp\": 23.5}"," - it sends you ",[19,24,25],{},"[1, 3, 4, 1, 44, 0, 200, 190, 125]",". Those numbers are meaningless unless you know how to decode them.",[15,28,29],{},"This is what makes working with legacy PLCs and Modbus sensors challenging. You're not dealing with modern APIs that return JSON. You're dealing with raw binary data where byte 3 might be temperature and byte 4 might be humidity, and if you read them in the wrong order, everything breaks.",[15,31,32],{},"Node-RED's Buffer Parser node solves this problem. Instead of writing JavaScript to manually decode every buffer, you configure it once visually and it handles the conversion automatically. In this article, we'll learn how to use this node effectively.",[34,35,37],"h2",{"id":36},"understanding-buffer-data","Understanding Buffer Data",[15,39,40],{},"When your Modbus sensor responds to a query, Node-RED shows you something like this in the debug panel:",[42,43,48],"pre",{"className":44,"code":46,"language":47},[45],"language-text","[1, 3, 4, 1, 44, 0, 200, 190, 125]\n","text",[19,49,46],{"__ignoreMap":50},"",[15,52,53],{},"Nine bytes. Each one is a number between 0 and 255. This data is completely meaningless without context.",[15,55,56],{},"Byte 0 might be a device address. Bytes 3 and 4 together might encode temperature. But the buffer itself doesn't tell you any of this - you need the device manual to figure out what goes where.",[15,58,59,60,63,64,67],{},"Take bytes 3 and 4: ",[19,61,62],{},"1"," and ",[19,65,66],{},"44",". If you're supposed to read them as a 16-bit integer, that's either 300 or 11265 depending on byte order. If the device uses a scale factor of 10, the actual temperature could be 30.0°C or 1126.5°C. Get any part of this wrong and your dashboard shows nonsense.",[15,69,70,71,74],{},"So why do devices work this way? Why not just send ",[19,72,73],{},"{\"temperature\": 30.0}","?",[15,76,77],{},"That JSON message takes 21 bytes. The same information in binary takes 2 bytes. When you're on a serial connection running at 9600 baud, sending thousands of sensor readings per day, those extra bytes add up fast.",[15,79,80],{},"More importantly, this is how industrial hardware has worked since 1979. Modbus was designed when PLCs had kilobytes of memory. Modern factories still run equipment from the 90s. The protocol isn't going to change because it would be more convenient for us.",[15,82,83],{},"You work with what's on the factory floor, this means you need to parse buffers.",[34,85,87],{"id":86},"example-temperature-and-humidity-sensor","Example: Temperature and Humidity Sensor",[15,89,90],{},"Suppose you have a temperature and humidity sensor connected via Modbus RTU. When you query it for data, you get this buffer:",[42,92,94],{"className":93,"code":46,"language":47},[45],[19,95,46],{"__ignoreMap":50},[15,97,98],{},"The device manual says this buffer breaks down as:",[100,101,102,106,109,112,115,118],"ul",{},[103,104,105],"li",{},"Byte 0: Device address (1)",[103,107,108],{},"Byte 1: Modbus function code (3 = read holding registers)",[103,110,111],{},"Byte 2: Data length in bytes (4)",[103,113,114],{},"Bytes 3-4: Temperature reading",[103,116,117],{},"Bytes 5-6: Humidity reading",[103,119,120],{},"Bytes 7-8: CRC checksum",[15,122,123],{},"You need to extract temperature and humidity. Everything else is Modbus protocol overhead.",[15,125,126],{},"The manual also specifies:",[100,128,129,132],{},[103,130,131],{},"Temperature: 16-bit unsigned integer, big-endian, divide by 10 for actual value in °C",[103,133,134],{},"Humidity: 16-bit unsigned integer, big-endian, divide by 10 for actual value in %RH",[15,136,137],{},"Without Buffer Parser, you'd write a function node like this:",[42,139,143],{"className":140,"code":141,"language":142,"meta":50,"style":50},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const buffer = Buffer.from(msg.payload);\nconst temperature = buffer.readUInt16BE(3) \u002F 10;\nconst humidity = buffer.readUInt16BE(5) \u002F 10;\n\nmsg.payload = { temperature, humidity };\nreturn msg;\n","javascript",[19,144,145,183,219,248,255,282],{"__ignoreMap":50},[146,147,150,154,158,162,165,168,172,175,177,180],"span",{"class":148,"line":149},"line",1,[146,151,153],{"class":152},"spNyl","const",[146,155,157],{"class":156},"sTEyZ"," buffer ",[146,159,161],{"class":160},"sMK4o","=",[146,163,164],{"class":156}," Buffer",[146,166,167],{"class":160},".",[146,169,171],{"class":170},"s2Zo4","from",[146,173,174],{"class":156},"(msg",[146,176,167],{"class":160},[146,178,179],{"class":156},"payload)",[146,181,182],{"class":160},";\n",[146,184,186,188,191,193,196,198,201,204,208,211,214,217],{"class":148,"line":185},2,[146,187,153],{"class":152},[146,189,190],{"class":156}," temperature ",[146,192,161],{"class":160},[146,194,195],{"class":156}," buffer",[146,197,167],{"class":160},[146,199,200],{"class":170},"readUInt16BE",[146,202,203],{"class":156},"(",[146,205,207],{"class":206},"sbssI","3",[146,209,210],{"class":156},") ",[146,212,213],{"class":160},"\u002F",[146,215,216],{"class":206}," 10",[146,218,182],{"class":160},[146,220,222,224,227,229,231,233,235,237,240,242,244,246],{"class":148,"line":221},3,[146,223,153],{"class":152},[146,225,226],{"class":156}," humidity ",[146,228,161],{"class":160},[146,230,195],{"class":156},[146,232,167],{"class":160},[146,234,200],{"class":170},[146,236,203],{"class":156},[146,238,239],{"class":206},"5",[146,241,210],{"class":156},[146,243,213],{"class":160},[146,245,216],{"class":206},[146,247,182],{"class":160},[146,249,251],{"class":148,"line":250},4,[146,252,254],{"emptyLinePlaceholder":253},true,"\n",[146,256,258,261,263,266,268,271,274,277,279],{"class":148,"line":257},5,[146,259,260],{"class":156},"msg",[146,262,167],{"class":160},[146,264,265],{"class":156},"payload ",[146,267,161],{"class":160},[146,269,270],{"class":160}," {",[146,272,273],{"class":156}," temperature",[146,275,276],{"class":160},",",[146,278,226],{"class":156},[146,280,281],{"class":160},"};\n",[146,283,285,289,292],{"class":148,"line":284},6,[146,286,288],{"class":287},"s7zQu","return",[146,290,291],{"class":156}," msg",[146,293,182],{"class":160},[15,295,296],{},"This works, but it's also fragile. When you need to add a pressure reading next month, you're editing code and hoping you don't break the offset calculations. When someone else looks at this flow, they have no idea what's happening without reading the function and if they don't understand JavaScript, they're stuck.",[15,298,299],{},"Buffer Parser turns this into configuration you can see and modify without touching code. Let's walk through a practical example.",[301,302,303],"blockquote",{},[15,304,305,306,313],{},"But before you start, make sure you have a Node-RED instance running on your edge device. The fastest, easiest, and most production-ready way is using FlowFuse. If you don't have an account yet, create one with our ",[307,308,312],"a",{"href":309,"rel":310},"https:\u002F\u002Fapp.flowfuse.com\u002F",[311],"nofollow","free trial",". FlowFuse simplifies managing remote instances and provides the ability to create hosted instances, no matter how many you need to manage, it makes it easy. There are no deployment headaches either; everything is managed by FlowFuse with built-in security. You'll also get access to tools such as DevOps pipelines, snapshots for recovery, audit logs, real-time collaboration with granular role-based access control (RBAC), and much more.",[34,315,317],{"id":316},"setting-up-the-buffer-parser","Setting Up the Buffer Parser",[15,319,320,321,324],{},"The Buffer Parser node is part of the ",[19,322,323],{},"node-red-contrib-buffer-parser"," package. To install it:",[326,327,328,331,334,341,348,353],"ol",{},[103,329,330],{},"Open your Node-RED editor",[103,332,333],{},"Click the menu icon (three horizontal lines) in the top-right corner",[103,335,336,337],{},"Select ",[338,339,340],"strong",{},"Manage palette",[103,342,343,344,347],{},"Go to the ",[338,345,346],{},"Install"," tab",[103,349,350,351],{},"Search for ",[19,352,323],{},[103,354,355,356],{},"Click ",[338,357,346],{},[34,359,361],{"id":360},"building-the-flow","Building the Flow",[15,363,364],{},"Now let's create a flow to parse the buffer data. For this example, we'll use an Inject node to simulate Modbus data, so while reading you can follow along and learn.",[326,366,367,374,377,384],{},[103,368,369,370,373],{},"Drag an ",[338,371,372],{},"Inject"," node from the palette onto the canvas",[103,375,376],{},"Double-click the Inject node to open its configuration",[103,378,379,380,383],{},"Set ",[338,381,382],{},"msg.payload"," to \"Buffer\" from the dropdown",[103,385,386,387],{},"In the field, enter: ",[19,388,25],{},[15,390,391,396],{},[392,393],"img",{"alt":394,"dataZoomable":50,"src":395},"Node-RED Inject node configuration showing payload set to a Buffer array for simulating data.","\u002Fblog\u002F2025\u002F12\u002Fimages\u002Fsimulate-data-inject.png",[397,398,394],"em",{},[326,400,401,406,412,418,421],{"start":257},[103,402,355,403],{},[338,404,405],{},"Done",[103,407,408,409,373],{},"Drag a ",[338,410,411],{},"Buffer Parser",[103,413,408,414,417],{},[338,415,416],{},"Debug"," node onto the canvas",[103,419,420],{},"Connect the Inject node output to the Buffer Parser node input",[103,422,423],{},"Connect the Buffer Parser node output to the Debug node input",[34,425,427],{"id":426},"configuring-the-buffer-parser","Configuring the Buffer Parser",[15,429,430],{},"Now let's configure the Buffer Parser node to extract temperature and humidity.",[326,432,433],{},[103,434,435,436,438],{},"Double-click the ",[338,437,411],{}," node to open its configuration panel",[15,440,441],{},[392,442],{"alt":443,"dataZoomable":50,"src":444},"Buffer Parser Configuration","\u002Fblog\u002F2025\u002F12\u002Fimages\u002Fbuffer-parser.png",[15,446,447],{},"You'll see several fields. Most of them you can ignore for basic parsing. Here's what matters:",[15,449,450,453,454,456],{},[338,451,452],{},"Property",": Leave this set to ",[19,455,382],{},". This tells the node where to find your buffer data.",[15,458,459,462],{},[338,460,461],{},"Specification:"," Keep this set to UI Specification. This is the visual method, allowing you to configure everything directly within the node.",[15,464,465,468,469,167],{},[338,466,467],{},"Result Type",": Set this to \"Key\u002Fvalue\". This gives you clean JSON output like ",[19,470,471],{},"{\"temperature\": 30.0, \"humidity\": 20.0}",[15,473,474,477],{},[338,475,476],{},"Byte Swap",": Leave this set to \"No swap\" for now. Byte swapping reorders bytes within multi-byte values and is only needed when your device stores data in an unusual format that doesn't match standard big-endian or little-endian conventions. If you've selected the correct endianness (like uint16be or uint16le) and values still look wrong, you might need swap16 (for 16-bit values), swap32 (for 32-bit values), or swap64 (for 64-bit values). Most devices won't require this.",[15,479,480],{},"Now let's configure the actual data extraction.",[34,482,484],{"id":483},"extracting-temperature","Extracting Temperature",[15,486,487],{},"Let's add the first field to extract temperature from bytes 3-4.",[326,489,490],{},[103,491,492,493,496,497,500],{},"Click the ",[338,494,495],{},"add"," button at the bottom of the configuration panel to create a new row in ",[338,498,499],{},"buffer parser"," node",[15,502,503],{},"You'll see several fields appear in the row. Let's fill them in:",[326,505,506,517,548,556,564],{"start":185},[103,507,508,509,512,513,516],{},"In the ",[338,510,511],{},"Name"," field, enter ",[19,514,515],{},"temperature",". This is what you'll see in your output JSON.",[103,518,508,519,522,523,526],{},[338,520,521],{},"Type"," dropdown, select ",[19,524,525],{},"uint16be",[100,527,528,545],{},[103,529,530,531],{},"This breaks down as:\n",[100,532,533,539],{},[103,534,535,538],{},[19,536,537],{},"uint16"," = 16-bit unsigned integer (positive values only)",[103,540,541,544],{},[19,542,543],{},"be"," = big-endian (reads bytes in order: first byte is high, second is low)",[103,546,547],{},"The manual specified \"16-bit unsigned integer, big-endian\" so this is a direct match",[103,549,508,550,512,553,555],{},[338,551,552],{},"Offset",[19,554,207],{},". Temperature data starts at byte 3. Offset counts from zero, so byte 0 is first, byte 3 is fourth.",[103,557,508,558,512,561,563],{},[338,559,560],{},"Length",[19,562,62],{},". We're reading one value, not an array of values. Length stays at 1 for single values.",[103,565,508,566,512,569,572,573,576],{},[338,567,568],{},"Scale",[19,570,571],{},"0.1",". Here's the important part: the Buffer Parser ",[338,574,575],{},"multiplies"," by the scale value, so to divide by 10, you need to multiply by 0.1. Raw value 300 × 0.1 = 30.0.",[301,578,579],{},[15,580,581],{},"Note: Beyond simple multiplication, the Buffer Parser also supports scale equations for more complex transformations. You can use operators like >> for bit shifting (e.g., >>4 to shift right 4 bits), + or - for offsets (e.g., +42 to add 42), \u002F or * for division\u002Fmultiplication, ** for exponents, and comparison operators like ==, !=, >, \u003C for boolean results. These equations are applied after any mask, giving you powerful options for handling bit-packed data or applying formulas without writing JavaScript.",[15,583,584,585,63,587,589],{},"Let's verify this works with a simple calculation. Bytes 3 and 4 in our buffer are ",[19,586,62],{},[19,588,66],{},". Big-endian means we read them in order: (1 × 256) + 44 = 300 in decimal. Multiplied by 0.1 gives 30.0°C. Perfect.",[34,591,593],{"id":592},"extracting-humidity","Extracting Humidity",[15,595,596],{},"Now let's add the second field to extract humidity from bytes 5-6.",[326,598,599,604,612,635,642,649],{},[103,600,492,601,603],{},[338,602,495],{}," button again to create a second row",[103,605,508,606,512,608,611],{},[338,607,511],{},[19,609,610],{},"humidity",". This will appear as the key in your output JSON.",[103,613,508,614,522,616,618],{},[338,615,521],{},[19,617,525],{},[100,619,620],{},[103,621,622,623],{},"Same as temperature:\n",[100,624,625,630],{},[103,626,627,629],{},[19,628,537],{}," = 16-bit unsigned integer (no negative values)",[103,631,632,634],{},[19,633,543],{}," = big-endian (reads bytes in order)",[103,636,508,637,512,639,641],{},[338,638,552],{},[19,640,239],{},". Humidity data starts at byte 5.",[103,643,508,644,512,646,648],{},[338,645,560],{},[19,647,62],{},". We're reading a single humidity value.",[103,650,508,651,512,653,655],{},[338,652,568],{},[19,654,571],{},". Again, multiply by 0.1 to effectively divide by 10.",[15,657,658,662],{},[392,659],{"alt":660,"dataZoomable":50,"src":661},"Buffer Parser configuration rows defining temperature and humidity extraction using uint16be with offsets and scale values.","\u002Fblog\u002F2025\u002F12\u002Fimages\u002Fbuffer-parser-temp-and-hum.png",[397,663,660],{},[326,665,667],{"start":666},7,[103,668,355,669,671],{},[338,670,405],{}," to save the configuration",[15,673,674,675,63,678,681],{},"Let's verify this. Bytes 5 and 6 are ",[19,676,677],{},"0",[19,679,680],{},"200",". Big-endian reads them in order: (0 × 256) + 200 = 200. Multiplied by 0.1 = 20.0% RH. Perfect.",[34,683,685],{"id":684},"testing-the-configuration","Testing the Configuration",[15,687,688],{},"Now let's test if everything works correctly.",[326,690,691,697,700],{},[103,692,355,693,696],{},[338,694,695],{},"Deploy"," in the top-right corner of the Node-RED editor",[103,698,699],{},"Click the button on the left side of the Inject node to trigger it",[103,701,702],{},"Open the Debug panel on the right side of the editor",[15,704,705],{},"Your Buffer Parser output should look like:",[42,707,711],{"className":708,"code":709,"language":710,"meta":50,"style":50},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"temperature\": 30.0,\n  \"humidity\": 20.0\n}\n","json",[19,712,713,718,737,750],{"__ignoreMap":50},[146,714,715],{"class":148,"line":149},[146,716,717],{"class":160},"{\n",[146,719,720,723,725,728,731,734],{"class":148,"line":185},[146,721,722],{"class":160},"  \"",[146,724,515],{"class":152},[146,726,727],{"class":160},"\"",[146,729,730],{"class":160},":",[146,732,733],{"class":206}," 30.0",[146,735,736],{"class":160},",\n",[146,738,739,741,743,745,747],{"class":148,"line":221},[146,740,722],{"class":160},[146,742,610],{"class":152},[146,744,727],{"class":160},[146,746,730],{"class":160},[146,748,749],{"class":206}," 20.0\n",[146,751,752],{"class":148,"line":250},[146,753,754],{"class":160},"}\n",[15,756,757],{},"This example should work as shown, but with your actual device data, the output may differ. If the values in your JSON don't match what you expect, you're likely running into one of a few common issues. Here's how to recognize and fix them.",[15,759,760,761,764,765,768,769,772,773,213,776,779],{},"If your values are way off, for example, you expect 300 but see 11265, or expect 100 but see 25600, you've got the endianness backwards. Change ",[19,762,763],{},"int16le"," to ",[19,766,767],{},"int16be"," (or vice versa) in the Type field. Alternatively, try setting Byte Swap to ",[19,770,771],{},"swap16"," (or ",[19,774,775],{},"swap32",[19,777,778],{},"swap64"," for larger data types).",[15,781,782,783,764,786,772,788,764,791,794],{},"If you're seeing negative numbers when you know the value should be positive, you're using signed integers when you need unsigned. Change ",[19,784,785],{},"int16",[19,787,537],{},[19,789,790],{},"int32",[19,792,793],{},"uint32",") in the Type field.",[15,796,797,798,801,802,804,805,807,808,810,811,804,814,817],{},"If your values are exactly 256 times too large or too small, you're using the wrong data type size. If you're using ",[19,799,800],{},"int8"," but the device sends 16-bit values, change it to ",[19,803,767],{}," or ",[19,806,763],{},". If you're using ",[19,809,785],{}," but the device sends 32-bit values, change it to ",[19,812,813],{},"int32be",[19,815,816],{},"int32le",". Check your device manual for the correct bit size.",[15,819,820],{},"If you're getting raw values instead of scaled values (for example, 300 instead of 30.0), it means you either forgot to set the Scale field or set it incorrectly. If the device manual says ‘divide by 10,’ enter 0.1 in the Scale field (not 10). Remember: the Buffer Parser multiplies by the scale, so to divide by 10, you must multiply by 0.1",[15,822,823],{},"If every number looks completely wrong, recount your offsets carefully. Check each Offset value and verify against your device manual. Remember that byte 0 is first, byte 1 is second, byte 3 is fourth, and so on. The most common mistake is being off by one byte.",[15,825,826,827,830,831,833],{},"If you're getting only one value when the device sends multiple, check the Length field. If your device sends an array of 5 temperature readings starting at byte 10, set Offset to ",[19,828,829],{},"10"," and Length to ",[19,832,239],{},". The node will return an array of values instead of a single value.",[34,835,837],{"id":836},"what-about-those-other-fields","What About Those Other Fields?",[15,839,840],{},"The configuration screen has more fields we didn't touch:",[15,842,843,846,847,850],{},[338,844,845],{},"Bit Offset"," only matters when you're using the ",[19,848,849],{},"bool"," type to extract a single bit from a byte. This field is only visible when the bool type is selected. If you need to check whether bit 3 of byte 7 is set (like a \"pump running\" status flag), you'd set Offset to 7, Type to bool, and Bit Offset to 3. For reading whole numbers, ignore this field.",[15,852,853,856,857,860,861,864],{},[338,854,855],{},"Mask"," is for when multiple values are packed into the same byte. Industrial protocols do this to save space - why waste 8 bits on a simple on\u002Foff flag when you can pack eight flags into one byte? If bits 0-3 hold one sensor reading and bits 4-7 hold another, you use masks like ",[19,858,859],{},"0x0F"," (binary: 00001111, lower 4 bits) and ",[19,862,863],{},"0xF0"," (binary: 11110000, upper 4 bits) to extract each value separately. You'll know when you need this because the manual will say something like \"status bits 0-3 contain pump speed, bits 4-7 contain valve position.\"",[15,866,867,868,873],{},"For more details on the Buffer Parser node, you can also explore the official ",[307,869,872],{"href":870,"rel":871},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-buffer-parser",[311],"Buffer Parser node documentation",", it's comprehensive, well-maintained, and a great reference when working with more advanced parsing options.",[34,875,877],{"id":876},"when-you-need-more-complex-parsing","When You Need More Complex Parsing",[15,879,880],{},"Buffer Parser handles most industrial protocols, but not everything.",[15,882,883],{},"If you need conditional parsing where the structure changes based on values in the buffer itself, you'll need JavaScript. For example, if byte 2 tells you how many temperature readings follow and that number varies, Buffer Parser can't handle variable-length structures dynamically.",[15,885,886,887,889],{},"If you're dealing with bit-packed data where individual bits within bytes have meaning (common in PLC memory maps), you can use the ",[19,888,849],{}," type with Bit Offset, but extracting many bits gets tedious. Sometimes a function node doing bitwise operations is cleaner.",[301,891,892],{},[15,893,894,895,899],{},"If you're using FlowFuse, you don’t even need to write JavaScript yourself. You can simply ask the ",[307,896,898],{"href":897},"\u002Fblog\u002F2025\u002F07\u002Fflowfuse-ai-assistant-better-node-red-manufacturing\u002F","FlowFuse Expert"," in plain English, paste what your device manual says, and it will generate the Function node directly on your Node-RED canvas.",[15,901,902],{},"For standard Modbus registers, serial sensor protocols, and fixed-structure PLC memory layouts - which covers most industrial data, Buffer Parser does exactly what you need.",[34,904,906],{"id":905},"final-thoughts","Final Thoughts",[15,908,909],{},"Binary data from industrial devices isn’t going anywhere. Modbus isn’t disappearing. Neither are PLCs from the 90s or sensors that still speak in raw bytes. This is the reality of factory floors, and it will be for a long time.",[15,911,912],{},"The Buffer Parser makes that reality manageable. You don't need to be a buffer expert or master bitwise operations. You just need your device manual, a few minutes to configure the node, and, occasionally, the patience to flip the endianness when a value looks strange. The buffers are still just arrays of bytes, but now you have a tool that turns them into meaningful data without rewriting JavaScript every time you add a new sensor. That's worth something.",[15,914,915,916,922],{},"And if you’re managing industrial devices at scale, handling remote Node-RED instances, deploying updates across fleets of edge hardware, or keeping everything secure and consistent, FlowFuse can remove much of the daily friction. With features like remote deployment, snapshots, secure device connectivity, and the FlowFuse AI Expert built right into the editor, you can ",[338,917,918],{},[307,919,921],{"href":920},"\u002Fbook-demo\u002F","book a free demo",". Our team will understand your requirements, show you exactly how FlowFuse fits into your workflow, and guide you on getting the most out of your industrial data pipelines.",[924,925,926],"style",{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":928},[929,930,931,932,933,934,935,936,937,938,939],{"id":36,"depth":185,"text":37},{"id":86,"depth":185,"text":87},{"id":316,"depth":185,"text":317},{"id":360,"depth":185,"text":361},{"id":426,"depth":185,"text":427},{"id":483,"depth":185,"text":484},{"id":592,"depth":185,"text":593},{"id":684,"depth":185,"text":685},{"id":836,"depth":185,"text":837},{"id":876,"depth":185,"text":877},{"id":905,"depth":185,"text":906},{"type":941,"title":942,"description":943},"sign-up","Parse Industrial Data at Scale with FlowFuse","FlowFuse gives you a managed Node-RED platform with remote deployment, snapshots, and the FlowFuse AI Expert built into the editor, so you can decode industrial buffers and manage edge devices without the operational overhead.","2025-12-10","Learn how to parse Modbus and industrial device buffers in Node-RED using the Buffer Parser node. Visual configuration, no coding required. Handle endianness and scaling easily.","md","\u002Fblog\u002F2025\u002F12\u002Fimages\u002Fnode-red-buffer-parser-industrial-data.png","2026-06-03",{"keywords":950,"excerpt":951},"node-red, buffer parser, modbus, modbus rtu, modbus tcp, industrial automation, plc, raw data parsing, binary data, payload decoding, node-red tutorial, node-red buffer parser, node-red modbus parsing, industrial endianness, byte order, bitmask, byte offset, data decoding, data acquisition, serial communication, legacy devices, industrial sensors, modbus registers, buffer parsing guide",{"type":12,"value":952},[953],[15,954,17,955,22,957,26],{},[19,956,21],{},[19,958,25],{},"\u002Fblog\u002F2025\u002F12\u002Fnode-red-buffer-parser-industrial-data",{"title":8,"description":945},{"loc":959},"blog\u002F2025\u002F12\u002Fnode-red-buffer-parser-industrial-data","A practical guide to visual buffer parsing in Node-RED",[965,966],"flowfuse","how-to","Legacy industrial devices like Modbus sensors and PLCs communicate in raw binary buffers, compact byte sequences where each position encodes a specific value rather than human-readable JSON, because binary is far more efficient on low-bandwidth serial links. Node-RED's Buffer Parser node lets you decode these buffers visually through configuration rather than writing custom JavaScript, handling byte offsets, endianness, data types, and scaling factors automatically.","rmV4i5hvenpDYDWLMsWXuzEDr719VARKaysTdrQY_tQ",{"id":970,"title":971,"authors":972,"body":973,"cta":3,"date":2038,"description":2039,"extension":946,"image":2040,"lastUpdated":948,"meta":2041,"navigation":253,"path":2049,"seo":2050,"sitemap":2051,"stem":2052,"subtitle":2053,"tags":2054,"tldr":2056,"video":3,"__hash__":2057},"blog\u002Fblog\u002F2025\u002F07\u002Fconnect-legacy-equipment-serial-flowfuse.md","Node-RED Serial Port Tutorial: Connect RS232\u002FRS485 Manufacturing Equipment (2026)",[10],{"type":12,"value":974,"toc":2022},[975,982,985,989,992,995,1000,1003,1035,1042,1046,1052,1113,1117,1120,1143,1147,1150,1157,1160,1164,1167,1196,1200,1206,1239,1247,1303,1310,1319,1323,1330,1333,1362,1365,1372,1377,1381,1384,1414,1417,1426,1432,1631,1634,1677,1694,1698,1712,1722,1725,1755,1758,1772,1775,1779,1782,1792,1798,1801,1812,1815,1818,1844,1847,1869,1872,1984,1987,1990,1996,2000,2003,2006,2019],[15,976,977,978,981],{},"Many factories rely on machines, both new and old, that communicate via traditional serial interfaces such as ",[338,979,980],{},"RS-232, RS-422, or RS-485",". These machines remain reliable but can be challenging to integrate with modern systems due to their connectivity style.",[15,983,984],{},"This guide shows you how to use FlowFuse (Node-RED with enterprise capabilities) to connect manufacturing equipment, collect data, and enable real-time monitoring, without modifying your original hardware.",[34,986,988],{"id":987},"making-sense-of-serial-communication","Making Sense of Serial Communication",[15,990,991],{},"Before diving into the wiring and flow configuration, it helps to understand how serial communication works, and why it is still relevant in industrial settings.",[15,993,994],{},"Serial ports move data one bit at a time, like passing beads on a string. This may sound old-fashioned, but it remains one of the most reliable and predictable ways to connect machines.",[996,997,999],"h3",{"id":998},"data-direction-which-way-does-it-flow","Data Direction: Which Way Does It Flow?",[15,1001,1002],{},"Not all serial connections behave the same. The direction of data flow is defined by its duplex mode:",[100,1004,1005,1011,1021],{},[103,1006,1007,1010],{},[338,1008,1009],{},"Simplex",": One-way only. Like a speaker giving a lecture, the machine talks, you just listen. This is common for devices like weight scales or scanners that only output data.",[103,1012,1013,1016,1017,1020],{},[338,1014,1015],{},"Half-Duplex",": Data can flow in both directions, but only one side can transmit at a time. This is like using a walkie-talkie. It is the most common operational mode for ",[338,1018,1019],{},"RS-485"," (using two wires), where multiple devices share the same communication line.",[103,1022,1023,1026,1027,1030,1031,1034],{},[338,1024,1025],{},"Full-Duplex",": Two-way, simultaneous communication. This is like a phone call, both sides can talk and listen at once. This is the typical mode for ",[338,1028,1029],{},"RS-232"," (using separate transmit and receive lines) and ",[338,1032,1033],{},"RS-422",", which is designed for full-duplex, multi-drop scenarios (one sender, multiple listeners).",[15,1036,1037,1038,1041],{},"Across industrial environments, serial interfaces like ",[338,1039,1040],{},"RS-232, RS-422, and RS-485"," remain prevalent for device communication, depending on the wiring and number of connected devices.",[996,1043,1045],{"id":1044},"data-format-how-is-it-structured","Data Format: How Is It Structured?",[15,1047,1048,1049,730],{},"Machines are picky, they expect data to arrive in a specific format. Here are the key pieces that make up a serial data ",[338,1050,1051],{},"frame",[1053,1054,1055,1069],"table",{},[1056,1057,1058],"thead",{},[1059,1060,1061,1066],"tr",{},[1062,1063,1065],"th",{"align":1064},"left","Setting",[1062,1067,1068],{"align":1064},"What It Means",[1070,1071,1072,1083,1093,1103],"tbody",{},[1059,1073,1074,1080],{},[1075,1076,1077],"td",{"align":1064},[338,1078,1079],{},"Baud Rate",[1075,1081,1082],{"align":1064},"Speed of transmission (e.g., 9600 or 115200)",[1059,1084,1085,1090],{},[1075,1086,1087],{"align":1064},[338,1088,1089],{},"Data Bits",[1075,1091,1092],{"align":1064},"The actual data, usually 7 or 8 bits",[1059,1094,1095,1100],{},[1075,1096,1097],{"align":1064},[338,1098,1099],{},"Parity",[1075,1101,1102],{"align":1064},"Optional error check, even, odd, or none",[1059,1104,1105,1110],{},[1075,1106,1107],{"align":1064},[338,1108,1109],{},"Stop Bits",[1075,1111,1112],{"align":1064},"Marks the end of each message",[996,1114,1116],{"id":1115},"interface-types-how-devices-physically-connect","Interface Types: How Devices Physically Connect",[15,1118,1119],{},"Different machines use different physical standards. The most common are:",[100,1121,1122,1127,1132,1137],{},[103,1123,1124,1126],{},[338,1125,1029],{},": Typically full-duplex and one-to-one. Good for short-distance device communication.",[103,1128,1129,1131],{},[338,1130,1033],{},": Full-duplex, multi-drop (one sender, multiple receivers). Used for longer distances than RS-232.",[103,1133,1134,1136],{},[338,1135,1019],{},": Typically half-duplex and multi-device. Ideal for networks and even longer cable runs.",[103,1138,1139,1142],{},[338,1140,1141],{},"USB (via adapter)",": Most modern PCs and gateways use USB-to-Serial adapters to talk to RS-232\u002F422\u002F485 devices.",[34,1144,1146],{"id":1145},"setting-up-serial-communication-with-flowfuse","Setting Up Serial Communication with FlowFuse",[15,1148,1149],{},"Now that you understand how serial communication works and what kind of interfaces your machine might use, the next step is to put that knowledge into practice.",[15,1151,1152,1153,1156],{},"Using ",[338,1154,1155],{},"FlowFuse",", you can easily establish serial communication, process the data, and integrate it into dashboards or automated workflows, all without modifying the original hardware.",[15,1158,1159],{},"Let's walk through how to set this up.",[996,1161,1163],{"id":1162},"prerequisites","Prerequisites",[15,1165,1166],{},"Before we start, ensure the following prerequisites are met:",[100,1168,1169,1175,1184],{},[103,1170,1171,1174],{},[338,1172,1173],{},"Hardware Connection:"," The machine must be physically connected to your system using a serial interface.",[103,1176,1177,1180,1181,167],{},[338,1178,1179],{},"Node-RED Instance:"," Make sure you have an instance of Node-RED up and running. The quickest way to do this is via FlowFuse. If you don't have an account, check out our ",[307,1182,312],{"href":309,"rel":1183},[311],[103,1185,1186,1189,1190,1195],{},[338,1187,1188],{},"Serialport Node:"," Install the ",[307,1191,1194],{"href":1192,"rel":1193},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-node-serialport",[311],"node-red-node-serialport"," package if it is not already available in your palette.",[996,1197,1199],{"id":1198},"configuring-the-serial-port-node","Configuring the Serial Port Node",[15,1201,1202,1203,1205],{},"After installing the ",[19,1204,1194],{}," package, follow these steps to configure serial communication in your Node-RED flow:",[326,1207,1208,1214,1217,1224],{},[103,1209,408,1210,1213],{},[338,1211,1212],{},"Serial In"," node from the Node-RED palette onto the canvas.",[103,1215,1216],{},"Double-click the node to open the configuration dialog.",[103,1218,1219,1220,1223],{},"Click the pencil icon next to the ",[338,1221,1222],{},"Serial Port"," field to add a new port configuration.",[103,1225,1226,1227,1230,1231,1234,1235,1238],{},"Enter the serial port path (e.g., ",[19,1228,1229],{},"\u002Fdev\u002FttyUSB0"," on Linux or ",[19,1232,1233],{},"COM3"," on Windows). You can also click the ",[338,1236,1237],{},"search"," option to list available ports.",[15,1240,1241,1245],{},[392,1242],{"alt":1243,"dataZoomable":50,"src":1244},"Screenshot of Node-RED serial port node configuration showing available serial ports after clicking the search option.","\u002Fblog\u002F2025\u002F07\u002Fimages\u002Fsearching-path.gif",[397,1246,1243],{},[326,1248,1249,1267,1298],{"start":284},[103,1250,1251,1252,1255,1256,1255,1259,1262,1263,1266],{},"Set the ",[338,1253,1254],{},"baud rate",", ",[338,1257,1258],{},"data bits",[338,1260,1261],{},"stop bits",", and ",[338,1264,1265],{},"parity"," according to your machine's specifications. These values must match the device exactly, or communication will fail or result in corrupted data.",[103,1268,1269,1270,1273,1274,804,1277,1280,1281,1284,1285,1288,1289,1292,1296],{},"Optionally, define an ",[338,1271,1272],{},"input delimiter",", such as ",[19,1275,1276],{},"\\n",[19,1278,1279],{},"\\r",", to segment incoming messages if your device sends data in lines or chunks.\nIf the output is fixed-length, you can configure it to wait for a specific number of characters. You can also set a ",[338,1282,1283],{},"timeout"," to receive data at regular intervals.\nLater in the output section, you can choose to ",[338,1286,1287],{},"add characters"," back to the message, such as restoring the line break.",[1290,1291],"br",{},[392,1293],{"alt":1294,"dataZoomable":50,"src":1295},"Screenshot of input and output settings in the Node-RED serial port node, showing options like delimiter, character count, and timeout.","\u002Fblog\u002F2025\u002F07\u002Fimages\u002Finput-output-serial-node.png",[397,1297,1294],{},[103,1299,355,1300,1302],{},[338,1301,405],{}," to save the configuration.",[15,1304,1305,1306,1309],{},"Once the serial port is correctly configured and the device is connected, the ",[338,1307,1308],{},"serial in"," node will show a \"connected\" status below the node with a small green square.",[15,1311,1312,1316,1318],{},[392,1313],{"alt":1314,"dataZoomable":50,"src":1315},"Screenshot of the Serial In node in Node-RED showing a green square that indicates a successful connection to the serial port.","\u002Fblog\u002F2025\u002F07\u002Fimages\u002Fserial-port-node-status.png",[397,1317,1314],{},"\n{data-zoomable}",[996,1320,1322],{"id":1321},"writing-to-serial-port","Writing to Serial Port",[15,1324,1325,1326,1329],{},"To send data to a machine, use the ",[338,1327,1328],{},"serial out"," node in Node-RED. This is often necessary to trigger actions such as starting a process, requesting a reading, or changing an internal state.",[15,1331,1332],{},"Follow these steps to send a command:",[326,1334,1335,1341,1347,1353],{},[103,1336,369,1337,1340],{},[338,1338,1339],{},"inject"," node onto the canvas.",[103,1342,1251,1343,1346],{},[338,1344,1345],{},"payload type"," appropriate to your machine's requirements. This could be a string, number, raw buffer, or JSON object.",[103,1348,1349,1350,1352],{},"Add a ",[338,1351,1328],{}," node and select the configured serial port.",[103,1354,1355,1356,1358,1359,1361],{},"Connect the ",[338,1357,1339],{}," node to the ",[338,1360,1328],{}," node.",[15,1363,1364],{},"Once deployed, clicking the inject button will send the specified data to the machine via the serial interface.",[15,1366,1367,1368,1371],{},"In this guide, we are using a real machine connected via a serial interface. The machine is programmed to simulate a production process when it receives the ",[19,1369,1370],{},"\"START\""," command (sent as a string). Once triggered, it begins incrementing the count of good and defect products and sends this data back over the same serial connection.",[15,1373,1374,1375,1361],{},"The next section demonstrates how to read and process this simulated production data using the ",[338,1376,1308],{},[996,1378,1380],{"id":1379},"reading-and-processing-serial-data","Reading and Processing Serial Data",[15,1382,1383],{},"Follow these steps to read and handle the serial data:",[326,1385,1386,1391,1400],{},[103,1387,408,1388,1390],{},[338,1389,1308],{}," node onto the canvas and configure it to use the same serial port.",[103,1392,1349,1393,1396,1397,1399],{},[338,1394,1395],{},"debug"," node and connect it to the output of the ",[338,1398,1308],{}," node. This helps you inspect the raw payload and confirm that data is being received correctly.",[103,1401,1402,1403,1255,1406,1409,1410,1413],{},"Once you confirm the format of the incoming data, use any of the ",[338,1404,1405],{},"change",[338,1407,1408],{},"JSON",", or ",[338,1411,1412],{},"function"," node to parse and convert it into a structured format, here we have used function.",[15,1415,1416],{},"In our case, the machine sends production data every 2 seconds in the following format:",[15,1418,1419,1423],{},[392,1420],{"alt":1421,"dataZoomable":50,"src":1422},"Screenshot of Node-RED debug panel showing production data sent from the machine every 2 seconds","\u002Fblog\u002F2025\u002F07\u002Fimages\u002Fdebug-panel-output.png",[397,1424,1425],{},"Screenshot of Node-RED debug panel showing production data sent from the machine every 2 seconds.",[15,1427,1428,1429,1431],{},"To convert this string into a structured JSON object, you can use a ",[338,1430,1412],{}," node with the following code:",[42,1433,1435],{"className":140,"code":1434,"language":142,"meta":50,"style":50},"let parts = msg.payload.trim().split(' ');\nlet result = {};\n\nparts.forEach(part => {\n    let [key, value] = part.split(':');\n    result[key.trim()] = parseInt(value);\n});\n\nmsg.payload = result;\nreturn msg;\n",[19,1436,1437,1480,1492,1496,1518,1561,1592,1601,1606,1622],{"__ignoreMap":50},[146,1438,1439,1442,1445,1447,1449,1451,1454,1456,1459,1462,1464,1467,1469,1472,1475,1478],{"class":148,"line":149},[146,1440,1441],{"class":152},"let",[146,1443,1444],{"class":156}," parts ",[146,1446,161],{"class":160},[146,1448,291],{"class":156},[146,1450,167],{"class":160},[146,1452,1453],{"class":156},"payload",[146,1455,167],{"class":160},[146,1457,1458],{"class":170},"trim",[146,1460,1461],{"class":156},"()",[146,1463,167],{"class":160},[146,1465,1466],{"class":170},"split",[146,1468,203],{"class":156},[146,1470,1471],{"class":160},"'",[146,1473,1474],{"class":160}," '",[146,1476,1477],{"class":156},")",[146,1479,182],{"class":160},[146,1481,1482,1484,1487,1489],{"class":148,"line":185},[146,1483,1441],{"class":152},[146,1485,1486],{"class":156}," result ",[146,1488,161],{"class":160},[146,1490,1491],{"class":160}," {};\n",[146,1493,1494],{"class":148,"line":221},[146,1495,254],{"emptyLinePlaceholder":253},[146,1497,1498,1501,1503,1506,1508,1512,1515],{"class":148,"line":250},[146,1499,1500],{"class":156},"parts",[146,1502,167],{"class":160},[146,1504,1505],{"class":170},"forEach",[146,1507,203],{"class":156},[146,1509,1511],{"class":1510},"sHdIc","part",[146,1513,1514],{"class":152}," =>",[146,1516,1517],{"class":160}," {\n",[146,1519,1520,1523,1526,1529,1531,1534,1537,1540,1543,1545,1547,1550,1552,1555,1557,1559],{"class":148,"line":257},[146,1521,1522],{"class":152},"    let",[146,1524,1525],{"class":160}," [",[146,1527,1528],{"class":156},"key",[146,1530,276],{"class":160},[146,1532,1533],{"class":156}," value",[146,1535,1536],{"class":160},"]",[146,1538,1539],{"class":160}," =",[146,1541,1542],{"class":156}," part",[146,1544,167],{"class":160},[146,1546,1466],{"class":170},[146,1548,203],{"class":1549},"swJcz",[146,1551,1471],{"class":160},[146,1553,730],{"class":1554},"sfazB",[146,1556,1471],{"class":160},[146,1558,1477],{"class":1549},[146,1560,182],{"class":160},[146,1562,1563,1566,1569,1571,1573,1575,1578,1580,1583,1585,1588,1590],{"class":148,"line":284},[146,1564,1565],{"class":156},"    result",[146,1567,1568],{"class":1549},"[",[146,1570,1528],{"class":156},[146,1572,167],{"class":160},[146,1574,1458],{"class":170},[146,1576,1577],{"class":1549},"()] ",[146,1579,161],{"class":160},[146,1581,1582],{"class":170}," parseInt",[146,1584,203],{"class":1549},[146,1586,1587],{"class":156},"value",[146,1589,1477],{"class":1549},[146,1591,182],{"class":160},[146,1593,1594,1597,1599],{"class":148,"line":666},[146,1595,1596],{"class":160},"}",[146,1598,1477],{"class":156},[146,1600,182],{"class":160},[146,1602,1604],{"class":148,"line":1603},8,[146,1605,254],{"emptyLinePlaceholder":253},[146,1607,1609,1611,1613,1615,1617,1620],{"class":148,"line":1608},9,[146,1610,260],{"class":156},[146,1612,167],{"class":160},[146,1614,265],{"class":156},[146,1616,161],{"class":160},[146,1618,1619],{"class":156}," result",[146,1621,182],{"class":160},[146,1623,1625,1627,1629],{"class":148,"line":1624},10,[146,1626,288],{"class":287},[146,1628,291],{"class":156},[146,1630,182],{"class":160},[15,1632,1633],{},"This transforms the string into a JSON object like:",[42,1635,1637],{"className":708,"code":1636,"language":710,"meta":50,"style":50},"{\n  \"GOOD\": 214,\n  \"DEFECT\": 22\n}\n",[19,1638,1639,1643,1659,1673],{"__ignoreMap":50},[146,1640,1641],{"class":148,"line":149},[146,1642,717],{"class":160},[146,1644,1645,1647,1650,1652,1654,1657],{"class":148,"line":185},[146,1646,722],{"class":160},[146,1648,1649],{"class":152},"GOOD",[146,1651,727],{"class":160},[146,1653,730],{"class":160},[146,1655,1656],{"class":206}," 214",[146,1658,736],{"class":160},[146,1660,1661,1663,1666,1668,1670],{"class":148,"line":221},[146,1662,722],{"class":160},[146,1664,1665],{"class":152},"DEFECT",[146,1667,727],{"class":160},[146,1669,730],{"class":160},[146,1671,1672],{"class":206}," 22\n",[146,1674,1675],{"class":148,"line":250},[146,1676,754],{"class":160},[301,1678,1679],{},[15,1680,1681,1684,1685,1687,1688,1693],{},[338,1682,1683],{},"Tip",": You do not need to know JavaScript to use the ",[338,1686,1412],{}," node.\nIf you are using FlowFuse, the built-in ",[307,1689,1692],{"href":1690,"rel":1691},"https:\u002F\u002Fwww.google.com\u002Fsearch?q=\u002Fdocs\u002Fuser\u002Fexpert\u002F",[311],"FlowFuse Assistant"," can help you write function code using natural language. Simply provide a sample of the data received from your machine and describe the output you expect, the Assistant will generate the function for you.",[996,1695,1697],{"id":1696},"handling-request-response-serial-communication","Handling Request-Response Serial Communication",[15,1699,1700,1701,1255,1703,1262,1705,1707,1708,1711],{},"Not all machines stream data continuously. Some expect a request command, and only then respond with data. In these cases, using a combination of ",[338,1702,1339],{},[338,1704,1328],{},[338,1706,1308],{}," nodes can become tricky, especially if you need to match each request with exactly one response. That's where the ",[338,1709,1710],{},"serial request"," node becomes useful.",[15,1713,1714,1715,1717,1718,1721],{},"The ",[338,1716,1710],{}," node handles this entire pattern for you. Internally, it combines the logic of sending a message and waiting for a single reply, working in a ",[338,1719,1720],{},"first-in, first-out"," manner. This means it will only send the next request after receiving a response (or timeout) for the previous one, making it ideal for synchronous devices.",[15,1723,1724],{},"To use it:",[326,1726,1727,1732,1735,1747],{},[103,1728,408,1729,1731],{},[338,1730,1710],{}," node from the palette.",[103,1733,1734],{},"Double-click to configure the port, use the same path and settings as your other serial nodes.",[103,1736,1737,1738,1740,1741,804,1744,167],{},"Connect it to an ",[338,1739,1339],{}," node configured with the command your machine expects, such as ",[19,1742,1743],{},"\"READ\"",[19,1745,1746],{},"\"STATUS\"",[103,1748,1749,1750,804,1752,1754],{},"On the output side, connect a ",[338,1751,1395],{},[338,1753,1412],{}," node to handle the response.",[15,1756,1757],{},"Each time you trigger the inject, the command will be sent over the serial port, and the response will be delivered to the output, ready to be parsed just like before. This approach is clean, predictable, and removes the guesswork from matching writes with reads.",[15,1759,1760,1761,1763,1764,1767,1768,1771],{},"The output message includes ",[19,1762,382],{}," containing the response (if any), ",[19,1765,1766],{},"msg.status"," with the result status, and ",[19,1769,1770],{},"msg.port"," for reference.",[15,1773,1774],{},"This node is especially useful for polling machines that respond with production counts, part IDs, temperature readings, or system status, but only when asked.",[996,1776,1778],{"id":1777},"dynamically-managing-serial-ports","Dynamically Managing Serial Ports",[15,1780,1781],{},"In a perfect setup, the serial device is connected, the port is stable, and everything just works. But in practice, hardware is not always so predictable.",[15,1783,1784,1785,1788,1789,1791],{},"For example, You might disconnect and reconnect a USB-to-serial adapter, and now the device shows up as ",[19,1786,1787],{},"\u002Fdev\u002FttyUSB1"," instead of ",[19,1790,1229],{},". Or maybe you need to temporarily release the serial port to flash new firmware onto an Arduino. In some environments, the port assignment could change on every reboot, making it difficult to hardcode anything.",[15,1793,1794,1795,1361],{},"Rather than redeploying or editing your flow every time, Node-RED gives you a more flexible option: the ",[338,1796,1797],{},"serial control",[15,1799,1800],{},"This node lets your flow adjust serial communication settings on the fly. You can:",[100,1802,1803,1806,1809],{},[103,1804,1805],{},"Stop the serial connection when needed.",[103,1807,1808],{},"Start it again later.",[103,1810,1811],{},"Even switch to a different port entirely, without touching the Node-RED editor.",[15,1813,1814],{},"All of this happens by sending a simple message to the control node.",[15,1816,1817],{},"To stop communication:",[42,1819,1821],{"className":708,"code":1820,"language":710,"meta":50,"style":50},"{ \"enabled\": false }\n",[19,1822,1823],{"__ignoreMap":50},[146,1824,1825,1828,1831,1834,1836,1838,1841],{"class":148,"line":149},[146,1826,1827],{"class":160},"{",[146,1829,1830],{"class":160}," \"",[146,1832,1833],{"class":152},"enabled",[146,1835,727],{"class":160},[146,1837,730],{"class":160},[146,1839,1840],{"class":160}," false",[146,1842,1843],{"class":160}," }\n",[15,1845,1846],{},"To start it again:",[42,1848,1850],{"className":708,"code":1849,"language":710,"meta":50,"style":50},"{ \"enabled\": true }\n",[19,1851,1852],{"__ignoreMap":50},[146,1853,1854,1856,1858,1860,1862,1864,1867],{"class":148,"line":149},[146,1855,1827],{"class":160},[146,1857,1830],{"class":160},[146,1859,1833],{"class":152},[146,1861,727],{"class":160},[146,1863,730],{"class":160},[146,1865,1866],{"class":160}," true",[146,1868,1843],{"class":160},[15,1870,1871],{},"And if the port changes or needs reconfiguration, you can send everything in one message:",[42,1873,1875],{"className":708,"code":1874,"language":710,"meta":50,"style":50},"{\n  \"serialport\": \"\u002Fdev\u002FttyUSB1\",\n  \"serialbaud\": 9600,\n  \"databits\": 8,\n  \"parity\": \"none\",\n  \"stopbits\": 1,\n  \"enabled\": true\n}\n",[19,1876,1877,1881,1900,1916,1932,1951,1967,1980],{"__ignoreMap":50},[146,1878,1879],{"class":148,"line":149},[146,1880,717],{"class":160},[146,1882,1883,1885,1888,1890,1892,1894,1896,1898],{"class":148,"line":185},[146,1884,722],{"class":160},[146,1886,1887],{"class":152},"serialport",[146,1889,727],{"class":160},[146,1891,730],{"class":160},[146,1893,1830],{"class":160},[146,1895,1787],{"class":1554},[146,1897,727],{"class":160},[146,1899,736],{"class":160},[146,1901,1902,1904,1907,1909,1911,1914],{"class":148,"line":221},[146,1903,722],{"class":160},[146,1905,1906],{"class":152},"serialbaud",[146,1908,727],{"class":160},[146,1910,730],{"class":160},[146,1912,1913],{"class":206}," 9600",[146,1915,736],{"class":160},[146,1917,1918,1920,1923,1925,1927,1930],{"class":148,"line":250},[146,1919,722],{"class":160},[146,1921,1922],{"class":152},"databits",[146,1924,727],{"class":160},[146,1926,730],{"class":160},[146,1928,1929],{"class":206}," 8",[146,1931,736],{"class":160},[146,1933,1934,1936,1938,1940,1942,1944,1947,1949],{"class":148,"line":257},[146,1935,722],{"class":160},[146,1937,1265],{"class":152},[146,1939,727],{"class":160},[146,1941,730],{"class":160},[146,1943,1830],{"class":160},[146,1945,1946],{"class":1554},"none",[146,1948,727],{"class":160},[146,1950,736],{"class":160},[146,1952,1953,1955,1958,1960,1962,1965],{"class":148,"line":284},[146,1954,722],{"class":160},[146,1956,1957],{"class":152},"stopbits",[146,1959,727],{"class":160},[146,1961,730],{"class":160},[146,1963,1964],{"class":206}," 1",[146,1966,736],{"class":160},[146,1968,1969,1971,1973,1975,1977],{"class":148,"line":666},[146,1970,722],{"class":160},[146,1972,1833],{"class":152},[146,1974,727],{"class":160},[146,1976,730],{"class":160},[146,1978,1979],{"class":160}," true\n",[146,1981,1982],{"class":148,"line":1603},[146,1983,754],{"class":160},[15,1985,1986],{},"This is especially useful when your flow needs to recover automatically, for example, after a USB reconnection, or if you want to let a user select the correct port from a dashboard interface.",[15,1988,1989],{},"Each time a message is received, the node also outputs the current port configuration. This allows you to log or verify changes as part of your flow, making it easy to track what the system is doing behind the scenes.",[15,1991,1992,1993,1995],{},"In short, the ",[338,1994,1797],{}," node adds a layer of resilience and flexibility that is often essential in real-world deployments, where devices come and go, ports are never quite consistent, and downtime is not an option.",[34,1997,1999],{"id":1998},"key-takeaways","Key Takeaways",[15,2001,2002],{},"Manufacturing floors often bring together a mix of old and new machines. Among them, many still operate on traditional communication methods that are reliable but difficult to integrate with modern systems. These machines continue to perform their core functions well, but without connectivity, they remain isolated from digital workflows.",[15,2004,2005],{},"With FlowFuse and Node-RED, you can bridge that gap, bringing equipment online without changing or replacing existing hardware. From data collection to triggering actions and monitoring performance, your machines can become part of a connected and intelligent system.",[15,2007,2008,2009,2013,2014,2018],{},"Whether you're in textiles, precision engineering, or ",[307,2010,2012],{"href":2011},"\u002Findustries\u002Fautomotive\u002F","automotive manufacturing",", FlowFuse helps you unlock the full potential of your existing equipment. No rip-and-replace needed, just smarter connections. ",[307,2015,2017],{"href":2016},"\u002Fcontact-us\u002F","Get in touch with us"," and start building your serial integration flow today.",[924,2020,2021],{},"html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":50,"searchDepth":250,"depth":250,"links":2023},[2024,2029,2037],{"id":987,"depth":185,"text":988,"children":2025},[2026,2027,2028],{"id":998,"depth":221,"text":999},{"id":1044,"depth":221,"text":1045},{"id":1115,"depth":221,"text":1116},{"id":1145,"depth":185,"text":1146,"children":2030},[2031,2032,2033,2034,2035,2036],{"id":1162,"depth":221,"text":1163},{"id":1198,"depth":221,"text":1199},{"id":1321,"depth":221,"text":1322},{"id":1379,"depth":221,"text":1380},{"id":1696,"depth":221,"text":1697},{"id":1777,"depth":221,"text":1778},{"id":1998,"depth":185,"text":1999},"2025-07-09","Learn how to connect manufacturing equipment using serial interfaces like RS-232\u002F422\u002F485 in Node-RED with FlowFuse. Enable monitoring, data collection, and automation, no hardware changes required","blog\u002F2025\u002F07\u002Fimages\u002Fconnect-serial-port-node-red-ff.png",{"keywords":2042,"excerpt":2043},"node-red, serial communication, rs232, rs485, rs422, modbus, industrial automation, flowfuse, legacy equipment, machine data collection, manufacturing connectivity, serial port integration, factory floor monitoring, serial devices, equipment integration",{"type":12,"value":2044},[2045],[15,2046,977,2047,981],{},[338,2048,980],{},"\u002Fblog\u002F2025\u002F07\u002Fconnect-legacy-equipment-serial-flowfuse",{"title":971,"description":2039},{"loc":2049},"blog\u002F2025\u002F07\u002Fconnect-legacy-equipment-serial-flowfuse","Learn how to bring serial-connected equipment online using Node-RED and FlowFuse",[965,2055,966],"opcua","Many factory machines communicate via RS-232, RS-422, or RS-485 serial interfaces and can be integrated with modern systems using Node-RED and FlowFuse without any hardware modifications. This guide covers serial communication fundamentals, installing the node-red-contrib-serialport nodes, and building flows to collect and monitor data from legacy equipment in real time.","Yqu_YEoJ5IZNNbdOoxMxpkuuP2iX4vxrWZW1rWoJgec",{"id":2059,"title":2060,"authors":2061,"body":2062,"cta":5296,"date":5299,"description":5300,"extension":946,"image":5301,"lastUpdated":948,"meta":5302,"navigation":253,"path":5308,"seo":5309,"sitemap":5310,"stem":5311,"subtitle":5312,"tags":5313,"tldr":5314,"video":3,"__hash__":5315},"blog\u002Fblog\u002F2025\u002F05\u002Fhow-to-generate-pdf-reports-using-node-red.md","How to Generate PDF Reports Using Node-RED in FlowFuse (2026)",[10],{"type":12,"value":2063,"toc":5283},[2064,2067,2070,2072,2075,2109,2113,2116,2120,2128,2145,2148,2159,2163,2166,2169,2172,2389,2398,2401,3741,3744,3752,3756,3759,3764,3767,3831,3846,3857,3865,4924,4969,4976,4979,4983,4986,4991,5091,5094,5099,5103,5106,5140,5143,5146,5154,5252,5257,5267,5271,5280],[15,2065,2066],{},"Generating PDF reports is a common need in many workflows, whether you're logging data, sharing results, or creating summaries. With Node-RED and FlowFuse, you can easily automate turning your data into well-structured PDF files. This guide will show you how to set up step-by-step PDF report generation using simple tools and flows.",[15,2068,2069],{},"Generating reports allows you to capture snapshots of critical data, summarize system activities, and distribute insights in an easy-to-read and stored format. PDF is one of the most universally accepted formats for sharing documents, making it ideal for delivering structured information from your Node-RED flows.",[34,2071,1163],{"id":1162},[15,2073,2074],{},"Before you begin, make sure the following requirements are met:",[100,2076,2077,2086,2095],{},[103,2078,2079,2080,2085],{},"You have an active ",[307,2081,2084],{"href":2082,"rel":2083},"https:\u002F\u002Fapp.flowfuse.com",[311],"FlowFuse account"," and a running FlowFuse instance.",[103,2087,2088,2089,2094],{},"You are familiar with creating and deploying basic flows in Node-RED. If not, consider taking the ",[307,2090,2093],{"href":2091,"rel":2092},"https:\u002F\u002Fnode-red-academy.learnworlds.com\u002Fcourse\u002Fnode-red-getting-started",[311],"Node-RED Fundamentals Course"," sponsored by FlowFuse.",[103,2096,2097,2098,2101,2102,63,2105,2108],{},"Ensure you have installed ",[19,2099,2100],{},"flowfuse\u002Fnode-red-dashboard"," ",[19,2103,2104],{},"@flowfuse\u002Fnode-red-dashboard-2-ui-iframe",[19,2106,2107],{},"node-red-contrib-sqlite"," (The SQLite node is required for the demo data generation flow we provided. If you're not using that flow, you can skip this.).",[34,2110,2112],{"id":2111},"setting-up-pdf-generation-in-node-red","Setting Up PDF Generation in Node-RED",[15,2114,2115],{},"Once the prerequisites are in place, the next step is setting up your Node-RED environment to generate PDF reports. In this section, we will go over how to install the necessary Node-RED node and configure a flow to generate PDF reports.",[996,2117,2119],{"id":2118},"step-1-install-the-platmacnode-red-pdfbuilder","Step 1: Install the @platmac\u002Fnode-red-pdfbuilder",[15,2121,1714,2122,2127],{},[307,2123,2126],{"href":2124,"rel":2125},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002F@platmac\u002Fnode-red-pdfbuilder",[311],"platmac\u002Fnode-red-pdfbuilder"," node is the primary node for creating PDF reports in Node-RED. To install this node:",[326,2129,2130,2133,2136,2142],{},[103,2131,2132],{},"Open your Node-RED editor.",[103,2134,2135],{},"Navigate to the \"Manage palette\" section from the top-right menu.",[103,2137,2138,2139,167],{},"Click on the \"Install\" tab and search for ",[19,2140,2141],{},"@platmac\u002Fnode-red-pdfbuilder",[103,2143,2144],{},"Click \"Install\" to add the node to your palette.",[15,2146,2147],{},"This node allows you to dynamically generate PDFs from various inputs, which is exactly what you will need to generate reports.",[15,2149,2150,2151,1255,2154,63,2156,2158],{},"If you haven't installed the ",[19,2152,2153],{},"@flowfuse\u002Fnode-red-dashboard",[19,2155,2104],{},[19,2157,2107],{}," nodes, you can install them similarly.",[996,2160,2162],{"id":2161},"step-2-understanding-how-to-use-the-pdfbuilder-node","Step 2: Understanding How to Use the Pdfbuilder Node",[15,2164,2165],{},"Now that the required node is installed, let's dive into how to use it and how to leverage the different attributes to customize your PDF reports. The pdfbuilder node in Node-RED simplifies generating PDFs by allowing you to define content, layout, and styling directly in your flow.",[15,2167,2168],{},"The key advantage of using pdfbuilder node is that it operates server-side, meaning PDFs can be generated automatically without a browser or manual interaction. This makes it ideal for automated workflows where consistent, programmatically created documents are needed.",[15,2170,2171],{},"When working with this node, you can use various attributes to customize the content and layout of the PDF, such as text, tables, images, page sizes, margins, headers, footers, and more. Below are the most commonly used attributes:",[1053,2173,2174,2193],{},[1056,2175,2176],{},[1059,2177,2178,2183,2188],{},[1062,2179,2180],{},[338,2181,2182],{},"Attribute",[1062,2184,2185],{},[338,2186,2187],{},"Description",[1062,2189,2190],{},[338,2191,2192],{},"Example",[1070,2194,2195,2210,2224,2239,2254,2269,2284,2299,2314,2329,2344,2359,2374],{},[1059,2196,2197,2202,2205],{},[1075,2198,2199],{},[19,2200,2201],{},"content",[1075,2203,2204],{},"Defines the content of the PDF (text, tables, images, etc.).",[1075,2206,2207],{},[19,2208,2209],{},"{ \"content\": \"Hello, World!\" }",[1059,2211,2212,2216,2219],{},[1075,2213,2214],{},[19,2215,924],{},[1075,2217,2218],{},"Specifies the style for content (font size, font family, etc.).",[1075,2220,2221],{},[19,2222,2223],{},"{ \"style\": \"headerStyle\" }",[1059,2225,2226,2231,2234],{},[1075,2227,2228],{},[19,2229,2230],{},"layout",[1075,2232,2233],{},"Defines the layout of a table (e.g., 'lightHorizontalLines', 'noBorders').",[1075,2235,2236],{},[19,2237,2238],{},"{ \"layout\": \"lightHorizontalLines\" }",[1059,2240,2241,2246,2249],{},[1075,2242,2243],{},[19,2244,2245],{},"pageSize",[1075,2247,2248],{},"Defines the page size for the PDF.",[1075,2250,2251],{},[19,2252,2253],{},"{ \"pageSize\": \"A4\" }",[1059,2255,2256,2261,2264],{},[1075,2257,2258],{},[19,2259,2260],{},"pageMargins",[1075,2262,2263],{},"Sets the margins for the PDF (left, top, right, bottom).",[1075,2265,2266],{},[19,2267,2268],{},"{ \"pageMargins\": [40, 60, 40, 60] }",[1059,2270,2271,2276,2279],{},[1075,2272,2273],{},[19,2274,2275],{},"header",[1075,2277,2278],{},"Specifies a header for the PDF. Can be a static text or dynamic content.",[1075,2280,2281],{},[19,2282,2283],{},"{ \"header\": \"My PDF Report\" }",[1059,2285,2286,2291,2294],{},[1075,2287,2288],{},[19,2289,2290],{},"footer",[1075,2292,2293],{},"Specifies a footer for the PDF. Can be a static text or dynamic content.",[1075,2295,2296],{},[19,2297,2298],{},"{ \"footer\": \"Page {PAGE_NUM} of {PAGE_COUNT}\" }",[1059,2300,2301,2306,2309],{},[1075,2302,2303],{},[19,2304,2305],{},"defaultStyle",[1075,2307,2308],{},"Defines the default style for all content in the PDF.",[1075,2310,2311],{},[19,2312,2313],{},"{ \"defaultStyle\": { \"font\": \"Helvetica\", \"fontSize\": 12 } }",[1059,2315,2316,2321,2324],{},[1075,2317,2318],{},[19,2319,2320],{},"background",[1075,2322,2323],{},"Adds a background to the page or content area.",[1075,2325,2326],{},[19,2327,2328],{},"{ \"background\": { \"image\": \"imageData\" } }",[1059,2330,2331,2336,2339],{},[1075,2332,2333],{},[19,2334,2335],{},"width",[1075,2337,2338],{},"Sets the width of table cells or other elements.",[1075,2340,2341],{},[19,2342,2343],{},"{ \"width\": 150 }",[1059,2345,2346,2351,2354],{},[1075,2347,2348],{},[19,2349,2350],{},"height",[1075,2352,2353],{},"Sets the height of table cells or other elements.",[1075,2355,2356],{},[19,2357,2358],{},"{ \"height\": 50 }",[1059,2360,2361,2366,2369],{},[1075,2362,2363],{},[19,2364,2365],{},"alignment",[1075,2367,2368],{},"Specifies the text alignment (left, center, right).",[1075,2370,2371],{},[19,2372,2373],{},"{ \"alignment\": \"center\" }",[1059,2375,2376,2381,2384],{},[1075,2377,2378],{},[19,2379,2380],{},"border",[1075,2382,2383],{},"Defines the border for tables or table cells (style, width, and color).",[1075,2385,2386],{},[19,2387,2388],{},"{ \"border\": [true, true, true, true] }",[15,2390,2391,2392,2397],{},"For additional attributes and information, refer to the ",[307,2393,2396],{"href":2394,"rel":2395},"https:\u002F\u002Fpdfmake.github.io\u002Fdocs\u002F0.1\u002Fdocument-definition-object\u002F",[311],"pdfmake documentation",", as pdfbuilder-node uses this library to generate PDFs.",[15,2399,2400],{},"Here’s a simple example of how you can use these attributes to create a basic PDF:",[42,2402,2404],{"className":708,"code":2403,"language":710,"meta":50,"style":50},"{\n  \"content\": [\n    {\n      \"svg\": \"logoDataHere\",\n      \"width\": 150,\n      \"alignment\": \"center\",\n      \"margin\": [0, 0, 0, 20]\n    },\n    {\n      \"text\": \"Production Report - 2025\",\n      \"style\": \"header\"\n    },\n    {\n      \"text\": \"Daily Production Summary with Operator Performance\",\n      \"style\": \"subheader\",\n      \"alignment\": \"center\",\n      \"margin\": [0, 10, 0, 20]\n    },\n    {\n      \"layout\": \"lightHorizontalLines\",\n      \"table\": {\n        \"headerRows\": 1,\n        \"widths\": [\"auto\", \"auto\", \"*\", \"auto\", \"auto\", \"*\"],\n        \"body\": [\n          [\"Date\", \"Shift\", \"Product\", \"Units Produced\", \"Defective Units\", \"Operator\"],\n          [\"2025-01-01\", \"Morning\", \"Product A\", \"1000\", \"20\", \"John Doe\"],\n          [\"2025-01-01\", \"Afternoon\", \"Product B\", \"950\", \"15\", \"Jane Smith\"],\n          [\"2025-01-02\", \"Morning\", \"Product A\", \"1050\", \"10\", \"James Brown\"],\n          [\"2025-01-02\", \"Afternoon\", \"Product C\", \"800\", \"30\", \"Emily Clark\"],\n          [\"2025-01-03\", \"Morning\", \"Product B\", \"1100\", \"25\", \"Michael Green\"],\n          [\"2025-01-03\", \"Afternoon\", \"Product A\", \"980\", \"18\", \"Sarah White\"]\n        ]\n      }\n    },\n    {\n      \"text\": \"This table summarizes the daily production output across different shifts and operators. It includes total units produced and defective units recorded for quality analysis.\",\n      \"fontSize\": 12,\n      \"alignment\": \"justify\",\n      \"margin\": [0, 10, 0, 20]\n    },\n    {\n      \"text\": \"Internal Use Only - Manufacturing Co.\",\n      \"style\": \"footer\",\n      \"alignment\": \"center\",\n      \"margin\": [0, 20, 0, 0]\n    }\n  ],\n  \"styles\": {\n    \"header\": {\n      \"fontSize\": 20,\n      \"bold\": true,\n      \"alignment\": \"center\",\n      \"margin\": [0, 20, 0, 10]\n    },\n    \"subheader\": {\n      \"fontSize\": 14,\n      \"italics\": true,\n      \"color\": \"grey\",\n      \"margin\": [0, 10, 0, 20]\n    },\n    \"footer\": {\n      \"fontSize\": 10,\n      \"color\": \"grey\"\n    }\n  },\n  \"pageSize\": \"A4\",\n  \"pageMargins\": [40, 60, 40, 60]\n}\n",[19,2405,2406,2410,2423,2428,2450,2465,2484,2516,2521,2525,2544,2562,2567,2572,2592,2612,2631,2660,2665,2670,2690,2703,2720,2785,2799,2859,2918,2976,3032,3089,3146,3202,3208,3214,3219,3224,3244,3261,3281,3310,3315,3320,3340,3359,3378,3407,3413,3419,3433,3447,3462,3477,3496,3525,3530,3543,3559,3573,3594,3623,3628,3641,3656,3673,3678,3684,3704,3736],{"__ignoreMap":50},[146,2407,2408],{"class":148,"line":149},[146,2409,717],{"class":160},[146,2411,2412,2414,2416,2418,2420],{"class":148,"line":185},[146,2413,722],{"class":160},[146,2415,2201],{"class":152},[146,2417,727],{"class":160},[146,2419,730],{"class":160},[146,2421,2422],{"class":160}," [\n",[146,2424,2425],{"class":148,"line":221},[146,2426,2427],{"class":160},"    {\n",[146,2429,2430,2433,2437,2439,2441,2443,2446,2448],{"class":148,"line":250},[146,2431,2432],{"class":160},"      \"",[146,2434,2436],{"class":2435},"sBMFI","svg",[146,2438,727],{"class":160},[146,2440,730],{"class":160},[146,2442,1830],{"class":160},[146,2444,2445],{"class":1554},"logoDataHere",[146,2447,727],{"class":160},[146,2449,736],{"class":160},[146,2451,2452,2454,2456,2458,2460,2463],{"class":148,"line":257},[146,2453,2432],{"class":160},[146,2455,2335],{"class":2435},[146,2457,727],{"class":160},[146,2459,730],{"class":160},[146,2461,2462],{"class":206}," 150",[146,2464,736],{"class":160},[146,2466,2467,2469,2471,2473,2475,2477,2480,2482],{"class":148,"line":284},[146,2468,2432],{"class":160},[146,2470,2365],{"class":2435},[146,2472,727],{"class":160},[146,2474,730],{"class":160},[146,2476,1830],{"class":160},[146,2478,2479],{"class":1554},"center",[146,2481,727],{"class":160},[146,2483,736],{"class":160},[146,2485,2486,2488,2491,2493,2495,2497,2499,2501,2504,2506,2508,2510,2513],{"class":148,"line":666},[146,2487,2432],{"class":160},[146,2489,2490],{"class":2435},"margin",[146,2492,727],{"class":160},[146,2494,730],{"class":160},[146,2496,1525],{"class":160},[146,2498,677],{"class":206},[146,2500,276],{"class":160},[146,2502,2503],{"class":206}," 0",[146,2505,276],{"class":160},[146,2507,2503],{"class":206},[146,2509,276],{"class":160},[146,2511,2512],{"class":206}," 20",[146,2514,2515],{"class":160},"]\n",[146,2517,2518],{"class":148,"line":1603},[146,2519,2520],{"class":160},"    },\n",[146,2522,2523],{"class":148,"line":1608},[146,2524,2427],{"class":160},[146,2526,2527,2529,2531,2533,2535,2537,2540,2542],{"class":148,"line":1624},[146,2528,2432],{"class":160},[146,2530,47],{"class":2435},[146,2532,727],{"class":160},[146,2534,730],{"class":160},[146,2536,1830],{"class":160},[146,2538,2539],{"class":1554},"Production Report - 2025",[146,2541,727],{"class":160},[146,2543,736],{"class":160},[146,2545,2547,2549,2551,2553,2555,2557,2559],{"class":148,"line":2546},11,[146,2548,2432],{"class":160},[146,2550,924],{"class":2435},[146,2552,727],{"class":160},[146,2554,730],{"class":160},[146,2556,1830],{"class":160},[146,2558,2275],{"class":1554},[146,2560,2561],{"class":160},"\"\n",[146,2563,2565],{"class":148,"line":2564},12,[146,2566,2520],{"class":160},[146,2568,2570],{"class":148,"line":2569},13,[146,2571,2427],{"class":160},[146,2573,2575,2577,2579,2581,2583,2585,2588,2590],{"class":148,"line":2574},14,[146,2576,2432],{"class":160},[146,2578,47],{"class":2435},[146,2580,727],{"class":160},[146,2582,730],{"class":160},[146,2584,1830],{"class":160},[146,2586,2587],{"class":1554},"Daily Production Summary with Operator Performance",[146,2589,727],{"class":160},[146,2591,736],{"class":160},[146,2593,2595,2597,2599,2601,2603,2605,2608,2610],{"class":148,"line":2594},15,[146,2596,2432],{"class":160},[146,2598,924],{"class":2435},[146,2600,727],{"class":160},[146,2602,730],{"class":160},[146,2604,1830],{"class":160},[146,2606,2607],{"class":1554},"subheader",[146,2609,727],{"class":160},[146,2611,736],{"class":160},[146,2613,2615,2617,2619,2621,2623,2625,2627,2629],{"class":148,"line":2614},16,[146,2616,2432],{"class":160},[146,2618,2365],{"class":2435},[146,2620,727],{"class":160},[146,2622,730],{"class":160},[146,2624,1830],{"class":160},[146,2626,2479],{"class":1554},[146,2628,727],{"class":160},[146,2630,736],{"class":160},[146,2632,2634,2636,2638,2640,2642,2644,2646,2648,2650,2652,2654,2656,2658],{"class":148,"line":2633},17,[146,2635,2432],{"class":160},[146,2637,2490],{"class":2435},[146,2639,727],{"class":160},[146,2641,730],{"class":160},[146,2643,1525],{"class":160},[146,2645,677],{"class":206},[146,2647,276],{"class":160},[146,2649,216],{"class":206},[146,2651,276],{"class":160},[146,2653,2503],{"class":206},[146,2655,276],{"class":160},[146,2657,2512],{"class":206},[146,2659,2515],{"class":160},[146,2661,2663],{"class":148,"line":2662},18,[146,2664,2520],{"class":160},[146,2666,2668],{"class":148,"line":2667},19,[146,2669,2427],{"class":160},[146,2671,2673,2675,2677,2679,2681,2683,2686,2688],{"class":148,"line":2672},20,[146,2674,2432],{"class":160},[146,2676,2230],{"class":2435},[146,2678,727],{"class":160},[146,2680,730],{"class":160},[146,2682,1830],{"class":160},[146,2684,2685],{"class":1554},"lightHorizontalLines",[146,2687,727],{"class":160},[146,2689,736],{"class":160},[146,2691,2693,2695,2697,2699,2701],{"class":148,"line":2692},21,[146,2694,2432],{"class":160},[146,2696,1053],{"class":2435},[146,2698,727],{"class":160},[146,2700,730],{"class":160},[146,2702,1517],{"class":160},[146,2704,2706,2709,2712,2714,2716,2718],{"class":148,"line":2705},22,[146,2707,2708],{"class":160},"        \"",[146,2710,2711],{"class":206},"headerRows",[146,2713,727],{"class":160},[146,2715,730],{"class":160},[146,2717,1964],{"class":206},[146,2719,736],{"class":160},[146,2721,2723,2725,2728,2730,2732,2734,2736,2739,2741,2743,2745,2747,2749,2751,2753,2756,2758,2760,2762,2764,2766,2768,2770,2772,2774,2776,2778,2780,2782],{"class":148,"line":2722},23,[146,2724,2708],{"class":160},[146,2726,2727],{"class":206},"widths",[146,2729,727],{"class":160},[146,2731,730],{"class":160},[146,2733,1525],{"class":160},[146,2735,727],{"class":160},[146,2737,2738],{"class":1554},"auto",[146,2740,727],{"class":160},[146,2742,276],{"class":160},[146,2744,1830],{"class":160},[146,2746,2738],{"class":1554},[146,2748,727],{"class":160},[146,2750,276],{"class":160},[146,2752,1830],{"class":160},[146,2754,2755],{"class":1554},"*",[146,2757,727],{"class":160},[146,2759,276],{"class":160},[146,2761,1830],{"class":160},[146,2763,2738],{"class":1554},[146,2765,727],{"class":160},[146,2767,276],{"class":160},[146,2769,1830],{"class":160},[146,2771,2738],{"class":1554},[146,2773,727],{"class":160},[146,2775,276],{"class":160},[146,2777,1830],{"class":160},[146,2779,2755],{"class":1554},[146,2781,727],{"class":160},[146,2783,2784],{"class":160},"],\n",[146,2786,2788,2790,2793,2795,2797],{"class":148,"line":2787},24,[146,2789,2708],{"class":160},[146,2791,2792],{"class":206},"body",[146,2794,727],{"class":160},[146,2796,730],{"class":160},[146,2798,2422],{"class":160},[146,2800,2802,2805,2807,2810,2812,2814,2816,2819,2821,2823,2825,2828,2830,2832,2834,2837,2839,2841,2843,2846,2848,2850,2852,2855,2857],{"class":148,"line":2801},25,[146,2803,2804],{"class":160},"          [",[146,2806,727],{"class":160},[146,2808,2809],{"class":1554},"Date",[146,2811,727],{"class":160},[146,2813,276],{"class":160},[146,2815,1830],{"class":160},[146,2817,2818],{"class":1554},"Shift",[146,2820,727],{"class":160},[146,2822,276],{"class":160},[146,2824,1830],{"class":160},[146,2826,2827],{"class":1554},"Product",[146,2829,727],{"class":160},[146,2831,276],{"class":160},[146,2833,1830],{"class":160},[146,2835,2836],{"class":1554},"Units Produced",[146,2838,727],{"class":160},[146,2840,276],{"class":160},[146,2842,1830],{"class":160},[146,2844,2845],{"class":1554},"Defective Units",[146,2847,727],{"class":160},[146,2849,276],{"class":160},[146,2851,1830],{"class":160},[146,2853,2854],{"class":1554},"Operator",[146,2856,727],{"class":160},[146,2858,2784],{"class":160},[146,2860,2862,2864,2866,2869,2871,2873,2875,2878,2880,2882,2884,2887,2889,2891,2893,2896,2898,2900,2902,2905,2907,2909,2911,2914,2916],{"class":148,"line":2861},26,[146,2863,2804],{"class":160},[146,2865,727],{"class":160},[146,2867,2868],{"class":1554},"2025-01-01",[146,2870,727],{"class":160},[146,2872,276],{"class":160},[146,2874,1830],{"class":160},[146,2876,2877],{"class":1554},"Morning",[146,2879,727],{"class":160},[146,2881,276],{"class":160},[146,2883,1830],{"class":160},[146,2885,2886],{"class":1554},"Product A",[146,2888,727],{"class":160},[146,2890,276],{"class":160},[146,2892,1830],{"class":160},[146,2894,2895],{"class":1554},"1000",[146,2897,727],{"class":160},[146,2899,276],{"class":160},[146,2901,1830],{"class":160},[146,2903,2904],{"class":1554},"20",[146,2906,727],{"class":160},[146,2908,276],{"class":160},[146,2910,1830],{"class":160},[146,2912,2913],{"class":1554},"John Doe",[146,2915,727],{"class":160},[146,2917,2784],{"class":160},[146,2919,2921,2923,2925,2927,2929,2931,2933,2936,2938,2940,2942,2945,2947,2949,2951,2954,2956,2958,2960,2963,2965,2967,2969,2972,2974],{"class":148,"line":2920},27,[146,2922,2804],{"class":160},[146,2924,727],{"class":160},[146,2926,2868],{"class":1554},[146,2928,727],{"class":160},[146,2930,276],{"class":160},[146,2932,1830],{"class":160},[146,2934,2935],{"class":1554},"Afternoon",[146,2937,727],{"class":160},[146,2939,276],{"class":160},[146,2941,1830],{"class":160},[146,2943,2944],{"class":1554},"Product B",[146,2946,727],{"class":160},[146,2948,276],{"class":160},[146,2950,1830],{"class":160},[146,2952,2953],{"class":1554},"950",[146,2955,727],{"class":160},[146,2957,276],{"class":160},[146,2959,1830],{"class":160},[146,2961,2962],{"class":1554},"15",[146,2964,727],{"class":160},[146,2966,276],{"class":160},[146,2968,1830],{"class":160},[146,2970,2971],{"class":1554},"Jane Smith",[146,2973,727],{"class":160},[146,2975,2784],{"class":160},[146,2977,2979,2981,2983,2986,2988,2990,2992,2994,2996,2998,3000,3002,3004,3006,3008,3011,3013,3015,3017,3019,3021,3023,3025,3028,3030],{"class":148,"line":2978},28,[146,2980,2804],{"class":160},[146,2982,727],{"class":160},[146,2984,2985],{"class":1554},"2025-01-02",[146,2987,727],{"class":160},[146,2989,276],{"class":160},[146,2991,1830],{"class":160},[146,2993,2877],{"class":1554},[146,2995,727],{"class":160},[146,2997,276],{"class":160},[146,2999,1830],{"class":160},[146,3001,2886],{"class":1554},[146,3003,727],{"class":160},[146,3005,276],{"class":160},[146,3007,1830],{"class":160},[146,3009,3010],{"class":1554},"1050",[146,3012,727],{"class":160},[146,3014,276],{"class":160},[146,3016,1830],{"class":160},[146,3018,829],{"class":1554},[146,3020,727],{"class":160},[146,3022,276],{"class":160},[146,3024,1830],{"class":160},[146,3026,3027],{"class":1554},"James Brown",[146,3029,727],{"class":160},[146,3031,2784],{"class":160},[146,3033,3035,3037,3039,3041,3043,3045,3047,3049,3051,3053,3055,3058,3060,3062,3064,3067,3069,3071,3073,3076,3078,3080,3082,3085,3087],{"class":148,"line":3034},29,[146,3036,2804],{"class":160},[146,3038,727],{"class":160},[146,3040,2985],{"class":1554},[146,3042,727],{"class":160},[146,3044,276],{"class":160},[146,3046,1830],{"class":160},[146,3048,2935],{"class":1554},[146,3050,727],{"class":160},[146,3052,276],{"class":160},[146,3054,1830],{"class":160},[146,3056,3057],{"class":1554},"Product C",[146,3059,727],{"class":160},[146,3061,276],{"class":160},[146,3063,1830],{"class":160},[146,3065,3066],{"class":1554},"800",[146,3068,727],{"class":160},[146,3070,276],{"class":160},[146,3072,1830],{"class":160},[146,3074,3075],{"class":1554},"30",[146,3077,727],{"class":160},[146,3079,276],{"class":160},[146,3081,1830],{"class":160},[146,3083,3084],{"class":1554},"Emily Clark",[146,3086,727],{"class":160},[146,3088,2784],{"class":160},[146,3090,3092,3094,3096,3099,3101,3103,3105,3107,3109,3111,3113,3115,3117,3119,3121,3124,3126,3128,3130,3133,3135,3137,3139,3142,3144],{"class":148,"line":3091},30,[146,3093,2804],{"class":160},[146,3095,727],{"class":160},[146,3097,3098],{"class":1554},"2025-01-03",[146,3100,727],{"class":160},[146,3102,276],{"class":160},[146,3104,1830],{"class":160},[146,3106,2877],{"class":1554},[146,3108,727],{"class":160},[146,3110,276],{"class":160},[146,3112,1830],{"class":160},[146,3114,2944],{"class":1554},[146,3116,727],{"class":160},[146,3118,276],{"class":160},[146,3120,1830],{"class":160},[146,3122,3123],{"class":1554},"1100",[146,3125,727],{"class":160},[146,3127,276],{"class":160},[146,3129,1830],{"class":160},[146,3131,3132],{"class":1554},"25",[146,3134,727],{"class":160},[146,3136,276],{"class":160},[146,3138,1830],{"class":160},[146,3140,3141],{"class":1554},"Michael Green",[146,3143,727],{"class":160},[146,3145,2784],{"class":160},[146,3147,3149,3151,3153,3155,3157,3159,3161,3163,3165,3167,3169,3171,3173,3175,3177,3180,3182,3184,3186,3189,3191,3193,3195,3198,3200],{"class":148,"line":3148},31,[146,3150,2804],{"class":160},[146,3152,727],{"class":160},[146,3154,3098],{"class":1554},[146,3156,727],{"class":160},[146,3158,276],{"class":160},[146,3160,1830],{"class":160},[146,3162,2935],{"class":1554},[146,3164,727],{"class":160},[146,3166,276],{"class":160},[146,3168,1830],{"class":160},[146,3170,2886],{"class":1554},[146,3172,727],{"class":160},[146,3174,276],{"class":160},[146,3176,1830],{"class":160},[146,3178,3179],{"class":1554},"980",[146,3181,727],{"class":160},[146,3183,276],{"class":160},[146,3185,1830],{"class":160},[146,3187,3188],{"class":1554},"18",[146,3190,727],{"class":160},[146,3192,276],{"class":160},[146,3194,1830],{"class":160},[146,3196,3197],{"class":1554},"Sarah White",[146,3199,727],{"class":160},[146,3201,2515],{"class":160},[146,3203,3205],{"class":148,"line":3204},32,[146,3206,3207],{"class":160},"        ]\n",[146,3209,3211],{"class":148,"line":3210},33,[146,3212,3213],{"class":160},"      }\n",[146,3215,3217],{"class":148,"line":3216},34,[146,3218,2520],{"class":160},[146,3220,3222],{"class":148,"line":3221},35,[146,3223,2427],{"class":160},[146,3225,3227,3229,3231,3233,3235,3237,3240,3242],{"class":148,"line":3226},36,[146,3228,2432],{"class":160},[146,3230,47],{"class":2435},[146,3232,727],{"class":160},[146,3234,730],{"class":160},[146,3236,1830],{"class":160},[146,3238,3239],{"class":1554},"This table summarizes the daily production output across different shifts and operators. It includes total units produced and defective units recorded for quality analysis.",[146,3241,727],{"class":160},[146,3243,736],{"class":160},[146,3245,3247,3249,3252,3254,3256,3259],{"class":148,"line":3246},37,[146,3248,2432],{"class":160},[146,3250,3251],{"class":2435},"fontSize",[146,3253,727],{"class":160},[146,3255,730],{"class":160},[146,3257,3258],{"class":206}," 12",[146,3260,736],{"class":160},[146,3262,3264,3266,3268,3270,3272,3274,3277,3279],{"class":148,"line":3263},38,[146,3265,2432],{"class":160},[146,3267,2365],{"class":2435},[146,3269,727],{"class":160},[146,3271,730],{"class":160},[146,3273,1830],{"class":160},[146,3275,3276],{"class":1554},"justify",[146,3278,727],{"class":160},[146,3280,736],{"class":160},[146,3282,3284,3286,3288,3290,3292,3294,3296,3298,3300,3302,3304,3306,3308],{"class":148,"line":3283},39,[146,3285,2432],{"class":160},[146,3287,2490],{"class":2435},[146,3289,727],{"class":160},[146,3291,730],{"class":160},[146,3293,1525],{"class":160},[146,3295,677],{"class":206},[146,3297,276],{"class":160},[146,3299,216],{"class":206},[146,3301,276],{"class":160},[146,3303,2503],{"class":206},[146,3305,276],{"class":160},[146,3307,2512],{"class":206},[146,3309,2515],{"class":160},[146,3311,3313],{"class":148,"line":3312},40,[146,3314,2520],{"class":160},[146,3316,3318],{"class":148,"line":3317},41,[146,3319,2427],{"class":160},[146,3321,3323,3325,3327,3329,3331,3333,3336,3338],{"class":148,"line":3322},42,[146,3324,2432],{"class":160},[146,3326,47],{"class":2435},[146,3328,727],{"class":160},[146,3330,730],{"class":160},[146,3332,1830],{"class":160},[146,3334,3335],{"class":1554},"Internal Use Only - Manufacturing Co.",[146,3337,727],{"class":160},[146,3339,736],{"class":160},[146,3341,3343,3345,3347,3349,3351,3353,3355,3357],{"class":148,"line":3342},43,[146,3344,2432],{"class":160},[146,3346,924],{"class":2435},[146,3348,727],{"class":160},[146,3350,730],{"class":160},[146,3352,1830],{"class":160},[146,3354,2290],{"class":1554},[146,3356,727],{"class":160},[146,3358,736],{"class":160},[146,3360,3362,3364,3366,3368,3370,3372,3374,3376],{"class":148,"line":3361},44,[146,3363,2432],{"class":160},[146,3365,2365],{"class":2435},[146,3367,727],{"class":160},[146,3369,730],{"class":160},[146,3371,1830],{"class":160},[146,3373,2479],{"class":1554},[146,3375,727],{"class":160},[146,3377,736],{"class":160},[146,3379,3381,3383,3385,3387,3389,3391,3393,3395,3397,3399,3401,3403,3405],{"class":148,"line":3380},45,[146,3382,2432],{"class":160},[146,3384,2490],{"class":2435},[146,3386,727],{"class":160},[146,3388,730],{"class":160},[146,3390,1525],{"class":160},[146,3392,677],{"class":206},[146,3394,276],{"class":160},[146,3396,2512],{"class":206},[146,3398,276],{"class":160},[146,3400,2503],{"class":206},[146,3402,276],{"class":160},[146,3404,2503],{"class":206},[146,3406,2515],{"class":160},[146,3408,3410],{"class":148,"line":3409},46,[146,3411,3412],{"class":160},"    }\n",[146,3414,3416],{"class":148,"line":3415},47,[146,3417,3418],{"class":160},"  ],\n",[146,3420,3422,3424,3427,3429,3431],{"class":148,"line":3421},48,[146,3423,722],{"class":160},[146,3425,3426],{"class":152},"styles",[146,3428,727],{"class":160},[146,3430,730],{"class":160},[146,3432,1517],{"class":160},[146,3434,3436,3439,3441,3443,3445],{"class":148,"line":3435},49,[146,3437,3438],{"class":160},"    \"",[146,3440,2275],{"class":2435},[146,3442,727],{"class":160},[146,3444,730],{"class":160},[146,3446,1517],{"class":160},[146,3448,3450,3452,3454,3456,3458,3460],{"class":148,"line":3449},50,[146,3451,2432],{"class":160},[146,3453,3251],{"class":206},[146,3455,727],{"class":160},[146,3457,730],{"class":160},[146,3459,2512],{"class":206},[146,3461,736],{"class":160},[146,3463,3465,3467,3470,3472,3474],{"class":148,"line":3464},51,[146,3466,2432],{"class":160},[146,3468,3469],{"class":206},"bold",[146,3471,727],{"class":160},[146,3473,730],{"class":160},[146,3475,3476],{"class":160}," true,\n",[146,3478,3480,3482,3484,3486,3488,3490,3492,3494],{"class":148,"line":3479},52,[146,3481,2432],{"class":160},[146,3483,2365],{"class":206},[146,3485,727],{"class":160},[146,3487,730],{"class":160},[146,3489,1830],{"class":160},[146,3491,2479],{"class":1554},[146,3493,727],{"class":160},[146,3495,736],{"class":160},[146,3497,3499,3501,3503,3505,3507,3509,3511,3513,3515,3517,3519,3521,3523],{"class":148,"line":3498},53,[146,3500,2432],{"class":160},[146,3502,2490],{"class":206},[146,3504,727],{"class":160},[146,3506,730],{"class":160},[146,3508,1525],{"class":160},[146,3510,677],{"class":206},[146,3512,276],{"class":160},[146,3514,2512],{"class":206},[146,3516,276],{"class":160},[146,3518,2503],{"class":206},[146,3520,276],{"class":160},[146,3522,216],{"class":206},[146,3524,2515],{"class":160},[146,3526,3528],{"class":148,"line":3527},54,[146,3529,2520],{"class":160},[146,3531,3533,3535,3537,3539,3541],{"class":148,"line":3532},55,[146,3534,3438],{"class":160},[146,3536,2607],{"class":2435},[146,3538,727],{"class":160},[146,3540,730],{"class":160},[146,3542,1517],{"class":160},[146,3544,3546,3548,3550,3552,3554,3557],{"class":148,"line":3545},56,[146,3547,2432],{"class":160},[146,3549,3251],{"class":206},[146,3551,727],{"class":160},[146,3553,730],{"class":160},[146,3555,3556],{"class":206}," 14",[146,3558,736],{"class":160},[146,3560,3562,3564,3567,3569,3571],{"class":148,"line":3561},57,[146,3563,2432],{"class":160},[146,3565,3566],{"class":206},"italics",[146,3568,727],{"class":160},[146,3570,730],{"class":160},[146,3572,3476],{"class":160},[146,3574,3576,3578,3581,3583,3585,3587,3590,3592],{"class":148,"line":3575},58,[146,3577,2432],{"class":160},[146,3579,3580],{"class":206},"color",[146,3582,727],{"class":160},[146,3584,730],{"class":160},[146,3586,1830],{"class":160},[146,3588,3589],{"class":1554},"grey",[146,3591,727],{"class":160},[146,3593,736],{"class":160},[146,3595,3597,3599,3601,3603,3605,3607,3609,3611,3613,3615,3617,3619,3621],{"class":148,"line":3596},59,[146,3598,2432],{"class":160},[146,3600,2490],{"class":206},[146,3602,727],{"class":160},[146,3604,730],{"class":160},[146,3606,1525],{"class":160},[146,3608,677],{"class":206},[146,3610,276],{"class":160},[146,3612,216],{"class":206},[146,3614,276],{"class":160},[146,3616,2503],{"class":206},[146,3618,276],{"class":160},[146,3620,2512],{"class":206},[146,3622,2515],{"class":160},[146,3624,3626],{"class":148,"line":3625},60,[146,3627,2520],{"class":160},[146,3629,3631,3633,3635,3637,3639],{"class":148,"line":3630},61,[146,3632,3438],{"class":160},[146,3634,2290],{"class":2435},[146,3636,727],{"class":160},[146,3638,730],{"class":160},[146,3640,1517],{"class":160},[146,3642,3644,3646,3648,3650,3652,3654],{"class":148,"line":3643},62,[146,3645,2432],{"class":160},[146,3647,3251],{"class":206},[146,3649,727],{"class":160},[146,3651,730],{"class":160},[146,3653,216],{"class":206},[146,3655,736],{"class":160},[146,3657,3659,3661,3663,3665,3667,3669,3671],{"class":148,"line":3658},63,[146,3660,2432],{"class":160},[146,3662,3580],{"class":206},[146,3664,727],{"class":160},[146,3666,730],{"class":160},[146,3668,1830],{"class":160},[146,3670,3589],{"class":1554},[146,3672,2561],{"class":160},[146,3674,3676],{"class":148,"line":3675},64,[146,3677,3412],{"class":160},[146,3679,3681],{"class":148,"line":3680},65,[146,3682,3683],{"class":160},"  },\n",[146,3685,3687,3689,3691,3693,3695,3697,3700,3702],{"class":148,"line":3686},66,[146,3688,722],{"class":160},[146,3690,2245],{"class":152},[146,3692,727],{"class":160},[146,3694,730],{"class":160},[146,3696,1830],{"class":160},[146,3698,3699],{"class":1554},"A4",[146,3701,727],{"class":160},[146,3703,736],{"class":160},[146,3705,3707,3709,3711,3713,3715,3717,3720,3722,3725,3727,3730,3732,3734],{"class":148,"line":3706},67,[146,3708,722],{"class":160},[146,3710,2260],{"class":152},[146,3712,727],{"class":160},[146,3714,730],{"class":160},[146,3716,1525],{"class":160},[146,3718,3719],{"class":206},"40",[146,3721,276],{"class":160},[146,3723,3724],{"class":206}," 60",[146,3726,276],{"class":160},[146,3728,3729],{"class":206}," 40",[146,3731,276],{"class":160},[146,3733,3724],{"class":206},[146,3735,2515],{"class":160},[146,3737,3739],{"class":148,"line":3738},68,[146,3740,754],{"class":160},[15,3742,3743],{},"This example creates a simple PDF featuring a centered logo, a title, a subtitle, a table with a light horizontal line layout, a paragraph of text, and a footer at the end. The following screenshot shows how it looks. You can customize it by adjusting the styles, layout, and content.",[15,3745,3746,3750],{},[392,3747],{"alt":3748,"dataZoomable":50,"src":3749},"Example pdf result","\u002Fblog\u002F2025\u002F05\u002Fimages\u002Fexample-pdf.png",[397,3751,3748],{},[996,3753,3755],{"id":3754},"step-3-creating-a-flow-to-generate-a-pdf","Step 3: Creating a Flow to Generate a PDF",[15,3757,3758],{},"Let's learn how to generate a PDF using dynamic inputs. For this, we’ll use the same example PDF report shown earlier, but this time, we’ll replace the hardcoded values with dynamic input data.",[326,3760,3761],{},[103,3762,3763],{},"For this guide's practical example, we will use the following SQLite flow that generates simulated production data. If you don't have the data source, you can import the flow below to follow along. After importing, deploy the flow and click the Inject node button to generate and insert the data.",[15,3765,3766],{},"When generating PDFs for your specific data, start by creating a flow to collect the information you want in the report. This data can come from sensors, databases, APIs, or even manual inputs.",[15,3768,3769,3770,3830],{},"{% renderFlow 300 %}\n",[146,3771,3772,3773,3776,3777,3780,3781,3783,3784,3787,3788,3790,3791,3794,3795,3798,3799,3802,3803,3805,3806,3808,3809,3812,3813,3816,3817,3820,3821,3823,3824,3827,3828,1596],{},"{\"id\":\"1e73fef718bb4876\",\"type\":\"group\",\"z\":\"b37428694e90b2c5\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,3774,3775],{},"\"5169b96ad66dcff6\",\"b75fde37ea431d84\",\"a571bbd7b0c0cb25\"",",\"x\":14,\"y\":59,\"w\":812,\"h\":82},{\"id\":\"5169b96ad66dcff6\",\"type\":\"inject\",\"z\":\"b37428694e90b2c5\",\"g\":\"1e73fef718bb4876\",\"name\":\"Create Table\",\"props\":",[146,3778,3779],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"vt\":\"str\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":130,\"y\":100,\"wires\":[[\"b75fde37ea431d84\"]]},{\"id\":\"b75fde37ea431d84\",\"type\":\"sqlite\",\"z\":\"b37428694e90b2c5\",\"g\":\"1e73fef718bb4876\",\"mydb\":\"1ae6d7f7fdb60191\",\"sqlquery\":\"fixed\",\"sql\":\"CREATE TABLE IF NOT EXISTS production_report (\\n    id INTEGER PRIMARY KEY AUTOINCREMENT,\\n    date TEXT NOT NULL,\\n    shift TEXT NOT NULL,\\n    product TEXT NOT NULL,\\n    units_produced INTEGER NOT NULL,\\n    defective_units INTEGER NOT NULL,\\n    operator TEXT NOT NULL\\n);\",\"name\":\"\",\"x\":440,\"y\":100,\"wires\":[[\"a571bbd7b0c0cb25\"]]},{\"id\":\"a571bbd7b0c0cb25\",\"type\":\"debug\",\"z\":\"b37428694e90b2c5\",\"g\":\"1e73fef718bb4876\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":720,\"y\":100,\"wires\":",[146,3782],{},"},{\"id\":\"1ae6d7f7fdb60191\",\"type\":\"sqlitedb\",\"db\":\"productiondata.sqlite\",\"mode\":\"RWC\"},{\"id\":\"ccca7810c6b3db41\",\"type\":\"group\",\"z\":\"b37428694e90b2c5\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,3785,3786],{},"\"19ad08d3015ef8f2\",\"b706e4aa8a2d0740\",\"f32cdc1dd16b56b7\",\"3708b00ae17defa5\",\"c4464e3454a8805e\",\"2df338e18c9a60d5\"",",\"x\":14,\"y\":179,\"w\":1332,\"h\":82},{\"id\":\"19ad08d3015ef8f2\",\"type\":\"sqlite\",\"z\":\"b37428694e90b2c5\",\"g\":\"ccca7810c6b3db41\",\"mydb\":\"1ae6d7f7fdb60191\",\"sqlquery\":\"prepared\",\"sql\":\"INSERT INTO production_report (\\n    date,\\n    shift,\\n    product,\\n    units_produced,\\n    defective_units,\\n    operator\\n) VALUES (\\n    $date,\\n    $shift,\\n    $product,\\n    $units_produced,\\n    $defective_units,\\n    $operator\\n);\\n\",\"name\":\"\",\"x\":1060,\"y\":220,\"wires\":[[\"2df338e18c9a60d5\"]]},{\"id\":\"b706e4aa8a2d0740\",\"type\":\"inject\",\"z\":\"b37428694e90b2c5\",\"g\":\"ccca7810c6b3db41\",\"name\":\"Click to generate and insert data\",\"props\":",[146,3789,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":190,\"y\":220,\"wires\":[[\"f32cdc1dd16b56b7\"]]},{\"id\":\"f32cdc1dd16b56b7\",\"type\":\"function\",\"z\":\"b37428694e90b2c5\",\"g\":\"ccca7810c6b3db41\",\"name\":\"Generate Simulated Production Data\",\"func\":\"const products = ",[146,3792,3793],{},"\"Widget A\", \"Widget B\", \"Gadget X\", \"Component Z\"",";\\nconst operators = ",[146,3796,3797],{},"\"John Matthews\", \"Sarah Lee\", \"Amit Kumar\", \"Rita Patel\"",";\\nconst shifts = ",[146,3800,3801],{},"\"A\", \"B\", \"C\"",";\\n\\nfunction getRandomInt(min, max) {\\n    return Math.floor(Math.random() * (max - min + 1)) + min;\\n}\\n\\nconst data = ",[146,3804],{},";\\n\\nfor (let i = 0; i \u003C 10; i++) {\\n    const date = new Date();\\n    date.setDate(date.getDate() - i); \u002F\u002F Last 10 days\\n\\n    data.push({\\n        date: date.toISOString().split('T')",[146,3807,677],{},",\\n        shift: shifts",[146,3810,3811],{},"getRandomInt(0, shifts.length - 1)",",\\n        product: products",[146,3814,3815],{},"getRandomInt(0, products.length - 1)",",\\n        units_produced: getRandomInt(400, 600),\\n        defective_units: getRandomInt(0, 10),\\n        operator: operators",[146,3818,3819],{},"getRandomInt(0, operators.length - 1)","\\n    });\\n}\\n\\nmsg.payload = data;\\nreturn msg;\\n\",\"outputs\":1,\"timeout\":0,\"noerr\":0,\"initialize\":\"\",\"finalize\":\"\",\"libs\":",[146,3822],{},",\"x\":490,\"y\":220,\"wires\":[[\"3708b00ae17defa5\"]]},{\"id\":\"3708b00ae17defa5\",\"type\":\"split\",\"z\":\"b37428694e90b2c5\",\"g\":\"ccca7810c6b3db41\",\"name\":\"\",\"splt\":\"\\n\",\"spltType\":\"str\",\"arraySplt\":1,\"arraySpltType\":\"len\",\"stream\":false,\"addname\":\"\",\"property\":\"payload\",\"x\":710,\"y\":220,\"wires\":[[\"c4464e3454a8805e\"]]},{\"id\":\"c4464e3454a8805e\",\"type\":\"change\",\"z\":\"b37428694e90b2c5\",\"g\":\"ccca7810c6b3db41\",\"name\":\"\",\"rules\":",[146,3825,3826],{},"{\"t\":\"set\",\"p\":\"params\",\"pt\":\"msg\",\"to\":\"{}\",\"tot\":\"json\"},{\"t\":\"set\",\"p\":\"params.$date\",\"pt\":\"msg\",\"to\":\"payload.date\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"params.$shift\",\"pt\":\"msg\",\"to\":\"payload.shift\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"params.$product\",\"pt\":\"msg\",\"to\":\"payload.product\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"params.$units_produced\",\"pt\":\"msg\",\"to\":\"payload.units_produced\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"params.$defective_units\",\"pt\":\"msg\",\"to\":\"payload.defective_units\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"params.$operator\",\"pt\":\"msg\",\"to\":\"payload.operator\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":860,\"y\":220,\"wires\":[[\"19ad08d3015ef8f2\"]]},{\"id\":\"2df338e18c9a60d5\",\"type\":\"debug\",\"z\":\"b37428694e90b2c5\",\"g\":\"ccca7810c6b3db41\",\"name\":\"debug 3\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":1240,\"y\":220,\"wires\":",[146,3829],{},"\n{% endrenderFlow %}",[326,3832,3833,3837],{"start":185},[103,3834,369,3835,1340],{},[338,3836,372],{},[103,3838,369,3839,3842,3843,3845],{},[338,3840,3841],{},"SQLite"," node and connect it to the Inject node. Configure the ",[338,3844,3841],{}," node with the same database to generate the simulated data. Set the SQL Query type to \"fixed statement\" and use the following query:",[42,3847,3851],{"className":3848,"code":3849,"language":3850,"meta":50,"style":50},"language-sql shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","SELECT * FROM production_report;\n","sql",[19,3852,3853],{"__ignoreMap":50},[146,3854,3855],{"class":148,"line":149},[146,3856,3849],{},[326,3858,3859],{"start":250},[103,3860,408,3861,3864],{},[338,3862,3863],{},"Function"," node onto the canvas and paste the following JavaScript code. When generating PDFs for your specific data, make sure to adjust the code to match your data structure.",[42,3866,3868],{"className":140,"code":3867,"language":142,"meta":50,"style":50},"\u002F\u002F Initialize table body with headers\nconst tableBody = [\n    ['Date', 'Shift', 'Product', 'Units Produced', 'Defective Units', 'Operator']\n];\n\nconst logo = `\u003Creplace-this-your-logo-svg`\n\n\u002F\u002F Loop through data rows from SQLite (msg.payload)\nfor (const row of msg.payload) {\n    tableBody.push([\n        row.date,\n        row.shift,\n        row.product,\n        row.units_produced.toString(),\n        row.defective_units.toString(),\n        row.operator\n    ]);\n}\n\nconst docDefinition = {\n    content: [\n        {\n            svg: logo,\n            width: 150, \u002F\u002F Adjust the logo size as needed\n            alignment: 'center',\n            margin: [0, 0, 0, 20]\n        },\n        \u002F\u002F Header\n        {\n            text: 'Production Report - 2025',\n            style: 'header'\n        },\n\n        \u002F\u002F Subheader\n        {\n            text: 'Daily Production Summary with Operator Performance',\n            style: 'subheader',\n            alignment: 'center',\n            margin: [0, 10, 0, 20]\n        },\n\n        \u002F\u002F Table\n        {\n            layout: 'lightHorizontalLines',\n            table: {\n                headerRows: 1,\n                widths: ['auto', 'auto', '*', 'auto', 'auto', '*'],\n                body: tableBody\n            }\n        },\n\n        \u002F\u002F Description\n        {\n            text: 'This table summarizes the daily production output across different shifts and operators. It includes total units produced and defective units recorded for quality analysis.',\n            fontSize: 12,\n            alignment: 'justify',\n            margin: [0, 10, 0, 20]\n        },\n\n        \u002F\u002F Footer\n        {\n            text: 'Internal Use Only - Manufacturing Co.',\n            style: 'footer',\n            alignment: 'center',\n            margin: [0, 20, 0, 0]\n        }\n    ],\n\n    styles: {\n        header: {\n            fontSize: 20,\n            bold: true,\n            alignment: 'center',\n            margin: [0, 20, 0, 10]\n        },\n        subheader: {\n            fontSize: 14,\n            italics: true,\n            color: 'grey',\n            margin: [0, 10, 0, 20]\n        },\n        footer: {\n            fontSize: 10,\n            color: 'grey'\n        }\n    },\n\n    pageSize: 'A4',\n    pageMargins: [40, 60, 40, 60]\n};\n\nmsg.payload = docDefinition;\nreturn msg;\n",[19,3869,3870,3876,3887,3940,3946,3950,3968,3972,3977,4002,4015,4027,4038,4049,4067,4084,4093,4100,4104,4108,4119,4128,4133,4145,4159,4174,4199,4204,4209,4213,4228,4242,4246,4250,4255,4259,4273,4287,4301,4325,4329,4333,4338,4342,4357,4366,4377,4436,4446,4451,4455,4459,4464,4468,4482,4493,4507,4531,4535,4539,4544,4548,4562,4576,4590,4614,4619,4626,4630,4640,4650,4661,4674,4689,4714,4719,4729,4740,4752,4768,4793,4798,4808,4819,4832,4837,4842,4847,4863,4889,4894,4899,4915],{"__ignoreMap":50},[146,3871,3872],{"class":148,"line":149},[146,3873,3875],{"class":3874},"sHwdD","\u002F\u002F Initialize table body with headers\n",[146,3877,3878,3880,3883,3885],{"class":148,"line":185},[146,3879,153],{"class":152},[146,3881,3882],{"class":156}," tableBody ",[146,3884,161],{"class":160},[146,3886,2422],{"class":156},[146,3888,3889,3892,3894,3896,3898,3900,3902,3904,3906,3908,3910,3912,3914,3916,3918,3920,3922,3924,3926,3928,3930,3932,3934,3936,3938],{"class":148,"line":221},[146,3890,3891],{"class":156},"    [",[146,3893,1471],{"class":160},[146,3895,2809],{"class":1554},[146,3897,1471],{"class":160},[146,3899,276],{"class":160},[146,3901,1474],{"class":160},[146,3903,2818],{"class":1554},[146,3905,1471],{"class":160},[146,3907,276],{"class":160},[146,3909,1474],{"class":160},[146,3911,2827],{"class":1554},[146,3913,1471],{"class":160},[146,3915,276],{"class":160},[146,3917,1474],{"class":160},[146,3919,2836],{"class":1554},[146,3921,1471],{"class":160},[146,3923,276],{"class":160},[146,3925,1474],{"class":160},[146,3927,2845],{"class":1554},[146,3929,1471],{"class":160},[146,3931,276],{"class":160},[146,3933,1474],{"class":160},[146,3935,2854],{"class":1554},[146,3937,1471],{"class":160},[146,3939,2515],{"class":156},[146,3941,3942,3944],{"class":148,"line":250},[146,3943,1536],{"class":156},[146,3945,182],{"class":160},[146,3947,3948],{"class":148,"line":257},[146,3949,254],{"emptyLinePlaceholder":253},[146,3951,3952,3954,3957,3959,3962,3965],{"class":148,"line":284},[146,3953,153],{"class":152},[146,3955,3956],{"class":156}," logo ",[146,3958,161],{"class":160},[146,3960,3961],{"class":160}," `",[146,3963,3964],{"class":1554},"\u003Creplace-this-your-logo-svg",[146,3966,3967],{"class":160},"`\n",[146,3969,3970],{"class":148,"line":666},[146,3971,254],{"emptyLinePlaceholder":253},[146,3973,3974],{"class":148,"line":1603},[146,3975,3976],{"class":3874},"\u002F\u002F Loop through data rows from SQLite (msg.payload)\n",[146,3978,3979,3982,3985,3987,3990,3993,3995,3997,4000],{"class":148,"line":1608},[146,3980,3981],{"class":287},"for",[146,3983,3984],{"class":156}," (",[146,3986,153],{"class":152},[146,3988,3989],{"class":156}," row ",[146,3991,3992],{"class":160},"of",[146,3994,291],{"class":156},[146,3996,167],{"class":160},[146,3998,3999],{"class":156},"payload) ",[146,4001,717],{"class":160},[146,4003,4004,4007,4009,4012],{"class":148,"line":1624},[146,4005,4006],{"class":156},"    tableBody",[146,4008,167],{"class":160},[146,4010,4011],{"class":170},"push",[146,4013,4014],{"class":1549},"([\n",[146,4016,4017,4020,4022,4025],{"class":148,"line":2546},[146,4018,4019],{"class":156},"        row",[146,4021,167],{"class":160},[146,4023,4024],{"class":156},"date",[146,4026,736],{"class":160},[146,4028,4029,4031,4033,4036],{"class":148,"line":2564},[146,4030,4019],{"class":156},[146,4032,167],{"class":160},[146,4034,4035],{"class":156},"shift",[146,4037,736],{"class":160},[146,4039,4040,4042,4044,4047],{"class":148,"line":2569},[146,4041,4019],{"class":156},[146,4043,167],{"class":160},[146,4045,4046],{"class":156},"product",[146,4048,736],{"class":160},[146,4050,4051,4053,4055,4058,4060,4063,4065],{"class":148,"line":2574},[146,4052,4019],{"class":156},[146,4054,167],{"class":160},[146,4056,4057],{"class":156},"units_produced",[146,4059,167],{"class":160},[146,4061,4062],{"class":170},"toString",[146,4064,1461],{"class":1549},[146,4066,736],{"class":160},[146,4068,4069,4071,4073,4076,4078,4080,4082],{"class":148,"line":2594},[146,4070,4019],{"class":156},[146,4072,167],{"class":160},[146,4074,4075],{"class":156},"defective_units",[146,4077,167],{"class":160},[146,4079,4062],{"class":170},[146,4081,1461],{"class":1549},[146,4083,736],{"class":160},[146,4085,4086,4088,4090],{"class":148,"line":2614},[146,4087,4019],{"class":156},[146,4089,167],{"class":160},[146,4091,4092],{"class":156},"operator\n",[146,4094,4095,4098],{"class":148,"line":2633},[146,4096,4097],{"class":1549},"    ])",[146,4099,182],{"class":160},[146,4101,4102],{"class":148,"line":2662},[146,4103,754],{"class":160},[146,4105,4106],{"class":148,"line":2667},[146,4107,254],{"emptyLinePlaceholder":253},[146,4109,4110,4112,4115,4117],{"class":148,"line":2672},[146,4111,153],{"class":152},[146,4113,4114],{"class":156}," docDefinition ",[146,4116,161],{"class":160},[146,4118,1517],{"class":160},[146,4120,4121,4124,4126],{"class":148,"line":2692},[146,4122,4123],{"class":1549},"    content",[146,4125,730],{"class":160},[146,4127,2422],{"class":156},[146,4129,4130],{"class":148,"line":2705},[146,4131,4132],{"class":160},"        {\n",[146,4134,4135,4138,4140,4143],{"class":148,"line":2722},[146,4136,4137],{"class":1549},"            svg",[146,4139,730],{"class":160},[146,4141,4142],{"class":156}," logo",[146,4144,736],{"class":160},[146,4146,4147,4150,4152,4154,4156],{"class":148,"line":2787},[146,4148,4149],{"class":1549},"            width",[146,4151,730],{"class":160},[146,4153,2462],{"class":206},[146,4155,276],{"class":160},[146,4157,4158],{"class":3874}," \u002F\u002F Adjust the logo size as needed\n",[146,4160,4161,4164,4166,4168,4170,4172],{"class":148,"line":2801},[146,4162,4163],{"class":1549},"            alignment",[146,4165,730],{"class":160},[146,4167,1474],{"class":160},[146,4169,2479],{"class":1554},[146,4171,1471],{"class":160},[146,4173,736],{"class":160},[146,4175,4176,4179,4181,4183,4185,4187,4189,4191,4193,4195,4197],{"class":148,"line":2861},[146,4177,4178],{"class":1549},"            margin",[146,4180,730],{"class":160},[146,4182,1525],{"class":156},[146,4184,677],{"class":206},[146,4186,276],{"class":160},[146,4188,2503],{"class":206},[146,4190,276],{"class":160},[146,4192,2503],{"class":206},[146,4194,276],{"class":160},[146,4196,2512],{"class":206},[146,4198,2515],{"class":156},[146,4200,4201],{"class":148,"line":2920},[146,4202,4203],{"class":160},"        },\n",[146,4205,4206],{"class":148,"line":2978},[146,4207,4208],{"class":3874},"        \u002F\u002F Header\n",[146,4210,4211],{"class":148,"line":3034},[146,4212,4132],{"class":160},[146,4214,4215,4218,4220,4222,4224,4226],{"class":148,"line":3091},[146,4216,4217],{"class":1549},"            text",[146,4219,730],{"class":160},[146,4221,1474],{"class":160},[146,4223,2539],{"class":1554},[146,4225,1471],{"class":160},[146,4227,736],{"class":160},[146,4229,4230,4233,4235,4237,4239],{"class":148,"line":3148},[146,4231,4232],{"class":1549},"            style",[146,4234,730],{"class":160},[146,4236,1474],{"class":160},[146,4238,2275],{"class":1554},[146,4240,4241],{"class":160},"'\n",[146,4243,4244],{"class":148,"line":3204},[146,4245,4203],{"class":160},[146,4247,4248],{"class":148,"line":3210},[146,4249,254],{"emptyLinePlaceholder":253},[146,4251,4252],{"class":148,"line":3216},[146,4253,4254],{"class":3874},"        \u002F\u002F Subheader\n",[146,4256,4257],{"class":148,"line":3221},[146,4258,4132],{"class":160},[146,4260,4261,4263,4265,4267,4269,4271],{"class":148,"line":3226},[146,4262,4217],{"class":1549},[146,4264,730],{"class":160},[146,4266,1474],{"class":160},[146,4268,2587],{"class":1554},[146,4270,1471],{"class":160},[146,4272,736],{"class":160},[146,4274,4275,4277,4279,4281,4283,4285],{"class":148,"line":3246},[146,4276,4232],{"class":1549},[146,4278,730],{"class":160},[146,4280,1474],{"class":160},[146,4282,2607],{"class":1554},[146,4284,1471],{"class":160},[146,4286,736],{"class":160},[146,4288,4289,4291,4293,4295,4297,4299],{"class":148,"line":3263},[146,4290,4163],{"class":1549},[146,4292,730],{"class":160},[146,4294,1474],{"class":160},[146,4296,2479],{"class":1554},[146,4298,1471],{"class":160},[146,4300,736],{"class":160},[146,4302,4303,4305,4307,4309,4311,4313,4315,4317,4319,4321,4323],{"class":148,"line":3283},[146,4304,4178],{"class":1549},[146,4306,730],{"class":160},[146,4308,1525],{"class":156},[146,4310,677],{"class":206},[146,4312,276],{"class":160},[146,4314,216],{"class":206},[146,4316,276],{"class":160},[146,4318,2503],{"class":206},[146,4320,276],{"class":160},[146,4322,2512],{"class":206},[146,4324,2515],{"class":156},[146,4326,4327],{"class":148,"line":3312},[146,4328,4203],{"class":160},[146,4330,4331],{"class":148,"line":3317},[146,4332,254],{"emptyLinePlaceholder":253},[146,4334,4335],{"class":148,"line":3322},[146,4336,4337],{"class":3874},"        \u002F\u002F Table\n",[146,4339,4340],{"class":148,"line":3342},[146,4341,4132],{"class":160},[146,4343,4344,4347,4349,4351,4353,4355],{"class":148,"line":3361},[146,4345,4346],{"class":1549},"            layout",[146,4348,730],{"class":160},[146,4350,1474],{"class":160},[146,4352,2685],{"class":1554},[146,4354,1471],{"class":160},[146,4356,736],{"class":160},[146,4358,4359,4362,4364],{"class":148,"line":3380},[146,4360,4361],{"class":1549},"            table",[146,4363,730],{"class":160},[146,4365,1517],{"class":160},[146,4367,4368,4371,4373,4375],{"class":148,"line":3409},[146,4369,4370],{"class":1549},"                headerRows",[146,4372,730],{"class":160},[146,4374,1964],{"class":206},[146,4376,736],{"class":160},[146,4378,4379,4382,4384,4386,4388,4390,4392,4394,4396,4398,4400,4402,4404,4406,4408,4410,4412,4414,4416,4418,4420,4422,4424,4426,4428,4430,4432,4434],{"class":148,"line":3415},[146,4380,4381],{"class":1549},"                widths",[146,4383,730],{"class":160},[146,4385,1525],{"class":156},[146,4387,1471],{"class":160},[146,4389,2738],{"class":1554},[146,4391,1471],{"class":160},[146,4393,276],{"class":160},[146,4395,1474],{"class":160},[146,4397,2738],{"class":1554},[146,4399,1471],{"class":160},[146,4401,276],{"class":160},[146,4403,1474],{"class":160},[146,4405,2755],{"class":1554},[146,4407,1471],{"class":160},[146,4409,276],{"class":160},[146,4411,1474],{"class":160},[146,4413,2738],{"class":1554},[146,4415,1471],{"class":160},[146,4417,276],{"class":160},[146,4419,1474],{"class":160},[146,4421,2738],{"class":1554},[146,4423,1471],{"class":160},[146,4425,276],{"class":160},[146,4427,1474],{"class":160},[146,4429,2755],{"class":1554},[146,4431,1471],{"class":160},[146,4433,1536],{"class":156},[146,4435,736],{"class":160},[146,4437,4438,4441,4443],{"class":148,"line":3421},[146,4439,4440],{"class":1549},"                body",[146,4442,730],{"class":160},[146,4444,4445],{"class":156}," tableBody\n",[146,4447,4448],{"class":148,"line":3435},[146,4449,4450],{"class":160},"            }\n",[146,4452,4453],{"class":148,"line":3449},[146,4454,4203],{"class":160},[146,4456,4457],{"class":148,"line":3464},[146,4458,254],{"emptyLinePlaceholder":253},[146,4460,4461],{"class":148,"line":3479},[146,4462,4463],{"class":3874},"        \u002F\u002F Description\n",[146,4465,4466],{"class":148,"line":3498},[146,4467,4132],{"class":160},[146,4469,4470,4472,4474,4476,4478,4480],{"class":148,"line":3527},[146,4471,4217],{"class":1549},[146,4473,730],{"class":160},[146,4475,1474],{"class":160},[146,4477,3239],{"class":1554},[146,4479,1471],{"class":160},[146,4481,736],{"class":160},[146,4483,4484,4487,4489,4491],{"class":148,"line":3532},[146,4485,4486],{"class":1549},"            fontSize",[146,4488,730],{"class":160},[146,4490,3258],{"class":206},[146,4492,736],{"class":160},[146,4494,4495,4497,4499,4501,4503,4505],{"class":148,"line":3545},[146,4496,4163],{"class":1549},[146,4498,730],{"class":160},[146,4500,1474],{"class":160},[146,4502,3276],{"class":1554},[146,4504,1471],{"class":160},[146,4506,736],{"class":160},[146,4508,4509,4511,4513,4515,4517,4519,4521,4523,4525,4527,4529],{"class":148,"line":3561},[146,4510,4178],{"class":1549},[146,4512,730],{"class":160},[146,4514,1525],{"class":156},[146,4516,677],{"class":206},[146,4518,276],{"class":160},[146,4520,216],{"class":206},[146,4522,276],{"class":160},[146,4524,2503],{"class":206},[146,4526,276],{"class":160},[146,4528,2512],{"class":206},[146,4530,2515],{"class":156},[146,4532,4533],{"class":148,"line":3575},[146,4534,4203],{"class":160},[146,4536,4537],{"class":148,"line":3596},[146,4538,254],{"emptyLinePlaceholder":253},[146,4540,4541],{"class":148,"line":3625},[146,4542,4543],{"class":3874},"        \u002F\u002F Footer\n",[146,4545,4546],{"class":148,"line":3630},[146,4547,4132],{"class":160},[146,4549,4550,4552,4554,4556,4558,4560],{"class":148,"line":3643},[146,4551,4217],{"class":1549},[146,4553,730],{"class":160},[146,4555,1474],{"class":160},[146,4557,3335],{"class":1554},[146,4559,1471],{"class":160},[146,4561,736],{"class":160},[146,4563,4564,4566,4568,4570,4572,4574],{"class":148,"line":3658},[146,4565,4232],{"class":1549},[146,4567,730],{"class":160},[146,4569,1474],{"class":160},[146,4571,2290],{"class":1554},[146,4573,1471],{"class":160},[146,4575,736],{"class":160},[146,4577,4578,4580,4582,4584,4586,4588],{"class":148,"line":3675},[146,4579,4163],{"class":1549},[146,4581,730],{"class":160},[146,4583,1474],{"class":160},[146,4585,2479],{"class":1554},[146,4587,1471],{"class":160},[146,4589,736],{"class":160},[146,4591,4592,4594,4596,4598,4600,4602,4604,4606,4608,4610,4612],{"class":148,"line":3680},[146,4593,4178],{"class":1549},[146,4595,730],{"class":160},[146,4597,1525],{"class":156},[146,4599,677],{"class":206},[146,4601,276],{"class":160},[146,4603,2512],{"class":206},[146,4605,276],{"class":160},[146,4607,2503],{"class":206},[146,4609,276],{"class":160},[146,4611,2503],{"class":206},[146,4613,2515],{"class":156},[146,4615,4616],{"class":148,"line":3686},[146,4617,4618],{"class":160},"        }\n",[146,4620,4621,4624],{"class":148,"line":3706},[146,4622,4623],{"class":156},"    ]",[146,4625,736],{"class":160},[146,4627,4628],{"class":148,"line":3738},[146,4629,254],{"emptyLinePlaceholder":253},[146,4631,4633,4636,4638],{"class":148,"line":4632},69,[146,4634,4635],{"class":1549},"    styles",[146,4637,730],{"class":160},[146,4639,1517],{"class":160},[146,4641,4643,4646,4648],{"class":148,"line":4642},70,[146,4644,4645],{"class":1549},"        header",[146,4647,730],{"class":160},[146,4649,1517],{"class":160},[146,4651,4653,4655,4657,4659],{"class":148,"line":4652},71,[146,4654,4486],{"class":1549},[146,4656,730],{"class":160},[146,4658,2512],{"class":206},[146,4660,736],{"class":160},[146,4662,4664,4667,4669,4672],{"class":148,"line":4663},72,[146,4665,4666],{"class":1549},"            bold",[146,4668,730],{"class":160},[146,4670,1866],{"class":4671},"sfNiH",[146,4673,736],{"class":160},[146,4675,4677,4679,4681,4683,4685,4687],{"class":148,"line":4676},73,[146,4678,4163],{"class":1549},[146,4680,730],{"class":160},[146,4682,1474],{"class":160},[146,4684,2479],{"class":1554},[146,4686,1471],{"class":160},[146,4688,736],{"class":160},[146,4690,4692,4694,4696,4698,4700,4702,4704,4706,4708,4710,4712],{"class":148,"line":4691},74,[146,4693,4178],{"class":1549},[146,4695,730],{"class":160},[146,4697,1525],{"class":156},[146,4699,677],{"class":206},[146,4701,276],{"class":160},[146,4703,2512],{"class":206},[146,4705,276],{"class":160},[146,4707,2503],{"class":206},[146,4709,276],{"class":160},[146,4711,216],{"class":206},[146,4713,2515],{"class":156},[146,4715,4717],{"class":148,"line":4716},75,[146,4718,4203],{"class":160},[146,4720,4722,4725,4727],{"class":148,"line":4721},76,[146,4723,4724],{"class":1549},"        subheader",[146,4726,730],{"class":160},[146,4728,1517],{"class":160},[146,4730,4732,4734,4736,4738],{"class":148,"line":4731},77,[146,4733,4486],{"class":1549},[146,4735,730],{"class":160},[146,4737,3556],{"class":206},[146,4739,736],{"class":160},[146,4741,4743,4746,4748,4750],{"class":148,"line":4742},78,[146,4744,4745],{"class":1549},"            italics",[146,4747,730],{"class":160},[146,4749,1866],{"class":4671},[146,4751,736],{"class":160},[146,4753,4755,4758,4760,4762,4764,4766],{"class":148,"line":4754},79,[146,4756,4757],{"class":1549},"            color",[146,4759,730],{"class":160},[146,4761,1474],{"class":160},[146,4763,3589],{"class":1554},[146,4765,1471],{"class":160},[146,4767,736],{"class":160},[146,4769,4771,4773,4775,4777,4779,4781,4783,4785,4787,4789,4791],{"class":148,"line":4770},80,[146,4772,4178],{"class":1549},[146,4774,730],{"class":160},[146,4776,1525],{"class":156},[146,4778,677],{"class":206},[146,4780,276],{"class":160},[146,4782,216],{"class":206},[146,4784,276],{"class":160},[146,4786,2503],{"class":206},[146,4788,276],{"class":160},[146,4790,2512],{"class":206},[146,4792,2515],{"class":156},[146,4794,4796],{"class":148,"line":4795},81,[146,4797,4203],{"class":160},[146,4799,4801,4804,4806],{"class":148,"line":4800},82,[146,4802,4803],{"class":1549},"        footer",[146,4805,730],{"class":160},[146,4807,1517],{"class":160},[146,4809,4811,4813,4815,4817],{"class":148,"line":4810},83,[146,4812,4486],{"class":1549},[146,4814,730],{"class":160},[146,4816,216],{"class":206},[146,4818,736],{"class":160},[146,4820,4822,4824,4826,4828,4830],{"class":148,"line":4821},84,[146,4823,4757],{"class":1549},[146,4825,730],{"class":160},[146,4827,1474],{"class":160},[146,4829,3589],{"class":1554},[146,4831,4241],{"class":160},[146,4833,4835],{"class":148,"line":4834},85,[146,4836,4618],{"class":160},[146,4838,4840],{"class":148,"line":4839},86,[146,4841,2520],{"class":160},[146,4843,4845],{"class":148,"line":4844},87,[146,4846,254],{"emptyLinePlaceholder":253},[146,4848,4850,4853,4855,4857,4859,4861],{"class":148,"line":4849},88,[146,4851,4852],{"class":1549},"    pageSize",[146,4854,730],{"class":160},[146,4856,1474],{"class":160},[146,4858,3699],{"class":1554},[146,4860,1471],{"class":160},[146,4862,736],{"class":160},[146,4864,4866,4869,4871,4873,4875,4877,4879,4881,4883,4885,4887],{"class":148,"line":4865},89,[146,4867,4868],{"class":1549},"    pageMargins",[146,4870,730],{"class":160},[146,4872,1525],{"class":156},[146,4874,3719],{"class":206},[146,4876,276],{"class":160},[146,4878,3724],{"class":206},[146,4880,276],{"class":160},[146,4882,3729],{"class":206},[146,4884,276],{"class":160},[146,4886,3724],{"class":206},[146,4888,2515],{"class":156},[146,4890,4892],{"class":148,"line":4891},90,[146,4893,281],{"class":160},[146,4895,4897],{"class":148,"line":4896},91,[146,4898,254],{"emptyLinePlaceholder":253},[146,4900,4902,4904,4906,4908,4910,4913],{"class":148,"line":4901},92,[146,4903,260],{"class":156},[146,4905,167],{"class":160},[146,4907,265],{"class":156},[146,4909,161],{"class":160},[146,4911,4912],{"class":156}," docDefinition",[146,4914,182],{"class":160},[146,4916,4918,4920,4922],{"class":148,"line":4917},93,[146,4919,288],{"class":287},[146,4921,291],{"class":156},[146,4923,182],{"class":160},[326,4925,4926,4937,4954,4966],{"start":257},[103,4927,408,4928,4931,4932,4934,4935,167],{},[338,4929,4930],{},"pdfbuilder"," node onto the canvas. Set the input property to ",[19,4933,382],{},", set output type to Buffer, and output property to ",[19,4936,382],{},[103,4938,408,4939,4942,4943],{},[338,4940,4941],{},"Write File"," node, configure it with:\n",[100,4944,4945,4948,4951],{},[103,4946,4947],{},"Filename: test.pdf",[103,4949,4950],{},"Action: Overwrite file",[103,4952,4953],{},"Add newline (\\n) to each payload?: Checked",[103,4955,1355,4956,1358,4958,4960,4961,4963,4964,1361],{},[338,4957,3841],{},[338,4959,3863],{}," node, then to the ",[338,4962,4930],{}," node, and finally to the ",[338,4965,4941],{},[103,4967,4968],{},"Deploy the flow and click inject node to generate the pdf.",[15,4970,4971,4972,4975],{},"Once the PDF is generated, you can find it in the ",[19,4973,4974],{},".node-red"," directory.",[15,4977,4978],{},"However, if you want to share the PDF with others, display it on the dashboard, and provide a download button, you can use the HTTP API, an iframe, and a few supporting nodes. Let's walk through how to do that next.",[996,4980,4982],{"id":4981},"step-4-serving-the-pdf-via-http-and-previewing-it-on-the-dashboard","Step 4: Serving the PDF via HTTP and Previewing It on the Dashboard",[15,4984,4985],{},"In this step, we’ll make the generated PDF accessible through a web interface. You’ll be able to preview the PDF directly in the browser and embed it in your FlowFuse dashboard for a smooth, integrated experience. We’ll also add a download button so users can easily save the report. Instead of manually retrieving the file, we’ll create an HTTP endpoint to serve the PDF and use an iframe to display it.",[4987,4988,4990],"h4",{"id":4989},"exposing-the-pdf-via-http","Exposing the PDF via HTTP",[326,4992,4993,5004,5011,5082,5088],{},[103,4994,4995,4996,4999,5000,5003],{},"Drag the ",[338,4997,4998],{},"http-in"," node onto the canvas. Set the method to 'GET' and the URL to ",[19,5001,5002],{},"\u002Freport.pdf",". This will create an HTTP endpoint for retrieving the generated PDF.",[103,5005,1355,5006,1358,5008,5010],{},[338,5007,4998],{},[338,5009,3841],{}," node. This ensures that when a request is made to this endpoint, the necessary data is fetched from the database.",[103,5012,5013,5014,5016,5017,5020,5021,5023,5024,5032],{},"After the ",[338,5015,4941],{}," node in your flow, add a ",[338,5018,5019],{},"Change"," node. Connect it to the ",[338,5022,4941],{}," node, and configure it to set the following headers for the HTTP response:",[100,5025,5026],{},[103,5027,379,5028,5031],{},[19,5029,5030],{},"msg.headers"," to:",[42,5033,5035],{"className":708,"code":5034,"language":710,"meta":50,"style":50},"{   \n   'Content-Type': 'application\u002Fpdf',   \n   'Content-Disposition': 'inline; filename=\"report.pdf\"' \n}\n",[19,5036,5037,5044,5058,5078],{"__ignoreMap":50},[146,5038,5039,5041],{"class":148,"line":149},[146,5040,1827],{"class":160},[146,5042,5043],{"class":156},"   \n",[146,5045,5046,5049,5051,5054,5056],{"class":148,"line":185},[146,5047,5048],{"class":156},"   'Content-Type'",[146,5050,730],{"class":160},[146,5052,5053],{"class":156}," 'application\u002Fpdf'",[146,5055,276],{"class":160},[146,5057,5043],{"class":156},[146,5059,5060,5063,5065,5068,5070,5073,5075],{"class":148,"line":221},[146,5061,5062],{"class":156},"   'Content-Disposition'",[146,5064,730],{"class":160},[146,5066,5067],{"class":156}," 'inline; filename=",[146,5069,727],{"class":160},[146,5071,5072],{"class":1554},"report.pdf",[146,5074,727],{"class":160},[146,5076,5077],{"class":156},"' \n",[146,5079,5080],{"class":148,"line":250},[146,5081,754],{"class":160},[103,5083,4995,5084,5087],{},[338,5085,5086],{},"HTTP-response"," node onto the canvas and connect it to the Change node.",[103,5089,5090],{},"Deploy the flow",[15,5092,5093],{},"Now, this will send the generated PDF as a response to the incoming HTTP request, allowing it to be previewed in the browser. You can check by entering the URL:",[15,5095,5096],{},[19,5097,5098],{},"https:\u002F\u002F\u003Cyour-instance-name>.flowfuse.cloud\u002Freport.pdf",[996,5100,5102],{"id":5101},"embedding-the-pdf-on-the-dashboard-and-adding-the-download-button","Embedding the PDF on the Dashboard and Adding the Download Button",[15,5104,5105],{},"Now, let's embed the PDF into the dashboard:",[326,5107,5108,5114,5137],{},[103,5109,408,5110,5113],{},[338,5111,5112],{},"ui-event"," node onto the canvas and configure it with the appropriate UI base settings.",[103,5115,5116,5117,1340,5120,5131],{},"Next, drag an ",[338,5118,5119],{},"iframe",[100,5121,5122,5125,5128],{},[103,5123,5124],{},"Select the correct group where the PDF should be displayed.",[103,5126,5127],{},"Adjust the size according to your preferences.",[103,5129,5130],{},"In the URL field, enter:",[42,5132,5135],{"className":5133,"code":5134,"language":47},[45],"https:\u002F\u002F\u003Cyour-instance-name>.flowfuse.cloud\u002Freport.pdf\n",[19,5136,5134],{"__ignoreMap":50},[103,5138,5139],{},"Click Done and Deploy the flow.",[15,5141,5142],{},"Once deployed, when you open the dashboard, the generated PDF will be embedded and displayed directly on the dashboard page.",[15,5144,5145],{},"Now, let's add a download button:",[326,5147,5148],{"start":250},[103,5149,408,5150,5153],{},[338,5151,5152],{},"ui-template"," widget onto the canvas and paste the following HTML code into it:",[42,5155,5159],{"className":5156,"code":5157,"language":5158,"meta":50,"style":50},"language-html shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003Cdiv style=\"text-align:center; margin-top: 20px;\">\n    \u003Ca href=\"https:\u002F\u002F\u003Cyour-instance-name>.flowfuse.cloud\u002Freport.pdf\" download=\"report.pdf\"\n        style=\"display: inline-block; background-color: #4f7a28; color: white; padding: 14px 20px; text-align: center; text-decoration: none; font-size: 16px; border-radius: 5px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); transition: background-color 0.3s ease;\">\n        Download Report\n    \u003C\u002Fa>\n\u003C\u002Fdiv>\n","html",[19,5160,5161,5184,5213,5229,5234,5243],{"__ignoreMap":50},[146,5162,5163,5166,5169,5172,5174,5176,5179,5181],{"class":148,"line":149},[146,5164,5165],{"class":160},"\u003C",[146,5167,5168],{"class":1549},"div",[146,5170,5171],{"class":152}," style",[146,5173,161],{"class":160},[146,5175,727],{"class":160},[146,5177,5178],{"class":1554},"text-align:center; margin-top: 20px;",[146,5180,727],{"class":160},[146,5182,5183],{"class":160},">\n",[146,5185,5186,5189,5191,5194,5196,5198,5200,5202,5205,5207,5209,5211],{"class":148,"line":185},[146,5187,5188],{"class":160},"    \u003C",[146,5190,307],{"class":1549},[146,5192,5193],{"class":152}," href",[146,5195,161],{"class":160},[146,5197,727],{"class":160},[146,5199,5098],{"class":1554},[146,5201,727],{"class":160},[146,5203,5204],{"class":152}," download",[146,5206,161],{"class":160},[146,5208,727],{"class":160},[146,5210,5072],{"class":1554},[146,5212,2561],{"class":160},[146,5214,5215,5218,5220,5222,5225,5227],{"class":148,"line":221},[146,5216,5217],{"class":152},"        style",[146,5219,161],{"class":160},[146,5221,727],{"class":160},[146,5223,5224],{"class":1554},"display: inline-block; background-color: #4f7a28; color: white; padding: 14px 20px; text-align: center; text-decoration: none; font-size: 16px; border-radius: 5px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); transition: background-color 0.3s ease;",[146,5226,727],{"class":160},[146,5228,5183],{"class":160},[146,5230,5231],{"class":148,"line":250},[146,5232,5233],{"class":156},"        Download Report\n",[146,5235,5236,5239,5241],{"class":148,"line":257},[146,5237,5238],{"class":160},"    \u003C\u002F",[146,5240,307],{"class":1549},[146,5242,5183],{"class":160},[146,5244,5245,5248,5250],{"class":148,"line":284},[146,5246,5247],{"class":160},"\u003C\u002F",[146,5249,5168],{"class":1549},[146,5251,5183],{"class":160},[326,5253,5254],{"start":257},[103,5255,5256],{},"Deploy the flow.",[15,5258,5259,5263],{},[392,5260],{"alt":5261,"dataZoomable":50,"src":5262},"Dashboard displaying embedded PDF with a download button","\u002Fblog\u002F2025\u002F05\u002Fimages\u002Fdashboard-with-embedded-report.png",[397,5264,5265],{},[146,5266,5261],{},[34,5268,5270],{"id":5269},"final-thought","Final thought",[15,5272,5273,5274,5279],{},"Automating PDF report generation in Node-RED is a great way to save time and effort. Using tools like the node-red-contrib-pdfmake node, you can quickly turn your data into well-designed PDFs without manual work. If you want to save time and avoid the setup process, you can directly use our ready-made ",[307,5275,5278],{"href":5276,"rel":5277},"https:\u002F\u002Fflowfuse.com\u002Fblueprints\u002Fmanufacturing\u002Fpdf-report-generator\u002F",[311],"PDF generation blueprint",". It’s an easy way to get started and generate professional reports quickly.",[924,5281,5282],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}",{"title":50,"searchDepth":250,"depth":250,"links":5284},[5285,5286,5295],{"id":1162,"depth":185,"text":1163},{"id":2111,"depth":185,"text":2112,"children":5287},[5288,5289,5290,5291,5294],{"id":2118,"depth":221,"text":2119},{"id":2161,"depth":221,"text":2162},{"id":3754,"depth":221,"text":3755},{"id":4981,"depth":221,"text":4982,"children":5292},[5293],{"id":4989,"depth":250,"text":4990},{"id":5101,"depth":221,"text":5102},{"id":5269,"depth":185,"text":5270},{"type":941,"title":5297,"description":5298},"Automate PDF Reports with Node-RED on FlowFuse","FlowFuse gives you a managed Node-RED environment with one-click deployment, secure HTTP endpoints, and Dashboard 2.0 integration, everything you need to automate and serve PDF reports in production.","2025-05-07","Discover how to create automated PDF reports in Node-RED with FlowFuse. This guide covers everything from setting up the required nodes to generating and serving PDF reports with dynamic data, making sharing and archiving important business insights easy.","\u002Fblog\u002F2025\u002F05\u002Fimages\u002Fhow-to-generate-pdf-with-nr-and-ff.png",{"keywords":5303,"excerpt":5304},"pdf generate with node-red, node-red pdf report generation, pdf report node-red, pdfmake node-red, platmac\u002Fnode-red-pdfbuilder, automate pdf node-red, node-red pdfbuilder",{"type":12,"value":5305},[5306],[15,5307,2066],{},"\u002Fblog\u002F2025\u002F05\u002Fhow-to-generate-pdf-reports-using-node-red",{"title":2060,"description":5300},{"loc":5308},"blog\u002F2025\u002F05\u002Fhow-to-generate-pdf-reports-using-node-red","Learn how to automate the generation of dynamic PDF reports within Node-RED and FlowFuse.",[965,966],"This guide shows how to automate PDF report generation in Node-RED using the @platmac\u002Fnode-red-pdfbuilder node and FlowFuse. It covers installing the node, building a flow that pulls production data from SQLite and renders it as a styled PDF, serving the file via an HTTP endpoint, and embedding it with a download button in a Dashboard 2.0 iframe widget.","5biroVbj6H-YR8xUO55Vrqx7YrxPkoft7xY9rIKPlhM",{"id":5317,"title":5318,"authors":5319,"body":5321,"cta":6544,"date":6548,"description":6549,"extension":946,"image":6550,"lastUpdated":948,"meta":6551,"navigation":253,"path":6557,"seo":6558,"sitemap":6559,"stem":6560,"subtitle":6561,"tags":6562,"tldr":6565,"video":3,"__hash__":6566},"blog\u002Fblog\u002F2025\u002F01\u002Fintegrating-siemens-s7-plcs-with-node-red-guide.md","How to Read and Write Siemens S7 PLC Data, A Node-RED Guide (2026)",[10,5320],"stephen-mclaughlin",{"type":12,"value":5322,"toc":6530},[5323,5326,5329,5332,5336,5339,5344,5349,5357,5362,5370,5375,5384,5394,5399,5404,5408,5411,5414,5422,5425,5429,5441,5445,5465,5468,5472,5475,6157,6160,6169,6173,6176,6190,6198,6206,6214,6222,6230,6234,6237,6251,6259,6272,6279,6282,6289,6293,6296,6303,6306,6315,6318,6321,6324,6353,6361,6367,6370,6373,6376,6379,6387,6425,6434,6497,6501,6504,6512,6515,6519,6522],[15,5324,5325],{},"Siemens S7 PLCs are a staple in industrial automation, powering everything from basic control functions to complex, large-scale processes. However, integrating these PLCs with other systems for remote monitoring or data sharing can present challenges.",[15,5327,5328],{},"This is where Node-RED comes in, offering a user-friendly solution to seamlessly connect Siemens S7 PLCs with a variety of platforms. With its intuitive flow-based interface, Node-RED enables you to create custom workflows and dashboards, no deep technical expertise required.",[15,5330,5331],{},"Siemens S7 PLCs are typically programmed using TIA Portal, Siemens' integrated development environment, and communication with external systems usually relies on the S7 protocol (ISO over TCP\u002FIP). In this article, we’ll walk you through how to use Node-RED to read from and write to Siemens S7 PLCs via the S7 protocol, unlocking new possibilities for remote control and system integration in your industrial automation setup.",[34,5333,5335],{"id":5334},"prerequisite","Prerequisite",[15,5337,5338],{},"Before integrating your Siemens S7 PLC with Node-RED, make sure you have the following :",[326,5340,5341],{},[103,5342,5343],{},"Before downloading the ladder program and all configurations and settings to your PLC, make sure you have the following settings:",[100,5345,5346],{},[103,5347,5348],{},"Allow PUT\u002FGET Communication from remote partners.",[15,5350,5351,5355],{},[392,5352],{"alt":5353,"src":5354,"dataZoomable":50},"PUT\u002FGET Communication from remote partners is Allowed","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fallow-put-get-communication.png",[397,5356,5353],{},[100,5358,5359],{},[103,5360,5361],{},"Provide full access to the PLC (no protection), allowing unrestricted access to data exchange.",[15,5363,5364,5368],{},[392,5365],{"alt":5366,"src":5367,"dataZoomable":50},"Providing complete access to the PLC","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fproviding-full-access-to-plc.png",[397,5369,5366],{},[326,5371,5372],{"start":185},[103,5373,5374],{},"Ensure that the appropriate ladder program (or any other logic) is written according to your requirements and successfully downloaded to the PLC. However, before downloading, make sure the 'Optimized Block Access' option is disabled for the data block that your ladder program using.",[15,5376,5377,5381],{},[392,5378],{"alt":5379,"src":5380,"dataZoomable":50},"Untick 'Optimized Block Access'.","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Foptimized-block-access.png",[397,5382,5383],{},"Untick 'Optimized Block Access.'",[326,5385,5386],{"start":221},[103,5387,5388,5389,5393],{},"Install Node-RED on the device that will communicate with the S7 PLC. You cannot install Node-RED directly on the S7 PLC, as PLCs are typically controllers, not computers. For example, you can use a device like the Revolutionary Pi to connect and transfer data across systems. Use the ",[307,5390,5392],{"href":5391},"\u002Fplatform\u002Fdevice-agent\u002F","FlowFuse Device Agent"," to install Node-RED on your device.",[100,5395,5396],{},[103,5397,5398],{},"Why FlowFuse Device Agent? It allows you to manage Node-RED remotely, enabling control, monitoring, and flow creation without the need for on-site visits. FlowFuse also offers a suite of enterprise-grade features such as collaboration, device management, and DevOps pipelines, which are essential in industrial environments. These features help streamline operations and ensure scalability in complex automation systems. [Sign up for free]({% include \"sign-up-url.njk\" %}) to get started.",[326,5400,5401],{"start":250},[103,5402,5403],{},"Verify that the device running Node-RED is in the same network as the PLC and can successfully ping the PLC. Also, a firewall should not block the S7 port (typically port 102).",[34,5405,5407],{"id":5406},"integrating-siemens-s7-plcs-with-node-red","Integrating Siemens S7 PLCs with Node-RED",[15,5409,5410],{},"Now that everything is set up, let's integrate your Siemens S7 PLC with Node-RED. In this article, I’ll demonstrate the process using a Siemens S7-1212C PLC. I’ve connected it to a stack\u002Ftower light and will walk you through how to write data to the PLC to control this light. Later, I’ll show you how to read data and reflect the status of the light.",[15,5412,5413],{},"My program in TIA Portal is structured as shown below, utilizing DB (Data Blocks) and Q (physical outputs) to control devices. However, Node-RED can retrieve almost all types of data from the PLC. The process is similar for most data types.",[15,5415,5416,5420],{},[392,5417],{"alt":5418,"src":5419},"Ladder Logic to Control Outputs for Managing Lights","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fladder-to-control-lights.png",[397,5421,5418],{},[15,5423,5424],{},"Let’s break down what’s happening in the ladder logic above. First, we have open contacts, each with address variables defined in a separate Data Block. There are three open branches, each starting with an open contact. Each contact is connected to an output that alters the status of a Q physical address. Each Q corresponds to a physical output on the PLC, which is wired to the lights. When we change the status of a contact to \"true,\" it activates the corresponding light by altering the state of the Q output, which reflects the change in the physical output.",[996,5426,5428],{"id":5427},"installing-the-s7-node","Installing the S7 Node",[15,5430,5431,5432,5435,5436,167],{},"To communicate from Node-RED to the PLC, we need to install the S7 node, which allows Node-RED to interface with Siemens S7 PLCs. In this article, we will be using ",[19,5433,5434],{},"node-red-contrib-s7",", which is quite popular. If this particular node is not suitable for your workflow you can find alternatives in the ",[307,5437,5440],{"href":5438,"rel":5439},"https:\u002F\u002Fflows.nodered.org\u002Fsearch?term=siemens&type=node",[311],"Node-RED catalog",[4987,5442,5444],{"id":5443},"steps-to-install-the-s7-node","Steps to Install the S7 Node:",[326,5446,5447,5450,5453,5456,5462],{},[103,5448,5449],{},"Open your Node-RED editor in a web browser.",[103,5451,5452],{},"Open the main menu by clicking the three horizontal lines in the top-right corner.",[103,5454,5455],{},"Click \"Manage Palette\" from the menu.",[103,5457,5458,5459,5461],{},"Switch to the \"Install\" tab and type ",[19,5460,5434],{}," in the search field.",[103,5463,5464],{},"Click \"Install\" next to the node name.",[15,5466,5467],{},"Once the installation is complete, the S7 nodes will be available in your Node-RED palette, and you can start using it to communicate with your Siemens S7 PLC.",[996,5469,5471],{"id":5470},"addressing-scheme-for-variables-in-node-red-with-the-s7-node","Addressing Scheme for Variables in Node-RED with the S7 Node",[15,5473,5474],{},"Before we start, it's important to note that the variables and their addresses configured on the S7 endpoint follow a slightly different addressing scheme compared to those used in Step 7 or the TIA Portal. Therefore, when adding variables to the S7 node in Node-RED, you must ensure that you follow the correct addressing format outlined in the table below.",[1053,5476,5477,5500],{},[1056,5478,5479],{},[1059,5480,5481,5486,5491,5496],{},[1062,5482,5483],{},[338,5484,5485],{},"Node-RED Address",[1062,5487,5488],{},[338,5489,5490],{},"Step7 Equivalent",[1062,5492,5493],{},[338,5494,5495],{},"Data Type",[1062,5497,5498],{},[338,5499,2187],{},[1070,5501,5502,5520,5538,5556,5574,5591,5609,5626,5644,5659,5675,5691,5707,5723,5739,5755,5772,5789,5806,5823,5840,5857,5873,5889,5905,5922,5939,5956,5972,5988,6004,6021,6037,6053,6067,6081,6095,6109,6125,6141],{},[1059,5503,5504,5509,5514,5517],{},[1075,5505,5506],{},[19,5507,5508],{},"DB5,X0.1",[1075,5510,5511],{},[19,5512,5513],{},"DB5.DBX0.1",[1075,5515,5516],{},"Boolean",[1075,5518,5519],{},"Bit 1 of byte 0 in DB5",[1059,5521,5522,5527,5532,5535],{},[1075,5523,5524],{},[19,5525,5526],{},"DB23,BYTE1",[1075,5528,5529],{},[19,5530,5531],{},"DB23.DBB1",[1075,5533,5534],{},"Number (Byte)",[1075,5536,5537],{},"Byte 1 (0-255) of DB23",[1059,5539,5540,5545,5550,5553],{},[1075,5541,5542],{},[19,5543,5544],{},"DB100,CHAR2",[1075,5546,5547],{},[19,5548,5549],{},"DB100.DBB2",[1075,5551,5552],{},"String",[1075,5554,5555],{},"Byte 2 of DB100 as Char",[1059,5557,5558,5563,5568,5571],{},[1075,5559,5560],{},[19,5561,5562],{},"DB42,INT3",[1075,5564,5565],{},[19,5566,5567],{},"DB42.DBW3",[1075,5569,5570],{},"Number (16-bit)",[1075,5572,5573],{},"Signed 16-bit number at byte 3 in DB42",[1059,5575,5576,5581,5586,5588],{},[1075,5577,5578],{},[19,5579,5580],{},"DB57,WORD4",[1075,5582,5583],{},[19,5584,5585],{},"DB57.DBW4",[1075,5587,5570],{},[1075,5589,5590],{},"Unsigned 16-bit number at byte 4 in DB57",[1059,5592,5593,5598,5603,5606],{},[1075,5594,5595],{},[19,5596,5597],{},"DB13,DINT5",[1075,5599,5600],{},[19,5601,5602],{},"DB13.DBD5",[1075,5604,5605],{},"Number (32-bit)",[1075,5607,5608],{},"Signed 32-bit number at byte 5 in DB13",[1059,5610,5611,5616,5621,5623],{},[1075,5612,5613],{},[19,5614,5615],{},"DB19,DWORD6",[1075,5617,5618],{},[19,5619,5620],{},"DB19.DBD6",[1075,5622,5605],{},[1075,5624,5625],{},"Unsigned 32-bit number at byte 6 in DB19",[1059,5627,5628,5633,5638,5641],{},[1075,5629,5630],{},[19,5631,5632],{},"DB21,REAL7",[1075,5634,5635],{},[19,5636,5637],{},"DB21.DBD7",[1075,5639,5640],{},"Floating Point (32)",[1075,5642,5643],{},"Floating point number at byte 7 in DB21",[1059,5645,5646,5651,5654,5656],{},[1075,5647,5648],{},[19,5649,5650],{},"DB2,S7.10*",[1075,5652,5653],{},"-",[1075,5655,5552],{},[1075,5657,5658],{},"String (length 10) starting at byte 7 in DB2",[1059,5660,5661,5666,5670,5672],{},[1075,5662,5663],{},[19,5664,5665],{},"I1.0",[1075,5667,5668],{},[19,5669,5665],{},[1075,5671,5516],{},[1075,5673,5674],{},"Bit 0 of byte 1 in input area",[1059,5676,5677,5682,5686,5688],{},[1075,5678,5679],{},[19,5680,5681],{},"Q2.1",[1075,5683,5684],{},[19,5685,5681],{},[1075,5687,5516],{},[1075,5689,5690],{},"Bit 1 of byte 2 in output area",[1059,5692,5693,5698,5702,5704],{},[1075,5694,5695],{},[19,5696,5697],{},"M3.2",[1075,5699,5700],{},[19,5701,5697],{},[1075,5703,5516],{},[1075,5705,5706],{},"Bit 2 of byte 3 in memory area",[1059,5708,5709,5714,5718,5720],{},[1075,5710,5711],{},[19,5712,5713],{},"IB4",[1075,5715,5716],{},[19,5717,5713],{},[1075,5719,5534],{},[1075,5721,5722],{},"Byte 4 (0-255) in input area",[1059,5724,5725,5730,5734,5736],{},[1075,5726,5727],{},[19,5728,5729],{},"QB5",[1075,5731,5732],{},[19,5733,5729],{},[1075,5735,5534],{},[1075,5737,5738],{},"Byte 5 (0-255) in output area",[1059,5740,5741,5746,5750,5752],{},[1075,5742,5743],{},[19,5744,5745],{},"MB6",[1075,5747,5748],{},[19,5749,5745],{},[1075,5751,5534],{},[1075,5753,5754],{},"Byte 6 (0-255) in memory area",[1059,5756,5757,5762,5767,5769],{},[1075,5758,5759],{},[19,5760,5761],{},"IC7",[1075,5763,5764],{},[19,5765,5766],{},"IB7",[1075,5768,5552],{},[1075,5770,5771],{},"Byte 7 of input area as Char",[1059,5773,5774,5779,5784,5786],{},[1075,5775,5776],{},[19,5777,5778],{},"QC8",[1075,5780,5781],{},[19,5782,5783],{},"QB8",[1075,5785,5552],{},[1075,5787,5788],{},"Byte 8 of output area as Char",[1059,5790,5791,5796,5801,5803],{},[1075,5792,5793],{},[19,5794,5795],{},"MC9",[1075,5797,5798],{},[19,5799,5800],{},"MB9",[1075,5802,5552],{},[1075,5804,5805],{},"Byte 9 of memory area as Char",[1059,5807,5808,5813,5818,5820],{},[1075,5809,5810],{},[19,5811,5812],{},"II10",[1075,5814,5815],{},[19,5816,5817],{},"IW10",[1075,5819,5570],{},[1075,5821,5822],{},"Signed 16-bit number at byte 10 in input area",[1059,5824,5825,5830,5835,5837],{},[1075,5826,5827],{},[19,5828,5829],{},"QI12",[1075,5831,5832],{},[19,5833,5834],{},"QW12",[1075,5836,5570],{},[1075,5838,5839],{},"Signed 16-bit number at byte 12 in output area",[1059,5841,5842,5847,5852,5854],{},[1075,5843,5844],{},[19,5845,5846],{},"MI14",[1075,5848,5849],{},[19,5850,5851],{},"MW14",[1075,5853,5570],{},[1075,5855,5856],{},"Signed 16-bit number at byte 14 in memory area",[1059,5858,5859,5864,5868,5870],{},[1075,5860,5861],{},[19,5862,5863],{},"IW16",[1075,5865,5866],{},[19,5867,5863],{},[1075,5869,5570],{},[1075,5871,5872],{},"Unsigned 16-bit number at byte 16 in input area",[1059,5874,5875,5880,5884,5886],{},[1075,5876,5877],{},[19,5878,5879],{},"QW18",[1075,5881,5882],{},[19,5883,5879],{},[1075,5885,5570],{},[1075,5887,5888],{},"Unsigned 16-bit number at byte 18 in output area",[1059,5890,5891,5896,5900,5902],{},[1075,5892,5893],{},[19,5894,5895],{},"MW20",[1075,5897,5898],{},[19,5899,5895],{},[1075,5901,5570],{},[1075,5903,5904],{},"Unsigned 16-bit number at byte 20 in memory area",[1059,5906,5907,5912,5917,5919],{},[1075,5908,5909],{},[19,5910,5911],{},"IDI22",[1075,5913,5914],{},[19,5915,5916],{},"ID22",[1075,5918,5605],{},[1075,5920,5921],{},"Signed 32-bit number at byte 22 in input area",[1059,5923,5924,5929,5934,5936],{},[1075,5925,5926],{},[19,5927,5928],{},"QDI24",[1075,5930,5931],{},[19,5932,5933],{},"QD24",[1075,5935,5605],{},[1075,5937,5938],{},"Signed 32-bit number at byte 24 in output area",[1059,5940,5941,5946,5951,5953],{},[1075,5942,5943],{},[19,5944,5945],{},"MDI26",[1075,5947,5948],{},[19,5949,5950],{},"MD26",[1075,5952,5605],{},[1075,5954,5955],{},"Signed 32-bit number at byte 26 in memory area",[1059,5957,5958,5963,5967,5969],{},[1075,5959,5960],{},[19,5961,5962],{},"ID28",[1075,5964,5965],{},[19,5966,5962],{},[1075,5968,5605],{},[1075,5970,5971],{},"Unsigned 32-bit number at byte 28 in input area",[1059,5973,5974,5979,5983,5985],{},[1075,5975,5976],{},[19,5977,5978],{},"QD30",[1075,5980,5981],{},[19,5982,5978],{},[1075,5984,5605],{},[1075,5986,5987],{},"Unsigned 32-bit number at byte 30 in output area",[1059,5989,5990,5995,5999,6001],{},[1075,5991,5992],{},[19,5993,5994],{},"MD32",[1075,5996,5997],{},[19,5998,5994],{},[1075,6000,5605],{},[1075,6002,6003],{},"Unsigned 32-bit number at byte 32 in memory area",[1059,6005,6006,6011,6015,6018],{},[1075,6007,6008],{},[19,6009,6010],{},"IR34",[1075,6012,6013],{},[19,6014,6010],{},[1075,6016,6017],{},"Floating Point",[1075,6019,6020],{},"Floating point number at byte 34 in input area",[1059,6022,6023,6028,6032,6034],{},[1075,6024,6025],{},[19,6026,6027],{},"QR36",[1075,6029,6030],{},[19,6031,6027],{},[1075,6033,6017],{},[1075,6035,6036],{},"Floating point number at byte 36 in output area",[1059,6038,6039,6044,6048,6050],{},[1075,6040,6041],{},[19,6042,6043],{},"MR38",[1075,6045,6046],{},[19,6047,6043],{},[1075,6049,6017],{},[1075,6051,6052],{},"Floating point number at byte 38 in memory area",[1059,6054,6055,6060,6062,6064],{},[1075,6056,6057],{},[19,6058,6059],{},"DB1,DT0",[1075,6061,5653],{},[1075,6063,2809],{},[1075,6065,6066],{},"Timestamp in DATE_AND_TIME format",[1059,6068,6069,6074,6076,6078],{},[1075,6070,6071],{},[19,6072,6073],{},"DB1,DTZ10",[1075,6075,5653],{},[1075,6077,2809],{},[1075,6079,6080],{},"Timestamp in DATE_AND_TIME format (UTC)",[1059,6082,6083,6088,6090,6092],{},[1075,6084,6085],{},[19,6086,6087],{},"DB2,DTL2",[1075,6089,5653],{},[1075,6091,2809],{},[1075,6093,6094],{},"Timestamp in DTL format",[1059,6096,6097,6102,6104,6106],{},[1075,6098,6099],{},[19,6100,6101],{},"DB2,DTLZ12",[1075,6103,5653],{},[1075,6105,2809],{},[1075,6107,6108],{},"Timestamp in DTL format (UTC)",[1059,6110,6111,6116,6120,6122],{},[1075,6112,6113],{},[19,6114,6115],{},"DB57,RWORD4",[1075,6117,6118],{},[19,6119,5585],{},[1075,6121,5570],{},[1075,6123,6124],{},"Unsigned 16-bit number, Little-Endian at byte 4",[1059,6126,6127,6132,6136,6138],{},[1075,6128,6129],{},[19,6130,6131],{},"DB13,RDI5",[1075,6133,6134],{},[19,6135,5602],{},[1075,6137,5605],{},[1075,6139,6140],{},"Signed 32-bit number, Little-Endian at byte 5",[1059,6142,6143,6148,6152,6154],{},[1075,6144,6145],{},[19,6146,6147],{},"MRW20",[1075,6149,6150],{},[19,6151,5895],{},[1075,6153,5570],{},[1075,6155,6156],{},"Unsigned 16-bit number, Little-Endian at byte 20",[15,6158,6159],{},"For example, consider that you have a ladder logic program in the TIA Portal with addresses like DB5.DBX0.0 and DB13.DBW4. You must adjust the address format slightly when you want to use these in the Node-RED S7 node. In Node-RED, DB5.DBX0.0 would be represented as DB5,X0.0 and DB13.DBW4 would be written as DB13,WORD4. Essentially, you look at the TIA Portal address, find the corresponding format in the Node-RED address column, and use that format in the S7 node configuration.",[15,6161,6162,6163,6168],{},"If you wanted integrate Siemens LOGO, please refer to the node's ",[307,6164,6167],{"href":6165,"rel":6166},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-s7",[311],"README",", as the addressing differs.",[996,6170,6172],{"id":6171},"configuring-the-s7-node-to-connect-to-the-plc","Configuring the S7 Node to Connect to the PLC",[15,6174,6175],{},"Now that you have all the necessary knowledge and setup, let's start by establishing a connection between Node-RED and your Siemens S7 PLC. The S7 node in Node-RED simplifies the process, making it easy to configure communication. Follow the steps below to connect and start interacting with your PLC",[326,6177,6178,6181,6184,6187],{},[103,6179,6180],{},"Drag the S7 node onto the Node-RED canvas.",[103,6182,6183],{},"Double-click on the S7 node and click on the \"+\" icon to add a PLC configuration.",[103,6185,6186],{},"Select \"Ethernet (ISO on TCP)\" as the transport protocol, then enter your PLC's IP address. The default port (102) is used for S7 communication, so leave it unchanged.",[103,6188,6189],{},"Set the Mode to \"Rack,\" then enter the Rack ID and Slot ID. These values can be found in the TIA Portal under the Device View tab on your configured device.",[15,6191,6192,6196],{},[392,6193],{"alt":6194,"src":6195,"dataZoomable":50},"Image showing window from where you will get the Rack No and Slot No","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fshowing-rack-and-slot.png",[397,6197,6194],{},[326,6199,6200,6203],{"start":257},[103,6201,6202],{},"Enter the Cycle Time (interval for communication with the PLC) and Timeout Duration (maximum time to wait for a response).",[103,6204,6205],{},"Once done, switch to the Variables tab and add all the variables with the correct address and name you want to read or write.",[15,6207,6208,6212],{},[392,6209],{"alt":6210,"src":6211,"dataZoomable":50},"Adding Variables into s7 node","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fs7-config-variables.png",[397,6213,6210],{},[326,6215,6216,6219],{"start":666},[103,6217,6218],{},"After adding the variables, click Add and then Done.",[103,6220,6221],{},"Deploy the flow by clicking the top-right Deploy button. Once deployed, the connection status will be displayed at the bottom of the node. If connected successfully, it will show a green squre with \"online\" status.",[15,6223,6224,6228],{},[392,6225],{"alt":6226,"src":6227,"dataZoomable":50},"Configuring S7 node for connection","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fs7-connection-configuration.png",[397,6229,6226],{},[996,6231,6233],{"id":6232},"writing-data-to-the-plc","Writing Data to the PLC",[15,6235,6236],{},"Now that you’ve configured the connection, it’s time to use Node-RED to write data to the PLC to control light.",[326,6238,6239,6242,6245,6248],{},[103,6240,6241],{},"Drag the s7-out node onto the canvas.",[103,6243,6244],{},"Double-click on the node and select the variable to which you want to update or write a value.",[103,6246,6247],{},"Select the PLC configuration that we have added.",[103,6249,6250],{},"Click Done.",[15,6252,6253,6257],{},[392,6254],{"alt":6255,"src":6256,"dataZoomable":50},"Configuring S7-out Node to write data to plc","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fconfiguring-s7-out-node.png",[397,6258,6255],{},[326,6260,6261,6269],{"start":257},[103,6262,6263,6264,6268],{},"The node is now ready to write data to the PLC. You can use standard Node-RED nodes like Inject, Change, or Function to create a workflow that sends the data. Ensure the data type matches the configuration set in the PLC program. For example, in my ladder logic, I need to modify the status of individual open contacts, each with its own address, such as DB1.DBX0.0, DB1.DBX0.1, and DB1.DBX0.2, to control the tower lights. Setting these contacts to TRUE will turn on the red, yellow, and green lights, respectively. You can send the data using the nodes I’ve mentioned, or you can build a custom dashboard with ",[307,6265,6267],{"href":6266},"\u002Fplatform\u002Fdashboard\u002F","FlowFuse Dashboard"," for easier interaction.",[103,6270,6271],{},"Once your flow is set up and the s7-out node for each variable is configured, click Deploy in the top-right corner to activate the flow.",[6273,6274],"lite-youtube",{"videoid":6275,"params":6276,"style":6277,"title":6278},"AilWMNPzP1Q","rel=0","width: 704px; height: 100%;","YouTube video player",[15,6280,6281],{},"In the video above, the dashboard interface is built to control the stack light. At the end of this article, I will provide the complete flow for you to download.",[15,6283,6284,6285,167],{},"If you're building a dashboard, keep in mind that while you can create it on Node-RED within the remote instance on FlowFuse Device Agent, you won’t be able to access it remotely across the editor tunnel. You can of course access it locally on the device or on the local LAN. For this demonstration, I wish to access the dashboard remotely across the internet and so I will create the dashboard in a hosted instance of Node-RED and use the FlowFuse Projects nodes to simply and securely pass the necessary values to and from the remote Node-RED instance. For more details on how to set this up, check out our article: ",[307,6286,6288],{"href":6287},"\u002Fblog\u002F2024\u002F10\u002Fexploring-flowfuse-project-nodes\u002F","Exploring FlowFuse Project Nodes",[996,6290,6292],{"id":6291},"reading-data-from-the-plc","Reading Data from the PLC",[15,6294,6295],{},"Now that we’ve covered how to write data to your Siemens S7 PLC, let's move on to reading data from it. Node-RED makes it easy to retrieve important information such as the status of inputs, outputs, or internal memory. By pulling this data into your workflows or visualizing it on a dashboard, you can monitor key parameters in real time and gain valuable insights.",[15,6297,6298,6299,167],{},"However, before we dive in, it's important to consider that reading individual data points one by one in large-scale manufacturing systems can lead to delays. This approach may not be efficient, especially when dealing with a large number of data points. For more information on these challenges and potential solutions, you can refer to this article: ",[307,6300,6302],{"href":6301},"\u002Fblog\u002F2023\u002F09\u002Fmodernize-your-legacy-industrial-data-part2\u002F","Modernize Your Legacy Industrial Data - Part 2",[15,6304,6305],{},"To address this issue, you can optimize data retrieval by storing output status values in a single word or double word within the PLC. For our example, I have created a custom function in my program that assigns the output values to individual bits of the word.",[15,6307,6308,6312],{},[392,6309],{"alt":6310,"src":6311,"dataZoomable":50},"Ladder diagram showing a custom function that stores the status of outputs in a single word within the PLC.","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fcustom-function-storing-bits-in-word.png",[397,6313,6314],{},"Custom ladder diagram function storing output statuses in a single word for optimized data retrieval.",[15,6316,6317],{},"There are several ways to implement this, and depending on your system’s needs, some methods may be more efficient than others. In this case, the output values are stored in a single word within the PLC, as shown in the ladder diagram above. This is not the only correct method, it's simply one approach that works for this particular scenario. Feel free to adapt or explore other methods that might better suit your setup.",[15,6319,6320],{},"Additionally, if the data you’re reading is mission-critical and you can't afford to lose any, consider using a FIFO stack or buffer in your PLC program. This method ensures that even if there is a network outage or computer problem, no data is lost as it will remain siting in the stack until your Node-RED is back on line and retrieves it.  This ensures no gaps or interruptions in your data and guarantees data integrity.",[15,6322,6323],{},"Now, let’s begin reading the data from the PLC.",[326,6325,6326,6331,6334,6337,6347,6350],{},[103,6327,4995,6328,1340],{},[19,6329,6330],{},"s7-in",[103,6332,6333],{},"Double-click on the node to open the configuration and select the appropriate PLC configuration from the list of available connections.",[103,6335,6336],{},"Choose the appropriate mode based on your requirements. If you want to read only one variable, select \"Single Variable Mode\". In this mode, the \"Variable\" dropdown will allow you to select only a single variable at a time. If you need to read multiple variables, you can select \"All Variables\" mode, but be aware that the node might still process each request sequentially, depending on its internal workings (which is not fully documented). This can be inefficient when dealing with hundreds of variables.",[103,6338,6339,6340,6343,6344,167],{},"Choose the variable that corresponds to the word or double word containing all the data points you want to read. For example, if you’ve configured the word in the PLC as ",[19,6341,6342],{},"DB.DBW2",", the format in the s7-in node will be ",[19,6345,6346],{},"DB,WORD2",[103,6348,6349],{},"Enable the \"Emit only when value changes (diff)\" option to ensure that the node only triggers when the value of the variable changes, reducing unnecessary reads and improving efficiency.",[103,6351,6352],{},"Once your configuration is set, click \"Done\" and then deploy the flow to start reading data from the PLC.",[15,6354,6355,6359],{},[392,6356],{"alt":6357,"src":6358,"dataZoomable":50},"Configuring S7-in Node to Read data from plc","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fconfiguring-s7-in-node.png",[397,6360,6357],{},[15,6362,6363,6364,6366],{},"You can add a \"Debug\" node to the ",[19,6365,6330],{}," node's output to verify that the data is being read correctly.",[15,6368,6369],{},"Once you see the printed data, you might be surprised, or perhaps you already expected this: since the word data type we're reading is not directly available in Node.js or Node-RED, we'll receive it as an integer. But don't worry, you can convert it into the format that suits your needs using node-red-contrib-buffer-parser. In this integer scenario, you'll need to shift the bits or extract individual values to match your desired output, such as isolating specific bits to represent different statuses or control points. The flow provided at the end demonstrates the implementation of this conversion.",[15,6371,6372],{},"Now that you have the desired format for your output data, you may want to build a dashboard interface with LEDs, gauges, or charts to monitor and visualize the data you've retrieved. You can use the FlowFuse Dashboard, as suggested earlier.",[15,6374,6375],{},"The video below shows the updated dashboard interface used to monitor the stack light LED status:",[6273,6377],{"videoid":6378,"params":6276,"style":6277,"title":6278},"Nlyk_BATKGE",[15,6380,6381,6382,63,6384,6386],{},"Here is the flow you can import into your FlowFuse remote instance and deploy. Ensure that you have installed ",[19,6383,5434],{},[19,6385,323],{},". This flow includes S7 nodes for interacting with the S7 PLC and Project nodes for communicating with the FlowFuse hosted instance, where you will build the dashboard.",[15,6388,6389,6390,3830],{},"{% renderFlow %}\n",[146,6391,6392,6393,6396,6397,6399,6400,6402,6403,6405,6406,6409,6410,6413,6414,6416,6417,6420,6421,6424],{},"{\"id\":\"0ffc8c2703b5e059\",\"type\":\"group\",\"z\":\"FFF0000000000001\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,6394,6395],{},"\"061313277591a004\",\"a8499bc2443f0bd9\",\"2974dd47fda54b9c\",\"4c009f6076f47eb6\",\"f4378d1e7c268e1e\",\"63abd67743263739\"",",\"x\":54,\"y\":99,\"w\":732,\"h\":202},{\"id\":\"061313277591a004\",\"type\":\"s7 out\",\"z\":\"FFF0000000000001\",\"g\":\"0ffc8c2703b5e059\",\"endpoint\":\"f2f06ce027c97e4d\",\"variable\":\"Button_1\",\"name\":\"Button to Turn the RED Light ON\",\"x\":620,\"y\":140,\"wires\":",[146,6398],{},"},{\"id\":\"a8499bc2443f0bd9\",\"type\":\"s7 out\",\"z\":\"FFF0000000000001\",\"g\":\"0ffc8c2703b5e059\",\"endpoint\":\"f2f06ce027c97e4d\",\"variable\":\"Button_2\",\"name\":\"Button to turn the Yellow light ON\",\"x\":620,\"y\":200,\"wires\":",[146,6401],{},"},{\"id\":\"2974dd47fda54b9c\",\"type\":\"s7 out\",\"z\":\"FFF0000000000001\",\"g\":\"0ffc8c2703b5e059\",\"endpoint\":\"f2f06ce027c97e4d\",\"variable\":\"Button_3\",\"name\":\"Button to turn Green light  ON\",\"x\":610,\"y\":260,\"wires\":",[146,6404],{},"},{\"id\":\"4c009f6076f47eb6\",\"type\":\"project link in\",\"z\":\"FFF0000000000001\",\"g\":\"0ffc8c2703b5e059\",\"name\":\"Project in node to control the red light\",\"project\":\"all\",\"broadcast\":true,\"topic\":\"light_control_red\",\"x\":230,\"y\":140,\"wires\":[[\"061313277591a004\"]]},{\"id\":\"f4378d1e7c268e1e\",\"type\":\"project link in\",\"z\":\"FFF0000000000001\",\"g\":\"0ffc8c2703b5e059\",\"name\":\"Project in node to control the yellow light\",\"project\":\"all\",\"broadcast\":true,\"topic\":\"light_control_yellow\",\"x\":240,\"y\":200,\"wires\":[[\"a8499bc2443f0bd9\"]]},{\"id\":\"63abd67743263739\",\"type\":\"project link in\",\"z\":\"FFF0000000000001\",\"g\":\"0ffc8c2703b5e059\",\"name\":\"Project in node to control the green light\",\"project\":\"all\",\"broadcast\":true,\"topic\":\"light_control_green\",\"x\":240,\"y\":260,\"wires\":[[\"2974dd47fda54b9c\"]]},{\"id\":\"f2f06ce027c97e4d\",\"type\":\"s7 endpoint\",\"transport\":\"iso-on-tcp\",\"address\":\"192.168.1.6\",\"port\":\"102\",\"rack\":\"0\",\"slot\":\"1\",\"localtsaphi\":\"01\",\"localtsaplo\":\"00\",\"remotetsaphi\":\"01\",\"remotetsaplo\":\"00\",\"connmode\":\"rack-slot\",\"adapter\":\"\",\"busaddr\":\"2\",\"cycletime\":\"1000\",\"timeout\":\"2000\",\"name\":\"S7 Connection Configuration\",\"vartable\":",[146,6407,6408],{},"{\"addr\":\"DB1,X0.0\",\"name\":\"Button_1\"},{\"addr\":\"DB1,X0.1\",\"name\":\"Button_2\"},{\"addr\":\"DB1,X0.2\",\"name\":\"Button_3\"},{\"addr\":\"DB1,WORD2\",\"name\":\"LightStatus\"}","},{\"id\":\"23fd40630dbef712\",\"type\":\"group\",\"z\":\"FFF0000000000001\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,6411,6412],{},"\"a45637418005d0e5\",\"a8ea1622d1fad4ba\",\"8d9a9dc4183a778e\",\"d60a74a5430df7ae\"",",\"x\":54,\"y\":339,\"w\":972,\"h\":82},{\"id\":\"a45637418005d0e5\",\"type\":\"s7 in\",\"z\":\"FFF0000000000001\",\"g\":\"23fd40630dbef712\",\"endpoint\":\"f2f06ce027c97e4d\",\"mode\":\"single\",\"variable\":\"LightStatus\",\"diff\":true,\"name\":\"\",\"x\":150,\"y\":380,\"wires\":[[\"d60a74a5430df7ae\"]]},{\"id\":\"a8ea1622d1fad4ba\",\"type\":\"project link out\",\"z\":\"FFF0000000000001\",\"g\":\"23fd40630dbef712\",\"name\":\"project out node to send the light status\",\"mode\":\"link\",\"broadcast\":true,\"project\":\"c51f38c2-6c80-442a-a9e2-10ddd68fb606\",\"topic\":\"light_status\",\"x\":840,\"y\":380,\"wires\":",[146,6415],{},"},{\"id\":\"8d9a9dc4183a778e\",\"type\":\"buffer-parser\",\"z\":\"FFF0000000000001\",\"g\":\"23fd40630dbef712\",\"name\":\"\",\"data\":\"payload\",\"dataType\":\"msg\",\"specification\":\"spec\",\"specificationType\":\"ui\",\"items\":",[146,6418,6419],{},"{\"type\":\"bool\",\"name\":\"red\",\"offset\":0,\"length\":1,\"offsetbit\":0,\"scale\":\"1\",\"mask\":\"\"},{\"type\":\"bool\",\"name\":\"yellow\",\"offset\":0,\"length\":1,\"offsetbit\":1,\"scale\":\"1\",\"mask\":\"\"},{\"type\":\"bool\",\"name\":\"green\",\"offset\":0,\"length\":1,\"offsetbit\":2,\"scale\":\"1\",\"mask\":\"\"},{\"type\":\"bool\",\"name\":\"all\",\"offset\":0,\"length\":16,\"offsetbit\":0,\"scale\":\"1\",\"mask\":\"\"}",",\"swap1\":\"\",\"swap2\":\"\",\"swap3\":\"\",\"swap1Type\":\"swap\",\"swap2Type\":\"swap\",\"swap3Type\":\"swap\",\"msgProperty\":\"payload\",\"msgPropertyType\":\"str\",\"resultType\":\"keyvalue\",\"resultTypeType\":\"return\",\"multipleResult\":false,\"fanOutMultipleResult\":false,\"setTopic\":true,\"outputs\":1,\"x\":530,\"y\":380,\"wires\":[[\"a8ea1622d1fad4ba\"]]},{\"id\":\"d60a74a5430df7ae\",\"type\":\"buffer-maker\",\"z\":\"FFF0000000000001\",\"g\":\"23fd40630dbef712\",\"name\":\"\",\"specification\":\"spec\",\"specificationType\":\"ui\",\"items\":",[146,6422,6423],{},"{\"name\":\"1stword\",\"type\":\"uint16le\",\"length\":1,\"dataType\":\"msg\",\"data\":\"payload\"}",",\"swap1\":\"\",\"swap2\":\"\",\"swap3\":\"\",\"swap1Type\":\"swap\",\"swap2Type\":\"swap\",\"swap3Type\":\"swap\",\"msgProperty\":\"payload\",\"msgPropertyType\":\"str\",\"x\":330,\"y\":380,\"wires\":[[\"8d9a9dc4183a778e\"]]}",[15,6426,6427,6428,63,6430,6433],{},"Below is the flow that you can import and deploy into the hosted instance created on FlowFuse. With this flow, you'll have a dashboard to control and monitor the tower lights. Just make sure you have installed ",[19,6429,2153],{},[19,6431,6432],{},"@flowfuse\u002Fnode-red-dashboard-2-ui-led",", and ensure the hosted instance is in the same FlowFuse team as your remote instance.",[15,6435,6389,6436,3830],{},[146,6437,6438,6439,6442,6443,6445,6446,6448,6449,6451,6452,6455,6456,6459,6460,6463,6464,6467,6468,6470,6471,6474,6475,6477,6478,6481,6482,6484,6485,6488,6489,6492,6493,6496],{},"{\"id\":\"1f56099d53798b99\",\"type\":\"group\",\"z\":\"eb351e503901d04f\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,6440,6441],{},"\"1e6a379c83bac6b4\",\"f015125886fec5a6\",\"76c696f160db3ca2\",\"1974cfc417898151\",\"9c503fe31081dc2f\",\"9501e2eb7690a0b5\"",",\"x\":74,\"y\":79,\"w\":652,\"h\":202},{\"id\":\"1e6a379c83bac6b4\",\"type\":\"ui-button\",\"z\":\"eb351e503901d04f\",\"g\":\"1f56099d53798b99\",\"group\":\"d4102809d229cb95\",\"name\":\"\",\"label\":\"YELLOW\",\"order\":5,\"width\":\"3\",\"height\":\"2\",\"emulateClick\":false,\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"iconPosition\":\"left\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"buttonColor\":\"yellow\",\"textColor\":\"\",\"iconColor\":\"\",\"enableClick\":false,\"enablePointerdown\":true,\"pointerdownPayload\":\"1\",\"pointerdownPayloadType\":\"num\",\"enablePointerup\":true,\"pointerupPayload\":\"0\",\"pointerupPayloadType\":\"num\",\"x\":160,\"y\":180,\"wires\":[[\"1974cfc417898151\"]]},{\"id\":\"f015125886fec5a6\",\"type\":\"ui-button\",\"z\":\"eb351e503901d04f\",\"g\":\"1f56099d53798b99\",\"group\":\"d4102809d229cb95\",\"name\":\"\",\"label\":\"RED\",\"order\":4,\"width\":\"3\",\"height\":\"2\",\"emulateClick\":false,\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"iconPosition\":\"left\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"buttonColor\":\"red\",\"textColor\":\"\",\"iconColor\":\"\",\"enableClick\":false,\"enablePointerdown\":true,\"pointerdownPayload\":\"1\",\"pointerdownPayloadType\":\"num\",\"enablePointerup\":true,\"pointerupPayload\":\"0\",\"pointerupPayloadType\":\"num\",\"x\":150,\"y\":120,\"wires\":[[\"9501e2eb7690a0b5\"]]},{\"id\":\"76c696f160db3ca2\",\"type\":\"ui-button\",\"z\":\"eb351e503901d04f\",\"g\":\"1f56099d53798b99\",\"group\":\"d4102809d229cb95\",\"name\":\"\",\"label\":\"GREEN\",\"order\":6,\"width\":\"3\",\"height\":\"2\",\"emulateClick\":false,\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"iconPosition\":\"left\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"buttonColor\":\"green\",\"textColor\":\"\",\"iconColor\":\"\",\"enableClick\":false,\"enablePointerdown\":true,\"pointerdownPayload\":\"1\",\"pointerdownPayloadType\":\"num\",\"enablePointerup\":true,\"pointerupPayload\":\"0\",\"pointerupPayloadType\":\"num\",\"x\":160,\"y\":240,\"wires\":[[\"9c503fe31081dc2f\"]]},{\"id\":\"1974cfc417898151\",\"type\":\"project link out\",\"z\":\"eb351e503901d04f\",\"g\":\"1f56099d53798b99\",\"name\":\"Project out node to control the yellow light\",\"mode\":\"link\",\"broadcast\":true,\"project\":\"c51f38c2-6c80-442a-a9e2-10ddd68fb606\",\"topic\":\"light_control_yellow\",\"x\":530,\"y\":180,\"wires\":",[146,6444],{},"},{\"id\":\"9c503fe31081dc2f\",\"type\":\"project link out\",\"z\":\"eb351e503901d04f\",\"g\":\"1f56099d53798b99\",\"name\":\"Project out node to control the green light\",\"mode\":\"link\",\"broadcast\":true,\"project\":\"c51f38c2-6c80-442a-a9e2-10ddd68fb606\",\"topic\":\"light_control_green\",\"x\":520,\"y\":240,\"wires\":",[146,6447],{},"},{\"id\":\"9501e2eb7690a0b5\",\"type\":\"project link out\",\"z\":\"eb351e503901d04f\",\"g\":\"1f56099d53798b99\",\"name\":\"Project out node to control the red light\",\"mode\":\"link\",\"broadcast\":true,\"project\":\"c51f38c2-6c80-442a-a9e2-10ddd68fb606\",\"topic\":\"light_control_red\",\"x\":520,\"y\":120,\"wires\":",[146,6450],{},"},{\"id\":\"d4102809d229cb95\",\"type\":\"ui-group\",\"name\":\"Group 1\",\"page\":\"62085b96f178f643\",\"width\":\"3\",\"height\":1,\"order\":1,\"showTitle\":false,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\",\"groupType\":\"default\"},{\"id\":\"62085b96f178f643\",\"type\":\"ui-page\",\"name\":\"Page 1\",\"ui\":\"02c25e8a30f9379d\",\"path\":\"\u002Fpage1\",\"icon\":\"home\",\"layout\":\"notebook\",\"theme\":\"f6f5e7ae33bf6878\",\"breakpoints\":",[146,6453,6454],{},"{\"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\":\"02c25e8a30f9379d\",\"type\":\"ui-base\",\"name\":\"My Dashboard\",\"path\":\"\u002Fdashboard\",\"appIcon\":\"\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,6457,6458],{},"\"ui-notification\",\"ui-control\"",",\"showPathInSidebar\":false,\"showPageTitle\":true,\"navigationStyle\":\"default\",\"titleBarStyle\":\"hidden\"},{\"id\":\"f6f5e7ae33bf6878\",\"type\":\"ui-theme\",\"name\":\"Default Theme\",\"colors\":{\"surface\":\"#ffffff\",\"primary\":\"#0094ce\",\"bgPage\":\"#1a1a1a\",\"groupBg\":\"#000000\",\"groupOutline\":\"#000000\"},\"sizes\":{\"density\":\"default\",\"pagePadding\":\"12px\",\"groupGap\":\"12px\",\"groupBorderRadius\":\"4px\",\"widgetGap\":\"12px\"}},{\"id\":\"82cc6997fddd0b4b\",\"type\":\"group\",\"z\":\"eb351e503901d04f\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,6461,6462],{},"\"d163a7ab23f7458f\",\"20d211683534362b\",\"b1477193956e591b\",\"fb8801a4accc3c15\",\"2f62948afcfde259\",\"6966f129e718d20a\",\"a5cecf8e8adf6eef\"",",\"x\":74,\"y\":299,\"w\":872,\"h\":202},{\"id\":\"d163a7ab23f7458f\",\"type\":\"ui-led\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"Status of RED light\",\"group\":\"d4102809d229cb95\",\"order\":1,\"width\":\"1\",\"height\":\"3\",\"label\":\"\",\"labelPlacement\":\"left\",\"labelAlignment\":\"left\",\"states\":",[146,6465,6466],{},"{\"value\":\"true\",\"valueType\":\"bool\",\"color\":\"#ff0000\"},{\"value\":\"false\",\"valueType\":\"bool\",\"color\":\"#787878\"}",",\"allowColorForValueInMessage\":false,\"shape\":\"circle\",\"showBorder\":true,\"showGlow\":true,\"x\":810,\"y\":340,\"wires\":",[146,6469],{},"},{\"id\":\"20d211683534362b\",\"type\":\"ui-led\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"Status of Yellow light\",\"group\":\"d4102809d229cb95\",\"order\":2,\"width\":\"1\",\"height\":\"3\",\"label\":\"\",\"labelPlacement\":\"left\",\"labelAlignment\":\"left\",\"states\":",[146,6472,6473],{},"{\"value\":\"true\",\"valueType\":\"bool\",\"color\":\"#c8ff00\"},{\"value\":\"false\",\"valueType\":\"bool\",\"color\":\"#787878\"}",",\"allowColorForValueInMessage\":false,\"shape\":\"circle\",\"showBorder\":true,\"showGlow\":true,\"x\":820,\"y\":400,\"wires\":",[146,6476],{},"},{\"id\":\"b1477193956e591b\",\"type\":\"ui-led\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"Status of Green light\",\"group\":\"d4102809d229cb95\",\"order\":3,\"width\":\"1\",\"height\":\"3\",\"label\":\"\",\"labelPlacement\":\"left\",\"labelAlignment\":\"left\",\"states\":",[146,6479,6480],{},"{\"value\":\"true\",\"valueType\":\"bool\",\"color\":\"#41891a\"},{\"value\":\"false\",\"valueType\":\"bool\",\"color\":\"#787878\"}",",\"allowColorForValueInMessage\":false,\"shape\":\"circle\",\"showBorder\":true,\"showGlow\":true,\"x\":820,\"y\":460,\"wires\":",[146,6483],{},"},{\"id\":\"fb8801a4accc3c15\",\"type\":\"change\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"\",\"rules\":",[146,6486,6487],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.red\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":600,\"y\":340,\"wires\":[[\"d163a7ab23f7458f\"]]},{\"id\":\"2f62948afcfde259\",\"type\":\"change\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"\",\"rules\":",[146,6490,6491],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.yellow\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":600,\"y\":400,\"wires\":[[\"20d211683534362b\"]]},{\"id\":\"6966f129e718d20a\",\"type\":\"change\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"\",\"rules\":",[146,6494,6495],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.green\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":600,\"y\":460,\"wires\":[[\"b1477193956e591b\"]]},{\"id\":\"a5cecf8e8adf6eef\",\"type\":\"project link in\",\"z\":\"eb351e503901d04f\",\"g\":\"82cc6997fddd0b4b\",\"name\":\"project in node to receive the light status\",\"project\":\"all\",\"broadcast\":true,\"topic\":\"light_status\",\"x\":260,\"y\":400,\"wires\":[[\"fb8801a4accc3c15\",\"2f62948afcfde259\",\"6966f129e718d20a\"]]}",[34,6498,6500],{"id":6499},"troubleshooting","Troubleshooting",[15,6502,6503],{},"When you try to establish a connection with the PLC, you may encounter the following error. This error occurs because your device has established the connection but is unable to communicate. To resolve this issue, ensure that you have configured all the settings mentioned in the prerequisites. If the problem persists, it could be because your PLC and the device running Node-RED are on different networks.",[15,6505,6506,6510],{},[392,6507],{"alt":6508,"src":6509,"dataZoomable":50},"\"Error: This service is not implemented on the modeul or frame error was reported\"","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Ferror.png",[397,6511,6508],{},[15,6513,6514],{},"Make sure the IP addresses of your device and PLC are in the same subnet. If the PLC is connected to the internet via a router, all devices (PLC, Node-RED device, and router) should have IP addresses within the same subnet. For example, if your PLC has the address 192.168.1.1, ensure that the other devices have IP addresses in the range 192.168.1.x.",[34,6516,6518],{"id":6517},"conclusion","Conclusion",[15,6520,6521],{},"Integrating Siemens S7 PLCs with Node-RED opens up powerful automation possibilities with minimal complexity. By following the steps outlined in this guide, you can easily connect your PLC to Node-RED, control devices, and visualize real-time data on dashboards. Whether you're writing data to control outputs or reading sensor values, Node-RED offers a flexible, user-friendly platform for industrial automation.",[15,6523,6524,6525,6529],{},"Beyond Siemens S7, FlowFuse connects Allen-Bradley, Omron, Beckhoff, and any Modbus or OPC UA-enabled PLC to MQTT, cloud, and enterprise systems. See the ",[307,6526,6528],{"href":6527},"\u002Flanding\u002Fplc\u002F","FlowFuse PLC integration overview"," for all supported protocols and use cases.",{"title":50,"searchDepth":250,"depth":250,"links":6531},[6532,6533,6542,6543],{"id":5334,"depth":185,"text":5335},{"id":5406,"depth":185,"text":5407,"children":6534},[6535,6538,6539,6540,6541],{"id":5427,"depth":221,"text":5428,"children":6536},[6537],{"id":5443,"depth":250,"text":5444},{"id":5470,"depth":221,"text":5471},{"id":6171,"depth":221,"text":6172},{"id":6232,"depth":221,"text":6233},{"id":6291,"depth":221,"text":6292},{"id":6499,"depth":185,"text":6500},{"id":6517,"depth":185,"text":6518},{"type":6545,"title":6546,"description":6547},"contact","Turn PLC Data Into Operational Visibility with FlowFuse","Reading and writing S7 data is just the start. FlowFuse extends your existing PLCs, SCADA, and MES, no rip-and-replace, into a single platform for remote device management, dashboards, and DevOps pipelines. Connect Siemens, Allen-Bradley, Modbus, and OPC UA devices, and get your first operational application running this week. SOC 2 certified, with RBAC, SSO, and self-hosted options. Talk to our team about your architecture.","2025-01-17","Learn how to read data from and write data to Siemens S7 PLCs (S7-1200\u002F1500) for industrial monitoring and control. This guide covers PLC setup, the S7 protocol over ISO-on-TCP, addressing, and building dashboards, no deep PLC expertise required.","\u002Fblog\u002F2025\u002F01\u002Fimages\u002Fs7-with-node-red.png",{"keywords":6552,"excerpt":6553},"siemens s7 plc data, read data from s7 plc, write data to s7 plc, siemens s7 1200 with node-red, siemens s7 1500 with node-red, s7 with node-red, plc data integration, industrial automation",{"type":12,"value":6554},[6555],[15,6556,5325],{},"\u002Fblog\u002F2025\u002F01\u002Fintegrating-siemens-s7-plcs-with-node-red-guide",{"title":5318,"description":6549},{"loc":6557},"blog\u002F2025\u002F01\u002Fintegrating-siemens-s7-plcs-with-node-red-guide","A step-by-step beginner's guide to reading, writing, and monitoring data from Siemens S7-1200 and S7-1500 PLCs.",[6563,965,6564,966],"node-red","plc","This guide explains how to read data from and write data to Siemens S7 PLCs (S7-1200\u002F1500) using Node-RED and the S7 protocol over ISO-on-TCP (port 102). Prerequisites: enable PUT\u002FGET communication, disable optimized block access on your data blocks, and run Node-RED on a networked device (the FlowFuse Device Agent makes this remotely manageable). After installing an S7 node, you configure an endpoint with the PLC's IP, rack, and slot, map TIA Portal addresses to the node's address format, then use s7-in to read values and s7-out to write them, enabling remote monitoring, control, and dashboards without deep PLC expertise. The same patterns extend to Allen-Bradley, Modbus, and OPC UA devices for multi-vendor environments.","Zumw3mIy-PpnM75KupEm8sJVEiDdkOzuF4B2qYSxufk",{"id":6568,"title":6569,"authors":6570,"body":6571,"cta":7425,"date":7429,"description":7430,"extension":946,"image":7431,"lastUpdated":948,"meta":7432,"navigation":253,"path":7438,"seo":7439,"sitemap":7440,"stem":7441,"subtitle":7442,"tags":7443,"tldr":7445,"video":3,"__hash__":7446},"blog\u002Fblog\u002F2024\u002F11\u002Fesp32-with-node-red.md","Interacting with ESP32 Using Node-RED and MQTT (2026)",[10],{"type":12,"value":6572,"toc":7414},[6573,6576,6579,6581,6584,6625,6628,6632,6635,6639,6649,6652,6656,6659,6662,6671,6674,6679,6702,6710,6721,6725,6728,6733,6787,6794,6829,6845,6848,6856,6860,6867,6872,6892,6897,6902,7339,7350,7353,7362,7365,7367,7389,7394,7406,7408,7411],[15,6574,6575],{},"The ESP32 is an affordable and powerful microchip that combines Wi-Fi and Bluetooth in one small package. It's commonly used in smart devices like home automation systems, wearables, and other IoT projects. Despite its low cost (around $6), it offers strong performance, and low power consumption, and is compatible with popular platforms like Arduino. Whether you're a hobbyist or a business, the ESP32 provides great value, making it easy to create wireless devices without a big investment. This tutorial demonstrates how to set up communication between the ESP32 and Node-RED using MQTT, along with an interactive dashboard via FlowFuse for a user-friendly interface.",[6273,6577],{"videoid":6578,"params":6276,"style":6277,"title":6278},"ecfJ-9MxyVE",[34,6580,1163],{"id":1162},[15,6582,6583],{},"To follow this tutorial, you'll need the following:",[100,6585,6586,6592,6598,6620],{},[103,6587,6588,6591],{},[338,6589,6590],{},"ESP32 microcontroller",": The hardware you'll be using for this project.",[103,6593,6594,6597],{},[338,6595,6596],{},"USB cable",": To connect the ESP32 to your computer.",[103,6599,6600,6603,6604,6609,6610],{},[338,6601,6602],{},"Arduino IDE",": Installed and set up to program your ESP32. ",[307,6605,6608],{"href":6606,"rel":6607},"https:\u002F\u002Fsupport.arduino.cc\u002Fhc\u002Fen-us\u002Farticles\u002F360019833020-Download-and-install-Arduino-IDE",[311],"Download"," the Arduino IDE if you haven't already done so.\n",[100,6611,6612],{},[103,6613,6614,6615],{},"Additionally, if you haven't set up the Arduino IDE for the ESP32 board, please follow this tutorial: ",[307,6616,6619],{"href":6617,"rel":6618},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=CD8VJl27n94",[311],"How to Set Up ESP32 with Arduino IDE",[103,6621,6622,6624],{},[338,6623,2084],{},": This will allow you to create and deploy Node-RED instances securely on the cloud with a single click, collaborate on your Node-RED projects with your team, manage and program your edge devices remotely, and provide an MQTT broker with an interface for securely managing clients.",[15,6626,6627],{},"If you haven’t signed up for a FlowFuse account yet, [sign up]({% include \"sign-up-url.njk\" %}?utm_campaign=60718323-BCTA&utm_source=blog&utm_medium=cta&utm_term=high_intent&utm_content=Interacting%20with%20ESP32%20Using%20Node-RED%20and%20MQTT) now.",[34,6629,6631],{"id":6630},"getting-started-with-esp32-and-node-red","Getting Started with ESP32 and Node-RED",[15,6633,6634],{},"In this section, we’ll set up Node-RED on FlowFuse, create an MQTT connection, and configure everything to interact with your ESP32. This will lay the foundation for building your IoT flows and controlling devices.",[996,6636,6638],{"id":6637},"step-1-creating-node-red-instance-on-flowfuse-cloud","Step 1: Creating Node-RED instance on FlowFuse Cloud",[15,6640,6641,6642,6644,6645,167],{},"Start by logging into your ",[307,6643,1155],{"href":213}," account and creating a new Node-RED instance. For more information on creating a Node-RED instance, refer to the ",[307,6646,6648],{"href":6647},"\u002Fdocs\u002Fuser\u002Fintroduction\u002F#creating-a-node-red-instance","FlowFuse documentation",[15,6650,6651],{},"Once the instance is created, open the Node-RED editor.",[996,6653,6655],{"id":6654},"step-2-creating-and-configuring-mqtt-clients-in-flowfuse","Step 2: Creating and Configuring MQTT Clients in FlowFuse",[15,6657,6658],{},"In this step, we’ll set up MQTT to enable communication between Node-RED and the ESP32. MQTT (Message Queuing Telemetry Transport) is a lightweight messaging protocol designed for reliable, low-bandwidth communication between devices in IoT applications.",[15,6660,6661],{},"We use MQTT because it allows devices to communicate over a network (like Wi-Fi) without the need for a direct physical connection. This makes it perfect for long-distance communication, where devices need to send and receive data efficiently, even when they are not physically connected or close to each other.",[15,6663,6664,6668],{},[392,6665],{"alt":6666,"dataZoomable":50,"src":6667},"Diagram showing the flow of data and how commands are sent to the ESP32 using MQTT using Node-RED.","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fesp32-mqtt-node-red.png",[397,6669,6670],{},"Diagram showing the flow of data and how commands are sent to the ESP32 using MQTT using Node-RED",[15,6672,6673],{},"In our setup, Node-RED will publish commands to the MQTT broker, and the ESP32 will subscribe to topics to receive responses. The ESP32 will then perform actions, such as controlling an LED. To facilitate this, we’ll create two MQTT clients in FlowFuse (since the MQTT broker is already set up and managed by FlowFuse, you don’t need to worry about its configuration or maintenance). One client will be for Node-RED, and the other will be for the ESP32. These clients will handle the secure and reliable exchange of messages, ensuring smooth communication between the two devices.",[15,6675,6676],{},[338,6677,6678],{},"To Create MQTT Clients in FlowFuse:",[326,6680,6681,6684,6687,6690],{},[103,6682,6683],{},"Navigate to your FlowFuse platform and log in to your account.",[103,6685,6686],{},"In the left sidebar, click on \"Broker\".",[103,6688,6689],{},"In the newly opened interface, click the “Create Client” button.",[103,6691,6692,6693],{},"Enter a username and password for your MQTT client. Confirm the password.\n",[100,6694,6695],{},[103,6696,6697,6698,6701],{},"You can leave the default pattern as ",[19,6699,6700],{},"#"," for access control, or set a custom pattern if needed.",[15,6703,6704,6708],{},[392,6705],{"alt":6706,"dataZoomable":50,"src":6707},"Interface for setting MQTT client details and credentials","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fmqtt-client-create.png",[397,6709,6706],{},[326,6711,6712,6715,6718],{"start":257},[103,6713,6714],{},"Click \"Create\" to generate the client.",[103,6716,6717],{},"Copy the client ID and save it somewhere secure for later use.",[103,6719,6720],{},"Repeat the same steps to create the second MQTT client for the ESP32.",[996,6722,6724],{"id":6723},"step-3-building-a-node-red-dashboard-to-send-commands-over-mqtt","Step 3: Building a Node-RED Dashboard to Send Commands Over MQTT",[15,6726,6727],{},"Now that we’ve created the MQTT clients, it’s time to build a Node-RED dashboard and create a flow that will publish commands to the FlowFuse MQTT broker. This will later allow you to interact with your ESP32 using a user-friendly interface.",[15,6729,6730],{},[338,6731,6732],{},"Let's first create a flow to connect to the MQTT broker with the client config we have created:",[326,6734,6735,6741,6746,6749,6769,6772,6778,6781,6784],{},[103,6736,4995,6737,6740],{},[338,6738,6739],{},"mqtt out"," node onto the canvas in Node-RED.",[103,6742,435,6743,6745],{},[338,6744,6739],{}," node to open the settings.",[103,6747,6748],{},"Click the pencil icon next to the Server field to open the MQTT broker configuration.",[103,6750,6751,6752],{},"In the configuration, enter the following details:\n",[100,6753,6754,6760,6763,6766],{},[103,6755,6756,6757],{},"Server: ",[19,6758,6759],{},"broker.flowfuse.cloud",[103,6761,6762],{},"Client ID: The Client ID you created earlier.",[103,6764,6765],{},"Username: The MQTT username (Client ID).",[103,6767,6768],{},"Password: The MQTT password.",[103,6770,6771],{},"Click \"Add\" to save the configuration, then select the newly added configuration.",[103,6773,6774,6775,167],{},"In the Topic field, enter a topic name, such as ",[19,6776,6777],{},"\u002FLEDControl",[103,6779,6780],{},"Click \"Done\" to close the settings.",[103,6782,6783],{},"Click \"Deploy\" in the top-right corner to deploy the flow.",[103,6785,6786],{},"Once deployed, check the MQTT out node for a Connected status, confirming the connection to the MQTT broker.",[15,6788,6789,6790],{},"For this example, we will create a very simple dashboard. If you're not familiar with FlowFuse Dashboard, you can refer to the following blog to get started: ",[307,6791,6793],{"href":6792},"\u002Fblog\u002F2024\u002F03\u002Fdashboard-getting-started\u002F","FlowFuse Dashboard: Getting Started",[326,6795,6796,6802,6809,6815,6821,6826],{},[103,6797,6798,6799,6801],{},"Install the ",[19,6800,2153],{}," from the Node-RED Palette Manager.",[103,6803,6804,6805,6808],{},"Drag two ",[338,6806,6807],{},"ui-button"," widgets onto the canvas.",[103,6810,6811,6812,6814],{},"Double-click on the first button and set the Label to \"ON\", the Background Color to Green, and the Payload to ",[19,6813,62],{},". Adjust the Width and Height as needed.",[103,6816,6817,6818,167],{},"Double-click on the second button and set the Label to \"OFF\", the Background Color to Red, and the Payload to ",[19,6819,6820],{},"2",[103,6822,6823,6824,1361],{},"Connect the output of both buttons to the input of the ",[338,6825,6739],{},[103,6827,6828],{},"Click \"Deploy\" to save the flow.",[15,6830,6389,6831,3830],{},[146,6832,6833,6834,6836,6837,6839,6840,6842,6843,1596],{},"{\"id\":\"59887a8115c95eae\",\"type\":\"tab\",\"label\":\"Flow 1\",\"disabled\":false,\"info\":\"\",\"env\":",[146,6835],{},"},{\"id\":\"02c25e8a30f9379d\",\"type\":\"ui-base\",\"name\":\"My Dashboard\",\"path\":\"\u002Fdashboard\",\"appIcon\":\"\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,6838,6458],{},",\"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\":\"\u002Fpage1\",\"icon\":\"home\",\"layout\":\"grid\",\"theme\":\"cfb2ab9ff30660fc\",\"breakpoints\":",[146,6841,6454],{},",\"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\":\"\u002FLedControl\",\"qos\":\"\",\"retain\":\"\",\"respTopic\":\"\",\"contentType\":\"\",\"userProps\":\"\",\"correl\":\"\",\"expiry\":\"\",\"broker\":\"def97b29f5f7baab\",\"x\":390,\"y\":140,\"wires\":",[146,6844],{},[15,6846,6847],{},"Now, when you click either the \"ON\" or \"OFF\" button on the dashboard, it will send either 1 or 2 as the payload. The ESP32 will use this payload in its code to turn the LED on or off. To view the dashboard, switch to the Dashboard 2.0 tab on the right side and click the Open Dashboard button. The dashboard will look similar to the image below.",[15,6849,6850,6854],{},[392,6851],{"alt":6852,"dataZoomable":50,"src":6853},"FlowFuse Dashboard Build to control the ESP32 LED","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fdashboard2.png",[397,6855,6852],{},[996,6857,6859],{"id":6858},"step-4-programming-esp32-to-receive-commands-from-mqtt-and-control-led","Step 4: Programming ESP32 to receive commands from MQTT and Control LED",[15,6861,6862,6863,6866],{},"Now, let's move on to the final step. Before proceeding, make sure your ESP32 is ",[338,6864,6865],{},"connected to your laptop or computer via USB","*. The USB connection is essential for uploading the code (sketch) to the ESP32, which will enable it to connect to the internet and communicate with the MQTT broker.\nThe ESP32 will subscribe to the MQTT topic we configured earlier (e.g., \u002FLEDControl). Based on the received payload (1 or 2), it will control the LED accordingly, turning it on or off.",[15,6868,6869],{},[338,6870,6871],{},"Setting up Arduino IDE:",[326,6873,6874,6877,6880,6883,6886,6889],{},[103,6875,6876],{},"Open the Arduino IDE on your computer.",[103,6878,6879],{},"Ensure you have selected the correct board and port in the Tools menu.",[103,6881,6882],{},"Install the necessary library:",[103,6884,6885],{},"Go to Tools > \"Manage Libraries\".",[103,6887,6888],{},"Search for and install the \"EspMQTTClient\" library by Patrick Lapointe.",[103,6890,6891],{},"The library installation will prompt you to install its dependencies, ensure that you tick that option and proceed to install.",[15,6893,6894],{},[338,6895,6896],{},"Code for ESP32:",[326,6898,6899],{},[103,6900,6901],{},"Copy the following code into the Arduino IDE:",[42,6903,6907],{"className":6904,"code":6905,"language":6906,"meta":50,"style":50},"language-cpp shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","#if defined(ESP32)\n#include \u003CWiFi.h>\n#elif defined(ESP8266)\n#include \u003CESP8266WiFi.h>\n#endif\n\n#include \u003CPubSubClient.h>\n\n#define LedPin 2 \u002F\u002F ESP32 built-in LED pin\n\n\u002F\u002F WiFi and MQTT settings\nconst char* ssid = \"\"; \u002F\u002F Change this to your WiFi SSID\nconst char* password = \"\"; \u002F\u002F Change this to your WiFi password\nconst char* mqtt_server = \"broker.flowfuse.cloud\"; \u002F\u002F FlowFuse MQTT broker server\n\n\u002F\u002F MQTT client credentials\nconst char* mqtt_client_id = \"\"; \u002F\u002F Replace with your MQTT client ID\nconst char* mqtt_username = \"\"; \u002F\u002F Replace with your MQTT username\nconst char* mqtt_password = \"\"; \u002F\u002F Replace with your MQTT password\n\nWiFiClient espClient;\nPubSubClient client(espClient);\n\n\u002F\u002F Function to connect to WiFi\nvoid setup_wifi() {\n    delay(10);\n    Serial.println();\n    Serial.print(\"Connecting to \");\n    Serial.print(ssid);\n    WiFi.begin(ssid, password);\n\n    while(WiFi.status() != WL_CONNECTED) {\n        delay(500);\n        Serial.print(\".\");\n }\n\n    Serial.println(\"\\nWiFi connected\");\n    Serial.print(\"IP address: \");\n    Serial.println(WiFi.localIP());\n}\n\n\u002F\u002F Callback function to handle messages from subscribed topics\nvoid callback(char* topic, byte* payload, unsigned int length) {\n\n String msg;\n    for (int i = 0; i \u003C length; i++) {\n msg += (char)payload[i];\n }\n\n \u002F\u002F Control LED based on message\n    if (msg == \"1\") {\n        digitalWrite(LedPin, HIGH); \u002F\u002F Turn LED on\n }\n    else if (msg == \"2\") {\n        digitalWrite(LedPin, LOW); \u002F\u002F Turn LED off\n }\n}\n\n\u002F\u002F Function to connect to MQTT broker\nvoid reconnect() {\n    while (!client.connected()) {\n        Serial.println(\"Attempting MQTT connection...\");\n\n \u002F\u002F Connect to MQTT broker with the client ID, username, and password\n        if (client.connect(mqtt_client_id, mqtt_username, mqtt_password)) {\n            Serial.println(\"Connected to MQTT broker\");\n            client.subscribe(\"\u002FLedControl\");\n }\n        else {\n            Serial.print(\"Failed, rc=\");\n            Serial.print(client.state());\n            Serial.println(\" trying again in 5 seconds\");\n            delay(5000);\n }\n }\n}\n\nvoid setup() {\n    Serial.begin(115200);\n    pinMode(LedPin, OUTPUT);\n    setup_wifi();\n    client.setServer(mqtt_server, 1883);\n    client.setCallback(callback);\n}\n\nvoid loop() {\n    if (!client.connected()) {\n        reconnect();\n }\n    client.loop();\n}\n","cpp",[19,6908,6909,6914,6919,6924,6929,6934,6938,6943,6947,6955,6959,6964,6969,6974,6979,6983,6988,6993,6998,7003,7007,7012,7017,7021,7026,7031,7036,7041,7046,7051,7056,7060,7065,7070,7075,7079,7083,7088,7093,7098,7102,7106,7111,7116,7120,7125,7130,7135,7139,7143,7148,7153,7158,7162,7167,7172,7176,7180,7184,7189,7194,7199,7204,7208,7213,7218,7223,7228,7232,7237,7242,7247,7252,7257,7261,7265,7269,7273,7278,7283,7288,7293,7298,7303,7307,7311,7316,7321,7326,7330,7335],{"__ignoreMap":50},[146,6910,6911],{"class":148,"line":149},[146,6912,6913],{},"#if defined(ESP32)\n",[146,6915,6916],{"class":148,"line":185},[146,6917,6918],{},"#include \u003CWiFi.h>\n",[146,6920,6921],{"class":148,"line":221},[146,6922,6923],{},"#elif defined(ESP8266)\n",[146,6925,6926],{"class":148,"line":250},[146,6927,6928],{},"#include \u003CESP8266WiFi.h>\n",[146,6930,6931],{"class":148,"line":257},[146,6932,6933],{},"#endif\n",[146,6935,6936],{"class":148,"line":284},[146,6937,254],{"emptyLinePlaceholder":253},[146,6939,6940],{"class":148,"line":666},[146,6941,6942],{},"#include \u003CPubSubClient.h>\n",[146,6944,6945],{"class":148,"line":1603},[146,6946,254],{"emptyLinePlaceholder":253},[146,6948,6949,6952],{"class":148,"line":1608},[146,6950,6951],{},"#define LedPin 2",[146,6953,6954],{}," \u002F\u002F ESP32 built-in LED pin\n",[146,6956,6957],{"class":148,"line":1624},[146,6958,254],{"emptyLinePlaceholder":253},[146,6960,6961],{"class":148,"line":2546},[146,6962,6963],{},"\u002F\u002F WiFi and MQTT settings\n",[146,6965,6966],{"class":148,"line":2564},[146,6967,6968],{},"const char* ssid = \"\"; \u002F\u002F Change this to your WiFi SSID\n",[146,6970,6971],{"class":148,"line":2569},[146,6972,6973],{},"const char* password = \"\"; \u002F\u002F Change this to your WiFi password\n",[146,6975,6976],{"class":148,"line":2574},[146,6977,6978],{},"const char* mqtt_server = \"broker.flowfuse.cloud\"; \u002F\u002F FlowFuse MQTT broker server\n",[146,6980,6981],{"class":148,"line":2594},[146,6982,254],{"emptyLinePlaceholder":253},[146,6984,6985],{"class":148,"line":2614},[146,6986,6987],{},"\u002F\u002F MQTT client credentials\n",[146,6989,6990],{"class":148,"line":2633},[146,6991,6992],{},"const char* mqtt_client_id = \"\"; \u002F\u002F Replace with your MQTT client ID\n",[146,6994,6995],{"class":148,"line":2662},[146,6996,6997],{},"const char* mqtt_username = \"\"; \u002F\u002F Replace with your MQTT username\n",[146,6999,7000],{"class":148,"line":2667},[146,7001,7002],{},"const char* mqtt_password = \"\"; \u002F\u002F Replace with your MQTT password\n",[146,7004,7005],{"class":148,"line":2672},[146,7006,254],{"emptyLinePlaceholder":253},[146,7008,7009],{"class":148,"line":2692},[146,7010,7011],{},"WiFiClient espClient;\n",[146,7013,7014],{"class":148,"line":2705},[146,7015,7016],{},"PubSubClient client(espClient);\n",[146,7018,7019],{"class":148,"line":2722},[146,7020,254],{"emptyLinePlaceholder":253},[146,7022,7023],{"class":148,"line":2787},[146,7024,7025],{},"\u002F\u002F Function to connect to WiFi\n",[146,7027,7028],{"class":148,"line":2801},[146,7029,7030],{},"void setup_wifi() {\n",[146,7032,7033],{"class":148,"line":2861},[146,7034,7035],{},"    delay(10);\n",[146,7037,7038],{"class":148,"line":2920},[146,7039,7040],{},"    Serial.println();\n",[146,7042,7043],{"class":148,"line":2978},[146,7044,7045],{},"    Serial.print(\"Connecting to \");\n",[146,7047,7048],{"class":148,"line":3034},[146,7049,7050],{},"    Serial.print(ssid);\n",[146,7052,7053],{"class":148,"line":3091},[146,7054,7055],{},"    WiFi.begin(ssid, password);\n",[146,7057,7058],{"class":148,"line":3148},[146,7059,254],{"emptyLinePlaceholder":253},[146,7061,7062],{"class":148,"line":3204},[146,7063,7064],{},"    while(WiFi.status() != WL_CONNECTED) {\n",[146,7066,7067],{"class":148,"line":3210},[146,7068,7069],{},"        delay(500);\n",[146,7071,7072],{"class":148,"line":3216},[146,7073,7074],{},"        Serial.print(\".\");\n",[146,7076,7077],{"class":148,"line":3221},[146,7078,1843],{},[146,7080,7081],{"class":148,"line":3226},[146,7082,254],{"emptyLinePlaceholder":253},[146,7084,7085],{"class":148,"line":3246},[146,7086,7087],{},"    Serial.println(\"\\nWiFi connected\");\n",[146,7089,7090],{"class":148,"line":3263},[146,7091,7092],{},"    Serial.print(\"IP address: \");\n",[146,7094,7095],{"class":148,"line":3283},[146,7096,7097],{},"    Serial.println(WiFi.localIP());\n",[146,7099,7100],{"class":148,"line":3312},[146,7101,754],{},[146,7103,7104],{"class":148,"line":3317},[146,7105,254],{"emptyLinePlaceholder":253},[146,7107,7108],{"class":148,"line":3322},[146,7109,7110],{},"\u002F\u002F Callback function to handle messages from subscribed topics\n",[146,7112,7113],{"class":148,"line":3342},[146,7114,7115],{},"void callback(char* topic, byte* payload, unsigned int length) {\n",[146,7117,7118],{"class":148,"line":3361},[146,7119,254],{"emptyLinePlaceholder":253},[146,7121,7122],{"class":148,"line":3380},[146,7123,7124],{}," String msg;\n",[146,7126,7127],{"class":148,"line":3409},[146,7128,7129],{},"    for (int i = 0; i \u003C length; i++) {\n",[146,7131,7132],{"class":148,"line":3415},[146,7133,7134],{}," msg += (char)payload[i];\n",[146,7136,7137],{"class":148,"line":3421},[146,7138,1843],{},[146,7140,7141],{"class":148,"line":3435},[146,7142,254],{"emptyLinePlaceholder":253},[146,7144,7145],{"class":148,"line":3449},[146,7146,7147],{}," \u002F\u002F Control LED based on message\n",[146,7149,7150],{"class":148,"line":3464},[146,7151,7152],{},"    if (msg == \"1\") {\n",[146,7154,7155],{"class":148,"line":3479},[146,7156,7157],{},"        digitalWrite(LedPin, HIGH); \u002F\u002F Turn LED on\n",[146,7159,7160],{"class":148,"line":3498},[146,7161,1843],{},[146,7163,7164],{"class":148,"line":3527},[146,7165,7166],{},"    else if (msg == \"2\") {\n",[146,7168,7169],{"class":148,"line":3532},[146,7170,7171],{},"        digitalWrite(LedPin, LOW); \u002F\u002F Turn LED off\n",[146,7173,7174],{"class":148,"line":3545},[146,7175,1843],{},[146,7177,7178],{"class":148,"line":3561},[146,7179,754],{},[146,7181,7182],{"class":148,"line":3575},[146,7183,254],{"emptyLinePlaceholder":253},[146,7185,7186],{"class":148,"line":3596},[146,7187,7188],{},"\u002F\u002F Function to connect to MQTT broker\n",[146,7190,7191],{"class":148,"line":3625},[146,7192,7193],{},"void reconnect() {\n",[146,7195,7196],{"class":148,"line":3630},[146,7197,7198],{},"    while (!client.connected()) {\n",[146,7200,7201],{"class":148,"line":3643},[146,7202,7203],{},"        Serial.println(\"Attempting MQTT connection...\");\n",[146,7205,7206],{"class":148,"line":3658},[146,7207,254],{"emptyLinePlaceholder":253},[146,7209,7210],{"class":148,"line":3675},[146,7211,7212],{}," \u002F\u002F Connect to MQTT broker with the client ID, username, and password\n",[146,7214,7215],{"class":148,"line":3680},[146,7216,7217],{},"        if (client.connect(mqtt_client_id, mqtt_username, mqtt_password)) {\n",[146,7219,7220],{"class":148,"line":3686},[146,7221,7222],{},"            Serial.println(\"Connected to MQTT broker\");\n",[146,7224,7225],{"class":148,"line":3706},[146,7226,7227],{},"            client.subscribe(\"\u002FLedControl\");\n",[146,7229,7230],{"class":148,"line":3738},[146,7231,1843],{},[146,7233,7234],{"class":148,"line":4632},[146,7235,7236],{},"        else {\n",[146,7238,7239],{"class":148,"line":4642},[146,7240,7241],{},"            Serial.print(\"Failed, rc=\");\n",[146,7243,7244],{"class":148,"line":4652},[146,7245,7246],{},"            Serial.print(client.state());\n",[146,7248,7249],{"class":148,"line":4663},[146,7250,7251],{},"            Serial.println(\" trying again in 5 seconds\");\n",[146,7253,7254],{"class":148,"line":4676},[146,7255,7256],{},"            delay(5000);\n",[146,7258,7259],{"class":148,"line":4691},[146,7260,1843],{},[146,7262,7263],{"class":148,"line":4716},[146,7264,1843],{},[146,7266,7267],{"class":148,"line":4721},[146,7268,754],{},[146,7270,7271],{"class":148,"line":4731},[146,7272,254],{"emptyLinePlaceholder":253},[146,7274,7275],{"class":148,"line":4742},[146,7276,7277],{},"void setup() {\n",[146,7279,7280],{"class":148,"line":4754},[146,7281,7282],{},"    Serial.begin(115200);\n",[146,7284,7285],{"class":148,"line":4770},[146,7286,7287],{},"    pinMode(LedPin, OUTPUT);\n",[146,7289,7290],{"class":148,"line":4795},[146,7291,7292],{},"    setup_wifi();\n",[146,7294,7295],{"class":148,"line":4800},[146,7296,7297],{},"    client.setServer(mqtt_server, 1883);\n",[146,7299,7300],{"class":148,"line":4810},[146,7301,7302],{},"    client.setCallback(callback);\n",[146,7304,7305],{"class":148,"line":4821},[146,7306,754],{},[146,7308,7309],{"class":148,"line":4834},[146,7310,254],{"emptyLinePlaceholder":253},[146,7312,7313],{"class":148,"line":4839},[146,7314,7315],{},"void loop() {\n",[146,7317,7318],{"class":148,"line":4844},[146,7319,7320],{},"    if (!client.connected()) {\n",[146,7322,7323],{"class":148,"line":4849},[146,7324,7325],{},"        reconnect();\n",[146,7327,7328],{"class":148,"line":4865},[146,7329,1843],{},[146,7331,7332],{"class":148,"line":4891},[146,7333,7334],{},"    client.loop();\n",[146,7336,7337],{"class":148,"line":4896},[146,7338,754],{},[326,7340,7341,7344,7347],{"start":185},[103,7342,7343],{},"Replace the placeholder values in the code: SSID (your Wi-Fi network's SSID), Wi-Fi Password (your Wi-Fi network's password), MQTT Client ID (the MQTT client ID you generated for esp32), MQTT Username and Password (the MQTT credentials you created).",[103,7345,7346],{},"After you've made these changes, click \"Upload\" in the Arduino IDE to upload the code to your ESP32.",[103,7348,7349],{},"Once the upload is complete, open the Serial Monitor (set the baud rate to 115200) to monitor the output.",[15,7351,7352],{},"If everything is set up correctly, you should see the output in the Serial Monitor as shown in the image.",[15,7354,7355,7359],{},[392,7356],{"alt":7357,"dataZoomable":50,"src":7358},"Serial monitor displaying the result when everything is set up correctly.","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fserial-monitor.png",[397,7360,7361],{},"Serial monitor displays the result when everything is set up correctly.",[15,7363,7364],{},"Once you verify the setup, you can unplug the USB from the computer and connect the ESP32 to a power adapter. With this, your ESP32 is now powered and connected to Wi-Fi (make sure your device is on the same Wi-Fi network as the one configured in the code), allowing you to control the LED from anywhere in the world via the MQTT commands sent through Node-RED.",[996,7366,6500],{"id":6499},[326,7368,7369,7384],{},[103,7370,7371,7374],{},[338,7372,7373],{},"Can't Upload Code to ESP32",[100,7375,7376],{},[103,7377,7378,7379,7383],{},"Solution: Make sure the correct board and port are selected in the Arduino IDE.\nCheck Tools > Board for the right ESP32 model and Tools > Port for the correct connection.\nIf the port is missing, ",[307,7380,6608],{"href":7381,"rel":7382},"https:\u002F\u002Fwww.silabs.com\u002Fdeveloper-tools\u002Fusb-to-uart-bridge-vcp-drivers?tab=downloads",[311]," and reinstall the CP210x USB drivers.",[103,7385,7386],{},[338,7387,7388],{},"ESP32 Keeps Disconnecting from MQTT",[100,7390,7391],{},[103,7392,7393],{},"Solution: Make sure both the ESP32 and Node-RED have unique MQTT client IDs.\nIf both devices share the same client ID, they will conflict and cause disconnections.",[326,7395,7396],{"start":221},[103,7397,7398,7401],{},[338,7399,7400],{},"ESP32 Doesn’t Respond to Commands (LED Not Turning On\u002FOff)",[100,7402,7403],{},[103,7404,7405],{},"Solution: Verify the topic in the ESP32 code matches the one in Node-RED (e.g., \u002FLedControl). If it still doesn't work, try rebooting your ESP32.",[34,7407,6518],{"id":6517},[15,7409,7410],{},"In this tutorial, we successfully connected the ESP32 to Node-RED using MQTT, enabling remote control of an LED via a FlowFuse dashboard. This simple IoT setup demonstrates how easy it is to interact with devices using MQTT and Node-RED, offering a flexible and scalable solution for future projects. With the ESP32, Node-RED, and FlowFuse, you can easily expand and integrate more devices into your IoT system.",[924,7412,7413],{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":7415},[7416,7417,7424],{"id":1162,"depth":185,"text":1163},{"id":6630,"depth":185,"text":6631,"children":7418},[7419,7420,7421,7422,7423],{"id":6637,"depth":221,"text":6638},{"id":6654,"depth":221,"text":6655},{"id":6723,"depth":221,"text":6724},{"id":6858,"depth":221,"text":6859},{"id":6499,"depth":221,"text":6500},{"id":6517,"depth":185,"text":6518},{"type":7426,"title":7427,"description":7428},"signup","Build and Scale Your IoT Projects with FlowFuse","Going from one ESP32 to a fleet of devices? FlowFuse gives you a managed MQTT broker, hosted Node-RED, remote device management, and dashboards in one platform, so you can deploy, monitor, and update your IoT devices from anywhere without managing infrastructure. Start free today.","2024-11-14","Learn how to connect an ESP32 to Node-RED using MQTT in 2026. Step-by-step guide covering the MQTT broker setup, Arduino IDE code, and building a dashboard to control your IoT device remotely, ideal for beginners and IoT hobbyists.","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fesp32-with-node-red.png",{"keywords":7433,"excerpt":7434},"esp32 with node red, node red esp32, connect esp32 to node red, esp node red, esp32 mqtt node red, esp32 node red, esp32 node red mqtt, mqtt esp32 node red, mqtt node red esp32, node red mqtt esp32, node red with esp32",{"type":12,"value":7435},[7436],[15,7437,6575],{},"\u002Fblog\u002F2024\u002F11\u002Fesp32-with-node-red",{"title":6569,"description":7430},{"loc":7438},"blog\u002F2024\u002F11\u002Fesp32-with-node-red","A step-by-step guide to controlling and monitoring an ESP32 over MQTT, with a FlowFuse dashboard.",[6563,966,7444],"mqtt","This guide explains how to connect an ESP32 to Node-RED using MQTT. The two devices don't talk directly, they exchange messages through an MQTT broker. Using FlowFuse's managed broker (broker.flowfuse.cloud), you create one MQTT client for Node-RED and one for the ESP32, build a Node-RED flow and FlowFuse dashboard that publishes commands (for example to \u002FLedControl), and program the ESP32 in the Arduino IDE with the PubSubClient library to subscribe to that topic and act on the payload, turning an LED on or off. Because everything routes through the broker, you can control the device from anywhere with Wi-Fi, and the same pattern works in reverse to read sensor data back into Node-RED.","X1ipdpj0DIsS_ivAQw8hXDwYq9oNghuNi4EvuQxUrr8",{"id":7448,"title":7449,"authors":7450,"body":7452,"cta":3,"date":7875,"description":7876,"extension":946,"image":7877,"lastUpdated":3,"meta":7878,"navigation":253,"path":7884,"seo":7885,"sitemap":7886,"stem":7887,"subtitle":7888,"tags":7889,"tldr":3,"video":3,"__hash__":7890},"blog\u002Fblog\u002F2024\u002F11\u002Fdevice-agent-as-service-on-mac.md","Run FlowFuse Device Agent as a service on MacOS using Docker",[10,7451],"rob-marcer",{"type":12,"value":7453,"toc":7862},[7454,7457,7460,7462,7465,7473,7478,7482,7485,7512,7515,7519,7522,7541,7544,7548,7551,7565,7569,7572,7585,7588,7600,7612,7616,7619,7636,7639,7643,7650,7654,7661,7707,7710,7742,7746,7749,7761,7773,7776,7799,7802,7813,7821,7824,7828,7834,7842,7854,7856,7859],[15,7455,7456],{},"The FlowFuse Device Agent is a tool that enables you to run Node-RED on various hardware devices, such as Raspberry Pi, Windows, MacOS, and PLCs. Running Node-RED directly on the device helps when your application flow needs direct access to sensors and actuators connected to the hardware, facilitating seamless integration with the FlowFuse platform. This integration enables secure management, monitoring, and remote editing of flows from a centralized platform, even at the edge.",[15,7458,7459],{},"In this article, we will explore how to run the FlowFuse Device Agent as a service on MacOS using Docker. This setup ensures that the Device Agent runs in the background, automatically starts on boot, and maintains a continuous connection the FlowFuse platform for remotely managing your Node-RED flows, even after a device restart. This eliminates the need to manually start the agent after each reboot, saving you time and effort.",[996,7461,1163],{"id":1162},[15,7463,7464],{},"Before starting, ensure that you have the following set up:",[100,7466,7467],{},[103,7468,7469,7472],{},[338,7470,7471],{},"FlowFuse Account",": You need an active FlowFuse account to register your device and manage your flows remotely. If you don't have an account, you can [sign up]({% include \"sign-up-url.njk\" %}?utm_campaign=60718323-BCTA&utm_source=blog&utm_medium=cta&utm_term=high_intent&utm_content=Run%20FlowFuse%20Device%20Agent%20as%20a%20service%20on%20MacOS%20using%20Docker) at FlowFuse.",[15,7474,7475],{},[397,7476,7477],{},"NOTE: The instructions in this guide were tested on MacBook M1 & M4 MacBook Pro",[996,7479,7481],{"id":7480},"step-1-install-homebrew","Step 1: Install Homebrew",[15,7483,7484],{},"Homebrew is the MacOS package manager for installing packages and libraries. You can install it using the following command:",[42,7486,7490],{"className":7487,"code":7488,"language":7489,"meta":50,"style":50},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002Fbin\u002Fbash -c \"$(curl -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002FHomebrew\u002Finstall\u002FHEAD\u002Finstall.sh)\"\n","bash",[19,7491,7492],{"__ignoreMap":50},[146,7493,7494,7497,7500,7503,7506,7509],{"class":148,"line":149},[146,7495,7496],{"class":2435},"\u002Fbin\u002Fbash",[146,7498,7499],{"class":1554}," -c",[146,7501,7502],{"class":160}," \"$(",[146,7504,7505],{"class":2435},"curl",[146,7507,7508],{"class":1554}," -fsSL https:\u002F\u002Fraw.githubusercontent.com\u002FHomebrew\u002Finstall\u002FHEAD\u002Finstall.sh",[146,7510,7511],{"class":160},")\"\n",[15,7513,7514],{},"This script will install the Homebrew package manager on your Mac. Once installed, you can easily install other packages like Docker and Colima.",[996,7516,7518],{"id":7517},"step-2-install-docker","Step 2: Install Docker",[15,7520,7521],{},"With Homebrew installed, you can now install Docker by running:",[42,7523,7525],{"className":7487,"code":7524,"language":7489,"meta":50,"style":50},"brew install docker-credential-helper docker\n",[19,7526,7527],{"__ignoreMap":50},[146,7528,7529,7532,7535,7538],{"class":148,"line":149},[146,7530,7531],{"class":2435},"brew",[146,7533,7534],{"class":1554}," install",[146,7536,7537],{"class":1554}," docker-credential-helper",[146,7539,7540],{"class":1554}," docker\n",[15,7542,7543],{},"This will install Docker and its credential helper, which is useful for managing authentication with Docker registries.",[996,7545,7547],{"id":7546},"step-3-install-colima","Step 3: Install Colima",[15,7549,7550],{},"Colima is a free alternative to Docker Desktop, particularly useful for MacOS, and offers better compatibility with Apple Silicon hardware. We’ll need it to run the Flowfuse Device Agent container that we will create later. To install Colima, run:",[42,7552,7554],{"className":7487,"code":7553,"language":7489,"meta":50,"style":50},"brew install colima\n",[19,7555,7556],{"__ignoreMap":50},[146,7557,7558,7560,7562],{"class":148,"line":149},[146,7559,7531],{"class":2435},[146,7561,7534],{"class":1554},[146,7563,7564],{"class":1554}," colima\n",[996,7566,7568],{"id":7567},"step-4-start-colima","Step 4: Start Colima",[15,7570,7571],{},"Once Colima is installed, start it with:",[42,7573,7575],{"className":7487,"code":7574,"language":7489,"meta":50,"style":50},"colima start\n",[19,7576,7577],{"__ignoreMap":50},[146,7578,7579,7582],{"class":148,"line":149},[146,7580,7581],{"class":2435},"colima",[146,7583,7584],{"class":1554}," start\n",[15,7586,7587],{},"This command starts the Colima virtual machine, which Docker will then use to run containers. If Colima is not running, Docker won't have the necessary environment to create and run containers.",[42,7589,7591],{"className":7487,"code":7590,"language":7489,"meta":50,"style":50},"colima status\n",[19,7592,7593],{"__ignoreMap":50},[146,7594,7595,7597],{"class":148,"line":149},[146,7596,7581],{"class":2435},[146,7598,7599],{"class":1554}," status\n",[15,7601,7602,7606],{},[392,7603],{"alt":7604,"dataZoomable":50,"src":7605},"CLI: Showing the result of colima status","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fcolima-status.png",[397,7607,7608,7609],{},"CLI: Showing the result of ",[19,7610,7611],{},"colima status",[996,7613,7615],{"id":7614},"step-5-set-colima-to-run-as-a-service","Step 5: Set Colima to Run as a Service",[15,7617,7618],{},"To ensure Colima starts automatically in the background, run the following:",[42,7620,7622],{"className":7487,"code":7621,"language":7489,"meta":50,"style":50},"brew services start colima\n",[19,7623,7624],{"__ignoreMap":50},[146,7625,7626,7628,7631,7634],{"class":148,"line":149},[146,7627,7531],{"class":2435},[146,7629,7630],{"class":1554}," services",[146,7632,7633],{"class":1554}," start",[146,7635,7564],{"class":1554},[15,7637,7638],{},"This will set Colima to run as a service, so it will start automatically every time your Mac boots up.",[996,7640,7642],{"id":7641},"step-6-adding-the-device-to-the-flowfuse-platform","Step 6: Adding the Device to the FlowFuse Platform",[15,7644,7645,7646,167],{},"Now, you'll need to add a new device to the FlowFuse platform and download the device configuration file. This configuration will allow to connect your MacOS device to your FlowFuse team. For more information on how to add a device and generate the configuration, refer to ",[307,7647,7649],{"href":7648},"\u002Fdocs\u002Fdevice-agent\u002Fregister\u002F","Generating \"Device Configuration\"",[996,7651,7653],{"id":7652},"step-7-run-the-flowfuse-device-agent-container","Step 7: Run the FlowFuse Device Agent Container",[15,7655,7656,7657,7660],{},"You can now run the FlowFuse Device Agent container using Docker. Replace ",[19,7658,7659],{},"\u002Fpath\u002Fto\u002Fdevice.yml"," with the actual path to the device configuration file you have downloaded. The following command will launch the container:",[42,7662,7664],{"className":7487,"code":7663,"language":7489,"meta":50,"style":50},"docker run -d --restart unless-stopped \\\n --mount type=bind,src=\u002Fpath\u002Fto\u002Fdevice.yml,target=\u002Fopt\u002Fflowfuse-device\u002Fdevice.yml \\\n  -p 1880:1880 flowfuse\u002Fdevice-agent:latest\n",[19,7665,7666,7686,7696],{"__ignoreMap":50},[146,7667,7668,7671,7674,7677,7680,7683],{"class":148,"line":149},[146,7669,7670],{"class":2435},"docker",[146,7672,7673],{"class":1554}," run",[146,7675,7676],{"class":1554}," -d",[146,7678,7679],{"class":1554}," --restart",[146,7681,7682],{"class":1554}," unless-stopped",[146,7684,7685],{"class":156}," \\\n",[146,7687,7688,7691,7694],{"class":148,"line":185},[146,7689,7690],{"class":1554}," --mount",[146,7692,7693],{"class":1554}," type=bind,src=\u002Fpath\u002Fto\u002Fdevice.yml,target=\u002Fopt\u002Fflowfuse-device\u002Fdevice.yml",[146,7695,7685],{"class":156},[146,7697,7698,7701,7704],{"class":148,"line":221},[146,7699,7700],{"class":1554},"  -p",[146,7702,7703],{"class":1554}," 1880:1880",[146,7705,7706],{"class":1554}," flowfuse\u002Fdevice-agent:latest\n",[15,7708,7709],{},"Explanation of the command:",[100,7711,7712,7718,7724,7730,7736],{},[103,7713,7714,7717],{},[19,7715,7716],{},"-d",": Run the container in detached mode (in the background).",[103,7719,7720,7723],{},[19,7721,7722],{},"--restart"," unless-stopped: Ensure the container restarts automatically unless explicitly stopped.",[103,7725,7726,7729],{},[19,7727,7728],{},"--mount type=bind,src=\u002Fpath\u002Fto\u002Fdevice.yml,target=\u002Fopt\u002Fflowfuse-device\u002Fdevice.yml",": Mounts your local device.yml file into the container so it can be accessed by the agent.",[103,7731,7732,7735],{},[19,7733,7734],{},"-p 1880:1880",": Exposes port 1880 on your host machine, which is typically used for the Node-RED web interface.",[103,7737,7738,7741],{},[19,7739,7740],{},"flowfuse\u002Fdevice-agent:latest",": The Docker image for the FlowFuse Device Agent.",[996,7743,7745],{"id":7744},"step-8-verify-the-device-agent-is-running","Step 8: Verify the Device Agent is Running",[15,7747,7748],{},"To verify that the Device Agent is running correctly, you can use the following command:",[42,7750,7752],{"className":7487,"code":7751,"language":7489,"meta":50,"style":50},"docker ps\n",[19,7753,7754],{"__ignoreMap":50},[146,7755,7756,7758],{"class":148,"line":149},[146,7757,7670],{"class":2435},[146,7759,7760],{"class":1554}," ps\n",[15,7762,7763,7767],{},[392,7764],{"alt":7765,"dataZoomable":50,"src":7766},"CLI: Showing the result of docker ps indicating device agent is running correctly","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fdocker-ps-result.png",[397,7768,7608,7769,7772],{},[19,7770,7771],{},"docker ps"," indicating the device agent is running correctly",[15,7774,7775],{},"This will list all running containers, and you should see the FlowFuse Device Agent listed there. If it's not running, you can check the logs to troubleshoot:",[42,7777,7779],{"className":7487,"code":7778,"language":7489,"meta":50,"style":50},"docker logs \u003Ccontainer_id>\n",[19,7780,7781],{"__ignoreMap":50},[146,7782,7783,7785,7788,7791,7794,7797],{"class":148,"line":149},[146,7784,7670],{"class":2435},[146,7786,7787],{"class":1554}," logs",[146,7789,7790],{"class":160}," \u003C",[146,7792,7793],{"class":1554},"container_i",[146,7795,7796],{"class":156},"d",[146,7798,5183],{"class":160},[15,7800,7801],{},"Additionally, you can confirm that the Device Agent is running and successfully connected to the FlowFuse platform by following these steps:",[326,7803,7804,7807,7810],{},[103,7805,7806],{},"Navigate to the FlowFuse platform.",[103,7808,7809],{},"In the left sidebar, click on \"Edge Devices\".",[103,7811,7812],{},"Then, select the device you added for MacOS.",[15,7814,7815,7819],{},[392,7816],{"alt":7817,"dataZoomable":50,"src":7818},"FlowFuse Platform: showing the status of your edge device","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fdevice-status-on-ff.png",[397,7820,7817],{},[15,7822,7823],{},"Now, you can start developing applications on the device remotely from any location and manage it efficiently.",[996,7825,7827],{"id":7826},"step-9-ensure-the-device-agent-restarts-automatically-after-a-reboot","Step 9: Ensure the Device Agent Restarts Automatically After a Reboot",[15,7829,1714,7830,7833],{},[19,7831,7832],{},"--restart unless-stopped"," flag in the Docker command ensures that your FlowFuse Device Agent container will automatically restart if your Mac reboots. However, it's always good to verify this by restarting your system:",[326,7835,7836,7839],{},[103,7837,7838],{},"Restart your Mac.",[103,7840,7841],{},"After rebooting, check the status of the FlowFuse Device Agent:",[42,7843,7845],{"className":7487,"code":7844,"language":7489,"meta":50,"style":50},"   docker ps\n",[19,7846,7847],{"__ignoreMap":50},[146,7848,7849,7852],{"class":148,"line":149},[146,7850,7851],{"class":2435},"   docker",[146,7853,7760],{"class":1554},[996,7855,6518],{"id":6517},[15,7857,7858],{},"By following these steps, you've successfully set up the FlowFuse Device Agent on your macOS system using Docker and Colima. Now, the agent will run seamlessly in the background and restart automatically after a system reboot.",[924,7860,7861],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":50,"searchDepth":250,"depth":250,"links":7863},[7864,7865,7866,7867,7868,7869,7870,7871,7872,7873,7874],{"id":1162,"depth":221,"text":1163},{"id":7480,"depth":221,"text":7481},{"id":7517,"depth":221,"text":7518},{"id":7546,"depth":221,"text":7547},{"id":7567,"depth":221,"text":7568},{"id":7614,"depth":221,"text":7615},{"id":7641,"depth":221,"text":7642},{"id":7652,"depth":221,"text":7653},{"id":7744,"depth":221,"text":7745},{"id":7826,"depth":221,"text":7827},{"id":6517,"depth":221,"text":6518},"2024-11-12","Learn how to run the FlowFuse Device Agent as a service on macOS using Docker and Colima, ensuring automatic startup and seamless integration with the FlowFuse platform for managing IoT edge devices.","\u002Fblog\u002F2024\u002F11\u002Fimages\u002Fflowfuse-as-service-on-mac.png",{"keywords":7879,"excerpt":7880},"FlowFuse Device Agent, Node-RED edge device, Node-RED macOS, FlowFuse agent macOS, IoT edge device management, Node-RED device agent",{"type":12,"value":7881},[7882],[15,7883,7456],{},"\u002Fblog\u002F2024\u002F11\u002Fdevice-agent-as-service-on-mac",{"title":7449,"description":7876},{"loc":7884},"blog\u002F2024\u002F11\u002Fdevice-agent-as-service-on-mac","Automating FlowFuse Device Agent on macOS with Docker and Colima.",[965,966],"mOZc6zOT5ug7CuE8K-nngdUVoeHyS6kRdMhKea2TW9A",{"id":7892,"title":7893,"authors":7894,"body":7895,"cta":8472,"date":8475,"description":8476,"extension":946,"image":8477,"lastUpdated":8478,"meta":8479,"navigation":253,"path":8485,"seo":8486,"sitemap":8487,"stem":8488,"subtitle":8489,"tags":8490,"tldr":8491,"video":3,"__hash__":8492},"blog\u002Fblog\u002F2024\u002F09\u002Fhow-to-scrape-web-data-with-node-red.md","How to Scrape Data from Websites Using Node-RED",[10],{"type":12,"value":7896,"toc":8458},[7897,7900,7904,7907,7910,7914,7917,7920,7924,7930,7934,7937,7982,7988,7992,7995,7999,8002,8010,8014,8053,8062,8066,8075,8378,8386,8434,8443,8447,8450,8452,8455],[15,7898,7899],{},"Web scraping has become an indispensable tool for monitoring news, tracking competitors, and gathering insights. In this guide, you'll learn how to harness the power of Node-RED for efficient web scraping, allowing you to extract and manage data from various websites with ease that are not exposed through an API.",[34,7901,7903],{"id":7902},"what-is-web-scraping","What is Web Scraping?",[15,7905,7906],{},"Web scraping is a technique for automatically extracting data from websites. Instead of manually copying information from web pages, web scraping uses tools or scripts to access and retrieve data from the Internet efficiently. This process allows you to quickly gather large volumes of information, which is helpful for tasks such as tracking market trends, aggregating news, or collecting product details. By automating data collection, web scraping helps save time and reduce human error. It enables users to extract and analyze structured data from various sources, making it easier to compile and utilize information for research, business intelligence, or other purposes.",[15,7908,7909],{},"Web scraping can be helpful when APIs are unavailable or do not meet your requirements. It allows you to collect data directly from web pages, which can be beneficial for tasks like competitive analysis, market research, or tracking specific online content.",[34,7911,7913],{"id":7912},"how-does-web-scraping-works","How Does Web Scraping Works?",[15,7915,7916],{},"Web scraping involves systematically extracting data from websites using automated tools or scripts. The process begins with requesting a specific webpage. The response from the server is the HTML content of the page. This HTML code contains the structured information displayed on the webpage, organized in a format that describes the layout and content.",[15,7918,7919],{},"Once the HTML is received, the next step is parsing it. Parsing involves analyzing the HTML structure to identify and extract the data of interest. This may include navigating through nested elements, locating specific tags, and using selectors to target precise content such as text blocks, images, or links. The extracted data is then processed and stored in a format that suits the user's needs, whether a database, a CSV file, or another format suitable for analysis.",[34,7921,7923],{"id":7922},"web-scrapping-with-node-red","Web scrapping with Node-RED",[15,7925,7926,7927],{},"In this section, we will guide you through the process of scraping data from publicly available websites using Node-RED and demonstrate how to extract data from a website specifically designed for scraping practice. For this example, we will scrape country data from the page at ",[19,7928,7929],{},"https:\u002F\u002Fwww.scrapethissite.com\u002Fpages\u002Fsimple\u002F.",[996,7931,7933],{"id":7932},"sending-requests-to-a-webpage","Sending Requests to a Webpage",[15,7935,7936],{},"To start scraping data, follow these steps to send an HTTP GET request to the webpage:",[326,7938,7939,7944,7961,7965,7977],{},[103,7940,4995,7941,7943],{},[338,7942,1339],{}," node onto the canvas. This node allows you to manually trigger the HTTP request or set it to fire at specific intervals.",[103,7945,4995,7946,7949,7950,764,7953,7956,7957,7960],{},[338,7947,7948],{},"http request"," node onto the canvas. Double-click it to configure and set the ",[338,7951,7952],{},"Method",[19,7954,7955],{},"GET."," Enter the webpage URL you want to scrape (e.g., ",[19,7958,7959],{},"https:\u002F\u002Fwww.scrapethissite.com\u002Fpages\u002Fsimple\u002F",").",[103,7962,4995,7963,1340],{},[338,7964,1395],{},[103,7966,1355,7967,7969,7970,7972,7973,7969,7975,1361],{},[338,7968,1339],{}," node's output to the input of the ",[338,7971,7948],{}," node and the ",[338,7974,7948],{},[338,7976,1395],{},[103,7978,355,7979,7981],{},[338,7980,695],{}," to save and deploy your flow.",[15,7983,7984,7985,7987],{},"Once deployed, click the ",[338,7986,1339],{}," button. You will see the raw HTML printed in the debug panel.",[996,7989,7991],{"id":7990},"parsing-and-extracting-data-from-html","Parsing and Extracting Data from HTML",[15,7993,7994],{},"Next, we need to process the raw HTML to extract meaningful data. This involves parsing the HTML content and identifying the specific information you want. To do this, first analyze the HTML structure of the webpage by opening the browser’s developer tools (press Ctrl + I or F12) and inspecting the elements to locate where the data is and in which HTML elements it resides.",[4987,7996,7998],{"id":7997},"analyzing-html-structure","Analyzing HTML Structure",[15,8000,8001],{},"Begin by analyzing the HTML structure of the webpage. Open your browser’s developer tools (press Ctrl + Shift + c ) and examine the elements to locate where the data resides and which HTML elements contain it. For example, on a page with a list of countries, each with its capital, population, and area, click on one of those countires elements to navigate to its HTML in the developer tools. Identify the selector that can be used to select those elements. On this webpage, the information about countries is contained within an element with the .countries class. You can use this class to extract all the data for the countries.",[15,8003,8004,8008],{},[392,8005],{"alt":8006,"dataZoomable":50,"src":8007},"Image showing the structure of the page and the data which we needed to extract","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fhtml-structer-of-target-website.png",[397,8009,8006],{},[4987,8011,8013],{"id":8012},"using-node-red-to-extract-data","Using Node-RED to extract data",[326,8015,8016,8020,8029,8032,8036,8049],{},[103,8017,4995,8018,1340],{},[338,8019,5158],{},[103,8021,435,8022,8024,8025,8028],{},[338,8023,5158],{}," node and enter the selector ",[19,8026,8027],{},".countries"," into the \"Selector\" field.",[103,8030,8031],{},"Set the output to \"only the text of element\" and keep other settings default.",[103,8033,4995,8034,1340],{},[338,8035,1395],{},[103,8037,8038,8039,8041,8042,8044,8045,8041,8047,1361],{},"Connect the output of the ",[338,8040,7948],{}," node to the input of the ",[338,8043,5158],{}," node and the output of the ",[338,8046,5158],{},[338,8048,1395],{},[103,8050,355,8051,7981],{},[338,8052,695],{},[15,8054,8055,8056,8058,8059,8061],{},"When you click the ",[338,8057,1339],{}," button, you will see the array containing the text content from each ",[19,8060,8027],{}," div. While this data is a good starting point, it has yet to be in a format that is directly useful for analysis. To make the data more helpful, you'll need to transform it into objects with meaningful properties.",[996,8063,8065],{"id":8064},"transforming-data-into-structured-objects","Transforming Data into Structured Objects",[15,8067,8068,8069,8074],{},"You can use JavaScript in a Node-RED function node to transform data into structured objects. If you are familiar with JavaScript, this process will be straightforward. However, if you are not, you can use FlowFuse Expert to generate the necessary function. For more details, refer to our ",[307,8070,8073],{"href":8071,"rel":8072},"https:\u002F\u002Fwww.linkedin.com\u002Fposts\u002Fflowfuse_flowfuse-nodered-automation-activity-7226171132796637184-vKKt\u002F?utm_source=share&utm_medium=member_desktop",[311],"LinkedIn Post"," for a quick guide. However, in this section, we will use a low-code approach to transform the data.",[326,8076,8077,8090,8129,8169,8199,8231,8261,8284,8369],{},[103,8078,408,8079,8082,8083,8086,8087,8089],{},[338,8080,8081],{},"Split"," node onto the canvas and connect it to the ",[338,8084,8085],{},"HTML"," node. This ",[338,8088,8081],{}," node will split the input array into individual string messages.",[103,8091,408,8092,8082,8094,8096,8097,8100,8101],{},[338,8093,5019],{},[338,8095,8081],{}," node. Set ",[19,8098,8099],{},"msg.name"," to the following JSONata expression to extract the country name:",[42,8102,8104],{"className":708,"code":8103,"language":710,"meta":50,"style":50},"$trim($split(payload, \"Capital: \")[0])\n",[19,8105,8106],{"__ignoreMap":50},[146,8107,8108,8111,8113,8116,8118,8120,8122,8124,8126],{"class":148,"line":149},[146,8109,8110],{"class":156},"$trim($split(payload, ",[146,8112,727],{"class":160},[146,8114,8115],{"class":1554},"Capital: ",[146,8117,727],{"class":160},[146,8119,1477],{"class":156},[146,8121,1568],{"class":160},[146,8123,677],{"class":206},[146,8125,1536],{"class":160},[146,8127,8128],{"class":156},")\n",[103,8130,379,8131,8133,8134],{},[19,8132,382],{}," to the following Jsonata expression that will extract the capital and population from the string:",[42,8135,8137],{"className":708,"code":8136,"language":710,"meta":50,"style":50},"$split($split(payload, \"Capital: \")[1], \"Population: \")\n",[19,8138,8139],{"__ignoreMap":50},[146,8140,8141,8144,8146,8148,8150,8152,8154,8156,8158,8160,8162,8165,8167],{"class":148,"line":149},[146,8142,8143],{"class":156},"$split($split(payload, ",[146,8145,727],{"class":160},[146,8147,8115],{"class":1554},[146,8149,727],{"class":160},[146,8151,1477],{"class":156},[146,8153,1568],{"class":160},[146,8155,62],{"class":206},[146,8157,1536],{"class":160},[146,8159,1255],{"class":156},[146,8161,727],{"class":160},[146,8163,8164],{"class":1554},"Population: ",[146,8166,727],{"class":160},[146,8168,8128],{"class":156},[103,8170,8171,8172,8174,8175,8096,8177,8180,8181],{},"Drag another ",[338,8173,5019],{}," node onto the canvas and connect it to the previous ",[338,8176,5019],{},[19,8178,8179],{},"msg.capital"," to the following Jsonata expression to trim and extract the value of the capital from the previously split data array:",[42,8182,8184],{"className":708,"code":8183,"language":710,"meta":50,"style":50},"$trim(payload[0])\n",[19,8185,8186],{"__ignoreMap":50},[146,8187,8188,8191,8193,8195,8197],{"class":148,"line":149},[146,8189,8190],{"class":156},"$trim(payload",[146,8192,1568],{"class":160},[146,8194,677],{"class":206},[146,8196,1536],{"class":160},[146,8198,8128],{"class":156},[103,8200,379,8201,8203,8204],{},[19,8202,382],{}," to the following Jsonata expression to split the remaining string for area extraction:",[42,8205,8207],{"className":708,"code":8206,"language":710,"meta":50,"style":50},"$split(payload[1], \"Area (km2): \")\n",[19,8208,8209],{"__ignoreMap":50},[146,8210,8211,8214,8216,8218,8220,8222,8224,8227,8229],{"class":148,"line":149},[146,8212,8213],{"class":156},"$split(payload",[146,8215,1568],{"class":160},[146,8217,62],{"class":206},[146,8219,1536],{"class":160},[146,8221,1255],{"class":156},[146,8223,727],{"class":160},[146,8225,8226],{"class":1554},"Area (km2): ",[146,8228,727],{"class":160},[146,8230,8128],{"class":156},[103,8232,8171,8233,8082,8235,8237,8238,8241,8242],{},[338,8234,5019],{},[338,8236,5019],{}," node from the previous step. Set ",[19,8239,8240],{},"msg.population"," to the following Jsonata expression to trim and convert the population value to a number:",[42,8243,8245],{"className":708,"code":8244,"language":710,"meta":50,"style":50},"$number($trim(payload[0]))\n",[19,8246,8247],{"__ignoreMap":50},[146,8248,8249,8252,8254,8256,8258],{"class":148,"line":149},[146,8250,8251],{"class":156},"$number($trim(payload",[146,8253,1568],{"class":160},[146,8255,677],{"class":206},[146,8257,1536],{"class":160},[146,8259,8260],{"class":156},"))\n",[103,8262,379,8263,8266,8267],{},[19,8264,8265],{},"msg.area"," to the following Jsonata expression to trim and convert the area value to a number:",[42,8268,8270],{"className":708,"code":8269,"language":710,"meta":50,"style":50},"$number($trim(payload[1]))\n",[19,8271,8272],{"__ignoreMap":50},[146,8273,8274,8276,8278,8280,8282],{"class":148,"line":149},[146,8275,8251],{"class":156},[146,8277,1568],{"class":160},[146,8279,62],{"class":206},[146,8281,1536],{"class":160},[146,8283,8260],{"class":156},[103,8285,8171,8286,8288,8289,8096,8291,8293,8294],{},[338,8287,5019],{}," node onto the canvas and connect it to the last ",[338,8290,5019],{},[19,8292,382],{}," to the following JSON object:",[42,8295,8297],{"className":708,"code":8296,"language":710,"meta":50,"style":50},"{\n  \"name\": name,\n  \"capital\": capital,\n  \"population\": population,\n  \"area\": area\n}\n",[19,8298,8299,8303,8319,8335,8351,8365],{"__ignoreMap":50},[146,8300,8301],{"class":148,"line":149},[146,8302,717],{"class":160},[146,8304,8305,8307,8310,8312,8314,8317],{"class":148,"line":185},[146,8306,722],{"class":160},[146,8308,8309],{"class":152},"name",[146,8311,727],{"class":160},[146,8313,730],{"class":160},[146,8315,8316],{"class":156}," name",[146,8318,736],{"class":160},[146,8320,8321,8323,8326,8328,8330,8333],{"class":148,"line":221},[146,8322,722],{"class":160},[146,8324,8325],{"class":152},"capital",[146,8327,727],{"class":160},[146,8329,730],{"class":160},[146,8331,8332],{"class":156}," capital",[146,8334,736],{"class":160},[146,8336,8337,8339,8342,8344,8346,8349],{"class":148,"line":250},[146,8338,722],{"class":160},[146,8340,8341],{"class":152},"population",[146,8343,727],{"class":160},[146,8345,730],{"class":160},[146,8347,8348],{"class":156}," population",[146,8350,736],{"class":160},[146,8352,8353,8355,8358,8360,8362],{"class":148,"line":257},[146,8354,722],{"class":160},[146,8356,8357],{"class":152},"area",[146,8359,727],{"class":160},[146,8361,730],{"class":160},[146,8363,8364],{"class":156}," area\n",[146,8366,8367],{"class":148,"line":284},[146,8368,754],{"class":160},[103,8370,8371,8372,8174,8375,8377],{},"Finally, drag a ",[338,8373,8374],{},"Join",[338,8376,5019],{}," node. This Join node will create an array of the objects we have created.",[15,8379,8380,8381,167],{},"When you click the inject button again, you will see that the data is now structured and formatted. The output will contain objects with properties such as name, capital, population, and area. This data can now be displayed on the FlowFuse dashboard table. For more details, refer to the ",[307,8382,8385],{"href":8383,"rel":8384},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fnodes\u002Fwidgets\u002Fui-table.html",[311],"FlowFuse table widget",[15,8387,6389,8388,3830],{},[146,8389,8390,8391,8393,8394,8399,8400,8409,8410,8419,8420,8429,8430,8433],{},"{\"id\":\"cc3c919ad9f93cc6\",\"type\":\"inject\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"props\":",[146,8392,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":280,\"y\":200,\"wires\":[[\"43ba04d623a8aa57\"]]},{\"id\":\"2d66b9fa2858cf5f\",\"type\":\"html\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"property\":\"payload\",\"outproperty\":\"payload\",\"tag\":\".country\",\"ret\":\"text\",\"as\":\"single\",\"x\":620,\"y\":200,\"wires\":[[\"c4e9a4c8.487e68\"]]},{\"id\":\"43ba04d623a8aa57\",\"type\":\"http request\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"method\":\"GET\",\"ret\":\"txt\",\"paytoqs\":\"ignore\",\"url\":\"",[307,8395,8398],{"href":8396,"rel":8397},"https:\u002F\u002Fwww.scrapethissite.com\u002Fpages\u002Fsimple\u002F%22,%22tls%22:%22%22,%22persist%22:false,%22proxy%22:%22%22,%22insecureHTTPParser%22:false,%22authType%22:%22%22,%22senderr%22:false,%22headers%22:%5B%5D,%22x%22:450,%22y%22:200,%22wires%22:%5B%5B%222d66b9fa2858cf5f%22%5D%5D%7D,%7B%22id%22:%228e7b462a5b5a064e%22,%22type%22:%22ui-table%22,%22z%22:%22380e37fed72e6885%22,%22group%22:%220c48f8d560157d3c%22,%22name%22:%22%22,%22label%22:%22text%22,%22order%22:1,%22width%22:0,%22height%22:0,%22maxrows%22:0,%22passthru%22:false,%22autocols%22:true,%22showSearch%22:true,%22selectionType%22:%22none%22,%22columns%22:%5B%5D,%22mobileBreakpoint%22:%22sm%22,%22mobileBreakpointType%22:%22defaults%22,%22x%22:1870,%22y%22:200,%22wires%22:%5B%5B%5D%5D%7D,%7B%22id%22:%22c4e9a4c8.487e68%22,%22type%22:%22split%22,%22z%22:%22380e37fed72e6885%22,%22name%22:%22Split",[311],"https:\u002F\u002Fwww.scrapethissite.com\u002Fpages\u002Fsimple\u002F\",\"tls\":\"\",\"persist\":false,\"proxy\":\"\",\"insecureHTTPParser\":false,\"authType\":\"\",\"senderr\":false,\"headers\":[],\"x\":450,\"y\":200,\"wires\":[[\"2d66b9fa2858cf5f\"]]},{\"id\":\"8e7b462a5b5a064e\",\"type\":\"ui-table\",\"z\":\"380e37fed72e6885\",\"group\":\"0c48f8d560157d3c\",\"name\":\"\",\"label\":\"text\",\"order\":1,\"width\":0,\"height\":0,\"maxrows\":0,\"passthru\":false,\"autocols\":true,\"showSearch\":true,\"selectionType\":\"none\",\"columns\":[],\"mobileBreakpoint\":\"sm\",\"mobileBreakpointType\":\"defaults\",\"x\":1870,\"y\":200,\"wires\":[[]]},{\"id\":\"c4e9a4c8.487e68\",\"type\":\"split\",\"z\":\"380e37fed72e6885\",\"name\":\"Split"," Array\",\"splt\":\"\\n\",\"spltType\":\"str\",\"arraySplt\":\"1\",\"arraySpltType\":\"len\",\"stream\":false,\"addname\":\"\",\"x\":810,\"y\":200,\"wires\":[[\"1a1e8a0a.8e7b06\"]]},{\"id\":\"1a1e8a0a.8e7b06\",\"type\":\"change\",\"z\":\"380e37fed72e6885\",\"name\":\"Extract Name\",\"rules\":",[146,8401,8402,8403,8405,8406,8408],{},"{\"t\":\"set\",\"p\":\"name\",\"pt\":\"msg\",\"to\":\"$trim($split(payload, \"Capital: \")",[146,8404,677],{},")\",\"tot\":\"jsonata\"},{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"$split($split(payload, \"Capital: \")",[146,8407,62],{},", \"Population: \")\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1000,\"y\":200,\"wires\":[[\"cfdb7c1f.9234b\"]]},{\"id\":\"cfdb7c1f.9234b\",\"type\":\"change\",\"z\":\"380e37fed72e6885\",\"name\":\"Extract Capital & Population\",\"rules\":",[146,8411,8412,8413,8415,8416,8418],{},"{\"t\":\"set\",\"p\":\"capital\",\"pt\":\"msg\",\"to\":\"$trim(payload",[146,8414,677],{},")\",\"tot\":\"jsonata\"},{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"$split(payload",[146,8417,62],{},", \"Area (km2): \")\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1240,\"y\":200,\"wires\":[[\"fb93f89b.b96138\"]]},{\"id\":\"fb93f89b.b96138\",\"type\":\"change\",\"z\":\"380e37fed72e6885\",\"name\":\"Extract Population & Area\",\"rules\":",[146,8421,8422,8423,8425,8426,8428],{},"{\"t\":\"set\",\"p\":\"population\",\"pt\":\"msg\",\"to\":\"$number($trim(payload",[146,8424,677],{},"))\",\"tot\":\"jsonata\"},{\"t\":\"set\",\"p\":\"area\",\"pt\":\"msg\",\"to\":\"$number($trim(payload",[146,8427,62],{},"))\",\"tot\":\"jsonata\"},{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"{   \"name\": name,   \"capital\": capital,   \"population\": population,   \"area\": area}\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1510,\"y\":200,\"wires\":[[\"342166ac8729e9d7\"]]},{\"id\":\"342166ac8729e9d7\",\"type\":\"join\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"mode\":\"auto\",\"build\":\"object\",\"property\":\"payload\",\"propertyType\":\"msg\",\"key\":\"topic\",\"joiner\":\"\\n\",\"joinerType\":\"str\",\"accumulate\":true,\"timeout\":\"\",\"count\":\"\",\"reduceRight\":false,\"reduceExp\":\"\",\"reduceInit\":\"\",\"reduceInitType\":\"\",\"reduceFixup\":\"\",\"x\":1710,\"y\":200,\"wires\":[[\"8e7b462a5b5a064e\"]]},{\"id\":\"0c48f8d560157d3c\",\"type\":\"ui-group\",\"name\":\"My Group\",\"page\":\"d0def7a91d3b7aa1\",\"width\":\"12\",\"height\":\"1\",\"order\":1,\"showTitle\":false,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"d0def7a91d3b7aa1\",\"type\":\"ui-page\",\"name\":\"Page 1\",\"ui\":\"c385dfc590b1308d\",\"path\":\"\u002F1\",\"icon\":\"home\",\"layout\":\"grid\",\"theme\":\"6be033291dd76b17\",\"order\":1,\"className\":\"\",\"visible\":false,\"disabled\":false},{\"id\":\"c385dfc590b1308d\",\"type\":\"ui-base\",\"name\":\"Dashboard\",\"path\":\"\u002Fdashboard\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,8431,8432],{},"\"ui-notification\",\"ui-control\",\"ui-button\"",",\"showPathInSidebar\":false,\"showPageTitle\":false,\"navigationStyle\":\"temporary\",\"titleBarStyle\":\"default\"},{\"id\":\"6be033291dd76b17\",\"type\":\"ui-theme\",\"name\":\"Default Theme\",\"colors\":{\"surface\":\"#202c34\",\"primary\":\"#202c34\",\"bgPage\":\"#eeeeee\",\"groupBg\":\"#ffffff\",\"groupOutline\":\"#ffffff\"},\"sizes\":{\"pagePadding\":\"12px\",\"groupGap\":\"12px\",\"groupBorderRadius\":\"4px\",\"widgetGap\":\"12px\",\"density\":\"default\"}}",[15,8435,8436,8440],{},[392,8437],{"alt":8438,"dataZoomable":50,"src":8439},"Left side: Image showing the countries table we created on the FlowFuse dashboard. Right side: The original webpage with countries.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fwebscrapping-result.png",[397,8441,8442],{},"Left side: Image showing the table we created on the FlowFuse dashboard. Right side: The original webpage with countries.",[34,8444,8446],{"id":8445},"legal-and-ethical-considerations","Legal and Ethical Considerations",[15,8448,8449],{},"Web scraping can be a valuable tool for gathering data, but it's crucial to navigate the legal and ethical landscape responsibly. Adhere to websites' terms of service, respect intellectual property and data privacy laws, and avoid actions that could disrupt a site's operation or misuse the scraped data. By staying informed and adhering to best practices, you can harness the power of web scraping tools like Node-RED while remaining ethically and legally compliant.",[34,8451,6518],{"id":6517},[15,8453,8454],{},"You’ve now learned to use Node-RED for web scraping, from sending requests and parsing HTML to transforming data into practical formats. This approach streamlines data collection from websites, making it easier to manage and analyze information efficiently.",[924,8456,8457],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}",{"title":50,"searchDepth":250,"depth":250,"links":8459},[8460,8461,8462,8470,8471],{"id":7902,"depth":185,"text":7903},{"id":7912,"depth":185,"text":7913},{"id":7922,"depth":185,"text":7923,"children":8463},[8464,8465,8469],{"id":7932,"depth":221,"text":7933},{"id":7990,"depth":221,"text":7991,"children":8466},[8467,8468],{"id":7997,"depth":250,"text":7998},{"id":8012,"depth":250,"text":8013},{"id":8064,"depth":221,"text":8065},{"id":8445,"depth":185,"text":8446},{"id":6517,"depth":185,"text":6518},{"type":941,"title":8473,"description":8474},"Automate Data Collection With Node-RED","FlowFuse gives you a production-ready platform to build, deploy, and manage Node-RED flows, with team collaboration, version control, and centralized management across all your instances.","2024-09-16","Learn how to use Node-RED for web scraping to efficiently collect, extract, and manage data from websites. This step-by-step guide covers everything you need to know about creating automated web scrapers using Node-RED.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fwebscraping-with-node-red.png","2025-07-23",{"keywords":8480,"excerpt":8481},"node red webscraping, websrcaping with node-red, node red https request, online web scraper free, online web scraper free, custom web scraper, automated web scraping, web scraping solutions, data scraper easy web scraping",{"type":12,"value":8482},[8483],[15,8484,7899],{},"\u002Fblog\u002F2024\u002F09\u002Fhow-to-scrape-web-data-with-node-red",{"title":7893,"description":8476},{"loc":8485},"blog\u002F2024\u002F09\u002Fhow-to-scrape-web-data-with-node-red","A step-by-step guide to leveraging Node-RED for efficient web scraping and automated data extraction.",[6563,966],"Node-RED can be used to scrape data from websites that don't expose an API, by sending HTTP GET requests and parsing the returned HTML. This guide walks through building a complete web scraper in Node-RED from sending requests and extracting data with CSS selectors to storing the results using a public scraping-practice site as the example.","3zQ6-ZhtM9sSWVoepXX35KtmOyFOQ7wm7mIK4O5fjKw",{"id":8494,"title":8495,"authors":8496,"body":8497,"cta":9056,"date":9059,"description":9060,"extension":946,"image":9061,"lastUpdated":948,"meta":9062,"navigation":253,"path":9068,"seo":9069,"sitemap":9070,"stem":9071,"subtitle":9072,"tags":9073,"tldr":9074,"video":3,"__hash__":9075},"blog\u002Fblog\u002F2024\u002F09\u002Fhow-to-use-subflow-in-node-red.md","How to create and use Subflow in Node-RED (2026)",[10],{"type":12,"value":8498,"toc":9042},[8499,8502,8505,8509,8517,8520,8524,8527,8530,8574,8578,8586,8594,8602,8605,8609,8617,8625,8634,8642,8651,8659,8671,8679,8684,8688,8691,8707,8715,8726,8734,8738,8741,8758,8766,8781,8789,8793,8796,8807,8815,8818,8845,8849,8852,8863,8871,8885,8889,8892,8901,8909,8918,8921,9004,9007,9011,9037,9039],[15,8500,8501],{},"In traditional programming, managing complex and repetitive tasks can quickly lead to a tangled mess of code that’s hard to maintain and update. To tackle this issue, developers use libraries or modules, reusable chunks of code that help organize functionality, minimize duplication, and keep codebases clean and manageable.",[15,8503,8504],{},"Node-RED brings a similar solution to its visual programming environment with Subflows. Imagine Subflows as the visual counterpart to libraries. In this guide, we will explore what Subflows are, how to create them, and how to use them effectively to enhance your Node-RED experience.",[34,8506,8508],{"id":8507},"what-exactly-are-subflows","What Exactly Are Subflows?",[15,8510,8511,8515],{},[392,8512],{"alt":8513,"dataZoomable":50,"src":8514},"Image showing a Node-RED flow at the top selected for creating a Subflow, and the resulting Subflow at the bottom.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fsubflow.png",[397,8516,8513],{},[15,8518,8519],{},"Subflows in Node-RED are a way to group together a set of nodes and reusable flows into a single, reusable node. This helps you manage and organize complex workflows by encapsulating repetitive or complex logic into a modular unit. You can think of Subflows as custom nodes that you create and use within your flows to simplify your design and reduce redundancy.",[34,8521,8523],{"id":8522},"creating-a-subflow-in-node-red","Creating a Subflow in Node-RED",[15,8525,8526],{},"In this section, we will create a Subflow for a flow that sends requests to an API and returns results. If the request faces an issue, it retries until it reaches the maximum retry limit. Let's assume we need to use this flow in multiple places, for different APIs, each with different retry timeouts. To avoid duplicating the flow logic, we can create a Subflow.",[15,8528,8529],{},"To follow along, import the following flow into your Node-RED instance.",[15,8531,6389,8532,3830],{},[146,8533,8534,8535,8537,8538,8540,8541,8543,8544,8549,8550,8553,8554,8557,8558,8561,8562,8565,8566,8569,8570,8573],{},"{\"id\":\"132f4fdc40d55e89\",\"type\":\"debug\",\"z\":\"380e37fed72e6885\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":2840,\"y\":280,\"wires\":",[146,8536],{},"},{\"id\":\"612819f76617e5a8\",\"type\":\"debug\",\"z\":\"380e37fed72e6885\",\"name\":\"debug 3\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":2840,\"y\":220,\"wires\":",[146,8539],{},"},{\"id\":\"7e9e8e1af751bb92\",\"type\":\"inject\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"props\":",[146,8542,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":1460,\"y\":240,\"wires\":[[\"8899fea497064b8c\"]]},{\"id\":\"b4eca1de14599dd1\",\"type\":\"delay\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"pauseType\":\"delayv\",\"timeout\":\"5\",\"timeoutUnits\":\"milliseconds\",\"rate\":\"1\",\"nbRateUnits\":\"1\",\"rateUnits\":\"second\",\"randomFirst\":\"1\",\"randomLast\":\"5\",\"randomUnits\":\"seconds\",\"drop\":false,\"allowrate\":false,\"outputs\":1,\"x\":1900,\"y\":240,\"wires\":[[\"96ca1ed69e7fa7ac\"]]},{\"id\":\"96ca1ed69e7fa7ac\",\"type\":\"http request\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"method\":\"GET\",\"ret\":\"txt\",\"paytoqs\":\"ignore\",\"url\":\"",[307,8545,8548],{"href":8546,"rel":8547},"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Ftodos%22,%22tls%22:%22%22,%22persist%22:false,%22proxy%22:%22%22,%22insecureHTTPParser%22:false,%22authType%22:%22%22,%22senderr%22:false,%22headers%22:%5B%5D,%22x%22:2050,%22y%22:240,%22wires%22:%5B%5B%22f523bb034db360a4%22%5D%5D%7D,%7B%22id%22:%223eb4ec6b71fc383b%22,%22type%22:%22switch%22,%22z%22:%22380e37fed72e6885%22,%22name%22:%22if",[311],"https:\u002F\u002Fjsonplaceholder.typicode.com\u002Ftodos\",\"tls\":\"\",\"persist\":false,\"proxy\":\"\",\"insecureHTTPParser\":false,\"authType\":\"\",\"senderr\":false,\"headers\":[],\"x\":2050,\"y\":240,\"wires\":[[\"f523bb034db360a4\"]]},{\"id\":\"3eb4ec6b71fc383b\",\"type\":\"switch\",\"z\":\"380e37fed72e6885\",\"name\":\"if"," success\",\"property\":\"statusCode\",\"propertyType\":\"msg\",\"rules\":",[146,8551,8552],{},"{\"t\":\"btwn\",\"v\":\"200\",\"vt\":\"num\",\"v2\":\"299\",\"v2t\":\"num\"},{\"t\":\"else\"}",",\"checkall\":\"true\",\"repair\":false,\"outputs\":2,\"x\":2420,\"y\":240,\"wires\":[[\"612819f76617e5a8\"],",[146,8555,8556],{},"\"bad4bdb7e00b7a80\"","]},{\"id\":\"bad4bdb7e00b7a80\",\"type\":\"switch\",\"z\":\"380e37fed72e6885\",\"name\":\"if max retries\",\"property\":\"retry_counter\",\"propertyType\":\"msg\",\"rules\":",[146,8559,8560],{},"{\"t\":\"gte\",\"v\":\"10000\",\"vt\":\"num\"},{\"t\":\"else\"}",",\"checkall\":\"true\",\"repair\":false,\"outputs\":2,\"x\":2590,\"y\":280,\"wires\":[[\"132f4fdc40d55e89\"],",[146,8563,8564],{},"\"b4eca1de14599dd1\"","]},{\"id\":\"8899fea497064b8c\",\"type\":\"change\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"rules\":",[146,8567,8568],{},"{\"t\":\"set\",\"p\":\"delay\",\"pt\":\"msg\",\"to\":\"retry_interval\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"retry_counter\",\"pt\":\"msg\",\"to\":\"0\",\"tot\":\"num\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1720,\"y\":240,\"wires\":[[\"b4eca1de14599dd1\"]]},{\"id\":\"f523bb034db360a4\",\"type\":\"change\",\"z\":\"380e37fed72e6885\",\"name\":\"retry_counter++\",\"rules\":",[146,8571,8572],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"retry_counter+1\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":2240,\"y\":240,\"wires\":[[\"3eb4ec6b71fc383b\"]]}",[996,8575,8577],{"id":8576},"creating-subflow-of-selection","Creating subflow of selection",[15,8579,8580,8584],{},[392,8581],{"alt":8582,"dataZoomable":50,"src":8583},"Image showing process of creating subflow from the selection","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fselecting-and-converting-subflow.gif",[397,8585,8582],{},[326,8587,8588,8591],{},[103,8589,8590],{},"Select the flow you want to convert into a Subflow.",[103,8592,8593],{},"Open the main menu by clicking the top-right menu icon, and select \"Selection to Subflow\" under the Subflows option.",[15,8595,8596,8600],{},[392,8597],{"alt":8598,"dataZoomable":50,"src":8599},"Image showing subflow node added in the node palette","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fsubflow-showing-in-pallete.png",[397,8601,8598],{},[15,8603,8604],{},"Once selected, the Subflow will be added to the node palette like other nodes. The selected flow will also be converted into a single node representing the Subflow.",[996,8606,8608],{"id":8607},"adding-properties-to-the-subflow","Adding Properties to the Subflow",[326,8610,8611],{},[103,8612,8613,8614,167],{},"Double-click on the Subflow, then click on ",[338,8615,8616],{},"\"Edit Subflow template\"",[15,8618,8619,8623],{},[392,8620],{"alt":8621,"dataZoomable":50,"src":8622},"Editing the Subflow template by clicking on the 'Edit Subflow Template' option.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fedit-template-option.png",[397,8624,8621],{},[326,8626,8627],{"start":185},[103,8628,8629,8630,8633],{},"A new flow tab for the Subflow will open. Click on ",[338,8631,8632],{},"\"Edit Properties\""," in the top-left corner.",[15,8635,8636,8640],{},[392,8637],{"alt":8638,"dataZoomable":50,"src":8639},"The edit properties button for a Subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fedit-properties.png",[397,8641,8638],{},[326,8643,8644],{"start":221},[103,8645,8646,8647,8650],{},"To add environment properties, click on the ",[338,8648,8649],{},"\"+ add\""," button located at the bottom-left.",[15,8652,8653,8657],{},[392,8654],{"alt":8655,"dataZoomable":50,"src":8656},"The 'Add' button for adding environment properties for subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fadd-env.png",[397,8658,8655],{},[326,8660,8661,8664],{"start":250},[103,8662,8663],{},"In the field that opens, give the property a name and set its default value.",[103,8665,8666,8667,8670],{},"Once you have added all your properties, you can view a preview by switching to the ",[338,8668,8669],{},"\"UI PREVIEW\""," tab.",[15,8672,8673,8677],{},[392,8674],{"alt":8675,"dataZoomable":50,"src":8676},"A preview of the Subflow environment properties","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fui-preivew-env.png",[397,8678,8675],{},[326,8680,8681],{"start":284},[103,8682,8683],{},"Click \"Done\" to save.",[996,8685,8687],{"id":8686},"setting-added-environment-variables-in-the-nodes","Setting Added Environment Variables in the Nodes",[15,8689,8690],{},"Now that we have added properties for the Subflow (which are environment variables), we need to use them in the relevant nodes, such as the HTTP request node, which will require an API and the max-retry setting.",[326,8692,8693],{},[103,8694,8695,8696,8699,8700,8703,8704,8706],{},"Double-click on the ",[338,8697,8698],{},"HTTP request"," node, set the environment variable as ",[19,8701,8702],{},"${your_env_name}"," into the URL feild, and click ",[338,8705,405],{}," to save.",[15,8708,8709,8713],{},[392,8710],{"alt":8711,"dataZoomable":50,"src":8712},"The URL field of an HTTP request node in Node-RED with an environment variable added.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fhttp-request-url-env-adding.png",[397,8714,8711],{},[326,8716,8717],{"start":185},[103,8718,8719,8720,8723,8724,8706],{},"Next, double-click on the ",[338,8721,8722],{},"switch"," node named \"if max retries,\" update the hardcoded max retry condition value to the environment variable you set for it, and click ",[338,8725,405],{},[15,8727,8728,8732],{},[392,8729],{"alt":8730,"dataZoomable":50,"src":8731},"The switch node in Node-RED with a max retry condition set using an environment variable.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fmax-retry-setting.png",[397,8733,8730],{},[996,8735,8737],{"id":8736},"managing-subflow-input-and-output-ports","Managing Subflow Input and Output Ports",[15,8739,8740],{},"As we know, any node in Node-RED requires input and output ports to manage its data flow. Similarly, a Subflow node requires these ports to function correctly. In our Subflow example, it needs to be triggered and therefore requires at least one input port and one or more output ports. Specifically, our Subflow has two outputs: one for successfully fetched data and another to indicate when the maximum retry limit has been exceeded.",[326,8742,8743],{},[103,8744,508,8745,8748,8749,8752,8753,8755,8756,1361],{},[338,8746,8747],{},"Subflow"," tab, at the top, you will see an option for ",[338,8750,8751],{},"inputs"," with values 0 and 1. Click on ",[338,8754,62],{}," to add an input port (as a any Node-RED node can have only one input port). Once set to 1, you will see an input port added in the Subflow tab. Connect it to the appropriate node; in our example, it should be connected to the first ",[338,8757,1405],{},[15,8759,8760,8764],{},[392,8761],{"alt":8762,"dataZoomable":50,"src":8763},"Option to add input port for subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Finput-adding-subflow.png",[397,8765,8762],{},[326,8767,8768],{"start":185},[103,8769,8770,8771,8773,8774,8777,8778,8780],{},"Next, right after the ",[338,8772,8751],{}," option, you will see an option for ",[338,8775,8776],{},"outputs",". Unlike inputs, you can add as many outputs as you need. Once you've added the outputs, connect them to the appropriate nodes. In our example, the first output should be connected to the first input of both ",[338,8779,8722],{}," nodes.",[15,8782,8783,8787],{},[392,8784],{"alt":8785,"dataZoomable":50,"src":8786},"Option to add output ports for subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Foutput-adding-subflow.png",[397,8788,8785],{},[996,8790,8792],{"id":8791},"adding-status-for-subflow-nodes","Adding Status for Subflow Nodes",[15,8794,8795],{},"To effectively manage and monitor the execution of Subflows, you can add status indicators to your Subflow nodes. This allows you to see if the Subflow is functioning correctly and helps in debugging if something goes wrong. To add a status indicator:",[326,8797,8798],{},[103,8799,8800,8801,8804,8805,167],{},"In the Subflow flow tab at the top, click on the ",[338,8802,8803],{},"Status"," node option to add a status node. This status node can be connected to the Node-RED status node to capture and display all statuses, or you can configure it to use ",[19,8806,382],{},[15,8808,8809,8813],{},[392,8810],{"alt":8811,"dataZoomable":50,"src":8812},"Option to add status for subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fstatus-adding-subflow.png",[397,8814,8811],{},[15,8816,8817],{},"In our example, we need two indicators: one to display when the flow is retrying to request and another to indicate that the fetch operation has successfully completed.",[326,8819,8820],{"start":185},[103,8821,6804,8822,8824,8825,8828,8829,764,8831,8834,8835,8838,8839,764,8841,8844],{},[338,8823,5019],{}," nodes onto the Canvas. Connect one Change node to the ",[338,8826,8827],{},"if success"," switch node's first output and set the ",[19,8830,382],{},[19,8832,8833],{},"\"completed\"",". Connect the other Change node to the ",[338,8836,8837],{},"if max retries"," switch node's first output and set its ",[19,8840,382],{},[19,8842,8843],{},"\"retrying\"",". Then, connect both Change nodes to the input of the Subflow status node.",[996,8846,8848],{"id":8847},"customizing-the-appearance-of-a-subflow-node","Customizing the Appearance of a Subflow Node",[15,8850,8851],{},"Node-RED allows you to customize the appearance of Subflow nodes, including setting the color, icon, port labels, and selecting the category in which it will be visible in the node palette.",[326,8853,8854],{},[103,8855,8856,8857,8859,8860,8670],{},"In the Subflow flow tab, click on the ",[338,8858,8632],{}," option in the top-left corner and switch to the ",[338,8861,8862],{},"\"Appearance\"",[15,8864,8865,8869],{},[392,8866],{"alt":8867,"dataZoomable":50,"src":8868},"Image showing the apperance tab of subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fcustomizing-apperance.png",[397,8870,8867],{},[326,8872,8873,8879,8882],{"start":185},[103,8874,8875,8876,167],{},"Select a category from the available categories or add a new one by clicking on ",[338,8877,8878],{},"\"Add new\"",[103,8880,8881],{},"Choose a color for the Subflow node and select an icon.",[103,8883,8884],{},"Provide labels for the ports so that when someone hovers over the Subflow input or output ports, they can quickly understand their purpose.",[996,8886,8888],{"id":8887},"adding-documentation-for-a-subflow-node","Adding Documentation for a Subflow Node",[15,8890,8891],{},"Node-RED allows you to add documentation for Subflow nodes, providing guidance on how to use them. This documentation will be rendered in the help sidebar, similar to other nodes.",[326,8893,8894],{},[103,8895,8856,8896,8859,8898,8670],{},[338,8897,8632],{},[338,8899,8900],{},"\"Description\"",[15,8902,8903,8907],{},[392,8904],{"alt":8905,"dataZoomable":50,"src":8906},"Image showing the description tab of subflow","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fdocumentation-tab.png",[397,8908,8867],{},[326,8910,8911,8914],{"start":185},[103,8912,8913],{},"Enter the documentation content in markdown format that provides guidance on how to use the Subflow node effectively.",[103,8915,355,8916,8706],{},[338,8917,405],{},[15,8919,8920],{},"Once saved, the documentation will be displayed in the help sidebar when users click on the Subflow node in the Node-RED palette or hover over and select the help option for that node.",[15,8922,6389,8923,3830],{},[146,8924,8925,8926,8932,8933,8943,8944,8947,8948,8951,8952,8955,8956,8958,8959,8961,8962,8964,8965,8968,8969,8971,8972,8974,8975,8978,8979,8982,8983,8985,8986,8540,8988,8990,8991,8999,9000,9003],{},"{\"id\":\"ea5436b592a86b90\",\"type\":\"subflow\",\"name\":\"API Retry \",\"info\":\"## hee\",\"category\":\"function\",\"in\":",[146,8927,8928,8929,1596],{},"{\"x\":50,\"y\":30,\"wires\":",[146,8930,8931],{},"{\"id\":\"8899fea497064b8c\"}",",\"out\":",[146,8934,8935,8936,8939,8940,1596],{},"{\"x\":1140,\"y\":60,\"wires\":",[146,8937,8938],{},"{\"id\":\"3eb4ec6b71fc383b\",\"port\":0}","},{\"x\":1610,\"y\":120,\"wires\":",[146,8941,8942],{},"{\"id\":\"092547737a0cbee2\",\"port\":0}",",\"env\":",[146,8945,8946],{},"{\"name\":\"URL\",\"type\":\"str\",\"value\":\"\"},{\"name\":\"MAX_RETRY\",\"type\":\"str\",\"value\":\"\"}",",\"meta\":{},\"color\":\"#D7D7A0\",\"inputLabels\":",[146,8949,8950],{},"\"Trigger\"",",\"outputLabels\":",[146,8953,8954],{},"\"API Response\",\"Max Retry Exeeded\"",",\"icon\":\"node-red\u002Fwhite-globe.svg\"},{\"id\":\"b4eca1de14599dd1\",\"type\":\"delay\",\"z\":\"ea5436b592a86b90\",\"name\":\"\",\"pauseType\":\"delayv\",\"timeout\":\"5\",\"timeoutUnits\":\"milliseconds\",\"rate\":\"1\",\"nbRateUnits\":\"1\",\"rateUnits\":\"second\",\"randomFirst\":\"1\",\"randomLast\":\"5\",\"randomUnits\":\"seconds\",\"drop\":false,\"allowrate\":false,\"outputs\":1,\"x\":400,\"y\":80,\"wires\":[[\"96ca1ed69e7fa7ac\"]]},{\"id\":\"96ca1ed69e7fa7ac\",\"type\":\"http request\",\"z\":\"ea5436b592a86b90\",\"name\":\"\",\"method\":\"GET\",\"ret\":\"txt\",\"paytoqs\":\"ignore\",\"url\":\"${URL}\",\"tls\":\"\",\"persist\":false,\"proxy\":\"\",\"insecureHTTPParser\":false,\"authType\":\"\",\"senderr\":false,\"headers\":",[146,8957],{},",\"x\":550,\"y\":80,\"wires\":[[\"f523bb034db360a4\"]]},{\"id\":\"3eb4ec6b71fc383b\",\"type\":\"switch\",\"z\":\"ea5436b592a86b90\",\"name\":\"if success\",\"property\":\"statusCode\",\"propertyType\":\"msg\",\"rules\":",[146,8960,8552],{},",\"checkall\":\"true\",\"repair\":false,\"outputs\":2,\"x\":920,\"y\":80,\"wires\":[[],",[146,8963,8556],{},"]},{\"id\":\"bad4bdb7e00b7a80\",\"type\":\"switch\",\"z\":\"ea5436b592a86b90\",\"name\":\"if max retries\",\"property\":\"retry_counter\",\"propertyType\":\"msg\",\"rules\":",[146,8966,8967],{},"{\"t\":\"gte\",\"v\":\"MAX_RETRY\",\"vt\":\"env\"},{\"t\":\"else\"}",",\"checkall\":\"true\",\"repair\":false,\"outputs\":2,\"x\":1090,\"y\":120,\"wires\":[[\"092547737a0cbee2\"],",[146,8970,8564],{},"]},{\"id\":\"8899fea497064b8c\",\"type\":\"change\",\"z\":\"ea5436b592a86b90\",\"name\":\"\",\"rules\":",[146,8973,8568],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":220,\"y\":80,\"wires\":[[\"b4eca1de14599dd1\"]]},{\"id\":\"f523bb034db360a4\",\"type\":\"change\",\"z\":\"ea5436b592a86b90\",\"name\":\"retry_counter++\",\"rules\":",[146,8976,8977],{},"{\"t\":\"set\",\"p\":\"retry_counter\",\"pt\":\"msg\",\"to\":\"retry_counter+1\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":740,\"y\":80,\"wires\":[[\"3eb4ec6b71fc383b\"]]},{\"id\":\"092547737a0cbee2\",\"type\":\"change\",\"z\":\"ea5436b592a86b90\",\"name\":\"\",\"rules\":",[146,8980,8981],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"Max retry exeeded\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1360,\"y\":120,\"wires\":[[]]},{\"id\":\"132f4fdc40d55e89\",\"type\":\"debug\",\"z\":\"380e37fed72e6885\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":2420,\"y\":320,\"wires\":",[146,8984],{},"},{\"id\":\"612819f76617e5a8\",\"type\":\"debug\",\"z\":\"380e37fed72e6885\",\"name\":\"debug 3\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":2420,\"y\":200,\"wires\":",[146,8987],{},[146,8989,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":1840,\"y\":260,\"wires\":[[\"ccadc2ba40bb8606\"]]},{\"id\":\"ccadc2ba40bb8606\",\"type\":\"subflow:ea5436b592a86b90\",\"z\":\"380e37fed72e6885\",\"name\":\"\",\"env\":",[146,8992,8993,8994],{},"{\"name\":\"URL\",\"value\":\"",[307,8995,8998],{"href":8996,"rel":8997},"https:\u002F\u002Fjsonplaceholder.typicode.com\u002FphotosS%22,%22type%22:%22str%22%7D,%7B%22name%22:%22MAX_RETRY%22,%22value%22:%2210000%22,%22type%22:%22str%22%7D",[311],"https:\u002F\u002Fjsonplaceholder.typicode.com\u002FphotosS\",\"type\":\"str\"},{\"name\":\"MAX_RETRY\",\"value\":\"10000\",\"type\":\"str\"}",",\"x\":2160,\"y\":260,\"wires\":[[\"612819f76617e5a8\"],",[146,9001,9002],{},"\"132f4fdc40d55e89\"","]}",[15,9005,9006],{},"Now, just like regular Node-RED nodes, you can effectively use this Subflow node in your projects. With its added documentation, customized appearance, and status indicators, it integrates seamlessly into your Node-RED environment, enhancing both usability and functionality.",[996,9008,9010],{"id":9009},"benefits-of-using-subflows","Benefits of using subflows.",[100,9012,9013,9019,9025,9031],{},[103,9014,9015,9018],{},[338,9016,9017],{},"Modularity",": Subflows allow you to group related nodes into a single, reusable unit, making complex flows easier to manage.",[103,9020,9021,9024],{},[338,9022,9023],{},"Code Reuse",": They help avoid duplicating similar logic across different parts of your flow, saving time and effort.",[103,9026,9027,9030],{},[338,9028,9029],{},"Simplified Design",": Subflows can simplify your main flow by hiding complexity within a single node.",[103,9032,9033,9036],{},[338,9034,9035],{},"Easier Maintenance",": Updating a Subflow automatically updates all instances where it is used, making maintenance quicker.",[34,9038,6518],{"id":6517},[15,9040,9041],{},"In this guide, we explored the concept of subflows in Node-RED, including their definition and purpose. We walked through the steps to create and configure subflows, demonstrating how to integrate them into your main flow. Additionally, we discussed how to edit and update existing subflows, and provided best practices for managing and organizing them effectively.",{"title":50,"searchDepth":250,"depth":250,"links":9043},[9044,9045,9055],{"id":8507,"depth":185,"text":8508},{"id":8522,"depth":185,"text":8523,"children":9046},[9047,9048,9049,9050,9051,9052,9053,9054],{"id":8576,"depth":221,"text":8577},{"id":8607,"depth":221,"text":8608},{"id":8686,"depth":221,"text":8687},{"id":8736,"depth":221,"text":8737},{"id":8791,"depth":221,"text":8792},{"id":8847,"depth":221,"text":8848},{"id":8887,"depth":221,"text":8888},{"id":9009,"depth":221,"text":9010},{"id":6517,"depth":185,"text":6518},{"type":941,"title":9057,"description":9058},"Build and Reuse Node-RED Flows Across Your Team","FlowFuse gives you version control, team collaboration, and centralized deployment, so your subflows and reusable logic stay consistent across every instance and every engineer.","2024-09-13","Learn how to effectively use subflows in Node-RED with this comprehensive guide. Discover the benefits, creation steps, and best practices for managing subflows to streamline your automation workflows.","\u002Fblog\u002F2024\u002F09\u002Fimages\u002Fsubflow-in-node-red.png",{"keywords":9063,"excerpt":9064},"node red subflow, nodered subflow, node red subflow environment variables, node red create subflow, node red subflows",{"type":12,"value":9065},[9066],[15,9067,8501],{},"\u002Fblog\u002F2024\u002F09\u002Fhow-to-use-subflow-in-node-red",{"title":8495,"description":9060},{"loc":9068},"blog\u002F2024\u002F09\u002Fhow-to-use-subflow-in-node-red","A Practical Guide to Implementing Subflows in Node-RED for Efficient Workflow Management",[6563,966],"Subflows in Node-RED let you group reusable logic into a single custom node, similar to libraries or modules in traditional programming. This guide covers how to create a subflow from an existing flow, add configurable environment variable properties to it, and use it across multiple places in your Node-RED projects to eliminate duplication.","nq7gZCzbGMp6zPXcb9RBGPxHZpCpsLdwmgl6k-WJGfw",{"id":9077,"title":9078,"authors":9079,"body":9080,"cta":9305,"date":9308,"description":9309,"extension":946,"image":9310,"lastUpdated":948,"meta":9311,"navigation":253,"path":9317,"seo":9318,"sitemap":9319,"stem":9320,"subtitle":9321,"tags":9322,"tldr":9325,"video":3,"__hash__":9326},"blog\u002Fblog\u002F2024\u002F08\u002Fopentelemetry-with-node-red.md","Monitoring and Optimizing Node-RED Flows with Open Telemetry (2026)",[10],{"type":12,"value":9081,"toc":9293},[9082,9085,9088,9092,9095,9099,9102,9105,9109,9112,9115,9135,9138,9140,9143,9167,9171,9180,9206,9209,9213,9216,9233,9236,9246,9249,9263,9266,9275,9278,9282,9285,9288,9290],[15,9083,9084],{},"Have you ever found yourself frustrated by unexpected delays in your Node-RED flows, wondering where the bottlenecks are hiding? Even small latency issues can have a big impact on your system's performance. That's where Open Telemetry comes in. With its powerful distributed tracing capabilities, you can finally take control and get a clear view of how your flows are performing in real time.",[15,9086,9087],{},"Integrating Open Telemetry with Node-RED allows you to monitor latency across your flows. By implementing distributed tracing, you’ll gain the ability to see exactly where delays occur, helping you optimize performance and ensure your IoT applications run efficiently.",[34,9089,9091],{"id":9090},"what-is-distributed-tracing-and-how-does-open-telemetry-help","What is Distributed Tracing and How Does Open Telemetry Help?",[15,9093,9094],{},"Distributed tracing is a method used to track and observe the flow of requests through different services within a distributed system. It provides insights into how requests are handled, where delays occur, and how different components interact. By visualizing the path of a request across your system, distributed tracing helps you identify performance bottlenecks and optimize the overall efficiency of your applications.",[996,9096,9098],{"id":9097},"what-is-opentelemetry","What is OpenTelemetry?",[15,9100,9101],{},"Open Telemetry is an open-source framework designed to help you monitor and understand your software systems. It collects and organizes data on how your applications perform and behave, allowing you to track requests as they move through various services. Open Telemetry provides a standardized way to gather and analyze telemetry data, including traces, metrics, and logs, to give you a comprehensive view of your system’s performance.",[15,9103,9104],{},"In Node-RED The Open Telemetry module helps track messages by creating \"spans\" that record details about each message's journey. Every time a message moves from one node to another, a span is created to capture where it came from, where it’s going, and how long it took. These spans are linked together, showing the entire path of the message through the system. This makes it easier to spot slowdowns, fix problems, and improve how data moves through Node-RED. The module also makes sure this tracking information follows the message as it moves across different nodes and external services.",[34,9106,9108],{"id":9107},"tracing-in-node-red-flows-using-opentelemetry","Tracing in Node-RED Flows using Opentelemetry",[15,9110,9111],{},"In a manufacturing plant, Node-RED manages different machines and sensors. Suppose there's a problem with the production line, such as a delay in processing or a GPIO node experiencing issues reading data. With Open Telemetry integrated, you can trace the data flow through the system to see exactly where the issue is happening. This helps you quickly identify whether the problem is with a specific node that is reading the machine data or a delay in data processing, allowing you to fix the issue faster and keep the production line running smoothly.",[15,9113,9114],{},"For demonstration purposes, we will use a flow that simulates sensor reading and data processing. We will monitor this flow using Open Telemetry to track data across the system, identify bottlenecks, and optimize performance.",[15,9116,6389,9117,3830],{},[146,9118,9119,9120,9123,9124,9127,9128,9130,9131,9134],{},"{\"id\":\"78e4a1255f9d0ad1\",\"type\":\"group\",\"z\":\"45e56b4089cada94\",\"name\":\"\",\"style\":{\"label\":true},\"nodes\":",[146,9121,9122],{},"\"636ac7b4d798a5c4\",\"ac87db82c2ab66f5\",\"c8a1749629359031\",\"c0d30281031f07db\"",",\"x\":34,\"y\":139,\"w\":852,\"h\":82},{\"id\":\"636ac7b4d798a5c4\",\"type\":\"inject\",\"z\":\"45e56b4089cada94\",\"g\":\"78e4a1255f9d0ad1\",\"name\":\"Temperature sensor\",\"props\":",[146,9125,9126],{},"{\"p\":\"payload\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"300\",\"payloadType\":\"jsonata\",\"x\":180,\"y\":180,\"wires\":[[\"c8a1749629359031\"]]},{\"id\":\"ac87db82c2ab66f5\",\"type\":\"debug\",\"z\":\"45e56b4089cada94\",\"g\":\"78e4a1255f9d0ad1\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":780,\"y\":180,\"wires\":",[146,9129],{},"},{\"id\":\"c8a1749629359031\",\"type\":\"change\",\"z\":\"45e56b4089cada94\",\"g\":\"78e4a1255f9d0ad1\",\"name\":\"Kelvin to Celsius\",\"rules\":",[146,9132,9133],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload - 273.15\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":400,\"y\":180,\"wires\":[[\"c0d30281031f07db\"]]},{\"id\":\"c0d30281031f07db\",\"type\":\"delay\",\"z\":\"45e56b4089cada94\",\"g\":\"78e4a1255f9d0ad1\",\"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\":600,\"y\":180,\"wires\":[[\"ac87db82c2ab66f5\"]]}",[15,9136,9137],{},"Deploy the flow above, and you might see a delay in the data shown on the debug panel. For this example, we added a Delay node before the Change node that converts temperature data from Kelvin to Celsius. While this delay is visible here, finding such delays in larger flows with many nodes can be difficult and time-consuming. Open Telemetry makes this easier by giving you detailed traces that show where delays or issues are happening",[996,9139,5335],{"id":5334},[15,9141,9142],{},"Before you start, ensure you have the following:",[100,9144,9145,9153],{},[103,9146,9147,9152],{},[307,9148,9151],{"href":9149,"rel":9150},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-opentelemetry",[311],"node-red-contrib-opentelemetry"," : Install this Node-RED module via the Node-RED Palette Manager.",[103,9154,9155,9156,9161,9162,167],{},"Open Telemetry exporter: Set up an Open Telemetry exporter to send trace data to a backend. For details on available exporters, visit ",[307,9157,9160],{"href":9158,"rel":9159},"https:\u002F\u002Fopentelemetry.io\u002Fdocs\u002Finstrumentation\u002Fjs\u002Fexporters\u002F",[311],"Open Telemetry Exporters",". For this guide, I have set up the ",[307,9163,9166],{"href":9164,"rel":9165},"https:\u002F\u002Fjaegertracing.io\u002F",[311],"Jaeger",[996,9168,9170],{"id":9169},"setting-open-telemetry-in-node-red","Setting Open Telemetry in Node-RED",[15,9172,9173,9178],{},[392,9174],{"alt":9175,"src":9176,"title":9177},"\"Screenshot showing the configuration of opentelmetry node\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fopentelmetry-node.png","Screenshot showing the configuration of opentelmetry node",[397,9179,9177],{},[326,9181,9182,9187,9194,9197,9200,9203],{},[103,9183,369,9184,1340],{},[19,9185,9186],{},"OTEL",[103,9188,9189,9190,9193],{},"Double-click on the node and set the URL to your exporter endpoint (e.g., ",[19,9191,9192],{},"http:\u002F\u002Flocalhost:4318\u002Fv1\u002Ftraces"," for a locally running Jaeger exporter). Provide a name for the service according to your preference, and set the Prefix, which will be added to the root Node-RED span name before the initial node name (you can keep it as \"Message\" if preferred).",[103,9195,9196],{},"In the Ignore field, add the names of nodes you want to exclude from Open Telemetry tracing.",[103,9198,9199],{},"In the Propagate field, add the names of nodes if you want them to forward trace headers to external systems or other nodes in the flow. This ensures that these nodes participate in the distributed trace, allowing the trace context to be maintained across different components.",[103,9201,9202],{},"Set the Timeout to define how long (in seconds) the OTEL node should wait before ending and discarding a message that has not been modified.",[103,9204,9205],{},"Now deploy the flow by clicking on the top-right deploy button.",[15,9207,9208],{},"Once the flow is deployed, Open Telemetry will start collecting and sending trace data to your specified exporter.",[996,9210,9212],{"id":9211},"monitoring-performance-using-the-exporter-web-ui","Monitoring Performance Using the Exporter Web UI",[15,9214,9215],{},"Now, let's monitor the performance and latency between each node to identify delays. For this section, I am assuming you have Jaeger running as your exporter.",[326,9217,9218,9224,9227,9230],{},[103,9219,9220,9221,167],{},"Open the Jaeger web UI in your browser. By default, it will be available at ",[19,9222,9223],{},"http:\u002F\u002Flocalhost:16686\u002F",[103,9225,9226],{},"Navigate to the \"Search\" by clicking on the \"Search\" option at the top.",[103,9228,9229],{},"Select the service name that you configured in the OTEL node from the service field. Once selected, you will see all the traces for each interaction in the flow. You can filter the traces by specific nodes using the operation field.",[103,9231,9232],{},"To monitor and find issues, select the desired trace and click on the \"Find Trace\" button. Click on the first trace to examine it.",[15,9234,9235],{},"Once the trace opens, you will see the duration taken by each node to process and pass data. Notice the time taken by the delay node, which is 2 seconds, indicating the problem. By clicking on the green line corresponding to this delay node, you can view more detailed information about the trace.",[15,9237,9238,9243],{},[392,9239],{"alt":9240,"src":9241,"title":9242},"\"Image showing the total duration taken by the flow\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fbefore.png","Image showing the total duration taken by the flow",[397,9244,9245],{},"Image showing the tototal duration taken by the flow",[15,9247,9248],{},"Since the issue was identified with the delay node, let's remove that delay node.",[15,9250,6389,9251,3830],{},[146,9252,9253,9254,9256,9257,9259,9260,9262],{},"{\"id\":\"636ac7b4d798a5c4\",\"type\":\"inject\",\"z\":\"350fb9fbb98012be\",\"name\":\"Temperature sensor\",\"props\":",[146,9255,9126],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"300\",\"payloadType\":\"jsonata\",\"x\":200,\"y\":100,\"wires\":[[\"c8a1749629359031\"]]},{\"id\":\"ac87db82c2ab66f5\",\"type\":\"debug\",\"z\":\"350fb9fbb98012be\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":640,\"y\":100,\"wires\":",[146,9258],{},"},{\"id\":\"c8a1749629359031\",\"type\":\"change\",\"z\":\"350fb9fbb98012be\",\"name\":\"Kelvin to Celsius\",\"rules\":",[146,9261,9133],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":420,\"y\":100,\"wires\":[[\"ac87db82c2ab66f5\"]]}",[15,9264,9265],{},"After updating the flow, redeploy the flow and check the traces again. You should see that the total time has been reduced significantly, with the overall flow now taking around 8 milliseconds instead of the previous 2 seconds. This demonstrates how Open Telemetry helps in identifying and resolving performance issues in your Node-RED flows.",[15,9267,9268,9273],{},[392,9269],{"alt":9270,"src":9271,"title":9272},"\"Image showing the total duration taken by the flow after fixing the issue\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fafter.png","Image showing the total duration taken by the flow after fixing the issue",[397,9274,9272],{},[15,9276,9277],{},"Throughout this guide, we’ve interacted with an exporter which is running locally. However, by deploying and setting up your exporter on a server, you can remotely monitor the performance of your Node-RED flows. This setup enables you to oversee your system's performance from anywhere, making it easier to detect and address issues promptly.",[34,9279,9281],{"id":9280},"enhancing-monitoring-and-optimization-with-flowfuse","Enhancing Monitoring and Optimization with FlowFuse",[15,9283,9284],{},"While OpenTelemetry excels at tracing and optimizing Node-RED flows, FlowFuse offers a powerful solution for managing and monitoring Node-RED instances. It streamlines the creation, deployment, and management of instances, allowing you to deploy your applications with a single click and minimizing deployment complexity and errors.",[15,9286,9287],{},"FlowFuse also boosts collaboration and security through features like team management, role-based access control, multi-factor authentication, and snapshot recovery. These capabilities ensure effective management, secure access, and easy recovery from changes, making FlowFuse an essential tool for optimizing and overseeing your Node-RED deployments.",[34,9289,6518],{"id":6517},[15,9291,9292],{},"Integrating OpenTelemetry with Node-RED enables you to efficiently trace and resolve delays in your flows, ensuring smoother and more efficient operation of your IoT applications. By following the steps outlined in this guide, you can leverage distributed tracing to identify performance bottlenecks and optimize your flows effectively. With OpenTelemetry's detailed insights and FlowFuse's robust features, you'll be well-equipped to maintain peak performance and manage your Node-RED environment seamlessly.",{"title":50,"searchDepth":250,"depth":250,"links":9294},[9295,9298,9303,9304],{"id":9090,"depth":185,"text":9091,"children":9296},[9297],{"id":9097,"depth":221,"text":9098},{"id":9107,"depth":185,"text":9108,"children":9299},[9300,9301,9302],{"id":5334,"depth":221,"text":5335},{"id":9169,"depth":221,"text":9170},{"id":9211,"depth":221,"text":9212},{"id":9280,"depth":185,"text":9281},{"id":6517,"depth":185,"text":6518},{"type":941,"title":9306,"description":9307},"Manage and Monitor Node-RED at Scale","FlowFuse gives you centralized deployment, team collaboration, role-based access control, and snapshot recovery, everything you need to keep your Node-RED flows running reliably in production.","2024-08-15","Learn to integrate Open Telemetry with Node-RED to track and optimize flow performance.","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fopentelemetry-with-node-red.png",{"keywords":9312,"excerpt":9313},"nodered opentelemetry node opentelemetry, opentelemetry nodejs example, opentelemetry node, open telemetry nodejs, node-red distributed tracing, node-red performance monitoring, opentelemetry jaeger node-red",{"type":12,"value":9314},[9315],[15,9316,9084],{},"\u002Fblog\u002F2024\u002F08\u002Fopentelemetry-with-node-red",{"title":9078,"description":9309},{"loc":9317},"blog\u002F2024\u002F08\u002Fopentelemetry-with-node-red","Integrating Open Telemetry with Node-RED for Efficient Distributed Tracing",[9323,6563,9324,966],"post","node-red tips","This guide explains how to integrate OpenTelemetry with Node-RED using the node-red-contrib-opentelemetry module to perform distributed tracing. By connecting to a Jaeger exporter, you can inspect per-node latency across your flows, pinpoint bottlenecks, and verify that fixes have the intended effect, reducing a 2-second delay to just 8 milliseconds in the worked example.","2MJmZhFXjYGCebpb9NL5v9JHzn3lF990Q1XMOXrFfzE",{"id":9328,"title":9329,"authors":9330,"body":9331,"cta":9814,"date":9817,"description":9818,"extension":946,"image":9819,"lastUpdated":948,"meta":9820,"navigation":253,"path":9826,"seo":9827,"sitemap":9828,"stem":9829,"subtitle":9830,"tags":9831,"tldr":9833,"video":3,"__hash__":9834},"blog\u002Fblog\u002F2024\u002F08\u002Fopc-ua-to-mqtt-with-node-red.md","Bridging OPC UA Data to MQTT with Node-RED (2026)",[10],{"type":12,"value":9332,"toc":9802},[9333,9336,9339,9343,9352,9355,9358,9368,9372,9375,9377,9405,9409,9412,9427,9435,9443,9452,9459,9467,9502,9505,9509,9512,9530,9538,9578,9582,9585,9594,9602,9618,9626,9683,9687,9690,9693,9726,9734,9761,9765],[15,9334,9335],{},"Have you ever found yourself trying to connect old industrial systems with new IoT tools? This is a common scenario when trying to digitally transform while setting up your Unified Name Space. Maybe you have machinery that uses OPC UA, but your data is sent through MQTT. How do you make these systems work together smoothly?",[15,9337,9338],{},"In this guide, we'll demonstrate how to use Node-RED to bridge OPC UA data to MQTT. This integration will streamline your data flow and enhance real-time monitoring, helping you modernize your setup and improve communication between systems.",[996,9340,9342],{"id":9341},"why-bridge-opc-ua-to-mqtt","Why Bridge OPC UA to MQTT",[15,9344,9345,9349],{},[392,9346],{"alt":9347,"dataZoomable":50,"src":9348},"Diagram showing the data flow when bridging OPC UA to MQTT to enable communication between non-OPC UA compatible systems and devices","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fopc-ua-to-mqtt.png",[397,9350,9351],{},"Diagram showing the data flow when bridging OPC UA to MQTT to enable communication between non-OPC UA compatible systems and devices.",[15,9353,9354],{},"In modern industrial environments, integrating systems with different communication protocols can be a significant challenge. For example, a CNC machine on the factory floor might use OPC UA, while some cloud solutions, edge devices, and other systems, such as custom ERP solutions and IoT applications, might rely on MQTT protocol. This is where bridging OPC UA to MQTT becomes highly beneficial.",[15,9356,9357],{},"By converting OPC UA data into MQTT messages, you make the data from the CNC machine accessible to a broader range of systems that use MQTT, which is a more universally supported messaging protocol. This bridging solution simplifies the integration process, allowing diverse systems to communicate effectively without needing direct OPC UA support.",[15,9359,9360,9363,9364,167],{},[338,9361,9362],{},"Node-RED"," is perfect for this job. It can connect both OPC UA and MQTT, making it easy to transform and route data between different systems. Its flexibility and support for many protocols make it great for integrating various industrial hardware and software. For more on how Node-RED can improve industrial operations, check out ",[307,9365,9367],{"href":9366},"\u002Fblog\u002F2024\u002F07\u002Fbuilding-on-flowfuse-devices\u002F","Building on FlowFuse: Remote Device Monitoring",[34,9369,9371],{"id":9370},"bridging-opc-ua-data-to-mqtt-with-node-red","Bridging OPC UA Data to MQTT with Node-RED",[15,9373,9374],{},"In this section, I'll demonstrate how to bridge OPC UA data to MQTT using Node-RED. We will use simulated OPC UA server data from a CNC machine as an example. The goal is to show how you can efficiently transfer this data to an MQTT broker, making it accessible to various applications and systems.",[996,9376,5335],{"id":5334},[100,9378,9379,9387,9390,9398],{},[103,9380,9381,9382,167],{},"OPC UA Server: Make sure you have an OPC UA server configured and running with the necessary data. For this blog, we'll use the Prosys OPC UA Simulation Server, which simulates data from CNC machines designed for testing OPC UA client applications and learning the technology. You can download it from ",[307,9383,9386],{"href":9384,"rel":9385},"https:\u002F\u002Fprosysopc.com\u002Fproducts\u002Fopc-ua-simulation-server\u002F",[311],"here",[103,9388,9389],{},"FlowFuse Account: A FlowFuse account lets you quickly create, deploy, and manage Node-RED instances in the cloud. [sign up now]({% include \"sign-up-url.njk\" %}?utm_campaign=60718323-BCTA&utm_source=blog&utm_medium=cta&utm_term=high_intent&utm_content=Bridging%20OPC%20UA%20Data%20to%20MQTT%20with%20Node-RED).",[103,9391,9392,9397],{},[307,9393,9396],{"href":9394,"rel":9395},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-opcua",[311],"node-red-contrib-opcua",": install the node-red contrib package that will enable integration of opcua in Node-RED.",[103,9399,9400,9401,167],{},"MQTT Broker: We’ll need an MQTT broker for data communication. FlowFuse offers an integrated MQTT Broker Service within Platform for easy setup. For more details, check out ",[307,9402,9404],{"href":9403},"\u002Fblog\u002F2024\u002F10\u002Fannouncement-mqtt-broker\u002F","FlowFuse's MQTT Broker Announcement",[996,9406,9408],{"id":9407},"retrieving-data-from-the-opc-ua-server","Retrieving Data from the OPC UA Server",[15,9410,9411],{},"To begin retrieving data from your OPC UA server using Node-RED, follow these steps:",[326,9413,9414,9418],{},[103,9415,4995,9416,1340],{},[338,9417,1339],{},[103,9419,4995,9420,9422,9423,9426],{},[338,9421,1405],{}," node onto the canvas and double-click on the node to open its configuration settings. Set the ",[19,9424,9425],{},"msg.topic"," to the node ID and datatype of the property you wish to read.",[15,9428,9429,9433],{},[392,9430],{"alt":9431,"dataZoomable":50,"src":9432},"(Left) Image of the Change node setting the 'msg.topic' to retrieve the cycle time data and (Right) the OPC UA Prosys interface.","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fchange-node-setting-nodeid-datatype.png",[397,9434,9431],{},[326,9436,9437],{"start":185},[103,9438,4995,9439,9442],{},[338,9440,9441],{},"OpcUa-Client"," node onto the canvas. Double-click on it to open its configuration settings. Click the \"+\" icon next to the Endpoint field and enter the URL of your running OPC UA server. Configure the security policy and mode according to your server setup. If you use the Prosys OPC UA Simulation Server and have not enabled any security features, you can leave the security policy and mode as \"None.\"",[15,9444,9445,9449],{},[392,9446],{"alt":9447,"dataZoomable":50,"src":9448},"Configuring opc-ua client node with the opc ua server endpoint","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fopc-ua-config.png",[397,9450,9451],{},"Configuring opc-ua node with the opc ua server endpoint",[326,9453,9454],{"start":221},[103,9455,508,9456,9458],{},[338,9457,9441],{}," node settings, select the action type as \"READ.\" This instructs Node-RED to read data from the OPC UA server.",[15,9460,9461,9465],{},[392,9462],{"alt":9463,"dataZoomable":50,"src":9464},"Configuring OpcUa-Client node to select the read operation","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fopc-ua-config2.png",[397,9466,9463],{},[326,9468,9469,9472,9477,9499],{"start":250},[103,9470,9471],{},"If your OPC UA server uses security features, specify the path to your certificate files in the relevant fields. If no security is configured, this step can be skipped.",[103,9473,4995,9474,9476],{},[338,9475,1395],{}," node onto the canvas. The output will help you verify the data retrieved from the OPC UA server.",[103,9478,8038,9479,8041,9481,8044,9483,8041,9485,9487,9488,8041,9490,9492,9493,9495,9496,9498],{},[338,9480,1339],{},[338,9482,1405],{},[338,9484,1405],{},[338,9486,9441],{}," node. Then, connect the output of the ",[338,9489,9441],{},[338,9491,1395],{}," node. This setup ensures that when the ",[338,9494,1339],{}," node triggers, it sends data to the ",[338,9497,9441],{}," node, and the results are displayed in the Debug node.",[103,9500,9501],{},"Deploy the flow by clicking the \"Deploy\" button in the top right corner. To test the setup, press the Inject button.",[15,9503,9504],{},"You can follow the same steps to retrieve other property values from the OPC UA server. In this example, we are retrieving four simulated data properties: the cycle time, temperature, and spindle speed of the simulated CNC machine. Your setup might differ depending on the properties and data available on your OPC UA server.",[996,9506,9508],{"id":9507},"transforming-and-aggregating-data","Transforming and Aggregating Data",[15,9510,9511],{},"Once you have successfully retrieved data from your OPC UA server, the next step is to transform and aggregate this data to make it suitable for publishing to an MQTT broker. This demonstration, we will aggregate the retrieved individual property values into a single object. Depending on your specific needs, you might choose to split the object properties and send them separately or perform various calculations and transformations on the data.",[326,9513,9514,9518],{},[103,9515,4995,9516,1340],{},[338,9517,1405],{},[103,9519,9520,9521,9523,9524,764,9526,9529],{},"Double-click on the node and set ",[19,9522,9425],{}," to the name of the property you want to set for the retrieved data. In this context, set ",[19,9525,9425],{},[19,9527,9528],{},"'cycle-time'",", which will be the key in the object that we will create.",[15,9531,9532,9535],{},[392,9533],{"alt":9534,"dataZoomable":50,"src":9432},"Setting the msg.topic with the Change node to retrieve data from the OPC UA server.",[397,9536,9537],{},"Setting the msg.topic with the change node to retrieve data from the OPC UA server.",[326,9539,9540,9555,9569,9575],{"start":221},[103,9541,4995,9542,9545,9546,9548,9549,9551,9552,9554],{},[338,9543,9544],{},"join"," node onto the canvas. Set the mode to manual, with the option to create ",[19,9547,382],{}," using the values of ",[19,9550,9425],{}," as keys. Set the count to 3 and ensure that the interval for all of the ",[338,9553,1339],{}," nodes triggering data retrieval is the same. This ensures that the data is collected and aggregated correctly at the same time.",[103,9556,8038,9557,9559,9560,9562,9563,9565,9566,9568],{},[338,9558,9441],{}," node (which retrieves the data) to the input of the ",[338,9561,1405],{}," node. For example, if I have set the ",[338,9564,1405],{}," node for the 'cycle-time' data property, connect it to the ",[338,9567,9441],{}," node that retrieves this data.",[103,9570,8038,9571,8041,9573,1361],{},[338,9572,1405],{},[338,9574,9544],{},[103,9576,9577],{},"Repeat this process for all of your data properties.",[996,9579,9581],{"id":9580},"sending-data-to-the-mqtt-broker","Sending Data to the MQTT Broker",[15,9583,9584],{},"Now, in this section, we will show you how to send the collected data to an MQTT broker:",[326,9586,9587,9591],{},[103,9588,4995,9589,1340],{},[338,9590,6739],{},[103,9592,9593],{},"Double-click on it and configure it with your MQTT broker details.",[15,9595,9596,9600],{},[392,9597],{"alt":9598,"dataZoomable":50,"src":9599},"Configuring the mqtt out node with broker information","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fmqtt-out-node-config.png",[397,9601,9598],{},[326,9603,9604,9609,9615],{"start":221},[103,9605,9606,9607,1361],{},"Set the topic for your data in the ",[338,9608,6739],{},[103,9610,8038,9611,8041,9613,1361],{},[338,9612,9544],{},[338,9614,6739],{},[103,9616,9617],{},"Deploy the flow. After deploying, you will see the status \"connected\" with a green dot at the bottom of each node, indicating that you have successfully connected to your MQTT broker.",[15,9619,9620,9624],{},[392,9621],{"alt":9622,"dataZoomable":50,"src":9623},"Image showing the successful bridging of OPC UA data to MQTT","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fopcua-to-mqtt.gif",[397,9625,9622],{},[15,9627,6389,9628,3830],{},[146,9629,9630,9631,9633,9634,9636,9637,9639,9640,9642,9643,9646,9647,9650,9651,9654,9655,9657,9658,9660,9661,9663,9664,9667,9668,9671,9672,9675,9676,9678,9679],{},"{\"id\":\"a099aefb08837e70\",\"type\":\"OpcUa-Client\",\"z\":\"807758ec576fbfd8\",\"endpoint\":\"9dd56eda04f5c5b5\",\"action\":\"read\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"\",\"x\":480,\"y\":320,\"wires\":[[\"f5fd1ffafdfe790f\"],",[146,9632],{},"]},{\"id\":\"1aa02b27b99dfe9d\",\"type\":\"mqtt out\",\"z\":\"807758ec576fbfd8\",\"name\":\"\",\"topic\":\"\u002Fmanufacturing\u002Fcnc\",\"qos\":\"2\",\"retain\":\"true\",\"respTopic\":\"\",\"contentType\":\"\",\"userProps\":\"\",\"correl\":\"\",\"expiry\":\"\",\"broker\":\"abd4e6202945fee3\",\"x\":1390,\"y\":380,\"wires\":",[146,9635],{},"},{\"id\":\"d565ae620d90498a\",\"type\":\"OpcUa-Client\",\"z\":\"807758ec576fbfd8\",\"endpoint\":\"9dd56eda04f5c5b5\",\"action\":\"read\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"\",\"x\":480,\"y\":400,\"wires\":[[\"fc4b83a8a0be3a35\"],",[146,9638],{},"]},{\"id\":\"0e0614ada3269627\",\"type\":\"OpcUa-Client\",\"z\":\"807758ec576fbfd8\",\"endpoint\":\"9dd56eda04f5c5b5\",\"action\":\"read\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"\",\"x\":480,\"y\":480,\"wires\":[[\"e1c4fe72e4f37b6a\"],",[146,9641],{},"]},{\"id\":\"f5fd1ffafdfe790f\",\"type\":\"change\",\"z\":\"807758ec576fbfd8\",\"name\":\"Set the topic for the data\",\"rules\":",[146,9644,9645],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"cycle-time\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":730,\"y\":300,\"wires\":[[\"913e9de1324a6f21\"]]},{\"id\":\"fc4b83a8a0be3a35\",\"type\":\"change\",\"z\":\"807758ec576fbfd8\",\"name\":\"Set the topic for the data\",\"rules\":",[146,9648,9649],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"spindle-speed\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":730,\"y\":380,\"wires\":[[\"913e9de1324a6f21\"]]},{\"id\":\"e1c4fe72e4f37b6a\",\"type\":\"change\",\"z\":\"807758ec576fbfd8\",\"name\":\"Set the topic for the data\",\"rules\":",[146,9652,9653],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"temperature\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":730,\"y\":460,\"wires\":[[\"913e9de1324a6f21\"]]},{\"id\":\"913e9de1324a6f21\",\"type\":\"join\",\"z\":\"807758ec576fbfd8\",\"name\":\"Create object from those three data property \",\"mode\":\"custom\",\"build\":\"object\",\"property\":\"payload\",\"propertyType\":\"msg\",\"key\":\"topic\",\"joiner\":\"\\n\",\"joinerType\":\"str\",\"useparts\":false,\"accumulate\":false,\"timeout\":\"\",\"count\":\"3\",\"reduceRight\":false,\"reduceExp\":\"\",\"reduceInit\":\"\",\"reduceInitType\":\"\",\"reduceFixup\":\"\",\"x\":1080,\"y\":380,\"wires\":[[\"1aa02b27b99dfe9d\"]]},{\"id\":\"3339483f64116fce\",\"type\":\"mqtt in\",\"z\":\"807758ec576fbfd8\",\"name\":\"\",\"topic\":\"\u002Fmanufacturing\u002Fcnc\",\"qos\":\"2\",\"datatype\":\"auto-detect\",\"broker\":\"abd4e6202945fee3\",\"nl\":false,\"rap\":true,\"rh\":0,\"inputs\":0,\"x\":170,\"y\":660,\"wires\":[[\"d881d251071bd317\"]]},{\"id\":\"d881d251071bd317\",\"type\":\"debug\",\"z\":\"807758ec576fbfd8\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":460,\"y\":660,\"wires\":",[146,9656],{},"},{\"id\":\"55f43460af9601ec\",\"type\":\"comment\",\"z\":\"807758ec576fbfd8\",\"name\":\"Retrieving the data from mqtt\",\"info\":\"\",\"x\":320,\"y\":600,\"wires\":",[146,9659],{},"},{\"id\":\"628ab54495901021\",\"type\":\"comment\",\"z\":\"807758ec576fbfd8\",\"name\":\"Bridging OPC UA data to MQTT\",\"info\":\"\",\"x\":350,\"y\":240,\"wires\":",[146,9662],{},"},{\"id\":\"ce6b8a0e2c8a895d\",\"type\":\"change\",\"z\":\"807758ec576fbfd8\",\"name\":\"\",\"rules\":",[146,9665,9666],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"ns=3;i=1010,datatype=float\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":290,\"y\":320,\"wires\":[[\"a099aefb08837e70\"]]},{\"id\":\"50fe2b9310d3bb3f\",\"type\":\"change\",\"z\":\"807758ec576fbfd8\",\"name\":\"\",\"rules\":",[146,9669,9670],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"ns=3;i=1011,datatype=basedatatype\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":290,\"y\":400,\"wires\":[[\"d565ae620d90498a\"]]},{\"id\":\"9cf691f55748d013\",\"type\":\"change\",\"z\":\"807758ec576fbfd8\",\"name\":\"\",\"rules\":",[146,9673,9674],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"ns=3;i=1012,datatype=float\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":290,\"y\":480,\"wires\":[[\"0e0614ada3269627\"]]},{\"id\":\"de74dabc616c3094\",\"type\":\"inject\",\"z\":\"807758ec576fbfd8\",\"name\":\"\",\"props\":",[146,9677,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":120,\"y\":400,\"wires\":[[\"ce6b8a0e2c8a895d\",\"50fe2b9310d3bb3f\",\"9cf691f55748d013\"]]},{\"id\":\"9dd56eda04f5c5b5\",\"type\":\"OpcUa-Endpoint\",\"endpoint\":\"opc.tcp:\u002F\u002FRoni:53530\u002FOPCUA\u002FSimulationServer\",\"secpol\":\"None\",\"secmode\":\"None\",\"none\":true,\"login\":false,\"usercert\":false,\"usercertificate\":\"\",\"userprivatekey\":\"\"},{\"id\":\"abd4e6202945fee3\",\"type\":\"mqtt-broker\",\"name\":\"\",\"broker\":\"",[307,9680,9682],{"rel":9681},[311],"http:\u002F\u002Fbroker.hivemq.com\",\"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\":\"\"}",[34,9684,9686],{"id":9685},"bridging-mqtt-data-to-opc-ua","Bridging MQTT Data to OPC UA",[15,9688,9689],{},"In addition to bridging data from OPC UA to MQTT, you might also need to send data from MQTT back to an OPC UA server. This is often required in scenarios where external systems, such as Manufacturing Execution Systems (MES), need to update or control machinery settings.",[15,9691,9692],{},"For example, an MES can send commands or configuration changes via MQTT, which then need to be applied to an OPC UA-controlled machine.",[326,9694,9695,9701,9709,9715],{},[103,9696,369,9697,9700],{},[338,9698,9699],{},"mqtt in"," node onto the Node-RED canvas and configure it with your MQTT broker details and the appropriate topic where the MES publishes commands.",[103,9702,4995,9703,9705,9706,9708],{},[338,9704,1405],{}," node onto the canvas, Set the ",[19,9707,9425],{}," to the node ID and datatype of the property you wish to update.",[103,9710,9711,9712,9714],{},"Add an ",[338,9713,9441],{}," node to the canvas and configure it with your OPC UA server. Set the action type to \"WRITE\" to send the received data.",[103,9716,8038,9717,8041,9719,9721,9722,8041,9724,1361],{},[338,9718,9699],{},[338,9720,1405],{}," node, and the output of the ",[338,9723,1405],{},[338,9725,9441],{},[15,9727,9728,9732],{},[392,9729],{"alt":9730,"dataZoomable":50,"src":9731},"Image showing the successful bridging of MQTT data to OPC UA","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fmqtt-to-opcua.gif",[397,9733,9622],{},[15,9735,6389,9736,3830],{},[146,9737,9738,9739,9741,9742,9744,9745,9748,9749,9751,9752,9754,9755,9757,9758],{},"{\"id\":\"a099aefb08837e70\",\"type\":\"OpcUa-Client\",\"z\":\"FFF0000000000001\",\"endpoint\":\"9dd56eda04f5c5b5\",\"action\":\"write\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"\",\"x\":760,\"y\":220,\"wires\":[[],",[146,9740],{},"]},{\"id\":\"628ab54495901021\",\"type\":\"comment\",\"z\":\"FFF0000000000001\",\"name\":\"Bridging MQTT to OPC UA\",\"info\":\"\",\"x\":510,\"y\":140,\"wires\":",[146,9743],{},"},{\"id\":\"ce6b8a0e2c8a895d\",\"type\":\"change\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"rules\":",[146,9746,9747],{},"{\"t\":\"set\",\"p\":\"topic\",\"pt\":\"msg\",\"to\":\"ns=3;i=1010,datatype=Boolean\",\"tot\":\"str\"},{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"paylad\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":540,\"y\":220,\"wires\":[[\"a099aefb08837e70\"]]},{\"id\":\"0a89ebd0d9f6f577\",\"type\":\"mqtt in\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"topic\":\"command\u002Fcnc\u002F\",\"qos\":\"2\",\"datatype\":\"auto-detect\",\"broker\":\"abd4e6202945fee3\",\"nl\":false,\"rap\":true,\"rh\":0,\"inputs\":0,\"x\":300,\"y\":220,\"wires\":[[\"ce6b8a0e2c8a895d\"]]},{\"id\":\"4cf4e425d075722a\",\"type\":\"mqtt out\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"topic\":\"command\u002Fcnc\u002F\",\"qos\":\"1\",\"retain\":\"\",\"respTopic\":\"\",\"contentType\":\"\",\"userProps\":\"\",\"correl\":\"\",\"expiry\":\"\",\"broker\":\"abd4e6202945fee3\",\"x\":660,\"y\":400,\"wires\":",[146,9750],{},"},{\"id\":\"8e339e511c573905\",\"type\":\"inject\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"props\":",[146,9753,9126],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"false\",\"payloadType\":\"bool\",\"x\":330,\"y\":400,\"wires\":[[\"4cf4e425d075722a\"]]},{\"id\":\"9c33cff54b0aca15\",\"type\":\"comment\",\"z\":\"FFF0000000000001\",\"name\":\"Sending Command\",\"info\":\"\",\"x\":490,\"y\":340,\"wires\":",[146,9756],{},"},{\"id\":\"9dd56eda04f5c5b5\",\"type\":\"OpcUa-Endpoint\",\"endpoint\":\"opc.tcp:\u002F\u002FRoni:53530\u002FOPCUA\u002FSimulationServer\",\"secpol\":\"None\",\"secmode\":\"None\",\"none\":true,\"login\":false,\"usercert\":false,\"usercertificate\":\"\",\"userprivatekey\":\"\"},{\"id\":\"abd4e6202945fee3\",\"type\":\"mqtt-broker\",\"name\":\"\",\"broker\":\"",[307,9759,9682],{"rel":9760},[311],[996,9762,9764],{"id":9763},"up-next","Up Next",[100,9766,9767,9774,9781,9788,9795],{},[103,9768,9769,9773],{},[307,9770,9772],{"href":9771},"\u002Fnode-red\u002Fprotocol\u002Fmqtt\u002F","Using MQTT with Node-RED","\nLearn how to integrate MQTT with Node-RED to enhance your IoT solutions with real-time data messaging.",[103,9775,9776,9780],{},[307,9777,9779],{"href":9778},"\u002Fblog\u002F2023\u002F07\u002Fhow-to-build-a-opc-client-dashboard-in-node-red\u002F","How to Build an OPC UA Client Dashboard in Node-RED","\nFollow a step-by-step guide to create a comprehensive OPC UA client dashboard in Node-RED for effective monitoring and control.",[103,9782,9783,9787],{},[307,9784,9786],{"href":9785},"\u002Fnode-red\u002Fprotocol\u002Fopc-ua\u002F","Building a Secure OPC UA Server in Node-RED","\nExplore best practices for configuring a secure OPC UA server in Node-RED to ensure safe and reliable data exchange.",[103,9789,9790,9794],{},[307,9791,9793],{"href":9792},"\u002Fblog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red\u002F","How to Deploy a Basic OPC UA Server in Node-RED","\nLearn how to quickly deploy a basic OPC UA server in Node-RED for testing and development purposes.",[103,9796,9797,9801],{},[307,9798,9800],{"href":9799},"\u002Fblog\u002F2023\u002F06\u002Fnode-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter\u002F","Node-RED as a No-Code EtherNet\u002FIP to S7 Protocol Converter","\nDiscover how to use Node-RED to seamlessly convert EtherNet\u002FIP to S7 protocols with Node-RED.",{"title":50,"searchDepth":250,"depth":250,"links":9803},[9804,9805,9811],{"id":9341,"depth":221,"text":9342},{"id":9370,"depth":185,"text":9371,"children":9806},[9807,9808,9809,9810],{"id":5334,"depth":221,"text":5335},{"id":9407,"depth":221,"text":9408},{"id":9507,"depth":221,"text":9508},{"id":9580,"depth":221,"text":9581},{"id":9685,"depth":185,"text":9686,"children":9812},[9813],{"id":9763,"depth":221,"text":9764},{"type":941,"title":9815,"description":9816},"Connect OPC UA and MQTT with Node-RED on FlowFuse","FlowFuse makes it easy to deploy Node-RED in the cloud or at the edge and bridge industrial protocols like OPC UA and MQTT, with built-in MQTT broker support, team collaboration, and production-ready infrastructure.","2024-08-13","Learn how to bridge OPC UA data to MQTT using Node-RED for seamless industrial IoT integration and real-time data flow.","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fbrdging-opcua-to-mqtt.png",{"keywords":9821,"excerpt":9822},"opc ua gateway, opc ua example, node-red-contrib-opcua, nodered opcua, opc ua mqtt, opc ua over mqtt, opc ua to mqtt gateway, mqtt to opc ua, mqtt and opc ua, opc ua and mqtt, opc ua via mqtt, opcua to mqtt",{"type":12,"value":9823},[9824],[15,9825,9335],{},"\u002Fblog\u002F2024\u002F08\u002Fopc-ua-to-mqtt-with-node-red",{"title":9329,"description":9818},{"loc":9826},"blog\u002F2024\u002F08\u002Fopc-ua-to-mqtt-with-node-red","Connecting OPC UA Data Streams to MQTT Brokers for Enhanced IoT Communication and Monitoring",[9832,6563,7444,2055,966],"posts","This guide demonstrates how to connect an OPC UA server to an MQTT broker using Node-RED, allowing industrial machine data to flow into modern IoT systems. It covers installing node-red-contrib-opcua, reading multiple OPC UA node values, aggregating them with a join node, and publishing the result to MQTT. A reverse bridging pattern for sending MQTT commands back to OPC UA is also covered.","1ykI1HGoZ2_Ok3cOEeLGGBNC4WqOMHPU3pfFFRiCFww",{"id":9836,"title":9837,"authors":9838,"body":9839,"cta":11052,"date":11055,"description":11056,"extension":946,"image":11057,"lastUpdated":948,"meta":11058,"navigation":253,"path":11064,"seo":11065,"sitemap":11066,"stem":11067,"subtitle":11068,"tags":11069,"tldr":11072,"video":3,"__hash__":11073},"blog\u002Fblog\u002F2024\u002F08\u002Fcustomise-theming-in-your-dashboards.md","Customise theming in your FlowFuse Dashboard (2026)",[10],{"type":12,"value":9840,"toc":11029},[9841,9844,9847,9850,9854,9866,9870,9877,9886,9890,9893,9896,9904,9913,9925,9929,9971,10190,10199,10208,10212,10222,10243,10285,10296,10305,10309,10316,10325,10329,10338,10359,10374,10754,10764,10768,10777,10781,10793,10835,10839,10849,10877,10881,10884,10887,10893,10899,10904,10913,10916,10920,10923,10932,10936,10939,10948,10952,10955,10964,10968,10971,10974,10982,10991,10999,11008,11013,11021,11023,11026],[15,9842,9843],{},"A recent release of FlowFuse Dashboard (Dashboard 2.0) has taken customization to the next level.",[15,9845,9846],{},"Previously, users enjoyed the flexibility of tweaking navigation sidebars, themes, and group and page padding. With the new update, you can fully personalize the header too, adding unique elements to enhance your dashboard experience and customize your application to your own branding.",[15,9848,9849],{},"In this article, we'll delve into these exciting new features, including theme adjustments, custom styling, and layout modifications, that empower you to tailor your Node-RED Dashboard like never before.",[34,9851,9853],{"id":9852},"adding-elements-in-the-header","Adding Elements in the Header",[15,9855,9856,9857,9862,9863,9865],{},"To add elements to the header, we can use ",[307,9858,9861],{"href":9859,"rel":9860},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fnodes\u002Fwidgets\u002Fui-template.html#teleports",[311],"Teleports"," within the ",[19,9864,5152],{}," node. This allows elements to be seamlessly rendered in specific areas of the dashboard. This method simplifies the process compared to manually positioning items with CSS, which can be complex, time-consuming, and potentially disruptive to other dashboard elements.",[996,9867,9869],{"id":9868},"left-side-of-the-header","Left Side of the Header",[15,9871,9872,9873,9876],{},"To render content on the left side of the header, we can teleport content into the ",[19,9874,9875],{},"#app-bar-title"," element, where our page name is displayed.",[15,9878,9879,9884],{},[392,9880],{"alt":9881,"dataZoomable":50,"src":9882,"title":9883},"\"Screenshot of Dashboard showing the #app-bar-title container\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fleft-side-area.png","Image of Dashboard showing the #app-bar-title container",[397,9885,9883],{},[4987,9887,9889],{"id":9888},"hiding-the-page-name-in-the-header","Hiding the Page Name in the Header",[15,9891,9892],{},"Before proceeding, you should hide the page name on the left side of the header by default. This will ensure that when you add elements to the header, they do not clash with the page name.",[15,9894,9895],{},"To hide the page name:",[326,9897,9898,9901],{},[103,9899,9900],{},"Go to the FlowFuse Dashboard sidebar",[103,9902,9903],{},"Click on to the \"Edit settings\" option located at the top of the FlowFuse Dashboard sidebar.",[15,9905,9906,9911],{},[392,9907],{"alt":9908,"dataZoomable":50,"src":9909,"title":9910},"\"Screenshot showing the 'edit setting' option in the dashboard sidebar\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fedit-setting-button.png","Screenshot showing the 'edit setting' option in the dashboard sidebar",[397,9912,9910],{},[326,9914,9915],{"start":221},[103,9916,9917,9918,9923],{},"Untick the option \"Show page name in the header bar\".\n",[392,9919],{"alt":9920,"dataZoomable":50,"src":9921,"title":9922},"\"Screenshot showing the 'Show page name in the header bar' option in the dashboard settings\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fsettings.png","Screenshot showing the 'Show page name in the header bar' option in the dashboard settings",[397,9924,9922],{},[4987,9926,9928],{"id":9927},"example-adding-buttons","Example: Adding Buttons",[326,9930,9931,9936,9956,9965],{},[103,9932,408,9933,9935],{},[19,9934,5152],{}," node onto the Node-RED Editor canvas.",[103,9937,9938,9939,804,9942,9945,9946,9948,9949,9952,9953,9955],{},"Double-click on it and select the scope to either ",[19,9940,9941],{},"ui-scope",[19,9943,9944],{},"page-scope",". Selecting ",[19,9947,9941],{}," will render this content on ",[397,9950,9951],{},"all"," pages. ",[19,9954,9944],{}," will just render to a specified page.",[103,9957,9958,9959,9961,9962,9964],{},"Choose the page on which you want to render the buttons if you selected ",[19,9960,9944],{},", or choose correct ui if ",[19,9963,9941],{}," is selected.",[103,9966,9967,9968,9970],{},"Paste the following Vue snippet into the template widget. In this snippet, note how we specify the \"to\" attribute targeting the ",[19,9969,9875],{}," ID in the teleport tag:",[42,9972,9974],{"className":5156,"code":9973,"language":5158,"meta":50,"style":50},"\u003Ctemplate>\n    \u003C!-- Teleport the button to the #app-bar-actions area when mounted -->\n    \u003CTeleport v-if=\"mounted\" to=\"#app-bar-title\">\n        \u003Cv-btn>Button 1\u003C\u002Fv-btn>\n        \u003Cv-btn>Button 2\u003C\u002Fv-btn>\n        \u003Cv-btn>Button 3\u003C\u002Fv-btn>\n    \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\n    export default {\n        data() {\n            return {\n                mounted: false\n            }\n        },\n        mounted() {\n            \u002F\u002F Set mounted to true when the component is mounted\n            this.mounted = true\n        }\n    }\n\u003C\u002Fscript>\n",[19,9975,9976,9985,9990,10022,10042,10059,10076,10084,10092,10096,10105,10115,10124,10131,10141,10145,10149,10158,10163,10174,10178,10182],{"__ignoreMap":50},[146,9977,9978,9980,9983],{"class":148,"line":149},[146,9979,5165],{"class":160},[146,9981,9982],{"class":1549},"template",[146,9984,5183],{"class":160},[146,9986,9987],{"class":148,"line":185},[146,9988,9989],{"class":3874},"    \u003C!-- Teleport the button to the #app-bar-actions area when mounted -->\n",[146,9991,9992,9994,9997,10000,10002,10004,10007,10009,10012,10014,10016,10018,10020],{"class":148,"line":221},[146,9993,5188],{"class":160},[146,9995,9996],{"class":1549},"Teleport",[146,9998,9999],{"class":152}," v-if",[146,10001,161],{"class":160},[146,10003,727],{"class":160},[146,10005,10006],{"class":1554},"mounted",[146,10008,727],{"class":160},[146,10010,10011],{"class":152}," to",[146,10013,161],{"class":160},[146,10015,727],{"class":160},[146,10017,9875],{"class":1554},[146,10019,727],{"class":160},[146,10021,5183],{"class":160},[146,10023,10024,10027,10030,10033,10036,10038,10040],{"class":148,"line":250},[146,10025,10026],{"class":160},"        \u003C",[146,10028,10029],{"class":1549},"v-btn",[146,10031,10032],{"class":160},">",[146,10034,10035],{"class":156},"Button 1",[146,10037,5247],{"class":160},[146,10039,10029],{"class":1549},[146,10041,5183],{"class":160},[146,10043,10044,10046,10048,10050,10053,10055,10057],{"class":148,"line":257},[146,10045,10026],{"class":160},[146,10047,10029],{"class":1549},[146,10049,10032],{"class":160},[146,10051,10052],{"class":156},"Button 2",[146,10054,5247],{"class":160},[146,10056,10029],{"class":1549},[146,10058,5183],{"class":160},[146,10060,10061,10063,10065,10067,10070,10072,10074],{"class":148,"line":284},[146,10062,10026],{"class":160},[146,10064,10029],{"class":1549},[146,10066,10032],{"class":160},[146,10068,10069],{"class":156},"Button 3",[146,10071,5247],{"class":160},[146,10073,10029],{"class":1549},[146,10075,5183],{"class":160},[146,10077,10078,10080,10082],{"class":148,"line":666},[146,10079,5238],{"class":160},[146,10081,9996],{"class":1549},[146,10083,5183],{"class":160},[146,10085,10086,10088,10090],{"class":148,"line":1603},[146,10087,5247],{"class":160},[146,10089,9982],{"class":1549},[146,10091,5183],{"class":160},[146,10093,10094],{"class":148,"line":1608},[146,10095,254],{"emptyLinePlaceholder":253},[146,10097,10098,10100,10103],{"class":148,"line":1624},[146,10099,5165],{"class":160},[146,10101,10102],{"class":1549},"script",[146,10104,5183],{"class":160},[146,10106,10107,10110,10113],{"class":148,"line":2546},[146,10108,10109],{"class":287},"    export",[146,10111,10112],{"class":287}," default",[146,10114,1517],{"class":160},[146,10116,10117,10120,10122],{"class":148,"line":2564},[146,10118,10119],{"class":1549},"        data",[146,10121,1461],{"class":160},[146,10123,1517],{"class":160},[146,10125,10126,10129],{"class":148,"line":2569},[146,10127,10128],{"class":287},"            return",[146,10130,1517],{"class":160},[146,10132,10133,10136,10138],{"class":148,"line":2574},[146,10134,10135],{"class":1549},"                mounted",[146,10137,730],{"class":160},[146,10139,10140],{"class":4671}," false\n",[146,10142,10143],{"class":148,"line":2594},[146,10144,4450],{"class":160},[146,10146,10147],{"class":148,"line":2614},[146,10148,4203],{"class":160},[146,10150,10151,10154,10156],{"class":148,"line":2633},[146,10152,10153],{"class":1549},"        mounted",[146,10155,1461],{"class":160},[146,10157,1517],{"class":160},[146,10159,10160],{"class":148,"line":2662},[146,10161,10162],{"class":3874},"            \u002F\u002F Set mounted to true when the component is mounted\n",[146,10164,10165,10168,10170,10172],{"class":148,"line":2667},[146,10166,10167],{"class":160},"            this.",[146,10169,10006],{"class":156},[146,10171,1539],{"class":160},[146,10173,1979],{"class":4671},[146,10175,10176],{"class":148,"line":2672},[146,10177,4618],{"class":160},[146,10179,10180],{"class":148,"line":2692},[146,10181,3412],{"class":160},[146,10183,10184,10186,10188],{"class":148,"line":2705},[146,10185,5247],{"class":160},[146,10187,10102],{"class":1549},[146,10189,5183],{"class":160},[326,10191,10192],{"start":257},[103,10193,10194,10195,10198],{},"Next, you can customize further by adding more buttons or different elements inside the ",[19,10196,10197],{},"\u003CTeleport>"," element.",[15,10200,10201,10206],{},[392,10202],{"alt":10203,"dataZoomable":50,"src":10204,"title":10205},"\"Screenshot of Dashboard showing the added buttons in the header\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fbutton-added-dashboard.png","Screenshot of Dashboard showing the added buttons in the header",[397,10207,10205],{},[4987,10209,10211],{"id":10210},"example-adding-logo","Example: Adding Logo",[15,10213,10214,10215],{},"If you want to add your brand's logo, you can replace the element inside ",[10216,10217,10218,10219,10221],"teleport",{}," with an ",[392,10220],{}," tag. You can do this in the same ui-template widget or in a different ui-template widget:",[326,10223,10224,10228,10231,10234],{},[103,10225,4995,10226,1340],{},[19,10227,5152],{},[103,10229,10230],{},"Select the correct scope for that widget to render.",[103,10232,10233],{},"Select the correct page or UI in which you want to render the element.",[103,10235,10236,10237,10239,10240],{},"Paste the same Vue snippet given in the above section into the ",[19,10238,5152],{}," widget and replace the code inside ",[10216,10241,10242],{}," with the following element:",[42,10244,10246],{"className":5156,"code":10245,"language":5158,"meta":50,"style":50},"\u003Cimg height=\"32px\" src=\"https:\u002F\u002Fapp.flowfuse.com\u002Fff-logo--wordmark-caps--dark.png\">\u003C\u002Fimg>\n",[19,10247,10248],{"__ignoreMap":50},[146,10249,10250,10252,10254,10257,10259,10261,10264,10266,10269,10271,10273,10276,10278,10281,10283],{"class":148,"line":149},[146,10251,5165],{"class":160},[146,10253,392],{"class":1549},[146,10255,10256],{"class":152}," height",[146,10258,161],{"class":160},[146,10260,727],{"class":160},[146,10262,10263],{"class":1554},"32px",[146,10265,727],{"class":160},[146,10267,10268],{"class":152}," src",[146,10270,161],{"class":160},[146,10272,727],{"class":160},[146,10274,10275],{"class":1554},"https:\u002F\u002Fapp.flowfuse.com\u002Fff-logo--wordmark-caps--dark.png",[146,10277,727],{"class":160},[146,10279,10280],{"class":160},">\u003C\u002F",[146,10282,392],{"class":1549},[146,10284,5183],{"class":160},[15,10286,10287,10288,10290,10291,167],{},"You can replace the URL with your logo's URL or set it using the ",[19,10289,382],{}," as shown in examples given ",[307,10292,10295],{"href":10293,"rel":10294},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fnodes\u002Fwidgets\u002Fui-template.html#page-name-app-bar-title",[311],"documentation",[15,10297,10298,10303],{},[392,10299],{"alt":10300,"dataZoomable":50,"src":10301,"title":10302},"\"Screenshot of the Dashboard displaying the added logo in the header\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Flogo-added-dashboard.png","Screenshot of the Dashboard displaying the added logo in the header",[397,10304,10302],{},[996,10306,10308],{"id":10307},"right-side-of-the-header","Right Side of the Header",[15,10310,10311,10312,10315],{},"To render elements on the right side of the header, you can use the empty div element having the ",[19,10313,10314],{},"#app-bar-actions"," ID, in which we can add elements.",[15,10317,10318,10323],{},[392,10319],{"alt":10320,"dataZoomable":50,"src":10321,"title":10322},"\"Screenshot of Dashboard showing the #app-bar-actions container\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fright-side-area.png","Screenshot of Dashboard showing the #app-bar-actions container",[397,10324,10322],{},[4987,10326,10328],{"id":10327},"example-adding-logged-in-user-profile","Example: Adding logged in user profile",[15,10330,10331,10336],{},[392,10332],{"alt":10333,"dataZoomable":50,"src":10334,"title":10335},"\"Screenshot of Dashboard displaying the logged in user profile at the right side of header\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fuser-profile.png","Screenshot of Dashboard displaying the logged in user profile at the right side of header",[397,10337,10335],{},[15,10339,10340,10341,10345,10346,10349,10350,10355,10356,167],{},"In this section, we will add the user profile of the currently logged-in user to the right side of the header. Make sure you have installed \"@flowfuse\u002Fnode-red-dashboard-2-user-addon\" via the palette manager and enabled ",[307,10342,10344],{"href":10343},"\u002Fdocs\u002Fuser\u002Finstance-settings\u002F#flowfuse-user-authentication","FlowFuse User Authentication",". Each message emitted by the FlowFuse Dashboard widget will include the logged-in user information under ",[19,10347,10348],{},"msg._client.user",". Additionally the ",[307,10351,10354],{"href":10352,"rel":10353},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fcontributing\u002Fguides\u002Fstate-management.html#setup-store",[311],"setup object"," will also contain this information under ",[19,10357,10358],{},"setup.socketio.auth.user",[326,10360,10361,10364,10366,10368],{},[103,10362,10363],{},"Drag the ui-template widget onto the canvas.",[103,10365,10230],{},[103,10367,10233],{},[103,10369,10370,10371,10373],{},"Paste the same Vue snippet given below into the ",[19,10372,5152],{}," widget:",[42,10375,10377],{"className":5156,"code":10376,"language":5158,"meta":50,"style":50},"\u003Ctemplate>\n    \u003C!-- Teleporting user info to #app-bar-actions, which is the ID of the action bars' right corners area -->\n    \u003CTeleport v-if=\"loaded\" to=\"#app-bar-actions\">\n        \u003Cdiv class=\"user-info\">\n            \u003C!-- Displaying user image -->\n            \u003Cimg :src=\"setup.socketio.auth.user.image\" \u002F>\n            \u003C!-- Greeting the user -->\n            \u003Cspan>Hi, {{ setup.socketio.auth.user.name }}\u003C\u002Fspan>\n        \u003C\u002Fdiv>\n    \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n    data() {\n        return {\n            \u002F\u002F Flag to indicate if the component is loaded\n            loaded: false\n        };\n    },\n    mounted() {\n        \u002F\u002F This function is called when the component is inserted into the DOM.\n        \u002F\u002F Setting loaded to true here ensures the component is ready to access #app-bar-actions,\n        \u002F\u002F as it's now part of the same DOM structure.\n        \u002F\u002F Accessing it before mounted() would cause an error because the component wouldn't be initialized in the DOM yet.\n        this.loaded = true; \u002F\u002F Setting loaded to true to indicate that the component has been mounted successfully\n    }\n}\n\u003C\u002Fscript>\n\n\u003Cstyle>\n\u002F* Styling for user info display *\u002F\n.user-info {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\u002F* Styling for user avatar image*\u002F\n.user-info img {\n    width: 24px;\n    height: 24px;\n}\n\u003C\u002Fstyle>\n",[19,10378,10379,10387,10392,10421,10441,10446,10468,10473,10490,10499,10507,10515,10519,10527,10536,10545,10552,10557,10566,10571,10575,10584,10589,10594,10599,10604,10621,10625,10629,10637,10641,10649,10654,10662,10675,10687,10699,10703,10708,10719,10731,10742,10746],{"__ignoreMap":50},[146,10380,10381,10383,10385],{"class":148,"line":149},[146,10382,5165],{"class":160},[146,10384,9982],{"class":1549},[146,10386,5183],{"class":160},[146,10388,10389],{"class":148,"line":185},[146,10390,10391],{"class":3874},"    \u003C!-- Teleporting user info to #app-bar-actions, which is the ID of the action bars' right corners area -->\n",[146,10393,10394,10396,10398,10400,10402,10404,10407,10409,10411,10413,10415,10417,10419],{"class":148,"line":221},[146,10395,5188],{"class":160},[146,10397,9996],{"class":1549},[146,10399,9999],{"class":152},[146,10401,161],{"class":160},[146,10403,727],{"class":160},[146,10405,10406],{"class":1554},"loaded",[146,10408,727],{"class":160},[146,10410,10011],{"class":152},[146,10412,161],{"class":160},[146,10414,727],{"class":160},[146,10416,10314],{"class":1554},[146,10418,727],{"class":160},[146,10420,5183],{"class":160},[146,10422,10423,10425,10427,10430,10432,10434,10437,10439],{"class":148,"line":250},[146,10424,10026],{"class":160},[146,10426,5168],{"class":1549},[146,10428,10429],{"class":152}," class",[146,10431,161],{"class":160},[146,10433,727],{"class":160},[146,10435,10436],{"class":1554},"user-info",[146,10438,727],{"class":160},[146,10440,5183],{"class":160},[146,10442,10443],{"class":148,"line":257},[146,10444,10445],{"class":3874},"            \u003C!-- Displaying user image -->\n",[146,10447,10448,10451,10453,10456,10458,10460,10463,10465],{"class":148,"line":284},[146,10449,10450],{"class":160},"            \u003C",[146,10452,392],{"class":1549},[146,10454,10455],{"class":152}," :src",[146,10457,161],{"class":160},[146,10459,727],{"class":160},[146,10461,10462],{"class":1554},"setup.socketio.auth.user.image",[146,10464,727],{"class":160},[146,10466,10467],{"class":160}," \u002F>\n",[146,10469,10470],{"class":148,"line":666},[146,10471,10472],{"class":3874},"            \u003C!-- Greeting the user -->\n",[146,10474,10475,10477,10479,10481,10484,10486,10488],{"class":148,"line":1603},[146,10476,10450],{"class":160},[146,10478,146],{"class":1549},[146,10480,10032],{"class":160},[146,10482,10483],{"class":156},"Hi, {{ setup.socketio.auth.user.name }}",[146,10485,5247],{"class":160},[146,10487,146],{"class":1549},[146,10489,5183],{"class":160},[146,10491,10492,10495,10497],{"class":148,"line":1608},[146,10493,10494],{"class":160},"        \u003C\u002F",[146,10496,5168],{"class":1549},[146,10498,5183],{"class":160},[146,10500,10501,10503,10505],{"class":148,"line":1624},[146,10502,5238],{"class":160},[146,10504,9996],{"class":1549},[146,10506,5183],{"class":160},[146,10508,10509,10511,10513],{"class":148,"line":2546},[146,10510,5247],{"class":160},[146,10512,9982],{"class":1549},[146,10514,5183],{"class":160},[146,10516,10517],{"class":148,"line":2564},[146,10518,254],{"emptyLinePlaceholder":253},[146,10520,10521,10523,10525],{"class":148,"line":2569},[146,10522,5165],{"class":160},[146,10524,10102],{"class":1549},[146,10526,5183],{"class":160},[146,10528,10529,10532,10534],{"class":148,"line":2574},[146,10530,10531],{"class":287},"export",[146,10533,10112],{"class":287},[146,10535,1517],{"class":160},[146,10537,10538,10541,10543],{"class":148,"line":2594},[146,10539,10540],{"class":1549},"    data",[146,10542,1461],{"class":160},[146,10544,1517],{"class":160},[146,10546,10547,10550],{"class":148,"line":2614},[146,10548,10549],{"class":287},"        return",[146,10551,1517],{"class":160},[146,10553,10554],{"class":148,"line":2633},[146,10555,10556],{"class":3874},"            \u002F\u002F Flag to indicate if the component is loaded\n",[146,10558,10559,10562,10564],{"class":148,"line":2662},[146,10560,10561],{"class":1549},"            loaded",[146,10563,730],{"class":160},[146,10565,10140],{"class":4671},[146,10567,10568],{"class":148,"line":2667},[146,10569,10570],{"class":160},"        };\n",[146,10572,10573],{"class":148,"line":2672},[146,10574,2520],{"class":160},[146,10576,10577,10580,10582],{"class":148,"line":2692},[146,10578,10579],{"class":1549},"    mounted",[146,10581,1461],{"class":160},[146,10583,1517],{"class":160},[146,10585,10586],{"class":148,"line":2705},[146,10587,10588],{"class":3874},"        \u002F\u002F This function is called when the component is inserted into the DOM.\n",[146,10590,10591],{"class":148,"line":2722},[146,10592,10593],{"class":3874},"        \u002F\u002F Setting loaded to true here ensures the component is ready to access #app-bar-actions,\n",[146,10595,10596],{"class":148,"line":2787},[146,10597,10598],{"class":3874},"        \u002F\u002F as it's now part of the same DOM structure.\n",[146,10600,10601],{"class":148,"line":2801},[146,10602,10603],{"class":3874},"        \u002F\u002F Accessing it before mounted() would cause an error because the component wouldn't be initialized in the DOM yet.\n",[146,10605,10606,10609,10611,10613,10615,10618],{"class":148,"line":2861},[146,10607,10608],{"class":160},"        this.",[146,10610,10406],{"class":156},[146,10612,1539],{"class":160},[146,10614,1866],{"class":4671},[146,10616,10617],{"class":160},";",[146,10619,10620],{"class":3874}," \u002F\u002F Setting loaded to true to indicate that the component has been mounted successfully\n",[146,10622,10623],{"class":148,"line":2920},[146,10624,3412],{"class":160},[146,10626,10627],{"class":148,"line":2978},[146,10628,754],{"class":160},[146,10630,10631,10633,10635],{"class":148,"line":3034},[146,10632,5247],{"class":160},[146,10634,10102],{"class":1549},[146,10636,5183],{"class":160},[146,10638,10639],{"class":148,"line":3091},[146,10640,254],{"emptyLinePlaceholder":253},[146,10642,10643,10645,10647],{"class":148,"line":3148},[146,10644,5165],{"class":160},[146,10646,924],{"class":1549},[146,10648,5183],{"class":160},[146,10650,10651],{"class":148,"line":3204},[146,10652,10653],{"class":3874},"\u002F* Styling for user info display *\u002F\n",[146,10655,10656,10658,10660],{"class":148,"line":3210},[146,10657,167],{"class":160},[146,10659,10436],{"class":2435},[146,10661,1517],{"class":160},[146,10663,10664,10668,10670,10673],{"class":148,"line":3216},[146,10665,10667],{"class":10666},"sqsOY","    display",[146,10669,730],{"class":160},[146,10671,10672],{"class":156}," flex",[146,10674,182],{"class":160},[146,10676,10677,10680,10682,10685],{"class":148,"line":3221},[146,10678,10679],{"class":10666},"    align-items",[146,10681,730],{"class":160},[146,10683,10684],{"class":156}," center",[146,10686,182],{"class":160},[146,10688,10689,10692,10694,10697],{"class":148,"line":3226},[146,10690,10691],{"class":10666},"    gap",[146,10693,730],{"class":160},[146,10695,10696],{"class":206}," 8px",[146,10698,182],{"class":160},[146,10700,10701],{"class":148,"line":3246},[146,10702,754],{"class":160},[146,10704,10705],{"class":148,"line":3263},[146,10706,10707],{"class":3874},"\u002F* Styling for user avatar image*\u002F\n",[146,10709,10710,10712,10714,10717],{"class":148,"line":3283},[146,10711,167],{"class":160},[146,10713,10436],{"class":2435},[146,10715,10716],{"class":2435}," img",[146,10718,1517],{"class":160},[146,10720,10721,10724,10726,10729],{"class":148,"line":3312},[146,10722,10723],{"class":10666},"    width",[146,10725,730],{"class":160},[146,10727,10728],{"class":206}," 24px",[146,10730,182],{"class":160},[146,10732,10733,10736,10738,10740],{"class":148,"line":3317},[146,10734,10735],{"class":10666},"    height",[146,10737,730],{"class":160},[146,10739,10728],{"class":206},[146,10741,182],{"class":160},[146,10743,10744],{"class":148,"line":3322},[146,10745,754],{"class":160},[146,10747,10748,10750,10752],{"class":148,"line":3342},[146,10749,5247],{"class":160},[146,10751,924],{"class":1549},[146,10753,5183],{"class":160},[15,10755,10756,10757,10761,10762,167],{},"For detailed guide on this section, refer to the guide on ",[307,10758,10760],{"href":10759},"\u002Fblog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard\u002F","Displaying logged in user on FlowFuse Dashboard",". Furthermore, if you want to add logos or buttons on the right side similar to the left side of the header, you just need to replace the to attribute with the ",[19,10763,10314],{},[996,10765,10767],{"id":10766},"centering-header-items","Centering Header Items",[15,10769,10770,10771,10773,10774,10776],{},"Sometimes you may want to center or position items added to either the ",[19,10772,9875],{}," or the ",[19,10775,10314],{},". By default, these elements do not have a specified width, and when you add items into them, they grow to fit their content. To center the elements, you first need to ensure that they are sized appropriately.",[4987,10778,10780],{"id":10779},"centering-items-in-the-left-side-of-the-header","Centering Items in the Left Side of the Header",[15,10782,10783,10784,10786,10787,10790,10791,10373],{},"To center items added to the ",[19,10785,9875],{},", apply the following CSS in the ",[19,10788,10789],{},"\u003Cstyle>"," tag of the ",[19,10792,5152],{},[42,10794,10798],{"className":10795,"code":10796,"language":10797,"meta":50,"style":50},"language-css shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","#app-bar-title {\n    flex-grow: 1;\n    justify-content: center;\n}\n","css",[19,10799,10800,10809,10820,10831],{"__ignoreMap":50},[146,10801,10802,10804,10807],{"class":148,"line":149},[146,10803,6700],{"class":160},[146,10805,10806],{"class":206},"app-bar-title",[146,10808,1517],{"class":160},[146,10810,10811,10814,10816,10818],{"class":148,"line":185},[146,10812,10813],{"class":10666},"    flex-grow",[146,10815,730],{"class":160},[146,10817,1964],{"class":206},[146,10819,182],{"class":160},[146,10821,10822,10825,10827,10829],{"class":148,"line":221},[146,10823,10824],{"class":10666},"    justify-content",[146,10826,730],{"class":160},[146,10828,10684],{"class":156},[146,10830,182],{"class":160},[146,10832,10833],{"class":148,"line":250},[146,10834,754],{"class":160},[4987,10836,10838],{"id":10837},"centering-items-in-the-right-side-of-the-header","Centering Items in the Right Side of the Header",[15,10840,10841,10842,10844,10845,10790,10847,10373],{},"To center items in the ",[19,10843,10314],{}," area, add the following CSS to the ",[19,10846,10789],{},[19,10848,5152],{},[42,10850,10852],{"className":10795,"code":10851,"language":10797,"meta":50,"style":50},"#v-toolbar__append {\n    flex-grow: 1;\n}\n",[19,10853,10854,10863,10873],{"__ignoreMap":50},[146,10855,10856,10858,10861],{"class":148,"line":149},[146,10857,6700],{"class":160},[146,10859,10860],{"class":206},"v-toolbar__append",[146,10862,1517],{"class":160},[146,10864,10865,10867,10869,10871],{"class":148,"line":185},[146,10866,10813],{"class":10666},[146,10868,730],{"class":160},[146,10870,1964],{"class":206},[146,10872,182],{"class":160},[146,10874,10875],{"class":148,"line":221},[146,10876,754],{"class":160},[34,10878,10880],{"id":10879},"styling-header","Styling Header",[15,10882,10883],{},"One of the significant customization features we've added recently is the ability to style the header in different ways.",[15,10885,10886],{},"To style the header:",[326,10888,10889,10891],{},[103,10890,9900],{},[103,10892,9903],{},[15,10894,10895,10897],{},[392,10896],{"alt":9908,"dataZoomable":50,"src":9909,"title":9910},[397,10898,9910],{},[326,10900,10901],{"start":221},[103,10902,10903],{},"Select the desired option from the \"Header Options\" dropdown.",[15,10905,10906,10911],{},[392,10907],{"alt":10908,"dataZoomable":50,"src":10909,"title":10910},"\"Screenshot showing the header style options in the dashboard settings\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fheader-style-options.png","Screenshot showing the header style options in the dashboard settings",[397,10912,10910],{},[15,10914,10915],{},"The following options are available for header styling:",[996,10917,10919],{"id":10918},"default","Default",[15,10921,10922],{},"This option as it name suggest it is the default option set for header. In which the header will get hidden if we scrolled down.",[15,10924,10925,10930],{},[392,10926],{"alt":10927,"dataZoomable":50,"src":10928,"title":10929},"\"Image showing the dashboard with default header\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fdefault-header.gif","Image showing the dashboard with default header",[397,10931,10929],{},[996,10933,10935],{"id":10934},"hidden","Hidden",[15,10937,10938],{},"Selecting this option completely hides the header, allowing you to use that space for other purposes.",[15,10940,10941,10946],{},[392,10942],{"alt":10943,"dataZoomable":50,"src":10944,"title":10945},"\"Image showing the dashboard with hidden header\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fhidden-header.png","Image showing the dashboard with hidden header",[397,10947,10945],{},[996,10949,10951],{"id":10950},"fixed","Fixed",[15,10953,10954],{},"Selecting this option keeps the header fixed at the top. This means that when you scroll the page down, the header will remain visible.",[15,10956,10957,10962],{},[392,10958],{"alt":10959,"dataZoomable":50,"src":10960,"title":10961},"\"Image showing the dashboard with fixed header\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Ffixed-header.gif","Image showing the dashboard with fixed header",[397,10963,10961],{},[34,10965,10967],{"id":10966},"changing-dashboard-theme","Changing Dashboard Theme",[15,10969,10970],{},"In this section of the guide, you will learn how to change the Dashboard theme, where you can adjust the colors of the header, navigation sidebar, group and page backgrounds, the border color of groups, and the padding, sizing, and gaps between pages, groups, and widgets.",[15,10972,10973],{},"To edit the existing theme:",[326,10975,10976,10979],{},[103,10977,10978],{},"Go to the FlowFuse Dashboard sidebar.",[103,10980,10981],{},"Switch to the \"Theme\" tab.",[15,10983,10984,10989],{},[392,10985],{"alt":10986,"dataZoomable":50,"src":10987,"title":10988},"\"Screenshot showing the dashboard theme tab in the sidebar\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fdashboard-theme-tab.png","Screenshot showing the dashboard theme tab in the sidebar",[397,10990,10988],{},[326,10992,10993,10996],{"start":221},[103,10994,10995],{},"Click on the edit button next to the theme.",[103,10997,10998],{},"You can adjust the header color and the primary color (which applies to the navigation sidebar and elements like buttons and dropdowns) under the \"Primary\" section. In the \"Pages\" section, set the background color for pages, and in the \"Groups\" section, adjust the background color and border color of groups.",[15,11000,11001,11006],{},[392,11002],{"alt":11003,"dataZoomable":50,"src":11004,"title":11005},"\"Screenshot showing the theme properties dialog\"","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fdashboard-theme-settings.png","Screenshot showing the theme properties dialog",[397,11007,11005],{},[326,11009,11010],{"start":257},[103,11011,11012],{},"Under \"Sizing,\" adjust the page padding (the space between dashboard groups), the page border, group gap, group border radius (the thickness of the group border), and widget gap.",[15,11014,11015,11016,11020],{},"For more information on  theme, how to add new themes, and set themes for pages, refer to the ",[307,11017,11019],{"href":11018},"\u002Fblog\u002F2024\u002F05\u002Fnode-red-dashboard-2-layout-navigation-styling\u002F#understanding-dashboard-2.0-theme","Comprehensive guide: FlowFuse Dashboard layout, sidebar, and styling",". Additionally, this guide covers FlowFuse Dashboard layouts, themes, and custom styling in detail.",[34,11022,6518],{"id":6517},[15,11024,11025],{},"In this article, we explored FlowFuse Dashboard's new customization features. We focused on adding elements like buttons and logos to the header, and discussed styling options such as default, hidden, and fixed for headers. We also covered how to adjust dashboard themes to personalize colors and layout. These insights empower users to create more personalized and functional Node-RED dashboards.",[924,11027,11028],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":50,"searchDepth":250,"depth":250,"links":11030},[11031,11045,11050,11051],{"id":9852,"depth":185,"text":9853,"children":11032},[11033,11038,11041],{"id":9868,"depth":221,"text":9869,"children":11034},[11035,11036,11037],{"id":9888,"depth":250,"text":9889},{"id":9927,"depth":250,"text":9928},{"id":10210,"depth":250,"text":10211},{"id":10307,"depth":221,"text":10308,"children":11039},[11040],{"id":10327,"depth":250,"text":10328},{"id":10766,"depth":221,"text":10767,"children":11042},[11043,11044],{"id":10779,"depth":250,"text":10780},{"id":10837,"depth":250,"text":10838},{"id":10879,"depth":185,"text":10880,"children":11046},[11047,11048,11049],{"id":10918,"depth":221,"text":10919},{"id":10934,"depth":221,"text":10935},{"id":10950,"depth":221,"text":10951},{"id":10966,"depth":185,"text":10967},{"id":6517,"depth":185,"text":6518},{"type":941,"title":11053,"description":11054},"Build Branded Node-RED Dashboards Your Way","FlowFuse gives you everything you need to build, customize, and deploy Node-RED Dashboard applications, with full theming control, multi-user support, and production-ready infrastructure built in.","2024-08-07","Discover the latest enhancements in FlowFuse Dashboard, including customizable headers, themes, and layout modifications to personalize your dashboard experience.","\u002Fblog\u002F2024\u002F08\u002Fimages\u002Fcustomize-theming-flowfuse-dashboard.png",{"keywords":11059,"excerpt":11060},"flowfuse dashboard theming, node-red dashboard customization, dashboard 2.0 header customization, dashboard custom logo, node-red dashboard theme, ui-template teleport, dashboard header styling",{"type":12,"value":11061},[11062],[15,11063,9843],{},"\u002Fblog\u002F2024\u002F08\u002Fcustomise-theming-in-your-dashboards",{"title":9837,"description":11056},{"loc":11064},"blog\u002F2024\u002F08\u002Fcustomise-theming-in-your-dashboards","Customising Headers, Themes, and Layouts in FlowFuse Dashboard",[9832,11070,11071,966],"dashboard","dashboard theming","FlowFuse Dashboard now supports deep visual customization including custom headers, themes, and layout modifications. Using Teleports inside the ui-template node, you can inject custom elements into specific dashboard areas without complex CSS. This guide covers adding header buttons, custom logos, modifying color themes, and adjusting group and page padding.","XH7aPap1yjzKY3O8JrBOUgGKuDkw12tTxVw0VoLsokc",{"id":11075,"title":11076,"authors":11077,"body":11078,"cta":11445,"date":11448,"description":11449,"extension":946,"image":11450,"lastUpdated":948,"meta":11451,"navigation":253,"path":11457,"seo":11458,"sitemap":11459,"stem":11460,"subtitle":11461,"tags":11462,"tldr":11464,"video":3,"__hash__":11465},"blog\u002Fblog\u002F2024\u002F07\u002Fcalling-python-script-from-node-red.md","Calling a Python script from Node-RED (2026)",[10],{"type":12,"value":11079,"toc":11437},[11080,11083,11087,11090,11093,11097,11106,11109,11122,11129,11132,11144,11155,11159,11162,11180,11188,11200,11207,11215,11238,11245,11249,11252,11260,11280,11283,11290,11299,11336,11340,11343,11372,11394,11409,11429,11431,11434],[15,11081,11082],{},"Python's robust data processing capabilities and extensive libraries are well-known in programming. When combined with Node-RED, these technologies can synergize to elevate data analytics and automation. This guide walks you through integrating Python scripts with Node-RED. You'll gain practical insights, troubleshooting tips, and effective techniques for executing scripts, enabling you to leverage this powerful combination for your IoT projects.",[34,11084,11086],{"id":11085},"why-use-python-with-node-red","Why use python with Node-RED",[15,11088,11089],{},"Integrating Python and Node-RED can significantly enhance your IoT and automation initiatives by leveraging their distinct strengths. Node-RED excels in creating easy workflows, efficiently processing data streams, and integrating hardware, APIs, and. Meanwhile, Python offers a rich set of libraries for advanced tasks such as machine learning and AI, pivotal in realizing Industry 4.0 concepts.",[15,11091,11092],{},"This combination allows developers to build robust and flexible solutions. For instance, while Node-RED manages data flow and device communication, Python can perform complex analytics, and predictive modeling, or integrate with AI frameworks. This integration bridges the gap between data collection and actionable insights, enabling systems to make informed decisions autonomously.",[34,11094,11096],{"id":11095},"installing-python","Installing Python",[15,11098,11099,11100,11105],{},"When executing Python scripts, it's essential to have the Python runtime installed on your system. Before proceeding, make sure you have it installed. You can follow the ",[307,11101,11104],{"href":11102,"rel":11103},"https:\u002F\u002Fwiki.python.org\u002Fmoin\u002FBeginnersGuide\u002FDownload",[311],"official guide"," for instructions.",[15,11107,11108],{},"To verify if Python is installed, open your terminal and execute:",[42,11110,11112],{"className":7487,"code":11111,"language":7489,"meta":50,"style":50},"python --version\n",[19,11113,11114],{"__ignoreMap":50},[146,11115,11116,11119],{"class":148,"line":149},[146,11117,11118],{"class":2435},"python",[146,11120,11121],{"class":1554}," --version\n",[15,11123,11124],{},[392,11125],{"alt":11126,"dataZoomable":50,"src":11127,"title":11128},"\"Screenshot of terminal showing the python version installed, conforming it is installed\"","\u002Fblog\u002F2024\u002F07\u002Fimages\u002Fcalling-python-script-from-node-red-py-conformation.png","Screenshot of terminal showing the python version installed, conforming it is installed",[15,11130,11131],{},"The above command displays the version of Python installed on your system as shown in the above image. If the above command doesn't work, try:",[42,11133,11135],{"className":7487,"code":11134,"language":7489,"meta":50,"style":50},"python3 --version\n",[19,11136,11137],{"__ignoreMap":50},[146,11138,11139,11142],{"class":148,"line":149},[146,11140,11141],{"class":2435},"python3",[146,11143,11121],{"class":1554},[15,11145,11146,11147,11150,11151,11154],{},"The specific command to use depends on how Python was installed and configured on your system. However, make sure to use ",[19,11148,11149],{},"python \u003Cfilename>.py"," if the first command works, or ",[19,11152,11153],{},"python3 \u003Cfilename>.py"," if the second command works, while executing Python scripts.",[34,11156,11158],{"id":11157},"executing-python-script-from-node-red","Executing Python Script from Node-RED",[15,11160,11161],{},"Let's now see how to call a Python script from Node-RED. First, we'll create a basic script file that contains a function to print text in the console based on input. Currently, the function uses hardcoded input. To create this file using Node-RED, import the following flow, deploy it, and press the inject button:",[15,11163,6389,11164,3830],{},[146,11165,11166,11167,11169,11170,11172,11173,11175,11176,11179],{},"{\"id\":\"b9d7d6aff0016631\",\"type\":\"inject\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"props\":",[146,11168,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":240,\"y\":100,\"wires\":[[\"2e1daccf2a7b3d0f\"]]},{\"id\":\"d2d1450deaa588f4\",\"type\":\"file\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"filename\":\".\\example.py\",\"filenameType\":\"str\",\"appendNewline\":true,\"createDir\":false,\"overwriteFile\":\"true\",\"encoding\":\"none\",\"x\":570,\"y\":100,\"wires\":[[\"e140a8508fb10d96\"]]},{\"id\":\"e140a8508fb10d96\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":760,\"y\":100,\"wires\":",[146,11171],{},"},{\"id\":\"2e1daccf2a7b3d0f\",\"type\":\"template\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"field\":\"payload\",\"fieldType\":\"msg\",\"format\":\"handlebars\",\"syntax\":\"mustache\",\"template\":\"def main():\\n    # Hardcoded value\\n    user_input = 20  \\n    \\n    # Check if the input is numeric\\n    if isinstance(user_input, int) or (isinstance(user_input, str) and user_input.isdigit()):\\n        number = int(user_input) if isinstance(user_input, str) else user_input\\n        \\n        # Conditionally render based on the input value\\n        if number \u003C 0:\\n            print(\"Negative number entered\")\\n        elif number == 0:\\n            print(\"Zero entered\")\\n        else:\\n            print(\"Positive number entered\")\\n    else:\\n        print(\"Invalid input. Please enter a valid number.\")\\n\\nif ",[338,11174,8309],{}," == \"",[338,11177,11178],{},"main","\":\\n    main()\\n\",\"output\":\"str\",\"x\":400,\"y\":100,\"wires\":[[\"d2d1450deaa588f4\"]]}",[15,11181,11182,11183,11187],{},"Now, let's execute this Python script from Node-RED. To do that, we will use Node-RED's ",[307,11184,11186],{"href":11185},"\u002Fnode-red\u002Fcore-nodes\u002Fexec\u002F","Exec"," node, which allows running commands on your system.",[326,11189,11190,11193],{},[103,11191,11192],{},"Drag an Inject node onto the canvas.",[103,11194,11195,11196,11199],{},"Drag an Exec node onto the canvas and Configure the command to ",[19,11197,11198],{},"python .\u002Fexample.py -u",". The -u flag prevents potential output buffering issues when executing Python scripts via exec.",[15,11201,11202],{},[392,11203],{"alt":11204,"dataZoomable":50,"src":11205,"title":11206},"\"Screenshot of the Exec node executing python file\"","\u002Fblog\u002F2024\u002F07\u002Fimages\u002Fcalling-python-scrpt-from-node-red-exec-node.png","Screenshot of the Exec node executing python file",[326,11208,11209,11212],{"start":221},[103,11210,11211],{},"Drag a Debug node onto the canvas.",[103,11213,11214],{},"Connect the output of the Inject node to the input of the Exec node, and the output of the Exec node to the input of Debug node.",[15,11216,6389,11217,3830],{},[146,11218,11219,11220,276,11223,11226,11227,11229,11230,11232,11233,11235,11236,1596],{},"{\"id\":\"2e26b84c0ce17312\",\"type\":\"exec\",\"z\":\"FFF0000000000001\",\"command\":\"python .\u002Fexample.py -u\",\"addpay\":\"\",\"append\":\"\",\"useSpawn\":\"false\",\"timer\":\"\",\"winHide\":false,\"oldrc\":false,\"name\":\"\",\"x\":460,\"y\":300,\"wires\":[[\"89589c56117004e0\"],",[146,11221,11222],{},"\"7fdb901b144749c2\"",[146,11224,11225],{},"\"3f49b49308941782\"","]},{\"id\":\"739e08c1ec77c2a1\",\"type\":\"inject\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"props\":",[146,11228,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":240,\"y\":300,\"wires\":[[\"2e26b84c0ce17312\"]]},{\"id\":\"89589c56117004e0\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"Output\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":690,\"y\":260,\"wires\":",[146,11231],{},"},{\"id\":\"7fdb901b144749c2\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"Error\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":690,\"y\":300,\"wires\":",[146,11234],{},"},{\"id\":\"3f49b49308941782\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"Return code\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":710,\"y\":340,\"wires\":",[146,11237],{},[15,11239,11240,11241,11244],{},"Now, when you deploy this flow and click on the inject node to execute the file, you should see the text 'Positive number entered' and ",[19,11242,11243],{},"{ code: 0 }",", which indicates your script has been successfully executed.",[34,11246,11248],{"id":11247},"reading-temperature-sensor-using-python-script","Reading Temperature Sensor using Python script",[15,11250,11251],{},"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.",[15,11253,11254,11255,11259],{},"Before proceeding, ensure that Node-RED is running on a device connected to a temperature sensor. For detailed instructions, refer to ",[307,11256,11258],{"href":11257},"\u002Fnode-red\u002Fhardware\u002F","Setting Up Node-RED on Different Hardware",", In this case, we are running Node-RED on a Raspberry Pi 5 with a DHT11 sensor connected to it.",[326,11261,11262,11265,11271,11274,11277],{},[103,11263,11264],{},"Drag an Inject node onto the canvas, and set repeat to 1 seconds of interval.",[103,11266,11267,11268,11270],{},"Drag an Exec node and set the path to ",[19,11269,11149],{},", 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.",[103,11272,11273],{},"Drag the JSON node onto the canvas and set the action to \"Always convert to JSON object\".",[103,11275,11276],{},"Drag the Debug node onto the canvas.",[103,11278,11279],{},"Connect the output of the Inject node to the input of the Exec node and output of the Exec node to the input of the JSON node, and finally the JSON node's output to the input of the Debug node.",[15,11281,11282],{},"Below is the complete flow which creates the Python file to read the DHT11 sensor and executes that file after 1 second of interval. After deploying the flow you should able to see the sensor data on the debug sidebar as shown in the below image.",[15,11284,11285],{},[392,11286],{"alt":11287,"dataZoomable":50,"src":11288,"title":11289},"\"Image showing the Node-RED flow executing the python script that reads the sensor data\"","\u002Fblog\u002F2024\u002F07\u002Fimages\u002Fcalling-python-scrpt-from-node-red-output.gif","Image showing the Node-RED flow executing the python script that reads the sensor data",[15,11291,11292,11293,11298],{},"Note: The Python script uses the ",[307,11294,11297],{"href":11295,"rel":11296},"https:\u002F\u002Fdocs.circuitpython.org\u002Fprojects\u002Fdht\u002Fen\u002Flatest\u002Findex.html",[311],"adafruit-circuitpython"," to read the sensor data so make sure to install it. Additionally, the code contained in the template node in the following flow considers that your sensor's signal pin is connected to GPIO 4.",[15,11300,6389,11301,3830],{},[146,11302,11303,11304,11306,11307,11309,11310,11312,11313,11315,11316,11318,11319,11322,11323,276,11325,11327,11328,11330,11331,11333,11334,1596],{},"{\"id\":\"94bc6fa766c4b397\",\"type\":\"file\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"filename\":\"~\u002Fsensor.py\",\"filenameType\":\"str\",\"appendNewline\":false,\"createDir\":false,\"overwriteFile\":\"true\",\"encoding\":\"none\",\"x\":610,\"y\":520,\"wires\":[[\"9b08eee57f666de5\"]]},{\"id\":\"37453becdf842bd7\",\"type\":\"template\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"field\":\"payload\",\"fieldType\":\"msg\",\"format\":\"handlebars\",\"syntax\":\"mustache\",\"template\":\"import time\\nimport board\\nimport adafruit_dht\\nimport json\\n\\ndef publish():\\n    dhtDevice = adafruit_dht.DHT11(board.D4)\\n    try:\\n        temperature_c = dhtDevice.temperature\\n        humidity = dhtDevice.humidity\\n\\n        # Create JSON object\\n        data = {\\n            \"temperature_c\": temperature_c,\\n            \"humidity\": humidity\\n        }\\n\\n        # Convert JSON object to string and print\\n        print(json.dumps(data))\\n\\n    except RuntimeError as error:\\n        print(error.args",[146,11305,677],{},")\\n    except Exception as error:\\n        dhtDevice.exit()\\n        raise error\\n    finally:\\n        dhtDevice.exit()\\n\\ndef run():\\n    publish()\\n\\nif ",[338,11308,8309],{}," == '",[338,11311,11178],{},"':\\n    run()\\n\",\"output\":\"str\",\"x\":380,\"y\":520,\"wires\":[[\"94bc6fa766c4b397\"]]},{\"id\":\"5b3642d39c122576\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":880,\"y\":660,\"wires\":",[146,11314],{},"},{\"id\":\"88144cbc887aada9\",\"type\":\"inject\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"props\":",[146,11317],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":150,\"y\":660,\"wires\":[[\"c896267214914641\"]]},{\"id\":\"1b1d792011ffac2c\",\"type\":\"inject\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"props\":",[146,11320,11321],{},"{\"p\":\"kill\",\"v\":\"g\",\"vt\":\"str\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":150,\"y\":520,\"wires\":[[\"37453becdf842bd7\"]]},{\"id\":\"c896267214914641\",\"type\":\"exec\",\"z\":\"FFF0000000000001\",\"command\":\"python ~\u002Fsensor.py -u\",\"addpay\":\"payload\",\"append\":\"\",\"useSpawn\":\"false\",\"timer\":\"5\",\"winHide\":false,\"oldrc\":false,\"name\":\"\",\"x\":400,\"y\":660,\"wires\":[[\"1d02a33a018f0f8d\"],",[146,11324],{},[146,11326],{},"]},{\"id\":\"1d02a33a018f0f8d\",\"type\":\"json\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"property\":\"payload\",\"action\":\"\",\"pretty\":false,\"x\":650,\"y\":660,\"wires\":[[\"5b3642d39c122576\"]]},{\"id\":\"9b08eee57f666de5\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"debug 3\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":880,\"y\":520,\"wires\":",[146,11329],{},"},{\"id\":\"343a9f704951f3ed\",\"type\":\"comment\",\"z\":\"FFF0000000000001\",\"name\":\"Create python file that reads the sensor data\",\"info\":\"\",\"x\":510,\"y\":440,\"wires\":",[146,11332],{},"},{\"id\":\"216eb1333b2c264c\",\"type\":\"comment\",\"z\":\"FFF0000000000001\",\"name\":\"Execute the python file that read the data\",\"info\":\"\",\"x\":500,\"y\":580,\"wires\":",[146,11335],{},[34,11337,11339],{"id":11338},"executing-python-script-with-arguments-from-node-red","Executing Python Script with Arguments from Node-RED",[15,11341,11342],{},"Now, let's revisit our first example. In that example, we executed a simple Python file with a hardcoded value. Now, we'll learn how to pass arguments or inputs to the Python script when executing from Node-RED. For this, we'll need to update the file. Import the following flow, deploy it, and click on the inject button to create the file.",[15,11344,6389,11345,3830],{},[146,11346,11166,11347,11349,11350,11352,11353],{},[146,11348,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":300,\"y\":440,\"wires\":[[\"2e1daccf2a7b3d0f\"]]},{\"id\":\"d2d1450deaa588f4\",\"type\":\"file\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"filename\":\".\u002Fexample.py\",\"filenameType\":\"str\",\"appendNewline\":true,\"createDir\":false,\"overwriteFile\":\"true\",\"encoding\":\"none\",\"x\":630,\"y\":440,\"wires\":[[\"e140a8508fb10d96\"]]},{\"id\":\"e140a8508fb10d96\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":820,\"y\":440,\"wires\":",[146,11351],{},"},{\"id\":\"2e1daccf2a7b3d0f\",\"type\":\"template\",\"z\":\"FFF0000000000001\",\"name\":\"\",\"field\":\"payload\",\"fieldType\":\"msg\",\"format\":\"handlebars\",\"syntax\":\"mustache\",\"template\":\"import sys\\n\\ndef main():\\n    if len(sys.argv) != 2:\\n        print(\"Usage: python your_script.py ",[11354,11355,11356,11357,11359,11360,11362,11363,11366,11367,11175,11369,11371],"number",{},"\")\\n        return\\n    \\n    user_input = sys.argv",[146,11358,62],{},"\\n    \\n    # Check if the input is numeric\\n    if user_input.isdigit() or (user_input",[146,11361,677],{}," == '-' and user_input",[146,11364,11365],{},"1:",".isdigit()):\\n        number = int(user_input)\\n        \\n        # Conditionally render based on the input value\\n        if number \u003C 0:\\n            print(\"Negative number entered\")\\n        elif number == 0:\\n            print(\"Zero entered\")\\n        else:\\n            print(\"Positive number entered\")\\n    else:\\n        print(\"Invalid input. Please enter a valid number.\")\\n\\nif ",[338,11368,8309],{},[338,11370,11178],{},"\":\\n    main()\\n\",\"output\":\"str\",\"x\":460,\"y\":440,\"wires\":[[\"d2d1450deaa588f4\"]]}",[326,11373,11374,11377,11388,11391],{},[103,11375,11376],{},"Drag the Inject node onto the canvas.",[103,11378,11379,11380,11383,11384,11387],{},"Drag the Exec node onto the canvas, set the command to ",[19,11381,11382],{},"python -u .\u002Fexample.py \u003Carg>",", and replace the ",[19,11385,11386],{},"\u003Carg>"," with your argument.",[103,11389,11390],{},"Now Drag the Debug node onto the canvas.",[103,11392,11393],{},"Connect the output of the Inject node to the input of the Exec node, and the output of the Exec node to the input of the Debug node.",[15,11395,11396,11397,11400,11401,11404,11405,11408],{},"If you examine the Python file we've created, you'll notice the use of the 'sys' module, which allows us to read command-line arguments. In our context, we execute the command ",[19,11398,11399],{},"python .\u002Fexample.py -30",". By accessing ",[19,11402,11403],{},"sys.argv[1]",", we retrieve the argument -30. The index 1 is used because ",[19,11406,11407],{},"sys.argv[0]"," provides the filename of the script being executed. Additionally, Python supports passing multiple arguments, so that you can pass as many arguments as you want.",[15,11410,6389,11411,3830],{},[146,11412,11413,11414,276,11416,11226,11418,11420,11421,11423,11424,11426,11427,1596],{},"{\"id\":\"2e26b84c0ce17312\",\"type\":\"exec\",\"z\":\"FFF0000000000001\",\"command\":\"python -u .\u002Fexample.py  -30\",\"addpay\":\"\",\"append\":\"\",\"useSpawn\":\"false\",\"timer\":\"\",\"winHide\":false,\"oldrc\":false,\"name\":\"\",\"x\":520,\"y\":580,\"wires\":[[\"89589c56117004e0\"],",[146,11415,11222],{},[146,11417,11225],{},[146,11419,9126],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":280,\"y\":580,\"wires\":[[\"2e26b84c0ce17312\"]]},{\"id\":\"89589c56117004e0\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"Output\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":730,\"y\":540,\"wires\":",[146,11422],{},"},{\"id\":\"7fdb901b144749c2\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"Error\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":730,\"y\":580,\"wires\":",[146,11425],{},"},{\"id\":\"3f49b49308941782\",\"type\":\"debug\",\"z\":\"FFF0000000000001\",\"name\":\"Return code\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":750,\"y\":620,\"wires\":",[146,11428],{},[34,11430,6518],{"id":6517},[15,11432,11433],{},"In this guide, we've demonstrated how to seamlessly execute Python scripts from Node-RED, along with troubleshooting tips and instructions on passing arguments to scripts. By leveraging Python's extensive libraries for data processing, machine learning, and other tasks in conjunction with Node-RED, developers can build powerful IoT solutions with ease.",[924,11435,11436],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":11438},[11439,11440,11441,11442,11443,11444],{"id":11085,"depth":185,"text":11086},{"id":11095,"depth":185,"text":11096},{"id":11157,"depth":185,"text":11158},{"id":11247,"depth":185,"text":11248},{"id":11338,"depth":185,"text":11339},{"id":6517,"depth":185,"text":6518},{"type":941,"title":11446,"description":11447},"Build Powerful IoT Solutions With Node-RED and Python","FlowFuse gives you a production-ready platform to deploy and manage Node-RED flows, with the flexibility to integrate Python scripts, external libraries, and any data source your project needs. Deploy to edge devices, collaborate with your team, and scale from one device to thousands. Start free today.","2024-07-10","Learn how to run a Python script from Node-RED using the Exec node. Step-by-step guide covering script execution, passing arguments, reading sensor data, and troubleshooting, combine Node-RED's visual flows with Python's libraries.","\u002Fblog\u002F2024\u002F07\u002Fimages\u002Fcalling-python-script-from-node-red.png",{"keywords":11452,"excerpt":11453},"node red and python, node red call python script, node red exec node python script, node red execute python script, node red for python, node red python example, node red python script, node red run python script, node red with python, python in node red",{"type":12,"value":11454},[11455],[15,11456,11082],{},"\u002Fblog\u002F2024\u002F07\u002Fcalling-python-script-from-node-red",{"title":11076,"description":11449},{"loc":11457},"blog\u002F2024\u002F07\u002Fcalling-python-script-from-node-red","A step-by-step guide to executing Python scripts from Node-RED, passing arguments, and capturing output.",[9323,6563,11463,966],"node red python","To run a Python script from Node-RED, use the Exec node with a command like 'python -u .\u002Fyour_script.py'. Trigger it with an Inject node and read the result from a Debug node, the Exec node returns the script's stdout as msg.payload, plus separate outputs for errors and the return code. Add the -u flag to avoid output buffering, pass arguments by appending them to the command (read in Python via sys.argv), and parse printed JSON with a JSON node. Python must be installed on the same machine as Node-RED. This pairs Node-RED's visual flow logic with Python's libraries for tasks like sensor reading, data processing, and machine learning.","X_Vh0vOvtrvtqu01S9PqeFXU8yMsqEy4qHbyK3z1BPA",{"id":11467,"title":11468,"authors":11469,"body":11470,"cta":11912,"date":11915,"description":11916,"extension":946,"image":11917,"lastUpdated":948,"meta":11918,"navigation":253,"path":11924,"seo":11925,"sitemap":11926,"stem":11927,"subtitle":11928,"tags":11929,"tldr":11932,"video":3,"__hash__":11933},"blog\u002Fblog\u002F2024\u002F06\u002Finteracting-with-google-sheet-from-node-red.md","Interacting with Google Sheets from Node-RED (2026)",[10],{"type":12,"value":11471,"toc":11900},[11472,11475,11479,11487,11491,11494,11503,11507,11510,11523,11529,11536,11541,11548,11553,11560,11565,11572,11577,11584,11589,11596,11601,11608,11615,11620,11627,11632,11639,11646,11651,11658,11663,11669,11676,11680,11688,11731,11735,11738,11766,11773,11776,11780,11795,11802,11806,11823,11830,11834,11849,11856,11859,11895,11897],[15,11473,11474],{},"Have you ever needed to integrate Google Sheets with your Node-RED application to track and manage data seamlessly? This guide will walk you through the process of integrating Google Sheets with Node-RED, enabling you to write, read, update, and delete data effortlessly.",[34,11476,11478],{"id":11477},"what-is-the-google-sheet","What is the Google Sheet?",[15,11480,11481,11486],{},[307,11482,11485],{"href":11483,"rel":11484},"https:\u002F\u002Fwww.google.com\u002Fsheets\u002Fabout\u002F",[311],"Google Sheets"," is a cloud-based spreadsheet application developed by Google. It allows users to create, edit, and collaborate on spreadsheets in real-time over the Internet. This makes it an ideal option for easily and securely collaborating on data that is not large in size. In businesses, Google Sheets is commonly used for tasks such as analyzing daily profits, tracking expenses, and managing collaborative projects. However, for products or services with large user bases, businesses often prefer using databases, which are recommended for efficiently managing and scaling data operations.",[34,11488,11490],{"id":11489},"prequisite","Prequisite",[15,11492,11493],{},"Before proceeding, make sure you have installed the following node from the pallet manager.",[100,11495,11496],{},[103,11497,11498],{},[307,11499,11502],{"href":11500,"rel":11501},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-google-sheets",[311],"node-red-contrib-google-sheets",[34,11504,11506],{"id":11505},"interacting-with-google-sheets-with-node-red","Interacting with Google Sheets with Node-RED",[15,11508,11509],{},"To integrate Google Sheets with our application we must first enable the Google Sheets API, and create the service account in the Google Cloud, before proceeding, make sure you have the Google Cloud account created.",[326,11511,11512,11520],{},[103,11513,11514,11515,167],{},"Open your browser and go to ",[307,11516,11519],{"href":11517,"rel":11518},"https:\u002F\u002Fconsole.cloud.google.com\u002Fprojectselector2\u002Fapis\u002Flibrary\u002Fsheets?supportedpurview=project&authuser=0",[311],"Service accounts",[103,11521,11522],{},"Create a new project by clicking the \"CREATE PROJECT\" button in the top right corner. Enter the project details such as project name and organization.",[15,11524,11525],{},[392,11526],{"alt":11527,"dataZoomable":50,"src":11528,"title":11527},"Screenshot showing the 'CREATE PROJECT' button","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_1.png",[15,11530,11531],{},[392,11532],{"alt":11533,"dataZoomable":50,"src":11534,"title":11535},"\"Screenshot showing the Form to create the project\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_2.png","Screenshot showing the Form to create the project",[326,11537,11538],{"start":221},[103,11539,11540],{},"Go to the main menu by clicking the menu icon in the top left, then select \"APIs & Services.\"",[15,11542,11543],{},[392,11544],{"alt":11545,"dataZoomable":50,"src":11546,"title":11547},"\"Screenshot showing the 'APIs & Services' from the menu\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_3.png","Screenshot showing the 'APIs & Services' option from the main menu",[326,11549,11550],{"start":250},[103,11551,11552],{},"Click on \"Enable APIs and Services\" in the header.",[15,11554,11555],{},[392,11556],{"alt":11557,"dataZoomable":50,"src":11558,"title":11559},"\"Screenshot showing the 'Enable APIs and Services' option","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_4.png","Screenshot showing the 'Enable APIs and Services' option",[326,11561,11562],{"start":257},[103,11563,11564],{},"In the search bar, type \"Google Sheets\" and select it from the results.",[15,11566,11567],{},[392,11568],{"alt":11569,"dataZoomable":50,"src":11570,"title":11571},"\"Screenshot showing the Google Sheet in the search result","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_5.png","Screenshot showing the Google Sheet in the search result",[326,11573,11574],{"start":284},[103,11575,11576],{},"Click the \"Enable\" button to enable the Google Sheets API.",[15,11578,11579],{},[392,11580],{"alt":11581,"dataZoomable":50,"src":11582,"title":11583},"\"Screenshot showing the 'Enable' button\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_6.png","Screenshot showing the 'Enable' button",[326,11585,11586],{"start":666},[103,11587,11588],{},"Go back to the main menu and click on \"IAM & Admin,\" then select \"Service Accounts\" from the left sidebar.",[15,11590,11591],{},[392,11592],{"alt":11593,"dataZoomable":50,"src":11594,"title":11595},"\"Screenshot showing the 'IAM & Admin' from the menu\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_7.png","Screenshot showing the 'IAM & Admin' from the menu",[326,11597,11598],{"start":1603},[103,11599,11600],{},"Click on \"Create Service Account\" in the header. Enter the necessary details and click \"Create\" to proceed.",[15,11602,11603],{},[392,11604],{"alt":11605,"dataZoomable":50,"src":11606,"title":11607},"\"Screenshot showing the 'Create Service Account' option\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_8.png","Screenshot showing the 'Create Service Account' option",[15,11609,11610],{},[392,11611],{"alt":11612,"dataZoomable":50,"src":11613,"title":11614},"\"Screenshot showing the 'Create' button\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_9.png","Screenshot showing the 'Create' button",[326,11616,11617],{"start":1608},[103,11618,11619],{},"Select the Role from the \"Owner\" and click on the \"Continue\" button.",[15,11621,11622],{},[392,11623],{"alt":11624,"dataZoomable":50,"src":11625,"title":11626},"\"Screenshot showing the 'Continue' button\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_10.png","Screenshot showing the 'Continue' button",[326,11628,11629],{"start":1624},[103,11630,11631],{},"Click \"Done.\" Make sure to copy the generated service account email and save it for later use.",[15,11633,11634],{},[392,11635],{"alt":11636,"dataZoomable":50,"src":11637,"title":11638},"\"Screenshot showing the 'Done' button\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_11.png","Screenshot showing the 'Done' button",[15,11640,11641],{},[392,11642],{"alt":11643,"dataZoomable":50,"src":11644,"title":11645},"\"Screenshot showing the created service account email\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_14.png","Screenshot showing the created service account email",[326,11647,11648],{"start":2546},[103,11649,11650],{},"To generate a private key, click on the three dots icon on the right of the newly created service account and select \"Manage keys.\"",[15,11652,11653],{},[392,11654],{"alt":11655,"dataZoomable":50,"src":11656,"title":11657},"\"Screenshot showing the three dot icon\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_15.png","Screenshot showing the three dot icon",[326,11659,11660],{"start":2564},[103,11661,11662],{},"Click on \"Add key,\" choose \"Create new key,\" select \"JSON\" as the key type, and click \"Create.\" Your private key will be generated and downloaded.",[15,11664,11665],{},[392,11666],{"alt":11667,"dataZoomable":50,"src":11656,"title":11668},"\"Screenshot showing the 'Add key' and the 'Create new key'\"","Screenshot showing the 'Add key' and the 'Create new key'",[15,11670,11671],{},[392,11672],{"alt":11673,"dataZoomable":50,"src":11674,"title":11675},"\"Screenshot showing the 'JSON' option and 'Create new key' button\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete-page_13.png","Screenshot showing the 'JSON' option and 'Create new key' button",[996,11677,11679],{"id":11678},"configuring-the-google-sheet-node","Configuring the Google Sheet Node",[15,11681,11682,11683,11687],{},"Before proceeding, ensure you have added the ",[307,11684,11686],{"href":11685},"\u002Fblog\u002F2023\u002F01\u002Fenvironment-variables-in-node-red\u002F","environment variable"," for the private key that was generated. Additionally, grant the editor access to the sheet you want to interact with for that service account email we created in the above section.",[326,11689,11690,11693,11696,11699,11705,11708],{},[103,11691,11692],{},"Drag a GSheet node onto the canvas.",[103,11694,11695],{},"Double-click on the node and click on the pencil icon next to \"creds.\"",[103,11697,11698],{},"Enter the environment variable added for the private key in the \"creds\" field and click \"Add.\"",[103,11700,11701,11702],{},"Go to the Google Sheet you want to interact with and copy its ID from the URL. The URL will be in this format: ",[19,11703,11704],{},"https:\u002F\u002Fdocs.google.com\u002Fspreadsheets\u002Fd\u002F\u003Cid_of_sheet>\u002F",[103,11706,11707],{},"Return to your Node-RED instance, double-click on the GSheet node again, and paste the spreadsheet ID into the \"SpreadsheetID\" field.",[103,11709,11710,11711,11714,11715,11718,11719,11722,11723,11726,11727,11730],{},"Enter the range of cells you want to work with using the syntax ",[19,11712,11713],{},"\u003Csheetname!first-cell-name:last-cell-name>",". For example, use ",[19,11716,11717],{},"Sheet1!A1:C3"," to specify that you are working with the \"Sheet1\" tab, starting from cell \"A1\" to cell \"C3\". This syntax allows you to define specific ranges such as a row (",[19,11720,11721],{},"A1:A5","), a column (",[19,11724,11725],{},"A1:E1","), or a block (",[19,11728,11729],{},"A1:C3",") within the spreadsheet.",[996,11732,11734],{"id":11733},"writing-data-to-cells","Writing Data to Cells",[15,11736,11737],{},"For demonstration purposes, I will write simulated sensor data which includes a timestamp and sensor data.",[326,11739,11740,11749,11760,11763],{},[103,11741,11742,11743,764,11745,11748],{},"Drag the Inject node onto the canvas, and set ",[19,11744,382],{},[19,11746,11747],{},"[$moment().format(), $random() * 100]"," as a JSONata expression, and set it to repeat every 3 seconds of interval.",[103,11750,11751,11752,11755,11756,11759],{},"Double-click on the GSheet node, select the method to \"Append Row\" set the range to ",[19,11753,11754],{},"\u003Csheetname>!A2",", and replace ",[19,11757,11758],{},"sheetname"," with the name of your sheet. I have defined cell A2 because I want to start writing data from cell A2.",[103,11761,11762],{},"Drag the Debug node onto the canvas, which will help in debugging in case of any error.",[103,11764,11765],{},"Connect the output of the Inject node to the input of the GSheet node, and the output of the GSheet node to the input of the Debug node.",[15,11767,11768],{},[392,11769],{"alt":11770,"dataZoomable":50,"src":11771,"title":11772},"\"Image showing the write operation\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-write.gif","Image showing the write operation",[15,11774,11775],{},"This flow generates a timestamp and a random number. The data is formatted as an array because I want the timestamp (the first item of the array) to be placed in column A and the random number (the second item of the array) to be placed in column B. If you want to insert data into additional columns, you can add more items to the array. For example, if you add a third item to the array, it will be placed in column C, a fourth item will be placed in column D, and so on.",[996,11777,11779],{"id":11778},"reading-data-from-cells","Reading Data from Cells",[326,11781,11782,11784,11791,11793],{},[103,11783,11192],{},[103,11785,11786,11787,11790],{},"Drag another GSheet node onto the canvas, and set the method to \"Get Cells\" and the range to ",[19,11788,11789],{},"\u003Csheetname>!A2:C1000",", as I wanted to read data from cell A2 to the next 1000 cells.",[103,11792,11211],{},[103,11794,11765],{},[15,11796,11797],{},[392,11798],{"alt":11799,"dataZoomable":50,"src":11800,"title":11801},"\"Image showing the read operation\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-read.gif","Image showing the read operation",[996,11803,11805],{"id":11804},"updating-data-of-cells","Updating Data of Cells",[326,11807,11808,11813,11819,11821],{},[103,11809,11810,11811,167],{},"Drag an Inject node onto the canvas, and set the updated value as the ",[19,11812,382],{},[103,11814,11815,11816,11818],{},"Drag another GSheet node onto the canvas, and set the method to \"Update Cells\" and the range to ",[19,11817,11754],{},", as I wanted to update the value of cell A2.",[103,11820,11211],{},[103,11822,11765],{},[15,11824,11825],{},[392,11826],{"alt":11827,"dataZoomable":50,"src":11828,"title":11829},"\"Image showing the update operation\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-update.gif","Image showing the update operation",[996,11831,11833],{"id":11832},"deleting-data-from-cells","Deleting Data from Cells",[326,11835,11836,11838,11845,11847],{},[103,11837,11192],{},[103,11839,11840,11841,11844],{},"Drag another GSheet node onto the canvas, and set the method to \"Clear Cells\" and the range to ",[19,11842,11843],{},"\u003Csheetname>!A2:C50",", as I wanted to clear the first 50 records.",[103,11846,11211],{},[103,11848,11765],{},[15,11850,11851],{},[392,11852],{"alt":11853,"dataZoomable":50,"src":11854,"title":11855},"\"Image showing the delete operation\"","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red-delete.gif","Image showing the delete operation",[15,11857,11858],{},"Below I have provided the complete flow that we have built through the guide, make sure to replace the environment variable with your environment variable added for the private key.",[15,11860,6389,11861,3830],{},[146,11862,11863,11864,11866,11867,11870,11871,11873,11874,11876,11877,11879,11880,11866,11882,11885,11886,11888,11889,11891,11892,11894],{},"{\"id\":\"7d0282761979574c\",\"type\":\"inject\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"Wrting data to the cells\",\"props\":",[146,11865,9126],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"",[146,11868,11869],{},"\\t    $moment(),\\t    $random()*100\\t    \\t","\",\"payloadType\":\"jsonata\",\"x\":240,\"y\":140,\"wires\":[[\"eda23377d98e1a51\"]]},{\"id\":\"eda23377d98e1a51\",\"type\":\"GSheet\",\"z\":\"baa50b8a4762ec1f\",\"creds\":\"d38cb80ae8574ea6\",\"method\":\"append\",\"action\":\"\",\"sheet\":\"1TEEShkuxxrb3WH4NTFyk1COeDyWpgX1w6HN08ZezC7s\",\"cells\":\"Sheet1!A2:C1000\",\"flatten\":false,\"name\":\"\",\"x\":510,\"y\":140,\"wires\":[[\"3e670f575b8227d0\"]]},{\"id\":\"3e670f575b8227d0\",\"type\":\"debug\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":760,\"y\":140,\"wires\":",[146,11872],{},"},{\"id\":\"2c916b1d5c10dffe\",\"type\":\"inject\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"Read the cells data\",\"props\":",[146,11875],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":210,\"y\":260,\"wires\":[[\"941c7fe7c7dbcbcd\"]]},{\"id\":\"941c7fe7c7dbcbcd\",\"type\":\"GSheet\",\"z\":\"baa50b8a4762ec1f\",\"creds\":\"d38cb80ae8574ea6\",\"method\":\"get\",\"action\":\"\",\"sheet\":\"1TEEShkuxxrb3WH4NTFyk1COeDyWpgX1w6HN08ZezC7s\",\"cells\":\"Sheet1!A2:C3\",\"flatten\":false,\"name\":\"\",\"x\":490,\"y\":260,\"wires\":[[\"f910d7637788361a\"]]},{\"id\":\"f910d7637788361a\",\"type\":\"debug\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":760,\"y\":260,\"wires\":",[146,11878],{},"},{\"id\":\"c20997333f9d4bda\",\"type\":\"inject\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"Updating the cells data\",\"props\":",[146,11881,9126],{},[146,11883,11884],{},"\\t   \"none\",\\t   \"none\"\\t    \\t","\",\"payloadType\":\"jsonata\",\"x\":220,\"y\":360,\"wires\":[[\"d9ca2a1e0614f764\"]]},{\"id\":\"d9ca2a1e0614f764\",\"type\":\"GSheet\",\"z\":\"baa50b8a4762ec1f\",\"creds\":\"d38cb80ae8574ea6\",\"method\":\"update\",\"action\":\"\",\"sheet\":\"1TEEShkuxxrb3WH4NTFyk1COeDyWpgX1w6HN08ZezC7s\",\"cells\":\"Sheet1!A35\",\"flatten\":false,\"name\":\"\",\"x\":510,\"y\":360,\"wires\":[[\"9febe629870b7a54\"]]},{\"id\":\"9febe629870b7a54\",\"type\":\"debug\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"debug 3\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":760,\"y\":360,\"wires\":",[146,11887],{},"},{\"id\":\"c9ceec9844fa74a9\",\"type\":\"inject\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"Deleting the cells data\",\"props\":",[146,11890],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":220,\"y\":460,\"wires\":[[\"b9cef9c376d1bea3\"]]},{\"id\":\"b9cef9c376d1bea3\",\"type\":\"GSheet\",\"z\":\"baa50b8a4762ec1f\",\"creds\":\"d38cb80ae8574ea6\",\"method\":\"clear\",\"action\":\"\",\"sheet\":\"1TEEShkuxxrb3WH4NTFyk1COeDyWpgX1w6HN08ZezC7s\",\"cells\":\"Sheet1!A2:C20\",\"flatten\":false,\"name\":\"\",\"x\":500,\"y\":460,\"wires\":[[\"a1766b498efb50f4\"]]},{\"id\":\"a1766b498efb50f4\",\"type\":\"debug\",\"z\":\"baa50b8a4762ec1f\",\"name\":\"debug 4\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":780,\"y\":460,\"wires\":",[146,11893],{},"},{\"id\":\"d38cb80ae8574ea6\",\"type\":\"gauth\",\"name\":\"Unknown\"}",[34,11896,6518],{"id":6517},[15,11898,11899],{},"This guide demonstrated how to integrate Google Sheets with Node-RED for streamlined data management. We covered setting up the Google Sheets API, configuring Node-RED to interact with sheets, and performing actions like writing, reading, updating, and deleting data.",{"title":50,"searchDepth":250,"depth":250,"links":11901},[11902,11903,11904,11911],{"id":11477,"depth":185,"text":11478},{"id":11489,"depth":185,"text":11490},{"id":11505,"depth":185,"text":11506,"children":11905},[11906,11907,11908,11909,11910],{"id":11678,"depth":221,"text":11679},{"id":11733,"depth":221,"text":11734},{"id":11778,"depth":221,"text":11779},{"id":11804,"depth":221,"text":11805},{"id":11832,"depth":221,"text":11833},{"id":6517,"depth":185,"text":6518},{"type":941,"title":11913,"description":11914},"Connect Node-RED to Any Data Source","FlowFuse makes it simple to build, deploy, and manage Node-RED integrations, from Google Sheets to industrial systems, with production-ready infrastructure and team collaboration built in.","2024-06-21","Learn how to interact with Google Sheet from Node-RED to write, read, update and delete data.","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Finteracting-with-google-sheet-from-node-red.png",{"keywords":11919,"excerpt":11920},"node-red google sheets, node-red-contrib-google-sheets, google sheets api node-red, read write google sheets node-red, google sheets integration, node-red spreadsheet, flowfuse google sheets",{"type":12,"value":11921},[11922],[15,11923,11474],{},"\u002Fblog\u002F2024\u002F06\u002Finteracting-with-google-sheet-from-node-red",{"title":11468,"description":11916},{"loc":11924},"blog\u002F2024\u002F06\u002Finteracting-with-google-sheet-from-node-red","Guide to learn how to write, read, update and delete data in Google sheet using Node-RED.",[9832,6563,11930,11931,966],"node red","google sheet","This guide walks through integrating Google Sheets with Node-RED by enabling the Sheets API, creating a service account in Google Cloud, and configuring the node-red-contrib-google-sheets package. It demonstrates all four CRUD operations, appending rows, reading cell ranges, updating specific cells, and clearing data, using GSheet nodes connected to inject and debug nodes.","wwhZFtf_hiDuN-JVdE6FwPKA7heVvC08rQupvGffhFU",{"id":11935,"title":11936,"authors":11937,"body":11938,"cta":12563,"date":12566,"description":12567,"extension":946,"image":12568,"lastUpdated":948,"meta":12569,"navigation":253,"path":12575,"seo":12576,"sitemap":12577,"stem":12578,"subtitle":12579,"tags":12580,"tldr":12581,"video":3,"__hash__":12582},"blog\u002Fblog\u002F2024\u002F06\u002Fhow-to-use-mqtt-in-node-red.md","Working with MQTT in Node-RED: Complete Guide (2026)",[10],{"type":12,"value":11939,"toc":12550},[11940,11943,11946,11950,11953,11971,11975,11985,11999,12007,12014,12019,12026,12063,12068,12075,12078,12093,12096,12105,12114,12118,12121,12172,12179,12183,12186,12249,12256,12261,12276,12283,12286,12331,12334,12376,12379,12383,12386,12393,12398,12405,12409,12412,12416,12425,12431,12437,12443,12449,12453,12456,12462,12468,12474,12477,12481,12484,12489,12495,12500,12506,12511,12517,12527,12530,12536,12538,12541,12544,12547],[15,11941,11942],{},"MQTT handles the messaging layer for most IoT deployments. Node-RED provides built-in nodes that connect to MQTT brokers, subscribe to topics, and publish messages, all through a visual interface.",[15,11944,11945],{},"This guide walks through the configuration steps and common patterns you'll need for working implementations.",[34,11947,11949],{"id":11948},"what-youll-need","What You'll Need",[15,11951,11952],{},"Before you start, make sure you have:",[100,11954,11955,11965],{},[103,11956,11957,11960,11961,11964],{},[338,11958,11959],{},"Node-RED Instance",": You need Node-RED running somewhere. Easiest option is FlowFuse, grab a ",[307,11962,312],{"href":309,"rel":11963},[311]," and you get a cloud-hosted instance ready to go. No server setup, no port forwarding hassles.",[103,11966,11967,11970],{},[338,11968,11969],{},"MQTT Broker",": You'll need an MQTT broker to handle message routing. If you're on FlowFuse Pro tier, you get a built-in MQTT broker service, no separate setup needed.",[34,11972,11974],{"id":11973},"getting-connected","Getting Connected",[15,11976,11977,11978,63,11981,11984],{},"Node-RED ships with MQTT nodes already installed. Open your Node-RED editor and look in the palette, you'll find ",[338,11979,11980],{},"mqtt-in",[338,11982,11983],{},"mqtt-out"," under the network section.",[301,11986,11987],{},[15,11988,11989,11990,63,11994,11998],{},"If you're using FlowFuse's managed MQTT broker, this is straightforward. Use the ",[307,11991,11993],{"href":11992},"\u002Fnode-red\u002Fflowfuse\u002Fmqtt\u002Fmqtt-in\u002F","ff-mqtt-in",[307,11995,11997],{"href":11996},"\u002Fnode-red\u002Fflowfuse\u002Fmqtt\u002Fmqtt-out\u002F","ff-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.",[15,12000,12001,12002,804,12004,12006],{},"For any other broker, drag an ",[338,12003,11980],{},[338,12005,11983],{}," node onto your workspace. Double-click it to open the configuration panel.",[15,12008,12009,12010,12013],{},"Click the pencil icon next to ",[338,12011,12012],{},"Server",". You'll see two tabs: Connection and Security.",[15,12015,12016],{},[338,12017,12018],{},"Connection Tab",[15,12020,12021],{},[392,12022],{"alt":12023,"dataZoomable":50,"src":12024,"title":12025},"MQTT broker connection configuration screen showing server address, port, and protocol settings","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fmqtt-broker-config.png","MQTT Connection Tab - Configure broker address, port, and TLS settings",[326,12027,12028,12037,12051,12057],{},[103,12029,12030,12032,12033,12036],{},[338,12031,12012],{},": Enter your broker address (",[19,12034,12035],{},"broker.hivemq.com"," for testing, or your broker's hostname)",[103,12038,12039,12042,12043,12046,12047,12050],{},[338,12040,12041],{},"Port",": Use ",[19,12044,12045],{},"8883"," for encrypted connections, ",[19,12048,12049],{},"1883"," for unencrypted",[103,12052,12053,12056],{},[338,12054,12055],{},"Enable TLS",": Check this if you're using port 8883",[103,12058,12059,12062],{},[338,12060,12061],{},"Protocol",": Leave it at MQTT 3.1.1 unless you specifically need MQTT 5 features",[15,12064,12065],{},[338,12066,12067],{},"Security Tab",[15,12069,12070],{},[392,12071],{"alt":12072,"dataZoomable":50,"src":12073,"title":12074},"MQTT broker security configuration showing username and password fields","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fmqtt-configuration-security-tab.png","MQTT Security Tab - Enter credentials using environment variables for security",[15,12076,12077],{},"Enter your username and password. For anything beyond local testing, use environment variables:",[100,12079,12080,12087],{},[103,12081,12082,12083,12086],{},"Put ",[19,12084,12085],{},"${MQTT_USER}"," in the username field",[103,12088,12082,12089,12092],{},[19,12090,12091],{},"${MQTT_PASSWORD}"," in the password field",[15,12094,12095],{},"Set these in your Node-RED settings or FlowFuse environment config",[301,12097,12098],{},[15,12099,12100,12101,12104],{},"See ",[307,12102,12103],{"href":11685},"Using Environment Variables in Node-RED"," for setup details.",[15,12106,355,12107,12110,12111,12113],{},[338,12108,12109],{},"Add",", then ",[338,12112,405],{},". Deploy your flow. If the node shows \"connected\" under it, you're in.",[34,12115,12117],{"id":12116},"publishing-data-to-a-topic-on-mqtt-broker","Publishing Data to a Topic on MQTT Broker",[15,12119,12120],{},"The mqtt-out node sends data from Node-RED to your MQTT broker. Configure the topic, quality of service level, and retention settings to control how your messages are delivered.",[326,12122,12123,12127,12135,12145,12159,12166],{},[103,12124,369,12125,1340],{},[338,12126,11983],{},[103,12128,435,12129,12131,12132,12134],{},[338,12130,11983],{}," node and select your broker from the ",[338,12133,12012],{}," dropdown.",[103,12136,12137,12138,12141,12142,167],{},"Enter your topic name in the ",[338,12139,12140],{},"Topic"," field. Use forward slashes for hierarchy like ",[19,12143,12144],{},"factory\u002Fline1\u002Ftemp",[103,12146,379,12147,764,12150,12152,12153,12155,12156,12158],{},[338,12148,12149],{},"QoS",[338,12151,6820],{}," for guaranteed delivery. Use ",[338,12154,62],{}," if you can tolerate occasional duplicates, or ",[338,12157,677],{}," for high-frequency data where missing readings don't matter.",[103,12160,12161,12162,12165],{},"Enable ",[338,12163,12164],{},"Retain"," if new subscribers should immediately receive the last published value.",[103,12167,12168,12169,12171],{},"Connect your data source to the mqtt-out node's input. The node expects ",[19,12170,382],{}," to contain your data.",[15,12173,12174],{},[392,12175],{"alt":12176,"dataZoomable":50,"src":12177,"title":12178},"MQTT-out node configuration panel settings","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fmqtt-out.png","MQTT-out Node Configuration",[34,12180,12182],{"id":12181},"subscribing-to-a-topic-on-mqtt-broker","Subscribing to a Topic on MQTT Broker",[15,12184,12185],{},"The mqtt-in node receives messages from topics you specify. You can subscribe to specific topics or use wildcards to monitor multiple topics at once.",[326,12187,12188,12192,12198,12231,12237,12246],{},[103,12189,369,12190,1340],{},[338,12191,11980],{},[103,12193,435,12194,12131,12196,12134],{},[338,12195,11980],{},[338,12197,12012],{},[103,12199,379,12200,764,12203,12206,12207,12209,12210],{},[338,12201,12202],{},"Action",[338,12204,12205],{},"subscribe to a single topic"," and enter the topic name in the ",[338,12208,12140],{}," field. You can use:\n",[100,12211,12212,12217,12224],{},[103,12213,12214,12215],{},"Specific topics: ",[19,12216,12144],{},[103,12218,12219,12220,12223],{},"Single-level wildcard: ",[19,12221,12222],{},"factory\u002F+\u002Ftemp"," (matches any value where + appears)",[103,12225,12226,12227,12230],{},"Multi-level wildcard: ",[19,12228,12229],{},"factory\u002F#"," (matches everything under factory\u002F)",[103,12232,379,12233,764,12235,167],{},[338,12234,12149],{},[338,12236,6820],{},[103,12238,379,12239,764,12242,12245],{},[338,12240,12241],{},"Output",[338,12243,12244],{},"auto-detect"," (it parses JSON automatically and passes through other formats as strings).",[103,12247,12248],{},"Connect the mqtt-in node's output to wherever you need the data.",[15,12250,12251],{},[392,12252],{"alt":12253,"dataZoomable":50,"src":12254,"title":12255},"MQTT-in node configuration showing subscription settings with topic field and QoS options","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fmqtt-in-config.png","MQTT-in Node Configuration",[15,12257,12258],{},[338,12259,12260],{},"For Dynamic Subscriptions:",[15,12262,12263,12264,764,12266,12269,12270,63,12272,12275],{},"If you need to change subscriptions at runtime based on user input or system conditions, set ",[338,12265,12202],{},[338,12267,12268],{},"dynamic"," instead. You'll control subscriptions by sending messages with ",[19,12271,9425],{},[19,12273,12274],{},"msg.action"," to the node's input.",[15,12277,12278],{},[392,12279],{"alt":12280,"dataZoomable":50,"src":12281,"title":12282},"MQTT-in node configured for dynamic subscription mode with empty topic field","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fmqtt-in-dynamic.png","MQTT-in Dynamic Mode",[15,12284,12285],{},"To subscribe dynamically, send a message like:",[42,12287,12289],{"className":140,"code":12288,"language":142,"meta":50,"style":50},"msg.topic = \"factory\u002Fline1\u002Ftemperature\";\nmsg.action = \"subscribe\";\n",[19,12290,12291,12311],{"__ignoreMap":50},[146,12292,12293,12295,12297,12300,12302,12304,12307,12309],{"class":148,"line":149},[146,12294,260],{"class":156},[146,12296,167],{"class":160},[146,12298,12299],{"class":156},"topic ",[146,12301,161],{"class":160},[146,12303,1830],{"class":160},[146,12305,12306],{"class":1554},"factory\u002Fline1\u002Ftemperature",[146,12308,727],{"class":160},[146,12310,182],{"class":160},[146,12312,12313,12315,12317,12320,12322,12324,12327,12329],{"class":148,"line":185},[146,12314,260],{"class":156},[146,12316,167],{"class":160},[146,12318,12319],{"class":156},"action ",[146,12321,161],{"class":160},[146,12323,1830],{"class":160},[146,12325,12326],{"class":1554},"subscribe",[146,12328,727],{"class":160},[146,12330,182],{"class":160},[15,12332,12333],{},"To unsubscribe:",[42,12335,12337],{"className":140,"code":12336,"language":142,"meta":50,"style":50},"msg.topic = \"factory\u002Fline1\u002Ftemperature\";\nmsg.action = \"unsubscribe\";\n",[19,12338,12339,12357],{"__ignoreMap":50},[146,12340,12341,12343,12345,12347,12349,12351,12353,12355],{"class":148,"line":149},[146,12342,260],{"class":156},[146,12344,167],{"class":160},[146,12346,12299],{"class":156},[146,12348,161],{"class":160},[146,12350,1830],{"class":160},[146,12352,12306],{"class":1554},[146,12354,727],{"class":160},[146,12356,182],{"class":160},[146,12358,12359,12361,12363,12365,12367,12369,12372,12374],{"class":148,"line":185},[146,12360,260],{"class":156},[146,12362,167],{"class":160},[146,12364,12319],{"class":156},[146,12366,161],{"class":160},[146,12368,1830],{"class":160},[146,12370,12371],{"class":1554},"unsubscribe",[146,12373,727],{"class":160},[146,12375,182],{"class":160},[15,12377,12378],{},"This is useful when users select which equipment to monitor from a dashboard, when topics depend on database queries or API responses, or when you need to add and remove subscriptions without redeploying your flow.",[34,12380,12382],{"id":12381},"deploying-the-flow","Deploying the Flow",[15,12384,12385],{},"Deploy your flow to activate the MQTT connection and start sending or receiving messages.",[326,12387,12388],{},[103,12389,492,12390,12392],{},[338,12391,695],{}," button at the top-right corner.",[15,12394,12395],{},[397,12396,12397],{},"Tip: Check the node status below each MQTT node. It will display 'connected' if the connection is successful.",[15,12399,12400],{},[392,12401],{"alt":12402,"dataZoomable":50,"src":12403,"title":12404},"MQTT nodes in Node-RED editor showing green connected status indicators below each node","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fmqtt-node-status.png","MQTT Node Status - Green 'connected' indicator confirms successful broker connection",[34,12406,12408],{"id":12407},"best-practices","Best Practices",[15,12410,12411],{},"Production MQTT deployments need more than basic configuration. These practices separate hobby projects from industrial-grade systems that run 24\u002F7 without issues.",[996,12413,12415],{"id":12414},"security-first","Security First",[15,12417,12418,12421,12422,12424],{},[338,12419,12420],{},"Always use TLS encryption."," Unencrypted MQTT sends credentials and data in plain text across your network. Enable port 8883 with TLS on your broker, then check the ",[338,12423,12055],{}," box in your Node-RED broker configuration. No exceptions for production systems.",[15,12426,12427,12430],{},[338,12428,12429],{},"Never hardcode credentials."," Putting usernames and passwords directly in nodes means anyone with access to your flows can see them. Use environment variables instead:",[42,12432,12435],{"className":12433,"code":12434,"language":47},[45],"Username: ${MQTT_USER}\nPassword: ${MQTT_PASS}\n",[19,12436,12434],{"__ignoreMap":50},[15,12438,12439,12440,12442],{},"Set these in your Node-RED settings file or FlowFuse environment configuration. See ",[307,12441,12103],{"href":11685}," for the complete setup.",[15,12444,12445,12448],{},[338,12446,12447],{},"Implement access control on your broker."," Not every device needs to publish and subscribe to every topic. Configure your MQTT broker's ACL (Access Control List) to restrict clients based on their role. Sensors should only publish to their specific topics. Dashboard applications should only subscribe to what they display. This limits damage if credentials get compromised.",[996,12450,12452],{"id":12451},"choose-the-right-qos","Choose the Right QoS",[15,12454,12455],{},"QoS isn't a \"set it and forget it\" decision. Each level trades reliability against network overhead and latency.",[15,12457,12458,12461],{},[338,12459,12460],{},"QoS 0:"," Fire and forget. Use for high-frequency sensor data where missing one reading doesn't matter.",[15,12463,12464,12467],{},[338,12465,12466],{},"QoS 1:"," Guaranteed delivery, possible duplicates. Good for status updates and notifications.",[15,12469,12470,12473],{},[338,12471,12472],{},"QoS 2:"," Exactly once delivery with higher latency. Use for commands that trigger actions or critical transactions.",[15,12475,12476],{},"Match QoS to your use case. Don't default everything to QoS 2 just to be safe, you'll waste bandwidth and add latency where it doesn't help.",[996,12478,12480],{"id":12479},"design-your-topic-hierarchy-properly","Design Your Topic Hierarchy Properly",[15,12482,12483],{},"Your topic structure determines how easy it is to filter, subscribe, and scale your system. A hierarchical approach mirrors your physical setup and makes wildcards useful.",[15,12485,12486],{},[338,12487,12488],{},"Structure topics from general to specific:",[42,12490,12493],{"className":12491,"code":12492,"language":47},[45],"company\u002Flocation\u002Farea\u002Fequipment\u002Fmeasurement\n",[19,12494,12492],{"__ignoreMap":50},[15,12496,12497],{},[338,12498,12499],{},"Good hierarchy:",[42,12501,12504],{"className":12502,"code":12503,"language":47},[45],"acme\u002Fchicago\u002Fpackaging\u002Ffiller-01\u002Ftemperature\nacme\u002Fchicago\u002Fpackaging\u002Ffiller-01\u002Fpressure\nacme\u002Fdenver\u002Fassembly\u002Frobot-03\u002Ftemperature\nacme\u002Fdenver\u002Fassembly\u002Fconveyor-02\u002Fspeed\n",[19,12505,12503],{"__ignoreMap":50},[15,12507,12508],{},[338,12509,12510],{},"Poor hierarchy:",[42,12512,12515],{"className":12513,"code":12514,"language":47},[45],"temp-sensor-1\ntemp-sensor-2\npressure-sensor-1\nline1-data\n",[19,12516,12514],{"__ignoreMap":50},[15,12518,12519,12520,12523,12524,167],{},"The difference: with a proper hierarchy, you can subscribe at any level. Need all data from Chicago? Subscribe to ",[19,12521,12522],{},"acme\u002Fchicago\u002F#",". Want temperature readings across all sites? Use ",[19,12525,12526],{},"acme\u002F+\u002F+\u002F+\u002Ftemperature",[15,12528,12529],{},"Flat topics force you to subscribe to dozens of individual topics and manage them manually. Hierarchical topics let the broker do the filtering.",[15,12531,12532,12535],{},[338,12533,12534],{},"Keep it simple:"," Use lowercase, separate levels with forward slashes, use hyphens within names. Topics are paths, not sentences.",[34,12537,6518],{"id":6517},[15,12539,12540],{},"You now know how to connect Node-RED to MQTT brokers, publish data to topics, subscribe to incoming messages, and secure your deployment with TLS and proper credentials. That's the foundation.",[15,12542,12543],{},"Go build something. Connect a sensor, route the data, display it somewhere useful. The patterns you've learned here scale from a single device to entire factory floors.",[15,12545,12546],{},"When you hit the limits of managing individual instances (tracking deployments, coordinating updates, maintaining uptime across sites), that's when tools like [FlowFuse]({% include \"sign-up-url.njk\" %}) become relevant. Until then, you have everything you need.",[924,12548,12549],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":12551},[12552,12553,12554,12555,12556,12557,12562],{"id":11948,"depth":185,"text":11949},{"id":11973,"depth":185,"text":11974},{"id":12116,"depth":185,"text":12117},{"id":12181,"depth":185,"text":12182},{"id":12381,"depth":185,"text":12382},{"id":12407,"depth":185,"text":12408,"children":12558},[12559,12560,12561],{"id":12414,"depth":221,"text":12415},{"id":12451,"depth":221,"text":12452},{"id":12479,"depth":221,"text":12480},{"id":6517,"depth":185,"text":6518},{"type":941,"title":12564,"description":12565},"Run Production-Grade MQTT and Node-RED in One Platform","FlowFuse gives you hosted Node-RED and a built-in MQTT broker with access control, TLS, and managed credentials, plus remote device management and DevOps pipelines to run reliably from one device to entire factory floors. Start free today.","2024-06-05","Complete MQTT Node-RED tutorial: configure brokers, implement pub\u002Fsub messaging, use mqtt-in and mqtt-out nodes, and create dynamic subscriptions for IoT","\u002Fblog\u002F2024\u002F06\u002Fimages\u002Fworking-with-mqtt.jpg",{"keywords":12570,"excerpt":12571},"node red mqtt, mqtt node red, mqtt in node red, node red mqtt in, node red mqtt out, node red mqtt broker, mqtt broker node red, node-red-contrib-mqtt-broker, node red mqtt dynamic subscription, node red mqtt example, mqtt dynamic subscription, mqtt in\u002Fout nodes, mqtt broker setup",{"type":12,"value":12572},[12573],[15,12574,11942],{},"\u002Fblog\u002F2024\u002F06\u002Fhow-to-use-mqtt-in-node-red",{"title":11936,"description":12567},{"loc":12575},"blog\u002F2024\u002F06\u002Fhow-to-use-mqtt-in-node-red","Connect, subscribe, and publish MQTT messages in Node-RED",[9832,6563,7444,966],"Node-RED's built-in mqtt-in and mqtt-out nodes make it straightforward to connect to any MQTT broker, subscribe to topics with wildcard support, and publish data. Dynamic subscriptions, TLS security, environment-variable credentials, and a well-structured topic hierarchy are the key practices for reliable, production-grade MQTT flows.","MM_Ln2w_vqrUlQzGO9sjEvDGcCW4gg9usLAqHHMBUxk",{"id":12584,"title":12585,"authors":12586,"body":12587,"cta":13076,"date":13079,"description":13080,"extension":946,"image":13081,"lastUpdated":948,"meta":13082,"navigation":253,"path":13088,"seo":13089,"sitemap":13090,"stem":13091,"subtitle":13092,"tags":13093,"tldr":13096,"video":3,"__hash__":13097},"blog\u002Fblog\u002F2024\u002F05\u002Fmapping-location-on-dashboard-2.md","Mapping location data within Node-RED Dashboard 2.0. (2026)",[10],{"type":12,"value":12588,"toc":13064},[12589,12592,12599,12603,12612,12631,12635,12644,12671,12678,12688,12692,12695,12711,12717,12737,12743,12759,12765,12788,12794,12813,12819,12860,12864,12875,12882,12898,12904,12908,12914,12918,12941,12945,12967,12974,12977,12984,13038,13053,13059,13061],[15,12590,12591],{},"Fleet management in IoT uses sensors and software to collect real-time data on vehicles, such as location, fuel consumption, and driver behavior. This data allows companies to optimize routes, reduce costs, improve safety, and enhance overall operational efficiency of their fleet. Building an application that allows the tracking of location to support Fleet management is what this post is about.",[15,12593,12594,12595,12598],{},"Before moving further, make sure you have installed Dashboard 2.0. If you are new to Dashboard 2.0, please refer to ",[307,12596,12597],{"href":6792},"Getting Started with Dashboard 2.0"," for more information.",[34,12600,12602],{"id":12601},"installing-world-map-custom-node","Installing world map custom node",[15,12604,12605,12606,12611],{},"To render an interactive world map webpage for plotting location data, we will use a popular custom node called ",[307,12607,12610],{"href":12608,"rel":12609},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-web-worldmap",[311],"node-red-contrib-web-worldmap-page",". This node offers extensive features enabling us to render a world map and plot various items with different icons, colors, NATO symbologies, ranges, and more.",[326,12613,12614,12617,12620,12623,12628],{},[103,12615,12616],{},"Click the Node-RED Settings (top-right).",[103,12618,12619],{},"Click \"Manage Palette\".",[103,12621,12622],{},"Switch to the \"Install\" tab.",[103,12624,350,12625,167],{},[19,12626,12627],{},"node-red-contrib-web-worldmap",[103,12629,12630],{},"Click \"Install\".",[34,12632,12634],{"id":12633},"retrieving-location-data","Retrieving location Data",[15,12636,12637,12638,12643],{},"Before plotting locations, we need to obtain the data first. For this purpose, we will utilize the ",[307,12639,12642],{"href":12640,"rel":12641},"https:\u002F\u002Ftfe-opendata.readme.io\u002Fdocs\u002Fgetting-started",[311],"Edenburg Open Public Transportation API",". This API provides the live locations of all of Edenburg's public buses and trams, enabling us to access the necessary data for plotting on Worldmap within Dashboard 2.0.",[326,12645,12646,12651],{},[103,12647,369,12648,12650],{},[338,12649,372],{}," node onto the canvas and set the repeat property to a 20-second interval.",[103,12652,12653,12654,12657,12658,12661,12662,12665,12666,12670],{},"Drag an HTTP request node onto the canvas, Dobule-click on it and choose ",[338,12655,12656],{},"GET"," method, and enter ",[19,12659,12660],{},"https:\u002F\u002Ftfe-opendata.com\u002Fapi\u002Fv1\u002Fvehicle_locations"," in the URL field and select return  as ",[338,12663,12664],{},"a parsed json object",". This API is public, so no need for environment variables. For private APIs, consider using ",[307,12667,12669],{"href":12668},"\u002Fblog\u002F2023\u002F01\u002Fenvironment-variables-in-node-red","environment variables"," for security.",[15,12672,12673],{},[392,12674],{"alt":12675,"dataZoomable":50,"src":12676,"title":12677},"\"Screenshot of the HTTP request node configuration for retrieving data from the API\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-http-request-node.png","Screenshot of the HTTP request node configuration for retrieving data from the API",[326,12679,12680],{"start":221},[103,12681,1355,12682,12684,12685,12687],{},[338,12683,372],{}," node's output to the ",[338,12686,8698],{}," node's input.",[34,12689,12691],{"id":12690},"formatting-location-data","Formatting Location Data",[15,12693,12694],{},"To ensure compatibility with the Worldmap custom node, we need to format the location data appropriately.",[326,12696,12697],{},[103,12698,4995,12699,12701,12702,764,12704,12707,12708,167],{},[338,12700,5019],{}," node onto the canvas, and set the ",[19,12703,382],{},[19,12705,12706],{},"msg.payload.vehicles",", and give it name ",[338,12709,12710],{},"Set payload",[15,12712,12713],{},[392,12714],{"alt":12715,"dataZoomable":50,"src":12716,"title":12715},"Screenshot of the Change node setting the payload to the vehicles JSON array containing actual vehicle location data","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-change-node.png",[326,12718,12719,12723],{"start":185},[103,12720,408,12721,1340],{},[338,12722,8081],{},[103,12724,12725,12726,12728,12729,12732,12733,12736],{},"Add another ",[338,12727,5019],{}," node to the canvas. Configure it to set and delete properties as shown in the image below, and give it name ",[338,12730,12731],{},"Change and delete properties",". By changing and deleting properties of the location, we ensure that only the properties acceptable by the ",[338,12734,12735],{},"Worldmap"," node are included in the location data.",[15,12738,12739],{},[392,12740],{"alt":12741,"dataZoomable":50,"src":12742,"title":12741},"Screenshot of the Change node configuring properties to ensure compatibility with the Worldmap node","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-change-node-changing-and-deleting-properties.png",[326,12744,12745],{"start":250},[103,12746,408,12747,12750,12751,12754,12755,12758],{},[338,12748,12749],{},"Switch"," node onto the canvas and add conditions to check if ",[19,12752,12753],{},"msg.payload.vehicle_type"," is equal to ",[338,12756,12757],{},"tram"," or not.",[15,12760,12761],{},[392,12762],{"alt":12763,"dataZoomable":50,"src":12764,"title":12763},"Screenshot of the Switch node checking if the vehicle type is tram or not","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-switch-node-checking-is-vehicale-type-is-tram-or-not.png",[326,12766,12767],{"start":257},[103,12768,12725,12769,12771,12772,12775,12776,764,12779,63,12782,764,12785,167],{},[338,12770,5019],{}," node to the canvas and give it a name ",[338,12773,12774],{},"set icon and icon color for tram",". Set ",[19,12777,12778],{},"msg.payload.icon",[338,12780,12781],{},"fa-train",[19,12783,12784],{},"msg.payload.iconColor",[338,12786,12787],{},"yellow",[15,12789,12790],{},[392,12791],{"alt":12792,"dataZoomable":50,"src":12793,"title":12792},"Screenshot of the Change node setting the icon and icon color for tram","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-change-node-setting-icon-for-tram.png",[326,12795,12796],{"start":666},[103,12797,12725,12798,12771,12800,12775,12803,764,12805,63,12808,764,12810,167],{},[338,12799,5019],{},[338,12801,12802],{},"set icon and icon color for bus",[19,12804,12778],{},[338,12806,12807],{},"bus",[19,12809,12784],{},[338,12811,12812],{},"red",[15,12814,12815],{},[392,12816],{"alt":12817,"dataZoomable":50,"src":12818,"title":12817},"Screenshot of the Change node setting the icon and icon color for bus","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-change-node-setting-icon-for-bus.png",[326,12820,12821,12835],{"start":1603},[103,12822,1355,12823,7969,12825,12827,12828,12830,12831,8041,12833,1361],{},[338,12824,8698],{},[338,12826,5019],{}," node named ",[338,12829,12710],{},", and connect the output of that ",[338,12832,5019],{},[338,12834,8081],{},[103,12836,12837,12838,8041,12840,12827,12842,12844,12845,12847,12848,12850,12851,12827,12853,12855,12856,12827,12858,167],{},"Then, connect the output of the ",[338,12839,8081],{},[338,12841,5019],{},[338,12843,12731],{},", and connect the output of the \"Change and delete properties\" node to the input of the ",[338,12846,12749],{}," node. Then ",[338,12849,12749],{}," node's first output to the input of the ",[338,12852,5019],{},[338,12854,12774],{},", and its second output to the input of the ",[338,12857,5019],{},[338,12859,12802],{},[34,12861,12863],{"id":12862},"plotting-location-data-on-worldmap","Plotting location data on Worldmap",[326,12865,12866],{},[103,12867,408,12868,12870,12871,12874],{},[338,12869,12735],{}," node onto the canvas. Set the path to ",[338,12872,12873],{},"\u002Fworldmap"," and keep the rest of the settings unchanged, although you can modify other properties according to your preferences.",[15,12876,12877],{},[392,12878],{"alt":12879,"dataZoomable":50,"src":12880,"title":12881},"\"Screenshot displaying the configuration of the Worldmap custom node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-worldmap-node.png","Screenshot displaying the configuration of the Worldmap custom node",[326,12883,12884,12891],{"start":185},[103,12885,12886,12887,12890],{},"With the ",[338,12888,12889],{},"worldmap"," node configured, it will generate a world map with plotted data accessible at the specified path.",[103,12892,1355,12893,12895,12896,12687],{},[338,12894,3863],{}," node's output to ",[338,12897,12735],{},[15,12899,12900,12901,167],{},"Now we have successfully created a page with a world map displaying plotted vehicle location data. To confirm, you can visit ",[19,12902,12903],{},"https:\u002F\u002F\u003Cyour-instance-name>.flowfuse.cloud\u002Fworldmap",[34,12905,12907],{"id":12906},"rendering-map-on-dashboard-20","Rendering map on Dashboard 2.0",[15,12909,12910,12911,12913],{},"To render worlmap webpage on dashboard 2.0 we will use ",[338,12912,5119],{}," custom widget which will allow us to embed an external webpage into Dashboard 2.0 applications using an iframe.",[996,12915,12917],{"id":12916},"installing-iframe-custom-widget","Installing iframe custom widget",[326,12919,12920,12923,12928,12933,12937],{},[103,12921,12922],{},"Click the Node-RED Settings (top-right)",[103,12924,355,12925],{},[338,12926,12927],{},"Manage Palette",[103,12929,12930,12931,347],{},"Switch to the ",[338,12932,346],{},[103,12934,350,12935],{},[19,12936,2104],{},[103,12938,355,12939],{},[338,12940,346],{},[996,12942,12944],{"id":12943},"rendering-worlmap-on-dashboard-20","Rendering worlmap on Dashboard 2.0",[326,12946,12947,12952,12961],{},[103,12948,369,12949,12951],{},[338,12950,5119],{}," widget onto the canvas.",[103,12953,336,12954,63,12957,12960],{},[338,12955,12956],{},"ui-group",[338,12958,12959],{},"ui-base"," for it, where you want to render the webpage.",[103,12962,12963,12964,167],{},"Set height and width for it according to your preference and set URL to ",[338,12965,12966],{},"\u002Fworlmap",[15,12968,12969],{},[392,12970],{"alt":12971,"dataZoomable":50,"src":12972,"title":12973},"\"Screenshot showing configurations of iframe widget for rendering worlmap page on dashboard\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-iframe-widget.png","Screenshot showing configurations of iframe widget for rendering worlmap page on dashboard",[34,12975,12976],{"id":12381},"Deploying the flow",[15,12978,12979],{},[392,12980],{"alt":12981,"dataZoomable":50,"src":12982,"title":12983},"\"Image displaying live locations of UK public transport on the dashboard\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-uk-live-transport.gif","Image displaying live locations of UK public transport on the dashboard",[15,12985,6389,12986,3830],{},[146,12987,12988,12989,12992,12993,12998,12999,13001,13002,13004,13005,13007,13008,13010,13011,13014,13015,13018,13019,13022,13023,13026,13027,13030,13031,13034,13035,13037],{},"{\"id\":\"4e45e8ef870b86fb\",\"type\":\"group\",\"z\":\"eacc68e72f120b0e\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,12990,12991],{},"\"b6917d83.d1bac\",\"3842171.4d487e8\",\"b4f2e2dabd5b8220\",\"5f0d149d4dc38916\",\"2ff7e9501ad50cd5\",\"a08f0a836ac412a7\",\"bc891dc2aaaedc39\",\"726a8da3fe930e54\",\"b3dd7814ea5270ca\",\"0c5d6bcfd71d40da\",\"e037ee3d1f702d25\",\"d7ca6c3cdf176e4e\"",",\"x\":814,\"y\":759,\"w\":1872,\"h\":322},{\"id\":\"b6917d83.d1bac\",\"type\":\"http request\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"\",\"method\":\"GET\",\"ret\":\"obj\",\"paytoqs\":\"ignore\",\"url\":\"",[307,12994,12997],{"href":12995,"rel":12996},"https:\u002F\u002Ftfe-opendata.com\u002Fapi\u002Fv1\u002Fvehicle_locations%22,%22tls%22:%22%22,%22persist%22:false,%22proxy%22:%22%22,%22insecureHTTPParser%22:false,%22authType%22:%22%22,%22senderr%22:false,%22headers%22:%5B%5D,%22x%22:1190,%22y%22:900,%22wires%22:%5B%5B%22a08f0a836ac412a7%22%5D%5D%7D,%7B%22id%22:%223842171.4d487e8%22,%22type%22:%22inject%22,%22z%22:%22eacc68e72f120b0e%22,%22g%22:%224e45e8ef870b86fb%22,%22name%22:%22get",[311],"https:\u002F\u002Ftfe-opendata.com\u002Fapi\u002Fv1\u002Fvehicle_locations\",\"tls\":\"\",\"persist\":false,\"proxy\":\"\",\"insecureHTTPParser\":false,\"authType\":\"\",\"senderr\":false,\"headers\":[],\"x\":1190,\"y\":900,\"wires\":[[\"a08f0a836ac412a7\"]]},{\"id\":\"3842171.4d487e8\",\"type\":\"inject\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"get"," transporatation data\",\"props\":",[146,13000,9126],{},",\"repeat\":\"5\",\"crontab\":\"\",\"once\":false,\"onceDelay\":\"\",\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"str\",\"x\":970,\"y\":900,\"wires\":[[\"b6917d83.d1bac\"]]},{\"id\":\"b4f2e2dabd5b8220\",\"type\":\"worldmap\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"worldmap\",\"lat\":\"\",\"lon\":\"\",\"zoom\":\"\",\"layer\":\"OSMG\",\"cluster\":\"\",\"maxage\":\"\",\"usermenu\":\"show\",\"layers\":\"show\",\"panit\":\"false\",\"panlock\":\"false\",\"zoomlock\":\"false\",\"hiderightclick\":\"false\",\"coords\":\"mgrs\",\"showgrid\":\"false\",\"showruler\":\"false\",\"allowFileDrop\":\"false\",\"path\":\"\u002Fworldmap\",\"overlist\":\"DR,CO,RA,DN\",\"maplist\":\"OSMG,OSMC,EsriC,EsriS,UKOS\",\"mapname\":\"\",\"mapurl\":\"\",\"mapopt\":\"\",\"mapwms\":false,\"x\":2600,\"y\":900,\"wires\":",[146,13003],{},"},{\"id\":\"5f0d149d4dc38916\",\"type\":\"comment\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"Retrieving, formatting, and plotting location data on a world map.\",\"info\":\"\",\"x\":1390,\"y\":800,\"wires\":",[146,13006],{},"},{\"id\":\"2ff7e9501ad50cd5\",\"type\":\"comment\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"Rendering a map with plotted data on Dashboard 2.0.\",\"info\":\"\",\"x\":1440,\"y\":980,\"wires\":",[146,13009],{},"},{\"id\":\"a08f0a836ac412a7\",\"type\":\"change\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"Set payload\",\"rules\":",[146,13012,13013],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.vehicles\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1390,\"y\":900,\"wires\":[[\"726a8da3fe930e54\"]]},{\"id\":\"bc891dc2aaaedc39\",\"type\":\"change\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"Change and delete properties\",\"rules\":",[146,13016,13017],{},"{\"t\":\"set\",\"p\":\"payload.lat\",\"pt\":\"msg\",\"to\":\"payload.latitude\",\"tot\":\"msg\"},{\"t\":\"delete\",\"p\":\"payload.latitude\",\"pt\":\"msg\"},{\"t\":\"set\",\"p\":\"payload.lon\",\"pt\":\"msg\",\"to\":\"payload.longitude\",\"tot\":\"msg\"},{\"t\":\"delete\",\"p\":\"payload.longitude\",\"pt\":\"msg\"},{\"t\":\"set\",\"p\":\"payload.color\",\"pt\":\"msg\",\"to\":\"blue\",\"tot\":\"str\"},{\"t\":\"set\",\"p\":\"payload.name\",\"pt\":\"msg\",\"to\":\"payload.vehicle_id\",\"tot\":\"msg\"},{\"t\":\"delete\",\"p\":\"payload.vehicle_id\",\"pt\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1770,\"y\":900,\"wires\":[[\"b3dd7814ea5270ca\"]]},{\"id\":\"726a8da3fe930e54\",\"type\":\"split\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"\",\"splt\":\"\\n\",\"spltType\":\"str\",\"arraySplt\":1,\"arraySpltType\":\"len\",\"stream\":false,\"addname\":\"\",\"x\":1550,\"y\":900,\"wires\":[[\"bc891dc2aaaedc39\"]]},{\"id\":\"b3dd7814ea5270ca\",\"type\":\"switch\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"is vehical type is tram\",\"property\":\"payload.vehicle_type\",\"propertyType\":\"msg\",\"rules\":",[146,13020,13021],{},"{\"t\":\"eq\",\"v\":\"tram\",\"vt\":\"str\"},{\"t\":\"else\"}",",\"checkall\":\"true\",\"repair\":false,\"outputs\":2,\"x\":2040,\"y\":900,\"wires\":[[\"0c5d6bcfd71d40da\"],",[146,13024,13025],{},"\"e037ee3d1f702d25\"","]},{\"id\":\"0c5d6bcfd71d40da\",\"type\":\"change\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"set icon and icon color for tram\",\"rules\":",[146,13028,13029],{},"{\"t\":\"set\",\"p\":\"payload.icon\",\"pt\":\"msg\",\"to\":\"fa-train\",\"tot\":\"str\"},{\"t\":\"set\",\"p\":\"payload.iconColor\",\"pt\":\"msg\",\"to\":\"yellow\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":2310,\"y\":860,\"wires\":[[\"b4f2e2dabd5b8220\"]]},{\"id\":\"e037ee3d1f702d25\",\"type\":\"change\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"set icon and icon color for bus\",\"rules\":",[146,13032,13033],{},"{\"t\":\"set\",\"p\":\"payload.icon\",\"pt\":\"msg\",\"to\":\"bus\",\"tot\":\"str\"},{\"t\":\"set\",\"p\":\"payload.iconColor\",\"pt\":\"msg\",\"to\":\"red\",\"tot\":\"str\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":2310,\"y\":940,\"wires\":[[\"b4f2e2dabd5b8220\"]]},{\"id\":\"d7ca6c3cdf176e4e\",\"type\":\"ui-iframe\",\"z\":\"eacc68e72f120b0e\",\"g\":\"4e45e8ef870b86fb\",\"name\":\"\",\"group\":\"15d2dfa55e99ea43\",\"order\":0,\"src\":\"\u002Fworldmap\",\"width\":\"12\",\"height\":\"10\",\"x\":1370,\"y\":1040,\"wires\":",[146,13036],{},"},{\"id\":\"15d2dfa55e99ea43\",\"type\":\"ui-group\",\"name\":\"U.K Transportation Live\",\"page\":\"e098e3047b4a4eaa\",\"width\":\"12\",\"height\":\"1\",\"order\":-1,\"showTitle\":false,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"e098e3047b4a4eaa\",\"type\":\"ui-page\",\"name\":\"U.K Transportation Live\",\"ui\":\"c2e1aa56f50f03bd\",\"path\":\"\u002Fworldmap\",\"icon\":\"earth\",\"layout\":\"grid\",\"theme\":\"129e99574def90a3\",\"order\":-1,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"c2e1aa56f50f03bd\",\"type\":\"ui-base\",\"name\":\"Dashboard\",\"path\":\"\u002Fdashboard\",\"showPathInSidebar\":false,\"navigationStyle\":\"default\"},{\"id\":\"129e99574def90a3\",\"type\":\"ui-theme\",\"name\":\"Another Theme\",\"colors\":{\"surface\":\"#000000\",\"primary\":\"#ff4000\",\"bgPage\":\"#f0f0f0\",\"groupBg\":\"#ffffff\",\"groupOutline\":\"#d9d9d9\"},\"sizes\":{\"pagePadding\":\"9px\",\"groupGap\":\"12px\",\"groupBorderRadius\":\"9px\",\"widgetGap\":\"6px\"}}",[326,13039,13040,13046],{},[103,13041,13042,13043,13045],{},"With your flow updated to include the above, click the ",[338,13044,695],{}," button in the top-right of the Node-RED Editor.",[103,13047,13048,13049,13052],{},"Locate the ",[338,13050,13051],{},"Open Dashboard"," button at the top-right corner of the Dashboard 2.0 sidebar and click on it to navigate to the dashboard.",[15,13054,13055,13056,167],{},"Now you can view the live location of Edinburgh public transport vehicles on the dashboard. Additionally, clicking on each vehicle reveals further details such as its name, speed, and other properties you've included. Moreover, if you wish to track the live locations of your own vehicles instead of Edinburgh's public transport vehicles, you can connect your devices and access GPS and sensor data using the ",[307,13057,13058],{"href":5391},"Flowfuse device agent",[34,13060,6518],{"id":6517},[15,13062,13063],{},"In conclusion, this guide shows an easy way to map location data on Dashboard 2.0. By following these steps, you can make interactive dashboards that give you real-time info, useful for things like managing fleets and tracking logistics.",{"title":50,"searchDepth":250,"depth":250,"links":13065},[13066,13067,13068,13069,13070,13074,13075],{"id":12601,"depth":185,"text":12602},{"id":12633,"depth":185,"text":12634},{"id":12690,"depth":185,"text":12691},{"id":12862,"depth":185,"text":12863},{"id":12906,"depth":185,"text":12907,"children":13071},[13072,13073],{"id":12916,"depth":221,"text":12917},{"id":12943,"depth":221,"text":12944},{"id":12381,"depth":185,"text":12976},{"id":6517,"depth":185,"text":6518},{"type":941,"title":13077,"description":13078},"Build Real-Time Fleet Tracking Dashboards","FlowFuse gives you everything you need to connect GPS devices, visualize live location data, and deploy production-ready dashboards, all on one platform.","2024-05-13","Learn how to plot location data on Dashboard 2.0 with this comprehensive step-by-step guide.","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fmapping-location-on-dashboard-2-worldmap.png",{"keywords":13083,"excerpt":13084},"node-red dashboard map, node-red worldmap, dashboard 2.0 location, fleet tracking node-red, node-red map widget",{"type":12,"value":13085},[13086],[15,13087,12591],{},"\u002Fblog\u002F2024\u002F05\u002Fmapping-location-on-dashboard-2",{"title":12585,"description":13080},{"loc":13088},"blog\u002F2024\u002F05\u002Fmapping-location-on-dashboard-2","Step-by-step guide to plot location data on dashboard 2.0.",[9832,11070,13094,13095,966],"fleet tracking","location","This guide shows how to build a real-time fleet tracking dashboard in Node-RED Dashboard 2.0 using the node-red-contrib-web-worldmap node. It covers retrieving live vehicle location data from a public API, formatting it for the Worldmap node, and embedding the interactive map inside a Dashboard 2.0 iframe widget.","7moJubu1ZyLVfHzxLjzUOI_d4fMGBubL5fmxst9JDoA",{"id":13099,"title":13100,"authors":13101,"body":13102,"cta":13610,"date":13613,"description":13614,"extension":946,"image":13615,"lastUpdated":948,"meta":13616,"navigation":253,"path":13622,"seo":13623,"sitemap":13624,"stem":13625,"subtitle":13626,"tags":13627,"tldr":13631,"video":3,"__hash__":13632},"blog\u002Fblog\u002F2024\u002F05\u002Fnode-red-dashboard-2-layout-navigation-styling.md","Comprehensive guide: Node-RED Dashboard 2.0 layout, sidebar, and styling",[10],{"type":12,"value":13103,"toc":13576},[13104,13107,13114,13118,13121,13125,13128,13132,13143,13150,13153,13157,13167,13174,13178,13188,13195,13200,13204,13216,13223,13227,13230,13236,13240,13243,13247,13250,13253,13257,13260,13268,13275,13279,13282,13285,13289,13296,13310,13321,13325,13342,13349,13353,13357,13368,13375,13379,13382,13386,13389,13396,13399,13401,13404,13411,13415,13418,13425,13429,13432,13439,13442,13446,13449,13455,13459,13462,13466,13474,13481,13485,13491,13494,13508,13511,13532,13539,13541,13544],[15,13105,13106],{},"In this comprehensive guide, we will explore different layouts and sidebar styles in Dashboard 2.0. Additionally, we will cover how you can style Dashboard 2.0 elements effortlessly.",[15,13108,13109,13110,13113],{},"If you are new to Dashboard 2.0, refer to ",[307,13111,13112],{"href":6792},"Getting started with Node-RED Dashboard 2.0"," and make sure you have installed it.",[34,13115,13117],{"id":13116},"understanding-dashboard-20-layouts","Understanding Dashboard 2.0 layouts.",[15,13119,13120],{},"A layout in Node-RED Dashboard 2.0 refers to how groups of widgets are organized and arranged on a page. It controls the visual structure and placement of these widget groups to create an organized and easy-to-use interface.",[996,13122,13124],{"id":13123},"exploring-dashboard-20-layouts","Exploring Dashboard 2.0 layouts",[15,13126,13127],{},"In Dashboard 2.0, we have three types of layouts: Grid, Notebook, and Fixed.",[4987,13129,13131],{"id":13130},"grid-layout","Grid layout",[15,13133,13134,13135,13138,13139,13142],{},"Choosing this layout divides your dashboard page into ",[338,13136,13137],{},"12 equally-sized columns",", and you can specify how many columns your group will occupy using the ",[19,13140,13141],{},"size"," property. When groups within a row take up all available columns, a new row automatically starts. The height of each row is determined by the tallest widget in that row.",[15,13144,13145],{},[392,13146],{"alt":13147,"dataZoomable":50,"src":13148,"title":13149},"\"Screenshot of dashboard having grid layout\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-grid-layout.jpg","Screenshot of dashboard having grid layout",[15,13151,13152],{},"In the image above, you can see that the first and last widget groups occupy all 12 columns, while in the middle, two groups each take up six columns.",[4987,13154,13156],{"id":13155},"notebook-layout","Notebook layout",[15,13158,13159,13160,63,13163,13166],{},"Choosing the Notebook layout for your page in Dashboard 2.0 makes it work like a Jupyter Notebook, fixed at a width of ",[338,13161,13162],{},"1024px",[338,13164,13165],{},"centered",". Here, a groups' \"width\" defines the number of columns the group contains. The group itself will always render the full width of the Notebook. It's great for dynamic Markdown, data tables, and visuals. Groups of pages are stacked vertically.",[15,13168,13169],{},[392,13170],{"alt":13171,"dataZoomable":50,"src":13172,"title":13173},"\"Screenshot of dashboard having notebook layout\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-notebook-layout.png","Screenshot of dashboard having notebook layout",[4987,13175,13177],{"id":13176},"fixed-layout","Fixed layout",[15,13179,13180,13181,13184,13185,13187],{},"In this layout, the width value is converted to \"units\", with each unit being ",[19,13182,13183],{},"90px"," wide. For example, if you set the group width to ",[19,13186,207],{},", it will be 3 * 90 = 270px wide. Within a given group, the group size represents a column in the group's internal grid, following the same pattern as other layouts.",[15,13189,13190],{},[392,13191],{"alt":13192,"dataZoomable":50,"src":13193,"title":13194},"\"Screenshot of dashboard having fixed layout\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-fix-layout.png","Screenshot of dashboard having fixed layout",[15,13196,13197],{},[397,13198,13199],{},"Note: Currently this layout is not optimised, with plans to make it similar to Dashboard 1.0 in how it compresses content vertically, so it is recommended to use other layouts.",[996,13201,13203],{"id":13202},"setting-page-layout","Setting page layout",[326,13205,13206,13213],{},[103,13207,13208,13209,13212],{},"Navigate to the page configuration by clicking on the ",[338,13210,13211],{},"edit"," button of your page in the Dashboard 2.0 sidebar.",[103,13214,13215],{},"In the page configuration, you can select the preferred layout for that page within the layout field.",[15,13217,13218],{},[392,13219],{"alt":13220,"dataZoomable":50,"src":13221,"title":13222},"\"Image showing process of setting page layout\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-setting-new-page-layout.gif","Image showing process of setting page layout",[34,13224,13226],{"id":13225},"setting-dashboard-20-elements-size","Setting Dashboard 2.0 elements size",[15,13228,13229],{},"Setting the size for elements in Dashboard 2.0 is straightforward, but understanding the actual unit size in the size property can be a bit tricky.",[15,13231,13232,13233,167],{},"It's important to note that the size of a single horizontal unit varies depending on the layout, but the vertical size of a single row is consistently ",[338,13234,13235],{},"48px",[34,13237,13239],{"id":13238},"sizing-widgets-within-a-group","Sizing Widgets within a Group",[15,13241,13242],{},"In any layout (Grid, Notebook, or Fixed), widgets within a group are sized using a unified approach. The size property assigned to widgets determines their width within the group. Each unit in the size property represents a fraction of the group's total width. This width is determined by an internal grid established by the group.",[996,13244,13246],{"id":13245},"widget-sizing","Widget Sizing",[15,13248,13249],{},"Widgets are sized relative to the number of columns in the internal grid. For example, if a group has 4 columns and two widgets, and the first widget is set to 1 width while the second to 3 width, the first widget will occupy 25% of the group's width, and the second widget will occupy 75%.",[15,13251,13252],{},"Regardless of the layout type, the concept of sizing widgets within a group remains consistent. Whether it's the grid, notebook, or fixed layout, the same principles apply, ensuring uniformity in widget layout and design.",[996,13254,13256],{"id":13255},"setting-element-size"," Setting element size",[15,13258,13259],{},"To set the size of groups and widgets in Dashboard 2.0, follow these steps:",[326,13261,13262,13265],{},[103,13263,13264],{},"Go to the Dashboard 2.0 sidebar and click on the edit button next to the element you want to resize.",[103,13266,13267],{},"Adjust the size using the size property.",[15,13269,13270],{},[392,13271],{"alt":13272,"dataZoomable":50,"src":13273,"title":13274},"\"Image showing process of setting element size\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-setting-size.gif","Image showing process of setting element size",[34,13276,13278],{"id":13277},"understanding-dashboard-20-theme","Understanding Dashboard 2.0 Theme",[15,13280,13281],{},"The theme is a collection of colors that control the look and feel of the widgets, groups, and other elements on the page.",[15,13283,13284],{},"In Dashboard 2.0, when adding a page ( ui-page ) we can specify which theme it will use. By default, we have one theme in Dashboard 2.0, we can add more themes using the Dashboard 2.0 side panel.",[996,13286,13288],{"id":13287},"understanding-theme-properties","Understanding theme properties",[15,13290,13291,13292,13295],{},"In the theme (",[19,13293,13294],{},"ui-theme",") configuration, there are two main sections:",[100,13297,13298,13304],{},[103,13299,13300,13303],{},[338,13301,13302],{},"Colors:"," Specify colors for Navigation, primary elements, page background, group backgrounds, and outlines.",[103,13305,13306,13309],{},[338,13307,13308],{},"Sizing:"," Define the gaps between groups, page padding, group outline radius, and gaps between widgets, all in pixels.",[15,13311,13312,13313,13315,13316,167],{},"For additional information on the ",[19,13314,13294],{}," settings, please refer to the ",[307,13317,13320],{"href":13318,"rel":13319},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fnodes\u002Fconfig\u002Fui-theme.html",[311],"ui-theme documentation",[996,13322,13324],{"id":13323},"setting-a-new-page-theme","Setting a new page theme",[326,13326,13327,13330,13333,13336,13339],{},[103,13328,13329],{},"Navigate to the Dashboard 2.0 sidebar and switch to the theme tab.",[103,13331,13332],{},"Click on the top-right “+theme” button to add a new theme.",[103,13334,13335],{},"After specifying colors and sizing click on the top right update button to save the theme.",[103,13337,13338],{},"Now switch to the layout tab and click on the edit button next to the page for which you want to set a new theme.",[103,13340,13341],{},"In the page config, select the newly added theme in the Theme field.",[15,13343,13344],{},[392,13345],{"alt":13346,"dataZoomable":50,"src":13347,"title":13348},"\"Image showing process of adding new theme\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-adding-new-theme.gif","Image showing process of adding new theme",[34,13350,13352],{"id":13351},"dashboard-20-navigation","Dashboard 2.0 Navigation",[996,13354,13356],{"id":13355},"setting-sidebar","Setting sidebar",[326,13358,13359,13362,13365],{},[103,13360,13361],{},"Navigate to the Dashboard 2.0 sidebar in the Node-RED editor",[103,13363,13364],{},"Click on the \"Edit Settings\" button located at the top left side of the Dashboard 2.0 sidebar.",[103,13366,13367],{},"Select your preferred sidebar style from the \"Style\" field in the sidebar options section.",[15,13369,13370],{},[392,13371],{"alt":13372,"dataZoomable":50,"src":13373,"title":13374},"\"Image showing process of changing sidebar style\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-setting-sidebar.gif","Image showing process of changing sidebar style",[996,13376,13378],{"id":13377},"sidebar-navigation-options","Sidebar Navigation Options",[15,13380,13381],{},"In Dashboard 2.0, we have 5 different navigation options for your application.",[4987,13383,13385],{"id":13384},"collapsing","Collapsing",[15,13387,13388],{},"This is the default sidebar, when it's opened, the page content adjusts to the width of the sidebar.",[15,13390,13391],{},[392,13392],{"alt":13393,"dataZoomable":50,"src":13394,"title":13395},"\"Image showing 'Collapsing' sidebar\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-collapsing-sidebar.gif","Image showing Collapsing sidebar",[15,13397,13398],{},"You can see in the image above how the page content automatically adjusts when the sidebar is opened.",[4987,13400,10951],{"id":10950},[15,13402,13403],{},"In this type, the sidebar is always visible and fixed on the left side, and the top menu icon is hidden. The page content adjusts to the width of the sidebar.",[15,13405,13406],{},[392,13407],{"alt":13408,"dataZoomable":50,"src":13409,"title":13410},"\"Image showing 'Fixed' sidebar\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-fixed-layout.png","Image showing Fixed sidebar",[4987,13412,13414],{"id":13413},"collapse-to-icon","Collapse to icon",[15,13416,13417],{},"This type of sidebar is similar to the collapsible one, but when the sidebar is collapsed, you can still navigate through different pages as the page icons become visible.",[15,13419,13420],{},[392,13421],{"alt":13422,"dataZoomable":50,"src":13423,"title":13424},"\"Image showing 'Collapse to icon' sidebar\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-collaps-to-icon-sidebar.gif","Image showing Collapse to icon sidebar",[4987,13426,13428],{"id":13427},"apear-over-content","Apear over content",[15,13430,13431],{},"When the sidebar is opened, the page is partially covered by a transparent layer, and the sidebar appears on top of this layer",[15,13433,13434],{},[392,13435],{"alt":13436,"dataZoomable":50,"src":13437,"title":13438},"\"Image showing 'Apear over content' sidebar\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-appear-over-content.gif","screenshot displaying searching for botFather bot for creating custom bot",[15,13440,13441],{},"In this type of sidebar, you can notice how the sidebar opens without affecting the width of the page content",[4987,13443,13445],{"id":13444},"always-hide","Always hide",[15,13447,13448],{},"In this type, the sidebar is always hidden, and navigation between different pages can be achieved using the ui-control widget.",[15,13450,13451],{},[392,13452],{"alt":13453,"dataZoomable":50,"src":13454,"title":13438},"\"screenshot displaying searching for botFather bot for creating custom bot\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-always-hidden.png",[34,13456,13458],{"id":13457},"customising-your-dashboard-20-further","Customising your Dashboard 2.0 further",[15,13460,13461],{}," \nIn Dashboard 2.0, we can add classes to almost all widgets, pages, and groups and style them using CSS.",[996,13463,13465],{"id":13464},"adding-classes","Adding classes",[326,13467,13468,13471],{},[103,13469,13470],{},"To add classes to your widget, page, or group, you'll need to open its configuration",[103,13472,13473],{},"Find the 'Class' field and enter your class.",[15,13475,13476],{},[392,13477],{"alt":13478,"dataZoomable":50,"src":13479,"title":13480},"\"Screenshot showing the class property input field\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-showing-class-property-feild.png","Screenshot showing the class property input field",[996,13482,13484],{"id":13483},"writing-custom-css","Writing custom CSS",[15,13486,13487,13488,13490],{},"In Dashboard 2.0, the ",[19,13489,5152],{}," node allows you to write custom CSS for Dashboard 2.0.",[15,13492,13493],{},"In the template node, you can add CSS for two different scopes:",[100,13495,13496,13502],{},[103,13497,13498,13501],{},[338,13499,13500],{},"Single Page:"," Selecting this allows you to specify CSS that is constrained to a single page of your dashboard.",[103,13503,13504,13507],{},[338,13505,13506],{},"All Pages:"," Selecting this allows you to define CSS that will apply across your whole dashboard.",[15,13509,13510],{},"To define your own CSS:",[326,13512,13513,13516,13519,13529],{},[103,13514,13515],{},"Drag an ui-template widget onto the canvas.",[103,13517,13518],{},"Double-click on it and select the scope within the type field.",[103,13520,13521,13522,13525,13526,13528],{},"If you select the \"CSS (Single Page)\" type, you'll then need to select the ",[19,13523,13524],{},"ui-page"," to which your custom class definitions will apply. If you select the \"CSS (All Pages)\" type, then you'll need to select the ",[19,13527,12959],{}," that includes those pages to which you want to add styling.",[103,13530,13531],{},"Now you can write your custom CSS within the ui-template.",[15,13533,13534],{},[392,13535],{"alt":13536,"dataZoomable":50,"src":13537,"title":13538},"\"Image displaying the left side with the page and group where custom CSS has been applied, and the right side showcasing the UI-template with the corresponding CSS\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling-adding-style.png","Image displaying the left side with the page and group where custom CSS has been applied, and the right side showcasing the UI-template with the corresponding CSS",[34,13540,9764],{"id":9763},[15,13542,13543],{},"To delve deeper into Node-RED Dashboard 2.0, we recommend exploring the following resources:",[100,13545,13546,13553,13561,13569],{},[103,13547,13548,13552],{},[307,13549,13551],{"href":13550},"\u002Fblog\u002Fdashboard\u002F","FlowFuse Dashboard Articles"," - Collection of examples and guides written by FlowFuse.",[103,13554,13555,13560],{},[307,13556,13559],{"href":13557,"rel":13558},"https:\u002F\u002Fdashboard.flowfuse.com\u002F",[311],"Node-RED Dashboard 2.0 Documentation"," - Detailed information for each of the nodes available in Dashboard 2.0, as well as useful guides on building custom nodes and widgets of your own.",[103,13562,13563,13568],{},[307,13564,13567],{"href":13565,"rel":13566},"https:\u002F\u002Fdiscourse.nodered.org\u002Ftag\u002Fdashboard-2",[311],"Node-RED Forums - Dashboard 2.0"," - The Node-RED forums are a great place to ask questions, share your projects and get help from the community.",[103,13570,13571,13575],{},[307,13572,13574],{"href":13573},"\u002Febooks\u002Fbeginner-guide-to-a-professional-nodered\u002F","Beginner Guide to a Professional Node-RED"," - A free guide to an enterprise-ready Node-RED. Learn all about Node-RED history, securing your flows, and dashboard data visualization.",{"title":50,"searchDepth":250,"depth":250,"links":13577},[13578,13586,13587,13591,13595,13605,13609],{"id":13116,"depth":185,"text":13117,"children":13579},[13580,13585],{"id":13123,"depth":221,"text":13124,"children":13581},[13582,13583,13584],{"id":13130,"depth":250,"text":13131},{"id":13155,"depth":250,"text":13156},{"id":13176,"depth":250,"text":13177},{"id":13202,"depth":221,"text":13203},{"id":13225,"depth":185,"text":13226},{"id":13238,"depth":185,"text":13239,"children":13588},[13589,13590],{"id":13245,"depth":221,"text":13246},{"id":13255,"depth":221,"text":13256},{"id":13277,"depth":185,"text":13278,"children":13592},[13593,13594],{"id":13287,"depth":221,"text":13288},{"id":13323,"depth":221,"text":13324},{"id":13351,"depth":185,"text":13352,"children":13596},[13597,13598],{"id":13355,"depth":221,"text":13356},{"id":13377,"depth":221,"text":13378,"children":13599},[13600,13601,13602,13603,13604],{"id":13384,"depth":250,"text":13385},{"id":10950,"depth":250,"text":10951},{"id":13413,"depth":250,"text":13414},{"id":13427,"depth":250,"text":13428},{"id":13444,"depth":250,"text":13445},{"id":13457,"depth":185,"text":13458,"children":13606},[13607,13608],{"id":13464,"depth":221,"text":13465},{"id":13483,"depth":221,"text":13484},{"id":9763,"depth":185,"text":9764},{"type":941,"title":13611,"description":13612},"Build Beautiful Node-RED Dashboards Faster","FlowFuse gives you everything you need to build, style, and deploy Node-RED Dashboard 2.0 applications, with multi-user support, authentication, and production-ready infrastructure built in. Go from prototype to a secure, shareable dashboard without managing servers. Start free today.","2024-05-10","Discover Node-RED Dashboard 2.0's three layouts (Grid, Notebook, Fixed), five navigation sidebar styles, themes, and custom CSS styling, a complete guide to controlling how your dashboards look and behave.","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fnode-red-dashboard-2-layout-navigation-styling.png",{"keywords":13617,"excerpt":13618},"node-red dashboard 2.0, node-red dashboard layout, dashboard 2.0 grid layout, node-red dashboard sidebar, node-red dashboard styling, node-red dashboard css, ui-template node, flowfuse dashboard",{"type":12,"value":13619},[13620],[15,13621,13106],{},"\u002Fblog\u002F2024\u002F05\u002Fnode-red-dashboard-2-layout-navigation-styling",{"title":13100,"description":13614},{"loc":13622},"blog\u002F2024\u002F05\u002Fnode-red-dashboard-2-layout-navigation-styling","Explore Dashboard 2.0 Different layouts and sidebars. learn how to style Dashboard 2.0 elements effortlessly.",[9832,11070,13628,13629,13630,966],"flowfuse dashboard","customizing dashboard","dashboard 2.0","Node-RED Dashboard 2.0 gives you three page layouts: Grid (12 equal columns), Notebook (centered 1024px, stacked groups), and Fixed (90px per width unit); plus five navigation sidebar styles (Collapsing, Fixed, Collapse to icon, Appear over content, Always hide). Widgets are sized as a fraction of their group's internal grid, with rows a consistent 48px tall. You control colors and spacing through themes (ui-theme) and can go further with custom CSS by adding classes and writing styles in a ui-template node, scoped to a single page or all pages.","qu_-ug7umATFlAlIEkkPylpb1Vkl4dEpxKc743BM0dg",{"id":13634,"title":13635,"authors":13636,"body":13637,"cta":14892,"date":14895,"description":14896,"extension":946,"image":14897,"lastUpdated":14898,"meta":14899,"navigation":253,"path":14956,"seo":14957,"sitemap":14958,"stem":14959,"subtitle":14960,"tags":14961,"tldr":14962,"video":3,"__hash__":14963},"blog\u002Fblog\u002F2024\u002F05\u002Funderstanding-node-flow-global-environment-variables-in-node-red.md","How to Use Variables in Node-RED: Flow, Global, Context & Environment (2026)",[10],{"type":12,"value":13638,"toc":14871},[13639,13687,13690,13694,13697,13700,13714,13720,13726,13730,13733,13739,13743,13749,13752,13763,13770,13773,13839,13845,13848,13855,13858,13919,13925,13932,13936,13939,13945,13949,13954,13957,13964,13966,14023,14028,14031,14038,14040,14100,14106,14113,14117,14120,14126,14130,14133,14138,14199,14204,14267,14273,14277,14280,14283,14287,14292,14295,14300,14303,14411,14416,14419,14426,14428,14538,14541,14545,14548,14555,14560,14577,14580,14587,14591,14594,14597,14603,14609,14613,14618,14632,14639,14645,14650,14653,14660,14662,14724,14727,14756,14763,14773,14779,14781,14784,14789,14803,14818,14824,14830,14836,14842,14844,14862,14865,14868],[15,13640,13641,13642,13645,13646,13648,13649,13652,13653,13656,13657,13660,13661,13664,13665,13667,13668,213,13671,1255,13674,213,13677,1262,13680,213,13683,13686],{},"Node-RED has four types of variables: ",[338,13643,13644],{},"message variables"," that travel with ",[19,13647,260],{}," through your nodes, and ",[338,13650,13651],{},"context variables"," in three scopes, ",[338,13654,13655],{},"node"," (private to a single function node), ",[338,13658,13659],{},"flow"," (shared within one tab), and ",[338,13662,13663],{},"global"," (shared across the entire instance), plus ",[338,13666,12669],{}," for configuration and secrets. You set and retrieve context variables with ",[19,13669,13670],{},"global.set()",[19,13672,13673],{},"global.get()",[19,13675,13676],{},"flow.set()",[19,13678,13679],{},"flow.get()",[19,13681,13682],{},"context.set()",[19,13684,13685],{},"context.get()"," in function nodes, or visually through the change node. Context variables are stored in memory by default and are lost on restart, so any state that must survive a reboot needs persistent storage.",[15,13688,13689],{},"That's the short version. The rest of this guide walks through each variable type in detail, showing you exactly how to set, retrieve, and delete them, when to use each scope, and how to make them persist across restarts and redeployments.",[34,13691,13693],{"id":13692},"what-are-node-red-variables","What Are Node-RED Variables?",[15,13695,13696],{},"Variables in Node-RED serve as containers for storing and managing data throughout your application. Understanding the different types and their scopes is essential for building efficient, organized flows.",[15,13698,13699],{},"Node-RED offers three primary variable categories:",[15,13701,13702,13705,13706,13708,13709,13713],{},[338,13703,13704],{},"Message variables"," travel with the message object as it flows through your nodes. The most common example is ",[19,13707,382],{},", which carries the primary data between nodes. For a deeper dive into message handling, see the ",[307,13710,13712],{"href":13711},"\u002Fnode-red\u002Fgetting-started\u002Fnode-red-messages\u002F","Understanding Node-RED Messages"," guide.",[15,13715,13716,13719],{},[338,13717,13718],{},"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.",[15,13721,13722,13725],{},[338,13723,13724],{},"Environment variables"," handle configuration data and sensitive information like API keys and credentials. By storing this data separately from your flows, you maintain security and make configuration management more flexible.",[34,13727,13729],{"id":13728},"global-variables-instance-wide-data-storage","Global Variables: Instance-Wide Data Storage",[15,13731,13732],{},"Global variables provide a centralized storage mechanism accessible throughout your entire Node-RED instance. Any function, change, inject, or switch node can read or write global variables, making them perfect for sharing data across multiple flows.",[15,13734,13735,13738],{},[338,13736,13737],{},"When to use global variables:"," Consider using them for system-wide settings, shared configuration, or data that multiple flows need to access. For example, in a home automation system with separate flows for lighting, security, and climate control, global variables can store user preferences that all flows reference.",[996,13740,13742],{"id":13741},"working-with-global-variables","Working with Global Variables",[15,13744,13745,13748],{},[338,13746,13747],{},"Setting global variables"," can be done through the change node or programmatically in a function node:",[15,13750,13751],{},"Using the change node:",[326,13753,13754,13757,13760],{},[103,13755,13756],{},"Select \"global\" from the variable type dropdown",[103,13758,13759],{},"Enter your variable name",[103,13761,13762],{},"Set the value or expression",[15,13764,13765],{},[392,13766],{"alt":13767,"dataZoomable":50,"src":13768,"title":13769},"\"Screenshot showing how to set global variable using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-setting-global-variable-using-change-node.png","Screenshot showing how to set global variable using the change node",[15,13771,13772],{},"Using a function node:",[42,13774,13776],{"className":140,"code":13775,"language":142,"meta":50,"style":50},"global.set('userName', 'John');\nglobal.set('systemMode', 'active');\n",[19,13777,13778,13809],{"__ignoreMap":50},[146,13779,13780,13782,13784,13787,13789,13791,13794,13796,13798,13800,13803,13805,13807],{"class":148,"line":149},[146,13781,13663],{"class":156},[146,13783,167],{"class":160},[146,13785,13786],{"class":170},"set",[146,13788,203],{"class":156},[146,13790,1471],{"class":160},[146,13792,13793],{"class":1554},"userName",[146,13795,1471],{"class":160},[146,13797,276],{"class":160},[146,13799,1474],{"class":160},[146,13801,13802],{"class":1554},"John",[146,13804,1471],{"class":160},[146,13806,1477],{"class":156},[146,13808,182],{"class":160},[146,13810,13811,13813,13815,13817,13819,13821,13824,13826,13828,13830,13833,13835,13837],{"class":148,"line":185},[146,13812,13663],{"class":156},[146,13814,167],{"class":160},[146,13816,13786],{"class":170},[146,13818,203],{"class":156},[146,13820,1471],{"class":160},[146,13822,13823],{"class":1554},"systemMode",[146,13825,1471],{"class":160},[146,13827,276],{"class":160},[146,13829,1474],{"class":160},[146,13831,13832],{"class":1554},"active",[146,13834,1471],{"class":160},[146,13836,1477],{"class":156},[146,13838,182],{"class":160},[15,13840,13841,13844],{},[338,13842,13843],{},"Retrieving global variables"," follows a similar pattern:",[15,13846,13847],{},"In a change, inject, or switch node, simply set the action to “set”, choose the type as “global”, and specify the variable name.",[15,13849,13850],{},[392,13851],{"alt":13852,"dataZoomable":50,"src":13853,"title":13854},"\"Screenshot showing how to retrieve global variables using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-retrieving-global-variable-using-change-node.png","Screenshot showing how to retrieve global variables using the change node",[15,13856,13857],{},"In function nodes:",[42,13859,13861],{"className":140,"code":13860,"language":142,"meta":50,"style":50},"const userName = global.get('userName');\nconst mode = global.get('systemMode');\n",[19,13862,13863,13892],{"__ignoreMap":50},[146,13864,13865,13867,13870,13872,13875,13877,13880,13882,13884,13886,13888,13890],{"class":148,"line":149},[146,13866,153],{"class":152},[146,13868,13869],{"class":156}," userName ",[146,13871,161],{"class":160},[146,13873,13874],{"class":156}," global",[146,13876,167],{"class":160},[146,13878,13879],{"class":170},"get",[146,13881,203],{"class":156},[146,13883,1471],{"class":160},[146,13885,13793],{"class":1554},[146,13887,1471],{"class":160},[146,13889,1477],{"class":156},[146,13891,182],{"class":160},[146,13893,13894,13896,13899,13901,13903,13905,13907,13909,13911,13913,13915,13917],{"class":148,"line":185},[146,13895,153],{"class":152},[146,13897,13898],{"class":156}," mode ",[146,13900,161],{"class":160},[146,13902,13874],{"class":156},[146,13904,167],{"class":160},[146,13906,13879],{"class":170},[146,13908,203],{"class":156},[146,13910,1471],{"class":160},[146,13912,13823],{"class":1554},[146,13914,1471],{"class":160},[146,13916,1477],{"class":156},[146,13918,182],{"class":160},[15,13920,13921,13924],{},[338,13922,13923],{},"Deleting global variables"," can be accomplished through the change node by selecting \"delete\" from the action dropdown, or via the Context Data sidebar panel, which provides a comprehensive view of all variables.",[15,13926,13927],{},[392,13928],{"alt":13929,"dataZoomable":50,"src":13930,"title":13931},"\"Screenshot showing how to delete global variable using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-deleting-global-variable-using-change-node.png","Screenshot showing how to delete global variable using the change node",[34,13933,13935],{"id":13934},"flow-variables-tab-scoped-data","Flow Variables: Tab-Scoped Data",[15,13937,13938],{},"Flow variables exist within a single tab or flow in your Node-RED editor. They're accessible to all nodes within that specific flow but isolated from other flows, providing logical data separation.",[15,13940,13941,13944],{},[338,13942,13943],{},"When to use flow variables:"," Use them for data that's relevant only to a specific workflow. For instance, in a temperature monitoring flow with multiple sensor nodes, flow variables can track the current reading, alert thresholds, or calculation results, data that doesn't need to be shared with other parts of your application.",[996,13946,13948],{"id":13947},"working-with-flow-variables","Working with Flow Variables",[15,13950,13951],{},[338,13952,13953],{},"Setting flow variables:",[15,13955,13956],{},"Using the change node, select the action “set”, choose “flow” as the variable type, and configure your variable.",[15,13958,13959],{},[392,13960],{"alt":13961,"dataZoomable":50,"src":13962,"title":13963},"\"Screenshot showing how to set flow variable using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-setting-flow-variable-using-change-node.png","Screenshot showing how to set flow variable using the change node",[15,13965,13857],{},[42,13967,13969],{"className":140,"code":13968,"language":142,"meta":50,"style":50},"flow.set('currentTemp', 72.5);\nflow.set('alertThreshold', 85);\n",[19,13970,13971,13997],{"__ignoreMap":50},[146,13972,13973,13975,13977,13979,13981,13983,13986,13988,13990,13993,13995],{"class":148,"line":149},[146,13974,13659],{"class":156},[146,13976,167],{"class":160},[146,13978,13786],{"class":170},[146,13980,203],{"class":156},[146,13982,1471],{"class":160},[146,13984,13985],{"class":1554},"currentTemp",[146,13987,1471],{"class":160},[146,13989,276],{"class":160},[146,13991,13992],{"class":206}," 72.5",[146,13994,1477],{"class":156},[146,13996,182],{"class":160},[146,13998,13999,14001,14003,14005,14007,14009,14012,14014,14016,14019,14021],{"class":148,"line":185},[146,14000,13659],{"class":156},[146,14002,167],{"class":160},[146,14004,13786],{"class":170},[146,14006,203],{"class":156},[146,14008,1471],{"class":160},[146,14010,14011],{"class":1554},"alertThreshold",[146,14013,1471],{"class":160},[146,14015,276],{"class":160},[146,14017,14018],{"class":206}," 85",[146,14020,1477],{"class":156},[146,14022,182],{"class":160},[15,14024,14025],{},[338,14026,14027],{},"Retrieving flow variables:",[15,14029,14030],{},"In a change, inject, or switch node, simply set the action to “set”, choose the type as “flow”, and specify the variable name.",[15,14032,14033],{},[392,14034],{"alt":14035,"dataZoomable":50,"src":14036,"title":14037},"\"Screenshot showing how to retrieve flow variable using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-retrieving-flow-variable-using-change-node.png","Screenshot showing how to retrieve flow variable using the change node",[15,14039,13857],{},[42,14041,14043],{"className":140,"code":14042,"language":142,"meta":50,"style":50},"const temp = flow.get('currentTemp');\nconst threshold = flow.get('alertThreshold');\n",[19,14044,14045,14073],{"__ignoreMap":50},[146,14046,14047,14049,14052,14054,14057,14059,14061,14063,14065,14067,14069,14071],{"class":148,"line":149},[146,14048,153],{"class":152},[146,14050,14051],{"class":156}," temp ",[146,14053,161],{"class":160},[146,14055,14056],{"class":156}," flow",[146,14058,167],{"class":160},[146,14060,13879],{"class":170},[146,14062,203],{"class":156},[146,14064,1471],{"class":160},[146,14066,13985],{"class":1554},[146,14068,1471],{"class":160},[146,14070,1477],{"class":156},[146,14072,182],{"class":160},[146,14074,14075,14077,14080,14082,14084,14086,14088,14090,14092,14094,14096,14098],{"class":148,"line":185},[146,14076,153],{"class":152},[146,14078,14079],{"class":156}," threshold ",[146,14081,161],{"class":160},[146,14083,14056],{"class":156},[146,14085,167],{"class":160},[146,14087,13879],{"class":170},[146,14089,203],{"class":156},[146,14091,1471],{"class":160},[146,14093,14011],{"class":1554},[146,14095,1471],{"class":160},[146,14097,1477],{"class":156},[146,14099,182],{"class":160},[15,14101,14102,14105],{},[338,14103,14104],{},"Deleting flow variables"," works the same way as global variables, use the change node's delete action or the Context Data panel.",[15,14107,14108],{},[392,14109],{"alt":14110,"dataZoomable":50,"src":14111,"title":14112},"\"Screenshot showing how to delete flow variable using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-deleting-flow-variable-using-change-node.png","Screenshot showing how to delete flow variable using the change node",[34,14114,14116],{"id":14115},"node-variables-node-level-isolation","Node Variables: Node-Level Isolation",[15,14118,14119],{},"Node variables (also called node context) are the most restrictive scope, they exist only within a single node. No other node can access or modify these variables, making them ideal for maintaining private state.",[15,14121,14122,14125],{},[338,14123,14124],{},"When to use node variables:"," Perfect for counters, temporary calculations, or any data that should remain private to a specific node. For example, a function node that generates unique IDs for database records can maintain a counter variable that's never exposed to other parts of your flow.",[996,14127,14129],{"id":14128},"working-with-node-variables","Working with Node Variables",[15,14131,14132],{},"Node variables are local to a Function node and cannot be read or modified by other nodes.",[15,14134,14135],{},[338,14136,14137],{},"Setting node variables:",[42,14139,14141],{"className":140,"code":14140,"language":142,"meta":50,"style":50},"context.set('counter', 0);\ncontext.set('lastProcessedId', 'ABC123');\n",[19,14142,14143,14169],{"__ignoreMap":50},[146,14144,14145,14148,14150,14152,14154,14156,14159,14161,14163,14165,14167],{"class":148,"line":149},[146,14146,14147],{"class":156},"context",[146,14149,167],{"class":160},[146,14151,13786],{"class":170},[146,14153,203],{"class":156},[146,14155,1471],{"class":160},[146,14157,14158],{"class":1554},"counter",[146,14160,1471],{"class":160},[146,14162,276],{"class":160},[146,14164,2503],{"class":206},[146,14166,1477],{"class":156},[146,14168,182],{"class":160},[146,14170,14171,14173,14175,14177,14179,14181,14184,14186,14188,14190,14193,14195,14197],{"class":148,"line":185},[146,14172,14147],{"class":156},[146,14174,167],{"class":160},[146,14176,13786],{"class":170},[146,14178,203],{"class":156},[146,14180,1471],{"class":160},[146,14182,14183],{"class":1554},"lastProcessedId",[146,14185,1471],{"class":160},[146,14187,276],{"class":160},[146,14189,1474],{"class":160},[146,14191,14192],{"class":1554},"ABC123",[146,14194,1471],{"class":160},[146,14196,1477],{"class":156},[146,14198,182],{"class":160},[15,14200,14201],{},[338,14202,14203],{},"Retrieving node variables:",[42,14205,14207],{"className":140,"code":14206,"language":142,"meta":50,"style":50},"let counter = context.get('counter');\ncounter++;\ncontext.set('counter', counter);\n",[19,14208,14209,14237,14244],{"__ignoreMap":50},[146,14210,14211,14213,14216,14218,14221,14223,14225,14227,14229,14231,14233,14235],{"class":148,"line":149},[146,14212,1441],{"class":152},[146,14214,14215],{"class":156}," counter ",[146,14217,161],{"class":160},[146,14219,14220],{"class":156}," context",[146,14222,167],{"class":160},[146,14224,13879],{"class":170},[146,14226,203],{"class":156},[146,14228,1471],{"class":160},[146,14230,14158],{"class":1554},[146,14232,1471],{"class":160},[146,14234,1477],{"class":156},[146,14236,182],{"class":160},[146,14238,14239,14241],{"class":148,"line":185},[146,14240,14158],{"class":156},[146,14242,14243],{"class":160},"++;\n",[146,14245,14246,14248,14250,14252,14254,14256,14258,14260,14262,14265],{"class":148,"line":221},[146,14247,14147],{"class":156},[146,14249,167],{"class":160},[146,14251,13786],{"class":170},[146,14253,203],{"class":156},[146,14255,1471],{"class":160},[146,14257,14158],{"class":1554},[146,14259,1471],{"class":160},[146,14261,276],{"class":160},[146,14263,14264],{"class":156}," counter)",[146,14266,182],{"class":160},[15,14268,14269,14272],{},[338,14270,14271],{},"Deleting node variables"," must be done through the Context Data sidebar panel.",[34,14274,14276],{"id":14275},"persistent-storage-with-flowfuse","Persistent Storage with FlowFuse",[15,14278,14279],{},"By default, all context variables (node, flow, and global) are stored in memory. This means they're lost whenever you restart Node-RED or redeploy your flows. For production applications, this is often unacceptable.",[15,14281,14282],{},"FlowFuse provides persistent storage that survives restarts, redeployments, and system updates. This ensures your application state remains intact across sessions.",[996,14284,14286],{"id":14285},"using-persistent-storage","Using Persistent Storage",[15,14288,14289],{},[338,14290,14291],{},"Setting persistent variables:",[15,14293,14294],{},"In the change node, select \"persistent\" from the store dropdown instead of \"memory\".",[15,14296,14297],{},[392,14298],{"alt":13767,"dataZoomable":50,"src":14299,"title":13769},"\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-change-node-persistent-store-option-for-while-setting-variable.gif",[15,14301,14302],{},"In function nodes, add \"persistent\" as a third parameter:",[42,14304,14306],{"className":140,"code":14305,"language":142,"meta":50,"style":50},"global.set('userData', userData, 'persistent');\nflow.set('sessionConfig', config, 'persistent');\ncontext.set('processedCount', count, 'persistent');\n",[19,14307,14308,14343,14377],{"__ignoreMap":50},[146,14309,14310,14312,14314,14316,14318,14320,14323,14325,14327,14330,14332,14334,14337,14339,14341],{"class":148,"line":149},[146,14311,13663],{"class":156},[146,14313,167],{"class":160},[146,14315,13786],{"class":170},[146,14317,203],{"class":156},[146,14319,1471],{"class":160},[146,14321,14322],{"class":1554},"userData",[146,14324,1471],{"class":160},[146,14326,276],{"class":160},[146,14328,14329],{"class":156}," userData",[146,14331,276],{"class":160},[146,14333,1474],{"class":160},[146,14335,14336],{"class":1554},"persistent",[146,14338,1471],{"class":160},[146,14340,1477],{"class":156},[146,14342,182],{"class":160},[146,14344,14345,14347,14349,14351,14353,14355,14358,14360,14362,14365,14367,14369,14371,14373,14375],{"class":148,"line":185},[146,14346,13659],{"class":156},[146,14348,167],{"class":160},[146,14350,13786],{"class":170},[146,14352,203],{"class":156},[146,14354,1471],{"class":160},[146,14356,14357],{"class":1554},"sessionConfig",[146,14359,1471],{"class":160},[146,14361,276],{"class":160},[146,14363,14364],{"class":156}," config",[146,14366,276],{"class":160},[146,14368,1474],{"class":160},[146,14370,14336],{"class":1554},[146,14372,1471],{"class":160},[146,14374,1477],{"class":156},[146,14376,182],{"class":160},[146,14378,14379,14381,14383,14385,14387,14389,14392,14394,14396,14399,14401,14403,14405,14407,14409],{"class":148,"line":221},[146,14380,14147],{"class":156},[146,14382,167],{"class":160},[146,14384,13786],{"class":170},[146,14386,203],{"class":156},[146,14388,1471],{"class":160},[146,14390,14391],{"class":1554},"processedCount",[146,14393,1471],{"class":160},[146,14395,276],{"class":160},[146,14397,14398],{"class":156}," count",[146,14400,276],{"class":160},[146,14402,1474],{"class":160},[146,14404,14336],{"class":1554},[146,14406,1471],{"class":160},[146,14408,1477],{"class":156},[146,14410,182],{"class":160},[15,14412,14413],{},[338,14414,14415],{},"Retrieving persistent variables:",[15,14417,14418],{},"In a change, inject, or switch node, ensure you're selecting from the \"persistent\" store.",[15,14420,14421],{},[392,14422],{"alt":14423,"dataZoomable":50,"src":14424,"title":14425},"\"Screenshot showing how to retrieve global variable using the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-change-node-persistent-store-option.gif","Screenshot showing how to retrieve global variable using the change node",[15,14427,13857],{},[42,14429,14431],{"className":140,"code":14430,"language":142,"meta":50,"style":50},"const userData = global.get('userData', 'persistent');\nconst config = flow.get('sessionConfig', 'persistent');\nconst count = context.get('processedCount', 'persistent');\n",[19,14432,14433,14468,14503],{"__ignoreMap":50},[146,14434,14435,14437,14440,14442,14444,14446,14448,14450,14452,14454,14456,14458,14460,14462,14464,14466],{"class":148,"line":149},[146,14436,153],{"class":152},[146,14438,14439],{"class":156}," userData ",[146,14441,161],{"class":160},[146,14443,13874],{"class":156},[146,14445,167],{"class":160},[146,14447,13879],{"class":170},[146,14449,203],{"class":156},[146,14451,1471],{"class":160},[146,14453,14322],{"class":1554},[146,14455,1471],{"class":160},[146,14457,276],{"class":160},[146,14459,1474],{"class":160},[146,14461,14336],{"class":1554},[146,14463,1471],{"class":160},[146,14465,1477],{"class":156},[146,14467,182],{"class":160},[146,14469,14470,14472,14475,14477,14479,14481,14483,14485,14487,14489,14491,14493,14495,14497,14499,14501],{"class":148,"line":185},[146,14471,153],{"class":152},[146,14473,14474],{"class":156}," config ",[146,14476,161],{"class":160},[146,14478,14056],{"class":156},[146,14480,167],{"class":160},[146,14482,13879],{"class":170},[146,14484,203],{"class":156},[146,14486,1471],{"class":160},[146,14488,14357],{"class":1554},[146,14490,1471],{"class":160},[146,14492,276],{"class":160},[146,14494,1474],{"class":160},[146,14496,14336],{"class":1554},[146,14498,1471],{"class":160},[146,14500,1477],{"class":156},[146,14502,182],{"class":160},[146,14504,14505,14507,14510,14512,14514,14516,14518,14520,14522,14524,14526,14528,14530,14532,14534,14536],{"class":148,"line":221},[146,14506,153],{"class":152},[146,14508,14509],{"class":156}," count ",[146,14511,161],{"class":160},[146,14513,14220],{"class":156},[146,14515,167],{"class":160},[146,14517,13879],{"class":170},[146,14519,203],{"class":156},[146,14521,1471],{"class":160},[146,14523,14391],{"class":1554},[146,14525,1471],{"class":160},[146,14527,276],{"class":160},[146,14529,1474],{"class":160},[146,14531,14336],{"class":1554},[146,14533,1471],{"class":160},[146,14535,1477],{"class":156},[146,14537,182],{"class":160},[15,14539,14540],{},"Persistent storage allows Node-RED to retain state between restarts, crucial for historical metrics, long-running counters, dashboard application data, and any flow that must persist through a reboot without losing information.",[34,14542,14544],{"id":14543},"the-context-data-panel","The Context Data Panel",[15,14546,14547],{},"Node-RED includes a dedicated Context Data panel in the sidebar that provides visibility into all your variables. This panel is invaluable for debugging and understanding your application's state.",[15,14549,14550],{},[392,14551],{"alt":14552,"dataZoomable":50,"src":14553,"title":14554},"\"Screenshot showing Node-RED Context data tab\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-context-data-tab.png","Screenshot showing Node-RED Context data tab",[15,14556,14557],{},[338,14558,14559],{},"Features of the Context Data panel:",[100,14561,14562,14565,14568,14571,14574],{},[103,14563,14564],{},"View all node, flow, and global variables in organized sections",[103,14566,14567],{},"See when each variable was last updated",[103,14569,14570],{},"Copy variable names or values with one click",[103,14572,14573],{},"Refresh individual variables to see current values",[103,14575,14576],{},"Delete variables directly from the interface",[15,14578,14579],{},"To access this panel, open the sidebar and choose ‘Context Data’ from the dropdown menu. Use the refresh icon in each section to update the display with the latest values.",[15,14581,14582],{},[392,14583],{"alt":14584,"dataZoomable":50,"src":14585,"title":14586},"\"Screenshot showing Node-RED Context data tab options for managing variables\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-context-data-tab-options-for-varrables.png","Screenshot showing Node-RED Context data tab options for managing variables",[34,14588,14590],{"id":14589},"environment-variables-secure-configuration","Environment Variables: Secure Configuration",[15,14592,14593],{},"Environment variables serve a different purpose than context variables, they're designed for configuration data, especially sensitive information that shouldn't be hardcoded in your flows.",[15,14595,14596],{},"Node-RED supports environment variables at two levels:",[15,14598,14599,14602],{},[338,14600,14601],{},"Flow-level environment variables"," are accessible only within a specific flow. This is useful when different flows need different configurations. For example, one flow might connect to a development database while another connects to production, each using its own credentials.",[15,14604,14605,14608],{},[338,14606,14607],{},"Global-level environment variables"," are accessible across all flows in your instance. Use these for shared configuration like API keys that multiple flows need to reference.",[996,14610,14612],{"id":14611},"working-with-environment-variables","Working with Environment Variables",[15,14614,14615],{},[338,14616,14617],{},"Setting flow-level environment variables:",[326,14619,14620,14623,14626,14629],{},[103,14621,14622],{},"Double-click on the flow tab to open the edit dialog",[103,14624,14625],{},"Navigate to the \"Environment Variables\" section",[103,14627,14628],{},"Add your variables as name-value pairs",[103,14630,14631],{},"Click Done and deploy",[15,14633,14634],{},[392,14635],{"alt":14636,"dataZoomable":50,"src":14637,"title":14638},"\"Screenshot showing how to set flow level environment variables\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-setting-flow-scope-enviroment-variable.gif","Screenshot showing how to set flow level environment variables",[15,14640,14641,14642,14644],{},"For global-level environment variables, see ",[307,14643,12103],{"href":11685}," for detailed instructions.",[15,14646,14647],{},[338,14648,14649],{},"Accessing environment variables:",[15,14651,14652],{},"In change, inject, or switch nodes, select \"env variable\" and specify the name.",[15,14654,14655],{},[392,14656],{"alt":14657,"dataZoomable":50,"src":14658,"title":14659},"\"Screenshot showing how to retrieve environment variable in the change node\"","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Fvariables-in-node-red-retrieving-environment-variable-using-change-node.png","Screenshot showing how to retrieve environment variable in the change node",[15,14661,13857],{},[42,14663,14665],{"className":140,"code":14664,"language":142,"meta":50,"style":50},"const apiKey = env.get('API_KEY');\nconst dbHost = env.get('DB_HOST');\n",[19,14666,14667,14696],{"__ignoreMap":50},[146,14668,14669,14671,14674,14676,14679,14681,14683,14685,14687,14690,14692,14694],{"class":148,"line":149},[146,14670,153],{"class":152},[146,14672,14673],{"class":156}," apiKey ",[146,14675,161],{"class":160},[146,14677,14678],{"class":156}," env",[146,14680,167],{"class":160},[146,14682,13879],{"class":170},[146,14684,203],{"class":156},[146,14686,1471],{"class":160},[146,14688,14689],{"class":1554},"API_KEY",[146,14691,1471],{"class":160},[146,14693,1477],{"class":156},[146,14695,182],{"class":160},[146,14697,14698,14700,14703,14705,14707,14709,14711,14713,14715,14718,14720,14722],{"class":148,"line":185},[146,14699,153],{"class":152},[146,14701,14702],{"class":156}," dbHost ",[146,14704,161],{"class":160},[146,14706,14678],{"class":156},[146,14708,167],{"class":160},[146,14710,13879],{"class":170},[146,14712,203],{"class":156},[146,14714,1471],{"class":160},[146,14716,14717],{"class":1554},"DB_HOST",[146,14719,1471],{"class":160},[146,14721,1477],{"class":156},[146,14723,182],{"class":160},[15,14725,14726],{},"In template nodes:",[42,14728,14730],{"className":140,"code":14729,"language":142,"meta":50,"style":50},"API Endpoint: {{env.API_ENDPOINT}}\n",[19,14731,14732],{"__ignoreMap":50},[146,14733,14734,14737,14740,14742,14745,14748,14750,14753],{"class":148,"line":149},[146,14735,14736],{"class":156},"API ",[146,14738,14739],{"class":2435},"Endpoint",[146,14741,730],{"class":160},[146,14743,14744],{"class":160}," {{",[146,14746,14747],{"class":156},"env",[146,14749,167],{"class":160},[146,14751,14752],{"class":156},"API_ENDPOINT",[146,14754,14755],{"class":160},"}}\n",[15,14757,14758,14759,14762],{},"For configuration nodes that don't have explicit environment variable support, you can often use the syntax ",[19,14760,14761],{},"${VARIABLE_NAME}"," in input fields.",[15,14764,14765,14768,14769,14772],{},[338,14766,14767],{},"Important note on precedence:"," When a flow-level and global-level environment variable share the same name, Node-RED uses the flow-level value. To explicitly access the global-level variable, prefix it with ",[19,14770,14771],{},"$parent."," in your reference.",[15,14774,14775,14778],{},[338,14776,14777],{},"Deleting environment variables:"," Return to the flow edit dialog where you added them and click the delete icon next to each variable. Remember to redeploy your flows after making changes.",[34,14780,12408],{"id":12407},[15,14782,14783],{},"Here are some guidelines for effective variable usage in Node-RED:",[15,14785,14786],{},[338,14787,14788],{},"Choose the right scope:",[100,14790,14791,14794,14797,14800],{},[103,14792,14793],{},"Use node variables for node-specific data",[103,14795,14796],{},"Use flow variables for data shared within a single workflow",[103,14798,14799],{},"Use global variables for system-wide shared data",[103,14801,14802],{},"Use environment variables for configuration and secrets",[15,14804,14805,14808,14809,1255,14812,1255,14815,7960],{},[338,14806,14807],{},"Naming conventions matter:"," Use clear, descriptive names. Consider prefixing variables with their purpose (e.g., ",[19,14810,14811],{},"sensor_temperature",[19,14813,14814],{},"config_timeout",[19,14816,14817],{},"user_preferences",[15,14819,14820,14823],{},[338,14821,14822],{},"Leverage persistent storage:"," For production applications, identify which variables need to survive restarts and use persistent storage for those.",[15,14825,14826,14829],{},[338,14827,14828],{},"Keep sensitive data in environment variables:"," Never hardcode API keys, passwords, or other secrets directly in flows. Always use environment variables.",[15,14831,14832,14835],{},[338,14833,14834],{},"Document your variables:"," Use the description field in your nodes to explain what variables are being set or read, especially for complex flows that others might maintain.",[15,14837,14838,14841],{},[338,14839,14840],{},"Monitor the Context Data panel:"," Regularly check this panel during development to verify variables are being set correctly and to catch potential issues early.",[34,14843,6518],{"id":6517},[15,14845,14846,14847,14850,14851,14854,14855,14858,14859,14861],{},"Node-RED's four variable types give you a clear toolkit for managing data and state. Use ",[338,14848,14849],{},"node context"," for private, node-specific values, ",[338,14852,14853],{},"flow variables"," for data shared within a single tab, and ",[338,14856,14857],{},"global variables"," for anything multiple flows need to reach. Reach for ",[338,14860,12669],{}," when you're handling configuration and secrets that shouldn't be hardcoded. Matching the scope to the job keeps your flows organized, predictable, and easy for others to maintain.",[15,14863,14864],{},"The one thing to keep front of mind: context variables live in memory by default, so they reset on every restart or redeploy. For anything your application genuinely depends on, counters, cached configuration, dashboard history, plan for persistent storage from the start rather than discovering the gap in production.",[15,14866,14867],{},"Get the scopes right and lean on persistence where it matters, and Node-RED will handle everything from quick prototypes to applications you can run with confidence.",[924,14869,14870],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":50,"searchDepth":250,"depth":250,"links":14872},[14873,14874,14877,14880,14883,14886,14887,14890,14891],{"id":13692,"depth":185,"text":13693},{"id":13728,"depth":185,"text":13729,"children":14875},[14876],{"id":13741,"depth":221,"text":13742},{"id":13934,"depth":185,"text":13935,"children":14878},[14879],{"id":13947,"depth":221,"text":13948},{"id":14115,"depth":185,"text":14116,"children":14881},[14882],{"id":14128,"depth":221,"text":14129},{"id":14275,"depth":185,"text":14276,"children":14884},[14885],{"id":14285,"depth":221,"text":14286},{"id":14543,"depth":185,"text":14544},{"id":14589,"depth":185,"text":14590,"children":14888},[14889],{"id":14611,"depth":221,"text":14612},{"id":12407,"depth":185,"text":12408},{"id":6517,"depth":185,"text":6518},{"type":7426,"title":14893,"description":14894},"Take Your Node-RED Flows to Production with FlowFuse","Built by the team behind Node-RED, FlowFuse turns your flows into production-grade applications, with persistent state that survives restarts, centralized management of every instance, team collaboration with role-based access and SSO, DevOps pipelines, and built-in monitoring with crash alerts. Start your free trial today.","2024-05-06","Learn how to use Node-RED global, flow, context, and environment variables in 2026. Step-by-step examples for setting, retrieving, and persisting state, plus best practices and an FAQ.","\u002Fblog\u002F2024\u002F05\u002Fimages\u002Funderstanding-node-red-variables.png","2026-06-01",{"keywords":14900,"faq":14901,"excerpt":14926},"node-red variables, node-red global variable, node-red context variable, node-red flow variable, node-red environment variables",[14902,14905,14908,14911,14914,14917,14920,14923],{"question":14903,"answer":14904},"What are the different types of variables in Node-RED?","Node-RED has three main categories: message variables (like msg.payload) that travel with the message through nodes; context variables that store state at node, flow, or global scope; and environment variables for configuration and secrets. Context variables are further split into node, flow, and global scopes based on visibility.",{"question":14906,"answer":14907},"What is the difference between flow and global variables in Node-RED?","Flow variables are scoped to a single tab and are accessible to all nodes within that flow but isolated from other flows. Global variables are accessible across your entire Node-RED instance, making them suited for system-wide settings or data multiple flows need to share.",{"question":14909,"answer":14910},"How do I set a global variable in a Node-RED function node?","Use global.set('variableName', value) to store a value and global.get('variableName') to retrieve it. The same pattern applies to flow variables (flow.set \u002F flow.get) and node context (context.set \u002F context.get).",{"question":14912,"answer":14913},"Do Node-RED variables persist after a restart?","By default, all context variables are stored in memory and are lost on restart or redeploy. To keep them, use persistent storage by adding 'persistent' as the store option in the change node or as a third parameter in a function node, e.g. global.set('userData', userData, 'persistent'). FlowFuse provides persistent storage that survives restarts and redeployments.",{"question":14915,"answer":14916},"What is the difference between context variables and environment variables?","Context variables (node, flow, global) store runtime application state that changes as your flows run. Environment variables hold configuration data and sensitive information like API keys and credentials, kept separate from your flows for security and easier configuration management.",{"question":14918,"answer":14919},"What happens if a flow-level and global-level environment variable have the same name?","Node-RED uses the flow-level value when names collide. To explicitly access the global-level variable instead, prefix the reference with $parent. in your variable name.",{"question":14921,"answer":14922},"When should I use node context instead of flow or global variables?","Use node context for private, node-specific data such as counters or temporary calculations that no other node should access. Node variables exist only within a single function node, providing the most restrictive and isolated scope.",{"question":14924,"answer":14925},"How can I view all the variables currently set in Node-RED?","Open the Context Data panel from the sidebar dropdown. It shows all node, flow, and global variables in organized sections, lets you see when each was last updated, copy names or values, refresh individual variables, and delete them directly from the interface.",{"type":12,"value":14927},[14928],[15,14929,13641,14930,13645,14932,13648,14934,13652,14936,13656,14938,13660,14940,13664,14942,13667,14944,213,14946,1255,14948,213,14950,1262,14952,213,14954,13686],{},[338,14931,13644],{},[19,14933,260],{},[338,14935,13651],{},[338,14937,13655],{},[338,14939,13659],{},[338,14941,13663],{},[338,14943,12669],{},[19,14945,13670],{},[19,14947,13673],{},[19,14949,13676],{},[19,14951,13679],{},[19,14953,13682],{},[19,14955,13685],{},"\u002Fblog\u002F2024\u002F05\u002Funderstanding-node-flow-global-environment-variables-in-node-red",{"title":13635,"description":14896},{"loc":14956},"blog\u002F2024\u002F05\u002Funderstanding-node-flow-global-environment-variables-in-node-red","A complete guide to setting, retrieving, and persisting Node-RED variables for efficient, production-ready flows.",[9832,6563,966],"Node-RED offers four kinds of variables for managing data and state: message variables that travel with msg through nodes, and context variables in three scopes node (private to one function node), flow (shared within a tab), and global (shared across the whole instance) plus environment variables for configuration and secrets. You set and retrieve them with global.set\u002Fget, flow.set\u002Fget, and context.set\u002Fget in function nodes, or visually through the change node. Context variables live in memory by default and are lost on restart, so use persistent storage (FlowFuse provides this) for any state that must survive reboots and redeployments.","JM0J3xiIkQQRtHldz5LdXINNjSanTuLf9BICQ6fXf24",{"id":14965,"title":14966,"authors":14967,"body":14968,"cta":16456,"date":16459,"description":16460,"extension":946,"image":16461,"lastUpdated":948,"meta":16462,"navigation":253,"path":16468,"seo":16469,"sitemap":16470,"stem":16471,"subtitle":16472,"tags":16473,"tldr":16474,"video":3,"__hash__":16475},"blog\u002Fblog\u002F2024\u002F04\u002Fhow-to-build-an-application-with-node-red-dashboard-2.md","How to Build An Application With Node-RED Dashboard 2.0 (2026)",[10],{"type":12,"value":14969,"toc":16443},[14970,14973,14979,14983,14991,14997,15001,15008,15011,15015,15025,15032,15040,15047,15051,15054,15065,15239,15249,15253,15263,15270,15294,15298,15301,15323,15330,15337,15437,15450,15454,15460,15469,15476,15492,15496,15510,15525,15532,15537,16371,16381,16383,16390,16401,16404,16408,16411,16440],[15,14971,14972],{},"In this guide, we'll build a Todo application to guide you through the features and explain how you can build rich and dynamic applications too. It shows many of the features that make Dashboard 2.0 great, and why you should use it over the deprecated node-red-dashboard.",[15,14974,14975,14976,14978],{},"If you're new to Dashboard 2.0, refer to our blog post ",[307,14977,12597],{"href":6792}," to install and get things started.",[34,14980,14982],{"id":14981},"installing-flowfuse-user-addon","Installing Flowfuse user addon",[15,14984,14985,14986,14990],{},"The FlowFuse User Addon is a plugin developed for Dashboard 2.0, that levereges the FlowFuse API to access logged in user's information at Dashboard 2.0. For detailed information refer to the ",[307,14987,14989],{"href":14988},"\u002Fblog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard\u002F#exploring-the-flowfuse-user-addon","Exploring the FlowFuse User Addon"," and make sure to install it.",[15,14992,14993,14994,14996],{},"Before you begin the application development process, please make sure that FlowFuse user authentication is enabled. This feature adds a layer of security to your application with a login page. By combining the FlowFuse user addon with user authentication, we gain access to the logged in user's data within our application. For more information on FlowFuse user authentication, refer to the ",[307,14995,10295],{"href":10343}," and make sure that it is enabled.",[34,14998,15000],{"id":14999},"building-task-management-application","Building Task Management application",[15,15002,15003],{},[392,15004],{"alt":15005,"dataZoomable":50,"src":15006,"title":15007},"\"Screenshot of the Todo application built with Node-RED Dashboard 2.0\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-2-task-management-system.gif","Screenshot of the task management system built with Node-RED Dashboard 2.0",[15,15009,15010],{},"Throughout this guide, we will be building a simple, secure, and personalized Task management application that will allow users to create and view their tasks.",[996,15012,15014],{"id":15013},"building-a-form-to-submit-tasks","Building a Form to Submit Tasks",[326,15016,15017,15022],{},[103,15018,369,15019,12951],{},[338,15020,15021],{},"ui-form",[103,15023,15024],{},"Click on the edit icon next to Page 1 (The default page added when you install Dashboard 2.0) in the Dashboard 2.0 sidebar. While this step is optional, updating the page configurations as shown in the image below ensures that your application aligns with the layout described in this guide.",[15,15026,15027],{},[392,15028],{"alt":15029,"dataZoomable":50,"src":15030,"title":15031},"\"Screenshot displaying 'new task' page configurations\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-2-page1-configuration.png","Screenshot displaying 'new task' page configurations",[326,15033,15034],{"start":221},[103,15035,15036,15037,15039],{},"Click on the ",[338,15038,15021],{}," widget to add form elements such as title, description, due date, and priority.",[15,15041,15042],{},[392,15043],{"alt":15044,"dataZoomable":50,"src":15045,"title":15046},"\"Screenshot of ui widget form configuration\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-2-task-submission-form.png","Screenshot of ui widget form configuration",[996,15048,15050],{"id":15049},"storing-tasks-in-the-global-context","Storing Tasks in the Global Context",[15,15052,15053],{},"For this guide, we are storing our Tasks in Node-RED global context but storing them  in a database will make it easy to manage your task data.",[326,15055,15056,15060],{},[103,15057,408,15058,417],{},[338,15059,1412],{},[103,15061,15062,15063,1361],{},"Paste the below code in the ",[338,15064,1412],{},[42,15066,15068],{"className":140,"code":15067,"language":142,"meta":50,"style":50},"\u002F\u002F Retrieve the existing tasks from the global context or initialize an empty array if none exists\n\nlet tasks = global.get('tasks') || [];\n\n\u002F\u002F Push the new task object into the tasks array, including the task details and the user object extracted from the message object, as each payload emitted by the Node-RED Dashboard 2.0 widgets contains user information due to the FlowFuse User Addon.\n\ntasks.push({\n  ...msg.payload,\n  ...{\n    user: msg._client.user \u002F\u002F Assign the user object to the task\n  }\n});\n\n\u002F\u002F Update the 'tasks' variable in the global context with the modified tasks array\n\nglobal.set('tasks', tasks);\n\nreturn msg;\n",[19,15069,15070,15075,15079,15113,15117,15122,15126,15138,15151,15156,15178,15183,15191,15195,15200,15204,15227,15231],{"__ignoreMap":50},[146,15071,15072],{"class":148,"line":149},[146,15073,15074],{"class":3874},"\u002F\u002F Retrieve the existing tasks from the global context or initialize an empty array if none exists\n",[146,15076,15077],{"class":148,"line":185},[146,15078,254],{"emptyLinePlaceholder":253},[146,15080,15081,15083,15086,15088,15090,15092,15094,15096,15098,15101,15103,15105,15108,15111],{"class":148,"line":221},[146,15082,1441],{"class":152},[146,15084,15085],{"class":156}," tasks ",[146,15087,161],{"class":160},[146,15089,13874],{"class":156},[146,15091,167],{"class":160},[146,15093,13879],{"class":170},[146,15095,203],{"class":156},[146,15097,1471],{"class":160},[146,15099,15100],{"class":1554},"tasks",[146,15102,1471],{"class":160},[146,15104,210],{"class":156},[146,15106,15107],{"class":160},"||",[146,15109,15110],{"class":156}," []",[146,15112,182],{"class":160},[146,15114,15115],{"class":148,"line":250},[146,15116,254],{"emptyLinePlaceholder":253},[146,15118,15119],{"class":148,"line":257},[146,15120,15121],{"class":3874},"\u002F\u002F Push the new task object into the tasks array, including the task details and the user object extracted from the message object, as each payload emitted by the Node-RED Dashboard 2.0 widgets contains user information due to the FlowFuse User Addon.\n",[146,15123,15124],{"class":148,"line":284},[146,15125,254],{"emptyLinePlaceholder":253},[146,15127,15128,15130,15132,15134,15136],{"class":148,"line":666},[146,15129,15100],{"class":156},[146,15131,167],{"class":160},[146,15133,4011],{"class":170},[146,15135,203],{"class":156},[146,15137,717],{"class":160},[146,15139,15140,15143,15145,15147,15149],{"class":148,"line":1603},[146,15141,15142],{"class":160},"  ...",[146,15144,260],{"class":156},[146,15146,167],{"class":160},[146,15148,1453],{"class":156},[146,15150,736],{"class":160},[146,15152,15153],{"class":148,"line":1608},[146,15154,15155],{"class":160},"  ...{\n",[146,15157,15158,15161,15163,15165,15167,15170,15172,15175],{"class":148,"line":1624},[146,15159,15160],{"class":1549},"    user",[146,15162,730],{"class":160},[146,15164,291],{"class":156},[146,15166,167],{"class":160},[146,15168,15169],{"class":156},"_client",[146,15171,167],{"class":160},[146,15173,15174],{"class":156},"user ",[146,15176,15177],{"class":3874},"\u002F\u002F Assign the user object to the task\n",[146,15179,15180],{"class":148,"line":2546},[146,15181,15182],{"class":160},"  }\n",[146,15184,15185,15187,15189],{"class":148,"line":2564},[146,15186,1596],{"class":160},[146,15188,1477],{"class":156},[146,15190,182],{"class":160},[146,15192,15193],{"class":148,"line":2569},[146,15194,254],{"emptyLinePlaceholder":253},[146,15196,15197],{"class":148,"line":2574},[146,15198,15199],{"class":3874},"\u002F\u002F Update the 'tasks' variable in the global context with the modified tasks array\n",[146,15201,15202],{"class":148,"line":2594},[146,15203,254],{"emptyLinePlaceholder":253},[146,15205,15206,15208,15210,15212,15214,15216,15218,15220,15222,15225],{"class":148,"line":2614},[146,15207,13663],{"class":156},[146,15209,167],{"class":160},[146,15211,13786],{"class":170},[146,15213,203],{"class":156},[146,15215,1471],{"class":160},[146,15217,15100],{"class":1554},[146,15219,1471],{"class":160},[146,15221,276],{"class":160},[146,15223,15224],{"class":156}," tasks)",[146,15226,182],{"class":160},[146,15228,15229],{"class":148,"line":2633},[146,15230,254],{"emptyLinePlaceholder":253},[146,15232,15233,15235,15237],{"class":148,"line":2662},[146,15234,288],{"class":287},[146,15236,291],{"class":156},[146,15238,182],{"class":160},[326,15240,15241],{"start":221},[103,15242,1355,15243,15245,15246,15248],{},[338,15244,15021],{}," widget’s output to the ",[338,15247,1412],{}," node’s input.",[996,15250,15252],{"id":15251},"displaying-notification-on-successful-task-submission","Displaying notification on successful task submission",[326,15254,15255],{},[103,15256,408,15257,15259,15260,15262],{},[338,15258,1405],{}," node onto the canvas and set ",[19,15261,382],{}," to the confirmation message you want to display on successful task submission.",[15,15264,15265],{},[392,15266],{"alt":15267,"dataZoomable":50,"src":15268,"title":15269},"\"screenshot of the change node setting payload for notification\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-change-node-setting-payload-for-notification.png","screenshot of the change node setting payload for notification",[326,15271,15272,15281],{"start":185},[103,15273,369,15274,15277,15278,15280],{},[338,15275,15276],{},"ui-notification"," onto the canvas select ",[338,15279,12959],{}," and set the position to \"center\".",[103,15282,1355,15283,15245,15285,15287,15288,15290,15291,15293],{},[338,15284,15021],{},[338,15286,1405],{}," node’s input and the ",[338,15289,1405],{}," node’s output to the ",[338,15292,15276],{}," widget's input.",[996,15295,15297],{"id":15296},"retrieving-and-filtering-tasks","Retrieving and Filtering Tasks",[15,15299,15300],{},"Now that we can store tasks along with the user details of who submitted them, we need to retrieve and filter them based on users, ensuring that users can only see their tasks only and not others.",[326,15302,15303,15314],{},[103,15304,408,15305,15307,15308,15310,15311,15313],{},[338,15306,5112],{}," widget onto the canvas and select ",[338,15309,12959],{}," for it. The ",[338,15312,5112],{}," will enable us to display updated tasks on the table without the need for polling, as it triggers when the page reloads or changes.",[103,15315,408,15316,15259,15318,764,15320,167],{},[338,15317,1405],{},[19,15319,382],{},[19,15321,15322],{},"global.tasks",[15,15324,15325],{},[392,15326],{"alt":15327,"dataZoomable":50,"src":15328,"title":15329},"\"Screenshot of the change setting retriving global context and setting to msg.payload\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-change-node-retriving-global-context-data.png","Screenshot of the change setting retriving global context and setting to msg.payload",[326,15331,15332],{"start":221},[103,15333,408,15334,15336],{},[338,15335,1412],{}," node onto the canvas and paste the below code into it.",[42,15338,15340],{"className":140,"code":15339,"language":142,"meta":50,"style":50},"\u002F\u002F Filter the payload array of tasks to include only those tasks associated with the currently logged in user.\n\nmsg.payload = msg.payload.filter((task) => task.user.userId === msg._client.user.userId);\n\n\u002F\u002F Return the modified message object containing the filtered tasks.\n\nreturn msg;\n",[19,15341,15342,15347,15351,15416,15420,15425,15429],{"__ignoreMap":50},[146,15343,15344],{"class":148,"line":149},[146,15345,15346],{"class":3874},"\u002F\u002F Filter the payload array of tasks to include only those tasks associated with the currently logged in user.\n",[146,15348,15349],{"class":148,"line":185},[146,15350,254],{"emptyLinePlaceholder":253},[146,15352,15353,15355,15357,15359,15361,15363,15365,15367,15369,15372,15374,15376,15379,15381,15383,15386,15388,15391,15393,15396,15399,15401,15403,15405,15407,15409,15411,15414],{"class":148,"line":221},[146,15354,260],{"class":156},[146,15356,167],{"class":160},[146,15358,265],{"class":156},[146,15360,161],{"class":160},[146,15362,291],{"class":156},[146,15364,167],{"class":160},[146,15366,1453],{"class":156},[146,15368,167],{"class":160},[146,15370,15371],{"class":170},"filter",[146,15373,203],{"class":156},[146,15375,203],{"class":160},[146,15377,15378],{"class":1510},"task",[146,15380,1477],{"class":160},[146,15382,1514],{"class":152},[146,15384,15385],{"class":156}," task",[146,15387,167],{"class":160},[146,15389,15390],{"class":156},"user",[146,15392,167],{"class":160},[146,15394,15395],{"class":156},"userId ",[146,15397,15398],{"class":160},"===",[146,15400,291],{"class":156},[146,15402,167],{"class":160},[146,15404,15169],{"class":156},[146,15406,167],{"class":160},[146,15408,15390],{"class":156},[146,15410,167],{"class":160},[146,15412,15413],{"class":156},"userId)",[146,15415,182],{"class":160},[146,15417,15418],{"class":148,"line":250},[146,15419,254],{"emptyLinePlaceholder":253},[146,15421,15422],{"class":148,"line":257},[146,15423,15424],{"class":3874},"\u002F\u002F Return the modified message object containing the filtered tasks.\n",[146,15426,15427],{"class":148,"line":284},[146,15428,254],{"emptyLinePlaceholder":253},[146,15430,15431,15433,15435],{"class":148,"line":666},[146,15432,288],{"class":287},[146,15434,291],{"class":156},[146,15436,182],{"class":160},[326,15438,15439],{"start":250},[103,15440,1355,15441,15245,15443,15287,15445,15447,15448,15248],{},[338,15442,5112],{},[338,15444,1405],{},[338,15446,1405],{}," nodes’ output to the ",[338,15449,1412],{},[996,15451,15453],{"id":15452},"enabling-client-constraint-for-ui-template","Enabling client constraint for ui-template",[15,15455,15456,15457,15459],{},"Before we begin building our table to display tasks, we need to enable access to client constraints for the ",[338,15458,5152],{}," widget. Access client constraints ensure that messages or actions are specifically targeted to individual clients. For instance, if 100 people are interacting with the same task management dashboard simultaneously and one person submits a task, the notification will only be visible to that person and not to the remaining 99 individuals.",[15,15461,15462,15463,63,15466,15468],{},"If you have experience with Node-RED Dashboard 1.0, you may recall that these client constraints were only available for ",[338,15464,15465],{},"ui-control",[338,15467,15276],{}," widgets but in Dashboard 2.0 you can enable it for any widget.",[15,15470,15471],{},[392,15472],{"alt":15473,"dataZoomable":50,"src":15474,"title":15475},"\"Screenshot displaying FF Auth tab\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-2-ff-auth-tab.png","Screenshot displaying FF Auth tab",[326,15477,15478,15481],{},[103,15479,15480],{},"Navigate to the Dashboard 2.0 sidebar and select the top-right \"FF Auth\" Tab",[103,15482,15483,15484,63,15486,15488,15489,15491],{},"In the \"Accept Client Constraints\" option, you'll see Dashboard 2.0 widgets where this option is by default enabled for ",[338,15485,15276],{},[338,15487,15465],{},", enable it for ",[338,15490,5152],{}," as well.",[996,15493,15495],{"id":15494},"creating-a-table-and-displaying-the-task","Creating a table and displaying the task",[15,15497,15498,15499,63,15501,15506,15507,15509],{},"In this section, we will build an interactive table using ",[338,15500,5152],{},[307,15502,15505],{"href":15503,"rel":15504},"https:\u002F\u002Fvuetifyjs.com\u002Fen\u002Fcomponents\u002Fall\u002F",[311],"vuetify component",". Vuetify offers a wide range of components, all of which are compatible with our Node-RED Dashboard 2.0's ui-template widget. You can easily use them by just simply copying and pasting them into the ",[338,15508,5152],{}," widget.",[326,15511,15512,15517],{},[103,15513,369,15514,15516],{},[338,15515,5152],{}," widget onto the canvas",[103,15518,15519,15520,63,15522,15524],{},"Create a new ",[338,15521,13524],{},[338,15523,12956],{}," for it. Below, I have provided a screenshot of the \"new task\" page configurations. Again You can replicate it if you want to align with the layout described in this guide, otherwise, it is optional.",[15,15526,15527],{},[392,15528],{"alt":15529,"dataZoomable":50,"src":15530,"title":15531},"\"Screenshot displaying ui-template widget with code for building table for displaying task\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-template-widget.png","Screenshot displaying ui-template widget with code for building table for displaying task",[326,15533,15534],{"start":221},[103,15535,15536],{},"Paste the below code into the widget, If you're new to Vue.js, rest assured I've included helpful comments for clarity.",[42,15538,15540],{"className":5156,"code":15539,"language":5158,"meta":50,"style":50},"` \u003Ctemplate>\n \u003C!-- Input field for searching tasks -->\n \u003Cv-text-field v-model=\"search\" label=\"Search\" prepend-inner-icon=\"mdi-magnify\" single-line variant=\"outlined\"\n  hide-details>\u003C\u002Fv-text-field>\n \n \u003C!-- Data table to display tasks -->\n \u003Cv-data-table :search=\"search\" :items=\"msg?.payload\">\n  \u003C!-- Custom header for the \"current\" column -->\n  \u003Ctemplate v-slot:header.current>\n   \u003Cdiv class=\"text-center\">Center-Aligned\u003C\u002Fdiv>\n  \u003C\u002Ftemplate>\n\n  \u003C!-- Template for the \"priority\" column -->\n  \u003Ctemplate v-slot:item.priority=\"{ item }\">\n   \u003C!-- Display priority icon if it exists -->\n   \u003Cv-icon v-if=\"item.priority\" icon=\"mdi-alert\" color=\"red\">\u003C\u002Fv-icon>\n  \u003C\u002Ftemplate>\n\n  \u003C!-- Template for the \"user\" column -->\n  \u003Ctemplate v-slot:item.user=\"{ item }\">\n   \u003C!-- Display user avatar and username -->\n   \u003Cdiv class=\"user\">\n    \u003C!-- User avatar -->\n    \u003Cimg :src=\"item.user.image\" width=\"24\" \u002F>\n    \u003C!-- Username -->\n    {% raw %}\u003Cspan>{{ item.user.username }}\u003C\u002Fspan>{% endraw %}\n   \u003C\u002Fdiv>\n  \u003C\u002Ftemplate>\n\n  \u003C!-- Template for the \"due\" column -->\n  \u003Ctemplate v-slot:item.due=\"{ item }\">\n   \u003C!-- Calculate and display the number of days between due date and current date -->\n   {% raw %}{{ daysBetween(item.due, new Date()) }} Days{% endraw %}\n  \u003C\u002Ftemplate>\n \u003C\u002Fv-data-table>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\n export default {\n  data() {\n   return {\n    \u002F\u002F Search input field model\n    search: '',\n   }\n  },\n  methods: {\n   \u002F\u002F Method to calculate the number of days between two dates\n   daysBetween(date1, date2) {\n    \u002F\u002F Calculate the difference in days\n    const oneDay = 24 * 60 * 60 * 1000; \u002F\u002F hours*minutes*seconds*milliseconds\n    const firstDate = new Date(date1);\n    const secondDate = new Date(date2);\n    const diffDays = Math.round(Math.abs((firstDate - secondDate) \u002F oneDay));\n    return diffDays;\n   }\n  }\n }\n\u003C\u002Fscript>\n\n\u003Cstyle scoped>\n \u002F* Styling for user avatar and username *\u002F\n .user {\n  display: flex;\n  gap: 5px; \u002F* Gap between avatar and username *\u002F\n }\n\n \u002F* Styling for user avatar *\u002F\n .user img {\n  width: 24px; \u002F* Set width of user avatar *\u002F\n }\n\u003C\u002Fstyle>\n\n",[19,15541,15542,15553,15558,15616,15627,15632,15637,15669,15674,15686,15715,15724,15728,15733,15753,15758,15805,15813,15817,15822,15841,15846,15864,15869,15901,15906,15929,15938,15946,15950,15955,15974,15979,15984,15992,16001,16009,16013,16021,16030,16039,16046,16051,16063,16068,16073,16082,16087,16106,16111,16143,16166,16188,16237,16246,16250,16254,16259,16267,16271,16282,16287,16296,16307,16322,16326,16330,16335,16345,16359,16363],{"__ignoreMap":50},[146,15543,15544,15547,15549,15551],{"class":148,"line":149},[146,15545,15546],{"class":156},"` ",[146,15548,5165],{"class":160},[146,15550,9982],{"class":1549},[146,15552,5183],{"class":160},[146,15554,15555],{"class":148,"line":185},[146,15556,15557],{"class":3874}," \u003C!-- Input field for searching tasks -->\n",[146,15559,15560,15563,15566,15569,15571,15573,15575,15577,15580,15582,15584,15587,15589,15592,15594,15596,15599,15601,15604,15607,15609,15611,15614],{"class":148,"line":221},[146,15561,15562],{"class":160}," \u003C",[146,15564,15565],{"class":1549},"v-text-field",[146,15567,15568],{"class":152}," v-model",[146,15570,161],{"class":160},[146,15572,727],{"class":160},[146,15574,1237],{"class":1554},[146,15576,727],{"class":160},[146,15578,15579],{"class":152}," label",[146,15581,161],{"class":160},[146,15583,727],{"class":160},[146,15585,15586],{"class":1554},"Search",[146,15588,727],{"class":160},[146,15590,15591],{"class":152}," prepend-inner-icon",[146,15593,161],{"class":160},[146,15595,727],{"class":160},[146,15597,15598],{"class":1554},"mdi-magnify",[146,15600,727],{"class":160},[146,15602,15603],{"class":152}," single-line",[146,15605,15606],{"class":152}," variant",[146,15608,161],{"class":160},[146,15610,727],{"class":160},[146,15612,15613],{"class":1554},"outlined",[146,15615,2561],{"class":160},[146,15617,15618,15621,15623,15625],{"class":148,"line":250},[146,15619,15620],{"class":152},"  hide-details",[146,15622,10280],{"class":160},[146,15624,15565],{"class":1549},[146,15626,5183],{"class":160},[146,15628,15629],{"class":148,"line":257},[146,15630,15631],{"class":156}," \n",[146,15633,15634],{"class":148,"line":284},[146,15635,15636],{"class":3874}," \u003C!-- Data table to display tasks -->\n",[146,15638,15639,15641,15644,15647,15649,15651,15653,15655,15658,15660,15662,15665,15667],{"class":148,"line":666},[146,15640,15562],{"class":160},[146,15642,15643],{"class":1549},"v-data-table",[146,15645,15646],{"class":152}," :search",[146,15648,161],{"class":160},[146,15650,727],{"class":160},[146,15652,1237],{"class":1554},[146,15654,727],{"class":160},[146,15656,15657],{"class":152}," :items",[146,15659,161],{"class":160},[146,15661,727],{"class":160},[146,15663,15664],{"class":1554},"msg?.payload",[146,15666,727],{"class":160},[146,15668,5183],{"class":160},[146,15670,15671],{"class":148,"line":1603},[146,15672,15673],{"class":3874},"  \u003C!-- Custom header for the \"current\" column -->\n",[146,15675,15676,15679,15681,15684],{"class":148,"line":1608},[146,15677,15678],{"class":160},"  \u003C",[146,15680,9982],{"class":1549},[146,15682,15683],{"class":152}," v-slot:header.current",[146,15685,5183],{"class":160},[146,15687,15688,15691,15693,15695,15697,15699,15702,15704,15706,15709,15711,15713],{"class":148,"line":1624},[146,15689,15690],{"class":160},"   \u003C",[146,15692,5168],{"class":1549},[146,15694,10429],{"class":152},[146,15696,161],{"class":160},[146,15698,727],{"class":160},[146,15700,15701],{"class":1554},"text-center",[146,15703,727],{"class":160},[146,15705,10032],{"class":160},[146,15707,15708],{"class":156},"Center-Aligned",[146,15710,5247],{"class":160},[146,15712,5168],{"class":1549},[146,15714,5183],{"class":160},[146,15716,15717,15720,15722],{"class":148,"line":2546},[146,15718,15719],{"class":160},"  \u003C\u002F",[146,15721,9982],{"class":1549},[146,15723,5183],{"class":160},[146,15725,15726],{"class":148,"line":2564},[146,15727,254],{"emptyLinePlaceholder":253},[146,15729,15730],{"class":148,"line":2569},[146,15731,15732],{"class":3874},"  \u003C!-- Template for the \"priority\" column -->\n",[146,15734,15735,15737,15739,15742,15744,15746,15749,15751],{"class":148,"line":2574},[146,15736,15678],{"class":160},[146,15738,9982],{"class":1549},[146,15740,15741],{"class":152}," v-slot:item.priority",[146,15743,161],{"class":160},[146,15745,727],{"class":160},[146,15747,15748],{"class":1554},"{ item }",[146,15750,727],{"class":160},[146,15752,5183],{"class":160},[146,15754,15755],{"class":148,"line":2594},[146,15756,15757],{"class":3874},"   \u003C!-- Display priority icon if it exists -->\n",[146,15759,15760,15762,15765,15767,15769,15771,15774,15776,15779,15781,15783,15786,15788,15791,15793,15795,15797,15799,15801,15803],{"class":148,"line":2614},[146,15761,15690],{"class":160},[146,15763,15764],{"class":1549},"v-icon",[146,15766,9999],{"class":152},[146,15768,161],{"class":160},[146,15770,727],{"class":160},[146,15772,15773],{"class":1554},"item.priority",[146,15775,727],{"class":160},[146,15777,15778],{"class":152}," icon",[146,15780,161],{"class":160},[146,15782,727],{"class":160},[146,15784,15785],{"class":1554},"mdi-alert",[146,15787,727],{"class":160},[146,15789,15790],{"class":152}," color",[146,15792,161],{"class":160},[146,15794,727],{"class":160},[146,15796,12812],{"class":1554},[146,15798,727],{"class":160},[146,15800,10280],{"class":160},[146,15802,15764],{"class":1549},[146,15804,5183],{"class":160},[146,15806,15807,15809,15811],{"class":148,"line":2633},[146,15808,15719],{"class":160},[146,15810,9982],{"class":1549},[146,15812,5183],{"class":160},[146,15814,15815],{"class":148,"line":2662},[146,15816,254],{"emptyLinePlaceholder":253},[146,15818,15819],{"class":148,"line":2667},[146,15820,15821],{"class":3874},"  \u003C!-- Template for the \"user\" column -->\n",[146,15823,15824,15826,15828,15831,15833,15835,15837,15839],{"class":148,"line":2672},[146,15825,15678],{"class":160},[146,15827,9982],{"class":1549},[146,15829,15830],{"class":152}," v-slot:item.user",[146,15832,161],{"class":160},[146,15834,727],{"class":160},[146,15836,15748],{"class":1554},[146,15838,727],{"class":160},[146,15840,5183],{"class":160},[146,15842,15843],{"class":148,"line":2692},[146,15844,15845],{"class":3874},"   \u003C!-- Display user avatar and username -->\n",[146,15847,15848,15850,15852,15854,15856,15858,15860,15862],{"class":148,"line":2705},[146,15849,15690],{"class":160},[146,15851,5168],{"class":1549},[146,15853,10429],{"class":152},[146,15855,161],{"class":160},[146,15857,727],{"class":160},[146,15859,15390],{"class":1554},[146,15861,727],{"class":160},[146,15863,5183],{"class":160},[146,15865,15866],{"class":148,"line":2722},[146,15867,15868],{"class":3874},"    \u003C!-- User avatar -->\n",[146,15870,15871,15874,15876,15878,15880,15882,15885,15887,15890,15892,15894,15897,15899],{"class":148,"line":2787},[146,15872,15873],{"class":160},"    \u003C",[146,15875,392],{"class":1549},[146,15877,10455],{"class":152},[146,15879,161],{"class":160},[146,15881,727],{"class":160},[146,15883,15884],{"class":1554},"item.user.image",[146,15886,727],{"class":160},[146,15888,15889],{"class":152}," width",[146,15891,161],{"class":160},[146,15893,727],{"class":160},[146,15895,15896],{"class":1554},"24",[146,15898,727],{"class":160},[146,15900,10467],{"class":160},[146,15902,15903],{"class":148,"line":2801},[146,15904,15905],{"class":3874},"    \u003C!-- Username -->\n",[146,15907,15908,15911,15913,15915,15917,15920,15922,15924,15926],{"class":148,"line":2861},[146,15909,15910],{"class":156},"    {% raw %}",[146,15912,5165],{"class":160},[146,15914,146],{"class":1549},[146,15916,10032],{"class":160},[146,15918,15919],{"class":156},"{{ item.user.username }}",[146,15921,5247],{"class":160},[146,15923,146],{"class":1549},[146,15925,10032],{"class":160},[146,15927,15928],{"class":156},"{% endraw %}\n",[146,15930,15931,15934,15936],{"class":148,"line":2920},[146,15932,15933],{"class":160},"   \u003C\u002F",[146,15935,5168],{"class":1549},[146,15937,5183],{"class":160},[146,15939,15940,15942,15944],{"class":148,"line":2978},[146,15941,15719],{"class":160},[146,15943,9982],{"class":1549},[146,15945,5183],{"class":160},[146,15947,15948],{"class":148,"line":3034},[146,15949,254],{"emptyLinePlaceholder":253},[146,15951,15952],{"class":148,"line":3091},[146,15953,15954],{"class":3874},"  \u003C!-- Template for the \"due\" column -->\n",[146,15956,15957,15959,15961,15964,15966,15968,15970,15972],{"class":148,"line":3148},[146,15958,15678],{"class":160},[146,15960,9982],{"class":1549},[146,15962,15963],{"class":152}," v-slot:item.due",[146,15965,161],{"class":160},[146,15967,727],{"class":160},[146,15969,15748],{"class":1554},[146,15971,727],{"class":160},[146,15973,5183],{"class":160},[146,15975,15976],{"class":148,"line":3204},[146,15977,15978],{"class":3874},"   \u003C!-- Calculate and display the number of days between due date and current date -->\n",[146,15980,15981],{"class":148,"line":3210},[146,15982,15983],{"class":156},"   {% raw %}{{ daysBetween(item.due, new Date()) }} Days{% endraw %}\n",[146,15985,15986,15988,15990],{"class":148,"line":3216},[146,15987,15719],{"class":160},[146,15989,9982],{"class":1549},[146,15991,5183],{"class":160},[146,15993,15994,15997,15999],{"class":148,"line":3221},[146,15995,15996],{"class":160}," \u003C\u002F",[146,15998,15643],{"class":1549},[146,16000,5183],{"class":160},[146,16002,16003,16005,16007],{"class":148,"line":3226},[146,16004,5247],{"class":160},[146,16006,9982],{"class":1549},[146,16008,5183],{"class":160},[146,16010,16011],{"class":148,"line":3246},[146,16012,254],{"emptyLinePlaceholder":253},[146,16014,16015,16017,16019],{"class":148,"line":3263},[146,16016,5165],{"class":160},[146,16018,10102],{"class":1549},[146,16020,5183],{"class":160},[146,16022,16023,16026,16028],{"class":148,"line":3283},[146,16024,16025],{"class":287}," export",[146,16027,10112],{"class":287},[146,16029,1517],{"class":160},[146,16031,16032,16035,16037],{"class":148,"line":3312},[146,16033,16034],{"class":1549},"  data",[146,16036,1461],{"class":160},[146,16038,1517],{"class":160},[146,16040,16041,16044],{"class":148,"line":3317},[146,16042,16043],{"class":287},"   return",[146,16045,1517],{"class":160},[146,16047,16048],{"class":148,"line":3322},[146,16049,16050],{"class":3874},"    \u002F\u002F Search input field model\n",[146,16052,16053,16056,16058,16061],{"class":148,"line":3342},[146,16054,16055],{"class":1549},"    search",[146,16057,730],{"class":160},[146,16059,16060],{"class":160}," ''",[146,16062,736],{"class":160},[146,16064,16065],{"class":148,"line":3361},[146,16066,16067],{"class":160},"   }\n",[146,16069,16070],{"class":148,"line":3380},[146,16071,16072],{"class":160},"  },\n",[146,16074,16075,16078,16080],{"class":148,"line":3409},[146,16076,16077],{"class":1549},"  methods",[146,16079,730],{"class":160},[146,16081,1517],{"class":160},[146,16083,16084],{"class":148,"line":3415},[146,16085,16086],{"class":3874},"   \u002F\u002F Method to calculate the number of days between two dates\n",[146,16088,16089,16092,16094,16097,16099,16102,16104],{"class":148,"line":3421},[146,16090,16091],{"class":1549},"   daysBetween",[146,16093,203],{"class":160},[146,16095,16096],{"class":1510},"date1",[146,16098,276],{"class":160},[146,16100,16101],{"class":1510}," date2",[146,16103,1477],{"class":160},[146,16105,1517],{"class":160},[146,16107,16108],{"class":148,"line":3435},[146,16109,16110],{"class":3874},"    \u002F\u002F Calculate the difference in days\n",[146,16112,16113,16116,16119,16121,16124,16127,16129,16131,16133,16135,16138,16140],{"class":148,"line":3449},[146,16114,16115],{"class":152},"    const",[146,16117,16118],{"class":156}," oneDay",[146,16120,1539],{"class":160},[146,16122,16123],{"class":206}," 24",[146,16125,16126],{"class":160}," *",[146,16128,3724],{"class":206},[146,16130,16126],{"class":160},[146,16132,3724],{"class":206},[146,16134,16126],{"class":160},[146,16136,16137],{"class":206}," 1000",[146,16139,10617],{"class":160},[146,16141,16142],{"class":3874}," \u002F\u002F hours*minutes*seconds*milliseconds\n",[146,16144,16145,16147,16150,16152,16155,16158,16160,16162,16164],{"class":148,"line":3464},[146,16146,16115],{"class":152},[146,16148,16149],{"class":156}," firstDate",[146,16151,1539],{"class":160},[146,16153,16154],{"class":160}," new",[146,16156,16157],{"class":170}," Date",[146,16159,203],{"class":1549},[146,16161,16096],{"class":156},[146,16163,1477],{"class":1549},[146,16165,182],{"class":160},[146,16167,16168,16170,16173,16175,16177,16179,16181,16184,16186],{"class":148,"line":3479},[146,16169,16115],{"class":152},[146,16171,16172],{"class":156}," secondDate",[146,16174,1539],{"class":160},[146,16176,16154],{"class":160},[146,16178,16157],{"class":170},[146,16180,203],{"class":1549},[146,16182,16183],{"class":156},"date2",[146,16185,1477],{"class":1549},[146,16187,182],{"class":160},[146,16189,16190,16192,16195,16197,16200,16202,16205,16207,16210,16212,16215,16218,16221,16224,16226,16228,16230,16232,16235],{"class":148,"line":3498},[146,16191,16115],{"class":152},[146,16193,16194],{"class":156}," diffDays",[146,16196,1539],{"class":160},[146,16198,16199],{"class":156}," Math",[146,16201,167],{"class":160},[146,16203,16204],{"class":170},"round",[146,16206,203],{"class":1549},[146,16208,16209],{"class":156},"Math",[146,16211,167],{"class":160},[146,16213,16214],{"class":170},"abs",[146,16216,16217],{"class":1549},"((",[146,16219,16220],{"class":156},"firstDate",[146,16222,16223],{"class":160}," -",[146,16225,16172],{"class":156},[146,16227,210],{"class":1549},[146,16229,213],{"class":160},[146,16231,16118],{"class":156},[146,16233,16234],{"class":1549},"))",[146,16236,182],{"class":160},[146,16238,16239,16242,16244],{"class":148,"line":3527},[146,16240,16241],{"class":287},"    return",[146,16243,16194],{"class":156},[146,16245,182],{"class":160},[146,16247,16248],{"class":148,"line":3532},[146,16249,16067],{"class":160},[146,16251,16252],{"class":148,"line":3545},[146,16253,15182],{"class":160},[146,16255,16256],{"class":148,"line":3561},[146,16257,16258],{"class":160}," }\n",[146,16260,16261,16263,16265],{"class":148,"line":3575},[146,16262,5247],{"class":160},[146,16264,10102],{"class":1549},[146,16266,5183],{"class":160},[146,16268,16269],{"class":148,"line":3596},[146,16270,254],{"emptyLinePlaceholder":253},[146,16272,16273,16275,16277,16280],{"class":148,"line":3625},[146,16274,5165],{"class":160},[146,16276,924],{"class":1549},[146,16278,16279],{"class":152}," scoped",[146,16281,5183],{"class":160},[146,16283,16284],{"class":148,"line":3630},[146,16285,16286],{"class":156}," \u002F* Styling for user avatar and username *\u002F\n",[146,16288,16289,16292,16294],{"class":148,"line":3643},[146,16290,16291],{"class":160}," .",[146,16293,15390],{"class":2435},[146,16295,1517],{"class":160},[146,16297,16298,16301,16303,16305],{"class":148,"line":3658},[146,16299,16300],{"class":10666},"  display",[146,16302,730],{"class":160},[146,16304,10672],{"class":156},[146,16306,182],{"class":160},[146,16308,16309,16312,16314,16317,16319],{"class":148,"line":3675},[146,16310,16311],{"class":10666},"  gap",[146,16313,730],{"class":160},[146,16315,16316],{"class":206}," 5px",[146,16318,10617],{"class":160},[146,16320,16321],{"class":3874}," \u002F* Gap between avatar and username *\u002F\n",[146,16323,16324],{"class":148,"line":3680},[146,16325,16258],{"class":160},[146,16327,16328],{"class":148,"line":3686},[146,16329,254],{"emptyLinePlaceholder":253},[146,16331,16332],{"class":148,"line":3706},[146,16333,16334],{"class":156}," \u002F* Styling for user avatar *\u002F\n",[146,16336,16337,16339,16341,16343],{"class":148,"line":3738},[146,16338,16291],{"class":160},[146,16340,15390],{"class":2435},[146,16342,10716],{"class":2435},[146,16344,1517],{"class":160},[146,16346,16347,16350,16352,16354,16356],{"class":148,"line":4632},[146,16348,16349],{"class":10666},"  width",[146,16351,730],{"class":160},[146,16353,10728],{"class":206},[146,16355,10617],{"class":160},[146,16357,16358],{"class":3874}," \u002F* Set width of user avatar *\u002F\n",[146,16360,16361],{"class":148,"line":4642},[146,16362,16258],{"class":160},[146,16364,16365,16367,16369],{"class":148,"line":4652},[146,16366,5247],{"class":160},[146,16368,924],{"class":1549},[146,16370,5183],{"class":160},[326,16372,16373],{"start":221},[103,16374,1355,16375,16377,16378,16380],{},[338,16376,5152],{}," widget's input to the ",[338,16379,1412],{}," node's output ( function node which we have added to filter tasks based on user ).",[34,16382,12976],{"id":12381},[15,16384,16385],{},[392,16386],{"alt":16387,"dataZoomable":50,"src":16388,"title":16389},"\"Screenshot displaying Node-RED flow of Task management system\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-an-application-with-dashboard-task-management-application-flow.png","Screenshot displaying Node-RED flow of Task management system",[326,16391,16392,16395,16398],{},[103,16393,16394],{},"Deploy the flow by clicking the top right Deploy button.",[103,16396,16397],{},"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.",[103,16399,16400],{},"Login with the registered FlowFuse account username and password.",[15,16402,16403],{},"Now, we're all set to add tasks. Navigate to the \"New Task\" page to add tasks. To view tasks, navigate to the \"your Task\" page.",[34,16405,16407],{"id":16406},"next-step","Next step",[15,16409,16410],{},"If you want to enhance this simple application by adding more features, consider the following resources:",[100,16412,16413,16420,16426,16433],{},[103,16414,16415,16419],{},[307,16416,16418],{"href":16417},"\u002Fwebinars\u002F2024\u002Fnode-red-dashboard-multi-user\u002F","Webinar"," - This webinar provides an in-depth discussion of the Personalised Multi-User Dashboards feature and offers guidance on how to get started with it.",[103,16421,16422,16425],{},[307,16423,16424],{"href":10759},"Displaying logged-in users on Dashboard 2.0"," - This detailed guide demonstrates how to display logged-in users on Dashboard 2.0 which using the FlowFuse Multiuser addon and FlowFuse.",[103,16427,16428,16432],{},[307,16429,16431],{"href":16430},"\u002Fblog\u002F2024\u002F04\u002Fbuilding-an-admin-panel-in-node-red-with-dashboard-2\u002F","How to Build an Admin Dashboard with Node-RED Dashboard 2.0"," - This detailed guide demonstrates how to build a secure admin page in Node-RED Dashboard 2.0.",[103,16434,16435,16439],{},[307,16436,16438],{"href":16437},"\u002Fblueprints\u002Fflowfuse-dashboard\u002Fmulti-user-dashboard\u002F","Multi-User Dashboard for Ticket\u002FTask Management"," blueprint, which allows you to utilize templates to develop a personalized multi-user dashboard quickly. This Task management blueprint has all features such as adding, updating, and deleting tasks, user profiles, and admin dashboard.",[924,16441,16442],{},"html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}",{"title":50,"searchDepth":250,"depth":250,"links":16444},[16445,16446,16454,16455],{"id":14981,"depth":185,"text":14982},{"id":14999,"depth":185,"text":15000,"children":16447},[16448,16449,16450,16451,16452,16453],{"id":15013,"depth":221,"text":15014},{"id":15049,"depth":221,"text":15050},{"id":15251,"depth":221,"text":15252},{"id":15296,"depth":221,"text":15297},{"id":15452,"depth":221,"text":15453},{"id":15494,"depth":221,"text":15495},{"id":12381,"depth":185,"text":12976},{"id":16406,"depth":185,"text":16407},{"type":941,"title":16457,"description":16458},"Build and Deploy Node-RED Applications Faster","FlowFuse gives you everything you need to build, secure, and scale Node-RED applications, multi-user support, authentication, and production-ready deployment included. Start your free trial today.","2024-04-25","Learn to build custom applications effortlessly with Node-RED Dashboard 2.0. This step-by-step guide walks you through building a personalized, secure, and fully functional application.","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuild-application-dashboard-2.png",{"keywords":16463,"excerpt":16464},"node-red dashboard 2.0 application, build application node-red dashboard, node-red dashboard 2 tutorial, flowfuse dashboard app, node-red task management dashboard",{"type":12,"value":16465},[16466],[15,16467,14972],{},"\u002Fblog\u002F2024\u002F04\u002Fhow-to-build-an-application-with-node-red-dashboard-2",{"title":14966,"description":16460},{"loc":16468},"blog\u002F2024\u002F04\u002Fhow-to-build-an-application-with-node-red-dashboard-2","A step-by-step guide to building a personalized, secure, and fully functional application with Dashboard 2.0.",[9832,6563,11070,966],"This guide walks through building a personalised, multi-user task management application with Node-RED Dashboard 2.0 and FlowFuse. It covers enabling user authentication, using the FlowFuse User Addon to access logged-in user data, storing and filtering tasks in global context per user, and rendering them in a Vuetify data table inside a ui-template widget with client constraints enabled.","uCiuBK4FAHIj8MDYEGdWktzIiWOu6ccwWYe3metOsjg",{"id":16477,"title":16478,"authors":16479,"body":16480,"cta":16720,"date":16723,"description":16724,"extension":946,"image":16725,"lastUpdated":948,"meta":16726,"navigation":253,"path":16732,"seo":16733,"sitemap":16734,"stem":16735,"subtitle":16736,"tags":16737,"tldr":16740,"video":3,"__hash__":16741},"blog\u002Fblog\u002F2024\u002F04\u002Fbuilding-an-admin-panel-in-node-red-with-dashboard-2.md","How to Build an Admin Dashboard with Node-RED Dashboard 2.0 (2026)",[10],{"type":12,"value":16481,"toc":16711},[16482,16485,16489,16493,16496,16502,16509,16513,16520,16524,16527,16539,16546,16554,16558,16561,16572,16579,16594,16601,16608,16619,16623,16626,16637,16644,16646,16653,16667,16670,16677,16684,16686,16689],[15,16483,16484],{},"Managing and analyzing increasing amounts of data becomes crucial for organizations. Dashboard 2.0 and Node-RED help organizations access the data, normalize it, and visualize it. But what about controlling who can access what data? That's where an admin-only page comes in. Now With Node-RED Dashboard 2.0, we can also create robust and secure admin-only pages easily. In this guide, we'll provide you with step-by-step instructions to Build an Admin-only page with Node-RED Dashboard 2.0.",[15,16486,14975,16487,14978],{},[307,16488,12597],{"href":6792},[34,16490,16492],{"id":16491},"enabling-flowfuse-user-authentication","Enabling FlowFuse User Authentication",[15,16494,16495],{},"Before proceeding further, let’s enable FlowFuse user authentication. This step adds an extra layer of protection to our dashboard by adding a login page that restricts access exclusively to registered FlowFuse users. Additionally, it further simplifies the process for the FlowFuse Multiuser addon to track and access logged-in user's data on the dashboard.",[15,16497,16498,16499,16501],{},"For more information, refer to the ",[307,16500,10295],{"href":10343}," and ensure that it is enabled.",[15,16503,16504],{},[392,16505],{"alt":16506,"dataZoomable":50,"src":16507,"title":16508},"\"Screenshot displaying the configuration settings within the FlowFuse instance, enabling user authentication for enhanced security.\n\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-flowfuse-instance-setting.png","Screenshot displaying the configuration settings within the FlowFuse instance, enabling user authentication for enhanced security.\n",[34,16510,16512],{"id":16511},"exploring-flowfuse-multiuser-addon","Exploring FlowFuse Multiuser Addon",[15,16514,16515,16516],{},"The FlowFuse Multiuser Addon is a plugin developed for Dashboard 2.0 to access logged-in user data on the dashboard. To install and understand how the FlowFuse Multiuser Addon works, refer to ",[307,16517,16519],{"href":16518},"\u002Fblog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard\u002F#enabling-flowfuse-user-authentication","Exploring the FlowFuse User Addon ",[34,16521,16523],{"id":16522},"storing-a-list-of-admin-users","Storing a list of Admin users",[15,16525,16526],{},"Before we start building the admin-only page We need to store a list of admin users somewhere so that we can later display the admin-only page to those users only, For this guide we will store the admin list in the global context.",[326,16528,16529,16532],{},[103,16530,16531],{},"Drag an inject node onto the canvas.",[103,16533,16534,16535,16538],{},"Drag the 'change' node onto the canvas and set ",[19,16536,16537],{},"global.admins"," to a JSON array containing the usernames of admin users. This will store the created admin list in our Node-RED global context.",[15,16540,16541],{},[392,16542],{"alt":16543,"dataZoomable":50,"src":16544,"title":16545},"\"Screenshot displaying the change node which which stores list of admins username in global context\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-change-node-for-storing-adminlist-to-global-contenxt.png","Screenshot displaying the change node which which stores list of admins username in global context",[326,16547,16548,16551],{"start":221},[103,16549,16550],{},"Connect the inject node’s output to the change node’s input.",[103,16552,16553],{},"To store the list in a global context, click the inject node’s button once you've deployed the flow.",[34,16555,16557],{"id":16556},"building-an-admin-only-page","Building an Admin-only page",[15,16559,16560],{},"Now, let's proceed with the practical steps to implement the admin-only page:",[326,16562,16563,16566,16569],{},[103,16564,16565],{},"Create a new page in Dashboard 2.0, where we will display sensitive data that we want to hide from regular users, this page will be our admin page.",[103,16567,16568],{},"Drag an event node on the canvas, then click on it, and select the UI base that contains your all pages including the admin page",[103,16570,16571],{},"Drag a switch node on the canvas, and add two conditions, one to check whether the user’s username is contained in the admin list or a second for otherwise, see the below image.",[15,16573,16574],{},[392,16575],{"alt":16576,"dataZoomable":50,"src":16577,"title":16578},"\"Screenshot displaying the switch node which checks whether the logged-in user's username is contained in the admin list or not\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-switch-node-checking-page-viewer-isadmin.png","Screenshot displaying the switch node which checks whether the logged-in user's username is contained in the admin list or not",[326,16580,16581],{"start":250},[103,16582,16583,16584,16586,16587,16590,16591,167],{},"Drag two change nodes onto the canvas, Configure the first change node to show the admin page by setting ",[19,16585,382],{}," as ",[19,16588,16589],{},"{\"pages\":{\"show\":[\"Admin View\"]}}",", and the second change node to hide the admin page by setting the payload as: ",[19,16592,16593],{},"{\"pages\":{\"hide\":[\"Admin View\"]}}",[15,16595,16596],{},[392,16597],{"alt":16598,"dataZoomable":50,"src":16599,"title":16600},"\"Screenshot displaying the change node which contains payload to show admin page\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-change-node-for-showing-page.png","Screenshot displaying the change node which contains payload to show admin page",[15,16602,16603],{},[392,16604],{"alt":16605,"dataZoomable":50,"src":16606,"title":16607},"\"Screenshot displaying the change node which contains payload to hide admin page\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-change-node-for-hidding-page.png","Screenshot displaying the change node which contains payload to display admin page",[326,16609,16610,16613,16616],{"start":257},[103,16611,16612],{},"Connect the first change node's input to the switch node's first output and the second change node's input to the switch node's second output.",[103,16614,16615],{},"Drag a ui-control widget onto the canvas, then click on it and select ui-base which includes all your pages including the admin page.",[103,16617,16618],{},"Finally, connect both change node’s outputs to the ui-control’s input.",[34,16620,16622],{"id":16621},"hidding-admin-only-page-by-default","Hidding Admin only page by default",[15,16624,16625],{},"To hide an admin-only page by default to ensure regular users don't accidentally land on the admin-only page the following steps are needed.",[326,16627,16628,16631,16634],{},[103,16629,16630],{},"Go to the Dashboard 2.0 sidebar, and select the layout tab.",[103,16632,16633],{},"Locate the admin-only page and click on the edit icon next to it.",[103,16635,16636],{},"Set visibility as \"hidden\".",[15,16638,16639],{},[392,16640],{"alt":16641,"dataZoomable":50,"src":16642,"title":16643},"\"Screenshot displaying admin-only page configuration\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-admin-only-page-configuration.png","Screenshot displaying admin-only page configuration",[34,16645,12976],{"id":12381},[15,16647,16648],{},[392,16649],{"alt":16650,"dataZoomable":50,"src":16651,"title":16652},"\"Screenshot displaying the FlowFuse Editor with flow of admin-only page\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-flowfuse-editior.png","Screenshot displaying the FlowFuse Editor with flow of admin-only page",[326,16654,16655,16658,16664],{},[103,16656,16657],{},"With your flow updated to include the above, click the \"Deploy\" button in the top-right of the Node-RED Editor.",[103,16659,16660,16661,167],{},"Navigate to ",[19,16662,16663],{},"https:\u002F\u002F\u003Cyour-instance-name>.flowfuse.cloud\u002Fdashboard",[103,16665,16666],{},"When you visit the page for the first time, you'll need to log in with your FlowFuse username and password or through Single-Sign on.",[15,16668,16669],{},"Now, if your username is added to the list of admin usernames stored in the global context, you will be able to see the admin-only page.",[15,16671,16672],{},[392,16673],{"alt":16674,"dataZoomable":50,"src":16675,"title":16676},"\"Screenshot displaying the Dashboard view of normal users\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-dashboard-view-for-normal-users.png","Screenshot displaying the Dashboard view of normal users",[15,16678,16679],{},[392,16680],{"alt":16681,"dataZoomable":50,"src":16682,"title":16683},"\"Screenshot displaying the Dashboard view of admin users\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fbuilding-admin-panel-node-red-dashboard-2-dashboard-view-for-admin-users.png","Screenshot displaying the Dashboard view of admin users",[34,16685,16407],{"id":16406},[15,16687,16688],{},"If you want to learn more about FlowFuse multiuser addon and personalize the multiuser dashboard. we do have many other resources, please refer to them to learn more.",[100,16690,16691,16695,16702,16706],{},[103,16692,16693,16419],{},[307,16694,16418],{"href":16417},[103,16696,16697,16701],{},[307,16698,16700],{"href":16699},"\u002Fblog\u002F2024\u002F01\u002Fdashboard-2-multi-user\u002F","Personalised Multi-user Dashboards with Node-RED Dashboard 2.0"," - This article explores the process of building multi-user Dashboards secured with FlowFuse Cloud.",[103,16703,16704,16425],{},[307,16705,16424],{"href":10759},[103,16707,16708,16710],{},[307,16709,16438],{"href":16437}," blueprint, which allows you to utilize templates to develop Personalize multi-user dashboard quickly.",{"title":50,"searchDepth":250,"depth":250,"links":16712},[16713,16714,16715,16716,16717,16718,16719],{"id":16491,"depth":185,"text":16492},{"id":16511,"depth":185,"text":16512},{"id":16522,"depth":185,"text":16523},{"id":16556,"depth":185,"text":16557},{"id":16621,"depth":185,"text":16622},{"id":12381,"depth":185,"text":12976},{"id":16406,"depth":185,"text":16407},{"type":941,"title":16721,"description":16722},"Build Secure Admin Dashboards with Node-RED and FlowFuse","FlowFuse makes it simple to secure Node-RED dashboards with user authentication, role-based access control, and multi-user support, so you can control exactly who sees what in your production dashboards.","2024-04-08","Discover step-by-step instructions for developing an admin-only page in Node-RED Dashboard 2.0 using the FlowFuse Multiuser addon. Additionally, learn how to secure Dashboard 2.0 and explore the features of the FlowFuse multiuser addon.","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fadmin-dashboard.png",{"keywords":16727,"excerpt":16728},"node-red admin dashboard, admin panel node-red dashboard 2, admin-only page node-red, flowfuse multiuser dashboard, role-based access node-red dashboard",{"type":12,"value":16729},[16730],[15,16731,16484],{},"\u002Fblog\u002F2024\u002F04\u002Fbuilding-an-admin-panel-in-node-red-with-dashboard-2",{"title":16478,"description":16724},{"loc":16732},"blog\u002F2024\u002F04\u002Fbuilding-an-admin-panel-in-node-red-with-dashboard-2","A guide to building an Admin Dashboard in Node-RED with Dashboard 2.0",[9832,13630,16738,16739,966],"admin dashboard","admin-only page","This guide shows how to build a secure admin-only page in Node-RED Dashboard 2.0 using FlowFuse user authentication and the Multiuser Addon. By storing admin usernames in global context and wiring a ui-event through a switch node to a ui-control widget, you can dynamically show or hide the admin page based on the logged-in user's role, while keeping the page hidden by default for all other users.","VE5U-g4zYnxr4fBzQA5JFFNCQEAgcTNN2mMu1ArJOCw",{"id":16743,"title":16744,"authors":16745,"body":16746,"cta":17317,"date":17320,"description":17321,"extension":946,"image":17322,"lastUpdated":948,"meta":17323,"navigation":253,"path":17329,"seo":17330,"sitemap":17331,"stem":17332,"subtitle":17333,"tags":17334,"tldr":17335,"video":3,"__hash__":17336},"blog\u002Fblog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard.md","Displaying logged in user on Node-RED Dashboard 2.0 (2026)",[10],{"type":12,"value":16747,"toc":17306},[16748,16751,16755,16757,16760,16771,16774,16781,16784,16787,16789,16807,16811,16820,16826,16848,16851,16856,16859,16864,16867,16870,16875,16882,16886,16895,16902,16914,17261,17263,17271,17278,17280,17283,17298,17300,17303],[15,16749,16750],{},"About a month ago, a powerful solution became available to the Node-RED community to deal with users and allow multiple to interact with the same dashboard in a personalized manner. It's called the  Multli user Dashboard for Node-RED. In this guide, we will provide a step-by-step guide to show you how to secure your dashboard and access and display logged in user information on Dashboard 2.0.",[15,16752,14975,16753],{},[307,16754,12597],{"href":6792},[34,16756,16492],{"id":16491},[15,16758,16759],{},"Before we display logged-in user data on the dashboard, first we need to set up a login mechanism with FlowFuse for the dashboard. This simplifies securing Node-RED Dashboards and provides contextual user data within the Dashboard itself for who is logged in.",[326,16761,16762,16765,16768],{},[103,16763,16764],{},"Navigate to the Instance \"settings\".",[103,16766,16767],{},"Select the \"Security\" tab.",[103,16769,16770],{},"Enable “FlowFuse User Authentication”",[15,16772,16773],{},"Now, the first time you visit the dashboard, you'll need to log in with your registered FlowFuse username and password",[15,16775,16776],{},[392,16777],{"alt":16778,"dataZoomable":50,"src":16779,"title":16780},"\"Screenshot displaying the configuration settings within the FlowFuse instance, enabling user authentication for enhanced security.\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fdisplaying-logged-in-user-flowfuse-instance-setting.png","Screenshot displaying the configuration settings within the FlowFuse instance, enabling user authentication for enhanced security.",[34,16782,14989],{"id":16783},"exploring-the-flowfuse-user-addon",[15,16785,16786],{},"The FlowFuse User Addon is a plugin developed for Dashboard 2.0, leveraging the FlowFuse API to retrieve information about logged in user.",[996,16788,14982],{"id":14981},[326,16790,16791,16793,16796,16799,16804],{},[103,16792,12922],{},[103,16794,16795],{},"Click \"Manage Palette\"",[103,16797,16798],{},"Switch to the \"Install\" tab",[103,16800,350,16801],{},[19,16802,16803],{},"@flowfuse\u002Fnode-red-dashboard-2-user-addon",[103,16805,16806],{},"Click \"Install\"",[996,16808,16810],{"id":16809},"how-it-works","How it Works",[15,16812,16813,16814,16816,16817,16819],{},"In this addon, user information is attached to the ",[19,16815,260],{}," emitted by Dashboard 2.0 nodes. This user information object is attached as ",[19,16818,10348],{},". Below is an example of how that object looks:",[42,16821,16824],{"className":16822,"code":16823,"language":47},[45],"{\n   \"userId\": \"\", \u002F\u002F unique identifier for the user\n   \"username\": \"\", \u002F\u002F FlowFuse Username\n   \"email\": \"\", \u002F\u002F E-Mail Address connected to their FlowFuse account\n   \"name\": \"\", \u002F\u002F Full Name\n   \"image\": \"\" \u002F\u002F User Avatar from FlowFuse\n}\n",[19,16825,16823],{"__ignoreMap":50},[15,16827,16828,16829,16831,16832,16836,16837,16840,16841,1409,16844,16847],{},"Behind the scenes, the user addon is appending the user object to the ",[19,16830,260],{},", via the SocketIO auth option. We make the socketio object available via a computed ",[307,16833,16835],{"href":10352,"rel":16834},[311],"setup"," object, this means that we can also access user data in any ui-template widget with ",[19,16838,16839],{},"{{ setup.socketio.auth.user }}",", in the ",[19,16842,16843],{},"\u003Ctemplate>",[19,16845,16846],{},"this.setup.socketio.auth.user",", in the JS.",[15,16849,16850],{},"When running Node-RED Dashboard 2.0 on FlowFuse, you'll have a new tab available in the \"Dashboard 2.0\" sidebar in the Node-RED Editor, you just have to navigate to the \"FF Auth\" tab and you’ll see two options.",[15,16852,16853],{},[338,16854,16855],{},"Option 1: Include Client Data",[15,16857,16858],{},"By default, this option is enabled. When this option is enabled, an object with user information will be added to the “msg” emitted by any widget of the Node-RED Dashboard 2.0.",[15,16860,16861],{},[338,16862,16863],{},"Option 2: Accept Client Constraints",[15,16865,16866],{},"A feature that ensures messages are specifically targeted to individual clients, which enhances the precision and security of data transmission within the platform. It determines by enabling the nodes option in the FF Auth tab whether the enabled node type will utilize client data, such as socketid, and restrict communications to only that client.",[15,16868,16869],{},"For example, consider a manufacturing facility where each production line has its own monitoring system. With this feature enabled, data from sensors on Production Line A will only be sent to the monitoring system designated for Production Line A. This ensures that data remains isolated and relevant to each specific area of operation, maintaining organizational efficiency and security.",[15,16871,16872],{},[397,16873,16874],{},"Note: Please note that Multi-User Addons can only be used by our Teams and Enterprise Self-Hosted customers. Upon request, we provide all required configurations to get started.",[15,16876,16877],{},[392,16878],{"alt":16879,"dataZoomable":50,"src":16880,"title":16881},"\"Screenshot displaying the FlowFuse Muti-user addon option\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fdisplaying-logged-in-user-ff-auth-tab.png","Screenshot displaying the FlowFuse Muti-user addon option",[34,16883,16885],{"id":16884},"displaying-logged-in-user-on-dashboard-20","Displaying logged in user on Dashboard 2.0",[15,16887,16888,16889,16891,16892,16894],{},"Now you know how the user add on works, you are all set to display logged in users on Dashboard 2.0. To confirm this you can use a ",[19,16890,1395],{}," node that receives the ",[19,16893,260],{}," object emitted by the Dashboard 2.0 widgets.",[15,16896,16897,16898,16901],{},"To display user information on the dashboard we will use Vue’s ",[307,16899,9996],{"href":9859,"rel":16900},[311]," feature to render content to a specific location in the DOM, we will display user information at the action bar’s right-hand side.",[326,16903,16904,16908,16911],{},[103,16905,408,16906,12951],{},[19,16907,5152],{},[103,16909,16910],{},"Click on that node, and select type as “Widget (Ui-Scoped)”. ( this allows us to render this ui-template at ui scoped which means I will not required to add separate ui-templates for different pages )",[103,16912,16913],{},"Copy the below vue snippet and paste that into the ui-template.",[42,16915,16917],{"className":5156,"code":16916,"language":5158,"meta":50,"style":50},"\u003Ctemplate>\n    \u003C!-- Teleporting user info to #app-bar-actions, which is the ID of the action bars' right corners area -->\n    \u003CTeleport v-if=\"loaded\" to=\"#app-bar-actions\">\n        \u003Cdiv class=\"user-info\">\n            \u003C!-- Displaying user image -->\n            \u003Cimg :src=\"setup.socketio.auth.user.image\" \u002F>\n            \u003C!-- Greeting the user -->{% raw %}\n            \u003Cspan>Hi, {{ setup.socketio.auth.user.name }}\u003C\u002Fspan>{% endraw %}\n        \u003C\u002Fdiv>\n    \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\nexport default {\n    data() {\n        return {\n            \u002F\u002F Flag to indicate if the component is loaded\n            loaded: false\n        };\n    },\n    mounted() {\n        \u002F\u002F This function is called when the component is inserted into the DOM.\n        \u002F\u002F Setting loaded to true here ensures the component is ready to access #app-bar-actions,\n        \u002F\u002F as it's now part of the same DOM structure.\n        \u002F\u002F Accessing it before mounted() would cause an error because the component wouldn't be initialized in the DOM yet.\n        this.loaded = true; \u002F\u002F Setting loaded to true to indicate that the component has been mounted successfully\n    }\n}\n\u003C\u002Fscript>\n\n\u003Cstyle>\n\u002F* Styling for user info display *\u002F\n.user-info {\n    display: flex;\n    align-items: center;\n    gap: 8px;\n}\n\u002F* Styling for user avatar image*\u002F\n.user-info img {\n    width: 24px;\n    height: 24px;\n}\n\u003C\u002Fstyle>\n",[19,16918,16919,16927,16931,16959,16977,16981,16999,17007,17025,17033,17041,17049,17053,17061,17069,17077,17083,17087,17095,17099,17103,17111,17115,17119,17123,17127,17141,17145,17149,17157,17161,17169,17173,17181,17191,17201,17211,17215,17219,17229,17239,17249,17253],{"__ignoreMap":50},[146,16920,16921,16923,16925],{"class":148,"line":149},[146,16922,5165],{"class":160},[146,16924,9982],{"class":1549},[146,16926,5183],{"class":160},[146,16928,16929],{"class":148,"line":185},[146,16930,10391],{"class":3874},[146,16932,16933,16935,16937,16939,16941,16943,16945,16947,16949,16951,16953,16955,16957],{"class":148,"line":221},[146,16934,5188],{"class":160},[146,16936,9996],{"class":1549},[146,16938,9999],{"class":152},[146,16940,161],{"class":160},[146,16942,727],{"class":160},[146,16944,10406],{"class":1554},[146,16946,727],{"class":160},[146,16948,10011],{"class":152},[146,16950,161],{"class":160},[146,16952,727],{"class":160},[146,16954,10314],{"class":1554},[146,16956,727],{"class":160},[146,16958,5183],{"class":160},[146,16960,16961,16963,16965,16967,16969,16971,16973,16975],{"class":148,"line":250},[146,16962,10026],{"class":160},[146,16964,5168],{"class":1549},[146,16966,10429],{"class":152},[146,16968,161],{"class":160},[146,16970,727],{"class":160},[146,16972,10436],{"class":1554},[146,16974,727],{"class":160},[146,16976,5183],{"class":160},[146,16978,16979],{"class":148,"line":257},[146,16980,10445],{"class":3874},[146,16982,16983,16985,16987,16989,16991,16993,16995,16997],{"class":148,"line":284},[146,16984,10450],{"class":160},[146,16986,392],{"class":1549},[146,16988,10455],{"class":152},[146,16990,161],{"class":160},[146,16992,727],{"class":160},[146,16994,10462],{"class":1554},[146,16996,727],{"class":160},[146,16998,10467],{"class":160},[146,17000,17001,17004],{"class":148,"line":666},[146,17002,17003],{"class":3874},"            \u003C!-- Greeting the user -->",[146,17005,17006],{"class":156},"{% raw %}\n",[146,17008,17009,17011,17013,17015,17017,17019,17021,17023],{"class":148,"line":1603},[146,17010,10450],{"class":160},[146,17012,146],{"class":1549},[146,17014,10032],{"class":160},[146,17016,10483],{"class":156},[146,17018,5247],{"class":160},[146,17020,146],{"class":1549},[146,17022,10032],{"class":160},[146,17024,15928],{"class":156},[146,17026,17027,17029,17031],{"class":148,"line":1608},[146,17028,10494],{"class":160},[146,17030,5168],{"class":1549},[146,17032,5183],{"class":160},[146,17034,17035,17037,17039],{"class":148,"line":1624},[146,17036,5238],{"class":160},[146,17038,9996],{"class":1549},[146,17040,5183],{"class":160},[146,17042,17043,17045,17047],{"class":148,"line":2546},[146,17044,5247],{"class":160},[146,17046,9982],{"class":1549},[146,17048,5183],{"class":160},[146,17050,17051],{"class":148,"line":2564},[146,17052,254],{"emptyLinePlaceholder":253},[146,17054,17055,17057,17059],{"class":148,"line":2569},[146,17056,5165],{"class":160},[146,17058,10102],{"class":1549},[146,17060,5183],{"class":160},[146,17062,17063,17065,17067],{"class":148,"line":2574},[146,17064,10531],{"class":287},[146,17066,10112],{"class":287},[146,17068,1517],{"class":160},[146,17070,17071,17073,17075],{"class":148,"line":2594},[146,17072,10540],{"class":1549},[146,17074,1461],{"class":160},[146,17076,1517],{"class":160},[146,17078,17079,17081],{"class":148,"line":2614},[146,17080,10549],{"class":287},[146,17082,1517],{"class":160},[146,17084,17085],{"class":148,"line":2633},[146,17086,10556],{"class":3874},[146,17088,17089,17091,17093],{"class":148,"line":2662},[146,17090,10561],{"class":1549},[146,17092,730],{"class":160},[146,17094,10140],{"class":4671},[146,17096,17097],{"class":148,"line":2667},[146,17098,10570],{"class":160},[146,17100,17101],{"class":148,"line":2672},[146,17102,2520],{"class":160},[146,17104,17105,17107,17109],{"class":148,"line":2692},[146,17106,10579],{"class":1549},[146,17108,1461],{"class":160},[146,17110,1517],{"class":160},[146,17112,17113],{"class":148,"line":2705},[146,17114,10588],{"class":3874},[146,17116,17117],{"class":148,"line":2722},[146,17118,10593],{"class":3874},[146,17120,17121],{"class":148,"line":2787},[146,17122,10598],{"class":3874},[146,17124,17125],{"class":148,"line":2801},[146,17126,10603],{"class":3874},[146,17128,17129,17131,17133,17135,17137,17139],{"class":148,"line":2861},[146,17130,10608],{"class":160},[146,17132,10406],{"class":156},[146,17134,1539],{"class":160},[146,17136,1866],{"class":4671},[146,17138,10617],{"class":160},[146,17140,10620],{"class":3874},[146,17142,17143],{"class":148,"line":2920},[146,17144,3412],{"class":160},[146,17146,17147],{"class":148,"line":2978},[146,17148,754],{"class":160},[146,17150,17151,17153,17155],{"class":148,"line":3034},[146,17152,5247],{"class":160},[146,17154,10102],{"class":1549},[146,17156,5183],{"class":160},[146,17158,17159],{"class":148,"line":3091},[146,17160,254],{"emptyLinePlaceholder":253},[146,17162,17163,17165,17167],{"class":148,"line":3148},[146,17164,5165],{"class":160},[146,17166,924],{"class":1549},[146,17168,5183],{"class":160},[146,17170,17171],{"class":148,"line":3204},[146,17172,10653],{"class":3874},[146,17174,17175,17177,17179],{"class":148,"line":3210},[146,17176,167],{"class":160},[146,17178,10436],{"class":2435},[146,17180,1517],{"class":160},[146,17182,17183,17185,17187,17189],{"class":148,"line":3216},[146,17184,10667],{"class":10666},[146,17186,730],{"class":160},[146,17188,10672],{"class":156},[146,17190,182],{"class":160},[146,17192,17193,17195,17197,17199],{"class":148,"line":3221},[146,17194,10679],{"class":10666},[146,17196,730],{"class":160},[146,17198,10684],{"class":156},[146,17200,182],{"class":160},[146,17202,17203,17205,17207,17209],{"class":148,"line":3226},[146,17204,10691],{"class":10666},[146,17206,730],{"class":160},[146,17208,10696],{"class":206},[146,17210,182],{"class":160},[146,17212,17213],{"class":148,"line":3246},[146,17214,754],{"class":160},[146,17216,17217],{"class":148,"line":3263},[146,17218,10707],{"class":3874},[146,17220,17221,17223,17225,17227],{"class":148,"line":3283},[146,17222,167],{"class":160},[146,17224,10436],{"class":2435},[146,17226,10716],{"class":2435},[146,17228,1517],{"class":160},[146,17230,17231,17233,17235,17237],{"class":148,"line":3312},[146,17232,10723],{"class":10666},[146,17234,730],{"class":160},[146,17236,10728],{"class":206},[146,17238,182],{"class":160},[146,17240,17241,17243,17245,17247],{"class":148,"line":3317},[146,17242,10735],{"class":10666},[146,17244,730],{"class":160},[146,17246,10728],{"class":206},[146,17248,182],{"class":160},[146,17250,17251],{"class":148,"line":3322},[146,17252,754],{"class":160},[146,17254,17255,17257,17259],{"class":148,"line":3342},[146,17256,5247],{"class":160},[146,17258,924],{"class":1549},[146,17260,5183],{"class":160},[34,17262,12976],{"id":12381},[326,17264,17265,17267],{},[103,17266,16657],{},[103,17268,16660,17269,167],{},[19,17270,16663],{},[15,17272,17273],{},[392,17274],{"alt":17275,"dataZoomable":50,"src":17276,"title":17277},"\"Screenshot of Dashboard displaying logged in user information\"","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Fdisplaying-logged-in-user-dashboard-view.png","Screenshot of Dashboard displaying logged in user information",[34,17279,16407],{"id":16406},[15,17281,17282],{},"If you want to learn more about the FlowFuse Multiuser addon and Personalize Multiuser Dashboard. we do have many other resources, please refer to them to learn more.",[100,17284,17285,17289,17293],{},[103,17286,17287,16419],{},[307,17288,16418],{"href":16417},[103,17290,17291,16701],{},[307,17292,16700],{"href":16699},[103,17294,17295,17297],{},[307,17296,16438],{"href":16437}," blueprint, which allows you to quickly utilize templates to develope Personalize multi-user dashboard.",[34,17299,6518],{"id":6517},[15,17301,17302],{},"In this guide, we have demonstrated how to secure your dashboard and how to retrieve and display logged in user data on Dashboard 2.0. Additionally, we have discussed the functionality of the FlowFuse multi-user addon.",[924,17304,17305],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sqsOY, html code.shiki .sqsOY{--shiki-light:#8796B0;--shiki-default:#B2CCD6;--shiki-dark:#B2CCD6}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":17307},[17308,17309,17313,17314,17315,17316],{"id":16491,"depth":185,"text":16492},{"id":16783,"depth":185,"text":14989,"children":17310},[17311,17312],{"id":14981,"depth":221,"text":14982},{"id":16809,"depth":221,"text":16810},{"id":16884,"depth":185,"text":16885},{"id":12381,"depth":185,"text":12976},{"id":16406,"depth":185,"text":16407},{"id":6517,"depth":185,"text":6518},{"type":941,"title":17318,"description":17319},"Build Secure Multi-User Dashboards With Node-RED","FlowFuse makes it simple to add user authentication, personalized views, and role-based access to your Node-RED dashboards. Start your free trial and see it in action.","2024-04-03","Learn how to secure your Dashboard, install, and configure the FlowFuse Multi-user addon, and display logged-in users on Node-RED Dashboard 2.0. Additionally, delve deeper into understanding how the FlowFuse Multi-user addon functions.","\u002Fblog\u002F2024\u002F04\u002Fimages\u002Flogged-in-user-dashboard.png",{"keywords":17324,"excerpt":17325},"node-red dashboard 2.0, display logged in user, flowfuse multi-user addon, dashboard user authentication, node-red user info, dashboard 2.0 personalization, flowfuse dashboard security",{"type":12,"value":17326},[17327],[15,17328,16750],{},"\u002Fblog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard",{"title":16744,"description":17321},{"loc":17329},"blog\u002F2024\u002F04\u002Fdisplaying-logged-in-users-on-dashboard","Step-by-Step Beginner's Guide to Displaying logged in User on Node-RED Dashboard 2.0",[9832,13630,966],"This guide explains how to display the logged-in user's name and avatar on a Node-RED Dashboard 2.0 by enabling FlowFuse User Authentication, installing the @flowfuse\u002Fnode-red-dashboard-2-user-addon, and using a Vue Teleport inside a ui-template node. The addon attaches user data to every dashboard message under msg._client.user, making personalization straightforward without custom backend code.","yGkDUc5egaBxzOnvICzIILNj48FwYQmmOwym9Kk6nmE",{"id":17338,"title":17339,"authors":17340,"body":17341,"cta":3,"date":17572,"description":17573,"extension":946,"image":17574,"lastUpdated":3,"meta":17575,"navigation":253,"path":17580,"seo":17581,"sitemap":17582,"stem":17583,"subtitle":17584,"tags":17585,"tldr":3,"video":3,"__hash__":17586},"blog\u002Fblog\u002F2024\u002F02\u002Ftaking-it-further-with-node-red.md","Storing Data: Getting Started with Node-RED",[7451],{"type":12,"value":17342,"toc":17565},[17343,17346,17349,17353,17356,17361,17364,17367,17378,17381,17385,17388,17391,17397,17400,17406,17408,17426,17430,17433,17436,17439,17445,17447,17493,17496,17500,17509,17512,17518,17521,17523,17551,17553,17556],[15,17344,17345],{},"It's quite straightforward to pass plenty of useful data with each message (msg) in your flows. Not only can you store information in msg.payload, but you can also place information in any other named object, for instance, msg.store.",[15,17347,17348],{},"In this article, we will explore some of the better solutions for storing and retrieving transactional information in your flows.",[4987,17350,17352],{"id":17351},"storing-data-outside-of-msgpayload","Storing data outside of msg.payload",[15,17354,17355],{},"In this example, we have data in msg.payload as well as in msg.later.",[15,17357,17358],{},[392,17359],{"alt":17352,"src":17360,"title":17352},"\u002Fblog\u002F2024\u002F02\u002Fimages\u002Fdata-outside-msg-payload.gif",[15,17362,17363],{},"If you want your debug to display the full content of the message, change the output to 'complete message object' as shown above.",[15,17365,17366],{},"You can import the flow using this code.",[15,17368,6389,17369,3830],{},[146,17370,17371,17372,17375,17376,1596],{},"{\"id\":\"aaa1e17e5f158004\",\"type\":\"inject\",\"z\":\"67746003c844dbc4\",\"name\":\"Inject the message\",\"props\":",[146,17373,17374],{},"{\"p\":\"payload\"},{\"p\":\"later\",\"v\":\"A string I want to be able to use later in my flow\",\"vt\":\"str\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"Hello World\",\"payloadType\":\"str\",\"x\":690,\"y\":140,\"wires\":[[\"bce1bf09736125b7\"]]},{\"id\":\"bce1bf09736125b7\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":880,\"y\":140,\"wires\":",[146,17377],{},[15,17379,17380],{},"Storing data outside of msg.payload can be very useful when you need access to that data later in your flow. You may notice that many nodes overwrite the content of msg.payload, so putting your data elsewhere is essential otherwise, it will be overwritten and lost.",[4987,17382,17384],{"id":17383},"tidying-your-messages","Tidying your messages",[15,17386,17387],{},"You may also want to remove data you don't need from your messages to optimize the speed of your flows.",[15,17389,17390],{},"It's easy enough to remove data you don't need, wherever it sits within your messages using the Change Node. In this example, we are going to delete the content of msg.other while leaving the rest of the message to be passed to the next Node.",[15,17392,17393],{},[392,17394],{"alt":17395,"src":17396,"title":17395},"Deleting data from msg.other","\u002Fblog\u002F2024\u002F02\u002Fimages\u002Fdelete-other.gif",[15,17398,17399],{},"The Change Node is configured as follows.",[15,17401,17402],{},[392,17403],{"alt":17404,"src":17405,"title":17404},"Change Node configuration","\u002Fblog\u002F2024\u002F02\u002Fimages\u002Fdelete.png",[15,17407,17366],{},[15,17409,6389,17410,3830],{},[146,17411,17412,17413,17416,17417,17419,17420,17423,17424,1596],{},"{\"id\":\"1ac51e71153f7c1f\",\"type\":\"inject\",\"z\":\"67746003c844dbc4\",\"name\":\"Inject the message\",\"props\":",[146,17414,17415],{},"{\"p\":\"payload\"},{\"p\":\"other\",\"v\":\"We don't need this string anymore\",\"vt\":\"str\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"Hello World\",\"payloadType\":\"str\",\"x\":530,\"y\":100,\"wires\":[[\"5d3a978ad9eab443\",\"cd7609101328caa2\"]]},{\"id\":\"5d3a978ad9eab443\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":700,\"y\":60,\"wires\":",[146,17418],{},"},{\"id\":\"cd7609101328caa2\",\"type\":\"change\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"rules\":",[146,17421,17422],{},"{\"t\":\"delete\",\"p\":\"other\",\"pt\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":730,\"y\":100,\"wires\":[[\"be2f7f68dee570be\"]]},{\"id\":\"be2f7f68dee570be\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 3\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":900,\"y\":100,\"wires\":",[146,17425],{},[4987,17427,17429],{"id":17428},"storing-data-outside-of-msgpayload-so-you-can-access-it-later-in-your-flows","Storing data outside of msg.payload so you can access it later in your flows",[15,17431,17432],{},"Storing data outside of msg.payload allows you to access it later in your flows. In this example, we inject geographical coordinates and use an API to get the sunset time for each location. We can then output the result as a sentence.",[15,17434,17435],{},"As the HTTP Node, which we are using to interact with the weather API, overwrites msg.payload with the response, we will store the submitted city name and coordinates in msg.store for later use.",[15,17437,17438],{},"You can see the flow working below.",[15,17440,17441],{},[392,17442],{"alt":17443,"src":17444,"title":17443},"Example flow which gets the sunset time for a given location","\u002Fblog\u002F2024\u002F02\u002Fimages\u002Fsunset-example.gif",[15,17446,17366],{},[15,17448,6389,17449,3830],{},[146,17450,17451,17452,17455,17456,17459,17460,17462,17463,17466,17467,17472,17473,17475,17476,17478,17479,17481,17482,17486,17487,17490,17491,1596],{},"{\"id\":\"809cc8f4678767b7\",\"type\":\"inject\",\"z\":\"67746003c844dbc4\",\"name\":\"London\",\"props\":",[146,17453,17454],{},"{\"p\":\"payload.city\",\"v\":\"London\",\"vt\":\"str\"},{\"p\":\"payload.lat\",\"v\":\"51.5072\",\"vt\":\"str\"},{\"p\":\"payload.lng\",\"v\":\"0.1276\",\"vt\":\"str\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":170,\"y\":80,\"wires\":[[\"6b5a6a2ef7a64f1a\"]]},{\"id\":\"b86d2d558eebbd7e\",\"type\":\"inject\",\"z\":\"67746003c844dbc4\",\"name\":\"Washington DC\",\"props\":",[146,17457,17458],{},"{\"p\":\"payload.city\",\"v\":\"Washington DC\",\"vt\":\"str\"},{\"p\":\"payload.lat\",\"v\":\"38.9072\",\"vt\":\"str\"},{\"p\":\"payload.lng\",\"v\":\"77.0369\",\"vt\":\"str\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":140,\"y\":160,\"wires\":[[\"6b5a6a2ef7a64f1a\"]]},{\"id\":\"aaecc81a2de233d6\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 4\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":820,\"y\":160,\"wires\":",[146,17461],{},"},{\"id\":\"6b5a6a2ef7a64f1a\",\"type\":\"change\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"rules\":",[146,17464,17465],{},"{\"t\":\"set\",\"p\":\"store\",\"pt\":\"msg\",\"to\":\"payload\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":320,\"y\":120,\"wires\":[[\"273ee743f8d1a5f1\",\"af43cea5866e11f5\"]]},{\"id\":\"273ee743f8d1a5f1\",\"type\":\"template\",\"z\":\"67746003c844dbc4\",\"name\":\"create the URL\",\"field\":\"url\",\"fieldType\":\"msg\",\"format\":\"handlebars\",\"syntax\":\"mustache\",\"template\":{% raw %}\"",[307,17468,17471],{"href":17469,"rel":17470},"https:\u002F\u002Fapi.sunrisesunset.io\u002Fjson?lat=%7B%7Bstore.lat%7D%7D&lng=%7B%7Bstore.lng%7D%7D%22%7B%25",[311],"https:\u002F\u002Fapi.sunrisesunset.io\u002Fjson?lat={{store.lat}}&lng={{store.lng}}\"{%"," endraw %},\"output\":\"str\",\"x\":500,\"y\":120,\"wires\":[[\"52e8913233f379eb\",\"1d94053c790791ee\"]]},{\"id\":\"af43cea5866e11f5\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 5\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":480,\"y\":160,\"wires\":",[146,17474],{},"},{\"id\":\"52e8913233f379eb\",\"type\":\"http request\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"method\":\"GET\",\"ret\":\"obj\",\"paytoqs\":\"ignore\",\"url\":\"\",\"tls\":\"\",\"persist\":false,\"proxy\":\"\",\"insecureHTTPParser\":false,\"authType\":\"\",\"senderr\":false,\"headers\":",[146,17477],{},",\"x\":670,\"y\":120,\"wires\":[[\"aaecc81a2de233d6\",\"a245b322ade3a7e9\"]]},{\"id\":\"1d94053c790791ee\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 6\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":660,\"y\":80,\"wires\":",[146,17480],{},"},{\"id\":\"a245b322ade3a7e9\",\"type\":\"template\",\"z\":\"67746003c844dbc4\",\"name\":\"Create the sentence\",\"field\":\"payload\",\"fieldType\":\"msg\",\"format\":\"handlebars\",\"syntax\":\"mustache\",\"template\":\"The sun will set at ",[17483,17484],"binding",{"value":17485},"payload.results.sunset"," in ",[17483,17488],{"value":17489},"store.city",".\",\"output\":\"str\",\"x\":860,\"y\":120,\"wires\":[[\"3e4b2d3644845f91\"]]},{\"id\":\"3e4b2d3644845f91\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 7\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":1040,\"y\":120,\"wires\":",[146,17492],{},[15,17494,17495],{},"Using this technique, we can build the content of message.store (or any other name you'd like to use) and then output the content at the end of a flow, even if you use msg.payload to interact with APIs and other custom nodes.",[4987,17497,17499],{"id":17498},"why-not-store-the-values-in-context","Why not store the values in context?",[15,17501,17502,17503,17508],{},"Where possible, it's more robust to store all the information related to a particular message within the message rather than saving it to context and retrieving it later. Using context risks a ",[307,17504,17507],{"href":17505,"rel":17506},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FRace_condition#:~:text=A%20race%20condition%20or%20race,to%20unexpected%20or%20inconsistent%20results",[311],"race condition"," within your flow that could result in data corruption.",[15,17510,17511],{},"In this example, we simulate how a race condition can make context a bad choice for transactional data storage. The flow passes in the name and age of two people then moves the age to context. The flow then adds a random delay for each message so that, in some cases, the messages do not reach the debug in the order they were created. After the delay, the age is pulled back from context and added to each msg.payload.",[15,17513,17514],{},[392,17515],{"alt":17516,"src":17517,"title":17516},"Example of how a race condition can make context a bad place to cache data","\u002Fblog\u002F2024\u002F02\u002Fimages\u002Frace-condition.gif",[15,17519,17520],{},"If there is a race condition in play, we should intermittently see Rob and John's stored ages being assigned to the wrong person. We can see in the image above that Rob is showing the incorrect age.",[15,17522,17366],{},[15,17524,6389,17525,3830],{},[146,17526,17527,17528,17531,17532,17535,17536,17539,17540,17543,17544,17546,17547,17550],{},"{\"id\":\"7ac4e7165d99f041\",\"type\":\"inject\",\"z\":\"67746003c844dbc4\",\"name\":\"Rob\",\"props\":",[146,17529,17530],{},"{\"p\":\"payload.name\",\"v\":\"Rob\",\"vt\":\"str\"},{\"p\":\"payload.age\",\"v\":\"46\",\"vt\":\"str\"}",",\"repeat\":\"2\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":570,\"y\":840,\"wires\":[[\"1e0575bd662f9277\"]]},{\"id\":\"e9c0baba4f50b0b2\",\"type\":\"inject\",\"z\":\"67746003c844dbc4\",\"name\":\"John\",\"props\":",[146,17533,17534],{},"{\"p\":\"payload.name\",\"v\":\"John\",\"vt\":\"str\"},{\"p\":\"payload.age\",\"v\":\"29\",\"vt\":\"str\"}",",\"repeat\":\"2\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":570,\"y\":880,\"wires\":[[\"1e0575bd662f9277\"]]},{\"id\":\"d5f688682206d316\",\"type\":\"change\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"rules\":",[146,17537,17538],{},"{\"t\":\"set\",\"p\":\"age\",\"pt\":\"flow\",\"to\":\"payload.age\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":850,\"y\":860,\"wires\":[[\"d56225e59bde93cf\"]]},{\"id\":\"d56225e59bde93cf\",\"type\":\"change\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"rules\":",[146,17541,17542],{},"{\"t\":\"delete\",\"p\":\"payload.age\",\"pt\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1050,\"y\":860,\"wires\":[[\"d5a119b311bf8377\"]]},{\"id\":\"e3ec11b4e038e564\",\"type\":\"debug\",\"z\":\"67746003c844dbc4\",\"name\":\"debug 8\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":1080,\"y\":940,\"wires\":",[146,17545],{},"},{\"id\":\"d5a119b311bf8377\",\"type\":\"delay\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"pauseType\":\"random\",\"timeout\":\"5\",\"timeoutUnits\":\"seconds\",\"rate\":\"1\",\"nbRateUnits\":\"1\",\"rateUnits\":\"second\",\"randomFirst\":\"0\",\"randomLast\":\"5\",\"randomUnits\":\"seconds\",\"drop\":false,\"allowrate\":false,\"outputs\":1,\"x\":720,\"y\":940,\"wires\":[[\"b329d7f2685d0ed6\"]]},{\"id\":\"b329d7f2685d0ed6\",\"type\":\"change\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"rules\":",[146,17548,17549],{},"{\"t\":\"set\",\"p\":\"payload.age\",\"pt\":\"msg\",\"to\":\"age\",\"tot\":\"flow\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":900,\"y\":940,\"wires\":[[\"e3ec11b4e038e564\"]]},{\"id\":\"1e0575bd662f9277\",\"type\":\"delay\",\"z\":\"67746003c844dbc4\",\"name\":\"\",\"pauseType\":\"random\",\"timeout\":\"5\",\"timeoutUnits\":\"seconds\",\"rate\":\"1\",\"nbRateUnits\":\"1\",\"rateUnits\":\"second\",\"randomFirst\":\"0\",\"randomLast\":\"1\",\"randomUnits\":\"seconds\",\"drop\":false,\"allowrate\":false,\"outputs\":1,\"x\":700,\"y\":860,\"wires\":[[\"d5f688682206d316\"]]}",[4987,17552,6518],{"id":6517},[15,17554,17555],{},"Storing transactional data in msg rather than context in Node-RED offers advantages in modularity, scalability, simplicity, and also helps prevent race conditions. By using msg, data transfer between nodes becomes more seamless, allowing for independent node reuse across flows without additional configuration. This approach ensures scalability by avoiding the imposition of large datasets on the global context. Moreover, storing data outside of msg.payload within the msg object enhances flexibility. It separates metadata and other relevant information from the main payload, promoting a cleaner and more organized structure. This practice not only aligns with Node-RED's visual programming paradigm but also improves code readability. Steering clear of context for transactional data, while also organizing it within the msg object, provides a comprehensive and reliable solution within the Node-RED framework.",[15,17557,17558,17559,17564],{},"Thanks to ",[307,17560,17563],{"href":17561,"rel":17562},"https:\u002F\u002Fsunrisesunset.io\u002F",[311],"SunriseSunset"," for creating the useful API we've used in this article.",{"title":50,"searchDepth":250,"depth":250,"links":17566},[17567,17568,17569,17570,17571],{"id":17351,"depth":250,"text":17352},{"id":17383,"depth":250,"text":17384},{"id":17428,"depth":250,"text":17429},{"id":17498,"depth":250,"text":17499},{"id":6517,"depth":250,"text":6518},"2024-02-19","In this article we are going to explain how you can store data outside of msg.payload for later use","\u002Fblog\u002F2024\u002F02\u002Fimages\u002Fstoring-data-getting-started-with-node-red.png",{"excerpt":17576},{"type":12,"value":17577},[17578],[15,17579,17345],{},"\u002Fblog\u002F2024\u002F02\u002Ftaking-it-further-with-node-red",{"title":17339,"description":17573},{"loc":17580},"blog\u002F2024\u002F02\u002Ftaking-it-further-with-node-red","Node-RED is one of the easiest ways to program ever created but everyone needs a little help",[9832,6563,966],"Lf90bJc8iDnUNrQLo3dAEUxwJlkA3J8tm2Jy0mgB1MU",{"id":17588,"title":17589,"authors":17590,"body":17592,"cta":3,"date":17877,"description":17878,"extension":946,"image":17879,"lastUpdated":3,"meta":17880,"navigation":253,"path":17890,"seo":17891,"sitemap":17892,"stem":17893,"subtitle":17894,"tags":17895,"tldr":3,"video":3,"__hash__":17896},"blog\u002Fblog\u002F2024\u002F01\u002Fhow-to-deploy-node-red-with-flowfuse-to-balenacloud.md","Step-by-Step Guide to Deploying Node-RED with FlowFuse in balenaCloud",[17591],"grey-dziuba",{"type":12,"value":17593,"toc":17862},[17594,17608,17612,17622,17626,17634,17638,17664,17668,17676,17680,17683,17704,17708,17735,17739,17742,17763,17767,17770,17795,17799,17812,17832,17836,17839,17857,17859],[15,17595,17596,17597,17601,17602,17607],{},"In a ",[307,17598,17600],{"href":17599},"\u002Fwebinars\u002F2024\u002Fbalena\u002F","recent webinar with balena",", we explored the dynamic capabilities of deploying FlowFuse to a fleet of devices using ",[307,17603,17606],{"href":17604,"rel":17605},"https:\u002F\u002Fwww.balena.io\u002Fcloud",[311],"balenaCloud",". This blog post serves as a practical guide to replicate that process, specifically tailored for those aiming to streamline their deployment of FlowFuse in an efficient and user-friendly manner.",[34,17609,17611],{"id":17610},"how-to-implement-flowfuse-with-balenacloud-on-a-fleet-of-devices","How to Implement FlowFuse with balenaCloud on a Fleet of devices",[15,17613,17614],{},[307,17615,17619],{"href":17616,"rel":17617,"title":17618},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=cKFu1ljUlKE",[311],"Deploying Node-RED with FlowFuse in balenaCloud",[392,17620],{"alt":17618,"src":17621},"https:\u002F\u002Fi.ytimg.com\u002Fvi\u002FcKFu1ljUlKE\u002Fhqdefault.jpg",[996,17623,17625],{"id":17624},"preparation-steps","Preparation Steps",[15,17627,17628,17629,17633],{},"Before diving into the deployment process, it's crucial to familiarize yourself with key resources. We recommend reviewing our previous ",[307,17630,17632],{"href":17631},"\u002Fblog\u002F2023\u002F11\u002Fdevice-agent-balena\u002F","blog post"," on deploying the FlowFuse Device Agent via balena. This post contains a vital link to the GitHub repository, essential for deploying FlowFuse with balena, laying the groundwork for the steps ahead.",[996,17635,17637],{"id":17636},"creating-a-new-fleet-in-balenacloud","Creating a New Fleet in balenaCloud",[326,17639,17640,17653,17656,17659],{},[103,17641,17642,17643,17648,17649,17652],{},"Navigate to the ",[307,17644,17647],{"href":17645,"rel":17646},"https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fbalena-device-agent",[311],"FlowFuse git"," repository. Click on the ",[338,17650,17651],{},"Deploy with balena"," button.",[103,17654,17655],{},"Name your fleet.",[103,17657,17658],{},"Select your default device.",[103,17660,355,17661,167],{},[338,17662,17663],{},"Create and Deploy",[996,17665,17667],{"id":17666},"adding-devices-to-the-fleet","Adding Devices to the Fleet",[15,17669,17670,17671,167],{},"Once your fleet is created, the next step is to add devices. To add a device to your fleet, follow these ",[307,17672,17675],{"href":17673,"rel":17674},"https:\u002F\u002Fdocs.balena.io\u002Flearn\u002Fgetting-started\u002Fvar-som-mx6\u002Frust\u002F#add-a-device-and-download-os",[311],"instructions",[996,17677,17679],{"id":17678},"setting-up-flowfuse","Setting Up FlowFuse",[15,17681,17682],{},"Setting up FlowFuse correctly is essential for seamless operation:",[326,17684,17685,17691,17701],{},[103,17686,17687,17688,17690],{},"Create a new instance within FlowFuse or use an existing one if you prefer. Follow these ",[307,17689,17675],{"href":6647}," to create a new instance.",[103,17692,17693,17694,17697,17698,167],{},"Create a ",[338,17695,17696],{},"Device Provisioning Token"," by following these ",[307,17699,17675],{"href":17700},"\u002Fdocs\u002Fdevice-agent\u002Fregister\u002F#bulk-registration",[103,17702,17703],{},"Ensure you add the FlowFuse Node-RED application you want the devices to provision. If left at default, devices will need to be manually added to applications.",[996,17705,17707],{"id":17706},"using-the-device-provisioning-token","Using the Device Provisioning Token",[326,17709,17710,17717,17732],{},[103,17711,17712,17713,17716],{},"First, convert the contents of the Device Provisioning Token to base64. Follow these ",[307,17714,17675],{"href":17715},"\u002Fblog\u002F2023\u002F11\u002Fdevice-agent-balena\u002F#environment-variable"," to convert the file to base64.",[103,17718,17719,17720,17723,17724,17728,17729,167],{},"Once converted, import this string into balena as a ",[338,17721,17722],{},"Fleet"," level variable, not a device level variable. Follow these ",[307,17725,17675],{"href":17726,"rel":17727},"https:\u002F\u002Fdocs.balena.io\u002Flearn\u002Fmanage\u002Fvariables\u002F#fleet-wide-variables",[311]," to import the Fleet level variable with the Name ",[19,17730,17731],{},"FF_DEVICE_YML",[103,17733,17734],{},"This action will provision any new device added to the fleet with the yaml file configuration, automatically adding the device to a FlowFuse instance.",[996,17736,17738],{"id":17737},"deploying-and-testing-the-flowfuse-instance","Deploying and Testing the FlowFuse Instance",[15,17740,17741],{},"Deploying the FlowFuse instance brings everything together:",[326,17743,17744,17747,17750,17760],{},[103,17745,17746],{},"Navigate to your FlowFuse application created earlier.",[103,17748,17749],{},"Go to your devices and you should now see your newly provisioned devices from balena.",[103,17751,17752,17753,17756,17757,167],{},"If this is your first time setting up your fleet, the device will not have a snapshot. You will need to deploy a snapshot. Follow these ",[307,17754,17675],{"href":17755},"\u002Fdocs\u002Fuser\u002Fsnapshots\u002F#create-a-snapshot"," to do so. Ensure that you select ",[338,17758,17759],{},"Set Target Snapshot",[103,17761,17762],{},"Once complete, the FlowFuse instance will deploy to your device(s).",[34,17764,17766],{"id":17765},"integrating-influxdb-optional","Integrating InfluxDB (Optional)",[15,17768,17769],{},"Integrating InfluxDB enables effective data storage and management:",[326,17771,17772,17783,17789],{},[103,17773,17774,17775,17780,17781,167],{},"Similar to the previous steps, navigate to this ",[307,17776,17779],{"href":17777,"rel":17778},"https:\u002F\u002Fgithub.com\u002Fmpous\u002Fflowfuse-agent-influx-balena\u002Ftree\u002Fmain?tab=readme-ov-file",[311],"Github repository"," and click ",[338,17782,17651],{},[103,17784,17785,17786,167],{},"This time, instead of creating a new fleet, select ",[338,17787,17788],{},"Use an existing fleet instead",[103,17790,17791,17792,167],{},"Choose your fleet for deployment and select ",[338,17793,17794],{},"Deploy to fleet",[996,17796,17798],{"id":17797},"data-generation-and-management","Data Generation and Management",[15,17800,17801,17802,17807,17808,17811],{},"For testing, we have created a flow to get you started. Follow this ",[307,17803,17806],{"href":17804,"rel":17805},"https:\u002F\u002Fflows.nodered.org\u002Fflow\u002F66f37bb739b6cdb0c7ad3a4e2edd68ef",[311],"link"," 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, ",[338,17809,17810],{},"mydb",". The last flow is a simple query that pulls data from InfluxDB.",[326,17813,17814,17821,17827],{},[103,17815,17816,17817,17820],{},"Import the flows into your FlowFuse instance of Node-RED and deploy. Follow these ",[307,17818,17675],{"href":17819},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-5\u002F#2.-import-helpful-example-flows-provided-with-custom-nodes"," for importing and exporting.",[103,17822,17823,17824,167],{},"Return to Flowfuse, go to your instance, and create another ",[307,17825,17826],{"href":17755},"snapshot",[103,17828,17829,17830,167],{},"Ensure that you ",[338,17831,17759],{},[996,17833,17835],{"id":17834},"finalizing-and-testing-the-setup","Finalizing and Testing the Setup",[15,17837,17838],{},"The final steps ensure that your setup is fully operational:",[326,17840,17841,17844,17850],{},[103,17842,17843],{},"Once deployed, navigate to the device.",[103,17845,17846,167],{},[307,17847,17849],{"href":17848},"\u002Fdocs\u002Fdevice-agent\u002Fdeploy\u002F#editing-the-node-red-flows-on-a-remote-instance-that-is-assigned-to-an-application","Enable Developer Mode",[103,17851,17852,17853,17856],{},"Next, click the newly revealed button, ",[338,17854,17855],{},"Open Editor",", to access the deployed Flow.",[996,17858,6518],{"id":6517},[15,17860,17861],{},"Implementing FlowFuse with balenaCloud significantly enhances your device management and data processing capabilities. This guide provides a foundational approach, but don't hesitate to delve deeper into each step to tailor the setup to your specific needs.",{"title":50,"searchDepth":250,"depth":250,"links":17863},[17864,17872],{"id":17610,"depth":185,"text":17611,"children":17865},[17866,17867,17868,17869,17870,17871],{"id":17624,"depth":221,"text":17625},{"id":17636,"depth":221,"text":17637},{"id":17666,"depth":221,"text":17667},{"id":17678,"depth":221,"text":17679},{"id":17706,"depth":221,"text":17707},{"id":17737,"depth":221,"text":17738},{"id":17765,"depth":185,"text":17766,"children":17873},[17874,17875,17876],{"id":17797,"depth":221,"text":17798},{"id":17834,"depth":221,"text":17835},{"id":6517,"depth":221,"text":6518},"2024-01-30","Deploy Node-RED with FlowFuse on balenaCloud effortlessly with our step-by-step guide. Simplify fleet management and enhance data processing capabilities.","blog\u002F2024\u002F01\u002Fimages\u002Fbalena-and-flowfuse.png",{"excerpt":17881},{"type":12,"value":17882},[17883],[15,17884,17596,17885,17601,17887,17607],{},[307,17886,17600],{"href":17599},[307,17888,17606],{"href":17604,"rel":17889},[311],"\u002Fblog\u002F2024\u002F01\u002Fhow-to-deploy-node-red-with-flowfuse-to-balenacloud",{"title":17589,"description":17878},{"loc":17890},"blog\u002F2024\u002F01\u002Fhow-to-deploy-node-red-with-flowfuse-to-balenacloud","Fleet management made easier with FlowFuse and balena.",[9832,965,966],"IzkujlB1cSD8fsvl6dly3x1AHcZRFXK2DcdnKeIgbsg",{"id":17898,"title":17899,"authors":17900,"body":17902,"cta":3,"date":18028,"description":18029,"extension":946,"image":18030,"lastUpdated":3,"meta":18031,"navigation":253,"path":18036,"seo":18037,"sitemap":18038,"stem":18039,"subtitle":18040,"tags":18041,"tldr":3,"video":3,"__hash__":18042},"blog\u002Fblog\u002F2024\u002F01\u002Fcapture-data-edge-with-node-red-flowfuse.md","Capture Data from edge devices with Node-RED",[17901],"zeger-jan-van-de-weg",{"type":12,"value":17903,"toc":18020},[17904,17907,17910,17914,17917,17920,17924,17931,17937,17943,17950,17954,17961,17967,17975,17979,17985,17988,18001,18007,18010,18014,18017],[15,17905,17906],{},"While cloud computing has revolutionized data access and analysis, not all data can be accessed from the cloud. In many scenarios, data collection from the edge – the location where data is generated – is essential for real-time decision-making or process observability.",[15,17908,17909],{},"FlowFuse enables data to be collected through Node-RED. Data can be processed locally on the edge or sent on to other services. FlowFuse doesn’t rely on continuous connections to the cloud, making it a good choice for locations with unreliable internet connectivity. Use cases like real-time monitoring of critical systems, proactive maintenance, and improved operational efficiency are now possible to implement.",[34,17911,17913],{"id":17912},"installing-the-flowfuse-agent","Installing the FlowFuse agent",[15,17915,17916],{},"To manage the capturing of data on the edge we’re going to first install the FlowFuse agent. It’s installed on your device to manage the communication between the edge device and the FlowFuse server, manage the installation of Node-RED, its execution environment, and facilitate communication between devices and the cloud.",[15,17918,17919],{},"The device agent can run anywhere you can run a Docker container or Node.JS runtime (version 16.0+) can be installed.",[996,17921,17923],{"id":17922},"registering-a-device-on-flowfuse","Registering a device on FlowFuse",[15,17925,17926,17927,17930],{},"For the edge device to know what it’s supposed to do, it needs to listen to the FlowFuse commands. The agent's configuration is provided by a ",[19,17928,17929],{},"device.yml"," file from FlowFuse. Go to the team you’d like to add an edge device to, and select “Devices” on the left-hand menu, followed by the “Add Device” button.",[15,17932,17933],{},[392,17934],{"alt":17935,"src":17936,"title":17935},"Setting up a FlowFuse agent","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fflowfuse-agent-setup.png",[15,17938,17939,17940,17942],{},"FlowFuse will prompt you to add a name (required), and a type (not required). When you’ve clicked ",[19,17941,12109],{}," you’ll get a new dialog to download the required file.",[15,17944,17945],{},[392,17946],{"alt":17947,"src":17948,"title":17949},"Configuration file for the FlowFuse agent","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fdevice-yml-flowfuse.png","The contents of a device.yml file",[996,17951,17953],{"id":17952},"install-the-flowfuse-agent-through-docker","Install the FlowFuse agent through Docker",[15,17955,17956,17957,17960],{},"If your device supports it, the fastest way to run the FlowFuse agent is with containers. Assuming you’ve already got Docker installed, there are two steps to follow: first, move the device YAML file downloaded from FlowFuse to the edge device and save it in ",[19,17958,17959],{},"\u002Fopt\u002Fflowfuse\u002Fdevice.yml",". Start the agent by running:",[42,17962,17965],{"className":17963,"code":17964,"language":47},[45],"docker run --mount type=bind,src=\u002Fpath\u002Fto\u002Fdevice.yml,target=\u002Fopt\u002Fflowfuse-device\u002Fdevice.yml -p 1880:1880 flowfuse\u002Fdevice-agent:latest\n",[19,17966,17964],{"__ignoreMap":50},[15,17968,17969,17970,167],{},"Note that for production cases, ensure the container is restarted on reboot. Docker can do this for you, ",[307,17971,17974],{"href":17972,"rel":17973},"https:\u002F\u002Fdocs.docker.com\u002Fconfig\u002Fcontainers\u002Fstart-containers-automatically\u002F",[311],"please follow their guide",[996,17976,17978],{"id":17977},"install-the-flowfuse-agent-with-npm","Install the FlowFuse agent with npm",[15,17980,17981,17982,167],{},"To install the agent through NPM, you’ll need a Node.JS version of 18.0 or later. Open a command prompt and run: ",[19,17983,17984],{},"npm install -g @flowfuse\u002Fdevice-agent",[15,17986,17987],{},"This will install the FlowFuse Device Agent as a global npm module, making the flowfuse-device-agent command available in any directory on your system.",[15,17989,17990,17991,17993,17994,17997,17998,167],{},"Once the installation is complete, you must configure the Device Agent to connect to your FlowFuse instance. In this guide, you’ve previously downloaded the ",[19,17992,17929],{}," file that’s needed now. On Linux or Mac, move the file to ",[19,17995,17996],{},"\u002Fopt\u002Fflowfuse-device\u002Fdevice.yml",", and for Windows-based systems, move the file to ",[19,17999,18000],{},"c:\\opt\\flowfuse-device\\device.yml",[15,18002,18003,18004,167],{},"Afterward, start the agent with: ",[19,18005,18006],{},"flowfuse-device-agent",[15,18008,18009],{},"This will launch the Device Agent and connect it to your FlowFuse instance. The Device Agent will wait for instructions on which flows to run.",[996,18011,18013],{"id":18012},"programming-flows-for-the-edge","Programming flows for the edge",[15,18015,18016],{},"Now the agent is running, the FlowFuse platform will show it has contacted back to the platform and is ready to do some work. First, add it to the application and start the developer mode. That enables the device editor and provides you secure access to the editor anywhere in the world for everyone in the FlowFuse team with the right access role.",[15,18018,18019],{},"When the development is done, be sure to create a snapshot of the developed flows to create a point-in-time backup, or to roll the snapshot out to many other devices later.",{"title":50,"searchDepth":250,"depth":250,"links":18021},[18022],{"id":17912,"depth":185,"text":17913,"children":18023},[18024,18025,18026,18027],{"id":17922,"depth":221,"text":17923},{"id":17952,"depth":221,"text":17953},{"id":17977,"depth":221,"text":17978},{"id":18012,"depth":221,"text":18013},"2024-01-17","Learn to capture data from edge devices with Node-RED using FlowFuse. Install the agent via Docker or npm, configure device registration, and program custom flows.","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fcapture-data-from-edge-devices.png",{"excerpt":18032},{"type":12,"value":18033},[18034],[15,18035,17906],{},"\u002Fblog\u002F2024\u002F01\u002Fcapture-data-edge-with-node-red-flowfuse",{"title":17899,"description":18029},{"loc":18036},"blog\u002F2024\u002F01\u002Fcapture-data-edge-with-node-red-flowfuse","FlowFuse allows you to run Node-RED anywhere to capture all the data",[9832,965,6563,966],"-Z2KwRVeMk4C6N8s6yJ8a0HC84jUEVvkcQygW9X4Sq8",{"id":18044,"title":18045,"authors":18046,"body":18047,"cta":3,"date":18127,"description":18128,"extension":946,"image":18129,"lastUpdated":3,"meta":18130,"navigation":253,"path":18135,"seo":18136,"sitemap":18137,"stem":18138,"subtitle":18139,"tags":18140,"tldr":3,"video":3,"__hash__":18141},"blog\u002Fblog\u002F2024\u002F01\u002Fimport-a-file.md","Import a File into Node-RED with Dashboard 2.0",[17591],{"type":12,"value":18048,"toc":18120},[18049,18052,18056,18059,18062,18066,18072,18075,18078,18098,18101,18108,18112],[15,18050,18051],{},"Need to get a file into Node-RED, but don't want to over complicate things.  This article outlines how you can leverage Dashboard 2.0 to import a file directly into Node-RED via a Dashboard.",[34,18053,18055],{"id":18054},"why-would-you-need-to-import-a-file-to-node-red","Why would you need to import a file to Node-RED?",[15,18057,18058],{},"Often times it is necessary to update lookup tables in a SQL database, but you don't necessarily want to give access to everyone to edit the database, nor do you want to have to do it all yourself. This can often be seen when new products are introduced into a manufacturing facility. It may not be often, but enough that it warrants its own application. This process will guide you in a way that will enable your teammates to upload the files to the system themselves.",[15,18060,18061],{},"Furthermore, on the management layer of most companies, Excel and Google Sheets are the go-to tools to perform data collection tasks. Getting management involved in processes might require you to build an import feature for them. Asking your manager to \"Save as\" CSV is much easier than teaching them SQL!",[996,18063,18065],{"id":18064},"node-red-dashboard-flowfuse","Node-RED Dashboard (FlowFuse)",[15,18067,18068],{},[392,18069],{"alt":18070,"src":18071},"csv dashboard","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fcsv-dashboard.png",[15,18073,18074],{},"This simple flow allows the user to visualize data from a CSV in the Node-RED Dashboard. The button then allows the user to initiate a request to send the data to the next step. This next step could be anything from loading into a SQL database to saving it.",[996,18076,18077],{"id":17675},"Instructions",[326,18079,18080,18086,18092],{},[103,18081,18082,18083,18085],{},"Install Node-RED Dashboard 2.0. Follow these ",[307,18084,17675],{"href":6792}," to install.",[103,18087,18088,18089,167],{},"Import Flow - to import the flow into your Node-RED instance follow these ",[307,18090,17675],{"href":18091},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-5\u002F#1.-copy-and-share-your-flows-using-export-and-import",[103,18093,18094,18095,167],{},"Access Dashboard - To access the dashboard, navigate to the ",[19,18096,18097],{},"https:\u002F\u002F\u003Cflowfuse-instance-name>.flowfuse.cloud\u002Fdashboard",[15,18099,18100],{},"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.",[5119,18102],{"width":18103,"height":18104,"src":18105,"allow":18106,"style":18107},"100%","225px","https:\u002F\u002Fflows.nodered.org\u002Fflow\u002F8c505039ac1b8dbed2bee1e22ee2975a\u002Fshare?height=100","clipboard-read; clipboard-write","border: none;",[34,18109,18111],{"id":18110},"need-to-send-a-file-to-node-red-from-another-application-or-source","Need to Send a File to Node-RED from another application or source?",[15,18113,18114,18115,18119],{},"Check out this ",[307,18116,18118],{"href":18117},"\u002Fblog\u002F2024\u002F01\u002Fsend-a-file","blog"," on how to send a file from either a stand alone web application or use the sample python script to imbed it into your current application.",{"title":50,"searchDepth":250,"depth":250,"links":18121},[18122,18126],{"id":18054,"depth":185,"text":18055,"children":18123},[18124,18125],{"id":18064,"depth":221,"text":18065},{"id":17675,"depth":221,"text":18077},{"id":18110,"depth":185,"text":18111},"2024-01-05","Learn how to import a CSV file into Node-RED hassle-free using Dashboard 2.0. Simplify data management with this step-by-step guide.","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fimport-file-to-node-red.png",{"excerpt":18131},{"type":12,"value":18132},[18133],[15,18134,18051],{},"\u002Fblog\u002F2024\u002F01\u002Fimport-a-file",{"title":18045,"description":18128},{"loc":18135},"blog\u002F2024\u002F01\u002Fimport-a-file","Use Dashboard 2.0 to import a CSV file into Node-RED.",[9832,965,966],"uiuC7oj2qhvAllvnefW3e0EwqkCdaXOd9eGAxO07XR0",{"id":18143,"title":18144,"authors":18145,"body":18146,"cta":3,"date":18127,"description":18534,"extension":946,"image":18535,"lastUpdated":3,"meta":18536,"navigation":253,"path":18117,"seo":18541,"sitemap":18542,"stem":18543,"subtitle":18544,"tags":18545,"tldr":3,"video":3,"__hash__":18546},"blog\u002Fblog\u002F2024\u002F01\u002Fsend-a-file.md","Send a File to Node-RED",[17591],{"type":12,"value":18147,"toc":18518},[18148,18151,18155,18157,18159,18163,18166,18182,18185,18191,18200,18203,18218,18221,18317,18324,18330,18335,18338,18347,18350,18356,18359,18363,18366,18391,18395,18404,18410,18414,18418,18421,18436,18439,18443,18446,18462,18465,18474,18481,18485,18488,18491,18498,18503,18506,18510,18515],[15,18149,18150],{},"Have you ever needed to send a CSV file to your Node-RED instance? This file can go on to populate a shift schedule, product specifications, or some other configuration file that is used. In this guide, we provide a couple of options to upload the data to your Node-RED for further processing and to organize the data to be sent on or used.",[34,18152,18154],{"id":18153},"why-would-you-need-to-send-a-file-to-node-red","Why would you need to send a file to Node-RED?",[15,18156,18058],{},[15,18158,18061],{},[34,18160,18162],{"id":18161},"_2-ways-to-send-a-file-to-node-red","2 Ways to send a file to Node-RED",[15,18164,18165],{},"There are many approaches that can be taken when solving this. We are going to go over 2 here.",[326,18167,18168,18175],{},[103,18169,18170,18174],{},[307,18171,18173],{"href":18172},"#simple-python-script","Simple Python Script"," - Simple script that will be shared below. It is a simple Python application that allows the user to send a file with a simple command, but this might require a little more technical skills that the end user may not feel comfortable with.",[103,18176,18177,18181],{},[307,18178,18180],{"href":18179},"#stand-alone-web-application","Stand Alone Web Application"," - A web-based application that allows the user to upload files to a browser with a selectable endpoint.",[996,18183,18173],{"id":18184},"simple-python-script",[15,18186,18187,18188,167],{},"This simple Python script sends a file to a Node-RED flow.  The flow that will work with this script can be seen ",[307,18189,9386],{"href":18190},"#node-red-ingress",[15,18192,18193,18194,63,18197,167],{},"The script requires ",[338,18195,18196],{},"requests",[338,18198,18199],{},"Python 3.x",[15,18201,18202],{},"Install requests:",[42,18204,18206],{"className":7487,"code":18205,"language":7489,"meta":50,"style":50},"pip install requests\n",[19,18207,18208],{"__ignoreMap":50},[146,18209,18210,18213,18215],{"class":148,"line":149},[146,18211,18212],{"class":2435},"pip",[146,18214,7534],{"class":1554},[146,18216,18217],{"class":1554}," requests\n",[15,18219,18220],{},"Create a file called run.py and paste the contents into the file.",[42,18222,18225],{"className":18223,"code":18224,"language":11118,"meta":50,"style":50},"language-python shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","import requests\n\ndef send_file(nodered_url, file_path):\n    # Open the file in binary mode\n    with open(file_path, 'rb') as file:\n        files = {'file': (file.name, file, 'multipart\u002Fform-data')}\n        response = requests.post(nodered_url, files=files)\n\n    return response\n\n# Update the ip address and port of your Node-RED instance\nnodered_url = 'http:\u002F\u002Flocalhost:1880\u002Ffileupload'\n\n# Update the location of your file\nfile_path = 'C:\u002FUsers\u002FmyUser\u002FDownloads\u002FshiftSchedule.csv'\n\nresponse = send_file(nodered_url, file_path)\nprint(f\"Response Status Code: {response.status_code}\")\nprint(f\"Response Body: {response.text}\")\n",[19,18226,18227,18232,18236,18241,18246,18251,18256,18261,18265,18270,18274,18279,18284,18288,18293,18298,18302,18307,18312],{"__ignoreMap":50},[146,18228,18229],{"class":148,"line":149},[146,18230,18231],{},"import requests\n",[146,18233,18234],{"class":148,"line":185},[146,18235,254],{"emptyLinePlaceholder":253},[146,18237,18238],{"class":148,"line":221},[146,18239,18240],{},"def send_file(nodered_url, file_path):\n",[146,18242,18243],{"class":148,"line":250},[146,18244,18245],{},"    # Open the file in binary mode\n",[146,18247,18248],{"class":148,"line":257},[146,18249,18250],{},"    with open(file_path, 'rb') as file:\n",[146,18252,18253],{"class":148,"line":284},[146,18254,18255],{},"        files = {'file': (file.name, file, 'multipart\u002Fform-data')}\n",[146,18257,18258],{"class":148,"line":666},[146,18259,18260],{},"        response = requests.post(nodered_url, files=files)\n",[146,18262,18263],{"class":148,"line":1603},[146,18264,254],{"emptyLinePlaceholder":253},[146,18266,18267],{"class":148,"line":1608},[146,18268,18269],{},"    return response\n",[146,18271,18272],{"class":148,"line":1624},[146,18273,254],{"emptyLinePlaceholder":253},[146,18275,18276],{"class":148,"line":2546},[146,18277,18278],{},"# Update the ip address and port of your Node-RED instance\n",[146,18280,18281],{"class":148,"line":2564},[146,18282,18283],{},"nodered_url = 'http:\u002F\u002Flocalhost:1880\u002Ffileupload'\n",[146,18285,18286],{"class":148,"line":2569},[146,18287,254],{"emptyLinePlaceholder":253},[146,18289,18290],{"class":148,"line":2574},[146,18291,18292],{},"# Update the location of your file\n",[146,18294,18295],{"class":148,"line":2594},[146,18296,18297],{},"file_path = 'C:\u002FUsers\u002FmyUser\u002FDownloads\u002FshiftSchedule.csv'\n",[146,18299,18300],{"class":148,"line":2614},[146,18301,254],{"emptyLinePlaceholder":253},[146,18303,18304],{"class":148,"line":2633},[146,18305,18306],{},"response = send_file(nodered_url, file_path)\n",[146,18308,18309],{"class":148,"line":2662},[146,18310,18311],{},"print(f\"Response Status Code: {response.status_code}\")\n",[146,18313,18314],{"class":148,"line":2667},[146,18315,18316],{},"print(f\"Response Body: {response.text}\")\n",[15,18318,18319,18320,18323],{},"Update the ",[338,18321,18322],{},"nodered_url"," to the location of the Node-RED instance.  Be sure to adjust the port if the default port of 1880 isn't being used.",[15,18325,18319,18326,18329],{},[338,18327,18328],{},"file_path"," with the path to where the file to be uploaded will be located.",[15,18331,18332],{},[338,18333,18334],{},"Save",[15,18336,18337],{},"To run:",[42,18339,18341],{"className":18223,"code":18340,"language":11118,"meta":50,"style":50},"python run.py\n",[19,18342,18343],{"__ignoreMap":50},[146,18344,18345],{"class":148,"line":149},[146,18346,18340],{},[996,18348,18180],{"id":18349},"stand-alone-web-application",[15,18351,18352],{},[392,18353],{"alt":18354,"src":18355},"csv upload application","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fcsv_upload_app.png",[15,18357,18358],{},"This stand-alone web application can be run on either Windows or Linux, .bat for Windows, and .sh for Linux.",[4987,18360,18362],{"id":18361},"installation","Installation",[15,18364,18365],{},"Clone the repository and navigate to the directory:",[42,18367,18369],{"className":7487,"code":18368,"language":7489,"meta":50,"style":50},"git clone https:\u002F\u002Fgithub.com\u002Fgdziuba\u002FFF_Send-File-to-NR.git && cd FF_Send-File-to-NR\n",[19,18370,18371],{"__ignoreMap":50},[146,18372,18373,18376,18379,18382,18385,18388],{"class":148,"line":149},[146,18374,18375],{"class":2435},"git",[146,18377,18378],{"class":1554}," clone",[146,18380,18381],{"class":1554}," https:\u002F\u002Fgithub.com\u002Fgdziuba\u002FFF_Send-File-to-NR.git",[146,18383,18384],{"class":160}," &&",[146,18386,18387],{"class":170}," cd",[146,18389,18390],{"class":1554}," FF_Send-File-to-NR\n",[4987,18392,18394],{"id":18393},"configuration","Configuration",[15,18396,18397,18398,18403],{},"Edit the lines in the body of ",[307,18399,18402],{"href":18400,"rel":18401},"https:\u002F\u002Fgithub.com\u002Fgdziuba\u002FFF_Send-File-to-NR\u002Fblob\u002F21214f88c6c4536f49efb88cf5f84bf52071a88b\u002Ftemplates\u002Findex.html#L69",[311],"index.html"," to include the endpoints to which you would like to send the files.",[42,18405,18408],{"className":18406,"code":18407,"language":47},[45],"\u003Coption value=\"http:\u002F\u002Flocalhost:1880\u002Ffileupload\">CSV File Upload\u003C\u002Foption>\n",[19,18409,18407],{"__ignoreMap":50},[996,18411,18413],{"id":18412},"operating-systems","Operating Systems",[4987,18415,18417],{"id":18416},"windows","Windows",[15,18419,18420],{},"Run the script:",[42,18422,18424],{"className":7487,"code":18423,"language":7489,"meta":50,"style":50},".\\start_app.bat\n",[19,18425,18426],{"__ignoreMap":50},[146,18427,18428,18430,18433],{"class":148,"line":149},[146,18429,167],{"class":170},[146,18431,18432],{"class":156},"\\",[146,18434,18435],{"class":1554},"start_app.bat\n",[15,18437,18438],{},"This will install if necessary, start the Flask Application, and take you to localhost:5000 on the browser.",[4987,18440,18442],{"id":18441},"linux","Linux",[15,18444,18445],{},"Make the script executable by running running:",[42,18447,18449],{"className":7487,"code":18448,"language":7489,"meta":50,"style":50},"chmod +x setup_and_run.sh\n",[19,18450,18451],{"__ignoreMap":50},[146,18452,18453,18456,18459],{"class":148,"line":149},[146,18454,18455],{"class":2435},"chmod",[146,18457,18458],{"class":1554}," +x",[146,18460,18461],{"class":1554}," setup_and_run.sh\n",[15,18463,18464],{},"Then run the application with:",[42,18466,18468],{"className":7487,"code":18467,"language":7489,"meta":50,"style":50},".\u002Fsetup_and_run.sh\n",[19,18469,18470],{"__ignoreMap":50},[146,18471,18472],{"class":148,"line":149},[146,18473,18467],{"class":2435},[15,18475,18476,18477,18480],{},"To access the application, open a browser to the ",[338,18478,18479],{},"\u003Cnode-red-host-ip>:5000"," of the running application.",[4987,18482,18484],{"id":18483},"node-red-ingress","Node-RED Ingress",[5119,18486],{"width":18103,"height":18104,"src":18487,"allow":18106,"style":18107},"https:\u002F\u002Fflows.nodered.org\u002Fflow\u002Feffb53752e5d6f767b3c7e5d41a4a6e8\u002Fshare?height=100",[15,18489,18490],{},"Once we have a file ready to be sent, we now need to configure the receiving side in Node-RED. In this example, we are leveraging a CSV formatted file and then converting it to be used at a later time.",[15,18492,18493,18494,167],{},"A link to the flow can be found ",[307,18495,9386],{"href":18496,"rel":18497},"https:\u002F\u002Fflows.nodered.org\u002Fflow\u002Feffb53752e5d6f767b3c7e5d41a4a6e8",[311],[15,18499,18500,18501,167],{},"To import the flow, follow these ",[307,18502,17675],{"href":18091},[15,18504,18505],{},"A Simple HTTP In node can be used in the form of a Post, ensuring the configuration allows for a file.",[34,18507,18509],{"id":18508},"wanna-import-it-directly-into-your-node-red-instance-via-a-dashboard","Wanna import it directly into your Node-RED instance via a Dashboard?",[15,18511,18114,18512,18514],{},[307,18513,18118],{"href":18135}," on how to directly import a file into a Node-RED instance via Dashboard 2.0.",[924,18516,18517],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}",{"title":50,"searchDepth":250,"depth":250,"links":18519},[18520,18521,18533],{"id":18153,"depth":185,"text":18154},{"id":18161,"depth":185,"text":18162,"children":18522},[18523,18524,18528],{"id":18184,"depth":221,"text":18173},{"id":18349,"depth":221,"text":18180,"children":18525},[18526,18527],{"id":18361,"depth":250,"text":18362},{"id":18393,"depth":250,"text":18394},{"id":18412,"depth":221,"text":18413,"children":18529},[18530,18531,18532],{"id":18416,"depth":250,"text":18417},{"id":18441,"depth":250,"text":18442},{"id":18483,"depth":250,"text":18484},{"id":18508,"depth":185,"text":18509},"Learn how to send CSV files to Node-RED with a Python script or web app. Streamline data processing with step-by-step guidance.","\u002Fblog\u002F2024\u002F01\u002Fimages\u002Fsend-file-to-node-red.png",{"excerpt":18537},{"type":12,"value":18538},[18539],[15,18540,18150],{},{"title":18144,"description":18534},{"loc":18117},"blog\u002F2024\u002F01\u002Fsend-a-file","A guide to sending a CSV file to Node-RED and start interacting with it.",[9832,965,966],"idPRZQFfWPHsTDYFUvbMfIyYlzGhqdDL3BO5_BMWS9g",{"id":18548,"title":18549,"authors":18550,"body":18551,"cta":3,"date":19589,"description":19590,"extension":946,"image":19591,"lastUpdated":3,"meta":19592,"navigation":253,"path":19602,"seo":19603,"sitemap":19604,"stem":19605,"subtitle":19606,"tags":19607,"tldr":3,"video":3,"__hash__":19608},"blog\u002Fblog\u002F2023\u002F12\u002Fdevice-agent-as-a-windows-service.md","Run Node-RED as a service on Windows",[5320,7451,17591],{"type":12,"value":18552,"toc":19557},[18553,18556,18565,18569,18575,18579,18582,18585,18593,18596,18611,18623,18630,18637,18645,18649,18662,18666,18684,18688,18704,18708,18712,18719,18722,18726,18736,18740,18748,18825,18834,18838,18847,18857,18962,18971,18978,19034,19038,19049,19059,19063,19069,19090,19097,19101,19104,19107,19125,19138,19166,19178,19182,19189,19207,19211,19214,19261,19272,19283,19287,19294,19298,19408,19412,19415,19424,19427,19434,19449,19453,19456,19470,19476,19483,19489,19493,19496,19509,19512,19524,19527,19541,19545,19554],[15,18554,18555],{},"FlowFuse's device agent allows you to manage and run your Node-RED instances on\nyour own hardware such as a Raspberry Pi or Windows computer. This can be very useful where an\napplication you've written needs to run flows with direct access to hardware sensors.",[15,18557,18558,18559,18564],{},"In this article, we're going to explain the steps to configure our device agent to run as a service in Windows\nusing the ",[307,18560,18563],{"href":18561,"rel":18562},"https:\u002F\u002Fnssm.cc\u002F",[311],"nssm"," utility.",[34,18566,18568],{"id":18567},"why-run-the-device-agent-as-a-service","Why run the device agent as a service?",[15,18570,18571,18572,18574],{},"The standard process for running FlowFuse's device agent is to start it on the\ncommand line using the command ",[19,18573,18006],{},". This works fine for testing\nbut for long-term installations it's useful to run the device agent as a service.\nOnce running as a service, the device agent will continue to run even if you\nlog off or the computer is restarted and no user is logged in.",[34,18576,18578],{"id":18577},"summary","Summary",[15,18580,18581],{},"The aim of this how-to is to install the FlowFuse device-agent as a service on a Windows computer.",[15,18583,18584],{},"There will be two main parts to this:",[326,18586,18587,18590],{},[103,18588,18589],{},"Install the device-agent",[103,18591,18592],{},"Setup the device-agent to run as a Windows service",[15,18594,18595],{},"Additionally, two user accounts will be needed for this configuration:",[326,18597,18598,18604],{},[103,18599,18600,18601,18603],{},"A ",[338,18602,15390],{}," account that will be used to run the device-agent (typically, non-admin account)",[103,18605,18606,18607,18610],{},"An ",[338,18608,18609],{},"admin"," account that can run elevated commands and will be used to setup the service",[15,18612,18613,18614,18616,18617],{},"We will create a directory for the device-agent files and set the permissions on that directory so that the ",[338,18615,15390],{}," account can read and write files in that directory.\n",[397,18618,18619,18620],{},"This will be ",[19,18621,18622],{},"c:\\opt\\flowfuse-device",[15,18624,18625,18626,18629],{},"To make the device-agent run as a service, we will (in this example), use ",[307,18627,18563],{"href":18561,"rel":18628},[311]," but you are free to choose an alternative tool to run the device agent as a service.",[15,18631,18632,18633,18636],{},"Finally, we set the service to run under the ",[338,18634,18635],{},"service"," account.",[15,18638,18639],{},[397,18640,18641,18642],{},"NOTE: The instructions in this how to were written on ",[338,18643,18644],{},"Windows 11 Pro 22H2",[996,18646,18648],{"id":18647},"tip-using-domain-accounts","TIP: Using domain accounts",[15,18650,18651,18652,18654,18655,18658,18659,18661],{},"If the account is a domain account, append the domain name to the ",[338,18653,15390],{}," e.g. ",[19,18656,18657],{},"user@domain"," whenever the ",[338,18660,15390],{}," name is used in the instructions below.",[996,18663,18665],{"id":18664},"tip-launching-an-elevated-command-prompt-window-eg-as-the-admin-user","TIP: Launching an elevated command prompt window (e.g. as the admin user)",[42,18667,18669],{"className":7487,"code":18668,"language":7489,"meta":50,"style":50},"powershell -Command \"Start-Process 'cmd' -Verb runAs\n",[19,18670,18671],{"__ignoreMap":50},[146,18672,18673,18676,18679,18681],{"class":148,"line":149},[146,18674,18675],{"class":2435},"powershell",[146,18677,18678],{"class":1554}," -Command",[146,18680,1830],{"class":160},[146,18682,18683],{"class":1554},"Start-Process 'cmd' -Verb runAs\n",[996,18685,18687],{"id":18686},"tip-launching-an-elevated-powershell-prompt-window-eg-as-the-admin-user","TIP: Launching an elevated powershell prompt window (e.g. as the admin user)",[42,18689,18691],{"className":7487,"code":18690,"language":7489,"meta":50,"style":50},"powershell -Command \"Start-Process 'powershell' -Verb runAs\n",[19,18692,18693],{"__ignoreMap":50},[146,18694,18695,18697,18699,18701],{"class":148,"line":149},[146,18696,18675],{"class":2435},[146,18698,18678],{"class":1554},[146,18700,1830],{"class":160},[146,18702,18703],{"class":1554},"Start-Process 'powershell' -Verb runAs\n",[34,18705,18707],{"id":18706},"pre-requisites","Pre-requisites",[996,18709,18711],{"id":18710},"install-nodejs","Install Node.js",[15,18713,18714,18715,167],{},"The device-agent requires Node.js to be installed. You can download the latest version from ",[307,18716,18717],{"href":18717,"rel":18718},"https:\u002F\u002Fnodejs.org\u002Fen\u002Fdownload\u002F",[311],[15,18720,18721],{},"It is recommended to install the LTS version and to check the \"Automatically install the necessary tools\" option. This is especially important if you intend on using any nodes that require native modules (like serialport).",[996,18723,18725],{"id":18724},"create-a-new-windows-user","Create a New Windows User",[15,18727,18728,18729,18731,18732,167],{},"If you need to create a new ",[338,18730,15390],{}," account follow these ",[307,18733,17675],{"href":18734,"rel":18735},"https:\u002F\u002Fsupport.microsoft.com\u002Fen-us\u002Fwindows\u002Fcreate-a-local-user-or-administrator-account-in-windows-20de74e0-ac7f-3502-a866-32915af2a34d#:~:text=Select%20Start%20%3E%20Settings%20%3E%20Accounts%20and,other%20user%2C%20select%20Add%20account.",[311],[34,18737,18739],{"id":18738},"prepare-the-device-agent-files-directory","Prepare the device-agent files directory",[15,18741,18742,18743,18747],{},"As the admin user, open an ",[307,18744,18746],{"href":18745},"#tip%3A-launching-an-elevated-command-prompt-window-(e.g.-as-the-admin-user)","elevated"," command prompt, create the files directory and setup access permissions.",[42,18749,18751],{"className":7487,"code":18750,"language":7489,"meta":50,"style":50},"# In an elevated command prompt\nmkdir c:\\opt\nmkdir c:\\opt\\flowfuse-device\n# grant full access to the service account that will run the device-agent\nicacls c:\\opt\\flowfuse-device \u002Fgrant \"user\":F \u002FT\n",[19,18752,18753,18758,18772,18789,18794],{"__ignoreMap":50},[146,18754,18755],{"class":148,"line":149},[146,18756,18757],{"class":3874},"# In an elevated command prompt\n",[146,18759,18760,18763,18766,18769],{"class":148,"line":185},[146,18761,18762],{"class":2435},"mkdir",[146,18764,18765],{"class":1554}," c:",[146,18767,18768],{"class":156},"\\o",[146,18770,18771],{"class":1554},"pt\n",[146,18773,18774,18776,18778,18780,18783,18786],{"class":148,"line":221},[146,18775,18762],{"class":2435},[146,18777,18765],{"class":1554},[146,18779,18768],{"class":156},[146,18781,18782],{"class":1554},"pt",[146,18784,18785],{"class":156},"\\f",[146,18787,18788],{"class":1554},"lowfuse-device\n",[146,18790,18791],{"class":148,"line":250},[146,18792,18793],{"class":3874},"# grant full access to the service account that will run the device-agent\n",[146,18795,18796,18799,18801,18803,18805,18807,18810,18813,18815,18817,18819,18822],{"class":148,"line":257},[146,18797,18798],{"class":2435},"icacls",[146,18800,18765],{"class":1554},[146,18802,18768],{"class":156},[146,18804,18782],{"class":1554},[146,18806,18785],{"class":156},[146,18808,18809],{"class":1554},"lowfuse-device",[146,18811,18812],{"class":1554}," \u002Fgrant",[146,18814,1830],{"class":160},[146,18816,15390],{"class":1554},[146,18818,727],{"class":160},[146,18820,18821],{"class":1554},":F",[146,18823,18824],{"class":1554}," \u002FT\n",[15,18826,18827],{},[397,18828,18829,18830,18833],{},"where ",[19,18831,18832],{},"\"user\""," is the service account (not the admin account)",[34,18835,18837],{"id":18836},"install-nssm","Install nssm",[15,18839,18840,18842,18843,18846],{},[19,18841,18563],{}," can simply be downloaded and executed from any path.\nWe will download it to the ",[19,18844,18845],{},"c:\\opt"," directory, extract the files and copy the 64 bit version to the current directory.",[996,18848,18850,18853,18854,18856],{"id":18849},"cmd-version-elevated-command-prompt",[19,18851,18852],{},"cmd"," version ",[307,18855,18746],{"href":18745}," command prompt",[42,18858,18860],{"className":7487,"code":18859,"language":7489,"meta":50,"style":50},"# starting in the device-agent files directory\ncd c:\\opt\n# download the nssm zip file\ncurl -LJO https:\u002F\u002Fnssm.cc\u002Frelease\u002Fnssm-2.24.zip\n# extract the files\ntar -xf nssm-2.24.zip\n# copy the 64 bit version to the current directory\ncopy nssm-2.24\\win64\\nssm.exe .\n# clean up\ndel nssm-2.24.zip\nrmdir \u002Fs \u002Fq nssm-2.24\n",[19,18861,18862,18867,18878,18883,18893,18898,18909,18914,18936,18941,18948],{"__ignoreMap":50},[146,18863,18864],{"class":148,"line":149},[146,18865,18866],{"class":3874},"# starting in the device-agent files directory\n",[146,18868,18869,18872,18874,18876],{"class":148,"line":185},[146,18870,18871],{"class":170},"cd",[146,18873,18765],{"class":1554},[146,18875,18768],{"class":156},[146,18877,18771],{"class":1554},[146,18879,18880],{"class":148,"line":221},[146,18881,18882],{"class":3874},"# download the nssm zip file\n",[146,18884,18885,18887,18890],{"class":148,"line":250},[146,18886,7505],{"class":2435},[146,18888,18889],{"class":1554}," -LJO",[146,18891,18892],{"class":1554}," https:\u002F\u002Fnssm.cc\u002Frelease\u002Fnssm-2.24.zip\n",[146,18894,18895],{"class":148,"line":257},[146,18896,18897],{"class":3874},"# extract the files\n",[146,18899,18900,18903,18906],{"class":148,"line":284},[146,18901,18902],{"class":2435},"tar",[146,18904,18905],{"class":1554}," -xf",[146,18907,18908],{"class":1554}," nssm-2.24.zip\n",[146,18910,18911],{"class":148,"line":666},[146,18912,18913],{"class":3874},"# copy the 64 bit version to the current directory\n",[146,18915,18916,18919,18922,18925,18928,18930,18933],{"class":148,"line":1603},[146,18917,18918],{"class":2435},"copy",[146,18920,18921],{"class":1554}," nssm-2.24",[146,18923,18924],{"class":156},"\\w",[146,18926,18927],{"class":1554},"in64",[146,18929,1276],{"class":156},[146,18931,18932],{"class":1554},"ssm.exe",[146,18934,18935],{"class":1554}," .\n",[146,18937,18938],{"class":148,"line":1608},[146,18939,18940],{"class":3874},"# clean up\n",[146,18942,18943,18946],{"class":148,"line":1624},[146,18944,18945],{"class":2435},"del",[146,18947,18908],{"class":1554},[146,18949,18950,18953,18956,18959],{"class":148,"line":2546},[146,18951,18952],{"class":2435},"rmdir",[146,18954,18955],{"class":1554}," \u002Fs",[146,18957,18958],{"class":1554}," \u002Fq",[146,18960,18961],{"class":1554}," nssm-2.24\n",[996,18963,18965,18853,18967,18970],{"id":18964},"powershell-version-elevated-powershell-prompt",[19,18966,18675],{},[307,18968,18746],{"href":18969},"#tip%3A-launching-an-elevated-powershell-prompt-window-(e.g.-as-the-admin-user)"," powershell prompt",[15,18972,18973,18974,18977],{},"If you don't have ",[19,18975,18976],{},"cURL"," installed, then powershell can be used to download the file. Here is how to do it:",[42,18979,18982],{"className":18980,"code":18981,"language":18675,"meta":50,"style":50},"language-powershell shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","# starting in the device-agent files directory\ncd c:\\opt\n# download the nssm zip file\nInvoke-WebRequest -Uri https:\u002F\u002Fnssm.cc\u002Frelease\u002Fnssm-2.24.zip -OutFile nssm-2.24.zip\n# extract the files\nExpand-Archive -Path nssm-2.24.zip .\n# copy the 64 bit version to the current directory\nCopy-Item -Path .\\nssm-2.24\\win64\\nssm.exe -Destination .\n# clean up\nRemove-Item -Path nssm-2.24.zip\nRemove-Item -Path nssm-2.24 -Recurse\n",[19,18983,18984,18988,18993,18997,19002,19006,19011,19015,19020,19024,19029],{"__ignoreMap":50},[146,18985,18986],{"class":148,"line":149},[146,18987,18866],{},[146,18989,18990],{"class":148,"line":185},[146,18991,18992],{},"cd c:\\opt\n",[146,18994,18995],{"class":148,"line":221},[146,18996,18882],{},[146,18998,18999],{"class":148,"line":250},[146,19000,19001],{},"Invoke-WebRequest -Uri https:\u002F\u002Fnssm.cc\u002Frelease\u002Fnssm-2.24.zip -OutFile nssm-2.24.zip\n",[146,19003,19004],{"class":148,"line":257},[146,19005,18897],{},[146,19007,19008],{"class":148,"line":284},[146,19009,19010],{},"Expand-Archive -Path nssm-2.24.zip .\n",[146,19012,19013],{"class":148,"line":666},[146,19014,18913],{},[146,19016,19017],{"class":148,"line":1603},[146,19018,19019],{},"Copy-Item -Path .\\nssm-2.24\\win64\\nssm.exe -Destination .\n",[146,19021,19022],{"class":148,"line":1608},[146,19023,18940],{},[146,19025,19026],{"class":148,"line":1624},[146,19027,19028],{},"Remove-Item -Path nssm-2.24.zip\n",[146,19030,19031],{"class":148,"line":2546},[146,19032,19033],{},"Remove-Item -Path nssm-2.24 -Recurse\n",[996,19035,19037],{"id":19036},"manual-download","Manual download",[15,19039,19040,19041,19045,19046,19048],{},"If you prefer, you can download the nssm zip file manually from ",[307,19042,19043],{"href":19043,"rel":19044},"https:\u002F\u002Fnssm.cc\u002Frelease\u002Fnssm-2.24.zip",[311]," and extract the files to the ",[19,19047,18845],{}," directory. Then copy the 64 bit version to the current directory.",[15,19050,19051,19052,19055,19056,4975],{},"Ultimately, you should end up with a file named ",[19,19053,19054],{},"nssm.exe"," in the ",[19,19057,19058],{},"c:\\opt\\",[34,19060,19062],{"id":19061},"install-and-configure-the-device-agent","Install and configure the device-agent",[15,19064,19065,19066,19068],{},"As the ",[338,19067,18635],{}," account, to do so open a command prompt window and run the following and authenticate:",[42,19070,19072],{"className":7487,"code":19071,"language":7489,"meta":50,"style":50},"runas \u002Fuser:{serviceuser} cmd\n# e.g. runas \u002Fuser:winserv cmd\n",[19,19073,19074,19085],{"__ignoreMap":50},[146,19075,19076,19079,19082],{"class":148,"line":149},[146,19077,19078],{"class":2435},"runas",[146,19080,19081],{"class":1554}," \u002Fuser:{serviceuser}",[146,19083,19084],{"class":1554}," cmd\n",[146,19086,19087],{"class":148,"line":185},[146,19088,19089],{"class":3874},"# e.g. runas \u002Fuser:winserv cmd\n",[15,19091,19092],{},[397,19093,18829,19094,18833],{},[19,19095,19096],{},"{serviceuser}",[996,19098,19100],{"id":19099},"check-the-users-npm-global-path-is-set-in-the-users-environment-variables","Check the users npm global path is set in the Users Environment Variables",[15,19102,19103],{},"NOTE: The recommended flowfuse-device-agent instructions will result in the flowfuse-device-agent being installed in the NPM global directory.  And the instructions to launch the device-agent expect the NPM global directory to be in your user path.  This section will instruct you to a) find the NPM global path, then b) check the user’s path setting and, if necessary c) add the NPM global path to your user path.",[15,19105,19106],{},"First, make a note of the path currently set for npm global. You can do this by running the following command:",[42,19108,19110],{"className":7487,"code":19109,"language":7489,"meta":50,"style":50},"npm config get prefix\n",[19,19111,19112],{"__ignoreMap":50},[146,19113,19114,19117,19119,19122],{"class":148,"line":149},[146,19115,19116],{"class":2435},"npm",[146,19118,14364],{"class":1554},[146,19120,19121],{"class":1554}," get",[146,19123,19124],{"class":1554}," prefix\n",[15,19126,19127,19128,19131,19132,19134,19135,19137],{},"Next, ensure the ",[19,19129,19130],{},"Path"," Variable under the \"User variables for ",[397,19133,15390],{},"\" contains the npm global path that we obtained in the previous step.\nUse the below command, to check the user’s ",[19,19136,19130],{}," setting.  If it is not present, edit the path to include it.",[42,19139,19141],{"className":7487,"code":19140,"language":7489,"meta":50,"style":50},"# This commands opens the environment variables editor, \n# look for the \"Path\" variable under \"User variables for user\",\n# and ensure it contains the npm global path\nrundll32 sysdm.cpl,EditEnvironmentVariables\n",[19,19142,19143,19148,19153,19158],{"__ignoreMap":50},[146,19144,19145],{"class":148,"line":149},[146,19146,19147],{"class":3874},"# This commands opens the environment variables editor, \n",[146,19149,19150],{"class":148,"line":185},[146,19151,19152],{"class":3874},"# look for the \"Path\" variable under \"User variables for user\",\n",[146,19154,19155],{"class":148,"line":221},[146,19156,19157],{"class":3874},"# and ensure it contains the npm global path\n",[146,19159,19160,19163],{"class":148,"line":250},[146,19161,19162],{"class":2435},"rundll32",[146,19164,19165],{"class":1554}," sysdm.cpl,EditEnvironmentVariables\n",[15,19167,19168,19169,19171,19172,19175,19176,167],{},"If you did have to add the npm path to the users ",[19,19170,19130],{}," variable, you will need to ",[338,19173,19174],{},"restart"," the command prompt for the change to take effect and relogin as ",[338,19177,15390],{},[996,19179,19181],{"id":19180},"install-the-device-agent","Install the device agent",[15,19183,19184,19185,19188],{},"Note: you may have already installed the device-agent, however, ",[338,19186,19187],{},"we strongly recommend"," you do this step again as the service account and ensure that account has the latest version.",[42,19190,19192],{"className":7487,"code":19191,"language":7489,"meta":50,"style":50},"npm i -g @flowfuse\u002Fdevice-agent\n",[19,19193,19194],{"__ignoreMap":50},[146,19195,19196,19198,19201,19204],{"class":148,"line":149},[146,19197,19116],{"class":2435},[146,19199,19200],{"class":1554}," i",[146,19202,19203],{"class":1554}," -g",[146,19205,19206],{"class":1554}," @flowfuse\u002Fdevice-agent\n",[996,19208,19210],{"id":19209},"link-the-device-agent-to-your-flowfuse-team","Link the device-agent to your flowfuse team",[15,19212,19213],{},"First, we must run the device-agent and link it to our FlowFuse team.  This will generate a \"device configuration\" details that we will use to configure the device-agent.\nBelow is how to run the device-agent with the UI enabled.  This will allow you to configure the device-agent via its web UI.",[42,19215,19217],{"className":7487,"code":19216,"language":7489,"meta":50,"style":50},"flowfuse-device-agent --ui --ui-port 8080 --ui-user admin --ui-pass admin -d c:\\opt\\flowfuse-device -p 1880\n",[19,19218,19219],{"__ignoreMap":50},[146,19220,19221,19223,19226,19229,19232,19235,19238,19241,19243,19245,19247,19249,19251,19253,19255,19258],{"class":148,"line":149},[146,19222,18006],{"class":2435},[146,19224,19225],{"class":1554}," --ui",[146,19227,19228],{"class":1554}," --ui-port",[146,19230,19231],{"class":206}," 8080",[146,19233,19234],{"class":1554}," --ui-user",[146,19236,19237],{"class":1554}," admin",[146,19239,19240],{"class":1554}," --ui-pass",[146,19242,19237],{"class":1554},[146,19244,7676],{"class":1554},[146,19246,18765],{"class":1554},[146,19248,18768],{"class":156},[146,19250,18782],{"class":1554},[146,19252,18785],{"class":156},[146,19254,18809],{"class":1554},[146,19256,19257],{"class":1554}," -p",[146,19259,19260],{"class":206}," 1880\n",[15,19262,19263,19264,19268,19269],{},"The device-agent will now be running and you can access the UI at ",[307,19265,19266],{"href":19266,"rel":19267},"http:\u002F\u002F127.0.0.1:8080",[311]," with the user and password both \"admin\" (you can change these in the command line if required).\n",[397,19270,19271],{},"NOTE: These credentials are temporary and only valid during the device setup",[15,19273,19274,19275,19278,19279,19282],{},"Proceed to configure the device-agent and link it to your flowfuse team. Full instructions can be found ",[307,19276,9386],{"href":19277},"\u002Fdocs\u002Fdevice-agent\u002Fregister",".\nOnce you have linked the device-agent to your team, you can stop it by pressing ",[19,19280,19281],{},"ctrl+c"," in the command prompt window.",[34,19284,19286],{"id":19285},"create-the-device-agent-service","Create the device-agent service",[15,19288,19289,19290,19293],{},"As the admin user, open an elevated command prompt see ",[307,19291,19292],{"href":18745},"TIP"," above",[996,19295,19297],{"id":19296},"install-device-agent-as-a-service","Install device-agent as a service",[42,19299,19301],{"className":7487,"code":19300,"language":7489,"meta":50,"style":50},"cd c:\\opt\n.\\nssm.exe install flowfuse-device-agent \"flowfuse-device-agent.cmd\"\n.\\nssm.exe set flowfuse-device-agent AppDirectory \"c:\\opt\\flowfuse-device\"\n.\\nssm.exe set flowfuse-device-agent Description \"FlowFuse Device Agent\"\n# set the AppParameters (cli options) to tell the agent where its home directory is\n# in our case, this is c:\\opt\\flowfuse-device and is set with the -d option\n.\\nssm.exe set flowfuse-device-agent AppParameters \"-d c:\\opt\\flowfuse-device -p 1880\"\n",[19,19302,19303,19313,19333,19355,19376,19381,19386],{"__ignoreMap":50},[146,19304,19305,19307,19309,19311],{"class":148,"line":149},[146,19306,18871],{"class":170},[146,19308,18765],{"class":1554},[146,19310,18768],{"class":156},[146,19312,18771],{"class":1554},[146,19314,19315,19317,19319,19321,19323,19326,19328,19331],{"class":148,"line":185},[146,19316,167],{"class":170},[146,19318,18432],{"class":156},[146,19320,19054],{"class":1554},[146,19322,7534],{"class":1554},[146,19324,19325],{"class":1554}," flowfuse-device-agent",[146,19327,1830],{"class":160},[146,19329,19330],{"class":1554},"flowfuse-device-agent.cmd",[146,19332,2561],{"class":160},[146,19334,19335,19337,19339,19341,19344,19346,19349,19351,19353],{"class":148,"line":221},[146,19336,167],{"class":170},[146,19338,18432],{"class":156},[146,19340,19054],{"class":1554},[146,19342,19343],{"class":1554}," set",[146,19345,19325],{"class":1554},[146,19347,19348],{"class":1554}," AppDirectory",[146,19350,1830],{"class":160},[146,19352,18622],{"class":1554},[146,19354,2561],{"class":160},[146,19356,19357,19359,19361,19363,19365,19367,19370,19372,19374],{"class":148,"line":250},[146,19358,167],{"class":170},[146,19360,18432],{"class":156},[146,19362,19054],{"class":1554},[146,19364,19343],{"class":1554},[146,19366,19325],{"class":1554},[146,19368,19369],{"class":1554}," Description",[146,19371,1830],{"class":160},[146,19373,5392],{"class":1554},[146,19375,2561],{"class":160},[146,19377,19378],{"class":148,"line":257},[146,19379,19380],{"class":3874},"# set the AppParameters (cli options) to tell the agent where its home directory is\n",[146,19382,19383],{"class":148,"line":284},[146,19384,19385],{"class":3874},"# in our case, this is c:\\opt\\flowfuse-device and is set with the -d option\n",[146,19387,19388,19390,19392,19394,19396,19398,19401,19403,19406],{"class":148,"line":666},[146,19389,167],{"class":170},[146,19391,18432],{"class":156},[146,19393,19054],{"class":1554},[146,19395,19343],{"class":1554},[146,19397,19325],{"class":1554},[146,19399,19400],{"class":1554}," AppParameters",[146,19402,1830],{"class":160},[146,19404,19405],{"class":1554},"-d c:\\opt\\flowfuse-device -p 1880",[146,19407,2561],{"class":160},[996,19409,19411],{"id":19410},"check-the-service-is-installed","Check the service is installed",[15,19413,19414],{},"Run the following command to check the service is installed:",[42,19416,19418],{"className":7487,"code":19417,"language":7489,"meta":50,"style":50},"services.msc\n",[19,19419,19420],{"__ignoreMap":50},[146,19421,19422],{"class":148,"line":149},[146,19423,19417],{"class":2435},[15,19425,19426],{},"(look for a service named `flowfuse-device-agent').",[15,19428,19429,19430,19433],{},"Alternatively, you can use the ",[19,19431,19432],{},"sc"," command:",[42,19435,19437],{"className":7487,"code":19436,"language":7489,"meta":50,"style":50},"sc query flowfuse-device-agent\n",[19,19438,19439],{"__ignoreMap":50},[146,19440,19441,19443,19446],{"class":148,"line":149},[146,19442,19432],{"class":2435},[146,19444,19445],{"class":1554}," query",[146,19447,19448],{"class":1554}," flowfuse-device-agent\n",[996,19450,19452],{"id":19451},"set-the-user-account-that-will-run-the-service","Set the user account that will run the service",[15,19454,19455],{},"Some things are easier to edit in the UI, so we will edit the service via the NSSM UI.",[42,19457,19459],{"className":7487,"code":19458,"language":7489,"meta":50,"style":50},"nssm edit flowfuse-device-agent\n",[19,19460,19461],{"__ignoreMap":50},[146,19462,19463,19465,19468],{"class":148,"line":149},[146,19464,18563],{"class":2435},[146,19466,19467],{"class":1554}," edit",[146,19469,19448],{"class":1554},[15,19471,19472],{},[392,19473],{"alt":19474,"src":19475},"nssm editor","\u002Fblog\u002F2023\u002F12\u002Fimages\u002Fnssm_service_editor.png",[15,19477,19478,19479,19482],{},"In the UI, you can edit the service name, description, startup type, etc.\nThe most important thing to check is the ",[19,19480,19481],{},"Application"," tab. This includes the path to the flowfuse-device-agent.cmd and its arguments.\nSelect the \"Log on\" tab, select \"This account\" and enter the service account name and password that will run the device-agent.\nClick the \"Edit Service\" button to save the changes.",[15,19484,19485,19486,19488],{},"Now you have a service that will run the device-agent as the ",[338,19487,18635],{}," account 🎉",[996,19490,19492],{"id":19491},"controlling-the-service","Controlling the service",[15,19494,19495],{},"You can start the service with the command:",[42,19497,19499],{"className":7487,"code":19498,"language":7489,"meta":50,"style":50},"sc start flowfuse-device-agent\n",[19,19500,19501],{"__ignoreMap":50},[146,19502,19503,19505,19507],{"class":148,"line":149},[146,19504,19432],{"class":2435},[146,19506,7633],{"class":1554},[146,19508,19448],{"class":1554},[15,19510,19511],{},"You can check the current status with the command:",[42,19513,19514],{"className":7487,"code":19436,"language":7489,"meta":50,"style":50},[19,19515,19516],{"__ignoreMap":50},[146,19517,19518,19520,19522],{"class":148,"line":149},[146,19519,19432],{"class":2435},[146,19521,19445],{"class":1554},[146,19523,19448],{"class":1554},[15,19525,19526],{},"You can stop the service with the command:",[42,19528,19530],{"className":7487,"code":19529,"language":7489,"meta":50,"style":50},"sc stop flowfuse-device-agent\n",[19,19531,19532],{"__ignoreMap":50},[146,19533,19534,19536,19539],{"class":148,"line":149},[146,19535,19432],{"class":2435},[146,19537,19538],{"class":1554}," stop",[146,19540,19448],{"class":1554},[996,19542,19544],{"id":19543},"further-reading","Further reading",[15,19546,19547,19548,19550,19551,19553],{},"If you'd like to learn about windows services via the ",[19,19549,19432],{}," command you can access\nthe help text by running ",[19,19552,19432],{}," from a command prompt.",[924,19555,19556],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}",{"title":50,"searchDepth":250,"depth":250,"links":19558},[19559,19560,19565,19569,19570,19577,19582],{"id":18567,"depth":185,"text":18568},{"id":18577,"depth":185,"text":18578,"children":19561},[19562,19563,19564],{"id":18647,"depth":221,"text":18648},{"id":18664,"depth":221,"text":18665},{"id":18686,"depth":221,"text":18687},{"id":18706,"depth":185,"text":18707,"children":19566},[19567,19568],{"id":18710,"depth":221,"text":18711},{"id":18724,"depth":221,"text":18725},{"id":18738,"depth":185,"text":18739},{"id":18836,"depth":185,"text":18837,"children":19571},[19572,19574,19576],{"id":18849,"depth":221,"text":19573},"cmd version elevated command prompt",{"id":18964,"depth":221,"text":19575},"powershell version elevated powershell prompt",{"id":19036,"depth":221,"text":19037},{"id":19061,"depth":185,"text":19062,"children":19578},[19579,19580,19581],{"id":19099,"depth":221,"text":19100},{"id":19180,"depth":221,"text":19181},{"id":19209,"depth":221,"text":19210},{"id":19285,"depth":185,"text":19286,"children":19583},[19584,19585,19586,19587,19588],{"id":19296,"depth":221,"text":19297},{"id":19410,"depth":221,"text":19411},{"id":19451,"depth":221,"text":19452},{"id":19491,"depth":221,"text":19492},{"id":19543,"depth":221,"text":19544},"2023-12-18","Learn how to configure the FlowFuse device agent as a Windows service using the nssm utility. Ensure uninterrupted Node-RED operation on your hardware for manufacturing efficiency.","\u002Fblog\u002F2023\u002F12\u002Fimages\u002Fagent-as-windows-service.png",{"excerpt":19593},{"type":12,"value":19594},[19595,19597],[15,19596,18555],{},[15,19598,18558,19599,18564],{},[307,19600,18563],{"href":18561,"rel":19601},[311],"\u002Fblog\u002F2023\u002F12\u002Fdevice-agent-as-a-windows-service",{"title":18549,"description":19590},{"loc":19602},"blog\u002F2023\u002F12\u002Fdevice-agent-as-a-windows-service","Step by step guide to run FlowFuse device agent as a Windows service",[9832,965,966],"rfDMq3q3LWD0kEeyAgQ1ZUYRSeg-lf1RYhaRRDUnSvg",{"id":19610,"title":19611,"authors":19612,"body":19613,"cta":3,"date":19892,"description":19893,"extension":946,"image":19894,"lastUpdated":3,"meta":19895,"navigation":253,"path":19902,"seo":19903,"sitemap":19904,"stem":19905,"subtitle":19893,"tags":19906,"tldr":3,"video":3,"__hash__":19907},"blog\u002Fblog\u002F2023\u002F10\u002Fcustom-vuetify-components-dashboard.md","Custom Vuetify components for Dashboard 2.0",[17901],{"type":12,"value":19614,"toc":19884},[19615,19622,19631,19635,19649,19652,19705,19709,19723,19730,19734,19743,19753,19756,19803,19813,19828,19835,19839,19842,19856,19859,19881],[15,19616,19617,19618,19621],{},"Vuetify is a library of UI components using Vue. This saves the developers of\nDashboard 2.0 a lot of time, but it can also help you, the end-user. As Vuetify\nis now included, it can be used to include ",[397,19619,19620],{},"any"," of their components. So in this\npost we're going to use a few of these to teach you how to use any of them.",[15,19623,19624,19625,19630],{},"Let's install the ",[307,19626,19629],{"href":19627,"rel":19628},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fgetting-started.html",[311],"Dashboard 2.0 package"," if you want to follow along. When that's done, let's figure\nout how to build custom components on dashboards.",[34,19632,19634],{"id":19633},"custom-components","Custom components",[15,19636,19637,19638,19642,19643,19648],{},"While going through the list of components on ",[307,19639,19641],{"href":15503,"rel":19640},[311],"Vuetify","\nthere's several examples that aren't natively implemented in Dashboard 2.0.\nOne example we'll use in a dashboard in this post is the\n",[307,19644,19647],{"href":19645,"rel":19646},"https:\u002F\u002Fvuetifyjs.com\u002Fen\u002Fcomponents\u002Fprogress-circular\u002F",[311],"Progress circular"," to\nbuild a count down timer.",[15,19650,19651],{},"The documentation explains which elements one can change, in this case the size and\nwidth. Having set those to the values you'd want in your dashboard, the HTML is\ngenerated for you, in my case it's:",[42,19653,19655],{"className":5156,"code":19654,"language":5158,"meta":50,"style":50},"\u003Cv-progress-circular model-value=\"20\" :size=\"128\" :width=\"12\">\u003C\u002Fv-progress-circular>\n",[19,19656,19657],{"__ignoreMap":50},[146,19658,19659,19661,19664,19667,19669,19671,19673,19675,19678,19680,19682,19685,19687,19690,19692,19694,19697,19699,19701,19703],{"class":148,"line":149},[146,19660,5165],{"class":160},[146,19662,19663],{"class":1549},"v-progress-circular",[146,19665,19666],{"class":152}," model-value",[146,19668,161],{"class":160},[146,19670,727],{"class":160},[146,19672,2904],{"class":1554},[146,19674,727],{"class":160},[146,19676,19677],{"class":152}," :size",[146,19679,161],{"class":160},[146,19681,727],{"class":160},[146,19683,19684],{"class":1554},"128",[146,19686,727],{"class":160},[146,19688,19689],{"class":152}," :width",[146,19691,161],{"class":160},[146,19693,727],{"class":160},[146,19695,19696],{"class":1554},"12",[146,19698,727],{"class":160},[146,19700,10280],{"class":160},[146,19702,19663],{"class":1549},[146,19704,5183],{"class":160},[996,19706,19708],{"id":19707},"using-the-template-node","Using the template node",[15,19710,19711,19712,19716,19717,19722],{},"Like the ",[307,19713,19715],{"href":19714},"\u002Fnode-red\u002Fcore-nodes\u002Ftemplate","template core node",", the dashboard package\ncomes with ",[307,19718,19721],{"href":19719,"rel":19720},"https:\u002F\u002Fdashboard.flowfuse.com\u002Fnodes\u002Fwidgets\u002Fui-template.html",[311],"a template node of its own",".\nIf we take the HTML from the Vuetify docs pages and copy it in a template node\nthe spinner will show up on the dashboard.",[15,19724,19725],{},[392,19726],{"alt":19727,"src":19728,"title":19729},"\"Custom widget on Dashboard 2.0\"","\u002Fblog\u002F2023\u002F10\u002Fimages\u002Fcustom-element-dashboard.png","Custom widget on Dashboard 2.0",[34,19731,19733],{"id":19732},"dynamic-templates","Dynamic templates",[15,19735,19736,19737,19742],{},"While a custom element on a page is cool, and shows you can inject arbitrary HTML\non a Dashboard, it's even better if we could make the element dynamic. So let's\nstart with a first dynamic element. The quickest way to get that done is have\nan ",[307,19738,19740],{"href":19739},"\u002Fnode-red\u002Fcore-nodes\u002Finject",[19,19741,372],{}," node output a random number every second.",[15,19744,19745,19746,19748,19749,19752],{},"So let's hook up an Inject, with ",[19,19747,382],{},"'s output being a JSONata expression\n",[19,19750,19751],{},"$round($random() * 100)"," to generate a random number. And let's make sure it\nsends a message every second.",[15,19754,19755],{},"Then we need to update the template node to the following snippet:",[42,19757,19759],{"className":5156,"code":19758,"language":5158,"meta":50,"style":50},"\u003Cv-progress-circular v-model=\"msg.payload\" :size=\"128\" :width=\"12\">\u003C\u002Fv-progress-circular>\n",[19,19760,19761],{"__ignoreMap":50},[146,19762,19763,19765,19767,19769,19771,19773,19775,19777,19779,19781,19783,19785,19787,19789,19791,19793,19795,19797,19799,19801],{"class":148,"line":149},[146,19764,5165],{"class":160},[146,19766,19663],{"class":1549},[146,19768,15568],{"class":152},[146,19770,161],{"class":160},[146,19772,727],{"class":160},[146,19774,382],{"class":1554},[146,19776,727],{"class":160},[146,19778,19677],{"class":152},[146,19780,161],{"class":160},[146,19782,727],{"class":160},[146,19784,19684],{"class":1554},[146,19786,727],{"class":160},[146,19788,19689],{"class":152},[146,19790,161],{"class":160},[146,19792,727],{"class":160},[146,19794,19696],{"class":1554},[146,19796,727],{"class":160},[146,19798,10280],{"class":160},[146,19800,19663],{"class":1549},[146,19802,5183],{"class":160},[15,19804,19805,19806,19809,19810,19812],{},"The difference is subtle, but important. Instead of hard-coding the ",[19,19807,19808],{},"model-value","\nto 20, the tag has changed name and it's set to ",[19,19811,382],{},". The latter makes\nthe value dynamic.",[15,19814,19815,19816,764,19818,19821,19822,19827],{},"Changing ",[19,19817,19808],{},[19,19819,19820],{},"v-model"," is due to leaking implementation details of\nDashboard 2.0. It uses VueJS to provide, among other features, easy updating of\ncomponents. If components are dynamic, ",[397,19823,19824,19825],{},"always use ",[19,19826,19820],{},". This allows VueJS\nto pick up changes made dynamically.",[15,19829,19830],{},[392,19831],{"alt":19832,"src":19833,"title":19834},"\"Progress spinner, random values\"","\u002Fblog\u002F2023\u002F10\u002Fimages\u002Frandom-progress-element.gif","Progress spinner, random values",[996,19836,19838],{"id":19837},"finishing-the-count-down-timer","Finishing the count down timer",[15,19840,19841],{},"This is mostly a programmers job, but it's not hard, so let's get to it. A button\nwould be great to reset the timer, and for the sake of this post we can hardcode\nthe deadline to 1m from the button press.",[15,19843,19844,19845,19848,19849,19852,19853,19855],{},"When dragging in a button node, connect it to a ",[307,19846,1405],{"href":19847},"\u002Fnode-red\u002Fcore-nodes\u002Fchange","\nnode. In the change node set the flow variable ",[19,19850,19851],{},"flow.deadline"," to the timestamp. The\nInject node from earlier needs updating to inject the ",[19,19854,19851],{},". All that's\nleft is calculating how many seconds passed, and normalizing 60 seconds to the\nrange between 0-100.",[15,19857,19858],{},"The complete flow is:",[15,19860,6389,19861,3830],{},[146,19862,19863,19864,19866,19867,19869,19870,19872,19873,19876,19877,19880],{},"{\"id\":\"ce9bb8f74e3fc934\",\"type\":\"ui-template\",\"z\":\"24065a0aadb305e3\",\"group\":\"8fa772a709ae3316\",\"dashboard\":\"e5a3f4cdb11e5e3b\",\"page\":\"5bedf7f49d5a6037\",\"name\":\"Progress spinner\",\"order\":0,\"width\":0,\"height\":0,\"format\":\"\u003Cv-progress-circular v-model=\"msg.payload\" ",[13141,19865],{},"=\"128\" ",[2335,19868],{},"=\"12\">\\n\",\"storeOutMessages\":true,\"fwdInMessages\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":810,\"y\":80,\"wires\":[[]]},{\"id\":\"8f3e6631414aa096\",\"type\":\"inject\",\"z\":\"24065a0aadb305e3\",\"name\":\"Inject deadline\",\"props\":",[146,19871,9126],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"deadline\",\"payloadType\":\"flow\",\"x\":140,\"y\":80,\"wires\":[[\"293cd6f9d727fa02\"]]},{\"id\":\"bd9032719d24a53d\",\"type\":\"ui-button\",\"z\":\"24065a0aadb305e3\",\"group\":\"8fa772a709ae3316\",\"name\":\"\",\"label\":\"Reset\",\"order\":0,\"width\":0,\"height\":0,\"passthru\":false,\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"topic\":\"deadline\",\"topicType\":\"msg\",\"x\":170,\"y\":140,\"wires\":[[\"61ef83d8b06ff626\"]]},{\"id\":\"61ef83d8b06ff626\",\"type\":\"change\",\"z\":\"24065a0aadb305e3\",\"name\":\"\",\"rules\":",[146,19874,19875],{},"{\"t\":\"set\",\"p\":\"deadline\",\"pt\":\"flow\",\"to\":\"\",\"tot\":\"date\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":350,\"y\":140,\"wires\":[[]]},{\"id\":\"293cd6f9d727fa02\",\"type\":\"change\",\"z\":\"24065a0aadb305e3\",\"name\":\"Secs since reset\",\"rules\":",[146,19878,19879],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"($millis() - msg.payload)\u002F1000\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":340,\"y\":80,\"wires\":[[\"9742da7e74fd3cd2\"]]},{\"id\":\"9742da7e74fd3cd2\",\"type\":\"range\",\"z\":\"24065a0aadb305e3\",\"minin\":\"0\",\"maxin\":\"60\",\"minout\":\"0\",\"maxout\":\"100\",\"action\":\"clamp\",\"round\":false,\"property\":\"payload\",\"name\":\"Seconds to percentages\",\"x\":570,\"y\":80,\"wires\":[[\"ce9bb8f74e3fc934\"]]},{\"id\":\"8fa772a709ae3316\",\"type\":\"ui-group\",\"name\":\"Group Name\",\"page\":\"5bedf7f49d5a6037\",\"width\":\"6\",\"height\":\"1\",\"order\":\"\",\"disp\":true},{\"id\":\"e5a3f4cdb11e5e3b\",\"type\":\"ui-base\",\"name\":\"UI Name\",\"path\":\"\u002Fdashboard\"},{\"id\":\"5bedf7f49d5a6037\",\"type\":\"ui-page\",\"name\":\"Page Name\",\"ui\":\"e5a3f4cdb11e5e3b\",\"path\":\"\u002F\",\"layout\":\"grid\",\"theme\":\"8240fbe7c09bc81c\"},{\"id\":\"8240fbe7c09bc81c\",\"type\":\"ui-theme\",\"name\":\"Theme Name\",\"colors\":{\"surface\":\"#ffffff\",\"primary\":\"#0094ce\",\"bgPage\":\"#eeeeee\",\"groupBg\":\"#ffffff\",\"groupOutline\":\"#cccccc\"}}",[924,19882,19883],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":19885},[19886,19889],{"id":19633,"depth":185,"text":19634,"children":19887},[19888],{"id":19707,"depth":221,"text":19708},{"id":19732,"depth":185,"text":19733,"children":19890},[19891],{"id":19837,"depth":221,"text":19838},"2023-10-02","Expand your dashboard with the full collection of Vuetify components","blog\u002F2023\u002F10\u002Fimages\u002Ftile-blog-custom-vuetify-components-for-Dashboard.png",{"excerpt":19896},{"type":12,"value":19897},[19898],[15,19899,19617,19900,19621],{},[397,19901,19620],{},"\u002Fblog\u002F2023\u002F10\u002Fcustom-vuetify-components-dashboard",{"title":19611,"description":19893},{"loc":19902},"blog\u002F2023\u002F10\u002Fcustom-vuetify-components-dashboard",[9832,966,6563,11070],"KO_LrX3IbVRXKJstHOLgJyEUJhvBN9uquLYkw9FdhGM",{"id":19909,"title":19910,"authors":19911,"body":19913,"cta":21308,"date":21311,"description":21312,"extension":946,"image":21313,"lastUpdated":948,"meta":21314,"navigation":253,"path":21324,"seo":21325,"sitemap":21326,"stem":21327,"subtitle":21328,"tags":21329,"tldr":21330,"video":3,"__hash__":21331},"blog\u002Fblog\u002F2023\u002F07\u002Fhow-to-build-a-opc-client-dashboard-in-node-red.md","How to Build an OPC UA Client Dashboard in Node-RED - Part 3 (2026)",[19912],"richard-meyer",{"type":12,"value":19914,"toc":21297},[19915,19926,19934,19937,19941,19948,19967,19970,19976,19980,19992,19995,20001,20008,20026,20029,20033,20036,20050,20053,20057,20060,20066,20069,20208,20214,20221,20231,20238,20244,20247,20253,20256,20262,20280,20286,20293,20299,20313,20332,20335,20340,20343,20354,20358,20361,20368,20590,20599,20609,20627,20646,20656,20663,20681,20688,20694,20701,20708,20711,20717,20721,20724,20731,20757,20773,20776,20790,20800,20814,20831,20834,20840,20844,20847,20853,20913,20927,20943,20958,20965,20979,20982,20985,20991,20995,20998,21004,21016,21018,21021,21024,21027],[15,19916,19917,19918,19921,19922,19925],{},"This article is the third and final part of our OPC UA content series. In the ",[307,19919,19920],{"href":9792},"first article",", we cover some OPC UA fundamentals and walk through an example OPC UA Server flow. In the ",[307,19923,19924],{"href":9785},"second article",", we built a SSL-secured OPC UA server using data from an Allen Bradley PLC as a source.\nIn 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.",[15,19927,19928,19929,19933],{},"This article will requires the ",[307,19930,19932],{"href":9384,"rel":19931},[311],"Prosys OPC UA Simulation Server",", an application designed for testing OPC UA client applications and learning the technology.  It’s a free cross-platform application that supports Windows, Linux, and MacOS.  This article will use the Windows version.",[15,19935,19936],{},"Note: full source code for the OPC Client Dashboard is included at the end of the article.",[34,19938,19940],{"id":19939},"custom-nodes-used-assumptions","Custom Nodes Used & Assumptions",[15,19942,19943,19944,19947],{},"Several custom nodes are required in order to properly deploy this flow.  For more detailed information on how to install a custom node, follow the instructions from an ",[307,19945,19946],{"href":9799},"earlier article"," where the process on installing custom nodes is explained in detail.",[100,19949,19950,19956,19961],{},[103,19951,19952],{},[307,19953,2153],{"href":19954,"rel":19955},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002F@flowfuse\u002Fnode-red-dashboard",[311],[103,19957,19958],{},[307,19959,9396],{"href":9394,"rel":19960},[311],[103,19962,19963],{},[307,19964,6432],{"href":19965,"rel":19966},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002F@flowfuse\u002Fnode-red-dashboard-2-ui-led",[311],[15,19968,19969],{},"As this is not a production application, no security will be utilized, and it is assumed that the OPC UA Server is running on the same network as the Node-RED OPC Client.",[15,19971,19972,19973,167],{},"Is it also assumed that the end user of this article has familiarization with dashboards.  There are many dashboard basic guides available on our FlowFuse website, For more infomation go to ",[307,19974,19975],{"href":13550},"Node-RED Dashboard 2.0 guides",[34,19977,19979],{"id":19978},"install-and-deploy-the-prosys-opc-ua-simulation-server","Install and Deploy the Prosys OPC UA Simulation Server",[15,19981,19982,19983,19988,19989,167],{},"The Prosys OPC UA Simulation Server is ",[307,19984,19987],{"href":19985,"rel":19986},"https:\u002F\u002Fprosysopc.com\u002Fproducts\u002Fopc-ua-simulation-server\u002Fevaluate\u002F",[311],"free to download",", but requires a sign-up process.  Download and install the server, then run the application.  Once the application is started, the first thing you should do is go to ",[19,19990,19991],{},"options -> switch to expert mode",[15,19993,19994],{},"This will give us access to the address space tab, which we will need to develop our client application in Node-RED.",[15,19996,19997],{},[392,19998],{"alt":19999,"dataZoomable":50,"src":20000},"expert-mode.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fexpert-mode.png",[15,20002,20003,20004,20007],{},"When the application is run, an endpoint url will be displayed on the ",[19,20005,20006],{},"status"," tab, along with an indication that the server is currently running.",[15,20009,20010,20014,20015,20018,20019,20022,20023,167],{},[392,20011],{"alt":20012,"dataZoomable":50,"src":20013},"opc-endpoint-url.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopc-endpoint-url.png","\nCopy the connection endpoint, but be warned that you will likely need to replace the computer name (in my case ",[19,20016,20017],{},"DESKTOP-0K0483A",", with the actual IP address of the machine running the server.  The IP address of the machine on my local network is ",[19,20020,20021],{},"192.168.0.141",", which changes my UA TCP endpoint address to ",[19,20024,20025],{},"opc.tcp:\u002F\u002F192.168.0.141:53530\u002FOPCUA\u002FSimulationServer",[15,20027,20028],{},"Now the simulation server is set up and we are ready to start developing the OPC Client application.",[34,20030,20032],{"id":20031},"objectives-of-the-node-red-opc-client-dashboard-application","Objectives of the Node-RED OPC Client Dashboard Application",[15,20034,20035],{},"The goal is not to develop a production-level application, rather, it’s to show a variety of features that one can utilize to demonstrate common OPC UA Client application capabilities in Node-RED, while also demonstrating a variety of methods to visualize the results in a dashboard.  There are 4 main objectives of the Node-RED OPC Client Dashboard application. They are:",[326,20037,20038,20041,20044,20047],{},[103,20039,20040],{},"Browse hierarchical server address space structure & display on a dashboard",[103,20042,20043],{},"Read OPC UA values from various namespaces, showing a variety of datatypes and different ways they can be visualized",[103,20045,20046],{},"Write OPC UA values back to the OPC UA server directly from the OPC UA Client dashboard",[103,20048,20049],{},"Read alarms & events from the OPC UA Server and display them on the dashboard",[15,20051,20052],{},"Rather than building the flow step-by-step, the flow source code will be presented for each objective, and a the flow will be explained so that it is understood what is happening in each section of code.",[34,20054,20056],{"id":20055},"browse-hierarchical-server-address-space-structure-with-opc-ua-browser-node","Browse Hierarchical Server Address Space Structure With OPC UA Browser Node",[15,20058,20059],{},"The first flow will browse the hierarchical OPC UA Server address space structure and display it on the dashboard.",[15,20061,20062],{},[392,20063],{"alt":20064,"dataZoomable":50,"src":20065},"image-20230727-085611.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fimage-20230727-085611.png",[15,20067,20068],{},"You can import this flow into Node-RED using the code below:",[15,20070,6389,20071,3830],{},[146,20072,20073,20074,20077,20078,20080,20081,20083,20084,20093,20094,20096,20097,20105,20106,20113,20114,20116,20117,20120,20121,20128,20129,20137,20138,20145,20146,20149,20150,20152,20153,20155,20156,20158,20159,20169,20170,20178,20179,20188,20189,20194,20195,20199,20200,20204,20205,20207],{},"{\"id\":\"ca62be3e01388319\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Browse Hierarchical Address Space Structure & Display on Dashboard\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,20075,20076],{},"\"6b17b2da2b942bb4\",\"61797eccf2785257\",\"4d92d940177b6ee3\",\"68a113d5893b7c01\",\"d0c969b6a59fac3a\",\"639da01fc957e547\",\"29437ca7222d9a64\",\"49983d5da0958bf2\",\"49040d0cf1144f0a\",\"e7c55f412ef86543\",\"de21b7ad98a05833\",\"2d56e9a431c21a3b\",\"ac95bd0e2b304eec\",\"6fdabcc2950ccf4e\",\"1c49fa5142d2cf17\",\"335878527020598c\",\"7b208f2e8cba6205\",\"52dd2e5dcddad58f\",\"a5acdccfd2033aec\",\"157322c9c360446d\",\"78a012e5db377fd9\"",",\"x\":94,\"y\":139,\"w\":1172,\"h\":422},{\"id\":\"6b17b2da2b942bb4\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,20079],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":550,\"y\":280,\"wires\":[[\"4d92d940177b6ee3\",\"d0c969b6a59fac3a\",\"639da01fc957e547\"]]},{\"id\":\"61797eccf2785257\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Get Base Folder Structure\",\"props\":",[146,20082,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":\"0.3\",\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":280,\"y\":280,\"wires\":[[\"6b17b2da2b942bb4\"]]},{\"id\":\"4d92d940177b6ee3\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Simulation Folder\",\"rules\":",[146,20085,20086,20087,20089,20090,20092],{},"{\"t\":\"set\",\"p\":\"Objects.Simulation.nodeId\",\"pt\":\"flow\",\"to\":\"payload",[146,20088,6820],{},".item.nodeId\",\"tot\":\"msg\"},{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload",[146,20091,6820],{},".item.browseName.name\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":830,\"y\":220,\"wires\":[[\"335878527020598c\"]]},{\"id\":\"68a113d5893b7c01\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Display on Dashboard\",\"info\":\"\",\"x\":1140,\"y\":180,\"wires\":",[146,20095],{},"},{\"id\":\"d0c969b6a59fac3a\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"MyObjects Folder\",\"rules\":",[146,20098,20099,20100,20089,20103,20092],{},"{\"t\":\"set\",\"p\":\"Objects.MyObjects.nodeId\",\"pt\":\"flow\",\"to\":\"payload",[146,20101,20102],{},"4",[146,20104,20102],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":830,\"y\":340,\"wires\":[[\"52dd2e5dcddad58f\"]]},{\"id\":\"639da01fc957e547\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"StaticData Folder\",\"rules\":",[146,20107,20108,20109,20089,20111,20092],{},"{\"t\":\"set\",\"p\":\"Objects.StaticData.nodeId\",\"pt\":\"flow\",\"to\":\"payload",[146,20110,207],{},[146,20112,207],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":830,\"y\":280,\"wires\":[[\"7b208f2e8cba6205\"]]},{\"id\":\"29437ca7222d9a64\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,20115],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":550,\"y\":440,\"wires\":[[\"49040d0cf1144f0a\",\"e7c55f412ef86543\"]]},{\"id\":\"49983d5da0958bf2\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Get StaticData Folder Structure\",\"props\":",[146,20118,20119],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"v\":\"Objects.StaticData.nodeId\",\"vt\":\"flow\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":\"0.3\",\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":270,\"y\":440,\"wires\":[[\"29437ca7222d9a64\"]]},{\"id\":\"49040d0cf1144f0a\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"AnalogItemArrays Folder\",\"rules\":",[146,20122,20123,20124,20089,20126,20092],{},"{\"t\":\"set\",\"p\":\"Objects.StaticData.AnalogItemArrays.nodeId\",\"pt\":\"flow\",\"to\":\"payload",[146,20125,62],{},[146,20127,62],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":850,\"y\":460,\"wires\":[[\"157322c9c360446d\"]]},{\"id\":\"e7c55f412ef86543\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"StaticArrayVariables Folder\",\"rules\":",[146,20130,20131,20132,20089,20135,20092],{},"{\"t\":\"set\",\"p\":\"Objects.StaticData.StaticArrayVariables.nodeId\",\"pt\":\"flow\",\"to\":\"payload",[146,20133,20134],{},"6",[146,20136,20134],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":860,\"y\":400,\"wires\":[[\"a5acdccfd2033aec\"]]},{\"id\":\"de21b7ad98a05833\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"MyDevice Object\",\"rules\":",[146,20139,20140,20141,20089,20143,20092],{},"{\"t\":\"set\",\"p\":\"Objects.MyObjects.MyDevice.nodeId\",\"pt\":\"flow\",\"to\":\"payload",[146,20142,677],{},[146,20144,677],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":830,\"y\":520,\"wires\":[[\"78a012e5db377fd9\"]]},{\"id\":\"2d56e9a431c21a3b\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Get MyObjects Object Structure\",\"props\":",[146,20147,20148],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"v\":\"Objects.MyObjects.nodeId\",\"vt\":\"flow\"}",",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":\"0.5\",\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":270,\"y\":520,\"wires\":[[\"ac95bd0e2b304eec\"]]},{\"id\":\"ac95bd0e2b304eec\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,20151],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":550,\"y\":520,\"wires\":[[\"de21b7ad98a05833\"]]},{\"id\":\"6fdabcc2950ccf4e\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Store & Parse nodeId & browseName\",\"info\":\"\",\"x\":850,\"y\":180,\"wires\":",[146,20154],{},"},{\"id\":\"1c49fa5142d2cf17\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"name\":\"Global Address Space Folder Browse\",\"info\":\"\",\"x\":410,\"y\":220,\"wires\":",[146,20157],{},"},{\"id\":\"335878527020598c\",\"type\":\"ui-template\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"group\":\"ef9998baf5f61e8a\",\"page\":\"\",\"ui\":\"\",\"name\":\"Simulation\",\"order\":1,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,20160,20161,20162,20165,20166,20168],{},"\\n    \u003Cdiv class=\"inline-content\">\\n        ",[15,20163,20164],{},"Namespace 3","\\n        \u003Cv-icon color=\"black\" icon=\"mdi-folder\" size=\"large\"> ",[17483,20167],{"value":382},"\\n    \\n","\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1110,\"y\":220,\"wires\":[[]]},{\"id\":\"7b208f2e8cba6205\",\"type\":\"ui-template\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"group\":\"ef9998baf5f61e8a\",\"page\":\"\",\"ui\":\"\",\"name\":\"StaticData\",\"order\":2,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,20171,20161,20172,20175,20176,20168],{},[15,20173,20174],{},"Namespace 5","\\n        \u003Cv-icon color=\"black\" icon=\"mdi-folder-arrow-down\" size=\"large\"> ",[17483,20177],{"value":382},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1110,\"y\":280,\"wires\":[[]]},{\"id\":\"52dd2e5dcddad58f\",\"type\":\"ui-template\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"group\":\"ef9998baf5f61e8a\",\"page\":\"\",\"ui\":\"\",\"name\":\"MyObjects\",\"order\":5,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,20180,20181,20182,20185,20186,20168],{},"\\n    ",[15,20183,20184],{},"Namespace 6","\\n    \u003Cdiv class=\"inline-content\">\\n        \u003Cv-icon color=\"black\" icon=\"mdi-folder\" size=\"large\"> ",[17483,20187],{"value":382},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1110,\"y\":340,\"wires\":[[]]},{\"id\":\"a5acdccfd2033aec\",\"type\":\"ui-template\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"group\":\"ef9998baf5f61e8a\",\"page\":\"\",\"ui\":\"\",\"name\":\"StaticArrayVariables\",\"order\":3,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,20190,20191,20192,20168],{},"\\n    \u003Cdiv class=\"d-flex align-center ml-3\">\\n        \u003Cv-icon color=\"black\" icon=\"mdi-folder\" size=\"large\"> ",[17483,20193],{"value":382},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1140,\"y\":400,\"wires\":[[]]},{\"id\":\"157322c9c360446d\",\"type\":\"ui-template\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"group\":\"ef9998baf5f61e8a\",\"page\":\"\",\"ui\":\"\",\"name\":\"AnalogItemArrays\",\"order\":4,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,20196,20191,20197,20168],{},[17483,20198],{"value":382},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1130,\"y\":460,\"wires\":[[]]},{\"id\":\"78a012e5db377fd9\",\"type\":\"ui-template\",\"z\":\"5b972161c4e0464e\",\"g\":\"ca62be3e01388319\",\"group\":\"ef9998baf5f61e8a\",\"page\":\"\",\"ui\":\"\",\"name\":\"MyDevice\",\"order\":6,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,20201,20191,20202,20168],{},[17483,20203],{"value":382},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1100,\"y\":520,\"wires\":[[]]},{\"id\":\"53f4394dbf12c6b7\",\"type\":\"OpcUa-Endpoint\",\"endpoint\":\"opc.tcp:\u002F\u002F192.168.56.1:53530\u002FOPCUA\u002FSimulationServer\",\"secpol\":\"None\",\"secmode\":\"None\",\"none\":true,\"login\":false,\"usercert\":false,\"usercertificate\":\"\",\"userprivatekey\":\"\"},{\"id\":\"ef9998baf5f61e8a\",\"type\":\"ui-group\",\"name\":\" Address Space Folder Structure\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"2\",\"height\":\"1\",\"order\":1,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"44d3feb2a1143d7b\",\"type\":\"ui-page\",\"name\":\"OPC UA\",\"ui\":\"5355e0c476f9da3b\",\"path\":\"\u002Fopcua\",\"icon\":\"home\",\"layout\":\"grid\",\"theme\":\"61eee6fc60281b9b\",\"order\":1,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"5355e0c476f9da3b\",\"type\":\"ui-base\",\"name\":\"My Dashboard\",\"path\":\"\u002Fdashboard\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,20206,6458],{},",\"showPathInSidebar\":false,\"navigationStyle\":\"default\"},{\"id\":\"61eee6fc60281b9b\",\"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\"}}",[15,20209,20210,20211,8670],{},"To understand what is going on in this flow, we must refer back to the OPC UA Simulation Server ",[19,20212,20213],{},"Address Space",[15,20215,20216,20217,20220],{},"When we browse the OPC Server base folder structure in Node-RED, we will be browsing everything included under the ",[19,20218,20219],{},"Objects"," tree.",[15,20222,20223,20227,20228,20230],{},[392,20224],{"alt":20225,"dataZoomable":50,"src":20226},"address-space.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Faddress-space.png","\nIn our flow, we get the base folder structure by using an OPC-UA Browser node, as shown, with an endpoint that points to our OPC UA Server endpoint url we grabbed earlier in this article.  It is also worth noting we leave the ",[19,20229,12140],{}," blank.  By doing this, we will browse the entire folder structure by default.",[15,20232,20233,20237],{},[392,20234],{"alt":20235,"dataZoomable":50,"src":20236},"image-20230727-090252.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fimage-20230727-090252.png","\nThe configuration of the endpoint properties includes no security credentials, as shown below.",[15,20239,20240],{},[392,20241],{"alt":20242,"dataZoomable":50,"src":20243},"endpoint-configure.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fendpoint-configure.png",[15,20245,20246],{},"Using the output of a debug node, we get from the OPC UA Browser yield a payload with an array of 5 objects.",[15,20248,20249],{},[392,20250],{"alt":20251,"dataZoomable":50,"src":20252},"address-debug.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Faddress-debug.png",[15,20254,20255],{},"Each object returned represents the 5 objects that are in our OPC UA Server Objects tree.",[15,20257,20258],{},[392,20259],{"alt":20260,"dataZoomable":50,"src":20261},"browse-payload-1.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fbrowse-payload-1.png",[15,20263,20264,20265,1255,20268,1262,20271,20274,20275,63,20278,167],{},"However, of those 5 objects, only 3 of them are folders that contain actual OPC values.  ",[19,20266,20267],{},"MyObjects",[19,20269,20270],{},"Simulation",[19,20272,20273],{},"StaticData",".  We can ignore ",[19,20276,20277],{},"Aliases",[19,20279,12012],{},[15,20281,20282],{},[392,20283],{"alt":20284,"dataZoomable":50,"src":20285},"address-space-folders-only.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Faddress-space-folders-only.png",[15,20287,20288,20289,20292],{},"So looking deeper into the payload of our global browse from the ",[19,20290,20291],{},"OPC UA Browser node",", we can drill down into the details and see how they correlate with the folders in the server.",[15,20294,20295],{},[392,20296],{"alt":20297,"dataZoomable":50,"src":20298},"browse-node.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fbrowse-node.png",[15,20300,20301,20302,20304,20305,20308,20309,20312],{},"As shown above, element 2 in the array returned from the global browse corresponds to the ",[19,20303,20270],{}," folder.  And we are interested in two important values in this data-structure - the ",[19,20306,20307],{},"NodeId",", which is topic an OPC Client uses to point specific OPC values, and the ",[19,20310,20311],{},"browseName",", which is the name we see visually when we try to identify an OPC topic.  We can now use this logic to parse out this useful information using a change node.",[15,20314,20315,20319,20320,63,20323,20325,20326,20328,20329,20331],{},[392,20316],{"alt":20317,"dataZoomable":50,"src":20318},"simulation-folder.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fsimulation-folder.png","\nThis change node is grabbing the ",[19,20321,20322],{},"nodeId",[19,20324,20311],{}," .  The ",[19,20327,20322],{}," is stored in a context variable for later use, while the ",[19,20330,20311],{}," is used as the payload to be displayed on our dashboard.",[15,20333,20334],{},"The rest of the flow follows this same pattern, to end up with a folder structure that we can display on our dashboard that matches the structure on our OPC Server",[100,20336,20337],{},[103,20338,20339],{},"note - to make the flow more manageable, not all browsable folders were included in the dashboard, as this flow is just meant to serve as an example, rather than be a 1:1 copy of everything in the server.",[15,20341,20342],{},"If you deploy the flow and pull up the dashboard, it results in the following output -",[15,20344,20345,20349,20350,20353],{},[392,20346],{"alt":20347,"dataZoomable":50,"src":20348},"address-space-dashboard.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Faddress-space-dashboard.png","\nShowing side-by-side with the server, you can see that we successfully browsed a portion of the address space and displayed the values on the dashboard.  Admittedly, a lot of work for not much pay-off, but it’s a worthwhile exercise in understanding how to browse topics using the ",[19,20351,20352],{},"OPC UA Browser"," node.  The browser node is best used for reading OPC UA values, which will be covered next.",[34,20355,20357],{"id":20356},"read-opc-ua-values-using-opc-ua-browser-node","Read OPC UA Values Using OPC UA Browser Node",[15,20359,20360],{},"The next set of flows read OPC UA values from the server and displays them on the dashboard.",[15,20362,20363,20367],{},[392,20364],{"alt":20365,"dataZoomable":50,"src":20366},"read-opc-values.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fread-opc-values.png","\nYou can import these flows into Node-RED using the code below:",[15,20369,6389,20370,3830],{},[146,20371,20073,20372,20077,20374,20080,20376,20083,20378,20093,20384,20096,20386,20105,20392,20113,20398,20116,20400,20120,20402,20128,20408,20137,20414,20145,20420,20149,20422,20152,20424,20155,20426,20158,20428,20169,20434,20178,20440,20188,20446,20194,20450,20199,20454,20204,20458,20460,20461,20464,20465,20467,20468,20471,20472,20474,20475,20481,20482,20485,20486,20490,20491,20495,20496,20500,20501,20503,20504,20506,20507,20509,20510,20513,20514,20516,20517,20519,20520,20522,20523,20526,20527,20529,20530,20533,20534,20536,20537,20540,20541,20543,20544,20550,20551,20553,20554,20556,20557,20560,20561,20565,20566,20568,20569,20571,20572,20574,20575,20577,20578,20580,20581,20583,20584,20586,20587,20589],{},[146,20373,20076],{},[146,20375],{},[146,20377,3779],{},[146,20379,20086,20380,20089,20382,20092],{},[146,20381,6820],{},[146,20383,6820],{},[146,20385],{},[146,20387,20099,20388,20089,20390,20092],{},[146,20389,20102],{},[146,20391,20102],{},[146,20393,20108,20394,20089,20396,20092],{},[146,20395,207],{},[146,20397,207],{},[146,20399],{},[146,20401,20119],{},[146,20403,20123,20404,20089,20406,20092],{},[146,20405,62],{},[146,20407,62],{},[146,20409,20131,20410,20089,20412,20092],{},[146,20411,20134],{},[146,20413,20134],{},[146,20415,20140,20416,20089,20418,20092],{},[146,20417,677],{},[146,20419,677],{},[146,20421,20148],{},[146,20423],{},[146,20425],{},[146,20427],{},[9982,20429,20161,20430,20165,20432,20168],{},[15,20431,20164],{},[17483,20433],{"value":382},[9982,20435,20161,20436,20175,20438,20168],{},[15,20437,20174],{},[17483,20439],{"value":382},[9982,20441,20181,20442,20185,20444,20168],{},[15,20443,20184],{},[17483,20445],{"value":382},[9982,20447,20191,20448,20168],{},[17483,20449],{"value":382},[9982,20451,20191,20452,20168],{},[17483,20453],{"value":382},[9982,20455,20191,20456,20168],{},[17483,20457],{"value":382},[146,20459,6458],{},",\"showPathInSidebar\":false,\"navigationStyle\":\"default\"},{\"id\":\"61eee6fc60281b9b\",\"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\"}},{\"id\":\"8557072f05e4bda0\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Read Simulation Values & Display on Dashboard\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,20462,20463],{},"\"9659d40ac9063764\",\"9f5b597ec8179fb4\",\"a8d919f497fcff04\",\"13f5c98b7fd5f5da\",\"ec5dca5eb9d4971b\",\"1780cb86597d3c67\",\"1a2fcac87247cda4\",\"4d9b758e39555124\",\"da468bc150517fa6\",\"82aa12173dd7bbca\",\"57d8777e34b55b7b\",\"10877909d1daf6fe\",\"c4d4a3b0df372e4c\",\"b0cf511f824f2a86\",\"f2efc6b419414c9a\"",",\"x\":94,\"y\":599,\"w\":1372,\"h\":302},{\"id\":\"9659d40ac9063764\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,20466],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":570,\"y\":760,\"wires\":[[\"ec5dca5eb9d4971b\"]]},{\"id\":\"9f5b597ec8179fb4\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Update Simulation Values @ 1 second\",\"props\":",[146,20469,20470],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"v\":\"Objects.Simulation.nodeId\",\"vt\":\"flow\"}",",\"repeat\":\"1\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":300,\"y\":760,\"wires\":[[\"9659d40ac9063764\"]]},{\"id\":\"a8d919f497fcff04\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Read Simulation Values\",\"info\":\"\",\"x\":460,\"y\":720,\"wires\":",[146,20473],{},"},{\"id\":\"13f5c98b7fd5f5da\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Get Counter Value\",\"rules\":",[146,20476,20477,20478,20480],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload",[146,20479,62],{},".item.value\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1070,\"y\":680,\"wires\":[[\"10877909d1daf6fe\"]]},{\"id\":\"ec5dca5eb9d4971b\",\"type\":\"switch\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"empty check\",\"property\":\"payload\",\"propertyType\":\"msg\",\"rules\":",[146,20483,20484],{},"{\"t\":\"nempty\"}",",\"checkall\":\"true\",\"repair\":false,\"outputs\":1,\"x\":790,\"y\":760,\"wires\":[[\"13f5c98b7fd5f5da\",\"1780cb86597d3c67\",\"1a2fcac87247cda4\",\"4d9b758e39555124\"]]},{\"id\":\"1780cb86597d3c67\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Get Random Number Value\",\"rules\":",[146,20487,20477,20488,20480],{},[146,20489,6820],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1100,\"y\":740,\"wires\":[[\"c4d4a3b0df372e4c\"]]},{\"id\":\"1a2fcac87247cda4\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Get Sawtooth Value\",\"rules\":",[146,20492,20477,20493,20480],{},[146,20494,207],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1080,\"y\":800,\"wires\":[[\"b0cf511f824f2a86\"]]},{\"id\":\"4d9b758e39555124\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Get Sawtooth Value\",\"rules\":",[146,20497,20477,20498,20480],{},[146,20499,20102],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1080,\"y\":860,\"wires\":[[\"f2efc6b419414c9a\"]]},{\"id\":\"da468bc150517fa6\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Discard Empty Datasets\",\"info\":\"\",\"x\":780,\"y\":720,\"wires\":",[146,20502],{},"},{\"id\":\"82aa12173dd7bbca\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Parse Simulation Values\",\"info\":\"\",\"x\":1070,\"y\":640,\"wires\":",[146,20505],{},"},{\"id\":\"57d8777e34b55b7b\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Display on Dashboard\",\"info\":\"\",\"x\":1340,\"y\":640,\"wires\":",[146,20508],{},"},{\"id\":\"10877909d1daf6fe\",\"type\":\"ui-gauge\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"name\":\"Counter\",\"group\":\"af263064820fb7d0\",\"order\":0,\"width\":3,\"height\":3,\"gtype\":\"gauge-half\",\"gstyle\":\"needle\",\"title\":\"gauge\",\"units\":\"units\",\"icon\":\"\",\"prefix\":\"\",\"suffix\":\"\",\"segments\":",[146,20511,20512],{},"{\"from\":\"0\",\"color\":\"#5cd65c\"},{\"from\":\"15\",\"color\":\"#ffc800\"},{\"from\":\"30\",\"color\":\"#ea5353\"}",",\"min\":0,\"max\":\"30\",\"sizeThickness\":16,\"sizeGap\":4,\"sizeKeyThickness\":8,\"styleRounded\":true,\"styleGlow\":false,\"className\":\"\",\"x\":1320,\"y\":680,\"wires\":",[146,20515],{},"},{\"id\":\"c4d4a3b0df372e4c\",\"type\":\"ui-text\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"group\":\"af263064820fb7d0\",\"order\":0,\"width\":0,\"height\":0,\"name\":\"Random Number\",\"label\":\"Random Number\",\"format\":\"",[17483,20518],{"value":382},"\",\"layout\":\"row-spread\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#717171\",\"className\":\"\",\"x\":1350,\"y\":740,\"wires\":",[146,20521],{},"},{\"id\":\"b0cf511f824f2a86\",\"type\":\"ui-chart\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"group\":\"af263064820fb7d0\",\"name\":\"\",\"label\":\"Sawtooth\",\"order\":9007199254740991,\"chartType\":\"line\",\"category\":\"Sawtooth\",\"categoryType\":\"str\",\"xAxisProperty\":\"\",\"xAxisPropertyType\":\"msg\",\"xAxisType\":\"time\",\"yAxisProperty\":\"\",\"ymin\":\"\",\"ymax\":\"\",\"action\":\"append\",\"pointShape\":\"line\",\"pointRadius\":4,\"showLegend\":true,\"removeOlder\":1,\"removeOlderUnit\":\"60\",\"removeOlderPoints\":\"\",\"colors\":",[146,20524,20525],{},"\"#1f77b4\",\"#aec7e8\",\"#ff7f0e\",\"#2ca02c\",\"#98df8a\",\"#d62728\",\"#ff9896\",\"#9467bd\",\"#c5b0d5\"",",\"width\":\"3\",\"height\":\"4\",\"className\":\"\",\"x\":1320,\"y\":800,\"wires\":[[]]},{\"id\":\"f2efc6b419414c9a\",\"type\":\"ui-chart\",\"z\":\"5b972161c4e0464e\",\"g\":\"8557072f05e4bda0\",\"group\":\"af263064820fb7d0\",\"name\":\"\",\"label\":\"Sinusoid\",\"order\":9007199254740991,\"chartType\":\"line\",\"category\":\"Sawtooth\",\"categoryType\":\"str\",\"xAxisProperty\":\"\",\"xAxisPropertyType\":\"msg\",\"xAxisType\":\"time\",\"yAxisProperty\":\"\",\"ymin\":\"\",\"ymax\":\"\",\"action\":\"append\",\"pointShape\":\"line\",\"pointRadius\":4,\"showLegend\":true,\"removeOlder\":1,\"removeOlderUnit\":\"60\",\"removeOlderPoints\":\"\",\"colors\":",[146,20528,20525],{},",\"width\":\"3\",\"height\":\"4\",\"className\":\"\",\"x\":1320,\"y\":860,\"wires\":[[]]},{\"id\":\"af263064820fb7d0\",\"type\":\"ui-group\",\"name\":\"Simulation values\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"3\",\"height\":\"1\",\"order\":2,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"5afdbddf71507886\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Read StaticData Values & Display on Dashboard\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,20531,20532],{},"\"e998aa804042128b\",\"6c9b7d4d195a1e9a\",\"cd097b744d0ec625\",\"18d21607c87ab153\",\"7b5143c4960f92a1\",\"0625b0cf6f546a4a\",\"9d899fbb4d1648b3\",\"6e1edc31687dde54\",\"051e1f282076fed2\",\"de2a1c3e380f743b\",\"c74606c48ccf5a40\",\"053bda13f2a2eabe\",\"277dcf430dc86996\",\"d708e6264cec0070\"",",\"x\":84,\"y\":939,\"w\":1382,\"h\":202},{\"id\":\"e998aa804042128b\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,20535],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":630,\"y\":1020,\"wires\":[[\"7b5143c4960f92a1\"]]},{\"id\":\"6c9b7d4d195a1e9a\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Update AnalogItemArrays Values @ 1 second\",\"props\":",[146,20538,20539],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"v\":\"Objects.StaticData.AnalogItemArrays.nodeId\",\"vt\":\"flow\"}",",\"repeat\":\"1\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":320,\"y\":1020,\"wires\":[[\"e998aa804042128b\"]]},{\"id\":\"cd097b744d0ec625\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Read StaticData Values\",\"info\":\"\",\"x\":520,\"y\":980,\"wires\":",[146,20542],{},"},{\"id\":\"18d21607c87ab153\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Get ByteAnalogItemArray Value\",\"rules\":",[146,20545,20546,20547,20549],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"$string(payload",[146,20548,677],{},".item.value)\\t\",\"tot\":\"jsonata\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1070,\"y\":1020,\"wires\":[[\"277dcf430dc86996\"]]},{\"id\":\"7b5143c4960f92a1\",\"type\":\"switch\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"empty check\",\"property\":\"payload\",\"propertyType\":\"msg\",\"rules\":",[146,20552,20484],{},",\"checkall\":\"true\",\"repair\":false,\"outputs\":1,\"x\":830,\"y\":1020,\"wires\":[[\"18d21607c87ab153\"]]},{\"id\":\"0625b0cf6f546a4a\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,20555],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":630,\"y\":1100,\"wires\":[[\"051e1f282076fed2\"]]},{\"id\":\"9d899fbb4d1648b3\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Update StaticArrayVariables Values @1 second\",\"props\":",[146,20558,20559],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"v\":\"Objects.StaticData.StaticArrayVariables.nodeId\",\"vt\":\"flow\"}",",\"repeat\":\"1\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":320,\"y\":1100,\"wires\":[[\"0625b0cf6f546a4a\"]]},{\"id\":\"6e1edc31687dde54\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Get BooleanArray Value\",\"rules\":",[146,20562,20477,20563,20480],{},[146,20564,677],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1050,\"y\":1100,\"wires\":[[\"d708e6264cec0070\"]]},{\"id\":\"051e1f282076fed2\",\"type\":\"switch\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"empty check\",\"property\":\"payload\",\"propertyType\":\"msg\",\"rules\":",[146,20567,20484],{},",\"checkall\":\"true\",\"repair\":false,\"outputs\":1,\"x\":830,\"y\":1100,\"wires\":[[\"6e1edc31687dde54\"]]},{\"id\":\"de2a1c3e380f743b\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Discard Empty Datasets\",\"info\":\"\",\"x\":820,\"y\":980,\"wires\":",[146,20570],{},"},{\"id\":\"c74606c48ccf5a40\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Parse StaticData Values\",\"info\":\"\",\"x\":1070,\"y\":980,\"wires\":",[146,20573],{},"},{\"id\":\"053bda13f2a2eabe\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"name\":\"Display on Dashboard\",\"info\":\"\",\"x\":1340,\"y\":980,\"wires\":",[146,20576],{},"},{\"id\":\"277dcf430dc86996\",\"type\":\"ui-text\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"group\":\"3d4f386e812e8b5f\",\"order\":0,\"width\":0,\"height\":0,\"name\":\"\",\"label\":\"ByteAnalogItemArray\",\"format\":\"",[17483,20579],{"value":382},"\",\"layout\":\"row-spread\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#717171\",\"className\":\"\",\"x\":1340,\"y\":1020,\"wires\":",[146,20582],{},"},{\"id\":\"d708e6264cec0070\",\"type\":\"ui-text\",\"z\":\"5b972161c4e0464e\",\"g\":\"5afdbddf71507886\",\"group\":\"3d4f386e812e8b5f\",\"order\":0,\"width\":0,\"height\":0,\"name\":\"\",\"label\":\"BooleanArray\",\"format\":\"",[17483,20585],{"value":382},"\",\"layout\":\"row-spread\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#717171\",\"className\":\"\",\"x\":1320,\"y\":1100,\"wires\":",[146,20588],{},"},{\"id\":\"3d4f386e812e8b5f\",\"type\":\"ui-group\",\"name\":\"StaticData Values\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"4\",\"height\":\"1\",\"order\":3,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"}",[15,20591,20592,20593,20595,20596,20598],{},"The values are derived from the ",[19,20594,20322],{}," values we stored in memory in our previous flow, via our ",[19,20597,1405],{}," nodes in the previous flow.",[15,20600,20601,20605,20606,20608],{},[392,20602],{"alt":20603,"dataZoomable":50,"src":20604},"flow-context-nodeid.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fflow-context-nodeid.png","\nAs stated earlier, you reference a OPC UA topic by its ",[19,20607,20322],{},".  So we will use these node IDs to read actual values from our OPC nodes.",[15,20610,20611,20612,20614,20615,20617,20618,20620,20621,20623,20624,20626],{},"In our first flow, we want to read the values in the ",[19,20613,20270],{}," folder at a 1 second interval.  So we use an ",[19,20616,1339],{}," node with a ",[19,20619,9425],{}," that references the ",[19,20622,20322],{}," corresponding to the ",[19,20625,20270],{}," folder.",[15,20628,20629,20633,20634,20636,20637,20639,20640,20642,20643,20645],{},[392,20630],{"alt":20631,"dataZoomable":50,"src":20632},"simulation-injection.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fsimulation-injection.png","\nThat ",[19,20635,9425],{}," tells the ",[19,20638,20352],{}," node what ",[19,20641,20322],{}," to browse.  If we look at the debug output of the browser ",[19,20644,382],{},", we can see that it produces an array of 7 objects, and an empty set array.",[15,20647,20648,20652,20653,20655],{},[392,20649],{"alt":20650,"dataZoomable":50,"src":20651},"simulation-debug.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fsimulation-debug.png","\nIf we allow that empty array to be passed, that means all values will be reset to 0 on each read.  So to prevent that from happening, we use a ",[19,20654,8722],{}," node to filter out the empty set.",[15,20657,20658,20662],{},[392,20659],{"alt":20660,"dataZoomable":50,"src":20661},"empty-check.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fempty-check.png","\nNow only non-empty payloads will be passed, preventing the values being reset to 0 on each read.",[15,20664,20665,20666,20668,20669,20671,20672,20674,20675,20678,20679,20626],{},"Now we can actually read the values.  To do this, we use a ",[19,20667,1405],{}," node again, referencing the non-empty payload and drilling down to the ",[19,20670,1587],{}," that corresponds to the ",[19,20673,8309],{}," of the node we want to read.  In this case, we’re getting the value of the node ",[19,20676,20677],{},"Counter"," located in the ",[19,20680,20270],{},[15,20682,20683,20687],{},[392,20684],{"alt":20685,"dataZoomable":50,"src":20686},"get-counter-value.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fget-counter-value.png","\nGoing back to our OPC Server, we can see that exactly where that value is derived below -",[15,20689,20690],{},[392,20691],{"alt":20692,"dataZoomable":50,"src":20693},"sim-counter-server.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fsim-counter-server.png",[15,20695,20696,20697,20700],{},"Now we add a ",[19,20698,20699],{},"gauge"," dashboard node to visualize the counter on the dashboard.  In the OPC Server, it is shown that the counter increments in a range of 0-30 in 1 count increments.",[15,20702,20703,20707],{},[392,20704],{"alt":20705,"dataZoomable":50,"src":20706},"counter-properties.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fcounter-propertie.png","\nNow that we’ve gone through the full process of reading an OPC UA value and displaying it on the dashboard, we can apply the same logic other values published by the OPC UA Server, which are repeated in the remaining parts of the flow.",[15,20709,20710],{},"The end result on the dashboard now looks like this -",[15,20712,20713],{},[392,20714],{"alt":20715,"dataZoomable":50,"src":20716},"opc-read-dashboard.gif","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopc-read-dashboard.gif",[34,20718,20720],{"id":20719},"write-opc-ua-values-to-server-using-opcua-item-and-opc-ua-client-nodes","Write OPC UA Values To Server Using OpcUa-Item and Opc-Ua-Client Nodes",[15,20722,20723],{},"The next flow writes OPC UA values to the server using dashboard UI elements.",[15,20725,20726,20730],{},[392,20727],{"alt":20728,"dataZoomable":50,"src":20729},"write-mydevice.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fwrite-mydevice.png","\nYou can import this flow into Node-RED using the code below:",[15,20732,6389,20733,3830],{},[146,20734,20735,20736,20739,20740,20742,20743,20745,20746,20748,20749,20751,20752,20754,20755,20207],{},"{\"id\":\"3de6c861611c3afa\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Write Mydevices values to OPC UA Server\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,20737,20738],{},"\"a66583d91b581cd8\",\"3e8cb6e199012155\",\"9fa33d1c9c621611\",\"fb7f57b4da5883ae\",\"9c5ff104eb9c8b10\",\"77bcb828bec95336\",\"afa83dbb46449d4a\",\"fa08f0ed04296363\",\"9f591797b56c565d\"",",\"x\":94,\"y\":1439,\"w\":792,\"h\":182},{\"id\":\"a66583d91b581cd8\",\"type\":\"OpcUa-Item\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"item\":\"ns=6;s=MySwitch\",\"datatype\":\"Boolean\",\"value\":\"\",\"name\":\"Toggle MySwitch\",\"x\":470,\"y\":1520,\"wires\":[[\"3e8cb6e199012155\"]]},{\"id\":\"3e8cb6e199012155\",\"type\":\"OpcUa-Client\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"endpoint\":\"53f4394dbf12c6b7\",\"action\":\"write\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"Write MySwitch\",\"x\":720,\"y\":1520,\"wires\":[[],",[146,20741],{},"]},{\"id\":\"9fa33d1c9c621611\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"name\":\"Dashboard Input\",\"info\":\"\",\"x\":200,\"y\":1480,\"wires\":",[146,20744],{},"},{\"id\":\"fb7f57b4da5883ae\",\"type\":\"OpcUa-Item\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"item\":\"ns=6;s=MyLevel\",\"datatype\":\"Double\",\"value\":\"\",\"name\":\"Modify MyLevel\",\"x\":460,\"y\":1580,\"wires\":[[\"9c5ff104eb9c8b10\"]]},{\"id\":\"9c5ff104eb9c8b10\",\"type\":\"OpcUa-Client\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"endpoint\":\"53f4394dbf12c6b7\",\"action\":\"write\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"Write MyLevel\",\"x\":720,\"y\":1580,\"wires\":[[],",[146,20747],{},"]},{\"id\":\"77bcb828bec95336\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"name\":\"Call OPC UA Item\",\"info\":\"\",\"x\":470,\"y\":1480,\"wires\":",[146,20750],{},"},{\"id\":\"afa83dbb46449d4a\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"name\":\"Write OPC UA Item to Client\",\"info\":\"\",\"x\":740,\"y\":1480,\"wires\":",[146,20753],{},"},{\"id\":\"fa08f0ed04296363\",\"type\":\"ui-switch\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"name\":\"\",\"label\":\"Toggle MySwitch\",\"group\":\"ec0ecb26fde8db3e\",\"order\":0,\"width\":0,\"height\":0,\"passthru\":false,\"topic\":\"topic\",\"topicType\":\"msg\",\"style\":\"\",\"className\":\"\",\"onvalue\":\"true\",\"onvalueType\":\"bool\",\"onicon\":\"\",\"oncolor\":\"\",\"offvalue\":\"false\",\"offvalueType\":\"bool\",\"officon\":\"\",\"offcolor\":\"\",\"x\":210,\"y\":1520,\"wires\":[[\"a66583d91b581cd8\"]]},{\"id\":\"9f591797b56c565d\",\"type\":\"ui-slider\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"group\":\"ec0ecb26fde8db3e\",\"name\":\"\",\"label\":\"Modify MyLevel\",\"tooltip\":\"\",\"order\":0,\"width\":0,\"height\":0,\"passthru\":false,\"outs\":\"all\",\"topic\":\"topic\",\"topicType\":\"msg\",\"thumbLabel\":true,\"min\":\"0\",\"max\":\"100\",\"step\":1,\"className\":\"\",\"x\":200,\"y\":1580,\"wires\":[[\"fb7f57b4da5883ae\"]]},{\"id\":\"53f4394dbf12c6b7\",\"type\":\"OpcUa-Endpoint\",\"endpoint\":\"opc.tcp:\u002F\u002F192.168.56.1:53530\u002FOPCUA\u002FSimulationServer\",\"secpol\":\"None\",\"secmode\":\"None\",\"none\":true,\"login\":false,\"usercert\":false,\"usercertificate\":\"\",\"userprivatekey\":\"\"},{\"id\":\"ec0ecb26fde8db3e\",\"type\":\"ui-group\",\"name\":\"MyDevice Status & Control\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"3\",\"height\":\"1\",\"order\":4,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"44d3feb2a1143d7b\",\"type\":\"ui-page\",\"name\":\"OPC UA\",\"ui\":\"5355e0c476f9da3b\",\"path\":\"\u002Fopcua\",\"icon\":\"home\",\"layout\":\"grid\",\"theme\":\"61eee6fc60281b9b\",\"order\":1,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"5355e0c476f9da3b\",\"type\":\"ui-base\",\"name\":\"My Dashboard\",\"path\":\"\u002Fdashboard\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,20756,6458],{},[15,20758,20759,20760,20763,20764,20767,20768,20770,20771,167],{},"We have two values to write, a boolean value corresponding to the node object ",[19,20761,20762],{},"MySwitch",", and an integer value corresponding to the object ",[19,20765,20766],{},"MyLevel",". Therefore, we will use a toggle switch to toggle the ",[19,20769,20762],{},", and a slider to modify ",[19,20772,20766],{},[15,20774,20775],{},"There’s no need to modify the toggle switch properties, other than giving it a name.  The slider needs to have the range modified to match the range of the level, which is 0-100%.",[15,20777,20778,20782,20783,20786,20787,20789],{},[392,20779],{"alt":20780,"dataZoomable":50,"src":20781},"level-range.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Flevel-range.png","\nFor the ",[19,20784,20785],{},"OpcUa-Item"," nodes, copy the ",[19,20788,20307],{}," corresponding to each device,",[15,20791,20792,20796,20797,20799],{},[392,20793],{"alt":20794,"dataZoomable":50,"src":20795},"copy-node-id.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fcopy-node-id.png","\nand paste it into ",[19,20798,20785],{}," node.  You must also ensure the data-type matches with the value you’re writing to.",[15,20801,20802,20806,20807,20810,20811,167],{},[392,20803],{"alt":20804,"dataZoomable":50,"src":20805},"opcua-item.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopcua-item.png","\nThe ",[19,20808,20809],{},"Opc-Ua-Client"," needs to have an endpoint and the action changed to ",[19,20812,20813],{},"WRITE",[15,20815,20816,20820,20821,63,20823,20825,20826,20828,20829,1361],{},[392,20817],{"alt":20818,"dataZoomable":50,"src":20819},"client-node.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fclient-node.png","\nThe process is the same for ",[19,20822,20762],{},[19,20824,20766],{},", the only difference being what ",[19,20827,20307],{}," is referenced in the  ",[19,20830,20785],{},[15,20832,20833],{},"When deployed, you can confirm values are being written to from the client to the server from the dashboard.",[15,20835,20836],{},[392,20837],{"alt":20838,"dataZoomable":50,"src":20839},"opc-write.gif","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopc-write.gif",[34,20841,20843],{"id":20842},"read-alarms-events-from-opc-ua-server-using-opcua-event-and-opc-ua-client-nodes","Read Alarms & Events from OPC UA Server Using OpcUa-Event and Opc-Ua-Client Nodes",[15,20845,20846],{},"Our last flow we’ll show how to read OPC UA Alarms & Events.",[15,20848,20849,20730],{},[392,20850],{"alt":20851,"dataZoomable":50,"src":20852},"opc-event-flow.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopc-event-flow.png",[15,20854,6389,20855,3830],{},[146,20856,20857,20858,20861,20862,20864,20865,20867,20868,20870,20871,20874,20875,20878,20879,20882,20883,20885,20886,20888,20889,20891,20892,20894,20895,20897,20898,20900,20901,20903,20904,20906,20907,20909,20910,20912],{},"{\"id\":\"a6e9abacd0bdf3b6\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Read Alarms & Events From OPC UA Server\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,20859,20860],{},"\"90fb4ca64a642edf\",\"b76f64786bc681c3\",\"71e24b671bc03fb8\",\"c7438df35b506470\",\"c7e8919b636cb51d\",\"5952b86dae22b056\",\"04992b24a3836f19\",\"325068cb935cd6d1\",\"5b4d1bd8b342fc05\",\"ba1ea89438335cb8\",\"d662d662c5ccb9c1\",\"1e3956200997581f\",\"0b8ac86e5e4f9f8d\",\"62b2e14ce0429eef\"",",\"x\":94,\"y\":1679,\"w\":1352,\"h\":282},{\"id\":\"90fb4ca64a642edf\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Call OPC UA Item\",\"info\":\"\",\"x\":470,\"y\":1820,\"wires\":",[146,20863],{},"},{\"id\":\"b76f64786bc681c3\",\"type\":\"OpcUa-Event\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"root\":\"ns=6;s=MyLevel.Alarm\",\"activatecustomevent\":false,\"eventtype\":\"i=2041\",\"customeventtype\":\"\",\"name\":\"MyLevel Alarms\",\"x\":500,\"y\":1860,\"wires\":[[\"c7438df35b506470\"]]},{\"id\":\"71e24b671bc03fb8\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Trigger Alarm Event Capture\",\"props\":",[146,20866,3779],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":260,\"y\":1860,\"wires\":[[\"b76f64786bc681c3\"]]},{\"id\":\"c7438df35b506470\",\"type\":\"OpcUa-Client\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"endpoint\":\"53f4394dbf12c6b7\",\"action\":\"events\",\"deadbandtype\":\"a\",\"deadbandvalue\":1,\"time\":10,\"timeUnit\":\"s\",\"certificate\":\"n\",\"localfile\":\"\",\"localkeyfile\":\"\",\"securitymode\":\"None\",\"securitypolicy\":\"None\",\"useTransport\":false,\"maxChunkCount\":1,\"maxMessageSize\":8192,\"receiveBufferSize\":8192,\"sendBufferSize\":8192,\"name\":\"Get MyLevel Events\",\"x\":720,\"y\":1860,\"wires\":[[\"c7e8919b636cb51d\",\"5952b86dae22b056\",\"04992b24a3836f19\"],",[146,20869],{},"]},{\"id\":\"c7e8919b636cb51d\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Event Text\",\"rules\":",[146,20872,20873],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.Message.text\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":990,\"y\":1800,\"wires\":[[\"d662d662c5ccb9c1\",\"1e3956200997581f\"]]},{\"id\":\"5952b86dae22b056\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Event Time\",\"rules\":",[146,20876,20877],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.Time\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":990,\"y\":1860,\"wires\":[[\"0b8ac86e5e4f9f8d\"]]},{\"id\":\"04992b24a3836f19\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Event Severity\",\"rules\":",[146,20880,20881],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"payload.Severity\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1000,\"y\":1920,\"wires\":[[\"62b2e14ce0429eef\"]]},{\"id\":\"325068cb935cd6d1\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Parse Event Dataset\",\"info\":\"\",\"x\":990,\"y\":1760,\"wires\":",[146,20884],{},"},{\"id\":\"5b4d1bd8b342fc05\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Get OPC Events from Client\",\"info\":\"\",\"x\":720,\"y\":1820,\"wires\":",[146,20887],{},"},{\"id\":\"ba1ea89438335cb8\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"name\":\"Display Events on Dashboard\",\"info\":\"\",\"x\":1240,\"y\":1720,\"wires\":",[146,20890],{},"},{\"id\":\"d662d662c5ccb9c1\",\"type\":\"ui-notification\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"ui\":\"5355e0c476f9da3b\",\"position\":\"center center\",\"colorDefault\":true,\"color\":\"#000000\",\"displayTime\":\"3\",\"showCountdown\":true,\"outputs\":1,\"allowDismiss\":true,\"dismissText\":\"Close\",\"raw\":false,\"className\":\"\",\"name\":\"Event Notification\",\"x\":1230,\"y\":1800,\"wires\":[[]]},{\"id\":\"1e3956200997581f\",\"type\":\"ui-text\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"group\":\"ec0ecb26fde8db3e\",\"order\":0,\"width\":0,\"height\":0,\"name\":\"\",\"label\":\"Latest MyLevel Event\",\"format\":\"",[17483,20893],{"value":382},"\",\"layout\":\"row-spread\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#717171\",\"className\":\"\",\"x\":1240,\"y\":1840,\"wires\":",[146,20896],{},"},{\"id\":\"0b8ac86e5e4f9f8d\",\"type\":\"ui-text\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"group\":\"ec0ecb26fde8db3e\",\"order\":0,\"width\":0,\"height\":0,\"name\":\"\",\"label\":\"Latest MyLevel Event Timestamp\",\"format\":\"",[17483,20899],{"value":382},"\",\"layout\":\"row-spread\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#717171\",\"className\":\"\",\"x\":1280,\"y\":1880,\"wires\":",[146,20902],{},"},{\"id\":\"62b2e14ce0429eef\",\"type\":\"ui-text\",\"z\":\"5b972161c4e0464e\",\"g\":\"a6e9abacd0bdf3b6\",\"group\":\"ec0ecb26fde8db3e\",\"order\":0,\"width\":0,\"height\":0,\"name\":\"\",\"label\":\"Latest MyLevel Event Severity\",\"format\":\"",[17483,20905],{"value":382},"\",\"layout\":\"row-spread\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#717171\",\"className\":\"\",\"x\":1270,\"y\":1920,\"wires\":",[146,20908],{},"},{\"id\":\"53f4394dbf12c6b7\",\"type\":\"OpcUa-Endpoint\",\"endpoint\":\"opc.tcp:\u002F\u002F192.168.56.1:53530\u002FOPCUA\u002FSimulationServer\",\"secpol\":\"None\",\"secmode\":\"None\",\"none\":true,\"login\":false,\"usercert\":false,\"usercertificate\":\"\",\"userprivatekey\":\"\"},{\"id\":\"5355e0c476f9da3b\",\"type\":\"ui-base\",\"name\":\"My Dashboard\",\"path\":\"\u002Fdashboard\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,20911,6458],{},",\"showPathInSidebar\":false,\"navigationStyle\":\"default\"},{\"id\":\"ec0ecb26fde8db3e\",\"type\":\"ui-group\",\"name\":\"MyDevice Status & Control\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"3\",\"height\":\"1\",\"order\":4,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"44d3feb2a1143d7b\",\"type\":\"ui-page\",\"name\":\"OPC UA\",\"ui\":\"5355e0c476f9da3b\",\"path\":\"\u002Fopcua\",\"icon\":\"home\",\"layout\":\"grid\",\"theme\":\"61eee6fc60281b9b\",\"order\":1,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"61eee6fc60281b9b\",\"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\"}}",[15,20914,20915,20916,20919,20920,20922,20923,20926],{},"We use an inject node to trigger the ",[19,20917,20918],{},"OpcUa-Event"," node.  In the properties of the event node, we get the ",[19,20921,20307],{}," from the ",[19,20924,20925],{},"MyLevelAlarm"," event from the OPC Server -",[15,20928,20929,20933,20934,20936,20937,20939,20940,167],{},[392,20930],{"alt":20931,"dataZoomable":50,"src":20932},"mylevel-event.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fmylevel-event.png","\nAnd copy that ",[19,20935,20307],{}," into the ",[19,20938,20918],{}," node.  Event type will be ",[19,20941,20942],{},"BaseEvent (all)",[15,20944,20945,20949,20950,20952,20953,764,20955,167],{},[392,20946],{"alt":20947,"dataZoomable":50,"src":20948},"event-node-properties.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fevent-node-properties.png","\nIn the ",[19,20951,20809],{}," node, we set the ",[19,20954,12202],{},[19,20956,20957],{},"EVENTS",[15,20959,20960,20964],{},[392,20961],{"alt":20962,"dataZoomable":50,"src":20963},"client-events.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fclient-events.png","\nIf we stick a debug node on the output of the client event, we can see how the OPC Server annunciates events.",[15,20966,20967,20971,20972,20974,20975,20978],{},[392,20968],{"alt":20969,"dataZoomable":50,"src":20970},"event-debug.png","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fevent-debug.png","\nEvery time ",[19,20973,20766],{}," exceeds certain thresholds (10%, 30%, 70% and 90%) it will flag a ",[19,20976,20977],{},"Level Exceeded"," alarm.   The event is timestamped and assigned a severity level, which we will record and put onto the dashboard.",[15,20980,20981],{},"To make things simple, we’ll only track the last event.  But in a production system, you’d likely want to store these events in a relational database (historian) to keep an alarm history.  We’ll also include a notification pop-up when an alarm occurs to notify someone monitoring the dashboard a new alarm has occurred.",[15,20983,20984],{},"Adding alarms and events to our dashboard creates the following result -",[15,20986,20987],{},[392,20988],{"alt":20989,"dataZoomable":50,"src":20990},"opc-event.gif","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopc-event.gif",[34,20992,20994],{"id":20993},"using-flowfuse-to-enhance-your-node-red-application-security-scalability-and-robustness","Using FlowFuse to Enhance Your Node-RED Application: Security, Scalability, and Robustness",[15,20996,20997],{},"So, you've successfully built your Node-RED application, congratulations! But now, how do you ensure its security, scalability, and ease of collaboration? What if you want to invite your team to work on the application simultaneously or access it remotely?",[15,20999,21000,21001,21003],{},"Enter ",[307,21002,1155],{"href":213},", 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.",[15,21005,21006,21007,21010,21011,21015],{},"If you're interested in learning how to use Node-RED for professional use cases, check out our eBook: ",[307,21008,21009],{"href":13573},"Ultimate Beginner's Guide to Professionals",". For additional resources, visit our ",[307,21012,21014],{"href":21013},"\u002Fnode-red\u002Fcore-nodes\u002F","Node-RED Learning Resources section",", where you can explore integrations with different protocols, messaging services, databases, hardware, and much more.",[34,21017,6518],{"id":6517},[15,21019,21020],{},"In this final article, we went over building a OPC UA Client dashboard that can browse the address space, read values from an OPC Server, write values to an OPC Server, and get events from an OPC Server.",[15,21022,21023],{},"This flow provides examples that can serve as a foundation for an interactive OPC Client application built in Node-RED.  This now concludes the OPC UA Series.",[15,21025,21026],{},"full source code for this project -",[15,21028,6389,21029,3830],{},[146,21030,20073,21031,20077,21033,20080,21035,20083,21037,20093,21043,20096,21045,20105,21051,20113,21057,20116,21059,20120,21061,20128,21067,20137,21073,20145,21079,20149,21081,20152,21083,20155,21085,20158,21087,20169,21093,20178,21099,20188,21105,20194,21109,20199,21113,20204,21117,20460,21119,20464,21121,20467,21123,20471,21125,20474,21127,20481,21131,20485,21133,20490,21137,20495,21141,20500,21145,20503,21147,20506,21149,20509,21151,20513,21153,20516,21155,20519,21157,20522,21159,20526,21161,20529,21163,20533,21165,20536,21167,20540,21169,20543,21171,20550,21175,20553,21177,20556,21179,20560,21181,20565,21185,20568,21187,20571,21189,20574,21191,20577,21193,20580,21195,20583,21197,20586,21199,21201,21202,21205,21206,21208,21209,21212,21213,21215,21216,21220,21221,21223,21224,21228,21229,21231,21232,21234,21235,21237,21238,21241,21242,21244,21245,21248,21249,21251,21252,20739,21254,20742,21256,20745,21258,20748,21260,20751,21262,21264,21265,20861,21267,20864,21269,20867,21271,20870,21273,20874,21275,20878,21277,20882,21279,20885,21281,20888,21283,20891,21285,20894,21287,20897,21289,20900,21291,20903,21293,20906,21295,1596],{},[146,21032,20076],{},[146,21034],{},[146,21036,3779],{},[146,21038,20086,21039,20089,21041,20092],{},[146,21040,6820],{},[146,21042,6820],{},[146,21044],{},[146,21046,20099,21047,20089,21049,20092],{},[146,21048,20102],{},[146,21050,20102],{},[146,21052,20108,21053,20089,21055,20092],{},[146,21054,207],{},[146,21056,207],{},[146,21058],{},[146,21060,20119],{},[146,21062,20123,21063,20089,21065,20092],{},[146,21064,62],{},[146,21066,62],{},[146,21068,20131,21069,20089,21071,20092],{},[146,21070,20134],{},[146,21072,20134],{},[146,21074,20140,21075,20089,21077,20092],{},[146,21076,677],{},[146,21078,677],{},[146,21080,20148],{},[146,21082],{},[146,21084],{},[146,21086],{},[9982,21088,20161,21089,20165,21091,20168],{},[15,21090,20164],{},[17483,21092],{"value":382},[9982,21094,20161,21095,20175,21097,20168],{},[15,21096,20174],{},[17483,21098],{"value":382},[9982,21100,20181,21101,20185,21103,20168],{},[15,21102,20184],{},[17483,21104],{"value":382},[9982,21106,20191,21107,20168],{},[17483,21108],{"value":382},[9982,21110,20191,21111,20168],{},[17483,21112],{"value":382},[9982,21114,20191,21115,20168],{},[17483,21116],{"value":382},[146,21118,6458],{},[146,21120,20463],{},[146,21122],{},[146,21124,20470],{},[146,21126],{},[146,21128,20477,21129,20480],{},[146,21130,62],{},[146,21132,20484],{},[146,21134,20477,21135,20480],{},[146,21136,6820],{},[146,21138,20477,21139,20480],{},[146,21140,207],{},[146,21142,20477,21143,20480],{},[146,21144,20102],{},[146,21146],{},[146,21148],{},[146,21150],{},[146,21152,20512],{},[146,21154],{},[17483,21156],{"value":382},[146,21158],{},[146,21160,20525],{},[146,21162,20525],{},[146,21164,20532],{},[146,21166],{},[146,21168,20539],{},[146,21170],{},[146,21172,20546,21173,20549],{},[146,21174,677],{},[146,21176,20484],{},[146,21178],{},[146,21180,20559],{},[146,21182,20477,21183,20480],{},[146,21184,677],{},[146,21186,20484],{},[146,21188],{},[146,21190],{},[146,21192],{},[17483,21194],{"value":382},[146,21196],{},[17483,21198],{"value":382},[146,21200],{},"},{\"id\":\"3d4f386e812e8b5f\",\"type\":\"ui-group\",\"name\":\"StaticData Values\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"4\",\"height\":\"1\",\"order\":3,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"25f95391088d4a08\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Read MyDevice Values & Display on Dashboard\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,21203,21204],{},"\"bfe4274963a84e2e\",\"0662c62c7f0cfac0\",\"249c223139c5779e\",\"efb293f13af17dc1\",\"507d2c11c7586957\",\"3b7695a52a1bf6e0\",\"594ec38acadc9673\",\"d4e2915ba92db8a6\",\"85619f0eab615ac7\",\"cd4941a8db3edcb6\",\"ad91d2ca81697fc2\"",",\"x\":94,\"y\":1179,\"w\":1292,\"h\":182},{\"id\":\"bfe4274963a84e2e\",\"type\":\"OpcUa-Browser\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"endpoint\":\"53f4394dbf12c6b7\",\"item\":\"\",\"datatype\":\"\",\"topic\":\"\",\"items\":",[146,21207],{},",\"name\":\"OPC Client Namespace Browse\",\"x\":590,\"y\":1280,\"wires\":[[\"507d2c11c7586957\"]]},{\"id\":\"0662c62c7f0cfac0\",\"type\":\"inject\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Read MyDevice Values @ 1 second\",\"props\":",[146,21210,21211],{},"{\"p\":\"payload\"},{\"p\":\"topic\",\"v\":\"Objects.MyObjects.MyDevice.nodeId\",\"vt\":\"flow\"}",",\"repeat\":\"1\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":290,\"y\":1280,\"wires\":[[\"bfe4274963a84e2e\"]]},{\"id\":\"249c223139c5779e\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Read MyDevice\",\"info\":\"\",\"x\":480,\"y\":1240,\"wires\":",[146,21214],{},"},{\"id\":\"efb293f13af17dc1\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Get MyLevel Value\",\"rules\":",[146,21217,20477,21218,20480],{},[146,21219,677],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1030,\"y\":1260,\"wires\":[[\"cd4941a8db3edcb6\"]]},{\"id\":\"507d2c11c7586957\",\"type\":\"switch\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"empty check\",\"property\":\"payload\",\"propertyType\":\"msg\",\"rules\":",[146,21222,20484],{},",\"checkall\":\"true\",\"repair\":false,\"outputs\":1,\"x\":810,\"y\":1280,\"wires\":[[\"efb293f13af17dc1\",\"3b7695a52a1bf6e0\"]]},{\"id\":\"3b7695a52a1bf6e0\",\"type\":\"change\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Get MySwitch Value\",\"rules\":",[146,21225,20477,21226,20480],{},[146,21227,20102],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1040,\"y\":1320,\"wires\":[[\"ad91d2ca81697fc2\"]]},{\"id\":\"594ec38acadc9673\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Display on Dashboard\",\"info\":\"\",\"x\":1260,\"y\":1220,\"wires\":",[146,21230],{},"},{\"id\":\"d4e2915ba92db8a6\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Parse MyDevice Values\",\"info\":\"\",\"x\":1040,\"y\":1220,\"wires\":",[146,21233],{},"},{\"id\":\"85619f0eab615ac7\",\"type\":\"comment\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Discard Empty Datasets\",\"info\":\"\",\"x\":800,\"y\":1240,\"wires\":",[146,21236],{},"},{\"id\":\"cd4941a8db3edcb6\",\"type\":\"ui-gauge\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"Level\",\"group\":\"ec0ecb26fde8db3e\",\"order\":0,\"width\":3,\"height\":3,\"gtype\":\"gauge-half\",\"gstyle\":\"needle\",\"title\":\"Level\",\"units\":\"%\",\"icon\":\"\",\"prefix\":\"\",\"suffix\":\"\",\"segments\":",[146,21239,21240],{},"{\"from\":\"0\",\"color\":\"#0094ce\"},{\"from\":\"25\",\"color\":\"#0094ce\"},{\"from\":\"50\",\"color\":\"#0094ce\"},{\"from\":\"100\",\"color\":\"#0094ce\"}",",\"min\":0,\"max\":\"100\",\"sizeThickness\":16,\"sizeGap\":4,\"sizeKeyThickness\":8,\"styleRounded\":true,\"styleGlow\":false,\"className\":\"\",\"x\":1250,\"y\":1260,\"wires\":",[146,21243],{},"},{\"id\":\"ad91d2ca81697fc2\",\"type\":\"ui-led\",\"z\":\"5b972161c4e0464e\",\"g\":\"25f95391088d4a08\",\"name\":\"\",\"group\":\"ec0ecb26fde8db3e\",\"order\":-1,\"width\":0,\"height\":0,\"label\":\"Switch\",\"labelPlacement\":\"left\",\"labelAlignment\":\"flex-start\",\"states\":",[146,21246,21247],{},"{\"value\":\"false\",\"valueType\":\"bool\",\"color\":\"#ff0000\"},{\"value\":\"true\",\"valueType\":\"bool\",\"color\":\"#00ff00\"}",",\"allowColorForValueInMessage\":false,\"shape\":\"circle\",\"showBorder\":true,\"showGlow\":true,\"x\":1250,\"y\":1320,\"wires\":",[146,21250],{},"},{\"id\":\"ec0ecb26fde8db3e\",\"type\":\"ui-group\",\"name\":\"MyDevice Status & Control\",\"page\":\"44d3feb2a1143d7b\",\"width\":\"3\",\"height\":\"1\",\"order\":4,\"showTitle\":true,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"3de6c861611c3afa\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Write Mydevices values to OPC UA Server\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,21253,20738],{},[146,21255],{},[146,21257],{},[146,21259],{},[146,21261],{},[146,21263],{},"},{\"id\":\"fa08f0ed04296363\",\"type\":\"ui-switch\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"name\":\"\",\"label\":\"Toggle MySwitch\",\"group\":\"ec0ecb26fde8db3e\",\"order\":0,\"width\":0,\"height\":0,\"passthru\":false,\"topic\":\"topic\",\"topicType\":\"msg\",\"style\":\"\",\"className\":\"\",\"onvalue\":\"true\",\"onvalueType\":\"bool\",\"onicon\":\"\",\"oncolor\":\"\",\"offvalue\":\"false\",\"offvalueType\":\"bool\",\"officon\":\"\",\"offcolor\":\"\",\"x\":210,\"y\":1520,\"wires\":[[\"a66583d91b581cd8\"]]},{\"id\":\"9f591797b56c565d\",\"type\":\"ui-slider\",\"z\":\"5b972161c4e0464e\",\"g\":\"3de6c861611c3afa\",\"group\":\"ec0ecb26fde8db3e\",\"name\":\"\",\"label\":\"Modify MyLevel\",\"tooltip\":\"\",\"order\":0,\"width\":0,\"height\":0,\"passthru\":false,\"outs\":\"all\",\"topic\":\"topic\",\"topicType\":\"msg\",\"thumbLabel\":true,\"min\":\"0\",\"max\":\"100\",\"step\":1,\"className\":\"\",\"x\":200,\"y\":1580,\"wires\":[[\"fb7f57b4da5883ae\"]]},{\"id\":\"a6e9abacd0bdf3b6\",\"type\":\"group\",\"z\":\"5b972161c4e0464e\",\"name\":\"Read Alarms & Events From OPC UA Server\",\"style\":{\"label\":true,\"color\":\"#000000\"},\"nodes\":",[146,21266,20860],{},[146,21268],{},[146,21270,3779],{},[146,21272],{},[146,21274,20873],{},[146,21276,20877],{},[146,21278,20881],{},[146,21280],{},[146,21282],{},[146,21284],{},[17483,21286],{"value":382},[146,21288],{},[17483,21290],{"value":382},[146,21292],{},[17483,21294],{"value":382},[146,21296],{},{"title":50,"searchDepth":250,"depth":250,"links":21298},[21299,21300,21301,21302,21303,21304,21305,21306,21307],{"id":19939,"depth":185,"text":19940},{"id":19978,"depth":185,"text":19979},{"id":20031,"depth":185,"text":20032},{"id":20055,"depth":185,"text":20056},{"id":20356,"depth":185,"text":20357},{"id":20719,"depth":185,"text":20720},{"id":20842,"depth":185,"text":20843},{"id":20993,"depth":185,"text":20994},{"id":6517,"depth":185,"text":6518},{"type":941,"title":21309,"description":21310},"Deploy Your OPC UA Dashboard with FlowFuse","Sign up for FlowFuse to securely deploy, share, and scale your Node-RED OPC UA client dashboard applications with team collaboration and DevOps pipelines.","2023-07-27","Building a Dashboard-Driven OPC UA Client to Browse, Read, Write, and Get Events from a 3rd party OPC UA Server","blog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-3\u002Fopc-ua-3-title-image.png",{"keywords":21315,"excerpt":21316},"opc ua client node-red, opc ua dashboard, node-red opcua client, opc ua browser node-red, read write opc ua node-red, opc ua alarms events, prosys simulation server",{"type":12,"value":21317},[21318],[15,21319,19917,21320,19921,21322,19925],{},[307,21321,19920],{"href":9792},[307,21323,19924],{"href":9785},"\u002Fblog\u002F2023\u002F07\u002Fhow-to-build-a-opc-client-dashboard-in-node-red",{"title":19910,"description":21312},{"loc":21324},"blog\u002F2023\u002F07\u002Fhow-to-build-a-opc-client-dashboard-in-node-red","Interactive OPC UA Client dashboard that communicates with a 3rd party OPC UA Server",[9832,965,6563,2055,966],"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.","iTvLAoss4hTIIwo4x9-95lGhpygpJvE5KrP3OBAPuPY",{"id":21333,"title":21334,"authors":21335,"body":21336,"cta":22188,"date":22191,"description":22192,"extension":946,"image":22193,"lastUpdated":948,"meta":22194,"navigation":253,"path":22200,"seo":22201,"sitemap":22202,"stem":22203,"subtitle":22204,"tags":22205,"tldr":22206,"video":3,"__hash__":22207},"blog\u002Fblog\u002F2023\u002F07\u002Fimages-in-node-red-dashboards.md","How to add images to Node-RED dashboards when using FlowFuse (2026)",[7451],{"type":12,"value":21337,"toc":22165},[21338,21341,21344,21355,21357,21360,21387,21391,21399,21403,21408,21420,21424,21436,21440,21451,21455,21464,21468,21488,21492,21523,21527,21714,21718,21748,21786,21789,21793,21796,21799,21803,21806,21810,21813,21824,21827,21830,21834,21837,21879,21883,21891,21895,21898,21982,21985,21988,21994,21996,22138,22141,22147,22150,22154,22157,22159,22162],[15,21339,21340],{},"Using images in your Node-RED dashboards can significantly improve your users' experience. The most common method to add images to dashboards is to store them within the filesystem of an Node-RED instance but sometimes that's not an option. How can you easily use images when working in a containerized environment such as Docker, or Kubernetes? We will also explore latest feature from FlowFuse that makes this step super easy.",[15,21342,21343],{},"When designing a dashboard, images allow you to significantly enrich your content. Some examples include:",[100,21345,21346,21349,21352],{},[103,21347,21348],{},"displaying maps to guide engineers to a problem which needs resolving.",[103,21350,21351],{},"displaying pictures of specific hardware on a factory-floor which needs to be checked.",[103,21353,21354],{},"displaying physical tools which should be used to resolve a problem.",[996,21356,1163],{"id":1162},[15,21358,21359],{},"Before we begin, ensure you have the following custom nodes installed:",[100,21361,21362,21371,21379],{},[103,21363,21364,21367,21368,167],{},[307,21365,2153],{"href":19954,"rel":21366},[311]," - A set of dashboard nodes for Node-RED. We will use this dashboard to demonstrate how to quickly display images using static assets. If you're a beginner and want to dive deeper, refer to ",[307,21369,21370],{"href":6792},"Getting started with FlowFuse Dashboarad",[103,21372,21373,21378],{},[307,21374,21377],{"href":21375,"rel":21376},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-string",[311],"node-red-contrib-string"," - A string manipulation node based on the lightweight stringjs library.",[103,21380,21381,21386],{},[307,21382,21385],{"href":21383,"rel":21384},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-node-base64",[311],"node-red-node-base64"," - A Node-RED node to encode and decode data to and from base64.",[34,21388,21390],{"id":21389},"easily-add-images-to-node-red-dashboards-with-flowfuses-static-asset-service","Easily Add Images to Node-RED Dashboards with FlowFuse’s Static Asset Service",[15,21392,21393,21398],{},[307,21394,21397],{"href":21395,"rel":21396},"https:\u002F\u002Fflowforge.com\u002Fdocs\u002Fuser\u002Fstatic-asset-service\u002F",[311],"FlowFuse's static assets"," service provides a simple way to manage images and other assets in Node-RED. Follow these steps to quickly add images to your Node-RED dashboard.",[996,21400,21402],{"id":21401},"steps-to-add-images-using-the-static-asset-service","Steps to Add Images Using the Static Asset Service:",[21404,21405,21407],"h5",{"id":21406},"_1-access-the-static-assets-service","1. Access the Static Assets Service",[100,21409,21410],{},[103,21411,21412,21413,21416,21417,8670],{},"Log into your FlowFuse account, navigate to your ",[338,21414,21415],{},"Node-RED instance",", and click on the ",[338,21418,21419],{},"Static Assets Service",[21404,21421,21423],{"id":21422},"_2-create-a-new-folder-optional","2. Create a New Folder (Optional)",[100,21425,21426],{},[103,21427,492,21428,21431,21432,21435],{},[338,21429,21430],{},"New Folder"," button to create a folder that will help you organize your assets. Provide a folder name and ",[338,21433,21434],{},"confirm"," the creation.",[21404,21437,21439],{"id":21438},"_3-upload-your-image","3. Upload Your Image",[100,21441,21442],{},[103,21443,21444,21445,21448,21449,167],{},"Enter the folder (or skip this step if not using folders), click ",[338,21446,21447],{},"Upload",", select your image file, and ",[338,21450,21434],{},[21404,21452,21454],{"id":21453},"_4-copy-the-image-path","4. Copy the Image Path",[100,21456,21457],{},[103,21458,21459,21460,21463],{},"Once uploaded, click the ",[338,21461,21462],{},"copy icon"," next to the image to get the path for use in Node-RED.",[21404,21465,21467],{"id":21466},"_5-set-up-the-image-flow-in-node-red","5. Set Up the Image Flow in Node-RED",[100,21469,21470,21473,21478],{},[103,21471,21472],{},"Open the Node-RED editor for the relevant instance.",[103,21474,369,21475,21477],{},[19,21476,1339],{}," node onto the canvas and set it to trigger immediately when the flow is deployed.",[103,21479,408,21480,21483,21484,21487],{},[19,21481,21482],{},"read file"," node, paste the copied file path in the ",[338,21485,21486],{},"Filename"," field, and set the output to Single buffer object.",[21404,21489,21491],{"id":21490},"_6-prepare-the-image-for-display","6. Prepare the Image for Display",[100,21493,21494,21512],{},[103,21495,1349,21496,21499,21500,16586,21503,21506,21507,764,21509,167],{},[19,21497,21498],{},"string"," node to convert the buffer to a base64 string. Set ",[338,21501,21502],{},"From",[19,21504,21505],{},"msg.filename"," and adjust the ",[338,21508,7952],{},[19,21510,21511],{},"getmost",[103,21513,1349,21514,21516,21517,21519],{},[19,21515,1405],{}," node and configure it to add the elements shown in the following image:",[1290,21518],{},[392,21520],{"alt":21521,"src":21522,"title":21521},"The change node showing added elements","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fchange-node.png",[21404,21524,21526],{"id":21525},"_7-display-the-image-in-the-dashboard","7. Display the Image in the Dashboard",[100,21528,21529,21533],{},[103,21530,408,21531,1340],{},[19,21532,5152],{},[103,21534,21535,21536,10218,21538,21541,21542,21545,21546,21548,21549],{},"Add the code in ",[19,21537,5152],{},[19,21539,21540],{},"\u003Cimg>"," tag, configuring the ",[19,21543,21544],{},"src"," attribute with ",[19,21547,382],{},", as shown in the following code. Alternatively, you can use the following code directly if you want to display the image in the top-left corner of your dashboard header:",[42,21550,21552],{"className":140,"code":21551,"language":142,"meta":50,"style":50},"\u003Ctemplate>\n    \u003CTeleport v-if=\"mounted\" to=\"#app-bar-title\">\n        \u003Cimg :src=\"msg.payload\" style=\"height: 32px;\" \u002F>\n    \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript>\n    export default {\n        data() {\n            return {\n                mounted: false\n            }\n        },\n        mounted() {\n            this.mounted = true;\n        }\n    }\n\u003C\u002Fscript>\n",[19,21553,21554,21562,21590,21612,21620,21628,21632,21640,21647,21656,21663,21671,21675,21679,21687,21698,21702,21706],{"__ignoreMap":50},[146,21555,21556,21558,21560],{"class":148,"line":149},[146,21557,5165],{"class":160},[146,21559,9982],{"class":1549},[146,21561,5183],{"class":160},[146,21563,21564,21566,21568,21570,21572,21574,21576,21578,21580,21582,21584,21586,21588],{"class":148,"line":185},[146,21565,5188],{"class":160},[146,21567,9996],{"class":2435},[146,21569,9999],{"class":152},[146,21571,161],{"class":160},[146,21573,727],{"class":160},[146,21575,10006],{"class":1554},[146,21577,727],{"class":160},[146,21579,10011],{"class":152},[146,21581,161],{"class":160},[146,21583,727],{"class":160},[146,21585,9875],{"class":1554},[146,21587,727],{"class":160},[146,21589,5183],{"class":160},[146,21591,21592,21594,21596,21599,21601,21603,21605,21608,21610],{"class":148,"line":221},[146,21593,10026],{"class":160},[146,21595,392],{"class":1549},[146,21597,21598],{"class":160}," :src=\"msg.payload\" ",[146,21600,924],{"class":152},[146,21602,161],{"class":160},[146,21604,727],{"class":160},[146,21606,21607],{"class":1554},"height: 32px;",[146,21609,727],{"class":160},[146,21611,10467],{"class":160},[146,21613,21614,21616,21618],{"class":148,"line":250},[146,21615,5238],{"class":160},[146,21617,9996],{"class":2435},[146,21619,5183],{"class":160},[146,21621,21622,21624,21626],{"class":148,"line":257},[146,21623,5247],{"class":160},[146,21625,9982],{"class":1549},[146,21627,5183],{"class":160},[146,21629,21630],{"class":148,"line":284},[146,21631,254],{"emptyLinePlaceholder":253},[146,21633,21634,21636,21638],{"class":148,"line":666},[146,21635,5165],{"class":160},[146,21637,10102],{"class":1549},[146,21639,5183],{"class":160},[146,21641,21642,21645],{"class":148,"line":1603},[146,21643,21644],{"class":156},"    export default ",[146,21646,717],{"class":160},[146,21648,21649,21651,21654],{"class":148,"line":1608},[146,21650,10119],{"class":170},[146,21652,21653],{"class":156},"() ",[146,21655,717],{"class":160},[146,21657,21658,21661],{"class":148,"line":1624},[146,21659,21660],{"class":156},"            return ",[146,21662,717],{"class":160},[146,21664,21665,21667,21669],{"class":148,"line":2546},[146,21666,10135],{"class":2435},[146,21668,730],{"class":160},[146,21670,10140],{"class":4671},[146,21672,21673],{"class":148,"line":2564},[146,21674,4450],{"class":160},[146,21676,21677],{"class":148,"line":2569},[146,21678,4203],{"class":160},[146,21680,21681,21683,21685],{"class":148,"line":2574},[146,21682,10153],{"class":170},[146,21684,21653],{"class":156},[146,21686,717],{"class":160},[146,21688,21689,21692,21694,21696],{"class":148,"line":2594},[146,21690,21691],{"class":156},"            this.mounted ",[146,21693,161],{"class":160},[146,21695,1866],{"class":4671},[146,21697,182],{"class":156},[146,21699,21700],{"class":148,"line":2614},[146,21701,4618],{"class":160},[146,21703,21704],{"class":148,"line":2633},[146,21705,3412],{"class":160},[146,21707,21708,21710,21712],{"class":148,"line":2662},[146,21709,5247],{"class":160},[146,21711,10102],{"class":1549},[146,21713,5183],{"class":160},[21404,21715,21717],{"id":21716},"_8-connect-the-nodes","8. Connect the Nodes",[100,21719,21720],{},[103,21721,21722,21723,21726,21727,1358,21729,21726,21732,21734,21735,21738,21739,4963,21741,1361,21743,21745],{},"Finally connect the nodes in the following order:\nthe ",[338,21724,21725],{},"output"," of the ",[19,21728,1339],{},[338,21730,21731],{},"input",[19,21733,21482],{}," node, then link to the ",[19,21736,21737],{},"string node",", followed by the ",[19,21740,1405],{},[19,21742,5152],{},[1290,21744],{},[19,21746,21747],{},"inject → read file → string → change → ui-template",[15,21749,6389,21750,3830],{},[146,21751,21752,21753,21756,21757,21763,21764,21767,21768,21770,21771,21774,21775,21781,21782,21785],{},"{\"id\":\"e50f7c57189d62f8\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"style\":{\"stroke\":\"#b2b3bd\",\"stroke-opacity\":\"1\",\"fill\":\"#f2f3fb\",\"fill-opacity\":\"0.5\",\"label\":true,\"label-position\":\"nw\",\"color\":\"#32333b\"},\"nodes\":",[146,21754,21755],{},"\"3bf4c71150bd0524\",\"4d3ca1b96c181645\",\"315948cc6a2cc9e8\",\"fcf061d4cc732e2e\",\"6563d8715af4cb06\",\"d40caa36040de881\"",",\"x\":514,\"y\":699,\"w\":1752,\"h\":82},{\"id\":\"3bf4c71150bd0524\",\"type\":\"ui-template\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"e50f7c57189d62f8\",\"group\":\"\",\"page\":\"\",\"ui\":\"25f447d87d1ce5c9\",\"name\":\"Display image\",\"order\":0,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,21758,20181,21759,21760,21762],{},"\\n    \u003CTeleport v-if=\"mounted\" to=\"#app-bar-title\">\\n        \u003Cimg ",[21544,21761],{},"=\"msg.payload\" style=\"height: 32px;\" \u002F>\\n    \\n","\\n\\n",[10102,21765,21766],{},"\\n    export default {\\n        data() {\\n            return {\\n                mounted: false\\n            }\\n        },\\n        mounted() {\\n            \u002F\u002F Set mounted to true when the component is mounted\\n            this.mounted = true\\n        }\\n    }\\n","\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"widget:ui\",\"className\":\"\",\"x\":2160,\"y\":740,\"wires\":[[]]},{\"id\":\"4d3ca1b96c181645\",\"type\":\"inject\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"e50f7c57189d62f8\",\"name\":\"\",\"props\":",[146,21769,9126],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"\",\"payloadType\":\"date\",\"x\":630,\"y\":740,\"wires\":[[\"315948cc6a2cc9e8\"]]},{\"id\":\"315948cc6a2cc9e8\",\"type\":\"file in\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"e50f7c57189d62f8\",\"name\":\"\",\"filename\":\"Images\u002Fff-logo--wordmark--light.png\",\"filenameType\":\"str\",\"format\":\"\",\"chunk\":false,\"sendError\":false,\"encoding\":\"none\",\"allProps\":false,\"x\":880,\"y\":740,\"wires\":[[\"d40caa36040de881\"]]},{\"id\":\"fcf061d4cc732e2e\",\"type\":\"change\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"e50f7c57189d62f8\",\"name\":\"Add the file type to the mimetype, add to image content\",\"rules\":",[146,21772,21773],{},"{\"t\":\"set\",\"p\":\"mimetype\",\"pt\":\"msg\",\"to\":\"\"data:image\u002F\"&msg.filetype&\";base64,\"\",\"tot\":\"jsonata\"},{\"t\":\"set\",\"p\":\"output\",\"pt\":\"msg\",\"to\":\"msg.mimetype&msg.payload\",\"tot\":\"jsonata\"},{\"t\":\"move\",\"p\":\"output\",\"pt\":\"msg\",\"to\":\"payload\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1820,\"y\":740,\"wires\":[[\"3bf4c71150bd0524\"]]},{\"id\":\"6563d8715af4cb06\",\"type\":\"string\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"e50f7c57189d62f8\",\"name\":\"Get file type from file name\",\"methods\":",[146,21776,21777,21778,1596],{},"{\"name\":\"getRightMost\",\"params\":",[146,21779,21780],{},"{\"type\":\"str\",\"value\":\".\"}",",\"prop\":\"filename\",\"propout\":\"filetype\",\"object\":\"msg\",\"objectout\":\"msg\",\"x\":1480,\"y\":740,\"wires\":[[\"fcf061d4cc732e2e\"]]},{\"id\":\"d40caa36040de881\",\"type\":\"base64\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"e50f7c57189d62f8\",\"name\":\"Convert Buffer to Base 64 String\",\"action\":\"\",\"property\":\"payload\",\"x\":1210,\"y\":740,\"wires\":[[\"6563d8715af4cb06\"]]},{\"id\":\"25f447d87d1ce5c9\",\"type\":\"ui-base\",\"name\":\"Dashboard\",\"path\":\"\u002Fdashboard\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,21783,21784],{},"\"ui-control\",\"ui-notification\"",",\"showPathInSidebar\":false,\"showPageTitle\":false,\"titleBarStyle\":\"default\"}",[15,21787,21788],{},"Using the FlowFuse Static Assets service is highly beneficial when you want to display images in Node-RED dashboards, as it saves time compared to alternative solutions. However, it’s important to note that moving Node-RED instances through a DevOps pipeline currently does not support handling static assets. This feature is expected in future updates. If you want to manage images effectively within your Node-RED dashboards, consider the alternative solutions discussed in this blog, ensuring that the movement of instances does not affect the usage of these assets.",[34,21790,21792],{"id":21791},"why-not-just-store-them-in-node-reds-host-operating-system","Why not just store them in Node-RED's host operating system?",[15,21794,21795],{},"Storing images locally can work well when you can access and edit the images on an operating system, but that approach doesn't scale if you are moving instances through a DevOps pipeline. It can also not work well when deploying to environments where you don't have easy access to the host operating system.",[15,21797,21798],{},"How can we include images in dashboards, and be confident that a given build of an application will show the correct images, no matter where your Node-RED instances are hosted?",[34,21800,21802],{"id":21801},"inspiration","Inspiration",[15,21804,21805],{},"There are various solutions to this problem, I wanted to share one I came across when working with a FlowFuse customer recently. I've modified the flows to make them more general in design, but the underlying principal is the same. I asked if I was OK to credit the customer but they said there was no need. Thanks for the inspiration, kind customer!",[34,21807,21809],{"id":21808},"solution-explanation","Solution explanation",[15,21811,21812],{},"There are three key sections to this solution:",[326,21814,21815,21818,21821],{},[103,21816,21817],{},"Pull the images we need from URLs",[103,21819,21820],{},"Store those images in the temporary filesystem of Node-RED",[103,21822,21823],{},"Serve up those images as needed in the dashboard",[15,21825,21826],{},"It is possible for us to skip step 2, but I wanted to have the images stored locally, in the Node-RED instance. storing the images locally will improve the loading times of the dashboard. This is especially beneficial when your dashboard is dynamically displaying relevant images, e.g. to show an image of a specific machine which needs to be attended to.",[15,21828,21829],{},"The key benefit of pulling the images from URLs this way is, no matter where you are running Node-RED, the correct images will be shown in your dashboard.",[34,21831,21833],{"id":21832},"prequsite","Prequsite",[15,21835,21836],{},"Before moving forward, ensure you have the following nodes installed, as the flows shared later will require them:",[100,21838,21839,21847,21855,21863,21868,21874],{},[103,21840,21841,21846],{},[307,21842,21845],{"href":21843,"rel":21844},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-calc",[311],"node-red-contrib-calc"," - A Node-RED node to perform basic mathematical calculations.",[103,21848,21849,21854],{},[307,21850,21853],{"href":21851,"rel":21852},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-image-output",[311],"node-red-contrib-image-output"," - A simple way to preview and examine images in your flows.",[103,21856,21857,21862],{},[307,21858,21861],{"href":21859,"rel":21860},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-os",[311],"node-red-contrib-os"," - Nodes for obtaining system information like CPU usage.",[103,21864,21865,21378],{},[307,21866,21377],{"href":21375,"rel":21867},[311],[103,21869,21870,21873],{},[307,21871,2153],{"href":19954,"rel":21872},[311]," - A set of dashboard nodes for Node-RED.",[103,21875,21876,21386],{},[307,21877,21385],{"href":21383,"rel":21878},[311],[34,21880,21882],{"id":21881},"file-and-file-in-nodes","File and file-in nodes",[15,21884,21885,21886,21890],{},"I've included the flows as json below so you can try them out yourself. Please note, I'm using FlowFuse's own ",[307,21887,21889],{"href":21888},"\u002Fdocs\u002Fuser\u002Ffilenodes\u002F","file and file-in nodes"," in these examples. If you want to use these flows on hosting other than FlowFuse, you will need to replace the nodes with the standard Node-RED file and file-in nodes.",[34,21892,21894],{"id":21893},"the-flows","The flows",[15,21896,21897],{},"The first flow takes image URLs in an array, each image is downloaded, processed, then saved to the local file storage. Let's take a look at the flow:",[15,21899,6389,21900,3830],{},[146,21901,21902,21903,21906,21907,21910,21911,11866,21913,21920,21921,21924,21925,21927,21928,21931,21932,21934,21935,21938,21939,21942,21943,21945,21946,21949,21950,21953,21954,21959,21960,21963,21964,21967,21968,21970,21971,21974,21975,21977,21978,21981],{},"{\"id\":\"6b8059f703d0f574\",\"type\":\"group\",\"z\":\"c6f2a894be05d857\",\"name\":\"Write the images to disk from the URLs\",\"style\":{\"label\":true},\"nodes\":",[146,21904,21905],{},"\"04fb6911559797a0\",\"8a3c077f0f85a905\",\"22c5026dd58e418b\",\"6fcca5cfee2bcb89\"",",\"x\":38,\"y\":53,\"w\":1004,\"h\":434},{\"id\":\"04fb6911559797a0\",\"type\":\"group\",\"z\":\"c6f2a894be05d857\",\"g\":\"6b8059f703d0f574\",\"name\":\"Inject the image URLs to download\",\"style\":{\"label\":true},\"nodes\":",[146,21908,21909],{},"\"e635ceb0577a86d5\",\"29fe40a054be5b2b\",\"1e81a35c27aae6ad\"",",\"x\":74,\"y\":79,\"w\":502,\"h\":82},{\"id\":\"e635ceb0577a86d5\",\"type\":\"inject\",\"z\":\"c6f2a894be05d857\",\"g\":\"04fb6911559797a0\",\"name\":\"Send in image URLs as an array\",\"props\":",[146,21912,9126],{},[146,21914,727,21915,727],{},[307,21916,21919],{"href":21917,"rel":21918},"https:\u002F\u002Fopenjsf.org\u002Fwp-content\u002Fuploads\u002Fsites\u002F84\u002F2023\u002F02\u002Fff-logo-wordmark-light_4x.png%5C%22,%5C%22https:\u002F\u002Fnodered.org\u002Fimages\u002Fnr-image-1.png%5C%22,%5C%22\u002Fimg\u002Fscreen-pseudo-overview-2QvTVle3Mr-384.avif%5C",[311],"https:\u002F\u002Fopenjsf.org\u002Fwp-content\u002Fuploads\u002Fsites\u002F84\u002F2023\u002F02\u002Fff-logo-wordmark-light_4x.png\\\",\\\"https:\u002F\u002Fnodered.org\u002Fimages\u002Fnr-image-1.png\\\",\\\"\u002Fimg\u002Fscreen-pseudo-overview-2QvTVle3Mr-384.avif\\","\",\"payloadType\":\"json\",\"x\":250,\"y\":120,\"wires\":[[\"29fe40a054be5b2b\"]]},{\"id\":\"29fe40a054be5b2b\",\"type\":\"split\",\"z\":\"c6f2a894be05d857\",\"g\":\"04fb6911559797a0\",\"name\":\"\",\"splt\":\"\\n\",\"spltType\":\"str\",\"arraySplt\":1,\"arraySpltType\":\"len\",\"stream\":false,\"addname\":\"\",\"x\":450,\"y\":120,\"wires\":[[\"1e81a35c27aae6ad\"]]},{\"id\":\"1e81a35c27aae6ad\",\"type\":\"link out\",\"z\":\"c6f2a894be05d857\",\"g\":\"04fb6911559797a0\",\"name\":\"link out 3\",\"mode\":\"link\",\"links\":",[146,21922,21923],{},"\"f582702ec222069c\"",",\"x\":535,\"y\":120,\"wires\":",[146,21926],{},"},{\"id\":\"8a3c077f0f85a905\",\"type\":\"group\",\"z\":\"c6f2a894be05d857\",\"g\":\"6b8059f703d0f574\",\"name\":\"Download the images\",\"style\":{\"label\":true},\"nodes\":",[146,21929,21930],{},"\"453f3b9d7d312bd2\",\"ecbd7b1a410ecd9d\",\"4d650baa2118036e\",\"f582702ec222069c\"",",\"x\":84,\"y\":179,\"w\":532,\"h\":82},{\"id\":\"453f3b9d7d312bd2\",\"type\":\"http request\",\"z\":\"c6f2a894be05d857\",\"g\":\"8a3c077f0f85a905\",\"name\":\"Get the image\",\"method\":\"GET\",\"ret\":\"bin\",\"paytoqs\":\"ignore\",\"url\":\"\",\"tls\":\"\",\"persist\":false,\"proxy\":\"\",\"insecureHTTPParser\":false,\"authType\":\"\",\"senderr\":false,\"headers\":",[146,21933],{},",\"x\":460,\"y\":220,\"wires\":[[\"4d650baa2118036e\"]]},{\"id\":\"ecbd7b1a410ecd9d\",\"type\":\"change\",\"z\":\"c6f2a894be05d857\",\"g\":\"8a3c077f0f85a905\",\"name\":\"Set URL to download\",\"rules\":",[146,21936,21937],{},"{\"t\":\"move\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"url\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":260,\"y\":220,\"wires\":[[\"453f3b9d7d312bd2\"]]},{\"id\":\"4d650baa2118036e\",\"type\":\"link out\",\"z\":\"c6f2a894be05d857\",\"g\":\"8a3c077f0f85a905\",\"name\":\"link out 1\",\"mode\":\"link\",\"links\":",[146,21940,21941],{},"\"8bc38803dec97185\"",",\"x\":575,\"y\":220,\"wires\":",[146,21944],{},"},{\"id\":\"f582702ec222069c\",\"type\":\"link in\",\"z\":\"c6f2a894be05d857\",\"g\":\"8a3c077f0f85a905\",\"name\":\"link in 3\",\"links\":",[146,21947,21948],{},"\"1e81a35c27aae6ad\"",",\"x\":125,\"y\":220,\"wires\":[[\"ecbd7b1a410ecd9d\"]]},{\"id\":\"22c5026dd58e418b\",\"type\":\"group\",\"z\":\"c6f2a894be05d857\",\"g\":\"6b8059f703d0f574\",\"name\":\"Save the images to the local storage\",\"style\":{\"label\":true},\"nodes\":",[146,21951,21952],{},"\"95c819560d22f394\",\"0fe7f013b6356c5d\",\"7edc6cb2b0d243db\",\"829bfc8e6e293ada\",\"8bc38803dec97185\",\"98980d88013c6e12\"",",\"x\":84,\"y\":279,\"w\":932,\"h\":82},{\"id\":\"95c819560d22f394\",\"type\":\"base64\",\"z\":\"c6f2a894be05d857\",\"g\":\"22c5026dd58e418b\",\"name\":\"convert to base64\",\"action\":\"str\",\"property\":\"payload\",\"x\":250,\"y\":320,\"wires\":[[\"829bfc8e6e293ada\"]]},{\"id\":\"0fe7f013b6356c5d\",\"type\":\"file\",\"z\":\"c6f2a894be05d857\",\"g\":\"22c5026dd58e418b\",\"name\":\"Write file to storage\",\"filename\":\"filename\",\"filenameType\":\"msg\",\"appendNewline\":true,\"createDir\":false,\"overwriteFile\":\"true\",\"encoding\":\"none\",\"x\":850,\"y\":320,\"wires\":[[\"98980d88013c6e12\"]]},{\"id\":\"7edc6cb2b0d243db\",\"type\":\"string\",\"z\":\"c6f2a894be05d857\",\"g\":\"22c5026dd58e418b\",\"name\":\"Get filename from the URL\",\"methods\":",[146,21955,21777,21956,1596],{},[146,21957,21958],{},"{\"type\":\"str\",\"value\":\"\u002F\"}",",\"prop\":\"responseUrl\",\"propout\":\"filename\",\"object\":\"msg\",\"objectout\":\"msg\",\"x\":620,\"y\":320,\"wires\":[[\"0fe7f013b6356c5d\"]]},{\"id\":\"829bfc8e6e293ada\",\"type\":\"image\",\"z\":\"c6f2a894be05d857\",\"g\":\"22c5026dd58e418b\",\"name\":\"preview\",\"width\":\"150\",\"data\":\"payload\",\"dataType\":\"msg\",\"thumbnail\":false,\"active\":true,\"pass\":true,\"outputs\":1,\"x\":420,\"y\":320,\"wires\":[[\"7edc6cb2b0d243db\"]]},{\"id\":\"8bc38803dec97185\",\"type\":\"link in\",\"z\":\"c6f2a894be05d857\",\"g\":\"22c5026dd58e418b\",\"name\":\"link in 1\",\"links\":",[146,21961,21962],{},"\"4d650baa2118036e\"",",\"x\":125,\"y\":320,\"wires\":[[\"95c819560d22f394\"]]},{\"id\":\"98980d88013c6e12\",\"type\":\"link out\",\"z\":\"c6f2a894be05d857\",\"g\":\"22c5026dd58e418b\",\"name\":\"link out 2\",\"mode\":\"link\",\"links\":",[146,21965,21966],{},"\"1e94b5bab542830a\"",",\"x\":975,\"y\":320,\"wires\":",[146,21969],{},"},{\"id\":\"6fcca5cfee2bcb89\",\"type\":\"group\",\"z\":\"c6f2a894be05d857\",\"g\":\"6b8059f703d0f574\",\"name\":\"Output a debug once all images have been processed\",\"style\":{\"label\":true},\"nodes\":",[146,21972,21973],{},"\"d1a6feea3ac829c6\",\"119f8008752bc4fb\",\"1e94b5bab542830a\"",",\"x\":64,\"y\":379,\"w\":382,\"h\":82},{\"id\":\"d1a6feea3ac829c6\",\"type\":\"debug\",\"z\":\"c6f2a894be05d857\",\"g\":\"6fcca5cfee2bcb89\",\"name\":\"debug 140\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"true\",\"targetType\":\"full\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":330,\"y\":420,\"wires\":",[146,21976],{},"},{\"id\":\"119f8008752bc4fb\",\"type\":\"join\",\"z\":\"c6f2a894be05d857\",\"g\":\"6fcca5cfee2bcb89\",\"name\":\"\",\"mode\":\"auto\",\"build\":\"object\",\"property\":\"payload\",\"propertyType\":\"msg\",\"key\":\"topic\",\"joiner\":\"\\n\",\"joinerType\":\"str\",\"accumulate\":\"false\",\"timeout\":\"\",\"count\":\"\",\"reduceRight\":false,\"x\":190,\"y\":420,\"wires\":[[\"d1a6feea3ac829c6\"]]},{\"id\":\"1e94b5bab542830a\",\"type\":\"link in\",\"z\":\"c6f2a894be05d857\",\"g\":\"6fcca5cfee2bcb89\",\"name\":\"link in 2\",\"links\":",[146,21979,21980],{},"\"98980d88013c6e12\"",",\"x\":105,\"y\":420,\"wires\":[[\"119f8008752bc4fb\"]]}",[15,21983,21984],{},"We've now downloaded the images we need, and saved them to our local storage, to make them load more quickly when a user views them in the dashboard.",[15,21986,21987],{},"Onto the second flow, which will get the images from the local storage and then load them into the dashboard. Let's take a look at it:",[15,21989,21990],{},[392,21991],{"alt":21992,"src":21993,"title":21992},"Get the images from the local storage and place them in a dashboard","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fload-images-from-disk-and-show-in-dashboard.png",[15,21995,20068],{},[15,21997,6389,21998,3830],{},[146,21999,22000,22001,22004,22005,22008,22009,22012,22013,22016,22017,22019,22020,22023,22024,22026,22027,22031,22032,22035,22036,22039,22040,22042,22043,22046,22047,22050,22051,276,22054,22057,22058,22061,22062,22065,22066,22068,22069,22079,22080,22088,22089,22097,22098,22101,22102,22104,22105,22108,22109,22112,22113,22115,22116,22119,22120,22122,22123,22126,22127,22129,22130,22137],{},"{\"id\":\"596006fe79275d55\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"name\":\"Get the images from the filestore and display in the Dashboard\",\"style\":{\"label\":true},\"nodes\":",[146,22002,22003],{},"\"be421f8265c4c090\",\"d2dc98b4f3d6b968\",\"b9029adc6097bd14\",\"1e54acecec6bd411\",\"99aaee673fc70bc2\"",",\"x\":1228,\"y\":3733,\"w\":974,\"h\":654},{\"id\":\"be421f8265c4c090\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"596006fe79275d55\",\"name\":\"Get the images from the local storage\",\"style\":{\"label\":true},\"nodes\":",[146,22006,22007],{},"\"c698b2945583e126\",\"ab22b978a2f5d80f\",\"2836914f9b643e34\",\"8e32aba67d9ac9d4\"",",\"x\":1264,\"y\":3899,\"w\":492,\"h\":82},{\"id\":\"c698b2945583e126\",\"type\":\"image\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"be421f8265c4c090\",\"name\":\"preview\",\"width\":\"150\",\"data\":\"payload\",\"dataType\":\"msg\",\"thumbnail\":false,\"active\":true,\"pass\":true,\"outputs\":1,\"x\":1620,\"y\":3940,\"wires\":[[\"8e32aba67d9ac9d4\"]]},{\"id\":\"ab22b978a2f5d80f\",\"type\":\"file in\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"be421f8265c4c090\",\"name\":\"Read file from storage\",\"filename\":\"payload\",\"filenameType\":\"msg\",\"format\":\"utf8\",\"chunk\":false,\"sendError\":false,\"encoding\":\"none\",\"allProps\":false,\"x\":1440,\"y\":3940,\"wires\":[[\"c698b2945583e126\"]]},{\"id\":\"2836914f9b643e34\",\"type\":\"link in\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"be421f8265c4c090\",\"name\":\"link in 4\",\"links\":",[146,22010,22011],{},"\"a9958e1e4b07191a\"",",\"x\":1305,\"y\":3940,\"wires\":[[\"ab22b978a2f5d80f\"]]},{\"id\":\"8e32aba67d9ac9d4\",\"type\":\"link out\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"be421f8265c4c090\",\"name\":\"link out 5\",\"mode\":\"link\",\"links\":",[146,22014,22015],{},"\"bbdd3a2e737e1074\"",",\"x\":1715,\"y\":3940,\"wires\":",[146,22018],{},"},{\"id\":\"d2dc98b4f3d6b968\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"596006fe79275d55\",\"name\":\"Prepare each image to be shown in the dashboard\",\"style\":{\"label\":true},\"nodes\":",[146,22021,22022],{},"\"54f0e7fddf447d10\",\"d856077cc6d37093\",\"bbdd3a2e737e1074\",\"2d29c953bcdab787\"",",\"x\":1264,\"y\":3999,\"w\":832,\"h\":82},{\"id\":\"54f0e7fddf447d10\",\"type\":\"change\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"d2dc98b4f3d6b968\",\"name\":\"Add the file type to the mimetype, add to image content\",\"rules\":",[146,22025,21773],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1810,\"y\":4040,\"wires\":[[\"2d29c953bcdab787\"]]},{\"id\":\"d856077cc6d37093\",\"type\":\"string\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"d2dc98b4f3d6b968\",\"name\":\"Get file type from file name\",\"methods\":",[146,22028,21777,22029,1596],{},[146,22030,21780],{},",\"prop\":\"filename\",\"propout\":\"filetype\",\"object\":\"msg\",\"objectout\":\"msg\",\"x\":1460,\"y\":4040,\"wires\":[[\"54f0e7fddf447d10\"]]},{\"id\":\"bbdd3a2e737e1074\",\"type\":\"link in\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"d2dc98b4f3d6b968\",\"name\":\"link in 5\",\"links\":",[146,22033,22034],{},"\"8e32aba67d9ac9d4\"",",\"x\":1305,\"y\":4040,\"wires\":[[\"d856077cc6d37093\"]]},{\"id\":\"2d29c953bcdab787\",\"type\":\"link out\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"d2dc98b4f3d6b968\",\"name\":\"link out 6\",\"mode\":\"link\",\"links\":",[146,22037,22038],{},"\"c5e4febe48363987\"",",\"x\":2055,\"y\":4040,\"wires\":",[146,22041],{},"},{\"id\":\"b9029adc6097bd14\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"596006fe79275d55\",\"name\":\"Send the images to the correct section of the dashboard\",\"style\":{\"label\":true},\"nodes\":",[146,22044,22045],{},"\"fa873d7bccd2b6be\",\"c5e4febe48363987\",\"d80e5a54a7c5b2f8\",\"fb0c76a6934b0a7f\",\"4e4105a9774c7b5c\",\"917c842a1b46ad4c\"",",\"x\":1264,\"y\":4099,\"w\":912,\"h\":162},{\"id\":\"fa873d7bccd2b6be\",\"type\":\"switch\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"b9029adc6097bd14\",\"name\":\"Send the image to the correct section of the dashboard\",\"property\":\"filename\",\"propertyType\":\"msg\",\"rules\":",[146,22048,22049],{},"{\"t\":\"eq\",\"v\":\"ff-logo-wordmark-light_4x.png\",\"vt\":\"str\"},{\"t\":\"eq\",\"v\":\"screen-pseudo-overview-2QvTVle3Mr-384.avif\",\"vt\":\"str\"},{\"t\":\"eq\",\"v\":\"nr-image-1.png\",\"vt\":\"str\"}",",\"checkall\":\"true\",\"repair\":false,\"outputs\":3,\"x\":1550,\"y\":4180,\"wires\":[[\"917c842a1b46ad4c\"],",[146,22052,22053],{},"\"4e4105a9774c7b5c\"",[146,22055,22056],{},"\"fb0c76a6934b0a7f\"","]},{\"id\":\"c5e4febe48363987\",\"type\":\"link in\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"b9029adc6097bd14\",\"name\":\"link in 6\",\"links\":",[146,22059,22060],{},"\"2d29c953bcdab787\"",",\"x\":1305,\"y\":4180,\"wires\":[[\"fa873d7bccd2b6be\"]]},{\"id\":\"d80e5a54a7c5b2f8\",\"type\":\"link out\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"b9029adc6097bd14\",\"name\":\"link out 7\",\"mode\":\"link\",\"links\":",[146,22063,22064],{},"\"c9de7384c9ca672f\"",",\"x\":2135,\"y\":4180,\"wires\":",[146,22067],{},"},{\"id\":\"fb0c76a6934b0a7f\",\"type\":\"ui-template\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"b9029adc6097bd14\",\"group\":\"ec62d482d77f7908\",\"page\":\"\",\"ui\":\"\",\"name\":\"Display the image on the Dashboard\",\"order\":6,\"width\":\"3\",\"height\":\"1\",\"head\":\"\",\"format\":\"",[9982,22070,20181,22071,1276],{},[5168,22072,22073,22074,22076,22077,20181],{},"\\n        \u003Cimg ",[21544,22075],{},"= \"msg.paylaod\" alt=\"Image loaded from the filestore\" style=\"width:100%\">",[1290,22078],{},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1930,\"y\":4220,\"wires\":[[\"d80e5a54a7c5b2f8\"]]},{\"id\":\"4e4105a9774c7b5c\",\"type\":\"ui-template\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"b9029adc6097bd14\",\"group\":\"ec62d482d77f7908\",\"page\":\"\",\"ui\":\"\",\"name\":\"Display the image on the Dashboard\",\"order\":5,\"width\":\"3\",\"height\":\"1\",\"head\":\"\",\"format\":\"",[9982,22081,20181,22082,1276],{},[5168,22083,22073,22084,22076,22086,20181],{},[21544,22085],{},[1290,22087],{},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1930,\"y\":4180,\"wires\":[[\"d80e5a54a7c5b2f8\"]]},{\"id\":\"917c842a1b46ad4c\",\"type\":\"ui-template\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"b9029adc6097bd14\",\"group\":\"ec62d482d77f7908\",\"page\":\"\",\"ui\":\"\",\"name\":\"Display the image on the Dashboard\",\"order\":2,\"width\":0,\"height\":0,\"head\":\"\",\"format\":\"",[9982,22090,20181,22091,1276],{},[5168,22092,22073,22093,22076,22095,20181],{},[21544,22094],{},[1290,22096],{},"\",\"storeOutMessages\":true,\"passthru\":true,\"resendOnRefresh\":true,\"templateScope\":\"local\",\"className\":\"\",\"x\":1930,\"y\":4140,\"wires\":[[\"d80e5a54a7c5b2f8\"]]},{\"id\":\"ec62d482d77f7908\",\"type\":\"ui-group\",\"name\":\"Default\",\"page\":\"550c9ac3b2ed01c9\",\"width\":\"6\",\"height\":\"1\",\"order\":1,\"showTitle\":false,\"className\":\"\",\"visible\":\"true\",\"disabled\":\"false\"},{\"id\":\"550c9ac3b2ed01c9\",\"type\":\"ui-page\",\"name\":\"Home\",\"ui\":\"25f447d87d1ce5c9\",\"path\":\"\u002F\",\"icon\":\"home\",\"layout\":\"grid\",\"theme\":\"c68088445147719b\",\"breakpoints\":",[146,22099,22100],{},"{\"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\":\"25f447d87d1ce5c9\",\"type\":\"ui-base\",\"name\":\"Dashboard\",\"path\":\"\u002Fdashboard\",\"includeClientData\":true,\"acceptsClientConfig\":",[146,22103,21784],{},",\"showPathInSidebar\":false,\"showPageTitle\":false,\"titleBarStyle\":\"default\"},{\"id\":\"c68088445147719b\",\"type\":\"ui-theme\",\"name\":\"Theme Name\",\"colors\":{\"surface\":\"#ffffff\",\"primary\":\"#0094ce\",\"bgPage\":\"#eeeeee\",\"groupBg\":\"#ffffff\",\"groupOutline\":\"#cccccc\"}},{\"id\":\"1e54acecec6bd411\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"596006fe79275d55\",\"name\":\"Output a debug once all images have been processed\",\"style\":{\"label\":true},\"nodes\":",[146,22106,22107],{},"\"c9de7384c9ca672f\",\"b073cd4970e6ddf2\",\"c1b52f44fd4260b3\"",",\"x\":1254,\"y\":4279,\"w\":392,\"h\":82},{\"id\":\"c9de7384c9ca672f\",\"type\":\"link in\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"1e54acecec6bd411\",\"name\":\"link in 7\",\"links\":",[146,22110,22111],{},"\"d80e5a54a7c5b2f8\"",",\"x\":1295,\"y\":4320,\"wires\":[[\"b073cd4970e6ddf2\"]]},{\"id\":\"b073cd4970e6ddf2\",\"type\":\"join\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"1e54acecec6bd411\",\"name\":\"\",\"mode\":\"auto\",\"build\":\"object\",\"property\":\"payload\",\"propertyType\":\"msg\",\"key\":\"topic\",\"joiner\":\"\\n\",\"joinerType\":\"str\",\"accumulate\":\"false\",\"timeout\":\"\",\"count\":\"\",\"reduceRight\":false,\"x\":1380,\"y\":4320,\"wires\":[[\"c1b52f44fd4260b3\"]]},{\"id\":\"c1b52f44fd4260b3\",\"type\":\"debug\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"1e54acecec6bd411\",\"name\":\"debug 141\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":1530,\"y\":4320,\"wires\":",[146,22114],{},"},{\"id\":\"99aaee673fc70bc2\",\"type\":\"group\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"596006fe79275d55\",\"name\":\"Inject the image files' names\",\"style\":{\"label\":true},\"nodes\":",[146,22117,22118],{},"\"11787d1ab6c8a9ec\",\"49b8f3cce48dd5a9\",\"a9958e1e4b07191a\",\"c452f250e1303c69\",\"08b30802899573a6\"",",\"x\":1254,\"y\":3759,\"w\":782,\"h\":122},{\"id\":\"11787d1ab6c8a9ec\",\"type\":\"inject\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"99aaee673fc70bc2\",\"name\":\"Inject\",\"props\":",[146,22121],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":\"1\",\"topic\":\"\",\"x\":1350,\"y\":3800,\"wires\":[[\"c452f250e1303c69\"]]},{\"id\":\"49b8f3cce48dd5a9\",\"type\":\"split\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"99aaee673fc70bc2\",\"name\":\"\",\"splt\":\"\\n\",\"spltType\":\"str\",\"arraySplt\":1,\"arraySpltType\":\"len\",\"stream\":false,\"addname\":\"\",\"property\":\"payload\",\"x\":1910,\"y\":3800,\"wires\":[[\"a9958e1e4b07191a\"]]},{\"id\":\"a9958e1e4b07191a\",\"type\":\"link out\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"99aaee673fc70bc2\",\"name\":\"link out 4\",\"mode\":\"link\",\"links\":",[146,22124,22125],{},"\"2836914f9b643e34\"",",\"x\":1995,\"y\":3800,\"wires\":",[146,22128],{},"},{\"id\":\"c452f250e1303c69\",\"type\":\"change\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"99aaee673fc70bc2\",\"name\":\"Image file names as an array\",\"rules\":",[146,22131,22132,22133,22136],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"",[146,22134,22135],{},"\"ff-logo-wordmark-light_4x.png\",\"screen-pseudo-overview-2QvTVle3Mr-384.avif\",\"nr-image-1.png\"","\",\"tot\":\"json\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":1720,\"y\":3800,\"wires\":[[\"49b8f3cce48dd5a9\"]]},{\"id\":\"08b30802899573a6\",\"type\":\"ui-event\",\"z\":\"d4aa6dd5b63a56de\",\"g\":\"99aaee673fc70bc2\",\"ui\":\"25f447d87d1ce5c9\",\"name\":\"Update images on dashboard open\",\"x\":1420,\"y\":3840,\"wires\":[[\"c452f250e1303c69\"]]}",[15,22139,22140],{},"I have also included some simple dashboard elements you can view alongside the images. Let's take a look at the dashboard:",[15,22142,22143],{},[392,22144],{"alt":22145,"src":22146,"title":22145},"The dashboard showing our images alongside other standard elements","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fthe-dashboard.gif",[15,22148,22149],{},"If you import these flows into Node-RED, you should see the images automatically loaded into the dashboard when you view it. You can also replace the URLs and file paths to try using some different images if you'd like to.",[34,22151,22153],{"id":22152},"more-things-to-try","More things to try",[15,22155,22156],{},"In this example, the images are static but it's simple to load images depending on the state of the flow. As mentioned in this article's introduction, you could display context aware images guiding the user of the dashboard to a specific location on a map, to complete a maintenance task. If you're interested in seeing examples of dynamic image loading please comment below.",[34,22158,6518],{"id":6517},[15,22160,22161],{},"Images can significantly enhance dashboards, but ensuring their proper display in different Node-RED hosting environments, especially within DevOps pipelines, can be challenging. The techniques discussed here enable effective use of images in dashboards, even within containerized setups. Additionally, if you are using FlowFuse, the new features simplify adding and managing static assets.\nI'd love to hear your comments and suggestions on this article. please tell us what you think about this article, and how you might use these techniques in the comments section below.",[924,22163,22164],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":22166},[22167,22168,22180,22181,22182,22183,22184,22185,22186,22187],{"id":1162,"depth":221,"text":1163},{"id":21389,"depth":185,"text":21390,"children":22169},[22170],{"id":21401,"depth":221,"text":21402,"children":22171},[22172,22173,22174,22175,22176,22177,22178,22179],{"id":21406,"depth":257,"text":21407},{"id":21422,"depth":257,"text":21423},{"id":21438,"depth":257,"text":21439},{"id":21453,"depth":257,"text":21454},{"id":21466,"depth":257,"text":21467},{"id":21490,"depth":257,"text":21491},{"id":21525,"depth":257,"text":21526},{"id":21716,"depth":257,"text":21717},{"id":21791,"depth":185,"text":21792},{"id":21801,"depth":185,"text":21802},{"id":21808,"depth":185,"text":21809},{"id":21832,"depth":185,"text":21833},{"id":21881,"depth":185,"text":21882},{"id":21893,"depth":185,"text":21894},{"id":22152,"depth":185,"text":22153},{"id":6517,"depth":185,"text":6518},{"type":941,"title":22189,"description":22190},"Start Building Better Node-RED Dashboards","Sign up for FlowFuse and use the Static Asset Service to easily add and manage images in your Node-RED dashboards, no filesystem access required.","2023-07-21","Learn to enhance Node-RED dashboards with images using FlowFuse. Pull images from URLs, store locally, and serve them in your dashboards.","blog\u002F2023\u002F07\u002Fimages\u002Fadd-images-in-node-red-header.png",{"keywords":22195,"excerpt":22196},"node-red dashboard images, node-red static assets, flowfuse static asset service, base64 image node-red, node-red image display, dashboard image upload, node-red docker images",{"type":12,"value":22197},[22198],[15,22199,21340],{},"\u002Fblog\u002F2023\u002F07\u002Fimages-in-node-red-dashboards",{"title":21334,"description":22192},{"loc":22200},"blog\u002F2023\u002F07\u002Fimages-in-node-red-dashboards","Import your images into your Node-RED dashboards, wherever you are running your instances",[9832,965,6563,11070,966],"This guide covers multiple methods for displaying images in Node-RED dashboards, including pulling from URLs, encoding files as base64, and serving them via FlowFuse's Static Asset Service. The Static Asset Service approach is the simplest option for containerized environments like Docker or Kubernetes where writing to the local filesystem is not practical.","NUoZ4VyBD5HJgjU5f1aRVQsgRJCDloMl8-mRvrvKhjo",{"id":22209,"title":22210,"authors":22211,"body":22212,"cta":24032,"date":24035,"description":24036,"extension":946,"image":24037,"lastUpdated":948,"meta":24038,"navigation":253,"path":24044,"seo":24045,"sitemap":24046,"stem":24047,"subtitle":24048,"tags":24049,"tldr":24050,"video":3,"__hash__":24051},"blog\u002Fblog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red.md","How to Deploy a Basic OPC-UA Server in Node-RED - Part 1 (2026)",[19912],{"type":12,"value":22213,"toc":24025},[22214,22217,22221,22224,22227,22242,22246,22261,22270,22273,22280,22288,22296,22301,22304,22311,22325,22328,22332,22345,22353,22356,22362,22379,22631,22646,22895,22898,22905,22912,22919,22942,22948,22954,22964,22971,22977,22987,22990,23019,23022,23048,23059,23225,23228,23375,23378,23695,23698,23906,23913,23933,23940,23946,23950,23959,23962,23974,23977,23991,23997,24008,24014,24016,24019,24022],[15,22215,22216],{},"This article is the first part of a series of OPC-UA content.  Here, we will explain some basic concepts of OPC-UA as they apply to building a server in Node-RED, then walk through and deploy an example OPC-UA Server.",[34,22218,22220],{"id":22219},"what-is-opc-ua","What is OPC-UA?",[15,22222,22223],{},"Open Platform Communications Unified Architecture (OPC UA) is an open, platform independent communication framework frequently utilized in industrial automation, and is considered one of the key protocol standards for Industry 4.0 and Industrial IoT (IIoT).  The standard is developed and maintained by a consortium called the OPC Foundation, with recognizable industry names such as Siemens, Honeywell, Microsoft, Beckhoff, SAP, Yokogawa, ABB, Rockwell, and Schneider Electric.",[15,22225,22226],{},"Because of OPC-UA’s wide industry acceptance, it is increasingly becoming natively supported on devices and systems spanning the entirety of the automation pyramid.",[15,22228,22229,22234],{},[392,22230],{"alt":22231,"src":22232,"title":22233},"\"Automation Pyramid\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fautomation-pyramid.jpg","Automation Pyramid",[397,22235,22236,22237],{},"Image reference - ",[307,22238,22241],{"href":22239,"rel":22240},"https:\u002F\u002Fwww.motioncontroltips.com\u002Fwhat-is-opc-ua-and-how-does-it-compare-with-industrial-ethernet\u002F",[311],"imagecontroltips.com",[34,22243,22245],{"id":22244},"fieldbus-model-vs-opc-ua-information-model","Fieldbus Model vs OPC-UA Information Model",[15,22247,22248,22249,22254],{},"As of today, industrial ethernet fieldbuses dominate the field\u002Fdevice-level (level 0) and controller\u002FPLC-level (level 1) of the automation pyramid.\n",[392,22250],{"alt":22251,"src":22252,"title":22253},"\"OPC-UA Pyramid\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002FOPC-UA-pyramid-2.webp","OPC-UA Pyramid",[397,22255,22236,22256],{},[307,22257,22260],{"href":22258,"rel":22259},"https:\u002F\u002Fwww.mdpi.com\u002F1424-8220\u002F21\u002F14\u002F4656",[311],"mdpi.com",[15,22262,22263,22264,22269],{},"Fieldbuses such as Profinet, Ethernet\u002FIP, and EtherCAT, employ deterministic, real-time communication, which is essential for mission-critical and safety-oriented automation tasks.  OPC-UA is most commonly encountered at the SCADA level and above (level 2-4).  However, with the inclusion of ",[307,22265,22268],{"href":22266,"rel":22267},"https:\u002F\u002Fwww.tttech-industrial.com\u002Fresource-library\u002Fblog-posts\u002Fopc-ua-fx",[311],"Time Sensitive Networking (TSN) into the OPC-UA technology stack",", OPC-UA can be feasibly used for real-time communication all the way down to the device level.",[15,22271,22272],{},"Traditionally, fieldbus protocols transmit only raw data from field devices (ie, a float to represent a pressure, or a boolean to represent the position of a switch).  The fieldbus data gets pushed up the automation stack layer by layer, where eventually it will be converted to a format suitable for IT systems to consume (such as OPC-UA).",[15,22274,22275],{},[392,22276],{"alt":22277,"src":22278,"title":22279},"\"Fieldbus Model\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Ffieldbus-model.png","Fieldbus Model",[15,22281,22282,22283,22287],{},"In contrast to fieldbus protocols, OPC-UA represents automation data in the form of nodes. The framework for constructing nodes is referred to as the ",[307,22284,22286],{"href":22285},"\u002Fblog\u002F2023\u002F07\u002Flhttps:\u002Freference.opcfoundation.org\u002FCore\u002FPart5\u002Fv104\u002Fdocs","OPC Information model",", and consists of pre-defined classes and methods that are programmed in the OPC Server address space.",[15,22289,22290,22295],{},[392,22291],{"alt":22292,"src":22293,"title":22294},"\"OPC Information Model\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-information-model.png","OPC Information Model","\nDevices can be described as objects that give a holistic view of the device, beyond simply the raw value.  To construct a device object, we can take different individual attributes associated with a device, such as the transmitter raw value, transmitter fault flag, alarm setpoint, and combine them, similar to how user-defined datatypes (UDTs) are objects used to represent devices in PLCs.  The information model also defines a folder structure, to allow devices information to reside in a structured hierarchy.  Using the example temperature transmitter above, an example folder structure can be constructed as follows:",[15,22297,22298],{},[19,22299,22300],{},"\u002FRoot\u002FObjects\u002FCalcinator 1 PLC\u002FTemperature Transmitters\u002FTank 1 Temperature\u002FTransmitter Value",[15,22302,22303],{},"This folder structure will be exposed via the OPC Client browser, allowing end-users to easily “drill down” to individual node information in a logical manner.",[15,22305,22306,22310],{},[392,22307],{"alt":22308,"src":22309,"title":22294},"\"OPC Client Browser\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-browser.png","\nIn summary, OPC-UA represents a trade-off between complex information modeling, with the versatility for that data to be consumed by devices and systems all the way up the automation pyramid layers.  The data does not have to pass through subsequent automation layers on the way up, nor does the data need to undergo any conversion along the way.",[15,22312,22313,22318],{},[392,22314],{"alt":22315,"src":22316,"title":22317},"\"OPC-UA Distributed Model\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002FOPC-UA-distributed-model.jpg","OPC-UA Distributed Model",[397,22319,22236,22320],{},[307,22321,22324],{"href":22322,"rel":22323},"https:\u002F\u002Fifr.org\u002Fpost\u002Ffaster-robot-communication-through-the-opc-robotics-companion-specification",[311],"ifr.org",[15,22326,22327],{},"The OPC client simply needs to subscribe to the OPC Server endpoint url (ex. opc.tcp:\u002F\u002Fserver.address), and the client will be able to browse the structured OPC data as it’s modeled in the server.  Any client will receive the information in the same manner, regardless if it’s a PLC, SCADA, MES, or ERP system.  This opens the possibility for horizontal and vertical system integration in a standardized manner. Additionally, the more information that is exposed about a device, the easier it is to track, and use said data to autonomously reconfigure, or pre-emptively take maintenance actions.",[34,22329,22331],{"id":22330},"deploying-an-example-opc-ua-server-in-node-red","Deploying an Example OPC-UA Server in Node-RED",[15,22333,22334,22335,22340,22341,22344],{},"With some background on OPC-UA and how information is modeled in mind, we can take a look at the ",[307,22336,22339],{"href":22337,"rel":22338},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-opcua-server",[311],"node-red-contrib-opcua-server"," node, which is merely a compact version of the ",[307,22342,9396],{"href":9394,"rel":22343},[311]," node that only focuses on the OPC-UA server and hence requires less dependencies.",[15,22346,18606,22347,22352],{},[307,22348,22351],{"href":22349,"rel":22350},"https:\u002F\u002Fgithub.com\u002FBiancoRoyal\u002Fnode-red-contrib-opcua-server\u002Fblob\u002Fmaster\u002Fexamples\u002Fserver-with-context.json",[311],"example flow"," is provided on github that can serve as a basis for understanding how a OPC-UA server is constructed.  Let’s get the example server up and running.",[15,22354,22355],{},"Deploying the example flow yields the following result -",[15,22357,22358],{},[392,22359],{"alt":22360,"src":22361,"title":22360},"Compact Server Flow","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcompact-server-flow.png",[100,22363,22364],{},[103,22365,22366,22367,22370,22371,22374,22375,22378],{},"an inject node is trigging the function ",[19,22368,22369],{},"set flow context Inputs"," at a one second interval, which creates 7 randomly generated float values and stores them as flow context variables, ",[19,22372,22373],{},"isoInput2"," - ",[19,22376,22377],{},"isoInput8"," (isolated inputs).  The values will change to a new random number each time the node is injected.",[42,22380,22382],{"className":140,"code":22381,"language":142,"meta":50,"style":50},"flow.set('isoInput2', Math.random() + 12.0)\nflow.set('isoInput3', Math.random() + 13.0)\nflow.set('isoInput4', Math.random() + 14.0)\nflow.set('isoInput5', Math.random() + 15.0)\nflow.set('isoInput6', Math.random() + 16.0)\nflow.set('isoInput7', Math.random() + 17.0)\nflow.set('isoInput8', Math.random() + 18.0)\n\n...\n",[19,22383,22384,22419,22453,22487,22521,22555,22589,22622,22626],{"__ignoreMap":50},[146,22385,22386,22388,22390,22392,22394,22396,22398,22400,22402,22404,22406,22409,22411,22414,22417],{"class":148,"line":149},[146,22387,13659],{"class":156},[146,22389,167],{"class":160},[146,22391,13786],{"class":170},[146,22393,203],{"class":156},[146,22395,1471],{"class":160},[146,22397,22373],{"class":1554},[146,22399,1471],{"class":160},[146,22401,276],{"class":160},[146,22403,16199],{"class":156},[146,22405,167],{"class":160},[146,22407,22408],{"class":170},"random",[146,22410,21653],{"class":156},[146,22412,22413],{"class":160},"+",[146,22415,22416],{"class":206}," 12.0",[146,22418,8128],{"class":156},[146,22420,22421,22423,22425,22427,22429,22431,22434,22436,22438,22440,22442,22444,22446,22448,22451],{"class":148,"line":185},[146,22422,13659],{"class":156},[146,22424,167],{"class":160},[146,22426,13786],{"class":170},[146,22428,203],{"class":156},[146,22430,1471],{"class":160},[146,22432,22433],{"class":1554},"isoInput3",[146,22435,1471],{"class":160},[146,22437,276],{"class":160},[146,22439,16199],{"class":156},[146,22441,167],{"class":160},[146,22443,22408],{"class":170},[146,22445,21653],{"class":156},[146,22447,22413],{"class":160},[146,22449,22450],{"class":206}," 13.0",[146,22452,8128],{"class":156},[146,22454,22455,22457,22459,22461,22463,22465,22468,22470,22472,22474,22476,22478,22480,22482,22485],{"class":148,"line":221},[146,22456,13659],{"class":156},[146,22458,167],{"class":160},[146,22460,13786],{"class":170},[146,22462,203],{"class":156},[146,22464,1471],{"class":160},[146,22466,22467],{"class":1554},"isoInput4",[146,22469,1471],{"class":160},[146,22471,276],{"class":160},[146,22473,16199],{"class":156},[146,22475,167],{"class":160},[146,22477,22408],{"class":170},[146,22479,21653],{"class":156},[146,22481,22413],{"class":160},[146,22483,22484],{"class":206}," 14.0",[146,22486,8128],{"class":156},[146,22488,22489,22491,22493,22495,22497,22499,22502,22504,22506,22508,22510,22512,22514,22516,22519],{"class":148,"line":250},[146,22490,13659],{"class":156},[146,22492,167],{"class":160},[146,22494,13786],{"class":170},[146,22496,203],{"class":156},[146,22498,1471],{"class":160},[146,22500,22501],{"class":1554},"isoInput5",[146,22503,1471],{"class":160},[146,22505,276],{"class":160},[146,22507,16199],{"class":156},[146,22509,167],{"class":160},[146,22511,22408],{"class":170},[146,22513,21653],{"class":156},[146,22515,22413],{"class":160},[146,22517,22518],{"class":206}," 15.0",[146,22520,8128],{"class":156},[146,22522,22523,22525,22527,22529,22531,22533,22536,22538,22540,22542,22544,22546,22548,22550,22553],{"class":148,"line":257},[146,22524,13659],{"class":156},[146,22526,167],{"class":160},[146,22528,13786],{"class":170},[146,22530,203],{"class":156},[146,22532,1471],{"class":160},[146,22534,22535],{"class":1554},"isoInput6",[146,22537,1471],{"class":160},[146,22539,276],{"class":160},[146,22541,16199],{"class":156},[146,22543,167],{"class":160},[146,22545,22408],{"class":170},[146,22547,21653],{"class":156},[146,22549,22413],{"class":160},[146,22551,22552],{"class":206}," 16.0",[146,22554,8128],{"class":156},[146,22556,22557,22559,22561,22563,22565,22567,22570,22572,22574,22576,22578,22580,22582,22584,22587],{"class":148,"line":284},[146,22558,13659],{"class":156},[146,22560,167],{"class":160},[146,22562,13786],{"class":170},[146,22564,203],{"class":156},[146,22566,1471],{"class":160},[146,22568,22569],{"class":1554},"isoInput7",[146,22571,1471],{"class":160},[146,22573,276],{"class":160},[146,22575,16199],{"class":156},[146,22577,167],{"class":160},[146,22579,22408],{"class":170},[146,22581,21653],{"class":156},[146,22583,22413],{"class":160},[146,22585,22586],{"class":206}," 17.0",[146,22588,8128],{"class":156},[146,22590,22591,22593,22595,22597,22599,22601,22603,22605,22607,22609,22611,22613,22615,22617,22620],{"class":148,"line":666},[146,22592,13659],{"class":156},[146,22594,167],{"class":160},[146,22596,13786],{"class":170},[146,22598,203],{"class":156},[146,22600,1471],{"class":160},[146,22602,22377],{"class":1554},[146,22604,1471],{"class":160},[146,22606,276],{"class":160},[146,22608,16199],{"class":156},[146,22610,167],{"class":160},[146,22612,22408],{"class":170},[146,22614,21653],{"class":156},[146,22616,22413],{"class":160},[146,22618,22619],{"class":206}," 18.0",[146,22621,8128],{"class":156},[146,22623,22624],{"class":148,"line":1603},[146,22625,254],{"emptyLinePlaceholder":253},[146,22627,22628],{"class":148,"line":1608},[146,22629,22630],{"class":160},"...\n",[100,22632,22633],{},[103,22634,22635,22636,22639,22640,22374,22643,22378],{},"another inject node is triggering the function ",[19,22637,22638],{},"set flow context Outputs",", also at a one second interval, which creates another set of 7 randomly generated float values and stores them as flow context variables, ",[19,22641,22642],{},"isoOutput2",[19,22644,22645],{},"isoOutput8",[42,22647,22649],{"className":140,"code":22648,"language":142,"meta":50,"style":50},"flow.set('isoOutput2', Math.random() + 2.0)\nflow.set('isoOutput3', Math.random() + 3.0)\nflow.set('isoOutput4', Math.random() + 4.0)\nflow.set('isoOutput5', Math.random() + 5.0)\nflow.set('isoOutput6', Math.random() + 6.0)\nflow.set('isoOutput7', Math.random() + 7.0)\nflow.set('isoOutput8', Math.random() + 8.0)\n\n...\n",[19,22650,22651,22684,22718,22752,22786,22820,22854,22887,22891],{"__ignoreMap":50},[146,22652,22653,22655,22657,22659,22661,22663,22665,22667,22669,22671,22673,22675,22677,22679,22682],{"class":148,"line":149},[146,22654,13659],{"class":156},[146,22656,167],{"class":160},[146,22658,13786],{"class":170},[146,22660,203],{"class":156},[146,22662,1471],{"class":160},[146,22664,22642],{"class":1554},[146,22666,1471],{"class":160},[146,22668,276],{"class":160},[146,22670,16199],{"class":156},[146,22672,167],{"class":160},[146,22674,22408],{"class":170},[146,22676,21653],{"class":156},[146,22678,22413],{"class":160},[146,22680,22681],{"class":206}," 2.0",[146,22683,8128],{"class":156},[146,22685,22686,22688,22690,22692,22694,22696,22699,22701,22703,22705,22707,22709,22711,22713,22716],{"class":148,"line":185},[146,22687,13659],{"class":156},[146,22689,167],{"class":160},[146,22691,13786],{"class":170},[146,22693,203],{"class":156},[146,22695,1471],{"class":160},[146,22697,22698],{"class":1554},"isoOutput3",[146,22700,1471],{"class":160},[146,22702,276],{"class":160},[146,22704,16199],{"class":156},[146,22706,167],{"class":160},[146,22708,22408],{"class":170},[146,22710,21653],{"class":156},[146,22712,22413],{"class":160},[146,22714,22715],{"class":206}," 3.0",[146,22717,8128],{"class":156},[146,22719,22720,22722,22724,22726,22728,22730,22733,22735,22737,22739,22741,22743,22745,22747,22750],{"class":148,"line":221},[146,22721,13659],{"class":156},[146,22723,167],{"class":160},[146,22725,13786],{"class":170},[146,22727,203],{"class":156},[146,22729,1471],{"class":160},[146,22731,22732],{"class":1554},"isoOutput4",[146,22734,1471],{"class":160},[146,22736,276],{"class":160},[146,22738,16199],{"class":156},[146,22740,167],{"class":160},[146,22742,22408],{"class":170},[146,22744,21653],{"class":156},[146,22746,22413],{"class":160},[146,22748,22749],{"class":206}," 4.0",[146,22751,8128],{"class":156},[146,22753,22754,22756,22758,22760,22762,22764,22767,22769,22771,22773,22775,22777,22779,22781,22784],{"class":148,"line":250},[146,22755,13659],{"class":156},[146,22757,167],{"class":160},[146,22759,13786],{"class":170},[146,22761,203],{"class":156},[146,22763,1471],{"class":160},[146,22765,22766],{"class":1554},"isoOutput5",[146,22768,1471],{"class":160},[146,22770,276],{"class":160},[146,22772,16199],{"class":156},[146,22774,167],{"class":160},[146,22776,22408],{"class":170},[146,22778,21653],{"class":156},[146,22780,22413],{"class":160},[146,22782,22783],{"class":206}," 5.0",[146,22785,8128],{"class":156},[146,22787,22788,22790,22792,22794,22796,22798,22801,22803,22805,22807,22809,22811,22813,22815,22818],{"class":148,"line":257},[146,22789,13659],{"class":156},[146,22791,167],{"class":160},[146,22793,13786],{"class":170},[146,22795,203],{"class":156},[146,22797,1471],{"class":160},[146,22799,22800],{"class":1554},"isoOutput6",[146,22802,1471],{"class":160},[146,22804,276],{"class":160},[146,22806,16199],{"class":156},[146,22808,167],{"class":160},[146,22810,22408],{"class":170},[146,22812,21653],{"class":156},[146,22814,22413],{"class":160},[146,22816,22817],{"class":206}," 6.0",[146,22819,8128],{"class":156},[146,22821,22822,22824,22826,22828,22830,22832,22835,22837,22839,22841,22843,22845,22847,22849,22852],{"class":148,"line":284},[146,22823,13659],{"class":156},[146,22825,167],{"class":160},[146,22827,13786],{"class":170},[146,22829,203],{"class":156},[146,22831,1471],{"class":160},[146,22833,22834],{"class":1554},"isoOutput7",[146,22836,1471],{"class":160},[146,22838,276],{"class":160},[146,22840,16199],{"class":156},[146,22842,167],{"class":160},[146,22844,22408],{"class":170},[146,22846,21653],{"class":156},[146,22848,22413],{"class":160},[146,22850,22851],{"class":206}," 7.0",[146,22853,8128],{"class":156},[146,22855,22856,22858,22860,22862,22864,22866,22868,22870,22872,22874,22876,22878,22880,22882,22885],{"class":148,"line":666},[146,22857,13659],{"class":156},[146,22859,167],{"class":160},[146,22861,13786],{"class":170},[146,22863,203],{"class":156},[146,22865,1471],{"class":160},[146,22867,22645],{"class":1554},[146,22869,1471],{"class":160},[146,22871,276],{"class":160},[146,22873,16199],{"class":156},[146,22875,167],{"class":160},[146,22877,22408],{"class":170},[146,22879,21653],{"class":156},[146,22881,22413],{"class":160},[146,22883,22884],{"class":206}," 8.0",[146,22886,8128],{"class":156},[146,22888,22889],{"class":148,"line":1603},[146,22890,254],{"emptyLinePlaceholder":253},[146,22892,22893],{"class":148,"line":1608},[146,22894,22630],{"class":160},[15,22896,22897],{},"We can confirm the values are being stored in memory by checking the flow context data and pressing the refresh button.",[15,22899,22900],{},[392,22901],{"alt":22902,"src":22903,"title":22904},"\"Screenshot showing the Context Data option\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcontext-data-1.png","Screenshot showing the Context Data option",[15,22906,22907,22911],{},[392,22908],{"alt":22909,"src":22910,"title":22909},"Screenshot showing the flow variables in the context data tab","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcontext-data-2.png","\nEach time we hit refresh, the values change, confirming that the values are randomly changing every second.",[15,22913,22914,22915,22918],{},"The last, and most important part of the flow, is the ",[19,22916,22917],{},"Compact-Server"," node, which actually stands alone without any incoming or outgoing connections.",[15,22920,22921,20949,22926,22928,22929,22932,22933,63,22935,22938,22939,22941],{},[392,22922],{"alt":22923,"src":22924,"title":22925},"\"Compact Server Node\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcompact-server-node.png","Compact Server Node",[19,22927,22917],{}," node properties, the first tab is ",[19,22930,22931],{},"Settings",", and the two important properties here are ",[19,22934,12041],{},[19,22936,22937],{},"Show Errors",".  As can be seen in the node screenshot above, the node is reporting ",[19,22940,13832],{},", which means the server is configured correctly.",[15,22943,22944],{},[392,22945],{"alt":22946,"src":22947,"title":22946},"Screenshot showing the Settings Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fsettings-tab.png",[15,22949,1714,22950,22953],{},[19,22951,22952],{},"Limits"," tab specifies some default limits that we can configure if we like, but are not necessary to be modified for test purposes.",[15,22955,1714,22956,22959,22960,22963],{},[19,22957,22958],{},"Security"," tab has one important option, ",[19,22961,22962],{},"Allow Anonymous",".  By default, anonymous access is enabled.",[15,22965,22966,22970],{},[392,22967],{"alt":22968,"src":22969,"title":22968},"Screenshot showing the Security Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fsecurity-tab.png","\nFor a production system, we will want to enable security, but for test purposes, we will leave anonymous access enabled.",[15,22972,22973,22976],{},[19,22974,22975],{},"Users & Sets"," tab is related to security and permissions.  We can leave this empty for testing.",[15,22978,1714,22979,22981,22982,167],{},[19,22980,20213],{}," tab is where our server OPC Information Model is constructed, using classes and methods from the ",[307,22983,22986],{"href":22984,"rel":22985},"https:\u002F\u002Fnode-opcua.github.io\u002F",[311],"node-opcua sdk",[15,22988,22989],{},"Breaking down the provided example code for further context, it starts with a function that is responsible for invoking the OPC-UA server,",[42,22991,22993],{"className":140,"code":22992,"language":142,"meta":50,"style":50},"  const opcua = coreServer.choreCompact.opcua;\n",[19,22994,22995],{"__ignoreMap":50},[146,22996,22997,23000,23003,23005,23008,23010,23013,23015,23017],{"class":148,"line":149},[146,22998,22999],{"class":152},"  const",[146,23001,23002],{"class":156}," opcua ",[146,23004,161],{"class":160},[146,23006,23007],{"class":156}," coreServer",[146,23009,167],{"class":160},[146,23011,23012],{"class":156},"choreCompact",[146,23014,167],{"class":160},[146,23016,2055],{"class":156},[146,23018,182],{"class":160},[15,23020,23021],{},"and then the namespace is created.",[42,23023,23025],{"className":140,"code":23024,"language":142,"meta":50,"style":50},"const namespace = addressSpace.getOwnNamespace();\n",[19,23026,23027],{"__ignoreMap":50},[146,23028,23029,23031,23034,23036,23039,23041,23044,23046],{"class":148,"line":149},[146,23030,153],{"class":152},[146,23032,23033],{"class":156}," namespace ",[146,23035,161],{"class":160},[146,23037,23038],{"class":156}," addressSpace",[146,23040,167],{"class":160},[146,23042,23043],{"class":170},"getOwnNamespace",[146,23045,1461],{"class":156},[146,23047,182],{"class":160},[15,23049,23050,23051,23054,23055,23058],{},"Further down, the variables that will be published by the server (which are our ",[19,23052,23053],{},"isoInput"," & ",[19,23056,23057],{},"isoOutput"," flow context variables) are initialized,",[42,23060,23062],{"className":140,"code":23061,"language":142,"meta":50,"style":50},"  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...\n",[19,23063,23064,23093,23108,23124,23134,23150,23157,23169,23195,23221],{"__ignoreMap":50},[146,23065,23066,23069,23072,23074,23076,23078,23080,23083,23085,23087,23089,23091],{"class":148,"line":149},[146,23067,23068],{"class":160},"  this.",[146,23070,23071],{"class":156},"sandboxFlowContext",[146,23073,167],{"class":160},[146,23075,13786],{"class":170},[146,23077,203],{"class":156},[146,23079,727],{"class":160},[146,23081,23082],{"class":1554},"isoInput1",[146,23084,727],{"class":160},[146,23086,276],{"class":160},[146,23088,2503],{"class":206},[146,23090,1477],{"class":156},[146,23092,182],{"class":160},[146,23094,23095,23097,23100,23102,23104,23106],{"class":148,"line":185},[146,23096,23068],{"class":160},[146,23098,23099],{"class":170},"setInterval",[146,23101,203],{"class":156},[146,23103,1461],{"class":160},[146,23105,1514],{"class":152},[146,23107,1517],{"class":160},[146,23109,23110,23113,23115,23117,23119,23121],{"class":148,"line":221},[146,23111,23112],{"class":156},"    flexServerInternals",[146,23114,167],{"class":160},[146,23116,23071],{"class":156},[146,23118,167],{"class":160},[146,23120,13786],{"class":170},[146,23122,23123],{"class":1549},"(\n",[146,23125,23126,23128,23130,23132],{"class":148,"line":250},[146,23127,2432],{"class":160},[146,23129,23082],{"class":1554},[146,23131,727],{"class":160},[146,23133,736],{"class":160},[146,23135,23136,23139,23141,23143,23145,23147],{"class":148,"line":257},[146,23137,23138],{"class":156},"      Math",[146,23140,167],{"class":160},[146,23142,22408],{"class":170},[146,23144,21653],{"class":1549},[146,23146,22413],{"class":160},[146,23148,23149],{"class":206}," 50.0\n",[146,23151,23152,23155],{"class":148,"line":284},[146,23153,23154],{"class":1549},"    )",[146,23156,182],{"class":160},[146,23158,23159,23162,23165,23167],{"class":148,"line":666},[146,23160,23161],{"class":160},"  },",[146,23163,23164],{"class":206}," 500",[146,23166,1477],{"class":156},[146,23168,182],{"class":160},[146,23170,23171,23173,23175,23177,23179,23181,23183,23185,23187,23189,23191,23193],{"class":148,"line":1603},[146,23172,23068],{"class":160},[146,23174,23071],{"class":156},[146,23176,167],{"class":160},[146,23178,13786],{"class":170},[146,23180,203],{"class":156},[146,23182,727],{"class":160},[146,23184,22373],{"class":1554},[146,23186,727],{"class":160},[146,23188,276],{"class":160},[146,23190,2503],{"class":206},[146,23192,1477],{"class":156},[146,23194,182],{"class":160},[146,23196,23197,23199,23201,23203,23205,23207,23209,23211,23213,23215,23217,23219],{"class":148,"line":1608},[146,23198,23068],{"class":160},[146,23200,23071],{"class":156},[146,23202,167],{"class":160},[146,23204,13786],{"class":170},[146,23206,203],{"class":156},[146,23208,727],{"class":160},[146,23210,22433],{"class":1554},[146,23212,727],{"class":160},[146,23214,276],{"class":160},[146,23216,2503],{"class":206},[146,23218,1477],{"class":156},[146,23220,182],{"class":160},[146,23222,23223],{"class":148,"line":1624},[146,23224,22630],{"class":160},[15,23226,23227],{},"and an OPC folder structure is defined.",[42,23229,23231],{"className":140,"code":23230,"language":142,"meta":50,"style":50},"  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\": \"RaspberryPI-Zero-WLAN\"\n  });\n...\n",[19,23232,23233,23256,23285,23289,23312,23316,23345,23362,23371],{"__ignoreMap":50},[146,23234,23235,23238,23240,23243,23245,23247,23250,23252,23254],{"class":148,"line":149},[146,23236,23237],{"class":156},"  coreServer",[146,23239,167],{"class":160},[146,23241,23242],{"class":170},"debugLog",[146,23244,203],{"class":156},[146,23246,727],{"class":160},[146,23248,23249],{"class":1554},"init dynamic address space",[146,23251,727],{"class":160},[146,23253,1477],{"class":156},[146,23255,182],{"class":160},[146,23257,23258,23260,23263,23265,23267,23269,23272,23274,23276,23279,23281,23283],{"class":148,"line":185},[146,23259,22999],{"class":152},[146,23261,23262],{"class":156}," rootFolder ",[146,23264,161],{"class":160},[146,23266,23038],{"class":156},[146,23268,167],{"class":160},[146,23270,23271],{"class":170},"findNode",[146,23273,203],{"class":156},[146,23275,727],{"class":160},[146,23277,23278],{"class":1554},"RootFolder",[146,23280,727],{"class":160},[146,23282,1477],{"class":156},[146,23284,182],{"class":160},[146,23286,23287],{"class":148,"line":221},[146,23288,254],{"emptyLinePlaceholder":253},[146,23290,23291,23294,23296,23299,23301,23303,23306,23308,23310],{"class":148,"line":250},[146,23292,23293],{"class":156},"  node",[146,23295,167],{"class":160},[146,23297,23298],{"class":170},"warn",[146,23300,203],{"class":156},[146,23302,727],{"class":160},[146,23304,23305],{"class":1554},"construct new address space for OPC UA",[146,23307,727],{"class":160},[146,23309,1477],{"class":156},[146,23311,182],{"class":160},[146,23313,23314],{"class":148,"line":257},[146,23315,254],{"emptyLinePlaceholder":253},[146,23317,23318,23320,23323,23325,23328,23330,23333,23336,23338,23341,23343],{"class":148,"line":284},[146,23319,22999],{"class":152},[146,23321,23322],{"class":156}," myDevice ",[146,23324,161],{"class":160},[146,23326,23327],{"class":156}," namespace",[146,23329,167],{"class":160},[146,23331,23332],{"class":170},"addFolder",[146,23334,23335],{"class":156},"(rootFolder",[146,23337,167],{"class":160},[146,23339,23340],{"class":156},"objects",[146,23342,276],{"class":160},[146,23344,1517],{"class":160},[146,23346,23347,23349,23351,23353,23355,23357,23360],{"class":148,"line":666},[146,23348,3438],{"class":160},[146,23350,20311],{"class":1549},[146,23352,727],{"class":160},[146,23354,730],{"class":160},[146,23356,1830],{"class":160},[146,23358,23359],{"class":1554},"RaspberryPI-Zero-WLAN",[146,23361,2561],{"class":160},[146,23363,23364,23367,23369],{"class":148,"line":1603},[146,23365,23366],{"class":160},"  }",[146,23368,1477],{"class":156},[146,23370,182],{"class":160},[146,23372,23373],{"class":148,"line":1608},[146,23374,22630],{"class":160},[15,23376,23377],{},"Then, with our variables and folder structure defined, nodes are added to the namespace for each context variable.",[42,23379,23381],{"className":140,"code":23380,"language":142,"meta":50,"style":50},"  const gpioDI1 = namespace.addVariable({\n    \"organizedBy\": isoInputs,\n    \"browseName\": \"I1\",\n    \"nodeId\": \"ns=1;s=Isolated_Input1\",\n    \"dataType\": \"Double\",\n    \"value\": {\n      \"get\": function() {\n        return new Variant({\n          \"dataType\": DataType.Double,\n          \"value\": flexServerInternals.sandboxFlowContext.get(\"isoInput1\")\n        });\n      },\n      \"set\": function(variant) {\n        flexServerInternals.sandboxFlowContext.set(\n          \"isoInput1\",\n          parseFloat(variant.value)\n        );\n        return opcua.StatusCodes.Good;\n      }\n    }\n  });\n\n...\n",[19,23382,23383,23403,23419,23438,23457,23477,23489,23506,23519,23539,23570,23579,23584,23605,23620,23630,23645,23652,23671,23675,23679,23687,23691],{"__ignoreMap":50},[146,23384,23385,23387,23390,23392,23394,23396,23399,23401],{"class":148,"line":149},[146,23386,22999],{"class":152},[146,23388,23389],{"class":156}," gpioDI1 ",[146,23391,161],{"class":160},[146,23393,23327],{"class":156},[146,23395,167],{"class":160},[146,23397,23398],{"class":170},"addVariable",[146,23400,203],{"class":156},[146,23402,717],{"class":160},[146,23404,23405,23407,23410,23412,23414,23417],{"class":148,"line":185},[146,23406,3438],{"class":160},[146,23408,23409],{"class":1549},"organizedBy",[146,23411,727],{"class":160},[146,23413,730],{"class":160},[146,23415,23416],{"class":156}," isoInputs",[146,23418,736],{"class":160},[146,23420,23421,23423,23425,23427,23429,23431,23434,23436],{"class":148,"line":221},[146,23422,3438],{"class":160},[146,23424,20311],{"class":1549},[146,23426,727],{"class":160},[146,23428,730],{"class":160},[146,23430,1830],{"class":160},[146,23432,23433],{"class":1554},"I1",[146,23435,727],{"class":160},[146,23437,736],{"class":160},[146,23439,23440,23442,23444,23446,23448,23450,23453,23455],{"class":148,"line":250},[146,23441,3438],{"class":160},[146,23443,20322],{"class":1549},[146,23445,727],{"class":160},[146,23447,730],{"class":160},[146,23449,1830],{"class":160},[146,23451,23452],{"class":1554},"ns=1;s=Isolated_Input1",[146,23454,727],{"class":160},[146,23456,736],{"class":160},[146,23458,23459,23461,23464,23466,23468,23470,23473,23475],{"class":148,"line":257},[146,23460,3438],{"class":160},[146,23462,23463],{"class":1549},"dataType",[146,23465,727],{"class":160},[146,23467,730],{"class":160},[146,23469,1830],{"class":160},[146,23471,23472],{"class":1554},"Double",[146,23474,727],{"class":160},[146,23476,736],{"class":160},[146,23478,23479,23481,23483,23485,23487],{"class":148,"line":284},[146,23480,3438],{"class":160},[146,23482,1587],{"class":1549},[146,23484,727],{"class":160},[146,23486,730],{"class":160},[146,23488,1517],{"class":160},[146,23490,23491,23493,23495,23497,23499,23502,23504],{"class":148,"line":666},[146,23492,2432],{"class":160},[146,23494,13879],{"class":1549},[146,23496,727],{"class":160},[146,23498,730],{"class":160},[146,23500,23501],{"class":152}," function",[146,23503,1461],{"class":160},[146,23505,1517],{"class":160},[146,23507,23508,23510,23512,23515,23517],{"class":148,"line":1603},[146,23509,10549],{"class":287},[146,23511,16154],{"class":160},[146,23513,23514],{"class":170}," Variant",[146,23516,203],{"class":1549},[146,23518,717],{"class":160},[146,23520,23521,23524,23526,23528,23530,23533,23535,23537],{"class":148,"line":1608},[146,23522,23523],{"class":160},"          \"",[146,23525,23463],{"class":1549},[146,23527,727],{"class":160},[146,23529,730],{"class":160},[146,23531,23532],{"class":156}," DataType",[146,23534,167],{"class":160},[146,23536,23472],{"class":156},[146,23538,736],{"class":160},[146,23540,23541,23543,23545,23547,23549,23552,23554,23556,23558,23560,23562,23564,23566,23568],{"class":148,"line":1624},[146,23542,23523],{"class":160},[146,23544,1587],{"class":1549},[146,23546,727],{"class":160},[146,23548,730],{"class":160},[146,23550,23551],{"class":156}," flexServerInternals",[146,23553,167],{"class":160},[146,23555,23071],{"class":156},[146,23557,167],{"class":160},[146,23559,13879],{"class":170},[146,23561,203],{"class":1549},[146,23563,727],{"class":160},[146,23565,23082],{"class":1554},[146,23567,727],{"class":160},[146,23569,8128],{"class":1549},[146,23571,23572,23575,23577],{"class":148,"line":2546},[146,23573,23574],{"class":160},"        }",[146,23576,1477],{"class":1549},[146,23578,182],{"class":160},[146,23580,23581],{"class":148,"line":2564},[146,23582,23583],{"class":160},"      },\n",[146,23585,23586,23588,23590,23592,23594,23596,23598,23601,23603],{"class":148,"line":2569},[146,23587,2432],{"class":160},[146,23589,13786],{"class":1549},[146,23591,727],{"class":160},[146,23593,730],{"class":160},[146,23595,23501],{"class":152},[146,23597,203],{"class":160},[146,23599,23600],{"class":1510},"variant",[146,23602,1477],{"class":160},[146,23604,1517],{"class":160},[146,23606,23607,23610,23612,23614,23616,23618],{"class":148,"line":2574},[146,23608,23609],{"class":156},"        flexServerInternals",[146,23611,167],{"class":160},[146,23613,23071],{"class":156},[146,23615,167],{"class":160},[146,23617,13786],{"class":170},[146,23619,23123],{"class":1549},[146,23621,23622,23624,23626,23628],{"class":148,"line":2594},[146,23623,23523],{"class":160},[146,23625,23082],{"class":1554},[146,23627,727],{"class":160},[146,23629,736],{"class":160},[146,23631,23632,23635,23637,23639,23641,23643],{"class":148,"line":2614},[146,23633,23634],{"class":170},"          parseFloat",[146,23636,203],{"class":1549},[146,23638,23600],{"class":156},[146,23640,167],{"class":160},[146,23642,1587],{"class":156},[146,23644,8128],{"class":1549},[146,23646,23647,23650],{"class":148,"line":2633},[146,23648,23649],{"class":1549},"        )",[146,23651,182],{"class":160},[146,23653,23654,23656,23659,23661,23664,23666,23669],{"class":148,"line":2662},[146,23655,10549],{"class":287},[146,23657,23658],{"class":156}," opcua",[146,23660,167],{"class":160},[146,23662,23663],{"class":156},"StatusCodes",[146,23665,167],{"class":160},[146,23667,23668],{"class":156},"Good",[146,23670,182],{"class":160},[146,23672,23673],{"class":148,"line":2667},[146,23674,3213],{"class":160},[146,23676,23677],{"class":148,"line":2672},[146,23678,3412],{"class":160},[146,23680,23681,23683,23685],{"class":148,"line":2692},[146,23682,23366],{"class":160},[146,23684,1477],{"class":156},[146,23686,182],{"class":160},[146,23688,23689],{"class":148,"line":2705},[146,23690,254],{"emptyLinePlaceholder":253},[146,23692,23693],{"class":148,"line":2722},[146,23694,22630],{"class":160},[15,23696,23697],{},"Last, OPC views are defined.  Views create custom hierarchies our OPC Client can browse as an alternative to the default folder structure.",[42,23699,23701],{"className":140,"code":23700,"language":142,"meta":50,"style":50},"  const viewDI = namespace.addView({\n    \"organizedBy\": rootFolder.views,\n    \"browseName\": \"RPIW0-Digital-Ins\"\n  });\n\n  const viewDO = namespace.addView({\n    \"organizedBy\": rootFolder.views,\n    \"browseName\": \"RPIW0-Digital-Outs\"\n  });\n\n  viewDI.addReference({\n    \"referenceType\": \"Organizes\",\n    \"nodeId\": gpioDI1.nodeId\n  });\n\n...\n\n",[19,23702,23703,23723,23743,23760,23768,23772,23791,23809,23826,23834,23838,23852,23872,23890,23898,23902],{"__ignoreMap":50},[146,23704,23705,23707,23710,23712,23714,23716,23719,23721],{"class":148,"line":149},[146,23706,22999],{"class":152},[146,23708,23709],{"class":156}," viewDI ",[146,23711,161],{"class":160},[146,23713,23327],{"class":156},[146,23715,167],{"class":160},[146,23717,23718],{"class":170},"addView",[146,23720,203],{"class":156},[146,23722,717],{"class":160},[146,23724,23725,23727,23729,23731,23733,23736,23738,23741],{"class":148,"line":185},[146,23726,3438],{"class":160},[146,23728,23409],{"class":1549},[146,23730,727],{"class":160},[146,23732,730],{"class":160},[146,23734,23735],{"class":156}," rootFolder",[146,23737,167],{"class":160},[146,23739,23740],{"class":156},"views",[146,23742,736],{"class":160},[146,23744,23745,23747,23749,23751,23753,23755,23758],{"class":148,"line":221},[146,23746,3438],{"class":160},[146,23748,20311],{"class":1549},[146,23750,727],{"class":160},[146,23752,730],{"class":160},[146,23754,1830],{"class":160},[146,23756,23757],{"class":1554},"RPIW0-Digital-Ins",[146,23759,2561],{"class":160},[146,23761,23762,23764,23766],{"class":148,"line":250},[146,23763,23366],{"class":160},[146,23765,1477],{"class":156},[146,23767,182],{"class":160},[146,23769,23770],{"class":148,"line":257},[146,23771,254],{"emptyLinePlaceholder":253},[146,23773,23774,23776,23779,23781,23783,23785,23787,23789],{"class":148,"line":284},[146,23775,22999],{"class":152},[146,23777,23778],{"class":156}," viewDO ",[146,23780,161],{"class":160},[146,23782,23327],{"class":156},[146,23784,167],{"class":160},[146,23786,23718],{"class":170},[146,23788,203],{"class":156},[146,23790,717],{"class":160},[146,23792,23793,23795,23797,23799,23801,23803,23805,23807],{"class":148,"line":666},[146,23794,3438],{"class":160},[146,23796,23409],{"class":1549},[146,23798,727],{"class":160},[146,23800,730],{"class":160},[146,23802,23735],{"class":156},[146,23804,167],{"class":160},[146,23806,23740],{"class":156},[146,23808,736],{"class":160},[146,23810,23811,23813,23815,23817,23819,23821,23824],{"class":148,"line":1603},[146,23812,3438],{"class":160},[146,23814,20311],{"class":1549},[146,23816,727],{"class":160},[146,23818,730],{"class":160},[146,23820,1830],{"class":160},[146,23822,23823],{"class":1554},"RPIW0-Digital-Outs",[146,23825,2561],{"class":160},[146,23827,23828,23830,23832],{"class":148,"line":1608},[146,23829,23366],{"class":160},[146,23831,1477],{"class":156},[146,23833,182],{"class":160},[146,23835,23836],{"class":148,"line":1624},[146,23837,254],{"emptyLinePlaceholder":253},[146,23839,23840,23843,23845,23848,23850],{"class":148,"line":2546},[146,23841,23842],{"class":156},"  viewDI",[146,23844,167],{"class":160},[146,23846,23847],{"class":170},"addReference",[146,23849,203],{"class":156},[146,23851,717],{"class":160},[146,23853,23854,23856,23859,23861,23863,23865,23868,23870],{"class":148,"line":2564},[146,23855,3438],{"class":160},[146,23857,23858],{"class":1549},"referenceType",[146,23860,727],{"class":160},[146,23862,730],{"class":160},[146,23864,1830],{"class":160},[146,23866,23867],{"class":1554},"Organizes",[146,23869,727],{"class":160},[146,23871,736],{"class":160},[146,23873,23874,23876,23878,23880,23882,23885,23887],{"class":148,"line":2569},[146,23875,3438],{"class":160},[146,23877,20322],{"class":1549},[146,23879,727],{"class":160},[146,23881,730],{"class":160},[146,23883,23884],{"class":156}," gpioDI1",[146,23886,167],{"class":160},[146,23888,23889],{"class":156},"nodeId\n",[146,23891,23892,23894,23896],{"class":148,"line":2574},[146,23893,23366],{"class":160},[146,23895,1477],{"class":156},[146,23897,182],{"class":160},[146,23899,23900],{"class":148,"line":2594},[146,23901,254],{"emptyLinePlaceholder":253},[146,23903,23904],{"class":148,"line":2614},[146,23905,22630],{"class":160},[15,23907,23908,23909,23912],{},"Finally, on the ",[19,23910,23911],{},"Discovery"," tab, we must define an endpoint for an OPC Client to subscribe to.",[15,23914,1714,23915,23918,23919,23922,23923,23925,23926,23929,23930],{},[19,23916,23917],{},"Endpoint Url"," follows the format ",[19,23920,23921],{},"opc.tcp:\u002F\u002F\u003Caddress>:port",".  Our port was defined on the ",[19,23924,22931],{}," tab, which by default, is port ",[19,23927,23928],{},"54845",". The address will be either the url or ip address of your Node-RED instance.  In my case, it’s 192.168.0.114.  So my Endpoint Url = ",[19,23931,23932],{},"opc.tcp:\u002F\u002F192.168.0.114:54845",[15,23934,23935,23939],{},[392,23936],{"alt":23937,"src":23938,"title":23937},"Screenshot showing the Discovery Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fdiscovery-tab.png","\nOnce the endpoint url is added, deploy the flow, and confirm the server is reporting “active”.",[15,23941,23942],{},[392,23943],{"alt":23944,"src":23945,"title":23944},"Screenshot showing the Active Tab of compact server node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fcompact-server-active.png",[34,23947,23949],{"id":23948},"connect-to-example-opc-server-using-opc-ua-browser","Connect to Example OPC-Server Using OPC-UA Browser",[15,23951,23952,23953,23958],{},"To connect to our OPC endpoint, we need an OPC Client.  Prosys provides a ",[307,23954,23957],{"href":23955,"rel":23956},"https:\u002F\u002Fwww.prosysopc.com\u002Fproducts\u002Fopc-ua-browser\u002F",[311],"free OPC-UA Browser ","that supports Windows, Linux, and Mac OS.  To test our Server, the Windows version of Prosys OPC-UA Browser will be utilized.",[15,23960,23961],{},"To connect to our Node-RED OPC server, enter the endpoint url and press “connect to server”.",[15,23963,23964,23969,23970,23973],{},[392,23965],{"alt":23966,"src":23967,"title":23968},"\"Screenshot showing the OPC Client\"","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-connect.png","Screenshot showing the OPC Client","\nIt will ask for security.  Remember that we allowed anonymous access, so the default security mode of ",[19,23971,23972],{},"None"," is the correct option.",[15,23975,23976],{},"Once connected, we can browse our OPC Server.",[15,23978,23979,23983,23984,23987,23988,23990],{},[392,23980],{"alt":23981,"src":23982},"OPC Client UI","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-ui.png","\nIf we navigate to ",[19,23985,23986],{},"Objects → RaspberryPI-Zero-WLAN → GPIO → Inputs",", we can see a list of inputs that correspond to the ",[19,23989,23053],{}," context variables defined in the example flow, which are randomly generated numbers.",[15,23992,23993,23994,23996],{},"Clicking ",[19,23995,23433],{}," we can see the value in real-time, along with some additional properties.",[15,23998,23999,24003,24004,24007],{},[392,24000],{"alt":24001,"src":24002,"title":24001},"OPC Client Node","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-node.png","\nIf we go to ",[19,24005,24006],{},"Views",", we can see the custom hierarchy defined in the example server, which divides the data by Digital-Ins and Digital-Outs.",[15,24009,24010],{},[392,24011],{"alt":24012,"src":24013},"OPC Client View","\u002Fblog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-client-view.png",[34,24015,18578],{"id":18577},[15,24017,24018],{},"In this article, we compare OPC-UA to traditional fieldbus protocols, explain the importance of the OPC UA Information Model to understand how data is modeled in the address space of an OPC Server, and then walk through and deploy an example compact OPC-UA Server flow.",[15,24020,24021],{},"In our next article, we will build a custom OPC-UA Server in Node-RED with data pulled from an Allen Bradley PLC over Ethernet\u002FIP, using the PLC data to develop a custom OPC UA Information Model programmed in the OPC server address space.",[924,24023,24024],{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}",{"title":50,"searchDepth":250,"depth":250,"links":24026},[24027,24028,24029,24030,24031],{"id":22219,"depth":185,"text":22220},{"id":22244,"depth":185,"text":22245},{"id":22330,"depth":185,"text":22331},{"id":23948,"depth":185,"text":23949},{"id":18577,"depth":185,"text":18578},{"type":941,"title":24033,"description":24034},"Run Your OPC UA Node-RED Server in the Cloud","Sign up for FlowFuse to deploy, manage, and scale your Node-RED OPC UA server flows securely in the cloud with built-in DevOps pipelines.","2023-07-13","Introduction to OPC-UA and how to deploy a Node-RED server flow.","blog\u002F2023\u002F07\u002Fimages\u002Fopc-ua-1\u002Fopc-ua-1-title-image.png",{"keywords":24039,"excerpt":24040},"opc ua server, opc ua server free, opc ua gateway, opc ua example, node-red-contrib-opcua, nodered opcua, node red opcua, node-red opcua, opcua node red, opcua nodered",{"type":12,"value":24041},[24042],[15,24043,22216],{},"\u002Fblog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red",{"title":22210,"description":24036},{"loc":24044},"blog\u002F2023\u002F07\u002Fhow-to-deploy-a-basic-opc-ua-server-in-node-red","OPC-UA Server Information Modeling in Node-RED",[9832,6563,2055,966],"This first installment in an OPC UA series explains the OPC UA information model how devices are represented as structured node objects rather than raw register values and walks through deploying a working OPC UA server flow in Node-RED using the node-red-contrib-opcua package. Understanding the information model and address-space hierarchy is the key prerequisite before building OPC UA clients or integrations.","aqJxabkyaGvGNG0wnFPvtB40UOJhS_j2ZBJQHM1dw2U",{"id":24053,"title":9800,"authors":24054,"body":24055,"cta":3,"date":24706,"description":24707,"extension":946,"image":24708,"lastUpdated":3,"meta":24709,"navigation":253,"path":24716,"seo":24717,"sitemap":24718,"stem":24719,"subtitle":24720,"tags":24721,"tldr":3,"video":24730,"__hash__":24731},"blog\u002Fblog\u002F2023\u002F06\u002Fnode-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter.md",[19912],{"type":12,"value":24056,"toc":24689},[24057,24063,24067,24074,24089,24093,24095,24101,24108,24112,24115,24199,24202,24205,24212,24215,24222,24226,24229,24254,24258,24261,24265,24268,24271,24277,24290,24297,24306,24318,24325,24329,24336,24343,24350,24357,24370,24377,24382,24393,24399,24410,24419,24422,24428,24431,24437,24440,24451,24457,24463,24466,24472,24476,24482,24488,24494,24501,24507,24519,24524,24531,24537,24544,24547,24554,24558,24561,24568,24574,24582,24586,24589,24596,24607,24610,24613,24619,24625,24631,24634,24636,24647,24650,24653],[15,24058,24059,24060,24062],{},"Frequently in industrial automation, there's a need for two devices that use different protocols to communicate with each other, requiring protocol conversion.",[1290,24061],{},"\nIn this tutorial, we present a mock scenario where Node-RED is used to enable an Allen Bradley PLC, which uses ethernet\u002FIP, to communicate with a Siemens PLC, which uses S7, using a no-code solution. This example is geared toward beginners and assumes that the end-user knows how to use PLCs, but may be using FlowFuse or Node-RED for the first time.",[34,24064,24066],{"id":24065},"premise","Premise",[15,24068,24069],{},[392,24070],{"alt":24071,"src":24072,"title":24073},"Mock production facility","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-1.png","FlowFuse Mock production facility",[15,24075,24076,24077,24079,24080,24082,24083,24085,24086,24088],{},"The figure above shows the layout of a mock production facility. Inside this facility, operations suggested adding stack lights as an extra visual aid for operators to get a quick status of its 4 conveyor lines, avoiding the need to constantly monitor the HMI\u002FSCADA displays.",[1290,24078],{},"\nEngineering has suggested adding a siemens S7 1200 PLC with an IO link connection to 4 stacklights, with each line PLC sending basic status information to the stacklight PLC to control the stack light outputs.",[1290,24081],{},"\nLine 1-3 PLCs are Siemens-based, and can communicate with the stacklight PLC natively over S7. But line 4 is an Allen Bradley PLC that uses ethernet\u002FIP, and can't communicate with the stacklight PLC without some form of protocol conversion.",[1290,24084],{},"\nTraditionally, we'd use protocol gateway hardware, like Anybus or Red Lion, to convert ethernet\u002FIP to S7.",[1290,24087],{},"\nBut for this application, we will instead use FlowFuse, a pure software-based approach, to convert ethernet\u002FIP to S7. Let's walk through the process.",[34,24090,24092],{"id":24091},"pre-requisites-and-set-up","Pre-Requisites and Set Up",[996,24094,1155],{"id":965},[15,24096,24097,24098,7960],{},"In addition to our two PLCs, we’ll be using FlowFuse software to serve our Node-RED instance. You can either self-host, on-premise or in the cloud. Or use the managed service [FlowFuse Cloud](",[17483,24099],{"value":24100},"site.appURL",[15,24102,24103,24104,167],{},"In this example, we will be using a self-hosted FlowFuse instance running on ",[307,24105,24107],{"href":24106},"\u002Fdocs\u002Finstall\u002Fdocker\u002F","Docker",[996,24109,24111],{"id":24110},"data-treatment-on-ethernetip-plc","Data Treatment on Ethernet\u002FIP PLC",[15,24113,24114],{},"In our Allen Bradley line 4 PLC, we will send some arbitrary tags of various datatypes to the stacklight PLC for illustrative purposes, described in table 1 below -",[1053,24116,24117,24134],{},[1056,24118,24119],{},[1059,24120,24121,24126,24130],{},[1062,24122,24123],{},[338,24124,24125],{},"Tag",[1062,24127,24128],{},[338,24129,5495],{},[1062,24131,24132],{},[338,24133,2187],{},[1070,24135,24136,24147,24157,24168,24178,24189],{},[1059,24137,24138,24141,24144],{},[1075,24139,24140],{},"Conveyor_RTS",[1075,24142,24143],{},"BOOL",[1075,24145,24146],{},"Conveyor Ready to Start",[1059,24148,24149,24152,24154],{},[1075,24150,24151],{},"Robot_RTS",[1075,24153,24143],{},[1075,24155,24156],{},"Robot is Ready to Start",[1059,24158,24159,24162,24165],{},[1075,24160,24161],{},"Robot_Position",[1075,24163,24164],{},"REAL",[1075,24166,24167],{},"Robot Arm position (degrees)",[1059,24169,24170,24173,24175],{},[1075,24171,24172],{},"Conveyor_Running",[1075,24174,24143],{},[1075,24176,24177],{},"Conveyor is running",[1059,24179,24180,24183,24186],{},[1075,24181,24182],{},"Line4_State",[1075,24184,24185],{},"DINT",[1075,24187,24188],{},"Line 4 Machine State",[1059,24190,24191,24194,24196],{},[1075,24192,24193],{},"Line4_Fault",[1075,24195,24143],{},[1075,24197,24198],{},"Line 4 is faulted",[15,24200,24201],{},"Table 1 - Line 4 Tags to be sent to Stacklight PLC",[15,24203,24204],{},"We can send any atomic data type we want, but it must be globally (controller) scoped.",[15,24206,24207],{},[392,24208],{"alt":24209,"src":24210,"title":24211},"\"Screenshot showing the AB Controller Tags\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-2.png","Screenshot showing the AB Controller Tags",[15,24213,24214],{},"Each tag must also have external read\u002Fwrite access enabled.",[15,24216,24217],{},[392,24218],{"alt":24219,"src":24220,"title":24221},"\"Screenshot showing the AB Tag Properties\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-3.png","Screenshot showing the AB Tag Properties",[996,24223,24225],{"id":24224},"data-treatment-on-s7-plc","Data Treatment on S7 PLC",[15,24227,24228],{},"In the Siemens PLC, we have a DB for the data from the Line 4 PLC to be written to.",[100,24230,24231,24234,24237,24247],{},[103,24232,24233],{},"In the DBs attributes, “optimized block access” must be disabled.",[103,24235,24236],{},"The tags must be writeable and accessible",[103,24238,24239,24244,24246],{},[392,24240],{"alt":24241,"src":24242,"title":24243},"\"Screenshot showing the Siemens Tag DB Properties\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-4.png","Screenshot showing the Siemens Tag DB Properties",[1290,24245],{},"“No protection” must be set in the CPU properties",[103,24248,24249],{},[392,24250],{"alt":24251,"src":24252,"title":24253},"\"Screenshot showing the Siemens CPU Properties\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-5.png","Screenshot showing the Siemens CPU Properties",[34,24255,24257],{"id":24256},"create-the-flow","Create The Flow",[15,24259,24260],{},"With both PLCs up and running and properly set up to send\u002Freceive remote data, we can now create a flow to act as our protocol converter.",[996,24262,24264],{"id":24263},"install-custom-nodes","Install Custom Nodes",[15,24266,24267],{},"First, we need to add two custom nodes that will give Node-RED the ability to read\u002Fwrite ethernet\u002FIP and S7 data.",[15,24269,24270],{},"Click the hamburger icon → manage pallette",[15,24272,24273],{},[392,24274],{"alt":24275,"src":24276},"Screenshot showing the 'Manage palette option' in the menu","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-6.png",[15,24278,24279,24280,24283,24284,24287,24288,1361],{},"On the ",[19,24281,24282],{},"install"," tab, search for ",[19,24285,24286],{},"s7"," and install the ",[19,24289,5434],{},[15,24291,24292],{},[392,24293],{"alt":24294,"src":24295,"title":24296},"\"Installing S7 node\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-7.png","Installing S7 node",[15,24298,24299,24300,24287,24303,1361],{},"Next, search for ",[19,24301,24302],{},"ethernet",[19,24304,24305],{},"node-red-contrib-cip-ethernet-ip",[15,24307,24308,24313,24314,24317],{},[392,24309],{"alt":24310,"src":24311,"title":24312},"\"InstallING EthernetIP Node\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-8.png","InstallING EthernetIP Node","\nGo to the ",[19,24315,24316],{},"nodes"," tab and confirm both custom nodes have been properly installed.",[15,24319,24320],{},[392,24321],{"alt":24322,"src":24323,"title":24324},"\"Screenshot of 'Nodes' tab showing Installed nodes List\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-9.png","Screenshot of 'Nodes' tab showing Installed nodes List",[996,24326,24328],{"id":24327},"set-up-ethernetip-data","Set Up Ethernet\u002FIP Data",[15,24330,24331,24332,24335],{},"Let’s start by dragging a ",[19,24333,24334],{},"eth-ip in"," node onto the pallette. Then add a new endpoint, which will point to our Line4 PLC.",[15,24337,24338],{},[392,24339],{"alt":24340,"src":24341,"title":24342},"\"Screenshot showing dragged 'eth-ip in' node and it's config tab\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-10.png","Screenshot showing dragged 'eth-ip in' node and it's config tab",[15,24344,24345,24346,24349],{},"In the endpoint ",[19,24347,24348],{},"connection"," properties, the connection information must match the PLC, so set the IP address and CPU slot number appropriately. Also, the default cycle time is 500ms. Depending on your application, polling the CPU at 500ms may be appropriate. But being that this is a simple stacklight, 500ms is unnecessarily fast. So we will change it to 1000ms, which is a more appropriate polling rate for this type of application.",[15,24351,24352],{},[392,24353],{"alt":24354,"src":24355,"title":24356},"\"Screenshot showing the eth-ip Endpoint config\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-11.png","Screenshot showing the eth-ip Endpoint config",[15,24358,24279,24359,24362,24363,24366,24367,167],{},[19,24360,24361],{},"Tags"," tab, populate the tag information to match our Allen Bradley PLC. Then select ",[19,24364,24365],{},"Update"," to complete configuration of the ",[19,24368,24369],{},"eth-ip endpoint",[15,24371,24372],{},[392,24373],{"alt":24374,"src":24375,"title":24376},"\"Screenshot showing eth-ip Endpoint Tags\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-12.png","Screenshot showing eth-ip Endpoint Tags",[15,24378,24379,24380,1361],{},"Now that we have our endpoint, let’s finish configuring the ",[19,24381,24334],{},[326,24383,24384,24387,24390],{},[103,24385,24386],{},"select the endpoint we just created",[103,24388,24389],{},"select the first tag in the drop-down",[103,24391,24392],{},"give the node a descriptive name",[15,24394,24395],{},[392,24396],{"alt":24397,"src":24398,"title":24397},"Screeshot showing the eth-ip in Node config","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-13.png",[15,24400,24401,24402,1358,24404,24406,24407,167],{},"Now let’s set up a quick test to confirm our PLC connection is valid by adding a ",[19,24403,1395],{},[19,24405,24334],{}," node. Then hit ",[19,24408,24409],{},"deploy",[100,24411,24412],{},[103,24413,24414,24415,24418],{},"note - you can see we also have a ",[19,24416,24417],{},"comment"," above the nodes that describes what is happening. This is optional but good practice to help organize and understand your flow.",[15,24420,24421],{},"The output of the debug console did not report any errors so communication appears to be okay.",[15,24423,24424],{},[392,24425],{"alt":24426,"src":24427,"title":24426},"Screenshot showing the output of eth-ip in Debug panel","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-14.png",[15,24429,24430],{},"But just to confirm, let’s toggle the value and see if comes through.",[15,24432,24433],{},[392,24434],{"alt":24435,"src":24436,"title":24435},"Screenshot showing the eth-ip node output in Debug panel after Toggle","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-15.png",[15,24438,24439],{},"So by toggling the value and see the result, here we confirmed 2 things:",[100,24441,24442,24445],{},[103,24443,24444],{},"We can detect changes in value",[103,24446,24447,24448,24450],{},"the ",[19,24449,24334],{}," node only sends a message when the value changes, also known as Report by Exception.",[15,24452,24453,24454,24456],{},"Because the ",[19,24455,24334],{}," node implicitly uses report by exception, and the protocol doesn't rely on contiguous data consistency (unlike modbus, for instance), we can receive our data one tag at a time to keep our flow simple.",[15,24458,24459,24460,24462],{},"Now we can remove the debug node and add the additional ",[19,24461,24334],{}," nodes to receive the remaining tags from our Line 4 PLC.",[15,24464,24465],{},"Here’s how the the flow should look at this point.",[15,24467,24468],{},[392,24469],{"alt":24470,"src":24471,"title":24470},"Screenshot of Line 4 PLC Nodes","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-16.png",[996,24473,24475],{"id":24474},"set-up-s7-data","Set Up S7 Data",[15,24477,24478,24479,1361],{},"Now we’ll set up the S7 endpoint, using an ",[19,24480,24481],{},"s7 out",[15,24483,24484],{},[392,24485],{"alt":24486,"src":24487,"title":24486},"Screenshot of s7 out Node on Palette","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-17.png",[15,24489,24490,24491,24493],{},"Populate the connection properties to match your hardware. The cycle time is updated to 1000ms to match the cycle time of our ",[19,24492,24334],{}," nodes. You can adjust this value to match your intended application.",[15,24495,24496],{},[392,24497],{"alt":24498,"src":24499,"title":24500},"\"Screenshot showing the S7 endpoint Connection\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-18.png","Screenshot showing the S7 endpoint Connection",[15,24502,24279,24503,24506],{},[19,24504,24505],{},"Variables"," tab, some special formatting is required to point to the absolute reference of the tag DB location in the S7 PLC.",[15,24508,24509,24510,24513,24514,24518],{},"For information on how to format S7 absolute tag references in a way the ",[19,24511,24512],{},"s7 endpoint"," node is expecting, refer to the ",[307,24515,24517],{"href":6165,"rel":24516},[311],"node documentation"," for further information.",[15,24520,24521,24522,167],{},"For reference, here is an example of how we set the tags in our stacklight PLC example and how it looks in our ",[19,24523,24512],{},[15,24525,24526],{},[392,24527],{"alt":24528,"src":24529,"title":24530},"\"Screenshot of s7 endpoint Variables\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-19.png","Screenshot of s7 endpoint Variables",[15,24532,24533,24534,24536],{},"Once the tags are populated we can select our configured endpoint from the dropdown list, point to our first variable, ",[19,24535,24140],{},", and give the node a name.",[15,24538,24539],{},[392,24540],{"alt":24541,"src":24542,"title":24543},"\"Screenshot of S7 out Config\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-20.png","Screenshot of S7 out Config",[15,24545,24546],{},"Repeat this process for the remaining tags.",[15,24548,24549],{},[392,24550],{"alt":24551,"src":24552,"title":24553},"\"Screenshot of Stacklight PLC Nodes\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-21.png","Screenshot of Stacklight PLC Nodes",[34,24555,24557],{"id":24556},"test-the-conversion","Test the Conversion",[15,24559,24560],{},"The only thing remaining is to simply wire the nodes together, and confirm the values pass through.",[15,24562,24563],{},[392,24564],{"alt":24565,"src":24566,"title":24567},"\"Screenshot of the complete flow with live Data\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-22.png","Screenshot of the complete flow with live Data",[15,24569,24570,24571,24573],{},"Manipulate the incoming values and confirm the data passes through as expected. Because of the report by exception nature of the ",[19,24572,24334],{}," node, tag changes should be near instantaneous on the receiving PLC.",[15,24575,24576,24577,24579,24580,167],{},"We can stop here, but we can improve this flow by adding a ",[19,24578,15371],{}," node on our REAL data-type, ",[19,24581,24161],{},[996,24583,24585],{"id":24584},"add-filter-to-real-data","Add Filter to REAL data",[15,24587,24588],{},"Depending on how noisy the REAL data is, which is common with unfiltered 4-20mA field transmitters, and how much granularity you need to capture, it is good practice to add a filter on REAL data to reduce FieldBus traffic coming out of our soft protocol converter.",[15,24590,24591],{},[392,24592],{"alt":24593,"src":24594,"title":24595},"\"Screenshot showing the Filter node Configuration\"","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-23.png","Screenshot showing the Filter node Configuration",[15,24597,24598,24599,24603,24604,24606],{},"In the example above, we arbitrarily applied a 3% ",[307,24600,24602],{"href":24601},"\u002Fnode-red\u002Fcore-nodes\u002Ffilter\u002F","deadband","\nto the ",[19,24605,24161],{}," 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.",[15,24608,24609],{},"You can adjust the deadband to find the right balance for your particular application.",[15,24611,24612],{},"We can see the effect the deadband filter had by adding debug nodes before and after the filter.",[15,24614,24615],{},[392,24616],{"alt":24617,"src":24618,"title":24617},"Filter Node Debug","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002Fe-to-p-24.png",[15,24620,24621,24622,24624],{},"As shown above, when ",[19,24623,24161],{}," changed from 15.6 to 15.6999..., the value was captured on the input of the filter, but was discarded on the output.",[15,24626,24627,24628,24630],{},"When the ",[19,24629,24161],{}," went from 15.6999 to 18, the filter allowed it to pass as it exceeded the deadband limit we had set.",[15,24632,24633],{},"Use filters to optimize your fieldbus converter network performance, especially if dealing with noisy signals or large quantities of REAL datatypes.",[34,24635,6518],{"id":6517},[15,24637,24638,24639,63,24641,24643,24644,24646],{},"In this tutorial, we demonstrated how to use Node-RED as a free Ethernet\u002FIP to S7 protocol converter using a simple no-code approach.  We showed how to configure PLC tags to be sent remotely using Ethernet\u002FIP, how to configure PLC tags to be received remotely using S7, and how to create the flow to use Node-RED to seamlessly convert incoming PLC data between the two protocols using ",[19,24640,24305],{},[19,24642,5434],{}," custom nodes.  We also took things one step further and added a ",[19,24645,15371],{}," node to optimize FieldBus network traffic by putting a deadband on REAL data being sent to the receiving PLC.",[15,24648,24649],{},"The end result is a simple to set up, free and performant industrial protocol converter that requires minimal PLC configuration, which allows this application to be applied in non-mission critical production systems with minimal, if any downtime.  Additionally, the protocol traffic can be visually observed in real-time for easy trouble-shooting and fault analysis by simply accessing the Node-RED UI.",[15,24651,24652],{},"In later tutorials, we can show ways this simple flow can be extended to add additional capabilities not normally available in traditional off-the-shelf protocol gateways. If you found this tutorial helpful, or have any questions or comments, please leave us a comment and let us know your thoughts.",[15,24654,24655,24656,3830],{},"JSON source code for the flow used in this tutorial is provided below -\n{% renderFlow 500 %}\n",[146,24657,24658,24659,24661,24662,24664,24665,24667,24668,24670,24671,24673,24674,24676,24677,24679,24680,24682,24683,24685,24686,1596],{},"{\"id\":\"ad7b17411c8e83aa\",\"type\":\"tab\",\"label\":\"Line 4 to Stacklight PLC\",\"disabled\":false,\"info\":\"\",\"env\":",[146,24660],{},"},{\"id\":\"c97a4c9bd1981757\",\"type\":\"comment\",\"z\":\"ad7b17411c8e83aa\",\"name\":\"AB EIP\u002FCIP - Line 4 PLC\",\"info\":\"\",\"x\":190,\"y\":140,\"wires\":",[146,24663],{},"},{\"id\":\"2cc5227ef6a90814\",\"type\":\"eth-ip in\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"4ab2910b66e16220\",\"mode\":\"single\",\"variable\":\"Conveyor_RTS\",\"program\":\"\",\"name\":\"Read Conveyor_RTS\",\"x\":200,\"y\":200,\"wires\":[[\"fe18ef80f9e18c13\"]]},{\"id\":\"9308dcbda17274c7\",\"type\":\"comment\",\"z\":\"ad7b17411c8e83aa\",\"name\":\"Siemens S7 - Stacklight PLC\",\"info\":\"\",\"x\":620,\"y\":140,\"wires\":",[146,24666],{},"},{\"id\":\"fe18ef80f9e18c13\",\"type\":\"s7 out\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"a1bec25858c6f3ef\",\"variable\":\"Conveyor_RTS\",\"name\":\"Write Conveyor_RTS\",\"x\":620,\"y\":200,\"wires\":",[146,24669],{},"},{\"id\":\"94fe6b73efa1c56b\",\"type\":\"eth-ip in\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"4ab2910b66e16220\",\"mode\":\"single\",\"variable\":\"Robot_RTS\",\"program\":\"\",\"name\":\"Read Robot_RTS\",\"x\":180,\"y\":280,\"wires\":[[\"7774d6ce188c288c\"]]},{\"id\":\"7e9564cd59e3d0a2\",\"type\":\"eth-ip in\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"4ab2910b66e16220\",\"mode\":\"single\",\"variable\":\"Robot_Position\",\"program\":\"\",\"name\":\"Read Robot_Position\",\"x\":200,\"y\":360,\"wires\":[[\"832807bfdc4b76f0\"]]},{\"id\":\"c0f712b9e355f1f8\",\"type\":\"eth-ip in\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"4ab2910b66e16220\",\"mode\":\"single\",\"variable\":\"Conveyor_Running\",\"program\":\"\",\"name\":\"Read Conveyor_Running\",\"x\":210,\"y\":440,\"wires\":[[\"fbf1b3e38897a9c7\"]]},{\"id\":\"db77621e418f1222\",\"type\":\"eth-ip in\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"4ab2910b66e16220\",\"mode\":\"single\",\"variable\":\"Line4_State\",\"program\":\"\",\"name\":\"Read Line4_State\",\"x\":190,\"y\":520,\"wires\":[[\"cdeffd9e52cc4384\"]]},{\"id\":\"848af9b76f969dd2\",\"type\":\"eth-ip in\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"4ab2910b66e16220\",\"mode\":\"single\",\"variable\":\"Line4_Fault\",\"program\":\"\",\"name\":\"Read Line4_Fault\",\"x\":190,\"y\":600,\"wires\":[[\"0c595b0ac2550593\"]]},{\"id\":\"7774d6ce188c288c\",\"type\":\"s7 out\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"a1bec25858c6f3ef\",\"variable\":\"Robot_RTS\",\"name\":\"Write Robot_RTS\",\"x\":610,\"y\":280,\"wires\":",[146,24672],{},"},{\"id\":\"f1572463c50bb4cb\",\"type\":\"s7 out\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"a1bec25858c6f3ef\",\"variable\":\"Robot_Position\",\"name\":\"Write Robot_Position\",\"x\":620,\"y\":360,\"wires\":",[146,24675],{},"},{\"id\":\"fbf1b3e38897a9c7\",\"type\":\"s7 out\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"a1bec25858c6f3ef\",\"variable\":\"Conveyor_Running\",\"name\":\"Write Conveyor_Running\",\"x\":630,\"y\":440,\"wires\":",[146,24678],{},"},{\"id\":\"cdeffd9e52cc4384\",\"type\":\"s7 out\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"a1bec25858c6f3ef\",\"variable\":\"Line4_State\",\"name\":\"Write Line4_State\",\"x\":610,\"y\":520,\"wires\":",[146,24681],{},"},{\"id\":\"0c595b0ac2550593\",\"type\":\"s7 out\",\"z\":\"ad7b17411c8e83aa\",\"endpoint\":\"a1bec25858c6f3ef\",\"variable\":\"Line4_Fault\",\"name\":\"Write Line4_Fault\",\"x\":610,\"y\":600,\"wires\":",[146,24684],{},"},{\"id\":\"832807bfdc4b76f0\",\"type\":\"rbe\",\"z\":\"ad7b17411c8e83aa\",\"name\":\"\",\"func\":\"deadbandEq\",\"gap\":\"3%\",\"start\":\"\",\"inout\":\"in\",\"septopics\":true,\"property\":\"payload\",\"topi\":\"topic\",\"x\":420,\"y\":360,\"wires\":[[\"f1572463c50bb4cb\"]]},{\"id\":\"4ab2910b66e16220\",\"type\":\"eth-ip endpoint\",\"address\":\"192.168.0.5\",\"slot\":\"0\",\"cycletime\":\"1000\",\"name\":\"Line4\",\"vartable\":{\"\":{\"Conveyor_RTS\":{\"type\":\"BOOL\"},\"Robot_RTS\":{\"type\":\"BOOL\"},\"Robot_Position\":{\"type\":\"REAL\"},\"Conveyor_Running\":{\"type\":\"BOOL\"},\"Line4_State\":{\"type\":\"DINT\"},\"Line4_Fault\":{\"type\":\"BOOL\"}}}},{\"id\":\"a1bec25858c6f3ef\",\"type\":\"s7 endpoint\",\"transport\":\"iso-on-tcp\",\"address\":\"192.168.0.10\",\"port\":\"102\",\"rack\":\"0\",\"slot\":\"1\",\"localtsaphi\":\"01\",\"localtsaplo\":\"00\",\"remotetsaphi\":\"01\",\"remotetsaplo\":\"00\",\"connmode\":\"rack-slot\",\"adapter\":\"\",\"busaddr\":\"2\",\"cycletime\":\"1000\",\"timeout\":\"3000\",\"name\":\"Stacklight PLC\",\"vartable\":",[146,24687,24688],{},"{\"addr\":\"DB1,X0.0\",\"name\":\"Conveyor_RTS\"},{\"addr\":\"DB1,X0.1\",\"name\":\"Robot_RTS\"},{\"addr\":\"DB1,R2\",\"name\":\"Robot_Position\"},{\"addr\":\"DB1,X6.0\",\"name\":\"Conveyor_Running\"},{\"addr\":\"DB1,DI8\",\"name\":\"Line4_State\"},{\"addr\":\"DB1,X12.0\",\"name\":\"Line4_Fault\"}",{"title":50,"searchDepth":250,"depth":250,"links":24690},[24691,24692,24697,24702,24705],{"id":24065,"depth":185,"text":24066},{"id":24091,"depth":185,"text":24092,"children":24693},[24694,24695,24696],{"id":965,"depth":221,"text":1155},{"id":24110,"depth":221,"text":24111},{"id":24224,"depth":221,"text":24225},{"id":24256,"depth":185,"text":24257,"children":24698},[24699,24700,24701],{"id":24263,"depth":221,"text":24264},{"id":24327,"depth":221,"text":24328},{"id":24474,"depth":221,"text":24475},{"id":24556,"depth":185,"text":24557,"children":24703},[24704],{"id":24584,"depth":221,"text":24585},{"id":6517,"depth":185,"text":6518},"2023-06-20","step-by-step guide for using Node-RED as an industrial protocol converter","blog\u002F2023\u002F06\u002Fimages\u002Fethip-to-S7\u002FNode-RED-as-a-No-Code-Ethernet_IP-to-S7-Protocol-Converter.png",{"excerpt":24710},{"type":12,"value":24711},[24712],[15,24713,24059,24714,24062],{},[1290,24715],{},"\u002Fblog\u002F2023\u002F06\u002Fnode-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter",{"title":9800,"description":24707},{"loc":24716},"blog\u002F2023\u002F06\u002Fnode-red-as-a-no-code-ethernet_ip-to-s7-protocol-converter","Beginner tutorial for using Node-RED as free industrial protocol converter",[9832,966,24722,24723,24724,24725,24726,24727,24728,24729],"Industrial protocol converter","EtherNet\u002FIP","S7 Protocol","node red siemens s7","node red s7 communication","node red s7comm","nodered s7","node red s7","dteXgcBXUnk","hqOhXxbbQgAVtgfwGKxt2osKqr1VA-g4QFzhY3eh5vA",{"id":24733,"title":24734,"authors":24735,"body":24737,"cta":24916,"date":24919,"description":24920,"extension":946,"image":24921,"lastUpdated":948,"meta":24922,"navigation":253,"path":24932,"seo":24933,"sitemap":24934,"stem":24935,"subtitle":24936,"tags":24937,"tldr":24938,"video":3,"__hash__":24939},"blog\u002Fblog\u002F2023\u002F06\u002Fimport-modules.md","Use any npm module in Node-RED (2026)",[24736,5320],"joe-pavitt",{"type":12,"value":24738,"toc":24909},[24739,24751,24758,24762,24769,24772,24796,24800,24811,24826,24829,24833,24838,24851,24855,24860,24873,24881,24887,24891,24899,24902],[15,24740,24741,24742,24747,24748],{},"Node-RED has ",[307,24743,24746],{"href":24744,"target":24745},"https:\u002F\u002Fflows.nodered.org\u002Fsearch?type=node","_blank","an incredibly rich resource of integrations available",", but sometimes you need that little bit of extra functionality, or access to a Node.js module that doesn't have it's own custom nodes in Node-RED. ",[338,24749,24750],{},"We can easily import any npm module within the built-in Node-RED function nodes.",[15,24752,24753,24754,24757],{},"Historically in Node-RED, you would have needed to manually ",[19,24755,24756],{},"npm install"," modules from the command line, but now that it's so easy to run Node-RED in the Cloud, where you don't have easy access to those tools, what are the other options available?",[34,24759,24761],{"id":24760},"function-node-setup","Function Node - Setup",[15,24763,24764],{},[392,24765],{"alt":24766,"src":24767,"title":24768},"Location of the \"add\" button in order to import an npm module intoa  function node","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fnpmimport-add.jpg","Location of the 'add' button in order to import an npm module intoa  function node",[15,24770,24771],{},"All you need is the name of the module you want to import, then:",[326,24773,24774,24777,24780,24783,24790],{},[103,24775,24776],{},"Drop in a new \"function\" node & double-click it",[103,24778,24779],{},"Switch to the \"Setup\" tab",[103,24781,24782],{},"Underneath the \"modules\" tab, click \"+ add\" in the bottom-left of the window.",[103,24784,24785,24786,24789],{},"Enter the name of the module you want to use in the newly created row, and (optionally) modify the ",[19,24787,24788],{},"variable"," that this module will be imported in as.",[103,24791,24792,24793,24795],{},"Switch back to the \"On Message\" tab and write your function. Your new module will be available via the ",[19,24794,24788],{}," you defined in the \"Setup\" tab.",[34,24797,24799],{"id":24798},"example-momentjs","Example: Moment.js",[24801,24802,24805,24806],"video",{"width":24803,"height":24804,"controls":253},560,315,"\n  ",[24807,24808],"source",{"src":24809,"type":24810},"https:\u002F\u002Fwebsite-data.s3.eu-west-1.amazonaws.com\u002FMomentJS+Demo.mp4","video\u002Fmp4",[15,24812,24813,24814,24819,24820,24825],{},"Recently we wanted to use ",[307,24815,24818],{"href":24816,"rel":24817},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fmoment",[311],"moment"," for some custom date calculations. Whilst there was set of ",[307,24821,24824],{"href":24822,"rel":24823},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-contrib-moment",[311],"Moment Node-RED nodes"," already available, it didn't have all of the functionality we needed.",[15,24827,24828],{},"So, all we needed to do was import the module into a function node, and define our comparison there instead, here's a working example:",[34,24830,24832],{"id":24831},"example-easy-crc","Example: Easy CRC",[24801,24834,24805,24835],{"width":24803,"height":24804,"controls":253},[24807,24836],{"src":24837,"type":24810},"https:\u002F\u002Fwebsite-data.s3.eu-west-1.amazonaws.com\u002FEasy+CRC+Demo.mp4",[15,24839,24840,24841,24846,24847,24850],{},"Something we see ",[307,24842,24845],{"href":24843,"rel":24844},"https:\u002F\u002Fdiscourse.nodered.org\u002Fsearch?q=crc%20order%3Alatest",[311],"a lot on the Node-RED Forums"," are questions on how to conduct CRC calculations. There is a popular node module ",[19,24848,24849],{},"easy-crc"," that can be imported and used in the function nodes, e.g:",[34,24852,24854],{"id":24853},"example-posthog","Example: PostHog",[24801,24856,24805,24857],{"width":24803,"height":24804,"controls":253},[24807,24858],{"src":24859,"type":24810},"https:\u002F\u002Fwebsite-data.s3.eu-west-1.amazonaws.com\u002FPostHog+Node+Demo.mp4",[15,24861,24862,24863,24867,24868,24872],{},"Node-RED is great for ",[307,24864,24866],{"href":24865},"\u002Fsolutions\u002Fdata-integration\u002F","data integration",". We use ",[307,24869,24871],{"href":24870,"target":24745},"https:\u002F\u002Fposthog.com\u002F","PostHog"," for our internal Product Analysis. We record live events as they occur on FlowFuse Cloud to better understand features that are (and are not) used.",[15,24874,24875,24876,24880],{},"We wanted to investigate whether or not we could add backdated data, which in theory was possible via their ",[307,24877,24879],{"href":24878,"target":24745},"https:\u002F\u002Fposthog.com\u002Fdocs\u002Flibraries\u002Fnode","posthog-node"," module. We wanted to populate it with data driven from our own database and API.",[15,24882,24883,24884,24886],{},"Within two minutes, we could wire up a node to retrieve data from our API, and then ingest it into ",[19,24885,24879],{}," via the import of a function node.",[34,24888,24890],{"id":24889},"simplify-function-node-creation-with-flowfuse","Simplify Function Node Creation with FlowFuse",[15,24892,24893,24895,24896,24898],{},[307,24894,1155],{"href":213}," provides a powerful platform to enhance, scale, and secure your Node-RED applications efficiently. One of our latest features, the ",[338,24897,1692],{},", is designed to streamline the process of creating Function nodes.",[15,24900,24901],{},"With the FlowFuse Assistant, you can leverage AI to generate Function nodes effortlessly. Just input your prompt, and the Assistant will handle the creation for you, saving time and reducing manual coding.",[15,24903,24904,24905,167],{},"To explore how to make the most of the FlowFuse Assistant and its capabilities, check out the ",[307,24906,24908],{"href":24907},"\u002Fdocs\u002Fuser\u002Fexpert\u002F","Assistants Documentation",{"title":50,"searchDepth":250,"depth":250,"links":24910},[24911,24912,24913,24914,24915],{"id":24760,"depth":185,"text":24761},{"id":24798,"depth":185,"text":24799},{"id":24831,"depth":185,"text":24832},{"id":24853,"depth":185,"text":24854},{"id":24889,"depth":185,"text":24890},{"type":941,"title":24917,"description":24918},"Supercharge Node-RED with FlowFuse","Sign up for FlowFuse to run Node-RED in the cloud, import any npm module in function nodes, and build powerful integrations without needing command-line access.","2023-06-05","Node-RED has an incredibly rich resource of integrations available, but sometimes you need that little extra. This shows you how.","\u002Fimages\u002Fblog\u002Ftile-import-npm.jpeg",{"keywords":24923,"excerpt":24924},"node-red npm module, import npm node-red, node-red function node npm, node-red require module, node-red custom module, npm package node-red, node-red momentjs",{"type":12,"value":24925},[24926],[15,24927,24741,24928,24747,24930],{},[307,24929,24746],{"href":24744,"target":24745},[338,24931,24750],{},"\u002Fblog\u002F2023\u002F06\u002Fimport-modules",{"title":24734,"description":24920},{"loc":24932},"blog\u002F2023\u002F06\u002Fimport-modules","See how you can easily import any npm module, for use in a Node-RED function node.",[9832,966],"Node-RED function nodes have a built-in Setup tab that lets you import any npm module by name without using the command line. This is particularly useful in cloud or containerized environments where CLI access is limited. Examples include moment for date handling, easy-crc for CRC calculations, and posthog-node for analytics ingestion.","2yVdMRejcCqy_bEI82aVzepoBlw-GSa6o7GVXCV-Wmo",{"id":24941,"title":24942,"authors":24943,"body":24944,"cta":3,"date":25190,"description":25191,"extension":946,"image":25192,"lastUpdated":3,"meta":25193,"navigation":253,"path":25198,"seo":25199,"sitemap":25200,"stem":25201,"subtitle":25202,"tags":25203,"tldr":3,"video":3,"__hash__":25204},"blog\u002Fblog\u002F2023\u002F06\u002F3-quick-node-red-tips-7.md","Node-RED Tips - Dashboard Edition",[7451],{"type":12,"value":24945,"toc":25185},[24946,24949,24958,24962,24965,24968,24971,24974,24980,24983,25050,25054,25057,25063,25066,25069,25094,25098,25101,25104,25110,25113,25116,25141,25149],[15,24947,24948],{},"There is usually more than one way to complete a given task in software, and Node-RED is no exception. In each of this series of blog posts, we are going to share three useful tips to save yourself time when working on your flows.",[15,24950,24951,24952,24957],{},"In this Node-RED Tips article, we are going to focus on ",[307,24953,24956],{"href":24954,"rel":24955},"https:\u002F\u002Fflows.nodered.org\u002Fnode\u002Fnode-red-dashboard",[311],"Node-RED Dashboard",". Dashboard is a great tool for creating HMI (Human Machine Interfaces), it's also the most popular custom node for Node-RED with thousands of downloads per week.",[996,24959,24961],{"id":24960},"_1-responsive-layouts-almost","1. Responsive layouts (almost)",[15,24963,24964],{},"Responsive design is the ability for a webpage to change its content to best fit the features of a device used to view the page. For example, when viewing a graph on a mobile phone or a laptop the available screen space differs significantly in size as well as aspect-ratio.",[15,24966,24967],{},"Dashboard doesn't offer the feature to change graph sizes based on the screen of a viewing device. That being said, there is one trick you can use to make your dashboards a lot more useful on small and large screens alike.",[15,24969,24970],{},"Place your content into Dashboard 'groups', those groups can make use of wider screens by sitting side by side where the screen is big enough while stacking vertically on smaller devices.",[15,24972,24973],{},"The image below shows what happens when you change the screen size for this dashboard.",[15,24975,24976],{},[392,24977],{"alt":24978,"src":24979,"title":24978},"Changing the aspect ratio of the screen","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fresponsive.gif",[15,24981,24982],{},"If you'd like to try this out on your own Node-RED, you can import the flow below.",[15,24984,24985,24986,3830],{},"{% renderFlow 500 %}\n",[146,24987,24988,24989,24991,24992,24994,24995,24998,24999,25001,25002,25004,25005,25007,25008,24994,25010,25012,25013,25015,25016,25018,25019,25021,25022,24994,25024,25026,25027,25029,25030,25032,25033,25035,25036,24994,25038,25040,25041,25043,25044,25046,25047,25049],{},"{\"id\":\"e351b1251dfbc2f7\",\"type\":\"tab\",\"label\":\"Flow 1\",\"disabled\":false,\"info\":\"\",\"env\":",[146,24990],{},"},{\"id\":\"d388b48fcbed93a1\",\"type\":\"ui_gauge\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"ba1ff527abfa5261\",\"order\":2,\"width\":0,\"height\":0,\"gtype\":\"gage\",\"title\":\"gauge\",\"label\":\"units\",\"format\":\"",[17483,24993],{"value":1587},"\",\"min\":0,\"max\":\"100\",\"colors\":",[146,24996,24997],{},"\"#00b500\",\"#e6e600\",\"#ca3838\"",",\"seg1\":\"\",\"seg2\":\"\",\"diff\":false,\"className\":\"\",\"x\":350,\"y\":80,\"wires\":",[146,25000],{},"},{\"id\":\"80780894450cfb6d\",\"type\":\"ui_button\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"ba1ff527abfa5261\",\"order\":1,\"width\":0,\"height\":0,\"passthru\":false,\"label\":\"Update\",\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"x\":80,\"y\":80,\"wires\":[[\"49e78ff51c3a1ea3\"]]},{\"id\":\"07b44990e5d5b5f6\",\"type\":\"ui_chart\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"ba1ff527abfa5261\",\"order\":3,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"60\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25003,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":350,\"y\":120,\"wires\":[[]]},{\"id\":\"1482bcf69325aa92\",\"type\":\"inject\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"props\":",[146,25006],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":90,\"y\":120,\"wires\":[[\"49e78ff51c3a1ea3\"]]},{\"id\":\"49e78ff51c3a1ea3\",\"type\":\"random\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"low\":\"0\",\"high\":\"100\",\"inte\":\"true\",\"property\":\"payload\",\"x\":220,\"y\":100,\"wires\":[[\"d388b48fcbed93a1\",\"07b44990e5d5b5f6\"]]},{\"id\":\"38996d8eb9f6535d\",\"type\":\"ui_gauge\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"f6052a3dccc77ea3\",\"order\":2,\"width\":0,\"height\":0,\"gtype\":\"gage\",\"title\":\"gauge\",\"label\":\"units\",\"format\":\"",[17483,25009],{"value":1587},[146,25011,24997],{},",\"seg1\":\"\",\"seg2\":\"\",\"diff\":false,\"className\":\"\",\"x\":350,\"y\":240,\"wires\":",[146,25014],{},"},{\"id\":\"cf5599fbda28e614\",\"type\":\"ui_button\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"f6052a3dccc77ea3\",\"order\":1,\"width\":0,\"height\":0,\"passthru\":false,\"label\":\"Update\",\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"x\":80,\"y\":240,\"wires\":[[\"f093c63e17a620ba\"]]},{\"id\":\"936438ebf9eef986\",\"type\":\"ui_chart\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"f6052a3dccc77ea3\",\"order\":3,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"60\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25017,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":350,\"y\":280,\"wires\":[[]]},{\"id\":\"60a40faa335f943e\",\"type\":\"inject\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"props\":",[146,25020],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":90,\"y\":280,\"wires\":[[\"f093c63e17a620ba\"]]},{\"id\":\"f093c63e17a620ba\",\"type\":\"random\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"low\":\"0\",\"high\":\"100\",\"inte\":\"true\",\"property\":\"payload\",\"x\":220,\"y\":260,\"wires\":[[\"38996d8eb9f6535d\",\"936438ebf9eef986\"]]},{\"id\":\"e284b90164e648e6\",\"type\":\"ui_gauge\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"1e4a72d62ed7564c\",\"order\":2,\"width\":0,\"height\":0,\"gtype\":\"gage\",\"title\":\"gauge\",\"label\":\"units\",\"format\":\"",[17483,25023],{"value":1587},[146,25025,24997],{},",\"seg1\":\"\",\"seg2\":\"\",\"diff\":false,\"className\":\"\",\"x\":350,\"y\":380,\"wires\":",[146,25028],{},"},{\"id\":\"cabe7a8150dbfaf6\",\"type\":\"ui_button\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"1e4a72d62ed7564c\",\"order\":1,\"width\":0,\"height\":0,\"passthru\":false,\"label\":\"Update\",\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"x\":80,\"y\":380,\"wires\":[[\"7b9862186954c4b3\"]]},{\"id\":\"ac116116c56e6c3c\",\"type\":\"ui_chart\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"1e4a72d62ed7564c\",\"order\":3,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"60\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25031,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":350,\"y\":420,\"wires\":[[]]},{\"id\":\"bb27ef41380ec1d2\",\"type\":\"inject\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"props\":",[146,25034],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":90,\"y\":420,\"wires\":[[\"7b9862186954c4b3\"]]},{\"id\":\"7b9862186954c4b3\",\"type\":\"random\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"low\":\"0\",\"high\":\"100\",\"inte\":\"true\",\"property\":\"payload\",\"x\":220,\"y\":400,\"wires\":[[\"e284b90164e648e6\",\"ac116116c56e6c3c\"]]},{\"id\":\"e891f555bad51f01\",\"type\":\"ui_gauge\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"fcc4481a9e329266\",\"order\":2,\"width\":0,\"height\":0,\"gtype\":\"gage\",\"title\":\"gauge\",\"label\":\"units\",\"format\":\"",[17483,25037],{"value":1587},[146,25039,24997],{},",\"seg1\":\"\",\"seg2\":\"\",\"diff\":false,\"className\":\"\",\"x\":350,\"y\":500,\"wires\":",[146,25042],{},"},{\"id\":\"fa090cd1a0e97885\",\"type\":\"ui_button\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"fcc4481a9e329266\",\"order\":1,\"width\":0,\"height\":0,\"passthru\":false,\"label\":\"Update\",\"tooltip\":\"\",\"color\":\"\",\"bgcolor\":\"\",\"className\":\"\",\"icon\":\"\",\"payload\":\"\",\"payloadType\":\"str\",\"topic\":\"topic\",\"topicType\":\"msg\",\"x\":80,\"y\":500,\"wires\":[[\"bb4e945a2a8e681c\"]]},{\"id\":\"1c3a8aa84dfc85fe\",\"type\":\"ui_chart\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"group\":\"fcc4481a9e329266\",\"order\":3,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"60\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25045,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":350,\"y\":540,\"wires\":[[]]},{\"id\":\"796f79538c15dd66\",\"type\":\"inject\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"props\":",[146,25048],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":90,\"y\":540,\"wires\":[[\"bb4e945a2a8e681c\"]]},{\"id\":\"bb4e945a2a8e681c\",\"type\":\"random\",\"z\":\"e351b1251dfbc2f7\",\"name\":\"\",\"low\":\"0\",\"high\":\"100\",\"inte\":\"true\",\"property\":\"payload\",\"x\":220,\"y\":520,\"wires\":[[\"e891f555bad51f01\",\"1c3a8aa84dfc85fe\"]]},{\"id\":\"ba1ff527abfa5261\",\"type\":\"ui_group\",\"name\":\"Machine 1\",\"tab\":\"39383a7a648193dd\",\"order\":1,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"f6052a3dccc77ea3\",\"type\":\"ui_group\",\"name\":\"Machine 2\",\"tab\":\"39383a7a648193dd\",\"order\":2,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"1e4a72d62ed7564c\",\"type\":\"ui_group\",\"name\":\"Machine 3\",\"tab\":\"39383a7a648193dd\",\"order\":3,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"fcc4481a9e329266\",\"type\":\"ui_group\",\"name\":\"Machine 4\",\"tab\":\"39383a7a648193dd\",\"order\":4,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"39383a7a648193dd\",\"type\":\"ui_tab\",\"name\":\"Node-RED Tips\",\"icon\":\"dashboard\",\"disabled\":false,\"hidden\":false}",[996,25051,25053],{"id":25052},"_2-add-more-than-one-series-of-data-to-a-line-chart","2. Add more than one series of data to a line chart",[15,25055,25056],{},"Being able to add more than one series of data to a single chart can make the data far more useful. One great way to use this is to compare the same data from different sensors. In this example I'm going to show the external and internal temperature at a location on the same chart.",[15,25058,25059],{},[392,25060],{"alt":25061,"src":25062,"title":25061},"Graphing two series on the same line chart","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Ftemp-graph.png",[15,25064,25065],{},"To do this you need to give a different msg.topic to each series, you can add that using a change node before passing the data to the chart.",[15,25067,25068],{},"If you'd like to view this chart on your own Node-RED, you can import the flow below.",[15,25070,6389,25071,3830],{},[146,25072,25073,25074,25076,25077,11866,25079,25093],{},"{\"id\":\"3eb08d4843164efc\",\"type\":\"ui_chart\",\"z\":\"58569b35dacd54f3\",\"name\":\"\",\"group\":\"41e847ff22249c0e\",\"order\":3,\"width\":\"12\",\"height\":\"5\",\"label\":\"Celsius\",\"chartType\":\"line\",\"legend\":\"true\",\"xformat\":\"dd HH:mm\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"604800\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25075,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":280,\"y\":180,\"wires\":[[]]},{\"id\":\"c4a13220356f76d3\",\"type\":\"inject\",\"z\":\"58569b35dacd54f3\",\"name\":\"\",\"props\":",[146,25078,9126],{},[146,25080,25081,25082,25085,25086,25089,25090,1596],{},"{\"series\":",[146,25083,25084],{},"\"inside\",\"outside\"",",\"data\":[[{\"x\":1685015544647,\"y\":20.8},{\"x\":1685015844687,\"y\":20.8},{\"x\":1685016144791,\"y\":20.8},{\"x\":1685016444933,\"y\":20.8},{\"x\":1685016745032,\"y\":20.8},{\"x\":1685017045123,\"y\":20.8},{\"x\":1685017345223,\"y\":20.8},{\"x\":1685017645339,\"y\":20.8},{\"x\":1685017945424,\"y\":20.8},{\"x\":1685018245500,\"y\":20.8},{\"x\":1685018545670,\"y\":20.8},{\"x\":1685018860580,\"y\":20.8},{\"x\":1685019160814,\"y\":20.8},{\"x\":1685019460832,\"y\":20.8},{\"x\":1685019760943,\"y\":20.8},{\"x\":1685020061037,\"y\":20.8},{\"x\":1685020361249,\"y\":20.8},{\"x\":1685020661264,\"y\":20.8},{\"x\":1685020961381,\"y\":20.8},{\"x\":1685021261473,\"y\":20.8},{\"x\":1685021561721,\"y\":20.8},{\"x\":1685021876580,\"y\":20.6},{\"x\":1685022176696,\"y\":20.7},{\"x\":1685022476852,\"y\":20.8},{\"x\":1685022776957,\"y\":20.8},{\"x\":1685023077032,\"y\":20.8},{\"x\":1685023377120,\"y\":20.8},{\"x\":1685023677234,\"y\":20.8},{\"x\":1685023977339,\"y\":20.8},{\"x\":1685024277455,\"y\":20.8},{\"x\":1685024577576,\"y\":20.8},{\"x\":1685024892578,\"y\":20.8},{\"x\":1685025192616,\"y\":20.8},{\"x\":1685025492757,\"y\":20.8},{\"x\":1685025792847,\"y\":20.7},{\"x\":1685026092949,\"y\":20.8},{\"x\":1685026393052,\"y\":20.8},{\"x\":1685026693174,\"y\":20.8},{\"x\":1685026993234,\"y\":20.8},{\"x\":1685027293387,\"y\":20.8},{\"x\":1685027593514,\"y\":20.8},{\"x\":1685027908550,\"y\":20.8},{\"x\":1685028208624,\"y\":20.8},{\"x\":1685028508708,\"y\":20.8},{\"x\":1685028808828,\"y\":20.8},{\"x\":1685029108909,\"y\":20.8},{\"x\":1685029409026,\"y\":20.8},{\"x\":1685029709096,\"y\":20.8},{\"x\":1685030009262,\"y\":20.8},{\"x\":1685030309361,\"y\":20.8},{\"x\":1685030609541,\"y\":20.8},{\"x\":1685030924565,\"y\":20.8},{\"x\":1685031239588,\"y\":20.8},{\"x\":1685031539702,\"y\":20.8},{\"x\":1685031839784,\"y\":20.8},{\"x\":1685032139922,\"y\":20.8},{\"x\":1685032440027,\"y\":20.8},{\"x\":1685032740134,\"y\":20.6},{\"x\":1685033040252,\"y\":20.7},{\"x\":1685033340317,\"y\":20.5},{\"x\":1685033640450,\"y\":20.5},{\"x\":1685033940507,\"y\":20.6},{\"x\":1685034240592,\"y\":20.5},{\"x\":1685034540633,\"y\":20.4},{\"x\":1685034840784,\"y\":20.4},{\"x\":1685035140888,\"y\":20.5},{\"x\":1685035441026,\"y\":20.4},{\"x\":1685035741107,\"y\":20.5},{\"x\":1685036041205,\"y\":20.5},{\"x\":1685036341328,\"y\":20.5},{\"x\":1685036641457,\"y\":20.5},{\"x\":1685036941583,\"y\":20.4},{\"x\":1685037256578,\"y\":20.5},{\"x\":1685037556715,\"y\":20.5},{\"x\":1685037856773,\"y\":20.5},{\"x\":1685038156902,\"y\":20.5},{\"x\":1685038457023,\"y\":20.5},{\"x\":1685038757090,\"y\":20.5},{\"x\":1685039057219,\"y\":20.5},{\"x\":1685039357278,\"y\":20.5},{\"x\":1685039657449,\"y\":20.5},{\"x\":1685039957553,\"y\":20.5},{\"x\":1685040257570,\"y\":20.5},{\"x\":1685040557615,\"y\":20.5},{\"x\":1685040857735,\"y\":20.4},{\"x\":1685041157843,\"y\":20.4},{\"x\":1685041457971,\"y\":20.5},{\"x\":1685041758072,\"y\":20.5},{\"x\":1685042058154,\"y\":20.5},{\"x\":1685042358273,\"y\":20.5},{\"x\":1685042658392,\"y\":20.5},{\"x\":1685042958486,\"y\":20.6},{\"x\":1685043258518,\"y\":20.6},{\"x\":1685043558568,\"y\":20.6},{\"x\":1685043858688,\"y\":20.5},{\"x\":1685044158769,\"y\":20.5},{\"x\":1685044458908,\"y\":20.5},{\"x\":1685044758984,\"y\":20.5},{\"x\":1685045059095,\"y\":20.5},{\"x\":1685045359169,\"y\":20.5},{\"x\":1685045659320,\"y\":20.5},{\"x\":1685045959367,\"y\":20.5},{\"x\":1685046259469,\"y\":20.7},{\"x\":1685046559497,\"y\":20.6},{\"x\":1685046859589,\"y\":20.5},{\"x\":1685047159647,\"y\":20.6},{\"x\":1685047459714,\"y\":20.6},{\"x\":1685047759793,\"y\":20.7},{\"x\":1685048059921,\"y\":20.7},{\"x\":1685048359994,\"y\":20.6},{\"x\":1685048659997,\"y\":20.7},{\"x\":1685048960056,\"y\":20.7},{\"x\":1685049260070,\"y\":20.8},{\"x\":1685049560116,\"y\":20.8},{\"x\":1685049860167,\"y\":20.8},{\"x\":1685050160212,\"y\":20.8},{\"x\":1685050460281,\"y\":20.8},{\"x\":1685050760368,\"y\":20.8},{\"x\":1685051060400,\"y\":20.7},{\"x\":1685051360435,\"y\":20.8},{\"x\":1685051660553,\"y\":20.8},{\"x\":1685051960585,\"y\":20.8},{\"x\":1685052260665,\"y\":20.8},{\"x\":1685052560675,\"y\":20.8},{\"x\":1685052860700,\"y\":20.7},{\"x\":1685053160749,\"y\":20.8},{\"x\":1685053460790,\"y\":20.8},{\"x\":1685053760855,\"y\":20.7},{\"x\":1685054060931,\"y\":20.7},{\"x\":1685054361019,\"y\":20.7},{\"x\":1685054661030,\"y\":20.7},{\"x\":1685054961121,\"y\":20.7},{\"x\":1685055261227,\"y\":20.7},{\"x\":1685055576250,\"y\":20.7},{\"x\":1685055891289,\"y\":20.5},{\"x\":1685056191290,\"y\":20.6},{\"x\":1685056491397,\"y\":20.7},{\"x\":1685056791444,\"y\":20.7},{\"x\":1685057091489,\"y\":20.7},{\"x\":1685057391516,\"y\":20.7},{\"x\":1685057691607,\"y\":20.7},{\"x\":1685057991660,\"y\":20.7},{\"x\":1685058291741,\"y\":20.6},{\"x\":1685058606760,\"y\":20.5},{\"x\":1685058906775,\"y\":20.5},{\"x\":1685059206866,\"y\":20.5},{\"x\":1685059506900,\"y\":20.5},{\"x\":1685059806979,\"y\":20.4},{\"x\":1685060107035,\"y\":20.5},{\"x\":1685060407099,\"y\":20.5},{\"x\":1685060707132,\"y\":20.4},{\"x\":1685061007199,\"y\":20.5},{\"x\":1685061307220,\"y\":20.4},{\"x\":1685061607228,\"y\":20.5},{\"x\":1685061907286,\"y\":20.6},{\"x\":1685062207289,\"y\":20.7},{\"x\":1685062507315,\"y\":20.6},{\"x\":1685062807355,\"y\":20.7},{\"x\":1685063107395,\"y\":20.7},{\"x\":1685063407413,\"y\":20.7},{\"x\":1685063707465,\"y\":20.7},{\"x\":1685064007492,\"y\":20.7},{\"x\":1685064307565,\"y\":20.7},{\"x\":1685064607587,\"y\":20.7},{\"x\":1685064907609,\"y\":20.7},{\"x\":1685065207618,\"y\":20.5},{\"x\":1685065507642,\"y\":20.4},{\"x\":1685065807670,\"y\":20.4},{\"x\":1685066107679,\"y\":20.4},{\"x\":1685066407758,\"y\":20.5},{\"x\":1685066707760,\"y\":20.3},{\"x\":1685067007798,\"y\":20.4},{\"x\":1685067307865,\"y\":20.4},{\"x\":1685067622903,\"y\":20.4},{\"x\":1685067937922,\"y\":20.4},{\"x\":1685068237945,\"y\":20.4},{\"x\":1685068537971,\"y\":20.3},{\"x\":1685068838009,\"y\":20.3},{\"x\":1685069138060,\"y\":20.2},{\"x\":1685069453065,\"y\":20.3},{\"x\":1685069753104,\"y\":20.3},{\"x\":1685070053108,\"y\":20.2},{\"x\":1685070353140,\"y\":20.3},{\"x\":1685070653170,\"y\":20.2},{\"x\":1685070953219,\"y\":20.2},{\"x\":1685071253232,\"y\":20.2},{\"x\":1685071553258,\"y\":20},{\"x\":1685071853260,\"y\":20},{\"x\":1685072153324,\"y\":20.2},{\"x\":1685072453329,\"y\":20.2},{\"x\":1685072753343,\"y\":19.9},{\"x\":1685073053359,\"y\":20},{\"x\":1685073353402,\"y\":19.9},{\"x\":1685073653411,\"y\":19.9},{\"x\":1685073953453,\"y\":20},{\"x\":1685074253462,\"y\":20},{\"x\":1685074553509,\"y\":20},{\"x\":1685074853529,\"y\":20},{\"x\":1685075153554,\"y\":20},{\"x\":1685075453556,\"y\":20},{\"x\":1685075753590,\"y\":20},{\"x\":1685076053642,\"y\":20.2},{\"x\":1685076368644,\"y\":20},{\"x\":1685076668676,\"y\":20.2},{\"x\":1685076968685,\"y\":20.3},{\"x\":1685077268752,\"y\":20.3},{\"x\":1685077583729,\"y\":20.3},{\"x\":1685077883800,\"y\":20.2},{\"x\":1685078198760,\"y\":20.2},{\"x\":1685078498762,\"y\":20.4},{\"x\":1685078798782,\"y\":20.4},{\"x\":1685079098837,\"y\":20.5},{\"x\":1685079398903,\"y\":20.5},{\"x\":1685079698965,\"y\":20.6},{\"x\":1685079999013,\"y\":20.7},{\"x\":1685080299091,\"y\":20.8},{\"x\":1685080599234,\"y\":20.8},{\"x\":1685080899276,\"y\":20.8},{\"x\":1685081199340,\"y\":20.8},{\"x\":1685081499403,\"y\":20.8},{\"x\":1685081799441,\"y\":20.8},{\"x\":1685082099467,\"y\":20.9},{\"x\":1685082399584,\"y\":20.8},{\"x\":1685082699624,\"y\":21},{\"x\":1685082999670,\"y\":21},{\"x\":1685083299724,\"y\":21.2},{\"x\":1685083599810,\"y\":21.2},{\"x\":1685083899884,\"y\":21.2},{\"x\":1685084199935,\"y\":21.2},{\"x\":1685084500001,\"y\":21.3},{\"x\":1685084800063,\"y\":21.4},{\"x\":1685085115072,\"y\":21.5},{\"x\":1685085415170,\"y\":21.5},{\"x\":1685085715226,\"y\":21.5},{\"x\":1685086015289,\"y\":21.5},{\"x\":1685086315365,\"y\":21.5},{\"x\":1685086615447,\"y\":21.5},{\"x\":1685086915501,\"y\":21.7},{\"x\":1685087215603,\"y\":21.7},{\"x\":1685087515642,\"y\":21.6},{\"x\":1685087815732,\"y\":21.6},{\"x\":1685088115759,\"y\":21.7},{\"x\":1685088415793,\"y\":21.9},{\"x\":1685088715840,\"y\":21.9},{\"x\":1685089015891,\"y\":21.9},{\"x\":1685089315950,\"y\":21.9},{\"x\":1685089616045,\"y\":21.9},{\"x\":1685089916094,\"y\":21.9},{\"x\":1685090216153,\"y\":21.9},{\"x\":1685090516202,\"y\":21.9},{\"x\":1685090816299,\"y\":21.9},{\"x\":1685091116304,\"y\":22},{\"x\":1685091416326,\"y\":22},{\"x\":1685091716409,\"y\":22},{\"x\":1685092016500,\"y\":22},{\"x\":1685092316555,\"y\":21.9},{\"x\":1685092616597,\"y\":21.9},{\"x\":1685092916687,\"y\":21.9},{\"x\":1685093216749,\"y\":21.9},{\"x\":1685093516770,\"y\":21.9},{\"x\":1685093816833,\"y\":21.8},{\"x\":1685094116862,\"y\":21.8},{\"x\":1685094416941,\"y\":21.9},{\"x\":1685094717014,\"y\":21.8},{\"x\":1685095017079,\"y\":21.8},{\"x\":1685095317146,\"y\":21.9},{\"x\":1685095617198,\"y\":21.9},{\"x\":1685095917267,\"y\":22},{\"x\":1685096217380,\"y\":22},{\"x\":1685096517436,\"y\":22.2},{\"x\":1685096817481,\"y\":22.1},{\"x\":1685097117519,\"y\":21.9},{\"x\":1685097417582,\"y\":21.9},{\"x\":1685097717607,\"y\":21.9},{\"x\":1685098017734,\"y\":21.9},{\"x\":1685098317791,\"y\":21.9},{\"x\":1685098617850,\"y\":21.9},{\"x\":1685098917939,\"y\":21.9},{\"x\":1685099218020,\"y\":21.9},{\"x\":1685099518088,\"y\":21.9},{\"x\":1685099818190,\"y\":21.9},{\"x\":1685100118269,\"y\":21.9},{\"x\":1685100418304,\"y\":21.9},{\"x\":1685100718355,\"y\":21.9},{\"x\":1685101018437,\"y\":22},{\"x\":1685101318544,\"y\":22.1},{\"x\":1685101618646,\"y\":22},{\"x\":1685101918720,\"y\":22},{\"x\":1685102218803,\"y\":22},{\"x\":1685102518914,\"y\":21.9},{\"x\":1685102818997,\"y\":21.9},{\"x\":1685103119093,\"y\":21.9},{\"x\":1685103419119,\"y\":21.9},{\"x\":1685103719183,\"y\":21.9},{\"x\":1685104019263,\"y\":21.9},{\"x\":1685104319361,\"y\":21.9},{\"x\":1685104619467,\"y\":21.9},{\"x\":1685104919565,\"y\":21.9},{\"x\":1685105219692,\"y\":21.9},{\"x\":1685105261657,\"y\":21.9},{\"x\":1685105275726,\"y\":21.9},{\"x\":1685105316848,\"y\":21.9},{\"x\":1685105388676,\"y\":21.9},{\"x\":1685105475001,\"y\":21.9},{\"x\":1685105501466,\"y\":21.9},{\"x\":1685105576628,\"y\":21.9},{\"x\":1685105627162,\"y\":21.9},{\"x\":1685105653330,\"y\":21.9},{\"x\":1685105684112,\"y\":21.9},{\"x\":1685105722222,\"y\":21.9},{\"x\":1685105769629,\"y\":21.9},{\"x\":1685105825666,\"y\":21.9},{\"x\":1685105889137,\"y\":21.9},{\"x\":1685105910507,\"y\":21.9},{\"x\":1685105970749,\"y\":21.9},{\"x\":1685106270828,\"y\":21.9},{\"x\":1685106570844,\"y\":21.9},{\"x\":1685106870845,\"y\":21.9},{\"x\":1685107170872,\"y\":21.9},{\"x\":1685107485846,\"y\":21.9},{\"x\":1685107785884,\"y\":21.9},{\"x\":1685108100859,\"y\":21.9},{\"x\":1685108400859,\"y\":21.9},{\"x\":1685108700881,\"y\":21.9},{\"x\":1685109000885,\"y\":21.9},{\"x\":1685109300898,\"y\":21.9},{\"x\":1685109615915,\"y\":21.8},{\"x\":1685109930873,\"y\":21.9},{\"x\":1685110230928,\"y\":21.9},{\"x\":1685110545931,\"y\":21.7},{\"x\":1685110845948,\"y\":21.7},{\"x\":1685111145989,\"y\":21.7},{\"x\":1685111460988,\"y\":21.7},{\"x\":1685111761062,\"y\":21.8},{\"x\":1685112061067,\"y\":21.6},{\"x\":1685112361107,\"y\":21.7},{\"x\":1685112661141,\"y\":21.7},{\"x\":1685112961145,\"y\":21.8},{\"x\":1685113261191,\"y\":21.8},{\"x\":1685113561195,\"y\":21.8},{\"x\":1685113861234,\"y\":21.8},{\"x\":1685114161272,\"y\":21.8},{\"x\":1685114461288,\"y\":21.7},{\"x\":1685114761326,\"y\":21.7},{\"x\":1685115061359,\"y\":21.7},{\"x\":1685115361389,\"y\":21.7},{\"x\":1685115661413,\"y\":21.6},{\"x\":1685115961522,\"y\":21.7},{\"x\":1685116276440,\"y\":21.7},{\"x\":1685116576471,\"y\":21.5},{\"x\":1685116876500,\"y\":21.5},{\"x\":1685117176511,\"y\":21.5},{\"x\":1685117476538,\"y\":21.6},{\"x\":1685117776599,\"y\":21.6},{\"x\":1685118091580,\"y\":21.8},{\"x\":1685118391620,\"y\":21.6},{\"x\":1685118691646,\"y\":21.7},{\"x\":1685118991656,\"y\":21.7},{\"x\":1685119291661,\"y\":21.5},{\"x\":1685119591684,\"y\":21.5},{\"x\":1685119891711,\"y\":21.5},{\"x\":1685120191732,\"y\":21.5},{\"x\":1685120491763,\"y\":21.5},{\"x\":1685120791797,\"y\":21.4},{\"x\":1685121091832,\"y\":21.2},{\"x\":1685121406802,\"y\":21.2},{\"x\":1685121706853,\"y\":21.1},{\"x\":1685122021885,\"y\":21.1},{\"x\":1685122336872,\"y\":21.2},{\"x\":1685122636878,\"y\":21.2},{\"x\":1685122936903,\"y\":21.1},{\"x\":1685123236933,\"y\":21},{\"x\":1685123536988,\"y\":21},{\"x\":1685123837007,\"y\":21.1},{\"x\":1685124137007,\"y\":21},{\"x\":1685124437098,\"y\":21},{\"x\":1685124737193,\"y\":21},{\"x\":1685125037270,\"y\":21},{\"x\":1685125337370,\"y\":20.9},{\"x\":1685125637493,\"y\":20.9},{\"x\":1685125937530,\"y\":21},{\"x\":1685126237627,\"y\":20.9},{\"x\":1685126537712,\"y\":20.8},{\"x\":1685126837733,\"y\":20.8},{\"x\":1685127137746,\"y\":20.8},{\"x\":1685127437870,\"y\":20.8},{\"x\":1685127737940,\"y\":20.8},{\"x\":1685128038026,\"y\":20.8},{\"x\":1685128338109,\"y\":20.8},{\"x\":1685128638215,\"y\":20.8},{\"x\":1685128938321,\"y\":20.8},{\"x\":1685129238409,\"y\":20.8},{\"x\":1685129538484,\"y\":20.8},{\"x\":1685129838514,\"y\":20.8},{\"x\":1685130138614,\"y\":20.8},{\"x\":1685130438644,\"y\":20.8},{\"x\":1685130738716,\"y\":20.8},{\"x\":1685131038856,\"y\":20.8},{\"x\":1685131338912,\"y\":20.8},{\"x\":1685131639028,\"y\":20.8},{\"x\":1685131939140,\"y\":20.8},{\"x\":1685132239222,\"y\":20.8},{\"x\":1685132539344,\"y\":20.8},{\"x\":1685132839390,\"y\":20.8},{\"x\":1685133154381,\"y\":20.8},{\"x\":1685133454402,\"y\":20.8},{\"x\":1685133754494,\"y\":20.8},{\"x\":1685134054557,\"y\":20.8},{\"x\":1685134354644,\"y\":20.8},{\"x\":1685134654753,\"y\":20.8},{\"x\":1685134954810,\"y\":20.8},{\"x\":1685135254894,\"y\":20.7},{\"x\":1685135554961,\"y\":20.8},{\"x\":1685135854990,\"y\":20.8},{\"x\":1685136155013,\"y\":20.8},{\"x\":1685136455070,\"y\":20.8},{\"x\":1685136755172,\"y\":20.8},{\"x\":1685137055242,\"y\":20.8},{\"x\":1685137355339,\"y\":20.8},{\"x\":1685137655440,\"y\":20.7},{\"x\":1685137955504,\"y\":20.7},{\"x\":1685138255592,\"y\":20.6},{\"x\":1685138555675,\"y\":20.6},{\"x\":1685138855742,\"y\":20.6},{\"x\":1685139155750,\"y\":20.6},{\"x\":1685139455841,\"y\":20.5},{\"x\":1685139755904,\"y\":20.6},{\"x\":1685140055961,\"y\":20.5},{\"x\":1685140356100,\"y\":20.6},{\"x\":1685140656187,\"y\":20.5},{\"x\":1685140956268,\"y\":20.4},{\"x\":1685141256384,\"y\":20.4},{\"x\":1685141556467,\"y\":20.4},{\"x\":1685141856547,\"y\":20.4},{\"x\":1685142156630,\"y\":20.3},{\"x\":1685142456678,\"y\":20.2},{\"x\":1685142756713,\"y\":20.3},{\"x\":1685143056833,\"y\":20.3},{\"x\":1685143356922,\"y\":20.3},{\"x\":1685143657019,\"y\":20.3},{\"x\":1685143957116,\"y\":20.2},{\"x\":1685144257203,\"y\":20.2},{\"x\":1685144557262,\"y\":20.1},{\"x\":1685144857393,\"y\":20.2},{\"x\":1685145157511,\"y\":20.2},{\"x\":1685145457515,\"y\":20.2},{\"x\":1685145757583,\"y\":20},{\"x\":1685146057687,\"y\":20},{\"x\":1685146357780,\"y\":20.1},{\"x\":1685146657878,\"y\":19.9},{\"x\":1685146957969,\"y\":19.9},{\"x\":1685147258066,\"y\":20},{\"x\":1685147558163,\"y\":20},{\"x\":1685147858255,\"y\":19.9},{\"x\":1685148158303,\"y\":19.9},{\"x\":1685148458377,\"y\":19.9},{\"x\":1685148758392,\"y\":19.9},{\"x\":1685149058441,\"y\":19.9},{\"x\":1685149358550,\"y\":19.9},{\"x\":1685149658652,\"y\":19.5},{\"x\":1685149958735,\"y\":19.5},{\"x\":1685150258816,\"y\":19.5},{\"x\":1685150558892,\"y\":19.5},{\"x\":1685150858996,\"y\":19.5},{\"x\":1685151159109,\"y\":19.5},{\"x\":1685151459166,\"y\":19.5},{\"x\":1685151759230,\"y\":19.5},{\"x\":1685152059291,\"y\":19.5},{\"x\":1685152359350,\"y\":19.5},{\"x\":1685152659430,\"y\":19.5},{\"x\":1685152959526,\"y\":19.5},{\"x\":1685153259622,\"y\":19.5},{\"x\":1685153559728,\"y\":19.5},{\"x\":1685153859883,\"y\":19.5},{\"x\":1685154159899,\"y\":19.5},{\"x\":1685154459974,\"y\":19.5},{\"x\":1685154760016,\"y\":19.5},{\"x\":1685155060096,\"y\":19.5},{\"x\":1685155360142,\"y\":19.5},{\"x\":1685155660284,\"y\":19.5},{\"x\":1685155960372,\"y\":19.5},{\"x\":1685156260469,\"y\":19.5},{\"x\":1685156560559,\"y\":19.5},{\"x\":1685156860664,\"y\":19.5},{\"x\":1685157160732,\"y\":19.5},{\"x\":1685157460827,\"y\":19.5},{\"x\":1685157760925,\"y\":19.5},{\"x\":1685158061041,\"y\":19.4},{\"x\":1685158361093,\"y\":19.4},{\"x\":1685158661174,\"y\":19.4},{\"x\":1685158961238,\"y\":19.4},{\"x\":1685159261356,\"y\":19.4},{\"x\":1685159561460,\"y\":19.4},{\"x\":1685159861591,\"y\":19.4},{\"x\":1685160161622,\"y\":19.5},{\"x\":1685160461727,\"y\":19.4},{\"x\":1685160761798,\"y\":19.3},{\"x\":1685161061899,\"y\":19.3},{\"x\":1685161361960,\"y\":19.3},{\"x\":1685161662030,\"y\":19.3},{\"x\":1685161962083,\"y\":19.2},{\"x\":1685162262205,\"y\":19.2},{\"x\":1685162562329,\"y\":19.2},{\"x\":1685162862420,\"y\":19.2},{\"x\":1685163162514,\"y\":19.3},{\"x\":1685163462630,\"y\":19.4},{\"x\":1685163762689,\"y\":19.5},{\"x\":1685164062785,\"y\":19.5},{\"x\":1685164362862,\"y\":19.5},{\"x\":1685164662929,\"y\":19.5},{\"x\":1685164962989,\"y\":19.5},{\"x\":1685165263080,\"y\":19.5},{\"x\":1685165563213,\"y\":19.5},{\"x\":1685165863284,\"y\":19.5},{\"x\":1685166163398,\"y\":19.7},{\"x\":1685166463432,\"y\":20},{\"x\":1685166763562,\"y\":20},{\"x\":1685167063657,\"y\":20.3},{\"x\":1685167363752,\"y\":20.3},{\"x\":1685167663818,\"y\":20.2},{\"x\":1685167963868,\"y\":20.4},{\"x\":1685168263959,\"y\":20.5},{\"x\":1685168564052,\"y\":20.7},{\"x\":1685168864152,\"y\":20.8},{\"x\":1685169164297,\"y\":20.8},{\"x\":1685169464371,\"y\":20.8},{\"x\":1685169764428,\"y\":20.9},{\"x\":1685170064553,\"y\":21},{\"x\":1685170364630,\"y\":20.8},{\"x\":1685170664689,\"y\":20.8},{\"x\":1685170964744,\"y\":20.9},{\"x\":1685171264839,\"y\":21},{\"x\":1685171564944,\"y\":21},{\"x\":1685171865046,\"y\":21.1},{\"x\":1685172165136,\"y\":21.1},{\"x\":1685172465216,\"y\":21.2},{\"x\":1685172765270,\"y\":21.5},{\"x\":1685173065378,\"y\":21.5},{\"x\":1685173365491,\"y\":21.5},{\"x\":1685173665574,\"y\":21.5},{\"x\":1685173965634,\"y\":21.5},{\"x\":1685174265651,\"y\":21.5},{\"x\":1685174565770,\"y\":21.5},{\"x\":1685174865842,\"y\":21.5},{\"x\":1685175165962,\"y\":21.5},{\"x\":1685175466056,\"y\":21.5},{\"x\":1685175766151,\"y\":21.6},{\"x\":1685176066247,\"y\":21.7},{\"x\":1685176366347,\"y\":21.6},{\"x\":1685176666421,\"y\":21.6},{\"x\":1685176966481,\"y\":21.7},{\"x\":1685177266535,\"y\":21.7},{\"x\":1685177566618,\"y\":21.7},{\"x\":1685177866715,\"y\":21.8},{\"x\":1685178166786,\"y\":21.7},{\"x\":1685178466868,\"y\":21.7},{\"x\":1685178766964,\"y\":21.8},{\"x\":1685179067013,\"y\":21.9},{\"x\":1685179367161,\"y\":21.9},{\"x\":1685179667226,\"y\":21.9},{\"x\":1685179967298,\"y\":21.9},{\"x\":1685180267304,\"y\":21.9},{\"x\":1685180567400,\"y\":21.9},{\"x\":1685180867474,\"y\":21.9},{\"x\":1685181167605,\"y\":21.9},{\"x\":1685181467661,\"y\":21.9},{\"x\":1685181767781,\"y\":21.9},{\"x\":1685182067847,\"y\":21.9},{\"x\":1685182367916,\"y\":21.9},{\"x\":1685182668010,\"y\":21.9},{\"x\":1685182968067,\"y\":21.9},{\"x\":1685183268142,\"y\":21.9},{\"x\":1685183568218,\"y\":21.9},{\"x\":1685183868264,\"y\":21.9},{\"x\":1685184168356,\"y\":22},{\"x\":1685184468449,\"y\":22},{\"x\":1685184768557,\"y\":22},{\"x\":1685185068624,\"y\":22.2},{\"x\":1685185368711,\"y\":22.1},{\"x\":1685185668784,\"y\":22.1},{\"x\":1685185968847,\"y\":22},{\"x\":1685186268953,\"y\":21.9},{\"x\":1685186568982,\"y\":21.9},{\"x\":1685186868996,\"y\":21.9},{\"x\":1685187169094,\"y\":21.9},{\"x\":1685187469146,\"y\":21.9},{\"x\":1685187769224,\"y\":21.9},{\"x\":1685188069250,\"y\":21.9},{\"x\":1685188369327,\"y\":21.9},{\"x\":1685188669411,\"y\":21.7},{\"x\":1685188969475,\"y\":21.7},{\"x\":1685189269500,\"y\":21.5},{\"x\":1685189569548,\"y\":21.5},{\"x\":1685189869583,\"y\":21.6},{\"x\":1685190169645,\"y\":21.7},{\"x\":1685190469717,\"y\":21.7},{\"x\":1685190769801,\"y\":21.7},{\"x\":1685191069848,\"y\":21.7},{\"x\":1685191369885,\"y\":21.7},{\"x\":1685191669980,\"y\":21.7},{\"x\":1685191970014,\"y\":21.7},{\"x\":1685192270055,\"y\":21.6},{\"x\":1685192570106,\"y\":21.7},{\"x\":1685192870150,\"y\":21.7},{\"x\":1685193170235,\"y\":21.6},{\"x\":1685193470249,\"y\":21.5},{\"x\":1685193770302,\"y\":21.5},{\"x\":1685194070311,\"y\":21.6},{\"x\":1685194370328,\"y\":21.7},{\"x\":1685194670393,\"y\":21.7},{\"x\":1685194985377,\"y\":21.7},{\"x\":1685195285440,\"y\":21.7},{\"x\":1685195585471,\"y\":21.7},{\"x\":1685195885473,\"y\":21.7},{\"x\":1685196185514,\"y\":21.6},{\"x\":1685196485562,\"y\":21.7},{\"x\":1685196800615,\"y\":21.7},{\"x\":1685197115572,\"y\":21.7},{\"x\":1685197415601,\"y\":21.7},{\"x\":1685197715640,\"y\":21.7},{\"x\":1685198015652,\"y\":21.7},{\"x\":1685198315706,\"y\":21.7},{\"x\":1685198615725,\"y\":21.7},{\"x\":1685198915775,\"y\":21.8},{\"x\":1685199215786,\"y\":21.8},{\"x\":1685199515831,\"y\":21.9},{\"x\":1685199815856,\"y\":21.8},{\"x\":1685200115858,\"y\":21.9},{\"x\":1685200415875,\"y\":21.8},{\"x\":1685200715913,\"y\":21.8},{\"x\":1685201015918,\"y\":21.8},{\"x\":1685201315964,\"y\":21.9},{\"x\":1685201616032,\"y\":21.9},{\"x\":1685201916044,\"y\":21.9},{\"x\":1685202231051,\"y\":21.9},{\"x\":1685202531062,\"y\":21.9},{\"x\":1685202831089,\"y\":21.8},{\"x\":1685203131095,\"y\":21.8},{\"x\":1685203431195,\"y\":21.8},{\"x\":1685203746138,\"y\":21.8},{\"x\":1685204046168,\"y\":21.8},{\"x\":1685204346182,\"y\":21.7},{\"x\":1685204646203,\"y\":21.8},{\"x\":1685204946241,\"y\":21.9},{\"x\":1685205246243,\"y\":21.9},{\"x\":1685205546262,\"y\":21.9},{\"x\":1685205846296,\"y\":21.9},{\"x\":1685206146312,\"y\":21.9},{\"x\":1685206446335,\"y\":21.9},{\"x\":1685206746375,\"y\":21.9},{\"x\":1685207046395,\"y\":21.9},{\"x\":1685207346549,\"y\":21.9},{\"x\":1685207661544,\"y\":21.9},{\"x\":1685207961647,\"y\":21.9},{\"x\":1685208261700,\"y\":21.9},{\"x\":1685208561766,\"y\":21.9},{\"x\":1685208861855,\"y\":21.9},{\"x\":1685209161943,\"y\":21.9},{\"x\":1685209462007,\"y\":21.9},{\"x\":1685209762097,\"y\":21.9},{\"x\":1685210077101,\"y\":21.9},{\"x\":1685210377152,\"y\":21.9},{\"x\":1685210677231,\"y\":21.9},{\"x\":1685210977296,\"y\":21.9},{\"x\":1685211277406,\"y\":21.9},{\"x\":1685211577537,\"y\":21.9},{\"x\":1685211877620,\"y\":21.9},{\"x\":1685212177660,\"y\":21.9},{\"x\":1685212477797,\"y\":21.9},{\"x\":1685212777837,\"y\":21.9},{\"x\":1685213077881,\"y\":21.9},{\"x\":1685213377932,\"y\":21.9},{\"x\":1685213677997,\"y\":21.9},{\"x\":1685213978205,\"y\":21.9},{\"x\":1685214293200,\"y\":21.9},{\"x\":1685214593270,\"y\":21.9},{\"x\":1685214893375,\"y\":21.9},{\"x\":1685215193482,\"y\":21.9},{\"x\":1685215493529,\"y\":21.8},{\"x\":1685215793531,\"y\":21.9},{\"x\":1685216093643,\"y\":21.9},{\"x\":1685216393738,\"y\":22},{\"x\":1685216693784,\"y\":21.9},{\"x\":1685216993880,\"y\":21.9},{\"x\":1685217293962,\"y\":21.7},{\"x\":1685217594100,\"y\":21.7},{\"x\":1685217894151,\"y\":21.7},{\"x\":1685218194256,\"y\":21.6},{\"x\":1685218494376,\"y\":21.5},{\"x\":1685218794467,\"y\":21.5},{\"x\":1685219094477,\"y\":21.5},{\"x\":1685219394675,\"y\":21.5},{\"x\":1685219709688,\"y\":21.5},{\"x\":1685220009789,\"y\":21.5},{\"x\":1685220309889,\"y\":21.5},{\"x\":1685220609986,\"y\":21.4},{\"x\":1685220910080,\"y\":21.5},{\"x\":1685221210236,\"y\":21.5},{\"x\":1685221510267,\"y\":21.5},{\"x\":1685221810331,\"y\":21.5},{\"x\":1685222110381,\"y\":21.4},{\"x\":1685222410481,\"y\":21.4},{\"x\":1685222710598,\"y\":21.3},{\"x\":1685223010661,\"y\":21.5},{\"x\":1685223310759,\"y\":21.5},{\"x\":1685223610881,\"y\":21.5},{\"x\":1685223911009,\"y\":21.5},{\"x\":1685224211106,\"y\":21.5},{\"x\":1685224511191,\"y\":21.5},{\"x\":1685224811272,\"y\":21.4},{\"x\":1685225111309,\"y\":21.4},{\"x\":1685225411398,\"y\":21.3},{\"x\":1685225711438,\"y\":21.2},{\"x\":1685226011552,\"y\":21.2},{\"x\":1685226311659,\"y\":21.2},{\"x\":1685226611773,\"y\":21.2},{\"x\":1685226911876,\"y\":21.2},{\"x\":1685227211995,\"y\":21.2},{\"x\":1685227512070,\"y\":21.2},{\"x\":1685227812175,\"y\":21.2},{\"x\":1685228112250,\"y\":21.2},{\"x\":1685228412314,\"y\":21},{\"x\":1685228712352,\"y\":21},{\"x\":1685229012470,\"y\":21},{\"x\":1685229312587,\"y\":21},{\"x\":1685229612662,\"y\":21.1},{\"x\":1685229912736,\"y\":21},{\"x\":1685230212847,\"y\":20.9},{\"x\":1685230512904,\"y\":21},{\"x\":1685230813034,\"y\":21},{\"x\":1685231113081,\"y\":21},{\"x\":1685231413122,\"y\":20.8},{\"x\":1685231713183,\"y\":20.8},{\"x\":1685232013342,\"y\":20.8},{\"x\":1685232328321,\"y\":20.8},{\"x\":1685232628419,\"y\":20.8},{\"x\":1685232928528,\"y\":20.8},{\"x\":1685233228604,\"y\":20.8},{\"x\":1685233528687,\"y\":20.8},{\"x\":1685233828743,\"y\":20.8},{\"x\":1685234128809,\"y\":20.8},{\"x\":1685234428883,\"y\":20.8},{\"x\":1685234728963,\"y\":20.8},{\"x\":1685235028977,\"y\":20.8},{\"x\":1685235329064,\"y\":20.8},{\"x\":1685235629163,\"y\":20.8},{\"x\":1685235929251,\"y\":20.8},{\"x\":1685236229341,\"y\":20.8},{\"x\":1685236529423,\"y\":20.7},{\"x\":1685236829513,\"y\":20.7},{\"x\":1685237129612,\"y\":20.8},{\"x\":1685237429697,\"y\":20.7},{\"x\":1685237729771,\"y\":20.7},{\"x\":1685238029832,\"y\":20.5},{\"x\":1685238329902,\"y\":20.5},{\"x\":1685238629974,\"y\":20.5},{\"x\":1685238930096,\"y\":20.5},{\"x\":1685239230144,\"y\":20.6},{\"x\":1685239530225,\"y\":20.6},{\"x\":1685239830331,\"y\":20.6},{\"x\":1685240130366,\"y\":20.6},{\"x\":1685240430509,\"y\":20.5},{\"x\":1685240730597,\"y\":20.5},{\"x\":1685241030697,\"y\":20.4},{\"x\":1685241330716,\"y\":20.4},{\"x\":1685241630792,\"y\":20.4},{\"x\":1685241930839,\"y\":20.4},{\"x\":1685242230957,\"y\":20.4},{\"x\":1685242531061,\"y\":20.4},{\"x\":1685242831116,\"y\":20.4},{\"x\":1685243131245,\"y\":20.4},{\"x\":1685243431358,\"y\":20.4},{\"x\":1685243731422,\"y\":20.3},{\"x\":1685244031484,\"y\":20.3},{\"x\":1685244331554,\"y\":20.2},{\"x\":1685244631614,\"y\":20.3},{\"x\":1685244931700,\"y\":20.3},{\"x\":1685245231790,\"y\":20.3},{\"x\":1685245531908,\"y\":20.3},{\"x\":1685245832059,\"y\":20.3},{\"x\":1685246132107,\"y\":20.3},{\"x\":1685246432206,\"y\":20.3},{\"x\":1685246732334,\"y\":20.3},{\"x\":1685247032445,\"y\":20.3},{\"x\":1685247332472,\"y\":20.2},{\"x\":1685247632538,\"y\":20.2},{\"x\":1685247932668,\"y\":20.2},{\"x\":1685248232731,\"y\":20.2},{\"x\":1685248532806,\"y\":20.2},{\"x\":1685248832930,\"y\":20.2},{\"x\":1685249133015,\"y\":20.2},{\"x\":1685249433093,\"y\":20.2},{\"x\":1685249733189,\"y\":20.2},{\"x\":1685250033291,\"y\":20.1},{\"x\":1685250333378,\"y\":20},{\"x\":1685250633446,\"y\":20},{\"x\":1685250933493,\"y\":20},{\"x\":1685251233569,\"y\":20},{\"x\":1685251533627,\"y\":20},{\"x\":1685251833774,\"y\":20.1},{\"x\":1685252133852,\"y\":20.2},{\"x\":1685252433955,\"y\":20.2},{\"x\":1685252734091,\"y\":20.2},{\"x\":1685253034179,\"y\":20},{\"x\":1685253334257,\"y\":19.9},{\"x\":1685253634325,\"y\":20},{\"x\":1685253934367,\"y\":20.2},{\"x\":1685254234489,\"y\":20.2},{\"x\":1685254534587,\"y\":20.2},{\"x\":1685254834680,\"y\":20.2},{\"x\":1685255134805,\"y\":20.3},{\"x\":1685255434885,\"y\":20.3},{\"x\":1685255735002,\"y\":20.3},{\"x\":1685256035085,\"y\":20.5},{\"x\":1685256335179,\"y\":20.5},{\"x\":1685256635254,\"y\":20.3},{\"x\":1685256935309,\"y\":20.3},{\"x\":1685257235338,\"y\":20.3},{\"x\":1685257535440,\"y\":20.3},{\"x\":1685257835520,\"y\":20.4},{\"x\":1685258135635,\"y\":20.5},{\"x\":1685258435709,\"y\":20.5},{\"x\":1685258735790,\"y\":20.5},{\"x\":1685259035904,\"y\":20.5},{\"x\":1685259335965,\"y\":20.4},{\"x\":1685259636005,\"y\":20.4},{\"x\":1685259936052,\"y\":20.4},{\"x\":1685260236148,\"y\":20.5},{\"x\":1685260536249,\"y\":20.4},{\"x\":1685260836320,\"y\":20.5},{\"x\":1685261136489,\"y\":20.5},{\"x\":1685261436489,\"y\":20.6},{\"x\":1685261736607,\"y\":20.5},{\"x\":1685262036710,\"y\":20.5},{\"x\":1685262336806,\"y\":20.4},{\"x\":1685262636943,\"y\":20.4},{\"x\":1685262936991,\"y\":20.3},{\"x\":1685263237019,\"y\":20.3},{\"x\":1685263537113,\"y\":20.3},{\"x\":1685263837223,\"y\":20.3},{\"x\":1685264137303,\"y\":20.3},{\"x\":1685264437449,\"y\":20.4},{\"x\":1685264737520,\"y\":20.3},{\"x\":1685265037596,\"y\":20.4},{\"x\":1685265337653,\"y\":20.3},{\"x\":1685265637725,\"y\":20.3},{\"x\":1685265937773,\"y\":20.3},{\"x\":1685266237884,\"y\":20.3},{\"x\":1685266537917,\"y\":20.3},{\"x\":1685266837940,\"y\":20.3},{\"x\":1685267138006,\"y\":20.3},{\"x\":1685267438089,\"y\":20.3},{\"x\":1685267738177,\"y\":20.3},{\"x\":1685268038239,\"y\":20.4},{\"x\":1685268338288,\"y\":20.3},{\"x\":1685268638340,\"y\":20.4},{\"x\":1685268938413,\"y\":20.3},{\"x\":1685269238479,\"y\":20.4},{\"x\":1685269538567,\"y\":20.4},{\"x\":1685269838641,\"y\":20.4},{\"x\":1685270138681,\"y\":20.3},{\"x\":1685270438761,\"y\":20.3},{\"x\":1685270738813,\"y\":20.3},{\"x\":1685271038911,\"y\":20.3},{\"x\":1685271338993,\"y\":20},{\"x\":1685271638994,\"y\":20},{\"x\":1685271939107,\"y\":20.2},{\"x\":1685272239155,\"y\":20.3},{\"x\":1685272539230,\"y\":20.3},{\"x\":1685272839299,\"y\":20.3},{\"x\":1685273139339,\"y\":20.3},{\"x\":1685273439382,\"y\":20.2},{\"x\":1685273739450,\"y\":19.9},{\"x\":1685274039516,\"y\":19.9},{\"x\":1685274339570,\"y\":20.2},{\"x\":1685274639651,\"y\":20.3},{\"x\":1685274939715,\"y\":20.5},{\"x\":1685275239718,\"y\":20.5},{\"x\":1685275539821,\"y\":20.6},{\"x\":1685275839883,\"y\":20.5},{\"x\":1685276139936,\"y\":20.3},{\"x\":1685276439971,\"y\":20.3},{\"x\":1685276740019,\"y\":20.3},{\"x\":1685277040061,\"y\":20.2},{\"x\":1685277340154,\"y\":20},{\"x\":1685277640177,\"y\":19.9},{\"x\":1685277940231,\"y\":20},{\"x\":1685278240249,\"y\":20},{\"x\":1685278540299,\"y\":19.9},{\"x\":1685278840376,\"y\":19.9},{\"x\":1685279140399,\"y\":19.5},{\"x\":1685279440422,\"y\":19.7},{\"x\":1685279740489,\"y\":19.8},{\"x\":1685280040542,\"y\":19.8},{\"x\":1685280340547,\"y\":19.5},{\"x\":1685280640612,\"y\":19.5},{\"x\":1685280940692,\"y\":19.5},{\"x\":1685280995978,\"y\":19.5},{\"x\":1685281186232,\"y\":19.5},{\"x\":1685281372604,\"y\":19.5},{\"x\":1685281687601,\"y\":19.5},{\"x\":1685281710226,\"y\":19.5},{\"x\":1685281749591,\"y\":19.5},{\"x\":1685281762017,\"y\":19.5},{\"x\":1685281799605,\"y\":19.5},{\"x\":1685282023556,\"y\":19.5},{\"x\":1685282044430,\"y\":19.5},{\"x\":1685282166915,\"y\":19.5},{\"x\":1685282208131,\"y\":19.5},{\"x\":1685282217923,\"y\":19.5},{\"x\":1685282260379,\"y\":19.5},{\"x\":1685282339652,\"y\":19.5},{\"x\":1685282364558,\"y\":19.5},{\"x\":1685282412077,\"y\":19.5},{\"x\":1685282588188,\"y\":19.5},{\"x\":1685282614967,\"y\":19.5},{\"x\":1685282852605,\"y\":19.5},{\"x\":1685282960718,\"y\":19.5},{\"x\":1685282980565,\"y\":19.5},{\"x\":1685283037810,\"y\":19.5},{\"x\":1685283067001,\"y\":19.5},{\"x\":1685283122144,\"y\":19.5},{\"x\":1685283272482,\"y\":19.5},{\"x\":1685283343031,\"y\":19.5},{\"x\":1685283445211,\"y\":19.5},{\"x\":1685283473050,\"y\":19.5},{\"x\":1685283503945,\"y\":19.4},{\"x\":1685283559122,\"y\":19.5},{\"x\":1685283661335,\"y\":19.5},{\"x\":1685283679196,\"y\":19.5},{\"x\":1685283753141,\"y\":19.5},{\"x\":1685283789923,\"y\":19.4},{\"x\":1685283796674,\"y\":19.4},{\"x\":1685284111596,\"y\":19.5},{\"x\":1685284411624,\"y\":19.5},{\"x\":1685284726634,\"y\":20},{\"x\":1685285041633,\"y\":19.9},{\"x\":1685285341650,\"y\":19.6},{\"x\":1685285641691,\"y\":19.8},{\"x\":1685285941711,\"y\":20},{\"x\":1685286241759,\"y\":20},{\"x\":1685286541841,\"y\":19.7},{\"x\":1685286856821,\"y\":20},{\"x\":1685287156833,\"y\":20.3},{\"x\":1685287456865,\"y\":20.4},{\"x\":1685287756902,\"y\":20.3},{\"x\":1685288056933,\"y\":20.3},{\"x\":1685288357007,\"y\":20.3},{\"x\":1685288671988,\"y\":20.1},{\"x\":1685288972004,\"y\":20.2},{\"x\":1685289272052,\"y\":20},{\"x\":1685289572077,\"y\":20},{\"x\":1685289872089,\"y\":20},{\"x\":1685290172161,\"y\":19.9},{\"x\":1685290487147,\"y\":20},{\"x\":1685290787163,\"y\":20},{\"x\":1685291087218,\"y\":19.9},{\"x\":1685291387247,\"y\":19.9},{\"x\":1685291687274,\"y\":20},{\"x\":1685291987283,\"y\":20.2},{\"x\":1685292287334,\"y\":20},{\"x\":1685292587380,\"y\":19.9},{\"x\":1685292887389,\"y\":19.9},{\"x\":1685293187427,\"y\":19.8},{\"x\":1685293502456,\"y\":19.7},{\"x\":1685293817480,\"y\":19.5},{\"x\":1685294117492,\"y\":19.6},{\"x\":1685294417507,\"y\":19.9},{\"x\":1685294717548,\"y\":20},{\"x\":1685295017585,\"y\":19.9},{\"x\":1685295317622,\"y\":19.6},{\"x\":1685295617633,\"y\":20},{\"x\":1685295917642,\"y\":19.7},{\"x\":1685296217701,\"y\":19.9},{\"x\":1685296532704,\"y\":19.5},{\"x\":1685296832750,\"y\":19.5},{\"x\":1685297132754,\"y\":19.5},{\"x\":1685297432771,\"y\":19.5},{\"x\":1685297732796,\"y\":19.5},{\"x\":1685298032828,\"y\":19.5},{\"x\":1685298332882,\"y\":19.5},{\"x\":1685298647889,\"y\":19.5},{\"x\":1685298947921,\"y\":19.5},{\"x\":1685299247969,\"y\":19.5},{\"x\":1685299547997,\"y\":19.5},{\"x\":1685299848026,\"y\":19.5},{\"x\":1685300163050,\"y\":19.5},{\"x\":1685300463065,\"y\":19.5},{\"x\":1685300763075,\"y\":19.5},{\"x\":1685301063109,\"y\":19.5},{\"x\":1685301363164,\"y\":19.5},{\"x\":1685301678208,\"y\":19.5},{\"x\":1685301993216,\"y\":19.5},{\"x\":1685302293269,\"y\":19.5},{\"x\":1685302593314,\"y\":19.5},{\"x\":1685302908310,\"y\":19.5},{\"x\":1685303208329,\"y\":19.5},{\"x\":1685303508359,\"y\":19.5},{\"x\":1685303808369,\"y\":19.5},{\"x\":1685304108395,\"y\":19.5},{\"x\":1685304408424,\"y\":19.3},{\"x\":1685304708455,\"y\":19.4},{\"x\":1685305008461,\"y\":19.4},{\"x\":1685305308490,\"y\":19.4},{\"x\":1685305608530,\"y\":19.5},{\"x\":1685305908539,\"y\":19.5},{\"x\":1685306208562,\"y\":19.4},{\"x\":1685306508616,\"y\":19.5},{\"x\":1685306823616,\"y\":19.5},{\"x\":1685307123649,\"y\":19.5},{\"x\":1685307423661,\"y\":19.5},{\"x\":1685307723699,\"y\":19.5},{\"x\":1685308023733,\"y\":19.5},{\"x\":1685308323779,\"y\":19.5},{\"x\":1685308638772,\"y\":19.5},{\"x\":1685308938793,\"y\":19.5},{\"x\":1685309238818,\"y\":19.5},{\"x\":1685309538833,\"y\":19.5},{\"x\":1685309838872,\"y\":19.5},{\"x\":1685310138910,\"y\":19.5},{\"x\":1685310453907,\"y\":19.5},{\"x\":1685310768906,\"y\":19.5},{\"x\":1685311068915,\"y\":19.5},{\"x\":1685311368965,\"y\":19.5},{\"x\":1685311668966,\"y\":19.5},{\"x\":1685311968998,\"y\":19.5},{\"x\":1685312269016,\"y\":19.5},{\"x\":1685312569034,\"y\":19.5},{\"x\":1685312869061,\"y\":19.5},{\"x\":1685313169072,\"y\":19.5},{\"x\":1685313484093,\"y\":19.5},{\"x\":1685313784097,\"y\":19.6},{\"x\":1685314084113,\"y\":19.5},{\"x\":1685314384134,\"y\":19.7},{\"x\":1685314684165,\"y\":19.9},{\"x\":1685314984191,\"y\":19.9},{\"x\":1685315299157,\"y\":19.9},{\"x\":1685315599168,\"y\":20},{\"x\":1685315899192,\"y\":19.9},{\"x\":1685316199221,\"y\":20.1},{\"x\":1685316514243,\"y\":20},{\"x\":1685316814264,\"y\":20.2},{\"x\":1685317129218,\"y\":20.2},{\"x\":1685317444217,\"y\":20.2},{\"x\":1685317744298,\"y\":20.3},{\"x\":1685318059259,\"y\":20.3},{\"x\":1685318359275,\"y\":20.3},{\"x\":1685318659355,\"y\":20.3},{\"x\":1685318959404,\"y\":20.3},{\"x\":1685319259419,\"y\":20.2},{\"x\":1685319559443,\"y\":20.3},{\"x\":1685319859535,\"y\":20.3},{\"x\":1685320159597,\"y\":20.3},{\"x\":1685320459678,\"y\":20.3},{\"x\":1685320759851,\"y\":20.3},{\"x\":1685321074857,\"y\":20.2},{\"x\":1685321374890,\"y\":20.3},{\"x\":1685321674930,\"y\":20.3},{\"x\":1685321974980,\"y\":20.3},{\"x\":1685322274993,\"y\":20.3},{\"x\":1685322575024,\"y\":20.3},{\"x\":1685322875122,\"y\":20.3},{\"x\":1685323175196,\"y\":20.3},{\"x\":1685323475244,\"y\":20.3},{\"x\":1685323775309,\"y\":20.4},{\"x\":1685324075375,\"y\":20.3},{\"x\":1685324375402,\"y\":20.4},{\"x\":1685324675425,\"y\":20.5},{\"x\":1685324975485,\"y\":20.5},{\"x\":1685325275523,\"y\":20.5},{\"x\":1685325575582,\"y\":20.5},{\"x\":1685325875647,\"y\":20.5},{\"x\":1685326175713,\"y\":20.5},{\"x\":1685326475768,\"y\":20.4},{\"x\":1685326775825,\"y\":20.4},{\"x\":1685327075908,\"y\":20.4},{\"x\":1685327376002,\"y\":20.4},{\"x\":1685327676015,\"y\":20.4},{\"x\":1685327976059,\"y\":20.4},{\"x\":1685328276184,\"y\":20.4},{\"x\":1685328591156,\"y\":20.4},{\"x\":1685328891332,\"y\":20.4},{\"x\":1685329206306,\"y\":20.4},{\"x\":1685329506397,\"y\":20.4},{\"x\":1685329806440,\"y\":20.3},{\"x\":1685330106549,\"y\":20.3},{\"x\":1685330406587,\"y\":20.3},{\"x\":1685330706687,\"y\":20.2},{\"x\":1685331006741,\"y\":20.3},{\"x\":1685331306759,\"y\":20.3},{\"x\":1685331606822,\"y\":20.2},{\"x\":1685331906872,\"y\":20.2},{\"x\":1685332206974,\"y\":20.2},{\"x\":1685332507043,\"y\":20.3},{\"x\":1685332807216,\"y\":20.2},{\"x\":1685333122210,\"y\":20.2},{\"x\":1685333422298,\"y\":20.2},{\"x\":1685333722396,\"y\":20.2},{\"x\":1685334022464,\"y\":20.2},{\"x\":1685334322536,\"y\":20.2},{\"x\":1685334622591,\"y\":20.2},{\"x\":1685334922644,\"y\":20.2},{\"x\":1685335222714,\"y\":20.1},{\"x\":1685335522841,\"y\":20},{\"x\":1685335822919,\"y\":20},{\"x\":1685336122954,\"y\":20},{\"x\":1685336423087,\"y\":19.9},{\"x\":1685336723138,\"y\":20},{\"x\":1685337023185,\"y\":19.9},{\"x\":1685337323274,\"y\":19.9},{\"x\":1685337623330,\"y\":19.9},{\"x\":1685337923414,\"y\":20},{\"x\":1685338223520,\"y\":19.9},{\"x\":1685338523616,\"y\":20},{\"x\":1685338823724,\"y\":19.9},{\"x\":1685339123797,\"y\":20},{\"x\":1685339423870,\"y\":19.9},{\"x\":1685339723950,\"y\":19.9},{\"x\":1685340024030,\"y\":19.9},{\"x\":1685340324090,\"y\":20},{\"x\":1685340624115,\"y\":20},{\"x\":1685340924164,\"y\":20},{\"x\":1685341224265,\"y\":20},{\"x\":1685341524353,\"y\":20},{\"x\":1685341824453,\"y\":20},{\"x\":1685342124531,\"y\":20.2},{\"x\":1685342424622,\"y\":19.9},{\"x\":1685342724753,\"y\":20},{\"x\":1685343024799,\"y\":20.2},{\"x\":1685343324830,\"y\":20},{\"x\":1685343624890,\"y\":19.9},{\"x\":1685343924971,\"y\":20.2},{\"x\":1685344225070,\"y\":20.2},{\"x\":1685344525149,\"y\":20.3},{\"x\":1685344825216,\"y\":20.2},{\"x\":1685345125344,\"y\":20.2},{\"x\":1685345425362,\"y\":20.3},{\"x\":1685345725441,\"y\":20.3},{\"x\":1685346025536,\"y\":20.3},{\"x\":1685346325591,\"y\":20},{\"x\":1685346625679,\"y\":20.2},{\"x\":1685346925731,\"y\":20.2},{\"x\":1685347225748,\"y\":20.3},{\"x\":1685347525839,\"y\":20.3},{\"x\":1685347825931,\"y\":20.3},{\"x\":1685348126031,\"y\":20.3},{\"x\":1685348426089,\"y\":20.3},{\"x\":1685348726163,\"y\":20.3},{\"x\":1685349026225,\"y\":20.3},{\"x\":1685349326308,\"y\":20.3},{\"x\":1685349626392,\"y\":20},{\"x\":1685349926466,\"y\":20},{\"x\":1685350226553,\"y\":20.2},{\"x\":1685350526651,\"y\":19.9},{\"x\":1685350826811,\"y\":20},{\"x\":1685351141763,\"y\":19.9},{\"x\":1685351441827,\"y\":20.2},{\"x\":1685351741927,\"y\":20.2},{\"x\":1685352042064,\"y\":20.1},{\"x\":1685352342100,\"y\":20.3},{\"x\":1685352642170,\"y\":20},{\"x\":1685352942245,\"y\":19.9},{\"x\":1685353242314,\"y\":19.9},{\"x\":1685353542387,\"y\":20},{\"x\":1685353842461,\"y\":20},{\"x\":1685354142491,\"y\":20},{\"x\":1685354442557,\"y\":19.9},{\"x\":1685354742628,\"y\":20},{\"x\":1685355042729,\"y\":20.3},{\"x\":1685355342838,\"y\":20.2},{\"x\":1685355642921,\"y\":20.3},{\"x\":1685355942977,\"y\":20},{\"x\":1685356243149,\"y\":19.9},{\"x\":1685356558148,\"y\":19.5},{\"x\":1685356858211,\"y\":19.9},{\"x\":1685357158249,\"y\":20},{\"x\":1685357458282,\"y\":19.6},{\"x\":1685357758424,\"y\":20},{\"x\":1685358058471,\"y\":19.5},{\"x\":1685358358569,\"y\":19.5},{\"x\":1685358658635,\"y\":20},{\"x\":1685358958716,\"y\":20.1},{\"x\":1685359258767,\"y\":20},{\"x\":1685359558873,\"y\":19.9},{\"x\":1685359858954,\"y\":20.2},{\"x\":1685360159014,\"y\":20.2},{\"x\":1685360459077,\"y\":20},{\"x\":1685360759117,\"y\":19.9},{\"x\":1685361059194,\"y\":19.8},{\"x\":1685361359285,\"y\":20},{\"x\":1685361659377,\"y\":20},{\"x\":1685361959454,\"y\":20.1},{\"x\":1685362259508,\"y\":20.2},{\"x\":1685362559586,\"y\":19.9},{\"x\":1685362859674,\"y\":20},{\"x\":1685363159756,\"y\":20},{\"x\":1685363459810,\"y\":19.5},{\"x\":1685363759869,\"y\":19.6},{\"x\":1685364059953,\"y\":19.5},{\"x\":1685364360040,\"y\":19.9},{\"x\":1685364660157,\"y\":19.9},{\"x\":1685364960261,\"y\":19.9},{\"x\":1685365260272,\"y\":19.9},{\"x\":1685365560367,\"y\":20},{\"x\":1685365860483,\"y\":19.9},{\"x\":1685366160569,\"y\":19.9},{\"x\":1685366460633,\"y\":19.5},{\"x\":1685366760670,\"y\":19.5},{\"x\":1685367060781,\"y\":19.5},{\"x\":1685367360928,\"y\":19.5},{\"x\":1685367660996,\"y\":19.5},{\"x\":1685367961125,\"y\":19.5},{\"x\":1685368261267,\"y\":19.5},{\"x\":1685368561326,\"y\":19.5},{\"x\":1685368861443,\"y\":19.5},{\"x\":1685369161528,\"y\":19.5},{\"x\":1685369461571,\"y\":19.5},{\"x\":1685369761618,\"y\":19.5},{\"x\":1685370061811,\"y\":19.5},{\"x\":1685370361876,\"y\":19.5},{\"x\":1685370661975,\"y\":19.5},{\"x\":1685370962096,\"y\":19.5},{\"x\":1685371262161,\"y\":19.5},{\"x\":1685371562287,\"y\":19.5},{\"x\":1685371862386,\"y\":19.5},{\"x\":1685372162471,\"y\":19.5},{\"x\":1685372462563,\"y\":19.4},{\"x\":1685372762583,\"y\":19.2},{\"x\":1685373062692,\"y\":19.2},{\"x\":1685373362813,\"y\":19.1},{\"x\":1685373662925,\"y\":19.3},{\"x\":1685373963012,\"y\":19.2},{\"x\":1685374263073,\"y\":19.2},{\"x\":1685374563193,\"y\":19},{\"x\":1685374863306,\"y\":19.2},{\"x\":1685375163412,\"y\":19.3},{\"x\":1685375463446,\"y\":19.4},{\"x\":1685375763533,\"y\":19.4},{\"x\":1685376063611,\"y\":19.2},{\"x\":1685376363690,\"y\":19.1},{\"x\":1685376663795,\"y\":19},{\"x\":1685376963909,\"y\":19.2},{\"x\":1685377263998,\"y\":19.3},{\"x\":1685377564112,\"y\":19.1},{\"x\":1685377864184,\"y\":19.2},{\"x\":1685378164260,\"y\":19.1},{\"x\":1685378464309,\"y\":19},{\"x\":1685378764426,\"y\":19},{\"x\":1685379064528,\"y\":19},{\"x\":1685379364568,\"y\":19.2},{\"x\":1685379664627,\"y\":19.1},{\"x\":1685379964711,\"y\":19},{\"x\":1685380264778,\"y\":19.1},{\"x\":1685380564838,\"y\":19.2},{\"x\":1685380864899,\"y\":19},{\"x\":1685381164922,\"y\":19},{\"x\":1685381464995,\"y\":18.9},{\"x\":1685381765085,\"y\":18.9},{\"x\":1685382065145,\"y\":18.9},{\"x\":1685382365238,\"y\":18.9},{\"x\":1685382665304,\"y\":18.9},{\"x\":1685382965394,\"y\":19},{\"x\":1685383265458,\"y\":19},{\"x\":1685383565523,\"y\":18.9},{\"x\":1685383865573,\"y\":18.9},{\"x\":1685384165635,\"y\":18.7},{\"x\":1685384465670,\"y\":18.7},{\"x\":1685384765740,\"y\":18.7},{\"x\":1685385065812,\"y\":18.9},{\"x\":1685385365909,\"y\":18.8},{\"x\":1685385665940,\"y\":18.7},{\"x\":1685385966015,\"y\":18.9},{\"x\":1685386266095,\"y\":18.9},{\"x\":1685386566159,\"y\":18.8},{\"x\":1685386866204,\"y\":18.5},{\"x\":1685387166270,\"y\":18.5},{\"x\":1685387466325,\"y\":18.7},{\"x\":1685387766371,\"y\":18.5},{\"x\":1685388066417,\"y\":18.7},{\"x\":1685388366441,\"y\":18.7},{\"x\":1685388666495,\"y\":18.7},{\"x\":1685388966561,\"y\":18.4},{\"x\":1685389266600,\"y\":18.4},{\"x\":1685389566607,\"y\":18.4},{\"x\":1685389866660,\"y\":18.4},{\"x\":1685390166677,\"y\":18.5},{\"x\":1685390466746,\"y\":18.5},{\"x\":1685390781720,\"y\":18.6},{\"x\":1685391081770,\"y\":18.6},{\"x\":1685391381808,\"y\":18.4},{\"x\":1685391681836,\"y\":18.4},{\"x\":1685391981922,\"y\":18.4},{\"x\":1685392281964,\"y\":18.4},{\"x\":1685392582032,\"y\":18.4},{\"x\":1685392897029,\"y\":18.4},{\"x\":1685393197037,\"y\":18.4},{\"x\":1685393497068,\"y\":18.4},{\"x\":1685393797107,\"y\":18.4},{\"x\":1685394097146,\"y\":18.4},{\"x\":1685394397195,\"y\":18.4},{\"x\":1685394697240,\"y\":18.4},{\"x\":1685394997269,\"y\":18.4},{\"x\":1685395297315,\"y\":18.4},{\"x\":1685395597344,\"y\":18.4},{\"x\":1685395897378,\"y\":18.4},{\"x\":1685396197391,\"y\":18.4},{\"x\":1685396497411,\"y\":18.4},{\"x\":1685396797418,\"y\":18.3},{\"x\":1685397097514,\"y\":18.4},{\"x\":1685397397526,\"y\":18.2},{\"x\":1685397697553,\"y\":18.1},{\"x\":1685397997603,\"y\":18.1},{\"x\":1685398297622,\"y\":18.1},{\"x\":1685398597700,\"y\":18.1},{\"x\":1685398897737,\"y\":18.2},{\"x\":1685399212765,\"y\":18.2},{\"x\":1685399527768,\"y\":18},{\"x\":1685399827776,\"y\":18.1},{\"x\":1685400127841,\"y\":17.9},{\"x\":1685400442841,\"y\":18},{\"x\":1685400742935,\"y\":17.9},{\"x\":1685401057950,\"y\":18},{\"x\":1685401357960,\"y\":18},{\"x\":1685401657984,\"y\":18.1},{\"x\":1685401957995,\"y\":18},{\"x\":1685402258067,\"y\":18.1},{\"x\":1685402573081,\"y\":18},{\"x\":1685402873096,\"y\":17.9},{\"x\":1685403173117,\"y\":17.9},{\"x\":1685403473129,\"y\":17.9},{\"x\":1685403773148,\"y\":17.9},{\"x\":1685404073175,\"y\":18},{\"x\":1685404373229,\"y\":17.9},{\"x\":1685404688249,\"y\":17.9},{\"x\":1685404988317,\"y\":17.9},{\"x\":1685405288409,\"y\":17.9},{\"x\":1685405588488,\"y\":17.9},{\"x\":1685405888522,\"y\":17.9},{\"x\":1685406188569,\"y\":17.9},{\"x\":1685406488623,\"y\":17.9},{\"x\":1685406788688,\"y\":17.9},{\"x\":1685407088721,\"y\":17.9},{\"x\":1685407388765,\"y\":17.9},{\"x\":1685407688838,\"y\":17.9},{\"x\":1685407988885,\"y\":17.9},{\"x\":1685408288915,\"y\":17.9},{\"x\":1685408588993,\"y\":17.9},{\"x\":1685408889074,\"y\":17.9},{\"x\":1685409189147,\"y\":17.9},{\"x\":1685409489206,\"y\":17.9},{\"x\":1685409789249,\"y\":17.9},{\"x\":1685410089310,\"y\":17.9},{\"x\":1685410389345,\"y\":17.9},{\"x\":1685410704354,\"y\":17.9},{\"x\":1685411004452,\"y\":17.9},{\"x\":1685411304516,\"y\":17.9},{\"x\":1685411604552,\"y\":17.9},{\"x\":1685411904625,\"y\":17.8},{\"x\":1685412204887,\"y\":17.8},{\"x\":1685412519762,\"y\":17.7},{\"x\":1685412819867,\"y\":17.7},{\"x\":1685413119886,\"y\":17.8},{\"x\":1685413419976,\"y\":17.7},{\"x\":1685413719986,\"y\":17.7},{\"x\":1685414020028,\"y\":17.7},{\"x\":1685414320085,\"y\":17.7},{\"x\":1685414620140,\"y\":17.7},{\"x\":1685414920229,\"y\":17.7},{\"x\":1685415220270,\"y\":17.7},{\"x\":1685415520407,\"y\":17.7},{\"x\":1685415820425,\"y\":17.7},{\"x\":1685416120509,\"y\":17.7},{\"x\":1685416420519,\"y\":17.7},{\"x\":1685416720605,\"y\":17.7},{\"x\":1685417020626,\"y\":17.7},{\"x\":1685417320672,\"y\":17.7},{\"x\":1685417620774,\"y\":17.7},{\"x\":1685417920843,\"y\":17.7},{\"x\":1685418220900,\"y\":17.8},{\"x\":1685418520971,\"y\":17.7},{\"x\":1685418821022,\"y\":17.7},{\"x\":1685419121073,\"y\":17.8},{\"x\":1685419421171,\"y\":17.9},{\"x\":1685419721181,\"y\":17.7},{\"x\":1685420021225,\"y\":17.7},{\"x\":1685420321304,\"y\":17.6},{\"x\":1685420621355,\"y\":17.7},{\"x\":1685420921447,\"y\":17.7},{\"x\":1685421221522,\"y\":17.7},{\"x\":1685421521582,\"y\":17.7},{\"x\":1685421821663,\"y\":17.7},{\"x\":1685422121736,\"y\":17.6},{\"x\":1685422421818,\"y\":17.7},{\"x\":1685422721865,\"y\":17.7},{\"x\":1685423021869,\"y\":17.6},{\"x\":1685423321956,\"y\":17.4},{\"x\":1685423622015,\"y\":17.5},{\"x\":1685423922080,\"y\":17.4},{\"x\":1685424222162,\"y\":17.5},{\"x\":1685424522203,\"y\":17.5},{\"x\":1685424822288,\"y\":17.5},{\"x\":1685425122362,\"y\":17.4},{\"x\":1685425422438,\"y\":17.4},{\"x\":1685425722513,\"y\":17.4},{\"x\":1685426022547,\"y\":17.4},{\"x\":1685426322620,\"y\":17.5},{\"x\":1685426622723,\"y\":17.5},{\"x\":1685426922744,\"y\":17.4},{\"x\":1685427222854,\"y\":17.4},{\"x\":1685427522913,\"y\":17.4},{\"x\":1685427823002,\"y\":17.4},{\"x\":1685428123104,\"y\":17.5},{\"x\":1685428423173,\"y\":17.5},{\"x\":1685428723329,\"y\":17.4},{\"x\":1685429038338,\"y\":17.4},{\"x\":1685429338390,\"y\":17.4},{\"x\":1685429638534,\"y\":17.4},{\"x\":1685429938550,\"y\":17.4},{\"x\":1685430238655,\"y\":17.4},{\"x\":1685430538750,\"y\":17.4},{\"x\":1685430838822,\"y\":17.3},{\"x\":1685431139008,\"y\":17.5},{\"x\":1685431439050,\"y\":17.5},{\"x\":1685431739109,\"y\":17.4},{\"x\":1685432039156,\"y\":17.5},{\"x\":1685432339204,\"y\":17.7},{\"x\":1685432639299,\"y\":17.3},{\"x\":1685432939394,\"y\":17.2},{\"x\":1685433239499,\"y\":17.2},{\"x\":1685433539575,\"y\":17.2},{\"x\":1685433839700,\"y\":17.2},{\"x\":1685434139771,\"y\":16.9},{\"x\":1685434439873,\"y\":16.7},{\"x\":1685434739965,\"y\":16.5},{\"x\":1685435040038,\"y\":16.5},{\"x\":1685435340054,\"y\":16.5},{\"x\":1685435640133,\"y\":16.5},{\"x\":1685435940205,\"y\":16.5},{\"x\":1685436240311,\"y\":16.8},{\"x\":1685436540423,\"y\":16.9},{\"x\":1685436840548,\"y\":17.1},{\"x\":1685437140665,\"y\":17},{\"x\":1685437440701,\"y\":16.8},{\"x\":1685437740799,\"y\":16.5},{\"x\":1685438040887,\"y\":16.5},{\"x\":1685438340964,\"y\":16.5},{\"x\":1685438641030,\"y\":17.2},{\"x\":1685438941063,\"y\":17.2},{\"x\":1685439241150,\"y\":17.2},{\"x\":1685439541265,\"y\":17},{\"x\":1685439841366,\"y\":17.2},{\"x\":1685440141451,\"y\":16.5},{\"x\":1685440441506,\"y\":16.5},{\"x\":1685440741607,\"y\":16.5},{\"x\":1685441041684,\"y\":16.5},{\"x\":1685441341786,\"y\":16.5},{\"x\":1685441641830,\"y\":16.5},{\"x\":1685441941863,\"y\":16.5},{\"x\":1685442241939,\"y\":17},{\"x\":1685442542010,\"y\":17.3},{\"x\":1685442842108,\"y\":17.2},{\"x\":1685443142199,\"y\":16.6},{\"x\":1685443442319,\"y\":17.2},{\"x\":1685443742376,\"y\":16.5},{\"x\":1685444042480,\"y\":16.8},{\"x\":1685444342574,\"y\":16.8},{\"x\":1685444642649,\"y\":16.5},{\"x\":1685444942748,\"y\":17.2},{\"x\":1685445242792,\"y\":17.1},{\"x\":1685445542799,\"y\":17.2},{\"x\":1685445842907,\"y\":17.3},{\"x\":1685446143003,\"y\":17.4},{\"x\":1685446443122,\"y\":17.4},{\"x\":1685446743194,\"y\":17.3},{\"x\":1685447043298,\"y\":17.2},{\"x\":1685447343341,\"y\":17.4},{\"x\":1685447643442,\"y\":17.2},{\"x\":1685447943557,\"y\":17.2},{\"x\":1685448243632,\"y\":17.3},{\"x\":1685448543708,\"y\":17.2},{\"x\":1685448843747,\"y\":17.1},{\"x\":1685449143768,\"y\":16.6},{\"x\":1685449443878,\"y\":16.5},{\"x\":1685449743965,\"y\":16.5},{\"x\":1685450044053,\"y\":16.5},{\"x\":1685450344178,\"y\":17.2},{\"x\":1685450644263,\"y\":17.3},{\"x\":1685450944279,\"y\":17.4},{\"x\":1685451244382,\"y\":17.2},{\"x\":1685451544448,\"y\":17.3},{\"x\":1685451844600,\"y\":17.3},{\"x\":1685452144626,\"y\":17.4},{\"x\":1685452444701,\"y\":17.4},{\"x\":1685452744749,\"y\":17.2},{\"x\":1685453044818,\"y\":17.4},{\"x\":1685453344909,\"y\":17.7},{\"x\":1685453645071,\"y\":17.6},{\"x\":1685453945113,\"y\":17.5},{\"x\":1685454245205,\"y\":17.6},{\"x\":1685454545276,\"y\":17.6},{\"x\":1685454845390,\"y\":17.7},{\"x\":1685455145455,\"y\":17.8},{\"x\":1685455445552,\"y\":17.7},{\"x\":1685455745631,\"y\":17.7},{\"x\":1685456045706,\"y\":17.7},{\"x\":1685456345813,\"y\":17.6},{\"x\":1685456645826,\"y\":17.7},{\"x\":1685456945928,\"y\":17.7},{\"x\":1685457246034,\"y\":17.4},{\"x\":1685457546104,\"y\":17.4},{\"x\":1685457846188,\"y\":17.6},{\"x\":1685458146263,\"y\":17.9},{\"x\":1685458446341,\"y\":17.9},{\"x\":1685458746470,\"y\":17.9},{\"x\":1685459046554,\"y\":17.8},{\"x\":1685459346643,\"y\":17.7},{\"x\":1685459646708,\"y\":17.9},{\"x\":1685459946734,\"y\":18},{\"x\":1685460246833,\"y\":17.9},{\"x\":1685460546913,\"y\":17.9},{\"x\":1685460847002,\"y\":17.9},{\"x\":1685461147079,\"y\":17.9},{\"x\":1685461447115,\"y\":17.9},{\"x\":1685461747181,\"y\":17.9},{\"x\":1685462047303,\"y\":17.9},{\"x\":1685462347360,\"y\":17.9},{\"x\":1685462647457,\"y\":17.9},{\"x\":1685462947533,\"y\":17.9},{\"x\":1685463247622,\"y\":17.9},{\"x\":1685463437946,\"y\":17.9},{\"x\":1685463752894,\"y\":17.9},{\"x\":1685464067891,\"y\":17.9},{\"x\":1685464367932,\"y\":17.9},{\"x\":1685464668016,\"y\":18.2},{\"x\":1685464968034,\"y\":18.2},{\"x\":1685465268109,\"y\":17.9},{\"x\":1685465568127,\"y\":17.9},{\"x\":1685465868179,\"y\":18},{\"x\":1685466168200,\"y\":18},{\"x\":1685466468292,\"y\":17.9},{\"x\":1685466783247,\"y\":17.9},{\"x\":1685467083281,\"y\":18},{\"x\":1685467383324,\"y\":18},{\"x\":1685467683356,\"y\":17.9},{\"x\":1685467983393,\"y\":17.9},{\"x\":1685468283508,\"y\":18},{\"x\":1685468598507,\"y\":17.9},{\"x\":1685468898532,\"y\":17.9},{\"x\":1685469198568,\"y\":17.9},{\"x\":1685469498602,\"y\":17.9},{\"x\":1685469798635,\"y\":17.9},{\"x\":1685470098653,\"y\":17.8},{\"x\":1685470398701,\"y\":17.8},{\"x\":1685470698741,\"y\":17.8},{\"x\":1685470998770,\"y\":17.7},{\"x\":1685471298800,\"y\":17.8},{\"x\":1685471598821,\"y\":17.9},{\"x\":1685471898877,\"y\":17.7},{\"x\":1685472198886,\"y\":17.7},{\"x\":1685472498957,\"y\":17.7},{\"x\":1685472798992,\"y\":17.7},{\"x\":1685473099049,\"y\":17.9},{\"x\":1685473414059,\"y\":17.9},{\"x\":1685473714086,\"y\":17.7},{\"x\":1685474014164,\"y\":17.6},{\"x\":1685474314238,\"y\":17.4},{\"x\":1685474629191,\"y\":17.5},{\"x\":1685474929212,\"y\":17.6},{\"x\":1685475229261,\"y\":17.7},{\"x\":1685475529275,\"y\":17.4},{\"x\":1685475829340,\"y\":17.4},{\"x\":1685476129417,\"y\":17.4},{\"x\":1685476429433,\"y\":17.4},{\"x\":1685476729449,\"y\":17.3},{\"x\":1685477029470,\"y\":17.2},{\"x\":1685477329512,\"y\":17.2},{\"x\":1685477629544,\"y\":17.3},{\"x\":1685477929562,\"y\":17.4},{\"x\":1685478229615,\"y\":17.3},{\"x\":1685478529641,\"y\":17},{\"x\":1685478829667,\"y\":16.5},{\"x\":1685479129749,\"y\":16.5},{\"x\":1685479429791,\"y\":16.5},{\"x\":1685479729807,\"y\":16.5},{\"x\":1685480029843,\"y\":16.5},{\"x\":1685480329850,\"y\":16.5},{\"x\":1685480629899,\"y\":16.7},{\"x\":1685480929954,\"y\":16.5},{\"x\":1685481244998,\"y\":16.5},{\"x\":1685481545012,\"y\":16.5},{\"x\":1685481845043,\"y\":16.5},{\"x\":1685482145093,\"y\":16.5},{\"x\":1685482445120,\"y\":16.5},{\"x\":1685482745153,\"y\":16.5},{\"x\":1685483045222,\"y\":16.5},{\"x\":1685483345235,\"y\":16.5},{\"x\":1685483645258,\"y\":16.5},{\"x\":1685483945299,\"y\":16.5},{\"x\":1685484245340,\"y\":16.5},{\"x\":1685484545382,\"y\":16.5},{\"x\":1685484845385,\"y\":16.5},{\"x\":1685485145425,\"y\":16.5},{\"x\":1685485445459,\"y\":16.5},{\"x\":1685485745479,\"y\":16.5},{\"x\":1685486045546,\"y\":16.5},{\"x\":1685486345575,\"y\":16.5},{\"x\":1685486645633,\"y\":16.5},{\"x\":1685486945725,\"y\":16.5},{\"x\":1685487260693,\"y\":16.5},{\"x\":1685487560707,\"y\":16.5},{\"x\":1685487860814,\"y\":16.5},{\"x\":1685488175808,\"y\":16.5},{\"x\":1685488475825,\"y\":16.5},{\"x\":1685488775853,\"y\":16.5},{\"x\":1685489075914,\"y\":16.5},{\"x\":1685489375930,\"y\":16.5},{\"x\":1685489675982,\"y\":16.5},{\"x\":1685489976039,\"y\":16.5},{\"x\":1685490276048,\"y\":16.5},{\"x\":1685490576126,\"y\":16.5},{\"x\":1685490876150,\"y\":16.5},{\"x\":1685491176154,\"y\":16.5},{\"x\":1685491476262,\"y\":16.5},{\"x\":1685491791218,\"y\":16.5},{\"x\":1685492091237,\"y\":16.5},{\"x\":1685492391276,\"y\":16.5},{\"x\":1685492691341,\"y\":16.5},{\"x\":1685492991352,\"y\":16.5},{\"x\":1685493291416,\"y\":16.5},{\"x\":1685493591464,\"y\":16.5},{\"x\":1685493891476,\"y\":16.2},{\"x\":1685494191553,\"y\":16.5},{\"x\":1685494491555,\"y\":16.5},{\"x\":1685494806655,\"y\":16.5},{\"x\":1685495121636,\"y\":16.5},{\"x\":1685495436647,\"y\":16.5},{\"x\":1685495736703,\"y\":16.5},{\"x\":1685496036726,\"y\":16.4},{\"x\":1685496336779,\"y\":16.5},{\"x\":1685496636854,\"y\":16.4},{\"x\":1685496951873,\"y\":16.4},{\"x\":1685497251911,\"y\":16.5},{\"x\":1685497566911,\"y\":16.5},{\"x\":1685497866915,\"y\":16.5},{\"x\":1685498166972,\"y\":16.2},{\"x\":1685498466989,\"y\":16.2},{\"x\":1685498767043,\"y\":16.4},{\"x\":1685499067095,\"y\":16.2},{\"x\":1685499367129,\"y\":16.5},{\"x\":1685499667180,\"y\":16.4},{\"x\":1685499967184,\"y\":16.5},{\"x\":1685500282215,\"y\":16.5},{\"x\":1685500582236,\"y\":16.5},{\"x\":1685500882309,\"y\":16.5},{\"x\":1685501197336,\"y\":16.4},{\"x\":1685501497371,\"y\":16.2},{\"x\":1685501797405,\"y\":16.3},{\"x\":1685502097446,\"y\":16.1},{\"x\":1685502397471,\"y\":16},{\"x\":1685502697518,\"y\":15.7},{\"x\":1685502997526,\"y\":16},{\"x\":1685503297549,\"y\":16},{\"x\":1685503597589,\"y\":16.2},{\"x\":1685503897624,\"y\":16.2},{\"x\":1685504197687,\"y\":16},{\"x\":1685504497728,\"y\":15.9},{\"x\":1685504797753,\"y\":16},{\"x\":1685505097809,\"y\":16},{\"x\":1685505397837,\"y\":16.2},{\"x\":1685505697885,\"y\":16.4},{\"x\":1685505997899,\"y\":16.3},{\"x\":1685506297916,\"y\":16.1},{\"x\":1685506597958,\"y\":16},{\"x\":1685506897987,\"y\":16},{\"x\":1685507198004,\"y\":15.8},{\"x\":1685507498078,\"y\":15.9},{\"x\":1685507798110,\"y\":16},{\"x\":1685508098131,\"y\":15.8},{\"x\":1685508398152,\"y\":16},{\"x\":1685508698194,\"y\":15.9},{\"x\":1685508998213,\"y\":15.8},{\"x\":1685509298220,\"y\":15.8},{\"x\":1685509598290,\"y\":15.9},{\"x\":1685509898323,\"y\":15.7},{\"x\":1685510198331,\"y\":15.7},{\"x\":1685510498370,\"y\":15.7},{\"x\":1685510798393,\"y\":15.5},{\"x\":1685511098454,\"y\":15.8},{\"x\":1685511398491,\"y\":15.7},{\"x\":1685511698520,\"y\":15.9},{\"x\":1685512013528,\"y\":15.9},{\"x\":1685512313605,\"y\":15.8},{\"x\":1685512628564,\"y\":15.7},{\"x\":1685512928630,\"y\":15.5},{\"x\":1685513228650,\"y\":15.5},{\"x\":1685513528663,\"y\":15.5},{\"x\":1685513828703,\"y\":15.6},{\"x\":1685514143711,\"y\":15.7},{\"x\":1685514443729,\"y\":15.7},{\"x\":1685514743749,\"y\":15.8},{\"x\":1685515043937,\"y\":15.7},{\"x\":1685515358896,\"y\":15.5},{\"x\":1685515658956,\"y\":15.6},{\"x\":1685515959007,\"y\":15.7},{\"x\":1685516259087,\"y\":15.6},{\"x\":1685516559112,\"y\":15.9},{\"x\":1685516859194,\"y\":16},{\"x\":1685517174175,\"y\":15.9},{\"x\":1685517474229,\"y\":15.8},{\"x\":1685517774323,\"y\":15.7},{\"x\":1685518074366,\"y\":15.7},{\"x\":1685518374438,\"y\":15.7},{\"x\":1685518674561,\"y\":15.9},{\"x\":1685518974586,\"y\":15.7},{\"x\":1685519274602,\"y\":16},{\"x\":1685519574700,\"y\":15.7},{\"x\":1685519889719,\"y\":15.8},{\"x\":1685520189753,\"y\":15.9},{\"x\":1685520489831,\"y\":15.8},{\"x\":1685520789861,\"y\":15.9},{\"x\":1685521089913,\"y\":16},{\"x\":1685521390016,\"y\":15.7},{\"x\":1685521690069,\"y\":15.5},{\"x\":1685521990135,\"y\":15.5},{\"x\":1685522290216,\"y\":15.5},{\"x\":1685522590301,\"y\":15.5},{\"x\":1685522905260,\"y\":15.7},{\"x\":1685523205311,\"y\":15.7},{\"x\":1685523505402,\"y\":15.7},{\"x\":1685523805456,\"y\":15.6},{\"x\":1685524105543,\"y\":15.9},{\"x\":1685524405592,\"y\":15.7},{\"x\":1685524705754,\"y\":15.5},{\"x\":1685525005757,\"y\":15.5},{\"x\":1685525305830,\"y\":15.7},{\"x\":1685525605894,\"y\":15.5},{\"x\":1685525905934,\"y\":15.7},{\"x\":1685526205944,\"y\":15.7},{\"x\":1685526505986,\"y\":15.5},{\"x\":1685526806078,\"y\":15.6},{\"x\":1685527106180,\"y\":15.8},{\"x\":1685527406287,\"y\":15.5},{\"x\":1685527706314,\"y\":15.6},{\"x\":1685528006417,\"y\":15.7},{\"x\":1685528306469,\"y\":15.8},{\"x\":1685528606541,\"y\":15.9},{\"x\":1685528906576,\"y\":15.7},{\"x\":1685529206598,\"y\":15.7},{\"x\":1685529506705,\"y\":15.7},{\"x\":1685529806799,\"y\":15.5},{\"x\":1685530106900,\"y\":15.7},{\"x\":1685530406997,\"y\":15.9},{\"x\":1685530707031,\"y\":15.9},{\"x\":1685531007135,\"y\":15.7},{\"x\":1685531307217,\"y\":15.9},{\"x\":1685531607277,\"y\":15.7},{\"x\":1685531907312,\"y\":15.9},{\"x\":1685532207333,\"y\":16},{\"x\":1685532507449,\"y\":15.9},{\"x\":1685532807538,\"y\":16},{\"x\":1685533107607,\"y\":15.9},{\"x\":1685533407716,\"y\":16.1},{\"x\":1685533707801,\"y\":16.4},{\"x\":1685534007891,\"y\":16.5},{\"x\":1685534307936,\"y\":17.9},{\"x\":1685534607976,\"y\":18.5},{\"x\":1685534908043,\"y\":19},{\"x\":1685535208090,\"y\":19.4},{\"x\":1685535508186,\"y\":19.5},{\"x\":1685535808331,\"y\":19.5},{\"x\":1685536108352,\"y\":19.5},{\"x\":1685536408438,\"y\":19.9},{\"x\":1685536708630,\"y\":20.2},{\"x\":1685537023666,\"y\":20.3},{\"x\":1685537323705,\"y\":20.5},{\"x\":1685537623753,\"y\":20.4},{\"x\":1685537923799,\"y\":20.4},{\"x\":1685538223965,\"y\":20.5},{\"x\":1685538538965,\"y\":20.4},{\"x\":1685538839060,\"y\":20.5},{\"x\":1685539139180,\"y\":20.5},{\"x\":1685539439233,\"y\":20.5},{\"x\":1685539739375,\"y\":20.4},{\"x\":1685540039424,\"y\":20.6},{\"x\":1685540339520,\"y\":20.4},{\"x\":1685540639601,\"y\":20.6},{\"x\":1685540939668,\"y\":20.5},{\"x\":1685541239749,\"y\":20.5},{\"x\":1685541539840,\"y\":20.8},{\"x\":1685541839913,\"y\":20.5},{\"x\":1685542139995,\"y\":20.4},{\"x\":1685542440177,\"y\":20.8},{\"x\":1685542740192,\"y\":20.4},{\"x\":1685543040283,\"y\":20.8},{\"x\":1685543340389,\"y\":20.5},{\"x\":1685543640471,\"y\":20.8},{\"x\":1685543940473,\"y\":20.5},{\"x\":1685544240621,\"y\":20.8},{\"x\":1685544540685,\"y\":20.5},{\"x\":1685544840799,\"y\":20.7},{\"x\":1685545140901,\"y\":20.5},{\"x\":1685545441001,\"y\":20.8},{\"x\":1685545741065,\"y\":20.6},{\"x\":1685546041186,\"y\":20.5},{\"x\":1685546341243,\"y\":20.7},{\"x\":1685546641292,\"y\":20.4},{\"x\":1685546941376,\"y\":20.8},{\"x\":1685547241438,\"y\":20.5},{\"x\":1685547541565,\"y\":20.8},{\"x\":1685547841634,\"y\":20.5},{\"x\":1685548141711,\"y\":20.8},{\"x\":1685548441854,\"y\":20.6},{\"x\":1685548741902,\"y\":20.5},{\"x\":1685549042002,\"y\":20.8},{\"x\":1685549342099,\"y\":20.5},{\"x\":1685549642156,\"y\":20.8},{\"x\":1685549942238,\"y\":20.7},{\"x\":1685550242287,\"y\":20.5},{\"x\":1685550542321,\"y\":20.8},{\"x\":1685550842435,\"y\":20.7},{\"x\":1685551142532,\"y\":20.5},{\"x\":1685551442679,\"y\":20.8},{\"x\":1685551742742,\"y\":20.8},{\"x\":1685552042824,\"y\":20.7},{\"x\":1685552342904,\"y\":20.6},{\"x\":1685552643024,\"y\":20.7},{\"x\":1685552943102,\"y\":20.7},{\"x\":1685553243197,\"y\":20.7},{\"x\":1685553543262,\"y\":20.7},{\"x\":1685553843353,\"y\":20.8},{\"x\":1685554143366,\"y\":20.8},{\"x\":1685554443531,\"y\":20.8},{\"x\":1685554743589,\"y\":20.8},{\"x\":1685555043690,\"y\":20.8},{\"x\":1685555343799,\"y\":20.8},{\"x\":1685555643872,\"y\":20.8},{\"x\":1685555943930,\"y\":20.8},{\"x\":1685556244056,\"y\":20.8},{\"x\":1685556544148,\"y\":20.8},{\"x\":1685556844209,\"y\":20.8},{\"x\":1685557144344,\"y\":20.7},{\"x\":1685557444363,\"y\":20.8},{\"x\":1685557744405,\"y\":20.7},{\"x\":1685558044539,\"y\":20.7},{\"x\":1685558344605,\"y\":20.7},{\"x\":1685558644713,\"y\":20.7},{\"x\":1685558944785,\"y\":20.7},{\"x\":1685559244901,\"y\":20.6},{\"x\":1685559544945,\"y\":20.6},{\"x\":1685559845042,\"y\":20.5},{\"x\":1685560145143,\"y\":20.5},{\"x\":1685560445241,\"y\":20.6},{\"x\":1685560745257,\"y\":20.5},{\"x\":1685561045328,\"y\":20.5},{\"x\":1685561345445,\"y\":20.5},{\"x\":1685561645525,\"y\":20.6},{\"x\":1685561945646,\"y\":20.8},{\"x\":1685562245696,\"y\":20.7},{\"x\":1685562545795,\"y\":20.6},{\"x\":1685562845871,\"y\":20.7},{\"x\":1685563145971,\"y\":20.8},{\"x\":1685563446094,\"y\":20.7},{\"x\":1685563746173,\"y\":20.5},{\"x\":1685564046283,\"y\":20.8},{\"x\":1685564346314,\"y\":20.8},{\"x\":1685564646386,\"y\":20.7},{\"x\":1685564946460,\"y\":20.6},{\"x\":1685565246561,\"y\":20.5},{\"x\":1685565546656,\"y\":20.7},{\"x\":1685565846745,\"y\":20.8},{\"x\":1685566146812,\"y\":20.6},{\"x\":1685566446933,\"y\":20.7},{\"x\":1685566746982,\"y\":20.7},{\"x\":1685567047084,\"y\":20.5},{\"x\":1685567347176,\"y\":20.8},{\"x\":1685567647243,\"y\":20.5},{\"x\":1685567947275,\"y\":20.8},{\"x\":1685568247414,\"y\":20.8},{\"x\":1685568547487,\"y\":20.8},{\"x\":1685568847582,\"y\":20.6},{\"x\":1685569147698,\"y\":20.6},{\"x\":1685569447750,\"y\":20.5},{\"x\":1685569747835,\"y\":20.8},{\"x\":1685570047948,\"y\":20.7},{\"x\":1685570348041,\"y\":20.5},{\"x\":1685570648135,\"y\":20.7},{\"x\":1685570948186,\"y\":20.7},{\"x\":1685571248285,\"y\":20.5},{\"x\":1685571548397,\"y\":20.7},{\"x\":1685571848461,\"y\":20.6},{\"x\":1685572148552,\"y\":20.7},{\"x\":1685572448646,\"y\":20.8},{\"x\":1685572748724,\"y\":20.7},{\"x\":1685573048812,\"y\":20.4},{\"x\":1685573348924,\"y\":20.8},{\"x\":1685573648998,\"y\":20.7},{\"x\":1685573949035,\"y\":20.5},{\"x\":1685574249162,\"y\":20.7},{\"x\":1685574564170,\"y\":20.5},{\"x\":1685574864211,\"y\":20.3},{\"x\":1685575164295,\"y\":20.3},{\"x\":1685575464392,\"y\":20.3},{\"x\":1685575764473,\"y\":20.2},{\"x\":1685576064520,\"y\":20},{\"x\":1685576364561,\"y\":19.9},{\"x\":1685576664649,\"y\":19.6},{\"x\":1685576964764,\"y\":19.5},{\"x\":1685577264783,\"y\":19.5},{\"x\":1685577564825,\"y\":19.5},{\"x\":1685577864898,\"y\":19.5},{\"x\":1685578165022,\"y\":19.5},{\"x\":1685578465041,\"y\":19.5},{\"x\":1685578765123,\"y\":19.5},{\"x\":1685579065190,\"y\":19.5},{\"x\":1685579365242,\"y\":19.5},{\"x\":1685579665270,\"y\":19.5},{\"x\":1685579965342,\"y\":19.5},{\"x\":1685580265419,\"y\":19.5},{\"x\":1685580565474,\"y\":19.5},{\"x\":1685580865528,\"y\":19.5},{\"x\":1685581165617,\"y\":19.5},{\"x\":1685581465707,\"y\":19.5},{\"x\":1685581765780,\"y\":19.5},{\"x\":1685582065871,\"y\":19.5},{\"x\":1685582365914,\"y\":19.5},{\"x\":1685582665994,\"y\":19.5},{\"x\":1685582966029,\"y\":19.5},{\"x\":1685583266054,\"y\":19.5},{\"x\":1685583566137,\"y\":19.5},{\"x\":1685583866167,\"y\":19.5},{\"x\":1685584166249,\"y\":19.5},{\"x\":1685584466332,\"y\":19.5},{\"x\":1685584766387,\"y\":19.5},{\"x\":1685585066444,\"y\":19.5},{\"x\":1685585366558,\"y\":19.5},{\"x\":1685585666591,\"y\":19.5},{\"x\":1685585966705,\"y\":19.5},{\"x\":1685586281700,\"y\":19.5},{\"x\":1685586581763,\"y\":19.5},{\"x\":1685586881831,\"y\":19.5},{\"x\":1685587181935,\"y\":19.5},{\"x\":1685587481999,\"y\":19.4},{\"x\":1685587782043,\"y\":19.4},{\"x\":1685588082116,\"y\":19.4},{\"x\":1685588382188,\"y\":19.4},{\"x\":1685588682255,\"y\":19.4},{\"x\":1685588982296,\"y\":19.4},{\"x\":1685589282341,\"y\":19.3},{\"x\":1685589582405,\"y\":19.4},{\"x\":1685589882472,\"y\":19.4},{\"x\":1685590182540,\"y\":19.3},{\"x\":1685590482618,\"y\":19.3},{\"x\":1685590782722,\"y\":19.3},{\"x\":1685591082771,\"y\":19.3},{\"x\":1685591382819,\"y\":19.2},{\"x\":1685591682931,\"y\":19.2},{\"x\":1685591982967,\"y\":19.2},{\"x\":1685592282986,\"y\":19.2},{\"x\":1685592583082,\"y\":19.2},{\"x\":1685592883165,\"y\":19.2},{\"x\":1685593183195,\"y\":19.2},{\"x\":1685593483277,\"y\":19.1},{\"x\":1685593783380,\"y\":19.2},{\"x\":1685594083450,\"y\":19.2},{\"x\":1685594383491,\"y\":19.2},{\"x\":1685594683592,\"y\":19.1},{\"x\":1685594983618,\"y\":19.1},{\"x\":1685595283711,\"y\":19.1},{\"x\":1685595583730,\"y\":19.1},{\"x\":1685595883817,\"y\":19},{\"x\":1685596183896,\"y\":19},{\"x\":1685596483953,\"y\":19},{\"x\":1685596784061,\"y\":19},{\"x\":1685597084124,\"y\":19},{\"x\":1685597384139,\"y\":19.1},{\"x\":1685597684213,\"y\":19},{\"x\":1685597984240,\"y\":19},{\"x\":1685598284312,\"y\":19},{\"x\":1685598584338,\"y\":19},{\"x\":1685598884381,\"y\":19},{\"x\":1685599184436,\"y\":19},{\"x\":1685599499407,\"y\":19},{\"x\":1685599799490,\"y\":18.9},{\"x\":1685600099528,\"y\":19},{\"x\":1685600399539,\"y\":19},{\"x\":1685600699637,\"y\":19},{\"x\":1685600999644,\"y\":19},{\"x\":1685601299689,\"y\":19},{\"x\":1685601599758,\"y\":19},{\"x\":1685601899826,\"y\":19},{\"x\":1685602199848,\"y\":18.9},{\"x\":1685602499869,\"y\":18.9},{\"x\":1685602799939,\"y\":19},{\"x\":1685603099979,\"y\":18.9},{\"x\":1685603414978,\"y\":18.9},{\"x\":1685603715053,\"y\":19},{\"x\":1685604030039,\"y\":19},{\"x\":1685604330117,\"y\":19},{\"x\":1685604630167,\"y\":19},{\"x\":1685604930199,\"y\":19},{\"x\":1685605230238,\"y\":18.9},{\"x\":1685605530260,\"y\":19},{\"x\":1685605830318,\"y\":19},{\"x\":1685606130319,\"y\":19},{\"x\":1685606430335,\"y\":19},{\"x\":1685606730393,\"y\":19},{\"x\":1685607030458,\"y\":19},{\"x\":1685607330485,\"y\":19.5},{\"x\":1685607630516,\"y\":20.3},{\"x\":1685607930568,\"y\":20.6},{\"x\":1685608230576,\"y\":20.4},{\"x\":1685608530643,\"y\":20.5},{\"x\":1685608830684,\"y\":20.5},{\"x\":1685609130723,\"y\":20.5},{\"x\":1685609430778,\"y\":20.5},{\"x\":1685609745753,\"y\":20.7},{\"x\":1685610045798,\"y\":20.4},{\"x\":1685610345834,\"y\":20.7},{\"x\":1685610645876,\"y\":20.4},{\"x\":1685610945921,\"y\":20.8},{\"x\":1685611245935,\"y\":20.5},{\"x\":1685611545963,\"y\":20.8},{\"x\":1685611845989,\"y\":20.6},{\"x\":1685612146117,\"y\":20.5},{\"x\":1685612446189,\"y\":20.8},{\"x\":1685612746312,\"y\":20.6},{\"x\":1685613046346,\"y\":20.5},{\"x\":1685613346425,\"y\":20.8},{\"x\":1685613646580,\"y\":20.6},{\"x\":1685613961556,\"y\":20.6},{\"x\":1685614261648,\"y\":20.7},{\"x\":1685614576669,\"y\":20.7},{\"x\":1685614876695,\"y\":20.5},{\"x\":1685615176733,\"y\":20.4},{\"x\":1685615476808,\"y\":20.7},{\"x\":1685615776863,\"y\":20.7},{\"x\":1685616076917,\"y\":20.5},{\"x\":1685616376987,\"y\":20.4},{\"x\":1685616677033,\"y\":20.3},{\"x\":1685616977113,\"y\":20.3},{\"x\":1685617277203,\"y\":20.3},{\"x\":1685617577236,\"y\":20.3},{\"x\":1685617877275,\"y\":20.2},{\"x\":1685618177324,\"y\":20.3},{\"x\":1685618477408,\"y\":20.2},{\"x\":1685618777447,\"y\":20.3},{\"x\":1685619077601,\"y\":20.2},{\"x\":1685619377646,\"y\":20.2},{\"x\":1685619677730,\"y\":20.1},{\"x\":1685619977761,\"y\":20.2},{\"x\":1685620017473,\"y\":20.1},{\"x\":1685620080667,\"y\":20.1}],",[146,25087,25088],{},"{\"x\":1685015251039,\"y\":19.18},{\"x\":1685015551015,\"y\":19.18},{\"x\":1685015851010,\"y\":19.18},{\"x\":1685016151028,\"y\":19.09},{\"x\":1685016451042,\"y\":18.75},{\"x\":1685016751019,\"y\":18.66},{\"x\":1685017051028,\"y\":18.75},{\"x\":1685017351047,\"y\":18.89},{\"x\":1685017651031,\"y\":18.8},{\"x\":1685017951098,\"y\":18.83},{\"x\":1685018251018,\"y\":18.3},{\"x\":1685018551020,\"y\":18.3},{\"x\":1685018851032,\"y\":18.36},{\"x\":1685019151038,\"y\":18.36},{\"x\":1685019451035,\"y\":19.14},{\"x\":1685019751031,\"y\":19.04},{\"x\":1685020051038,\"y\":19.64},{\"x\":1685020351040,\"y\":19.42},{\"x\":1685020651063,\"y\":19.88},{\"x\":1685020951079,\"y\":19.73},{\"x\":1685021251036,\"y\":19.73},{\"x\":1685021551137,\"y\":19.54},{\"x\":1685021851047,\"y\":19.21},{\"x\":1685022151057,\"y\":19.13},{\"x\":1685022451042,\"y\":19.21},{\"x\":1685022751080,\"y\":19.1},{\"x\":1685023051043,\"y\":18.46},{\"x\":1685023351058,\"y\":18.46},{\"x\":1685023651118,\"y\":18.47},{\"x\":1685023951188,\"y\":18.48},{\"x\":1685024251055,\"y\":18.48},{\"x\":1685024551051,\"y\":18.44},{\"x\":1685024851052,\"y\":18.44},{\"x\":1685025151135,\"y\":18.73},{\"x\":1685025451076,\"y\":18.73},{\"x\":1685025751061,\"y\":18.73},{\"x\":1685026051071,\"y\":19.02},{\"x\":1685026351158,\"y\":19.05},{\"x\":1685026651115,\"y\":19.03},{\"x\":1685026951071,\"y\":19.03},{\"x\":1685027251066,\"y\":19.03},{\"x\":1685027551148,\"y\":18.62},{\"x\":1685027851058,\"y\":18.62},{\"x\":1685028151059,\"y\":18.62},{\"x\":1685028451112,\"y\":19},{\"x\":1685028751146,\"y\":18.91},{\"x\":1685029051091,\"y\":18.1},{\"x\":1685029351104,\"y\":18.12},{\"x\":1685029651134,\"y\":18.51},{\"x\":1685029951350,\"y\":18.12},{\"x\":1685030251085,\"y\":18.51},{\"x\":1685030551087,\"y\":18.42},{\"x\":1685030851113,\"y\":18.46},{\"x\":1685031151160,\"y\":18.37},{\"x\":1685031451109,\"y\":18.33},{\"x\":1685031751080,\"y\":18.21},{\"x\":1685032051082,\"y\":18.3},{\"x\":1685032351518,\"y\":18.3},{\"x\":1685032651206,\"y\":17.89},{\"x\":1685032951081,\"y\":17.8},{\"x\":1685033251086,\"y\":17.8},{\"x\":1685033551096,\"y\":17.8},{\"x\":1685033851075,\"y\":17.8},{\"x\":1685034151086,\"y\":17.78},{\"x\":1685034451139,\"y\":17.63},{\"x\":1685034751066,\"y\":17.63},{\"x\":1685035051075,\"y\":17.63},{\"x\":1685035351084,\"y\":17.54},{\"x\":1685035651091,\"y\":17.54},{\"x\":1685035951102,\"y\":17.55},{\"x\":1685036251109,\"y\":17.28},{\"x\":1685036551080,\"y\":17.54},{\"x\":1685036851085,\"y\":17.25},{\"x\":1685037151113,\"y\":17.2},{\"x\":1685037451085,\"y\":16.99},{\"x\":1685037751124,\"y\":16.99},{\"x\":1685038051100,\"y\":16.99},{\"x\":1685038351547,\"y\":17.1},{\"x\":1685038651116,\"y\":17.02},{\"x\":1685038951187,\"y\":16.94},{\"x\":1685039251190,\"y\":16.88},{\"x\":1685039551190,\"y\":16.85},{\"x\":1685039851181,\"y\":16.85},{\"x\":1685040151138,\"y\":16.82},{\"x\":1685040451132,\"y\":16.79},{\"x\":1685040751183,\"y\":16.79},{\"x\":1685041051160,\"y\":16.79},{\"x\":1685041351171,\"y\":16.7},{\"x\":1685041651173,\"y\":16.44},{\"x\":1685041951215,\"y\":16.44},{\"x\":1685042251220,\"y\":16.48},{\"x\":1685042551262,\"y\":16.52},{\"x\":1685042851204,\"y\":16.48},{\"x\":1685043151252,\"y\":16.52},{\"x\":1685043451209,\"y\":16.4},{\"x\":1685043751246,\"y\":16.26},{\"x\":1685044051210,\"y\":16.26},{\"x\":1685044351522,\"y\":16.26},{\"x\":1685044651215,\"y\":16.17},{\"x\":1685044951218,\"y\":16.17},{\"x\":1685045251281,\"y\":15.98},{\"x\":1685045551345,\"y\":15.98},{\"x\":1685045851232,\"y\":16.01},{\"x\":1685046151240,\"y\":15.98},{\"x\":1685046451276,\"y\":15.75},{\"x\":1685046751262,\"y\":15.75},{\"x\":1685047051266,\"y\":15.75},{\"x\":1685047351285,\"y\":15.68},{\"x\":1685047651267,\"y\":15.68},{\"x\":1685047951728,\"y\":15.49},{\"x\":1685048251321,\"y\":15.5},{\"x\":1685048551265,\"y\":15.49},{\"x\":1685048851352,\"y\":15.35},{\"x\":1685049151302,\"y\":15.35},{\"x\":1685049451264,\"y\":14.98},{\"x\":1685049751268,\"y\":14.98},{\"x\":1685050051322,\"y\":14.97},{\"x\":1685050351309,\"y\":14.97},{\"x\":1685050651266,\"y\":14.75},{\"x\":1685050951264,\"y\":14.75},{\"x\":1685051251276,\"y\":14.82},{\"x\":1685051551291,\"y\":14.73},{\"x\":1685051851384,\"y\":14.57},{\"x\":1685052151274,\"y\":14.41},{\"x\":1685052451281,\"y\":14.41},{\"x\":1685052751280,\"y\":14.35},{\"x\":1685053051279,\"y\":14.49},{\"x\":1685053351326,\"y\":14.29},{\"x\":1685053651310,\"y\":14.3},{\"x\":1685053951323,\"y\":14.29},{\"x\":1685054251377,\"y\":13.95},{\"x\":1685054551303,\"y\":13.95},{\"x\":1685054851307,\"y\":13.95},{\"x\":1685055151655,\"y\":13.71},{\"x\":1685055451302,\"y\":13.71},{\"x\":1685055751335,\"y\":13.58},{\"x\":1685056051330,\"y\":13.55},{\"x\":1685056351435,\"y\":13.58},{\"x\":1685056651370,\"y\":13.33},{\"x\":1685056951348,\"y\":13.58},{\"x\":1685057251335,\"y\":13.23},{\"x\":1685057551642,\"y\":13.23},{\"x\":1685057851342,\"y\":13.23},{\"x\":1685058151360,\"y\":12.98},{\"x\":1685058451395,\"y\":13},{\"x\":1685058751511,\"y\":13.01},{\"x\":1685059051391,\"y\":13.01},{\"x\":1685059351339,\"y\":12.9},{\"x\":1685059651359,\"y\":12.9},{\"x\":1685059951438,\"y\":12.48},{\"x\":1685060251356,\"y\":12.48},{\"x\":1685060551372,\"y\":12.48},{\"x\":1685060851371,\"y\":12.32},{\"x\":1685061151374,\"y\":12.32},{\"x\":1685061451412,\"y\":11.72},{\"x\":1685061751394,\"y\":11.63},{\"x\":1685062051381,\"y\":11.72},{\"x\":1685062352047,\"y\":11.55},{\"x\":1685062651444,\"y\":11.36},{\"x\":1685062951373,\"y\":11.46},{\"x\":1685063251410,\"y\":11.36},{\"x\":1685063551479,\"y\":11.37},{\"x\":1685063851380,\"y\":11.36},{\"x\":1685064151391,\"y\":11.44},{\"x\":1685064451471,\"y\":11.34},{\"x\":1685064751589,\"y\":11.32},{\"x\":1685065051396,\"y\":11.26},{\"x\":1685065351399,\"y\":11.22},{\"x\":1685065651409,\"y\":11.39},{\"x\":1685065952272,\"y\":11.33},{\"x\":1685066251396,\"y\":11.21},{\"x\":1685066551403,\"y\":11.21},{\"x\":1685066851417,\"y\":11.34},{\"x\":1685067151442,\"y\":11.28},{\"x\":1685067451399,\"y\":11.27},{\"x\":1685067751460,\"y\":11.06},{\"x\":1685068051420,\"y\":11.27},{\"x\":1685068351485,\"y\":11.02},{\"x\":1685068651412,\"y\":11.02},{\"x\":1685068951402,\"y\":10.91},{\"x\":1685069251450,\"y\":11},{\"x\":1685069551482,\"y\":11},{\"x\":1685069851414,\"y\":10.88},{\"x\":1685070151414,\"y\":10.66},{\"x\":1685070451408,\"y\":10.66},{\"x\":1685070752331,\"y\":10.76},{\"x\":1685071051424,\"y\":10.76},{\"x\":1685071351428,\"y\":10.76},{\"x\":1685071651498,\"y\":10.55},{\"x\":1685071951451,\"y\":10.55},{\"x\":1685072251483,\"y\":10.55},{\"x\":1685072551428,\"y\":10.55},{\"x\":1685072851477,\"y\":10.34},{\"x\":1685073151490,\"y\":10.34},{\"x\":1685073451401,\"y\":10.34},{\"x\":1685073751433,\"y\":10.08},{\"x\":1685074051433,\"y\":10.08},{\"x\":1685074351659,\"y\":9.98},{\"x\":1685074651481,\"y\":9.98},{\"x\":1685074951442,\"y\":9.98},{\"x\":1685075251486,\"y\":9.76},{\"x\":1685075553145,\"y\":9.76},{\"x\":1685075851454,\"y\":9.76},{\"x\":1685076151559,\"y\":9.76},{\"x\":1685076451455,\"y\":9.76},{\"x\":1685076751637,\"y\":9.76},{\"x\":1685077051562,\"y\":10.23},{\"x\":1685077351466,\"y\":10.23},{\"x\":1685077651495,\"y\":10.23},{\"x\":1685077951596,\"y\":10.15},{\"x\":1685078251465,\"y\":10.36},{\"x\":1685078551490,\"y\":10.36},{\"x\":1685078851486,\"y\":10.82},{\"x\":1685079152413,\"y\":10.82},{\"x\":1685079451481,\"y\":11.06},{\"x\":1685079751484,\"y\":11.06},{\"x\":1685080051515,\"y\":11.26},{\"x\":1685080353153,\"y\":11.08},{\"x\":1685080651466,\"y\":11.37},{\"x\":1685080951559,\"y\":11.85},{\"x\":1685081251496,\"y\":12.07},{\"x\":1685081552662,\"y\":11.54},{\"x\":1685081851505,\"y\":11.54},{\"x\":1685082151498,\"y\":12.9},{\"x\":1685082451574,\"y\":11.98},{\"x\":1685082752768,\"y\":12.05},{\"x\":1685083051516,\"y\":12.05},{\"x\":1685083351518,\"y\":12.54},{\"x\":1685083651522,\"y\":12.54},{\"x\":1685083951825,\"y\":13.01},{\"x\":1685084251526,\"y\":13.01},{\"x\":1685084551531,\"y\":13.84},{\"x\":1685084851537,\"y\":14.15},{\"x\":1685085154315,\"y\":14.2},{\"x\":1685085451540,\"y\":14.2},{\"x\":1685085751599,\"y\":14.74},{\"x\":1685086051532,\"y\":14.65},{\"x\":1685086352440,\"y\":14.65},{\"x\":1685086651538,\"y\":15.31},{\"x\":1685086951544,\"y\":15.31},{\"x\":1685087251583,\"y\":15.43},{\"x\":1685087552469,\"y\":15.61},{\"x\":1685087851550,\"y\":15.43},{\"x\":1685088151549,\"y\":15.75},{\"x\":1685088451547,\"y\":15.75},{\"x\":1685088752994,\"y\":16.44},{\"x\":1685089051545,\"y\":16.44},{\"x\":1685089351589,\"y\":16.84},{\"x\":1685089651598,\"y\":16.44},{\"x\":1685089952825,\"y\":16.84},{\"x\":1685090251561,\"y\":17.35},{\"x\":1685090551636,\"y\":17.66},{\"x\":1685090851564,\"y\":17.56},{\"x\":1685091152016,\"y\":17.66},{\"x\":1685091451594,\"y\":17.93},{\"x\":1685091751567,\"y\":18.04},{\"x\":1685092051581,\"y\":17.93},{\"x\":1685092352469,\"y\":18.08},{\"x\":1685092651613,\"y\":18.17},{\"x\":1685092951598,\"y\":18.25},{\"x\":1685093251572,\"y\":18.23},{\"x\":1685093551684,\"y\":18.43},{\"x\":1685093851596,\"y\":18.5},{\"x\":1685094151578,\"y\":18.5},{\"x\":1685094451581,\"y\":18.85},{\"x\":1685094753849,\"y\":18.85},{\"x\":1685095051634,\"y\":19.17},{\"x\":1685095351592,\"y\":19.09},{\"x\":1685095651582,\"y\":19.37},{\"x\":1685095952262,\"y\":19.37},{\"x\":1685096251665,\"y\":19.46},{\"x\":1685096551653,\"y\":19.46},{\"x\":1685096851597,\"y\":19.46},{\"x\":1685097151894,\"y\":19.32},{\"x\":1685097451605,\"y\":19.59},{\"x\":1685097751603,\"y\":19.85},{\"x\":1685098051603,\"y\":19.75},{\"x\":1685098351825,\"y\":19.65},{\"x\":1685098651611,\"y\":19.82},{\"x\":1685098951612,\"y\":19.65},{\"x\":1685099251620,\"y\":19.57},{\"x\":1685099554557,\"y\":19.84},{\"x\":1685099851611,\"y\":19.98},{\"x\":1685100151610,\"y\":19.98},{\"x\":1685100451674,\"y\":19.9},{\"x\":1685100751631,\"y\":19.9},{\"x\":1685101051671,\"y\":20.45},{\"x\":1685101351650,\"y\":20.45},{\"x\":1685101651625,\"y\":20.45},{\"x\":1685101952592,\"y\":19.74},{\"x\":1685102251628,\"y\":19.74},{\"x\":1685102551620,\"y\":19.74},{\"x\":1685102851630,\"y\":19.84},{\"x\":1685103152663,\"y\":20.53},{\"x\":1685103451625,\"y\":20.53},{\"x\":1685103751638,\"y\":20.37},{\"x\":1685104051653,\"y\":20.37},{\"x\":1685104351690,\"y\":20.42},{\"x\":1685104651625,\"y\":20.92},{\"x\":1685104951669,\"y\":21.05},{\"x\":1685105251635,\"y\":20.92},{\"x\":1685106270209,\"y\":21.51},{\"x\":1685106570210,\"y\":21.28},{\"x\":1685106870262,\"y\":21.31},{\"x\":1685107170259,\"y\":20.87},{\"x\":1685107470202,\"y\":20.87},{\"x\":1685107770205,\"y\":20.87},{\"x\":1685108070201,\"y\":20.24},{\"x\":1685108370268,\"y\":20.24},{\"x\":1685108670211,\"y\":20.81},{\"x\":1685108970215,\"y\":20.61},{\"x\":1685109270264,\"y\":21.02},{\"x\":1685109570225,\"y\":21.02},{\"x\":1685109870210,\"y\":21.35},{\"x\":1685110170276,\"y\":21.42},{\"x\":1685110470215,\"y\":21.08},{\"x\":1685110770218,\"y\":21.08},{\"x\":1685111070267,\"y\":21.3},{\"x\":1685111370237,\"y\":21.32},{\"x\":1685111670242,\"y\":21.21},{\"x\":1685111970278,\"y\":21.56},{\"x\":1685112270243,\"y\":21.43},{\"x\":1685112570286,\"y\":20.62},{\"x\":1685112870312,\"y\":20.61},{\"x\":1685113170279,\"y\":20.61},{\"x\":1685113470259,\"y\":20.52},{\"x\":1685113770274,\"y\":21.03},{\"x\":1685114070330,\"y\":20.96},{\"x\":1685114370346,\"y\":20.62},{\"x\":1685114670290,\"y\":20.62},{\"x\":1685114970303,\"y\":20.62},{\"x\":1685115270293,\"y\":20.57},{\"x\":1685115570345,\"y\":20.81},{\"x\":1685115870301,\"y\":20.81},{\"x\":1685116170341,\"y\":20.85},{\"x\":1685116470425,\"y\":20.35},{\"x\":1685116770319,\"y\":20.73},{\"x\":1685117070306,\"y\":20.35},{\"x\":1685117370344,\"y\":20.75},{\"x\":1685117670330,\"y\":20.75},{\"x\":1685117970336,\"y\":20.75},{\"x\":1685118270330,\"y\":20.45},{\"x\":1685118570325,\"y\":20.45},{\"x\":1685118870362,\"y\":19.95},{\"x\":1685119170359,\"y\":19.95},{\"x\":1685119470369,\"y\":19.95},{\"x\":1685119770365,\"y\":19.92},{\"x\":1685120070363,\"y\":20.06},{\"x\":1685120370385,\"y\":20.17},{\"x\":1685120670359,\"y\":20.17},{\"x\":1685120970383,\"y\":20.08},{\"x\":1685121270373,\"y\":19.95},{\"x\":1685121570376,\"y\":19.85},{\"x\":1685121870394,\"y\":19.88},{\"x\":1685122170385,\"y\":19.88},{\"x\":1685122470467,\"y\":19},{\"x\":1685122770415,\"y\":19},{\"x\":1685123070412,\"y\":19},{\"x\":1685123370409,\"y\":18.86},{\"x\":1685123670402,\"y\":18.86},{\"x\":1685123970418,\"y\":18.86},{\"x\":1685124270399,\"y\":18.86},{\"x\":1685124570415,\"y\":18.79},{\"x\":1685124870446,\"y\":18.79},{\"x\":1685125170457,\"y\":18.85},{\"x\":1685125470494,\"y\":18.85},{\"x\":1685125770424,\"y\":18.85},{\"x\":1685126070436,\"y\":18.71},{\"x\":1685126370464,\"y\":18.71},{\"x\":1685126670446,\"y\":18.67},{\"x\":1685126970458,\"y\":18.67},{\"x\":1685127270730,\"y\":17.76},{\"x\":1685127570540,\"y\":17.84},{\"x\":1685127870479,\"y\":17.42},{\"x\":1685128170488,\"y\":17.42},{\"x\":1685128470446,\"y\":17.32},{\"x\":1685128770711,\"y\":17.38},{\"x\":1685129070493,\"y\":17.55},{\"x\":1685129370460,\"y\":17.46},{\"x\":1685129670456,\"y\":17.51},{\"x\":1685129970444,\"y\":16.98},{\"x\":1685130270452,\"y\":16.94},{\"x\":1685130570463,\"y\":16.94},{\"x\":1685130870454,\"y\":16.63},{\"x\":1685131170558,\"y\":16.63},{\"x\":1685131470459,\"y\":16.63},{\"x\":1685131770480,\"y\":15.83},{\"x\":1685132070539,\"y\":15.61},{\"x\":1685132370654,\"y\":15.61},{\"x\":1685132670510,\"y\":15.17},{\"x\":1685132970470,\"y\":15.17},{\"x\":1685133270483,\"y\":15.17},{\"x\":1685133570534,\"y\":15.33},{\"x\":1685133870537,\"y\":15.17},{\"x\":1685134170481,\"y\":15.33},{\"x\":1685134470522,\"y\":15.17},{\"x\":1685134770619,\"y\":15.17},{\"x\":1685135070468,\"y\":15.17},{\"x\":1685135370539,\"y\":14.12},{\"x\":1685135670470,\"y\":14.12},{\"x\":1685135970588,\"y\":14.12},{\"x\":1685136270506,\"y\":13.94},{\"x\":1685136570482,\"y\":13.94},{\"x\":1685136870483,\"y\":13.88},{\"x\":1685137170591,\"y\":13.53},{\"x\":1685137470482,\"y\":13.29},{\"x\":1685137770513,\"y\":13.29},{\"x\":1685138070565,\"y\":13.24},{\"x\":1685138370612,\"y\":13.3},{\"x\":1685138670513,\"y\":12.65},{\"x\":1685138970488,\"y\":13.03},{\"x\":1685139270567,\"y\":12.44},{\"x\":1685139570520,\"y\":12.44},{\"x\":1685139870495,\"y\":12.47},{\"x\":1685140170514,\"y\":12.41},{\"x\":1685140470469,\"y\":12.41},{\"x\":1685140770566,\"y\":12.32},{\"x\":1685141070532,\"y\":11.91},{\"x\":1685141370505,\"y\":11.91},{\"x\":1685141670535,\"y\":11.91},{\"x\":1685141970572,\"y\":11.87},{\"x\":1685142270528,\"y\":11.87},{\"x\":1685142570539,\"y\":11.49},{\"x\":1685142870540,\"y\":11.49},{\"x\":1685143170721,\"y\":11.69},{\"x\":1685143470532,\"y\":11.69},{\"x\":1685143770537,\"y\":11.69},{\"x\":1685144070587,\"y\":11.38},{\"x\":1685144370585,\"y\":11.29},{\"x\":1685144670544,\"y\":11.38},{\"x\":1685144970592,\"y\":11.66},{\"x\":1685145270578,\"y\":11.66},{\"x\":1685145570616,\"y\":11.66},{\"x\":1685145870578,\"y\":11.54},{\"x\":1685146170575,\"y\":11.3},{\"x\":1685146470586,\"y\":11.26},{\"x\":1685146770618,\"y\":10.97},{\"x\":1685147070575,\"y\":10.97},{\"x\":1685147370627,\"y\":11.06},{\"x\":1685147670593,\"y\":10.78},{\"x\":1685147970600,\"y\":11.06},{\"x\":1685148270605,\"y\":10.65},{\"x\":1685148570644,\"y\":10.63},{\"x\":1685148870584,\"y\":10.63},{\"x\":1685149170590,\"y\":10.63},{\"x\":1685149470587,\"y\":10.44},{\"x\":1685149770583,\"y\":10.44},{\"x\":1685150070580,\"y\":10.1},{\"x\":1685150370584,\"y\":10.1},{\"x\":1685150670582,\"y\":10.03},{\"x\":1685150970594,\"y\":10.03},{\"x\":1685151270632,\"y\":10.08},{\"x\":1685151570591,\"y\":10.08},{\"x\":1685151870605,\"y\":9.61},{\"x\":1685152170611,\"y\":9.61},{\"x\":1685152470611,\"y\":9.42},{\"x\":1685152770613,\"y\":9.42},{\"x\":1685153070628,\"y\":9.48},{\"x\":1685153370683,\"y\":9.39},{\"x\":1685153670664,\"y\":9.33},{\"x\":1685153970636,\"y\":9.33},{\"x\":1685154270611,\"y\":9.33},{\"x\":1685154570650,\"y\":9.14},{\"x\":1685154870623,\"y\":9.14},{\"x\":1685155170939,\"y\":9.05},{\"x\":1685155470674,\"y\":8.95},{\"x\":1685155770628,\"y\":8.95},{\"x\":1685156070642,\"y\":8.86},{\"x\":1685156370678,\"y\":8.95},{\"x\":1685156670691,\"y\":8.95},{\"x\":1685156970647,\"y\":8.95},{\"x\":1685157270649,\"y\":8.48},{\"x\":1685157570728,\"y\":8.51},{\"x\":1685157870653,\"y\":8.67},{\"x\":1685158170653,\"y\":8.67},{\"x\":1685158470652,\"y\":8.47},{\"x\":1685158770755,\"y\":8.29},{\"x\":1685159070671,\"y\":8.29},{\"x\":1685159370691,\"y\":8.19},{\"x\":1685159670661,\"y\":8.19},{\"x\":1685159971379,\"y\":8.08},{\"x\":1685160270711,\"y\":8.04},{\"x\":1685160570739,\"y\":8.22},{\"x\":1685160870655,\"y\":8.22},{\"x\":1685161170787,\"y\":8.04},{\"x\":1685161470703,\"y\":8.24},{\"x\":1685161770668,\"y\":8.04},{\"x\":1685162070717,\"y\":8.02},{\"x\":1685162371170,\"y\":7.93},{\"x\":1685162670708,\"y\":8.02},{\"x\":1685162970719,\"y\":8.55},{\"x\":1685163270676,\"y\":8.55},{\"x\":1685163571126,\"y\":8.55},{\"x\":1685163870678,\"y\":8.77},{\"x\":1685164170681,\"y\":8.69},{\"x\":1685164470773,\"y\":9.46},{\"x\":1685164771057,\"y\":9.68},{\"x\":1685165070675,\"y\":9.97},{\"x\":1685165370677,\"y\":9.83},{\"x\":1685165670713,\"y\":10.29},{\"x\":1685165970916,\"y\":10.29},{\"x\":1685166270681,\"y\":10.47},{\"x\":1685166570684,\"y\":10.47},{\"x\":1685166870680,\"y\":11},{\"x\":1685167171095,\"y\":10.85},{\"x\":1685167470678,\"y\":11.53},{\"x\":1685167770689,\"y\":12.14},{\"x\":1685168070683,\"y\":12.14},{\"x\":1685168370787,\"y\":11.68},{\"x\":1685168670739,\"y\":11.68},{\"x\":1685168970743,\"y\":11.98},{\"x\":1685169270696,\"y\":11.98},{\"x\":1685169570699,\"y\":11.98},{\"x\":1685169870784,\"y\":13.66},{\"x\":1685170170701,\"y\":13.66},{\"x\":1685170470695,\"y\":13.66},{\"x\":1685170770757,\"y\":13.66},{\"x\":1685171070684,\"y\":13.98},{\"x\":1685171370697,\"y\":13.98},{\"x\":1685171670698,\"y\":14.38},{\"x\":1685171971138,\"y\":14.38},{\"x\":1685172270759,\"y\":13.85},{\"x\":1685172570699,\"y\":13.85},{\"x\":1685172870711,\"y\":13.85},{\"x\":1685173170778,\"y\":15.4},{\"x\":1685173470740,\"y\":15.5},{\"x\":1685173770702,\"y\":15.18},{\"x\":1685174070719,\"y\":15.49},{\"x\":1685174371094,\"y\":15.8},{\"x\":1685174670695,\"y\":15.49},{\"x\":1685174970800,\"y\":16.03},{\"x\":1685175270722,\"y\":15.86},{\"x\":1685175570763,\"y\":15.86},{\"x\":1685175870697,\"y\":16.35},{\"x\":1685176170707,\"y\":16.48},{\"x\":1685176470754,\"y\":16.94},{\"x\":1685176770748,\"y\":16.94},{\"x\":1685177070739,\"y\":17.05},{\"x\":1685177370721,\"y\":17.05},{\"x\":1685177670735,\"y\":17.05},{\"x\":1685177971115,\"y\":17.66},{\"x\":1685178270740,\"y\":17.66},{\"x\":1685178570747,\"y\":17.64},{\"x\":1685178870774,\"y\":18.01},{\"x\":1685179171072,\"y\":17.92},{\"x\":1685179470822,\"y\":18.19},{\"x\":1685179770758,\"y\":18.1},{\"x\":1685180070762,\"y\":18.19},{\"x\":1685180370805,\"y\":18.1},{\"x\":1685180670780,\"y\":18.51},{\"x\":1685180970809,\"y\":18.65},{\"x\":1685181270793,\"y\":18.72},{\"x\":1685181570900,\"y\":19.19},{\"x\":1685181870801,\"y\":19.1},{\"x\":1685182170811,\"y\":19.19},{\"x\":1685182470860,\"y\":19.44},{\"x\":1685182771048,\"y\":19.67},{\"x\":1685183070826,\"y\":19.44},{\"x\":1685183370877,\"y\":19.67},{\"x\":1685183670882,\"y\":19.85},{\"x\":1685183970939,\"y\":19.85},{\"x\":1685184270841,\"y\":19.85},{\"x\":1685184570853,\"y\":19.96},{\"x\":1685184870900,\"y\":20.18},{\"x\":1685185170967,\"y\":20.47},{\"x\":1685185470866,\"y\":20.18},{\"x\":1685185770871,\"y\":20.65},{\"x\":1685186070870,\"y\":20.65},{\"x\":1685186370892,\"y\":20.6},{\"x\":1685186670893,\"y\":20.57},{\"x\":1685186970904,\"y\":20.71},{\"x\":1685187270868,\"y\":20.71},{\"x\":1685187572989,\"y\":20.99},{\"x\":1685187870872,\"y\":20.99},{\"x\":1685188170877,\"y\":20.99},{\"x\":1685188470913,\"y\":21.13},{\"x\":1685188771470,\"y\":21.2},{\"x\":1685189070899,\"y\":21.12},{\"x\":1685189370894,\"y\":21.2},{\"x\":1685189670921,\"y\":21.41},{\"x\":1685189971610,\"y\":21.41},{\"x\":1685190270898,\"y\":21.41},{\"x\":1685190570940,\"y\":21.32},{\"x\":1685190870918,\"y\":21.32},{\"x\":1685191171751,\"y\":21.78},{\"x\":1685191470923,\"y\":21.68},{\"x\":1685191770922,\"y\":21.68},{\"x\":1685192070981,\"y\":22.17},{\"x\":1685192371026,\"y\":22.17},{\"x\":1685192670934,\"y\":22.17},{\"x\":1685192970954,\"y\":22.46},{\"x\":1685193270953,\"y\":22.46},{\"x\":1685193571076,\"y\":21.99},{\"x\":1685193870942,\"y\":21.91},{\"x\":1685194171071,\"y\":21.99},{\"x\":1685194470972,\"y\":21.91},{\"x\":1685194770981,\"y\":21.91},{\"x\":1685195070960,\"y\":23.06},{\"x\":1685195370975,\"y\":22.73},{\"x\":1685195670969,\"y\":23.06},{\"x\":1685195971032,\"y\":22.82},{\"x\":1685196270973,\"y\":22.82},{\"x\":1685196570982,\"y\":22.82},{\"x\":1685196870986,\"y\":21.82},{\"x\":1685197171084,\"y\":21.93},{\"x\":1685197470984,\"y\":22.31},{\"x\":1685197770988,\"y\":22.31},{\"x\":1685198070990,\"y\":22.31},{\"x\":1685198371020,\"y\":22.31},{\"x\":1685198671053,\"y\":22.37},{\"x\":1685198970993,\"y\":22.23},{\"x\":1685199271011,\"y\":22.37},{\"x\":1685199571046,\"y\":22.48},{\"x\":1685199871115,\"y\":21.98},{\"x\":1685200171006,\"y\":21.98},{\"x\":1685200471008,\"y\":21.98},{\"x\":1685200771192,\"y\":22.9},{\"x\":1685201071014,\"y\":22.9},{\"x\":1685201371067,\"y\":22.11},{\"x\":1685201671018,\"y\":22.11},{\"x\":1685201971372,\"y\":22.11},{\"x\":1685202271010,\"y\":21.91},{\"x\":1685202571020,\"y\":21.91},{\"x\":1685202871094,\"y\":22.37},{\"x\":1685203171957,\"y\":22.04},{\"x\":1685203471024,\"y\":21.92},{\"x\":1685203771087,\"y\":22.72},{\"x\":1685204071079,\"y\":22.72},{\"x\":1685204372043,\"y\":22.63},{\"x\":1685204671023,\"y\":22.63},{\"x\":1685204971067,\"y\":22.65},{\"x\":1685205271028,\"y\":22.65},{\"x\":1685205571490,\"y\":22.29},{\"x\":1685205871024,\"y\":21.93},{\"x\":1685206171037,\"y\":21.93},{\"x\":1685206471023,\"y\":21.53},{\"x\":1685206771057,\"y\":21.53},{\"x\":1685207071049,\"y\":20.84},{\"x\":1685207371039,\"y\":20.84},{\"x\":1685207671107,\"y\":20.92},{\"x\":1685207971068,\"y\":20.84},{\"x\":1685208271098,\"y\":22.62},{\"x\":1685208571110,\"y\":22.62},{\"x\":1685208871086,\"y\":22.62},{\"x\":1685209171774,\"y\":20.91},{\"x\":1685209471092,\"y\":20.82},{\"x\":1685209771092,\"y\":19.71},{\"x\":1685210071129,\"y\":19.73},{\"x\":1685210371211,\"y\":19.73},{\"x\":1685210671121,\"y\":19.24},{\"x\":1685210971142,\"y\":21.59},{\"x\":1685211271109,\"y\":21.59},{\"x\":1685211573673,\"y\":21.59},{\"x\":1685211871103,\"y\":21.59},{\"x\":1685212171150,\"y\":21.56},{\"x\":1685212471136,\"y\":21.47},{\"x\":1685212771652,\"y\":20.46},{\"x\":1685213071195,\"y\":20.46},{\"x\":1685213371188,\"y\":19.23},{\"x\":1685213671169,\"y\":19.15},{\"x\":1685213971280,\"y\":19.23},{\"x\":1685214271228,\"y\":19.41},{\"x\":1685214571150,\"y\":19.41},{\"x\":1685214871151,\"y\":19.24},{\"x\":1685215171157,\"y\":19.21},{\"x\":1685215471219,\"y\":19.21},{\"x\":1685215771223,\"y\":18.87},{\"x\":1685216071239,\"y\":18.72},{\"x\":1685216371187,\"y\":18.87},{\"x\":1685216671220,\"y\":16.48},{\"x\":1685216971167,\"y\":16.48},{\"x\":1685217271190,\"y\":16.48},{\"x\":1685217571385,\"y\":16.48},{\"x\":1685217871202,\"y\":16.48},{\"x\":1685218171176,\"y\":16.48},{\"x\":1685218471193,\"y\":17.13},{\"x\":1685218772083,\"y\":17.04},{\"x\":1685219071242,\"y\":16.94},{\"x\":1685219371182,\"y\":16.94},{\"x\":1685219671182,\"y\":16.94},{\"x\":1685219971327,\"y\":17.29},{\"x\":1685220271195,\"y\":17.28},{\"x\":1685220571204,\"y\":17.28},{\"x\":1685220871265,\"y\":16.43},{\"x\":1685221171213,\"y\":16.43},{\"x\":1685221471206,\"y\":16.43},{\"x\":1685221771244,\"y\":16.32},{\"x\":1685222071214,\"y\":16.32},{\"x\":1685222371240,\"y\":16.22},{\"x\":1685222671231,\"y\":15.63},{\"x\":1685222971227,\"y\":15.63},{\"x\":1685223271215,\"y\":15.63},{\"x\":1685223571642,\"y\":15.63},{\"x\":1685223871216,\"y\":14.69},{\"x\":1685224171238,\"y\":14.69},{\"x\":1685224471255,\"y\":15.05},{\"x\":1685224771718,\"y\":15.05},{\"x\":1685225071254,\"y\":15},{\"x\":1685225371225,\"y\":14.91},{\"x\":1685225671230,\"y\":14.91},{\"x\":1685225971278,\"y\":15.03},{\"x\":1685226271229,\"y\":14.76},{\"x\":1685226571268,\"y\":14.62},{\"x\":1685226871228,\"y\":14.62},{\"x\":1685227171310,\"y\":14.58},{\"x\":1685227471236,\"y\":14.47},{\"x\":1685227771233,\"y\":14.47},{\"x\":1685228071264,\"y\":14.56},{\"x\":1685228371438,\"y\":14.56},{\"x\":1685228671307,\"y\":14.37},{\"x\":1685228971287,\"y\":14.26},{\"x\":1685229271247,\"y\":14.17},{\"x\":1685229571283,\"y\":14.26},{\"x\":1685229871252,\"y\":14.08},{\"x\":1685230171240,\"y\":14.08},{\"x\":1685230471249,\"y\":13.81},{\"x\":1685230771547,\"y\":13.9},{\"x\":1685231071282,\"y\":13.88},{\"x\":1685231371247,\"y\":13.88},{\"x\":1685231671245,\"y\":13.57},{\"x\":1685231971299,\"y\":13.8},{\"x\":1685232271267,\"y\":13.8},{\"x\":1685232571258,\"y\":13.67},{\"x\":1685232871256,\"y\":13.67},{\"x\":1685233172128,\"y\":13.34},{\"x\":1685233471266,\"y\":13.26},{\"x\":1685233771304,\"y\":13.55},{\"x\":1685234071280,\"y\":13.5},{\"x\":1685234371267,\"y\":13.55},{\"x\":1685234671308,\"y\":13.47},{\"x\":1685234971294,\"y\":13.47},{\"x\":1685235271259,\"y\":13.47},{\"x\":1685235573201,\"y\":13.47},{\"x\":1685235871295,\"y\":13.51},{\"x\":1685236171262,\"y\":13.65},{\"x\":1685236471308,\"y\":13.36},{\"x\":1685236772640,\"y\":13.36},{\"x\":1685237071263,\"y\":13.55},{\"x\":1685237371273,\"y\":13.46},{\"x\":1685237671265,\"y\":13.46},{\"x\":1685237972838,\"y\":13.08},{\"x\":1685238271265,\"y\":13.08},{\"x\":1685238571268,\"y\":13.08},{\"x\":1685238871275,\"y\":13.28},{\"x\":1685239172611,\"y\":13.41},{\"x\":1685239471288,\"y\":13.33},{\"x\":1685239771274,\"y\":13.31},{\"x\":1685240071325,\"y\":13.15},{\"x\":1685240372227,\"y\":13.07},{\"x\":1685240671318,\"y\":13.07},{\"x\":1685240971329,\"y\":13.07},{\"x\":1685241271288,\"y\":13.07},{\"x\":1685241571466,\"y\":13.07},{\"x\":1685241871279,\"y\":12.98},{\"x\":1685242171369,\"y\":13.28},{\"x\":1685242471346,\"y\":13.28},{\"x\":1685242771307,\"y\":13.28},{\"x\":1685243071278,\"y\":13.28},{\"x\":1685243371329,\"y\":13.28},{\"x\":1685243671340,\"y\":13.34},{\"x\":1685243971443,\"y\":13.34},{\"x\":1685244271334,\"y\":13.34},{\"x\":1685244571292,\"y\":13.34},{\"x\":1685244871333,\"y\":13.47},{\"x\":1685245171537,\"y\":13.69},{\"x\":1685245471361,\"y\":13.69},{\"x\":1685245771298,\"y\":13.69},{\"x\":1685246071304,\"y\":13.6},{\"x\":1685246371537,\"y\":13.65},{\"x\":1685246671321,\"y\":13.56},{\"x\":1685246971335,\"y\":13.65},{\"x\":1685247271382,\"y\":13.48},{\"x\":1685247571470,\"y\":13.48},{\"x\":1685247871384,\"y\":13.28},{\"x\":1685248171411,\"y\":13.28},{\"x\":1685248471353,\"y\":13.24},{\"x\":1685248771403,\"y\":13.24},{\"x\":1685249071402,\"y\":13.23},{\"x\":1685249371376,\"y\":13.23},{\"x\":1685249671393,\"y\":13.23},{\"x\":1685249971493,\"y\":13.17},{\"x\":1685250271426,\"y\":13.08},{\"x\":1685250571467,\"y\":13.11},{\"x\":1685250871415,\"y\":13.14},{\"x\":1685251171439,\"y\":13.14},{\"x\":1685251471413,\"y\":13.14},{\"x\":1685251771409,\"y\":13.14},{\"x\":1685252071420,\"y\":13.19},{\"x\":1685252371615,\"y\":13.25},{\"x\":1685252671425,\"y\":13.17},{\"x\":1685252971434,\"y\":13.18},{\"x\":1685253271482,\"y\":13.38},{\"x\":1685253571596,\"y\":13.3},{\"x\":1685253871453,\"y\":13.38},{\"x\":1685254171462,\"y\":13.45},{\"x\":1685254471470,\"y\":13.51},{\"x\":1685254771721,\"y\":13.58},{\"x\":1685255071473,\"y\":13.58},{\"x\":1685255371478,\"y\":13.58},{\"x\":1685255671578,\"y\":13.78},{\"x\":1685255971776,\"y\":13.54},{\"x\":1685256271512,\"y\":13.78},{\"x\":1685256571498,\"y\":13.76},{\"x\":1685256871502,\"y\":13.87},{\"x\":1685257171620,\"y\":13.87},{\"x\":1685257471509,\"y\":14.11},{\"x\":1685257771511,\"y\":14.36},{\"x\":1685258071508,\"y\":14.36},{\"x\":1685258371764,\"y\":14.39},{\"x\":1685258671517,\"y\":14.56},{\"x\":1685258971516,\"y\":14.56},{\"x\":1685259271515,\"y\":14.92},{\"x\":1685259571742,\"y\":14.92},{\"x\":1685259871562,\"y\":15.01},{\"x\":1685260171531,\"y\":15.01},{\"x\":1685260471534,\"y\":15.01},{\"x\":1685260771696,\"y\":14.85},{\"x\":1685261071539,\"y\":15.04},{\"x\":1685261371589,\"y\":15.04},{\"x\":1685261671538,\"y\":15.4},{\"x\":1685261971867,\"y\":15.59},{\"x\":1685262271550,\"y\":15.59},{\"x\":1685262571548,\"y\":15.59},{\"x\":1685262871567,\"y\":15.46},{\"x\":1685263171794,\"y\":15.73},{\"x\":1685263471562,\"y\":15.69},{\"x\":1685263771602,\"y\":15.85},{\"x\":1685264071565,\"y\":15.71},{\"x\":1685264371846,\"y\":15.71},{\"x\":1685264671596,\"y\":15.72},{\"x\":1685264971615,\"y\":15.73},{\"x\":1685265271570,\"y\":15.73},{\"x\":1685265571596,\"y\":15.65},{\"x\":1685265871585,\"y\":15.47},{\"x\":1685266171591,\"y\":15.47},{\"x\":1685266471584,\"y\":15.48},{\"x\":1685266771720,\"y\":15.72},{\"x\":1685267071581,\"y\":15.72},{\"x\":1685267371587,\"y\":15.9},{\"x\":1685267671638,\"y\":16.17},{\"x\":1685267971896,\"y\":16.17},{\"x\":1685268271589,\"y\":15.98},{\"x\":1685268571603,\"y\":16.37},{\"x\":1685268871604,\"y\":16.37},{\"x\":1685269171897,\"y\":16.65},{\"x\":1685269471607,\"y\":16.58},{\"x\":1685269771613,\"y\":16.58},{\"x\":1685270071662,\"y\":16.41},{\"x\":1685270371882,\"y\":16.41},{\"x\":1685270671617,\"y\":16.41},{\"x\":1685270971621,\"y\":16.28},{\"x\":1685271271686,\"y\":16.19},{\"x\":1685271571713,\"y\":16.22},{\"x\":1685271871621,\"y\":16.15},{\"x\":1685272171630,\"y\":16.22},{\"x\":1685272471630,\"y\":15.92},{\"x\":1685272771817,\"y\":15.96},{\"x\":1685273071635,\"y\":15.96},{\"x\":1685273371650,\"y\":16.12},{\"x\":1685273671673,\"y\":15.95},{\"x\":1685273971701,\"y\":16.31},{\"x\":1685274271656,\"y\":16.31},{\"x\":1685274571692,\"y\":16.32},{\"x\":1685274871686,\"y\":16.32},{\"x\":1685275171921,\"y\":16.73},{\"x\":1685275471665,\"y\":16.65},{\"x\":1685275771698,\"y\":16.73},{\"x\":1685276071659,\"y\":16.22},{\"x\":1685276371756,\"y\":16.22},{\"x\":1685276671662,\"y\":16.48},{\"x\":1685276971675,\"y\":15.89},{\"x\":1685277271661,\"y\":15.89},{\"x\":1685277571970,\"y\":15.63},{\"x\":1685277871714,\"y\":15.68},{\"x\":1685278171675,\"y\":15.63},{\"x\":1685278471667,\"y\":15.56},{\"x\":1685278771920,\"y\":15.65},{\"x\":1685279071689,\"y\":15.87},{\"x\":1685279371671,\"y\":15.87},{\"x\":1685279671675,\"y\":15.87},{\"x\":1685279972052,\"y\":15.71},{\"x\":1685280271668,\"y\":15.71},{\"x\":1685280571670,\"y\":16.04},{\"x\":1685280871676,\"y\":16.04},{\"x\":1685281671888,\"y\":15.97},{\"x\":1685284096032,\"y\":15.5},{\"x\":1685284395921,\"y\":15.5},{\"x\":1685284695933,\"y\":15.5},{\"x\":1685284995930,\"y\":15.47},{\"x\":1685285295935,\"y\":15.47},{\"x\":1685285595944,\"y\":15.8},{\"x\":1685285895939,\"y\":15.8},{\"x\":1685286195949,\"y\":15.51},{\"x\":1685286495951,\"y\":15.51},{\"x\":1685286796009,\"y\":15.36},{\"x\":1685287095965,\"y\":15.27},{\"x\":1685287395953,\"y\":15.27},{\"x\":1685287695970,\"y\":15.36},{\"x\":1685287995955,\"y\":15.36},{\"x\":1685288295961,\"y\":15.34},{\"x\":1685288595996,\"y\":14.97},{\"x\":1685288895968,\"y\":14.97},{\"x\":1685289196133,\"y\":14.79},{\"x\":1685289495999,\"y\":14.79},{\"x\":1685289795972,\"y\":14.69},{\"x\":1685290095973,\"y\":14.69},{\"x\":1685290395973,\"y\":14.69},{\"x\":1685290695981,\"y\":14.79},{\"x\":1685290996003,\"y\":14.79},{\"x\":1685291295989,\"y\":14.79},{\"x\":1685291595985,\"y\":14.77},{\"x\":1685291896008,\"y\":14.5},{\"x\":1685292196008,\"y\":14.5},{\"x\":1685292496041,\"y\":14.55},{\"x\":1685292796005,\"y\":14.47},{\"x\":1685293096013,\"y\":14.55},{\"x\":1685293396082,\"y\":14.49},{\"x\":1685293696011,\"y\":14.45},{\"x\":1685293996026,\"y\":14.65},{\"x\":1685294296016,\"y\":14.65},{\"x\":1685294596031,\"y\":14.43},{\"x\":1685294896026,\"y\":14.43},{\"x\":1685295196031,\"y\":14.42},{\"x\":1685295496032,\"y\":14.4},{\"x\":1685295796073,\"y\":14.53},{\"x\":1685296096031,\"y\":14.53},{\"x\":1685296396105,\"y\":14.32},{\"x\":1685296696030,\"y\":14.24},{\"x\":1685296996047,\"y\":14.24},{\"x\":1685297296080,\"y\":14.48},{\"x\":1685297596038,\"y\":14.4},{\"x\":1685297896041,\"y\":14.48},{\"x\":1685298196059,\"y\":14.08},{\"x\":1685298496041,\"y\":14.08},{\"x\":1685298796049,\"y\":14.33},{\"x\":1685299096058,\"y\":14},{\"x\":1685299396061,\"y\":13.59},{\"x\":1685299696048,\"y\":13.56},{\"x\":1685299996049,\"y\":13.56},{\"x\":1685300296111,\"y\":13.8},{\"x\":1685300596061,\"y\":13.71},{\"x\":1685300896068,\"y\":13.71},{\"x\":1685301196056,\"y\":13.49},{\"x\":1685301496064,\"y\":13.49},{\"x\":1685301796076,\"y\":13.49},{\"x\":1685302096103,\"y\":13.4},{\"x\":1685302396063,\"y\":13.46},{\"x\":1685302696077,\"y\":13.34},{\"x\":1685302996145,\"y\":13.04},{\"x\":1685303296067,\"y\":12.95},{\"x\":1685303596073,\"y\":13.04},{\"x\":1685303896072,\"y\":12.69},{\"x\":1685304196079,\"y\":12.67},{\"x\":1685304496103,\"y\":12.49},{\"x\":1685304796082,\"y\":12.49},{\"x\":1685305096071,\"y\":12.49},{\"x\":1685305396094,\"y\":12.49},{\"x\":1685305696124,\"y\":12.2},{\"x\":1685305996075,\"y\":12.14},{\"x\":1685306296121,\"y\":11.8},{\"x\":1685306596072,\"y\":11.8},{\"x\":1685306896081,\"y\":11.8},{\"x\":1685307196124,\"y\":11.34},{\"x\":1685307496091,\"y\":11.34},{\"x\":1685307797175,\"y\":11.28},{\"x\":1685308096131,\"y\":11.3},{\"x\":1685308396095,\"y\":11.28},{\"x\":1685308696095,\"y\":11.3},{\"x\":1685308996098,\"y\":11.22},{\"x\":1685309296119,\"y\":11},{\"x\":1685309596104,\"y\":11},{\"x\":1685309896135,\"y\":10.5},{\"x\":1685310196123,\"y\":10.5},{\"x\":1685310496134,\"y\":10.5},{\"x\":1685310796104,\"y\":10.45},{\"x\":1685311096115,\"y\":10.45},{\"x\":1685311396137,\"y\":10.24},{\"x\":1685311696121,\"y\":10.24},{\"x\":1685311996202,\"y\":10.21},{\"x\":1685312296125,\"y\":10.21},{\"x\":1685312596146,\"y\":10.21},{\"x\":1685312896148,\"y\":9.93},{\"x\":1685313196132,\"y\":10.07},{\"x\":1685313496203,\"y\":9.89},{\"x\":1685313796150,\"y\":9.83},{\"x\":1685314096147,\"y\":9.75},{\"x\":1685314396195,\"y\":9.81},{\"x\":1685314696202,\"y\":9.81},{\"x\":1685314996169,\"y\":9.68},{\"x\":1685315296183,\"y\":9.65},{\"x\":1685315596155,\"y\":9.37},{\"x\":1685315896207,\"y\":9.43},{\"x\":1685316196167,\"y\":9.43},{\"x\":1685316496181,\"y\":9.32},{\"x\":1685316796216,\"y\":9.22},{\"x\":1685317096164,\"y\":9.22},{\"x\":1685317396213,\"y\":8.84},{\"x\":1685317696170,\"y\":8.76},{\"x\":1685317996185,\"y\":8.81},{\"x\":1685318296172,\"y\":8.72},{\"x\":1685318596191,\"y\":8.81},{\"x\":1685318896205,\"y\":8.99},{\"x\":1685319196165,\"y\":8.99},{\"x\":1685319496187,\"y\":8.9},{\"x\":1685319796169,\"y\":8.9},{\"x\":1685320096204,\"y\":8.99},{\"x\":1685320396170,\"y\":8.7},{\"x\":1685320696174,\"y\":8.65},{\"x\":1685320996175,\"y\":8.65},{\"x\":1685321296205,\"y\":8.41},{\"x\":1685321596177,\"y\":8.41},{\"x\":1685321896203,\"y\":8.41},{\"x\":1685322196246,\"y\":8.09},{\"x\":1685322496225,\"y\":7.88},{\"x\":1685322796181,\"y\":7.88},{\"x\":1685323096176,\"y\":7.73},{\"x\":1685323396175,\"y\":7.73},{\"x\":1685323696184,\"y\":7.67},{\"x\":1685323996186,\"y\":7.67},{\"x\":1685324296221,\"y\":7.83},{\"x\":1685324596190,\"y\":7.74},{\"x\":1685324896222,\"y\":7.83},{\"x\":1685325196203,\"y\":7.74},{\"x\":1685325496188,\"y\":7.32},{\"x\":1685325796536,\"y\":7.32},{\"x\":1685326096185,\"y\":7.32},{\"x\":1685326396231,\"y\":7.41},{\"x\":1685326696191,\"y\":7.41},{\"x\":1685326996194,\"y\":7.41},{\"x\":1685327296195,\"y\":7.29},{\"x\":1685327596190,\"y\":7.3},{\"x\":1685327896227,\"y\":7.33},{\"x\":1685328196279,\"y\":7.33},{\"x\":1685328496276,\"y\":7.33},{\"x\":1685328796240,\"y\":7.2},{\"x\":1685329096191,\"y\":7.2},{\"x\":1685329396192,\"y\":7.11},{\"x\":1685329696248,\"y\":7.2},{\"x\":1685329996247,\"y\":7.2},{\"x\":1685330296206,\"y\":7.2},{\"x\":1685330596222,\"y\":7.11},{\"x\":1685330896241,\"y\":7.37},{\"x\":1685331196223,\"y\":7.37},{\"x\":1685331496227,\"y\":7.37},{\"x\":1685331796287,\"y\":7.54},{\"x\":1685332096241,\"y\":7.54},{\"x\":1685332396262,\"y\":7.25},{\"x\":1685332696288,\"y\":7.34},{\"x\":1685332996282,\"y\":7.25},{\"x\":1685333296250,\"y\":7.34},{\"x\":1685333596314,\"y\":7.3},{\"x\":1685333896248,\"y\":7.3},{\"x\":1685334196250,\"y\":7.22},{\"x\":1685334496287,\"y\":7.3},{\"x\":1685334796262,\"y\":7.3},{\"x\":1685335096310,\"y\":7.34},{\"x\":1685335396322,\"y\":7.34},{\"x\":1685335696328,\"y\":7.35},{\"x\":1685335996277,\"y\":7.35},{\"x\":1685336296341,\"y\":7.46},{\"x\":1685336596305,\"y\":7.46},{\"x\":1685336896288,\"y\":7.46},{\"x\":1685337196337,\"y\":7.77},{\"x\":1685337496307,\"y\":7.68},{\"x\":1685337796344,\"y\":7.77},{\"x\":1685338096310,\"y\":7.7},{\"x\":1685338396309,\"y\":7.7},{\"x\":1685338696313,\"y\":7.87},{\"x\":1685338996338,\"y\":7.91},{\"x\":1685339296305,\"y\":8.24},{\"x\":1685339596306,\"y\":8.24},{\"x\":1685339896363,\"y\":8.87},{\"x\":1685340196325,\"y\":8.81},{\"x\":1685340496328,\"y\":8.87},{\"x\":1685340796324,\"y\":8.93},{\"x\":1685341096323,\"y\":8.93},{\"x\":1685341396403,\"y\":9.54},{\"x\":1685341696331,\"y\":9.46},{\"x\":1685341996329,\"y\":9.48},{\"x\":1685342296334,\"y\":9.53},{\"x\":1685342596385,\"y\":9.94},{\"x\":1685342896330,\"y\":9.94},{\"x\":1685343196338,\"y\":10.15},{\"x\":1685343496342,\"y\":10.74},{\"x\":1685343796354,\"y\":10.74},{\"x\":1685344096339,\"y\":11.17},{\"x\":1685344396346,\"y\":11.17},{\"x\":1685344697407,\"y\":11.29},{\"x\":1685344996353,\"y\":11.26},{\"x\":1685345296424,\"y\":11.74},{\"x\":1685345596406,\"y\":11.65},{\"x\":1685345896351,\"y\":11.65},{\"x\":1685346196416,\"y\":12.07},{\"x\":1685346496410,\"y\":12.16},{\"x\":1685346796378,\"y\":12.07},{\"x\":1685347096406,\"y\":12.03},{\"x\":1685347396359,\"y\":12.03},{\"x\":1685347696378,\"y\":12.03},{\"x\":1685347996394,\"y\":12.1},{\"x\":1685348296366,\"y\":12.1},{\"x\":1685348596360,\"y\":12.02},{\"x\":1685348896369,\"y\":12.44},{\"x\":1685349196480,\"y\":12.49},{\"x\":1685349496365,\"y\":12.49},{\"x\":1685349796376,\"y\":12.49},{\"x\":1685350096394,\"y\":12.97},{\"x\":1685350396369,\"y\":12.97},{\"x\":1685350696451,\"y\":13.05},{\"x\":1685350996421,\"y\":13.19},{\"x\":1685351296370,\"y\":13.05},{\"x\":1685351596436,\"y\":13.25},{\"x\":1685351896375,\"y\":13.25},{\"x\":1685352196381,\"y\":13.25},{\"x\":1685352496379,\"y\":13.28},{\"x\":1685352796390,\"y\":13.28},{\"x\":1685353096503,\"y\":13.97},{\"x\":1685353396412,\"y\":13.97},{\"x\":1685353696392,\"y\":13.97},{\"x\":1685353996429,\"y\":14.15},{\"x\":1685354296437,\"y\":14.15},{\"x\":1685354596450,\"y\":14.15},{\"x\":1685354896393,\"y\":14.3},{\"x\":1685355196401,\"y\":14.3},{\"x\":1685355496440,\"y\":14.36},{\"x\":1685355796402,\"y\":14.36},{\"x\":1685356096449,\"y\":14.32},{\"x\":1685356396391,\"y\":14.24},{\"x\":1685356696401,\"y\":14.32},{\"x\":1685356996401,\"y\":14.61},{\"x\":1685357296408,\"y\":14.61},{\"x\":1685357596403,\"y\":15.01},{\"x\":1685357896429,\"y\":15.59},{\"x\":1685358196393,\"y\":15.59},{\"x\":1685358496468,\"y\":15.59},{\"x\":1685358796424,\"y\":16},{\"x\":1685359096400,\"y\":16},{\"x\":1685359396448,\"y\":16.11},{\"x\":1685359696410,\"y\":16.08},{\"x\":1685359996404,\"y\":16.08},{\"x\":1685360296411,\"y\":16.35},{\"x\":1685360596442,\"y\":16.35},{\"x\":1685360896460,\"y\":16.44},{\"x\":1685361196442,\"y\":16.44},{\"x\":1685361496409,\"y\":16.44},{\"x\":1685361796427,\"y\":16.52},{\"x\":1685362096416,\"y\":16.62},{\"x\":1685362396413,\"y\":16.67},{\"x\":1685362696411,\"y\":16.68},{\"x\":1685362996461,\"y\":16.79},{\"x\":1685363296411,\"y\":16.85},{\"x\":1685363596420,\"y\":16.79},{\"x\":1685363896467,\"y\":16.76},{\"x\":1685364196435,\"y\":16.76},{\"x\":1685364496434,\"y\":16.76},{\"x\":1685364796430,\"y\":16.68},{\"x\":1685365096423,\"y\":17.1},{\"x\":1685365396432,\"y\":17.1},{\"x\":1685365696497,\"y\":17.46},{\"x\":1685365996459,\"y\":17.44},{\"x\":1685366296423,\"y\":17.46},{\"x\":1685366596445,\"y\":17.44},{\"x\":1685366896437,\"y\":17.38},{\"x\":1685367196433,\"y\":17.38},{\"x\":1685367496432,\"y\":17.38},{\"x\":1685367796435,\"y\":17.27},{\"x\":1685368096426,\"y\":17.27},{\"x\":1685368396429,\"y\":17.26},{\"x\":1685368696436,\"y\":17.26},{\"x\":1685368996438,\"y\":17.44},{\"x\":1685369296428,\"y\":17.32},{\"x\":1685369596501,\"y\":17.5},{\"x\":1685369896441,\"y\":17.42},{\"x\":1685370196509,\"y\":17.5},{\"x\":1685370496485,\"y\":17.7},{\"x\":1685370796502,\"y\":17.7},{\"x\":1685371096455,\"y\":17.7},{\"x\":1685371396510,\"y\":17.39},{\"x\":1685371696485,\"y\":17.39},{\"x\":1685371996502,\"y\":17.64},{\"x\":1685372296476,\"y\":17.31},{\"x\":1685372596474,\"y\":17.78},{\"x\":1685372896482,\"y\":17.78},{\"x\":1685373196484,\"y\":17.78},{\"x\":1685373496572,\"y\":17.86},{\"x\":1685373796514,\"y\":17.78},{\"x\":1685374096510,\"y\":17.81},{\"x\":1685374396544,\"y\":17.86},{\"x\":1685374696519,\"y\":17.78},{\"x\":1685374996566,\"y\":17.78},{\"x\":1685375296542,\"y\":17.89},{\"x\":1685375596519,\"y\":17.89},{\"x\":1685375896531,\"y\":17.89},{\"x\":1685376196570,\"y\":17.89},{\"x\":1685376496581,\"y\":18.04},{\"x\":1685376796589,\"y\":18.04},{\"x\":1685377096547,\"y\":17.74},{\"x\":1685377396613,\"y\":17.74},{\"x\":1685377696596,\"y\":17.6},{\"x\":1685377996542,\"y\":17.51},{\"x\":1685378296565,\"y\":17.51},{\"x\":1685378596624,\"y\":17.51},{\"x\":1685378896627,\"y\":17.42},{\"x\":1685379196643,\"y\":17.2},{\"x\":1685379496586,\"y\":17.42},{\"x\":1685379796637,\"y\":17.09},{\"x\":1685380096596,\"y\":17.09},{\"x\":1685380396635,\"y\":17.11},{\"x\":1685380696607,\"y\":17.04},{\"x\":1685380996713,\"y\":17.08},{\"x\":1685381296598,\"y\":16.99},{\"x\":1685381596615,\"y\":17.08},{\"x\":1685381896608,\"y\":17.03},{\"x\":1685382196647,\"y\":17.03},{\"x\":1685382496621,\"y\":16.09},{\"x\":1685382796622,\"y\":16.25},{\"x\":1685383096636,\"y\":16.25},{\"x\":1685383396656,\"y\":16.25},{\"x\":1685383696677,\"y\":16.1},{\"x\":1685383996640,\"y\":16.09},{\"x\":1685384296634,\"y\":16.09},{\"x\":1685384596702,\"y\":16.09},{\"x\":1685384896755,\"y\":15.72},{\"x\":1685385196657,\"y\":15.7},{\"x\":1685385496648,\"y\":15.72},{\"x\":1685385796721,\"y\":14.94},{\"x\":1685386096661,\"y\":14.89},{\"x\":1685386396764,\"y\":14.48},{\"x\":1685386696644,\"y\":14.48},{\"x\":1685386996711,\"y\":14.39},{\"x\":1685387296665,\"y\":14.47},{\"x\":1685387596687,\"y\":14.47},{\"x\":1685387896719,\"y\":14.45},{\"x\":1685388196746,\"y\":14.37},{\"x\":1685388496677,\"y\":14.36},{\"x\":1685388796728,\"y\":14.16},{\"x\":1685389096728,\"y\":14.95},{\"x\":1685389396772,\"y\":15.89},{\"x\":1685389696700,\"y\":15.8},{\"x\":1685389996694,\"y\":15.8},{\"x\":1685390296741,\"y\":13.52},{\"x\":1685390596769,\"y\":13.52},{\"x\":1685390896706,\"y\":13.52},{\"x\":1685391196708,\"y\":12.38},{\"x\":1685391496778,\"y\":12.52},{\"x\":1685391796848,\"y\":12.52},{\"x\":1685392096805,\"y\":12.37},{\"x\":1685392396723,\"y\":12.01},{\"x\":1685392696723,\"y\":12.01},{\"x\":1685392996799,\"y\":11.33},{\"x\":1685393296731,\"y\":11.58},{\"x\":1685393596725,\"y\":11.33},{\"x\":1685393896759,\"y\":11.64},{\"x\":1685394196778,\"y\":11.64},{\"x\":1685394496727,\"y\":11.63},{\"x\":1685394796838,\"y\":11},{\"x\":1685395096733,\"y\":10.92},{\"x\":1685395396756,\"y\":10.84},{\"x\":1685395696744,\"y\":10.81},{\"x\":1685395996738,\"y\":10.81},{\"x\":1685396296745,\"y\":10.81},{\"x\":1685396596863,\"y\":9.92},{\"x\":1685396896748,\"y\":9.92},{\"x\":1685397196744,\"y\":9.92},{\"x\":1685397496752,\"y\":9.84},{\"x\":1685397797031,\"y\":9.29},{\"x\":1685398096745,\"y\":9.59},{\"x\":1685398396778,\"y\":9.29},{\"x\":1685398696753,\"y\":9.67},{\"x\":1685398996882,\"y\":9.8},{\"x\":1685399296749,\"y\":9.67},{\"x\":1685399596872,\"y\":9.64},{\"x\":1685399896856,\"y\":9.29},{\"x\":1685400196853,\"y\":9.29},{\"x\":1685400496758,\"y\":9.29},{\"x\":1685400796806,\"y\":8.88},{\"x\":1685401096749,\"y\":8.88},{\"x\":1685401396853,\"y\":8.88},{\"x\":1685401696823,\"y\":8.87},{\"x\":1685401996749,\"y\":8.93},{\"x\":1685402296834,\"y\":8.83},{\"x\":1685402596862,\"y\":8.83},{\"x\":1685402896766,\"y\":8.83},{\"x\":1685403196764,\"y\":8.69},{\"x\":1685403496778,\"y\":8.79},{\"x\":1685403796902,\"y\":8.76},{\"x\":1685404096775,\"y\":8.76},{\"x\":1685404396829,\"y\":8.45},{\"x\":1685404696790,\"y\":8.45},{\"x\":1685404996816,\"y\":8.36},{\"x\":1685405296883,\"y\":8.71},{\"x\":1685405596870,\"y\":8.7},{\"x\":1685405896793,\"y\":8.63},{\"x\":1685406196983,\"y\":8.7},{\"x\":1685406496794,\"y\":8.47},{\"x\":1685406796799,\"y\":8.47},{\"x\":1685407096806,\"y\":8.41},{\"x\":1685407396889,\"y\":8.27},{\"x\":1685407696814,\"y\":8.19},{\"x\":1685407996818,\"y\":8.27},{\"x\":1685408296823,\"y\":8.19},{\"x\":1685408596913,\"y\":8.19},{\"x\":1685408896867,\"y\":7.82},{\"x\":1685409196827,\"y\":7.82},{\"x\":1685409496885,\"y\":7.78},{\"x\":1685409796923,\"y\":7.69},{\"x\":1685410096833,\"y\":7.69},{\"x\":1685410396863,\"y\":7.68},{\"x\":1685410696853,\"y\":7.69},{\"x\":1685410996931,\"y\":7.59},{\"x\":1685411296850,\"y\":7.59},{\"x\":1685411596860,\"y\":7.59},{\"x\":1685411896863,\"y\":7.34},{\"x\":1685412197065,\"y\":7.41},{\"x\":1685412496865,\"y\":7.34},{\"x\":1685412796871,\"y\":7.34},{\"x\":1685413096914,\"y\":7.41},{\"x\":1685413396986,\"y\":7.41},{\"x\":1685413696881,\"y\":7.41},{\"x\":1685413996886,\"y\":7.35},{\"x\":1685414296923,\"y\":7.82},{\"x\":1685414597013,\"y\":7.95},{\"x\":1685414896976,\"y\":7.95},{\"x\":1685415196895,\"y\":7.95},{\"x\":1685415496889,\"y\":7.95},{\"x\":1685415796967,\"y\":8.17},{\"x\":1685416096899,\"y\":8.17},{\"x\":1685416396952,\"y\":8.17},{\"x\":1685416696900,\"y\":8.09},{\"x\":1685416997056,\"y\":8.23},{\"x\":1685417296914,\"y\":8.21},{\"x\":1685417596918,\"y\":8.23},{\"x\":1685417896941,\"y\":8.11},{\"x\":1685418197072,\"y\":8.11},{\"x\":1685418496972,\"y\":7.78},{\"x\":1685418796976,\"y\":7.78},{\"x\":1685419096917,\"y\":7.78},{\"x\":1685419397122,\"y\":7.76},{\"x\":1685419696925,\"y\":7.7},{\"x\":1685419996981,\"y\":7.76},{\"x\":1685420296927,\"y\":7.98},{\"x\":1685420597159,\"y\":7.68},{\"x\":1685420896981,\"y\":8.25},{\"x\":1685421196938,\"y\":8.25},{\"x\":1685421496968,\"y\":9.25},{\"x\":1685421797058,\"y\":8.95},{\"x\":1685422096935,\"y\":8.95},{\"x\":1685422397004,\"y\":9.27},{\"x\":1685422696956,\"y\":9.27},{\"x\":1685422997188,\"y\":9.29},{\"x\":1685423296955,\"y\":9.29},{\"x\":1685423597001,\"y\":9.11},{\"x\":1685423896956,\"y\":9.02},{\"x\":1685424197012,\"y\":9.02},{\"x\":1685424497000,\"y\":9.11},{\"x\":1685424796971,\"y\":9.02},{\"x\":1685425096964,\"y\":10.04},{\"x\":1685425397109,\"y\":10.04},{\"x\":1685425696964,\"y\":10.02},{\"x\":1685425996974,\"y\":9.99},{\"x\":1685426296965,\"y\":10.2},{\"x\":1685426597100,\"y\":10.28},{\"x\":1685426896989,\"y\":10.33},{\"x\":1685427196988,\"y\":10.33},{\"x\":1685427497031,\"y\":10.45},{\"x\":1685427797156,\"y\":10.38},{\"x\":1685428097083,\"y\":10.94},{\"x\":1685428396991,\"y\":10.94},{\"x\":1685428696996,\"y\":10.82},{\"x\":1685428997105,\"y\":11.01},{\"x\":1685429296992,\"y\":11.01},{\"x\":1685429597030,\"y\":11.01},{\"x\":1685429896994,\"y\":11.04},{\"x\":1685430197076,\"y\":11.04},{\"x\":1685430497010,\"y\":11.39},{\"x\":1685430796999,\"y\":11.6},{\"x\":1685431097007,\"y\":11.55},{\"x\":1685431397249,\"y\":11.81},{\"x\":1685431697002,\"y\":11.81},{\"x\":1685431997013,\"y\":11.81},{\"x\":1685432297009,\"y\":11.82},{\"x\":1685432597030,\"y\":11.82},{\"x\":1685432897132,\"y\":12.15},{\"x\":1685433197016,\"y\":12.07},{\"x\":1685433497009,\"y\":12.15},{\"x\":1685433797139,\"y\":12.16},{\"x\":1685434097016,\"y\":12.19},{\"x\":1685434397064,\"y\":12.22},{\"x\":1685434697027,\"y\":12.14},{\"x\":1685434997238,\"y\":12.14},{\"x\":1685435297077,\"y\":12.32},{\"x\":1685435597014,\"y\":12.33},{\"x\":1685435897071,\"y\":12.41},{\"x\":1685436197195,\"y\":12.35},{\"x\":1685436497013,\"y\":12.35},{\"x\":1685436797015,\"y\":12.32},{\"x\":1685437097050,\"y\":12.32},{\"x\":1685437397237,\"y\":12.33},{\"x\":1685437697030,\"y\":12.33},{\"x\":1685437997069,\"y\":12.27},{\"x\":1685438297034,\"y\":12.19},{\"x\":1685438597203,\"y\":12.17},{\"x\":1685438897033,\"y\":12.34},{\"x\":1685439197064,\"y\":12.31},{\"x\":1685439497033,\"y\":12.32},{\"x\":1685439797107,\"y\":12.31},{\"x\":1685440097051,\"y\":13.11},{\"x\":1685440397038,\"y\":13.21},{\"x\":1685440697073,\"y\":13.37},{\"x\":1685440997179,\"y\":13.19},{\"x\":1685441297048,\"y\":13.28},{\"x\":1685441597041,\"y\":13.19},{\"x\":1685441897039,\"y\":13.29},{\"x\":1685442197094,\"y\":13.29},{\"x\":1685442497030,\"y\":13.33},{\"x\":1685442797045,\"y\":13.33},{\"x\":1685443097030,\"y\":13.2},{\"x\":1685443397349,\"y\":13.31},{\"x\":1685443697027,\"y\":13.31},{\"x\":1685443997092,\"y\":13.23},{\"x\":1685444297033,\"y\":13.77},{\"x\":1685444597073,\"y\":13.68},{\"x\":1685444897078,\"y\":16.19},{\"x\":1685445197034,\"y\":16.19},{\"x\":1685445497068,\"y\":16.19},{\"x\":1685445797209,\"y\":16.19},{\"x\":1685446097058,\"y\":16.19},{\"x\":1685446397051,\"y\":17.22},{\"x\":1685446697059,\"y\":17.22},{\"x\":1685446997166,\"y\":18.41},{\"x\":1685447297115,\"y\":18.41},{\"x\":1685447597112,\"y\":18.41},{\"x\":1685447897089,\"y\":18.41},{\"x\":1685448197159,\"y\":12.74},{\"x\":1685448497098,\"y\":12.72},{\"x\":1685448797127,\"y\":12.89},{\"x\":1685449097105,\"y\":12.97},{\"x\":1685449397373,\"y\":13.15},{\"x\":1685449697109,\"y\":13.03},{\"x\":1685449997136,\"y\":13.06},{\"x\":1685450297128,\"y\":13.06},{\"x\":1685450597387,\"y\":13.06},{\"x\":1685450897183,\"y\":13.21},{\"x\":1685451197144,\"y\":13.21},{\"x\":1685451497142,\"y\":13.33},{\"x\":1685451797185,\"y\":13.33},{\"x\":1685452097148,\"y\":13.57},{\"x\":1685452397249,\"y\":13.64},{\"x\":1685452697161,\"y\":13.64},{\"x\":1685452997474,\"y\":13.64},{\"x\":1685453297206,\"y\":13.97},{\"x\":1685453597173,\"y\":13.69},{\"x\":1685453897183,\"y\":13.94},{\"x\":1685454197215,\"y\":13.94},{\"x\":1685454497192,\"y\":14.16},{\"x\":1685454797260,\"y\":14.48},{\"x\":1685455097202,\"y\":14.67},{\"x\":1685455397476,\"y\":14.67},{\"x\":1685455697209,\"y\":14.86},{\"x\":1685455997240,\"y\":15.03},{\"x\":1685456297262,\"y\":15.18},{\"x\":1685456597249,\"y\":15.18},{\"x\":1685456897228,\"y\":15.09},{\"x\":1685457197269,\"y\":15.55},{\"x\":1685457497236,\"y\":15.55},{\"x\":1685457797465,\"y\":15.55},{\"x\":1685458097286,\"y\":16.03},{\"x\":1685458397242,\"y\":16.03},{\"x\":1685458697267,\"y\":16.03},{\"x\":1685458997286,\"y\":15.84},{\"x\":1685459297248,\"y\":15.84},{\"x\":1685459597268,\"y\":16.22},{\"x\":1685459897263,\"y\":16.22},{\"x\":1685460197357,\"y\":16.94},{\"x\":1685460497301,\"y\":16.94},{\"x\":1685460797266,\"y\":16.94},{\"x\":1685461097305,\"y\":16.95},{\"x\":1685461397544,\"y\":17.05},{\"x\":1685461697280,\"y\":16.95},{\"x\":1685461997291,\"y\":16.97},{\"x\":1685462297287,\"y\":16.97},{\"x\":1685462597465,\"y\":17.35},{\"x\":1685462897303,\"y\":17.35},{\"x\":1685463197366,\"y\":17.2},{\"x\":1685463737229,\"y\":17.2},{\"x\":1685464037277,\"y\":17.14},{\"x\":1685464337245,\"y\":17.14},{\"x\":1685464637256,\"y\":17.03},{\"x\":1685464937262,\"y\":17.14},{\"x\":1685465237316,\"y\":17.08},{\"x\":1685465537294,\"y\":17.08},{\"x\":1685465837334,\"y\":17.31},{\"x\":1685466137337,\"y\":17.31},{\"x\":1685466437310,\"y\":17.03},{\"x\":1685466737354,\"y\":17.04},{\"x\":1685467037407,\"y\":16.96},{\"x\":1685467337326,\"y\":17.16},{\"x\":1685467637328,\"y\":17.13},{\"x\":1685467937333,\"y\":16.81},{\"x\":1685468237339,\"y\":16.48},{\"x\":1685468537360,\"y\":16.48},{\"x\":1685468837368,\"y\":16.47},{\"x\":1685469137413,\"y\":15.9},{\"x\":1685469437370,\"y\":16.04},{\"x\":1685469737372,\"y\":15.9},{\"x\":1685470037427,\"y\":15.82},{\"x\":1685470337392,\"y\":15.82},{\"x\":1685470637406,\"y\":15.82},{\"x\":1685470937407,\"y\":15.69},{\"x\":1685471237416,\"y\":15.83},{\"x\":1685471537421,\"y\":15.83},{\"x\":1685471837423,\"y\":15.95},{\"x\":1685472137555,\"y\":15.16},{\"x\":1685472437434,\"y\":15.08},{\"x\":1685472737439,\"y\":14.87},{\"x\":1685473037727,\"y\":14.79},{\"x\":1685473337523,\"y\":15.1},{\"x\":1685473637452,\"y\":14.79},{\"x\":1685473937500,\"y\":14.51},{\"x\":1685474237504,\"y\":14.29},{\"x\":1685474537449,\"y\":14.29},{\"x\":1685474837471,\"y\":14.21},{\"x\":1685475137479,\"y\":14.06},{\"x\":1685475437511,\"y\":13.27},{\"x\":1685475737497,\"y\":13.27},{\"x\":1685476037496,\"y\":13.19},{\"x\":1685476337572,\"y\":13.61},{\"x\":1685476637511,\"y\":13.49},{\"x\":1685476937513,\"y\":13.47},{\"x\":1685477237528,\"y\":13.42},{\"x\":1685477537526,\"y\":13.44},{\"x\":1685477837530,\"y\":13.44},{\"x\":1685478137582,\"y\":13.03},{\"x\":1685478437532,\"y\":13.14},{\"x\":1685478737565,\"y\":12.83},{\"x\":1685479037545,\"y\":12.91},{\"x\":1685479337555,\"y\":12.59},{\"x\":1685479637566,\"y\":12.56},{\"x\":1685479937595,\"y\":12.5},{\"x\":1685480237565,\"y\":12.61},{\"x\":1685480537635,\"y\":12.43},{\"x\":1685480837564,\"y\":12.43},{\"x\":1685481137578,\"y\":12.27},{\"x\":1685481437654,\"y\":12.32},{\"x\":1685481737583,\"y\":12.24},{\"x\":1685482037634,\"y\":12.13},{\"x\":1685482337596,\"y\":12.13},{\"x\":1685482637588,\"y\":12.04},{\"x\":1685482937605,\"y\":11.88},{\"x\":1685483237606,\"y\":11.88},{\"x\":1685483537617,\"y\":11.87},{\"x\":1685483837694,\"y\":11.87},{\"x\":1685484137652,\"y\":11.83},{\"x\":1685484437620,\"y\":11.55},{\"x\":1685484737629,\"y\":11.83},{\"x\":1685485037682,\"y\":11.43},{\"x\":1685485337673,\"y\":11.43},{\"x\":1685485637637,\"y\":11.43},{\"x\":1685485937698,\"y\":11.4},{\"x\":1685486237666,\"y\":11.4},{\"x\":1685486537695,\"y\":11.4},{\"x\":1685486837701,\"y\":11.34},{\"x\":1685487137670,\"y\":11.34},{\"x\":1685487437690,\"y\":11.34},{\"x\":1685487737711,\"y\":11.08},{\"x\":1685488037671,\"y\":11.08},{\"x\":1685488337728,\"y\":11.08},{\"x\":1685488637752,\"y\":11.06},{\"x\":1685488937679,\"y\":11.06},{\"x\":1685489237735,\"y\":11.04},{\"x\":1685489537691,\"y\":10.96},{\"x\":1685489837848,\"y\":11.05},{\"x\":1685490137701,\"y\":11.05},{\"x\":1685490437705,\"y\":10.95},{\"x\":1685490737730,\"y\":10.86},{\"x\":1685491037756,\"y\":10.8},{\"x\":1685491337708,\"y\":10.8},{\"x\":1685491637705,\"y\":10.8},{\"x\":1685491937711,\"y\":10.71},{\"x\":1685492237764,\"y\":10.8},{\"x\":1685492537769,\"y\":10.8},{\"x\":1685492837718,\"y\":10.8},{\"x\":1685493137753,\"y\":10.8},{\"x\":1685493437930,\"y\":10.87},{\"x\":1685493737724,\"y\":10.87},{\"x\":1685494037777,\"y\":10.61},{\"x\":1685494337747,\"y\":10.52},{\"x\":1685494637852,\"y\":10.61},{\"x\":1685494937737,\"y\":10.61},{\"x\":1685495237777,\"y\":10.63},{\"x\":1685495537775,\"y\":10.62},{\"x\":1685495837812,\"y\":10.51},{\"x\":1685496137755,\"y\":10.43},{\"x\":1685496437796,\"y\":10.49},{\"x\":1685496737809,\"y\":10.49},{\"x\":1685497037761,\"y\":10.49},{\"x\":1685497337761,\"y\":10.23},{\"x\":1685497637765,\"y\":10.23},{\"x\":1685497937814,\"y\":10.31},{\"x\":1685498237807,\"y\":10.31},{\"x\":1685498537788,\"y\":10.31},{\"x\":1685498837775,\"y\":10.23},{\"x\":1685499137789,\"y\":10.23},{\"x\":1685499437805,\"y\":10.23},{\"x\":1685499737817,\"y\":10.31},{\"x\":1685500037780,\"y\":10.31},{\"x\":1685500337844,\"y\":10.14},{\"x\":1685500637891,\"y\":10.14},{\"x\":1685500937793,\"y\":10.14},{\"x\":1685501237793,\"y\":10.14},{\"x\":1685501537807,\"y\":10.05},{\"x\":1685501837925,\"y\":10.05},{\"x\":1685502137801,\"y\":10.05},{\"x\":1685502437811,\"y\":9.97},{\"x\":1685502737895,\"y\":10.14},{\"x\":1685503037900,\"y\":10.16},{\"x\":1685503337808,\"y\":10.16},{\"x\":1685503637807,\"y\":10.08},{\"x\":1685503937812,\"y\":9.94},{\"x\":1685504237850,\"y\":9.94},{\"x\":1685504537822,\"y\":9.99},{\"x\":1685504837872,\"y\":9.96},{\"x\":1685505137866,\"y\":9.98},{\"x\":1685505438032,\"y\":9.98},{\"x\":1685505737834,\"y\":9.98},{\"x\":1685506037879,\"y\":9.81},{\"x\":1685506337861,\"y\":9.81},{\"x\":1685506637914,\"y\":9.81},{\"x\":1685506937872,\"y\":9.81},{\"x\":1685507237889,\"y\":9.81},{\"x\":1685507537838,\"y\":9.81},{\"x\":1685507837888,\"y\":9.81},{\"x\":1685508137887,\"y\":9.82},{\"x\":1685508437832,\"y\":9.73},{\"x\":1685508737867,\"y\":9.82},{\"x\":1685509037974,\"y\":9.62},{\"x\":1685509337857,\"y\":9.82},{\"x\":1685509637848,\"y\":9.62},{\"x\":1685509937889,\"y\":9.62},{\"x\":1685510237947,\"y\":9.62},{\"x\":1685510537889,\"y\":9.65},{\"x\":1685510837852,\"y\":9.65},{\"x\":1685511137855,\"y\":9.65},{\"x\":1685511437965,\"y\":9.57},{\"x\":1685511737859,\"y\":9.57},{\"x\":1685512037848,\"y\":9.54},{\"x\":1685512337852,\"y\":9.55},{\"x\":1685512638025,\"y\":9.58},{\"x\":1685512937857,\"y\":9.57},{\"x\":1685513237850,\"y\":9.58},{\"x\":1685513537848,\"y\":9.57},{\"x\":1685513837893,\"y\":9.57},{\"x\":1685514137857,\"y\":9.75},{\"x\":1685514437855,\"y\":9.75},{\"x\":1685514737977,\"y\":9.84},{\"x\":1685515037889,\"y\":9.84},{\"x\":1685515337874,\"y\":9.84},{\"x\":1685515637865,\"y\":9.84},{\"x\":1685515937961,\"y\":9.84},{\"x\":1685516238059,\"y\":9.84},{\"x\":1685516537872,\"y\":9.75},{\"x\":1685516837969,\"y\":10.01},{\"x\":1685517137902,\"y\":10.01},{\"x\":1685517437997,\"y\":10.01},{\"x\":1685517737887,\"y\":10.03},{\"x\":1685518037974,\"y\":10.03},{\"x\":1685518337895,\"y\":9.92},{\"x\":1685518637932,\"y\":9.92},{\"x\":1685518937904,\"y\":9.96},{\"x\":1685519238002,\"y\":10.1},{\"x\":1685519537959,\"y\":10.1},{\"x\":1685519837988,\"y\":10.1},{\"x\":1685520137912,\"y\":10.1},{\"x\":1685520437906,\"y\":10.37},{\"x\":1685520737913,\"y\":10.33},{\"x\":1685521038006,\"y\":10.33},{\"x\":1685521337908,\"y\":10.33},{\"x\":1685521637915,\"y\":10.33},{\"x\":1685521937963,\"y\":10.25},{\"x\":1685522238080,\"y\":10.54},{\"x\":1685522537924,\"y\":10.54},{\"x\":1685522837919,\"y\":10.54},{\"x\":1685523137944,\"y\":10.5},{\"x\":1685523438045,\"y\":10.6},{\"x\":1685523737930,\"y\":10.58},{\"x\":1685524037929,\"y\":10.52},{\"x\":1685524337974,\"y\":10.82},{\"x\":1685524638007,\"y\":10.82},{\"x\":1685524937934,\"y\":10.82},{\"x\":1685525237935,\"y\":10.95},{\"x\":1685525537943,\"y\":10.99},{\"x\":1685525838078,\"y\":11.08},{\"x\":1685526137951,\"y\":11.08},{\"x\":1685526437950,\"y\":10.99},{\"x\":1685526737944,\"y\":11.07},{\"x\":1685527038721,\"y\":11.07},{\"x\":1685527338012,\"y\":11.11},{\"x\":1685527637961,\"y\":11.11},{\"x\":1685527937952,\"y\":11.07},{\"x\":1685528238080,\"y\":11.16},{\"x\":1685528537978,\"y\":11.03},{\"x\":1685528837960,\"y\":11.12},{\"x\":1685529137967,\"y\":11.08},{\"x\":1685529438131,\"y\":11.61},{\"x\":1685529738016,\"y\":11.61},{\"x\":1685530037967,\"y\":11.61},{\"x\":1685530337963,\"y\":11.61},{\"x\":1685530638067,\"y\":11.61},{\"x\":1685530937995,\"y\":11.61},{\"x\":1685531237967,\"y\":11.61},{\"x\":1685531537979,\"y\":12.07},{\"x\":1685531838029,\"y\":11.73},{\"x\":1685532137972,\"y\":11.72},{\"x\":1685532437974,\"y\":11.73},{\"x\":1685532738070,\"y\":11.43},{\"x\":1685533038022,\"y\":11.92},{\"x\":1685533338038,\"y\":16.07},{\"x\":1685533637982,\"y\":16.07},{\"x\":1685533938045,\"y\":16.07},{\"x\":1685534238057,\"y\":16.07},{\"x\":1685534537980,\"y\":16.07},{\"x\":1685534838036,\"y\":12.17},{\"x\":1685535137993,\"y\":12.17},{\"x\":1685535438136,\"y\":12.17},{\"x\":1685535737984,\"y\":12.17},{\"x\":1685536037986,\"y\":12.17},{\"x\":1685536337985,\"y\":12.17},{\"x\":1685536638024,\"y\":11.9},{\"x\":1685536938025,\"y\":15.91},{\"x\":1685537237999,\"y\":15.91},{\"x\":1685537538002,\"y\":15.91},{\"x\":1685537838106,\"y\":15.91},{\"x\":1685538138007,\"y\":15.82},{\"x\":1685538438058,\"y\":13.28},{\"x\":1685538738048,\"y\":13.28},{\"x\":1685539038104,\"y\":13.28},{\"x\":1685539338007,\"y\":13.2},{\"x\":1685539638019,\"y\":13.2},{\"x\":1685539938003,\"y\":13.33},{\"x\":1685540238052,\"y\":13.2},{\"x\":1685540538039,\"y\":16.75},{\"x\":1685540838017,\"y\":16.67},{\"x\":1685541138064,\"y\":16.75},{\"x\":1685541438054,\"y\":16.67},{\"x\":1685541738229,\"y\":17.3},{\"x\":1685542038012,\"y\":17.22},{\"x\":1685542338042,\"y\":17.3},{\"x\":1685542638126,\"y\":18.38},{\"x\":1685542938012,\"y\":18.38},{\"x\":1685543238056,\"y\":18.97},{\"x\":1685543538110,\"y\":17.86},{\"x\":1685543838052,\"y\":17.86},{\"x\":1685544138125,\"y\":17.78},{\"x\":1685544438027,\"y\":17.78},{\"x\":1685544738110,\"y\":14.96},{\"x\":1685545038075,\"y\":14.94},{\"x\":1685545338027,\"y\":14.94},{\"x\":1685545638075,\"y\":15.63},{\"x\":1685545938022,\"y\":15.63},{\"x\":1685546238174,\"y\":16},{\"x\":1685546538079,\"y\":15.83},{\"x\":1685546838020,\"y\":15.81},{\"x\":1685547138129,\"y\":14.9},{\"x\":1685547438095,\"y\":14.83},{\"x\":1685547738125,\"y\":14.9},{\"x\":1685548038046,\"y\":14.83},{\"x\":1685548338056,\"y\":14.9},{\"x\":1685548638087,\"y\":14.85},{\"x\":1685548938038,\"y\":14.85},{\"x\":1685549238040,\"y\":14.85},{\"x\":1685549538033,\"y\":14.97},{\"x\":1685549838168,\"y\":14.97},{\"x\":1685550138087,\"y\":15.03},{\"x\":1685550438030,\"y\":14.97},{\"x\":1685550738120,\"y\":14.3},{\"x\":1685551038139,\"y\":14.3},{\"x\":1685551338037,\"y\":13.6},{\"x\":1685551638033,\"y\":13.49},{\"x\":1685551938041,\"y\":13.47},{\"x\":1685552238174,\"y\":13.61},{\"x\":1685552538043,\"y\":13.61},{\"x\":1685552838043,\"y\":13.52},{\"x\":1685553138067,\"y\":13.52},{\"x\":1685553438546,\"y\":13.6},{\"x\":1685553738055,\"y\":13.52},{\"x\":1685554038140,\"y\":13.59},{\"x\":1685554338045,\"y\":13.51},{\"x\":1685554638134,\"y\":13.16},{\"x\":1685554938070,\"y\":13.16},{\"x\":1685555238057,\"y\":13.18},{\"x\":1685555538064,\"y\":13.07},{\"x\":1685555838093,\"y\":13.07},{\"x\":1685556138085,\"y\":13.14},{\"x\":1685556438188,\"y\":13.11},{\"x\":1685556738092,\"y\":13.12},{\"x\":1685557038192,\"y\":13.01},{\"x\":1685557338095,\"y\":13.01},{\"x\":1685557638182,\"y\":12.89},{\"x\":1685557938150,\"y\":12.8},{\"x\":1685558238172,\"y\":12.61},{\"x\":1685558538110,\"y\":12.8},{\"x\":1685558838201,\"y\":12.58},{\"x\":1685559138123,\"y\":12.58},{\"x\":1685559438206,\"y\":12.59},{\"x\":1685559738189,\"y\":12.57},{\"x\":1685560038129,\"y\":12.57},{\"x\":1685560338155,\"y\":12.57},{\"x\":1685560638404,\"y\":12.29},{\"x\":1685560938173,\"y\":12.29},{\"x\":1685561238169,\"y\":12.29},{\"x\":1685561538183,\"y\":12.17},{\"x\":1685561838237,\"y\":12.17},{\"x\":1685562138233,\"y\":12.21},{\"x\":1685562438249,\"y\":12.18},{\"x\":1685562738211,\"y\":12.18},{\"x\":1685563038228,\"y\":12.09},{\"x\":1685563338214,\"y\":12.07},{\"x\":1685563638217,\"y\":12.07},{\"x\":1685563938231,\"y\":11.8},{\"x\":1685564238362,\"y\":11.86},{\"x\":1685564538381,\"y\":11.83},{\"x\":1685564838277,\"y\":11.83},{\"x\":1685565138242,\"y\":11.79},{\"x\":1685565438280,\"y\":11.7},{\"x\":1685565738251,\"y\":11.53},{\"x\":1685566038259,\"y\":11.53},{\"x\":1685566338265,\"y\":11.53},{\"x\":1685566638303,\"y\":11.43},{\"x\":1685566938268,\"y\":11.43},{\"x\":1685567238323,\"y\":11.33},{\"x\":1685567538343,\"y\":11.39},{\"x\":1685567838304,\"y\":11.31},{\"x\":1685568138281,\"y\":11.39},{\"x\":1685568438336,\"y\":11.31},{\"x\":1685568738284,\"y\":11.31},{\"x\":1685569038329,\"y\":11.31},{\"x\":1685569338310,\"y\":11.25},{\"x\":1685569638302,\"y\":11.25},{\"x\":1685569938300,\"y\":11.25},{\"x\":1685570238298,\"y\":10.97},{\"x\":1685570538297,\"y\":10.97},{\"x\":1685570838294,\"y\":10.97},{\"x\":1685571138323,\"y\":10.78},{\"x\":1685571438384,\"y\":10.78},{\"x\":1685571738313,\"y\":10.78},{\"x\":1685572038419,\"y\":10.8},{\"x\":1685572338315,\"y\":10.8},{\"x\":1685572638324,\"y\":10.8},{\"x\":1685572938327,\"y\":10.44},{\"x\":1685573238329,\"y\":10.44},{\"x\":1685573538333,\"y\":10.44},{\"x\":1685573838369,\"y\":10.44},{\"x\":1685574138332,\"y\":10.44},{\"x\":1685574438328,\"y\":10.43},{\"x\":1685574738335,\"y\":10.43},{\"x\":1685575038372,\"y\":10.42},{\"x\":1685575338334,\"y\":10.43},{\"x\":1685575638376,\"y\":10.44},{\"x\":1685575938389,\"y\":10.48},{\"x\":1685576238419,\"y\":10.39},{\"x\":1685576538344,\"y\":10.11},{\"x\":1685576838359,\"y\":10.2},{\"x\":1685577138380,\"y\":10.29},{\"x\":1685577438375,\"y\":10.29},{\"x\":1685577738413,\"y\":10.3},{\"x\":1685578038361,\"y\":10.29},{\"x\":1685578338371,\"y\":10.3},{\"x\":1685578638503,\"y\":10.18},{\"x\":1685578938376,\"y\":10.18},{\"x\":1685579238383,\"y\":10.09},{\"x\":1685579538380,\"y\":10.17},{\"x\":1685579838438,\"y\":10.13},{\"x\":1685580138376,\"y\":9.88},{\"x\":1685580438398,\"y\":9.88},{\"x\":1685580738405,\"y\":9.94},{\"x\":1685581038616,\"y\":9.94},{\"x\":1685581338395,\"y\":9.94},{\"x\":1685581638435,\"y\":9.94},{\"x\":1685581938409,\"y\":9.88},{\"x\":1685582238480,\"y\":9.86},{\"x\":1685582538402,\"y\":9.86},{\"x\":1685582838405,\"y\":9.86},{\"x\":1685583138415,\"y\":9.86},{\"x\":1685583438613,\"y\":9.86},{\"x\":1685583738415,\"y\":9.88},{\"x\":1685584038410,\"y\":9.88},{\"x\":1685584338448,\"y\":9.79},{\"x\":1685584638454,\"y\":9.88},{\"x\":1685584938414,\"y\":9.88},{\"x\":1685585238420,\"y\":9.98},{\"x\":1685585538423,\"y\":9.89},{\"x\":1685585838440,\"y\":9.89},{\"x\":1685586138428,\"y\":9.84},{\"x\":1685586438423,\"y\":9.84},{\"x\":1685586738424,\"y\":9.79},{\"x\":1685587038648,\"y\":9.93},{\"x\":1685587338431,\"y\":9.79},{\"x\":1685587638450,\"y\":9.79},{\"x\":1685587938475,\"y\":9.88},{\"x\":1685588238559,\"y\":9.88},{\"x\":1685588538441,\"y\":9.88},{\"x\":1685588838584,\"y\":9.67},{\"x\":1685589138428,\"y\":9.58},{\"x\":1685589438536,\"y\":9.67},{\"x\":1685589738473,\"y\":9.67},{\"x\":1685590038433,\"y\":9.67},{\"x\":1685590338478,\"y\":9.67},{\"x\":1685590638761,\"y\":9.42},{\"x\":1685590938425,\"y\":9.42},{\"x\":1685591238442,\"y\":9.42},{\"x\":1685591538439,\"y\":9.31},{\"x\":1685591838504,\"y\":9.31},{\"x\":1685592138436,\"y\":9.34},{\"x\":1685592438435,\"y\":9.34},{\"x\":1685592738487,\"y\":9.38},{\"x\":1685593038723,\"y\":9.38},{\"x\":1685593338444,\"y\":9.38},{\"x\":1685593638424,\"y\":9.34},{\"x\":1685593938457,\"y\":9.34},{\"x\":1685594238655,\"y\":9.86},{\"x\":1685594538452,\"y\":9.86},{\"x\":1685594838449,\"y\":9.86},{\"x\":1685595138489,\"y\":9.84},{\"x\":1685595438547,\"y\":9.84},{\"x\":1685595738460,\"y\":9.78},{\"x\":1685596038492,\"y\":9.85},{\"x\":1685596338571,\"y\":9.85},{\"x\":1685596638545,\"y\":9.85},{\"x\":1685596938457,\"y\":9.76},{\"x\":1685597238477,\"y\":9.85},{\"x\":1685597538472,\"y\":9.8},{\"x\":1685597838494,\"y\":9.83},{\"x\":1685598138501,\"y\":9.86},{\"x\":1685598438509,\"y\":10.14},{\"x\":1685598738535,\"y\":10.14},{\"x\":1685599038819,\"y\":10.14},{\"x\":1685599338484,\"y\":10.07},{\"x\":1685599638529,\"y\":10.07},{\"x\":1685599938480,\"y\":10.14},{\"x\":1685600238698,\"y\":10.23},{\"x\":1685600538482,\"y\":10.1},{\"x\":1685600838527,\"y\":10.43},{\"x\":1685601138487,\"y\":10.43},{\"x\":1685601438740,\"y\":10.43},{\"x\":1685601738547,\"y\":10.45},{\"x\":1685602038498,\"y\":10.36},{\"x\":1685602338488,\"y\":10.43},{\"x\":1685602638682,\"y\":10.46},{\"x\":1685602938581,\"y\":10.46},{\"x\":1685603238508,\"y\":10.47},{\"x\":1685603538503,\"y\":10.47},{\"x\":1685603838572,\"y\":10.47},{\"x\":1685604138511,\"y\":10.47},{\"x\":1685604438579,\"y\":10.6},{\"x\":1685604738515,\"y\":10.51},{\"x\":1685605038560,\"y\":10.55},{\"x\":1685605338556,\"y\":11.07},{\"x\":1685605638563,\"y\":11.07},{\"x\":1685605938519,\"y\":11.07},{\"x\":1685606238594,\"y\":11.05},{\"x\":1685606538513,\"y\":11.12},{\"x\":1685606838518,\"y\":11.15},{\"x\":1685607138518,\"y\":11.15},{\"x\":1685607438879,\"y\":11.19},{\"x\":1685607738543,\"y\":11.19},{\"x\":1685608038530,\"y\":11.42},{\"x\":1685608338527,\"y\":11.33},{\"x\":1685608638628,\"y\":11.33},{\"x\":1685608938572,\"y\":11.54},{\"x\":1685609238526,\"y\":11.54},{\"x\":1685609538568,\"y\":11.54},{\"x\":1685609838674,\"y\":11.73},{\"x\":1685610138530,\"y\":11.54},{\"x\":1685610438525,\"y\":11.73},{\"x\":1685610738604,\"y\":11.81},{\"x\":1685611038666,\"y\":11.81},{\"x\":1685611338564,\"y\":11.72},{\"x\":1685611638584,\"y\":12.09},{\"x\":1685611938537,\"y\":12.09},{\"x\":1685612238596,\"y\":12.09},{\"x\":1685612538547,\"y\":12.11},{\"x\":1685612838540,\"y\":12.11},{\"x\":1685613138539,\"y\":12.07},{\"x\":1685613438767,\"y\":12.53},{\"x\":1685613738611,\"y\":12.64},{\"x\":1685614038551,\"y\":12.51},{\"x\":1685614338551,\"y\":12.64},{\"x\":1685614638741,\"y\":12.64},{\"x\":1685614938559,\"y\":12.64},{\"x\":1685615238587,\"y\":13.05},{\"x\":1685615538593,\"y\":13.15},{\"x\":1685615838836,\"y\":13.07},{\"x\":1685616138551,\"y\":13.07},{\"x\":1685616438549,\"y\":13.17},{\"x\":1685616738558,\"y\":13.26},{\"x\":1685617038760,\"y\":13.26},{\"x\":1685617338558,\"y\":13.36},{\"x\":1685617638534,\"y\":13.36},{\"x\":1685617938635,\"y\":13.73},{\"x\":1685618238566,\"y\":13.71},{\"x\":1685618538558,\"y\":13.71},{\"x\":1685618838593,\"y\":13.75},{\"x\":1685619138565,\"y\":13.75},{\"x\":1685619438673,\"y\":13.93},{\"x\":1685619738585,\"y\":13.93}","],\"labels\":",[146,25091,25092],{},"\"\"","\",\"payloadType\":\"json\",\"x\":150,\"y\":180,\"wires\":[[\"3eb08d4843164efc\"]]},{\"id\":\"41e847ff22249c0e\",\"type\":\"ui_group\",\"name\":\"Temperature\",\"tab\":\"1d985094b1a81b0c\",\"order\":5,\"disp\":true,\"width\":\"24\",\"collapse\":false,\"className\":\"\"},{\"id\":\"1d985094b1a81b0c\",\"type\":\"ui_tab\",\"name\":\"Wide View\",\"icon\":\"dashboard\",\"disabled\":false,\"hidden\":false}",[996,25095,25097],{"id":25096},"_3-using-sliders-and-persisting-the-current-value","3. Using sliders and persisting the current value",[15,25099,25100],{},"Sliders are a really useful user-interface element. Where you need to control the speed of a piece of machinery, having the ability to use a slider rather than manually typing in a value is a much better fit for shop-floor HMIs.",[15,25102,25103],{},"When using sliders in your dashboards, it's important to consider how you will persist the state of the slider. If you don't persist the state, you will find that a redeploy of your dashboard will set the slider back to the default value. That would also change the speed of your machine.",[15,25105,25106],{},[392,25107],{"alt":25108,"src":25109,"title":25108},"An example of a slider in a HMI","\u002Fblog\u002F2023\u002F06\u002Fimages\u002Fslider-ui.gif",[15,25111,25112],{},"To retain the current value of the slider we can use Node-RED's context. Each time the slider value is updated, we store the value in context. Each time we deploy the flow, we can now load the value back from context.",[15,25114,25115],{},"If you'd like to view this slider and the flow which makes it work on your own Node-RED, you can import the flow below.",[15,25117,3769,25118,3830],{},[146,25119,25120,25121,25123,25124,25126,25127,25130,25131,25134,25135,25137,25138,25140],{},"{\"id\":\"05340e7a133098a6\",\"type\":\"ui_slider\",\"z\":\"0e6be5088cecccc1\",\"name\":\"\",\"label\":\"",[17483,25122],{"value":382},"\",\"tooltip\":\"\",\"group\":\"41e847ff22249c0e\",\"order\":1,\"width\":0,\"height\":0,\"passthru\":true,\"outs\":\"all\",\"topic\":\"topic\",\"topicType\":\"msg\",\"min\":0,\"max\":\"20\",\"step\":1,\"className\":\"\",\"x\":230,\"y\":300,\"wires\":[[\"945db61c94fc0704\",\"ad7819da2cfbcd4e\"]]},{\"id\":\"849d76d146ff8c12\",\"type\":\"inject\",\"z\":\"0e6be5088cecccc1\",\"name\":\"Inject on deploy\",\"props\":",[146,25125],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"x\":160,\"y\":240,\"wires\":[[\"aeedb3c60965c1af\"]]},{\"id\":\"945db61c94fc0704\",\"type\":\"change\",\"z\":\"0e6be5088cecccc1\",\"name\":\"Set global-slider-value = msg.payload\",\"rules\":",[146,25128,25129],{},"{\"t\":\"set\",\"p\":\"slider-value\",\"pt\":\"global\",\"to\":\"payload\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":450,\"y\":300,\"wires\":[[]]},{\"id\":\"aeedb3c60965c1af\",\"type\":\"change\",\"z\":\"0e6be5088cecccc1\",\"name\":\"Set msg.payload = global.slider-value\",\"rules\":",[146,25132,25133],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"slider-value\",\"tot\":\"global\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":410,\"y\":240,\"wires\":[[\"05340e7a133098a6\"]]},{\"id\":\"ad7819da2cfbcd4e\",\"type\":\"ui_text\",\"z\":\"0e6be5088cecccc1\",\"group\":\"41e847ff22249c0e\",\"order\":2,\"width\":0,\"height\":0,\"name\":\"\",\"label\":\"\",\"format\":\"The current value is ",[17483,25136],{"value":382}," meters per minute\",\"layout\":\"row-spread\",\"className\":\"\",\"style\":false,\"font\":\"\",\"fontSize\":16,\"color\":\"#000000\",\"x\":350,\"y\":340,\"wires\":",[146,25139],{},"},{\"id\":\"41e847ff22249c0e\",\"type\":\"ui_group\",\"name\":\"Meters per Minute\",\"tab\":\"466e33abb95e4dd4\",\"order\":5,\"disp\":true,\"width\":\"10\",\"collapse\":false,\"className\":\"\"},{\"id\":\"466e33abb95e4dd4\",\"type\":\"ui_tab\",\"name\":\"Sliders\",\"icon\":\"dashboard\",\"order\":3,\"disabled\":false,\"hidden\":false}",[15,25142,25143,25144,25148],{},"We hope you found these tips useful, if you'd like to suggest some of your own tips which you think we should share in our future blog posts please ",[307,25145,25147],{"href":25146},"mailto:contact@flowfuse.com","get in touch",". You can also read some of our previous Node-RED tips using the links below.",[15,25150,25151,25155,25157,25161,25163,25167,25169,25173,25175,25179,25181],{},[307,25152,25154],{"href":25153},"\u002Fblog\u002F2023\u002F04\u002F3-quick-node-red-tips-6\u002F","Node-RED Tips - Subflows, Link Nodes, and the Range Node",[1290,25156],{},[307,25158,25160],{"href":25159},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-5\u002F","Node-RED Tips - Importing, Exporting, and Grouping Flows",[1290,25162],{},[307,25164,25166],{"href":25165},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-4\u002F","Node-RED Tips - Smooth, Catch, and Maths",[1290,25168],{},[307,25170,25172],{"href":25171},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-3\u002F","Node-RED Tips - Exec, Filter, and Debug",[1290,25174],{},[307,25176,25178],{"href":25177},"\u002Fblog\u002F2023\u002F02\u002F3-quick-node-red-tips-2\u002F","Node-RED Tips - Deploying, Debugging, and Delaying",[1290,25180],{},[307,25182,25184],{"href":25183},"\u002Fblog\u002F2023\u002F02\u002F3-quick-node-red-tips-1\u002F","Node-RED Tips - Wiring Shortcuts",{"title":50,"searchDepth":250,"depth":250,"links":25186},[25187,25188,25189],{"id":24960,"depth":221,"text":24961},{"id":25052,"depth":221,"text":25053},{"id":25096,"depth":221,"text":25097},"2023-06-01","Learn three practical tips for improving your Node-RED Dashboard workflow, such as creating responsive layouts, adding multiple data series to charts, and persisting slider values.","\u002Fimages\u002Fblog\u002Fnr-quicktips.jpg",{"excerpt":25194},{"type":12,"value":25195},[25196],[15,25197,24948],{},"\u002Fblog\u002F2023\u002F06\u002F3-quick-node-red-tips-7",{"title":24942,"description":25191},{"loc":25198},"blog\u002F2023\u002F06\u002F3-quick-node-red-tips-7","Save yourself time when working with Node-RED Dashboards with these three tips.",[9832,966,6563,11070,9324],"h5GmOwlW7UuahwdTPwHCe-u7SZQDJw8zjkVdPQFXuZ4",{"id":25206,"title":25207,"authors":25208,"body":25209,"cta":3,"date":25513,"description":25514,"extension":946,"image":25515,"lastUpdated":3,"meta":25516,"navigation":253,"path":25524,"seo":25525,"sitemap":25526,"stem":25527,"subtitle":25528,"tags":25529,"tldr":3,"video":3,"__hash__":25530},"blog\u002Fblog\u002F2023\u002F05\u002Fpersisting-chart-data-in-node-red.md","Persisting chart data in Node-RED Dashboard 1",[7451],{"type":12,"value":25210,"toc":25503},[25211,25217,25220,25242,25246,25249,25252,25256,25259,25262,25277,25281,25284,25287,25291,25294,25297,25313,25316,25319,25337,25340,25346,25349,25353,25356,25359,25379,25382,25406,25409,25413,25416,25441,25444,25450,25457,25460,25466,25495,25498,25500],[15,25212,25213,25214,167],{},"Node-RED makes it easy to create HMI (Human Machine Interfaces) using ",[307,25215,24956],{"href":24954,"rel":25216},[311],[15,25218,25219],{},"One of the most useful features of Dashboard 1 is the ability to store historic data passed to a chart within the chart node itself. This makes your flows far simpler than would be the case if you needed to send the entire data set to the chart for each update.",[5168,25221,25224,25225,25224,25235],{"className":25222},[25223],"blog-update-notes","\n    ",[15,25226,25227,25230,25231,167],{},[338,25228,25229],{},"UPDATE:"," Since this article was published, Node-RED Dashboard (1.0) has been ",[307,25232,25234],{"href":25233,"target":24745},"https:\u002F\u002Fdiscourse.nodered.org\u002Ft\u002Fannouncement-node-red-dashboard-v1-deprecation-notice\u002F89006","deprecated",[15,25236,25237,25238,25241],{},"Instead, it is recommended to use ",[307,25239,25240],{"href":13557},"FlowFuse Dashboard (Dashboard 2.0)"," which is a more modern and feature-rich dashboard solution for Node-RED.",[996,25243,25245],{"id":25244},"the-importance-of-persisting-chart-data","The Importance of Persisting Chart Data",[15,25247,25248],{},"Storing the data in the chart node is fine to show prototypes of HMIs, but where it's vital the correct data is always shown we are going to need a backup. Data can easily be lost when you move your flow to a new device, restart your instance, or simply when upgrading Node-RED.",[15,25250,25251],{},"How can we store our chart data so we can be confident it will be there each time a user views your HMI?",[996,25253,25255],{"id":25254},"example-dashboard","Example Dashboard",[15,25257,25258],{},"In this example, we are passing in a random number between one and 10 each second. With each new value received the chart updates and as mentioned about, the values are also stored in the chart node.",[15,25260,25261],{},"If you'd like to see and edit the flows I've created, you can copy and paste the JSON below into your Node-RED import feature.",[15,25263,6389,25264,25276],{},[146,25265,25266,25267,25269,25270,25272,25273,25275],{},"{\"id\":\"c6825b1001216b89\",\"type\":\"inject\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"props\":",[146,25268],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":110,\"y\":220,\"wires\":[[\"6b609d978540fb2a\"]]},{\"id\":\"6b609d978540fb2a\",\"type\":\"Number\",\"z\":\"668c56888fd0f960\",\"name\":\"Random Number\",\"minimum\":\"1\",\"maximum\":\"10\",\"roundTo\":\"0\",\"Floor\":true,\"x\":270,\"y\":220,\"wires\":[[\"794846db6dc8cef8\"]]},{\"id\":\"794846db6dc8cef8\",\"type\":\"ui_chart\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"group\":\"af1535b39b74f94a\",\"order\":0,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"3600\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25271,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":430,\"y\":220,\"wires\":[[\"ad53848ee4b0d91e\"]]},{\"id\":\"ad53848ee4b0d91e\",\"type\":\"debug\",\"z\":\"668c56888fd0f960\",\"name\":\"debug\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":550,\"y\":220,\"wires\":",[146,25274],{},"},{\"id\":\"af1535b39b74f94a\",\"type\":\"ui_group\",\"name\":\"Example\",\"tab\":\"14f1442eb7525190\",\"order\":1,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"14f1442eb7525190\",\"type\":\"ui_tab\",\"name\":\"Home\",\"icon\":\"dashboard\",\"disabled\":false,\"hidden\":false}","\n{% endrenderFlow %}\nx",[996,25278,25280],{"id":25279},"how-can-we-store-and-recall-the-chart-data","How can we store and recall the chart data?",[15,25282,25283],{},"The chart node has a really useful feature which allows us to access all the data currently shown in the chart. Each time the chart receives new data, it's added to the existing values then the whole data set is sent out the outbound port of the chart node.",[15,25285,25286],{},"Now that we have a way to easily access the chart data in a single payload, we next need to store that data somewhere safer. I'm going to explain 3 potential solutions, which I use on a regular basis.",[4987,25288,25290],{"id":25289},"_1-node-red-file-out-and-file-in-nodes","1. Node-RED file-out and file-in nodes",[15,25292,25293],{},"Node-RED can read and write data to a local filesystem. Being that we already have the chart data in a single payload, we just need to write that payload to a file for later use, which we can do using the file-out node.",[15,25295,25296],{},"This example flow shows how to use the file-out node to write the chart data to your local filesystem.",[15,25298,6389,25299,3830],{},[146,25300,25301,25302,25304,25305,25307,25308,25310,25311,25275],{},"{\"id\":\"ead9df683d29fb8a\",\"type\":\"inject\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"props\":",[146,25303],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":110,\"y\":560,\"wires\":[[\"ef5359b8bd3f78b3\"]]},{\"id\":\"ef5359b8bd3f78b3\",\"type\":\"Number\",\"z\":\"668c56888fd0f960\",\"name\":\"Random Number\",\"minimum\":\"1\",\"maximum\":\"10\",\"roundTo\":\"0\",\"Floor\":true,\"x\":270,\"y\":560,\"wires\":[[\"69ad440cd8d1ce30\"]]},{\"id\":\"69ad440cd8d1ce30\",\"type\":\"ui_chart\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"group\":\"af1535b39b74f94a\",\"order\":0,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"3600\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25306,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":430,\"y\":560,\"wires\":[[\"e4e7758028477505\",\"d9d6a2e34767f568\"]]},{\"id\":\"e4e7758028477505\",\"type\":\"debug\",\"z\":\"668c56888fd0f960\",\"name\":\"debug\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":550,\"y\":560,\"wires\":",[146,25309],{},"},{\"id\":\"d9d6a2e34767f568\",\"type\":\"json\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"property\":\"payload\",\"action\":\"\",\"pretty\":false,\"x\":550,\"y\":600,\"wires\":[[\"b5b020fb17f615df\"]]},{\"id\":\"b5b020fb17f615df\",\"type\":\"file\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"filename\":\"example.json\",\"filenameType\":\"str\",\"appendNewline\":true,\"createDir\":false,\"overwriteFile\":\"true\",\"encoding\":\"none\",\"x\":690,\"y\":600,\"wires\":[[\"a6d9eab41d4dcf97\"]]},{\"id\":\"a6d9eab41d4dcf97\",\"type\":\"debug\",\"z\":\"668c56888fd0f960\",\"name\":\"debug 92\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":840,\"y\":600,\"wires\":",[146,25312],{},[15,25314,25315],{},"As the chart node sends the full data set each time new data is added, we overwrite the content of the file rather than append the new values.",[15,25317,25318],{},"The next step is to pull the data back from the filesystem to your Node-RED instance. Node-RED makes this very easy using the file-in node.",[15,25320,6389,25321,3830],{},[146,25322,25301,25323,25304,25325,25307,25327,25310,25329,25331,25332,25334,25335,25275],{},[146,25324],{},[146,25326,20525],{},[146,25328],{},[146,25330],{},"},{\"id\":\"e9cb9350f1aaeb38\",\"type\":\"inject\",\"z\":\"668c56888fd0f960\",\"name\":\"import data\",\"props\":",[146,25333],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":110,\"y\":660,\"wires\":[[\"600f014947f73d8f\"]]},{\"id\":\"600f014947f73d8f\",\"type\":\"file in\",\"z\":\"668c56888fd0f960\",\"name\":\"\",\"filename\":\"example.json\",\"filenameType\":\"str\",\"format\":\"utf8\",\"chunk\":false,\"sendError\":false,\"encoding\":\"none\",\"allProps\":false,\"x\":270,\"y\":660,\"wires\":[[\"972118c0e114f47b\",\"69ad440cd8d1ce30\"]]},{\"id\":\"972118c0e114f47b\",\"type\":\"debug\",\"z\":\"668c56888fd0f960\",\"name\":\"debug 93\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":420,\"y\":660,\"wires\":",[146,25336],{},[15,25338,25339],{},"When you press the 'import data' trigger node, the data is loaded in from the filesystem and shown in the chart. You may want to automate that task to run each you deploy your Node-RED instance.",[15,25341,25342],{},[392,25343],{"alt":25344,"src":25345,"title":25344},"Import data on deploy","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Finject-on-deploy.png",[15,25347,25348],{},"Bear in mind that your data is stored in your filesystem, if your storage drive fails you will lose your data, you might want to consider taking backups and storing elsewhere for emergencies.",[4987,25350,25352],{"id":25351},"_2-flowfuses-persistent-context","2. FlowFuse's persistent context",[15,25354,25355],{},"FlowFuse Cloud and premium self hosted version provides persistent context storage as part of its Node-RED instances. This allows you to create, read, update, and delete data as needed, even if you have restarted a Node-RED instance.",[15,25357,25358],{},"This flow shows chart data being sent to persistent context so we can access it later. The process is very similar to using the file-out and file-in nodes.",[15,25360,6389,25361,3830],{},[146,25362,25363,25364,25366,25367,25369,25370,25372,25373,25376,25377,25275],{},"{\"id\":\"c6825b1001216b89\",\"type\":\"inject\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"props\":",[146,25365],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":170,\"y\":100,\"wires\":[[\"6b609d978540fb2a\"]]},{\"id\":\"794846db6dc8cef8\",\"type\":\"ui_chart\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"group\":\"af1535b39b74f94a\",\"order\":0,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"3600\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25368,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":490,\"y\":100,\"wires\":[[\"ad53848ee4b0d91e\",\"938c7d878545e623\"]]},{\"id\":\"ad53848ee4b0d91e\",\"type\":\"debug\",\"z\":\"4767c2f7095bee53\",\"name\":\"debug\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":610,\"y\":100,\"wires\":",[146,25371],{},"},{\"id\":\"6b609d978540fb2a\",\"type\":\"Number\",\"z\":\"4767c2f7095bee53\",\"name\":\"Random Number\",\"minimum\":\"1\",\"maximum\":\"10\",\"roundTo\":\"0\",\"Floor\":true,\"x\":330,\"y\":100,\"wires\":[[\"794846db6dc8cef8\"]]},{\"id\":\"938c7d878545e623\",\"type\":\"change\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"rules\":",[146,25374,25375],{},"{\"t\":\"set\",\"p\":\"#:(persistent)::chart-data\",\"pt\":\"global\",\"to\":\"payload\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":660,\"y\":140,\"wires\":[[\"3792cc96a748e75a\"]]},{\"id\":\"3792cc96a748e75a\",\"type\":\"debug\",\"z\":\"4767c2f7095bee53\",\"name\":\"debug 1\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":840,\"y\":140,\"wires\":",[146,25378],{},[15,25380,25381],{},"We now need to have a method to load the data back into our chart. We will again use a manual 'import data' trigger to load the full set of data from the persistent context, and then push it back into the chart.",[15,25383,6389,25384,3830],{},[146,25385,25363,25386,25366,25388,25369,25390,25372,25392,25376,25394,25396,25397,25399,25400,25403,25404,25275],{},[146,25387],{},[146,25389,20525],{},[146,25391],{},[146,25393,25375],{},[146,25395],{},"},{\"id\":\"3379276c77b4691c\",\"type\":\"inject\",\"z\":\"4767c2f7095bee53\",\"name\":\"import data\",\"props\":",[146,25398],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"x\":150,\"y\":180,\"wires\":[[\"ddd1ef41321fb4a6\"]]},{\"id\":\"ddd1ef41321fb4a6\",\"type\":\"change\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"rules\":",[146,25401,25402],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"#:(persistent)::chart-data\",\"tot\":\"global\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":320,\"y\":180,\"wires\":[[\"794846db6dc8cef8\",\"fa65b958e34bc971\"]]},{\"id\":\"fa65b958e34bc971\",\"type\":\"debug\",\"z\":\"4767c2f7095bee53\",\"name\":\"debug 2\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"false\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":480,\"y\":180,\"wires\":",[146,25405],{},[15,25407,25408],{},"You may have noticed that when we are pushing duplicate data into the chart it automatically checks to see if the data is already stored. If the data points are already in the chart the new data is disregard. This saves us writing an extra section of the flow to delete the data before we load it in.",[4987,25410,25412],{"id":25411},"_3-expose-the-data-via-an-api-then-manually-import-it","3. Expose the data via an API then manually import it",[15,25414,25415],{},"In some cases you may want to copy your chart data to somewhere outside of your Node-RED instances. You can do this by creating a simple API which allows an outside system to request the chart data. You can also manually go to the URL of the API in a web browser to get your data. This example flow allows a user or system to access the chart data via a URL.",[15,25417,6389,25418,3830],{},[146,25419,25420,25421,25423,25424,25426,25427,25429,25430,25433,25434,25436,25437,25440],{},"{\"id\":\"6523e86042252710\",\"type\":\"inject\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"props\":",[146,25422],{},",\"repeat\":\"1\",\"crontab\":\"\",\"once\":false,\"onceDelay\":0.1,\"topic\":\"\",\"x\":150,\"y\":540,\"wires\":[[\"d51aba5f9a808592\"]]},{\"id\":\"427c60f2c4f523b7\",\"type\":\"ui_chart\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"group\":\"af1535b39b74f94a\",\"order\":0,\"width\":0,\"height\":0,\"label\":\"chart\",\"chartType\":\"line\",\"legend\":\"false\",\"xformat\":\"HH:mm:ss\",\"interpolate\":\"linear\",\"nodata\":\"\",\"dot\":false,\"ymin\":\"\",\"ymax\":\"\",\"removeOlder\":1,\"removeOlderPoints\":\"\",\"removeOlderUnit\":\"3600\",\"cutout\":0,\"useOneColor\":false,\"useUTC\":false,\"colors\":",[146,25425,20525],{},",\"outputs\":1,\"useDifferentColor\":false,\"className\":\"\",\"x\":470,\"y\":540,\"wires\":[[\"ba84cd1820120139\",\"d9087436f4769691\"]]},{\"id\":\"ba84cd1820120139\",\"type\":\"debug\",\"z\":\"4767c2f7095bee53\",\"name\":\"debug\",\"active\":true,\"tosidebar\":true,\"console\":false,\"tostatus\":false,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":590,\"y\":540,\"wires\":",[146,25428],{},"},{\"id\":\"d51aba5f9a808592\",\"type\":\"Number\",\"z\":\"4767c2f7095bee53\",\"name\":\"Random Number\",\"minimum\":\"1\",\"maximum\":\"10\",\"roundTo\":\"0\",\"Floor\":true,\"x\":310,\"y\":540,\"wires\":[[\"427c60f2c4f523b7\"]]},{\"id\":\"d9087436f4769691\",\"type\":\"change\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"rules\":",[146,25431,25432],{},"{\"t\":\"set\",\"p\":\"chart-data\",\"pt\":\"flow\",\"to\":\"payload\",\"tot\":\"msg\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":630,\"y\":580,\"wires\":[[]]},{\"id\":\"4dd97b29430ad2ba\",\"type\":\"http in\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"url\":\"\u002Fdata\",\"method\":\"get\",\"upload\":false,\"swaggerDoc\":\"\",\"x\":200,\"y\":640,\"wires\":[[\"b4f0eb085cc91834\"]]},{\"id\":\"7ca49e5465699355\",\"type\":\"http response\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"statusCode\":\"\",\"headers\":{},\"x\":670,\"y\":640,\"wires\":",[146,25435],{},"},{\"id\":\"b4f0eb085cc91834\",\"type\":\"change\",\"z\":\"4767c2f7095bee53\",\"name\":\"Get the chart data from flow.chart-data\",\"rules\":",[146,25438,25439],{},"{\"t\":\"set\",\"p\":\"payload\",\"pt\":\"msg\",\"to\":\"chart-data\",\"tot\":\"flow\"}",",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":440,\"y\":640,\"wires\":[[\"7ca49e5465699355\"]]},{\"id\":\"af1535b39b74f94a\",\"type\":\"ui_group\",\"name\":\"Example\",\"tab\":\"14f1442eb7525190\",\"order\":1,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"14f1442eb7525190\",\"type\":\"ui_tab\",\"name\":\"Home\",\"icon\":\"dashboard\",\"disabled\":false,\"hidden\":false}",[15,25442,25443],{},"We can now access the data by simply visiting the URL of the API.",[15,25445,25446],{},[392,25447],{"alt":25448,"src":25449,"title":25448},"The chart data accessed via the API in a web browser","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fdata-in-browser.png",[15,25451,25452,25453,167],{},"Bear in mind that you should secure the API as appropriate for the data. If you don't put security around the API, anyone on the same network as your Node-RED instance can access your chart data. Potentially that could give access to our data to the whole internet, so where needed take steps to keep your data safe. You can read more about securing Node-RED in our ",[307,25454,25456],{"href":25455},"\u002Fblog\u002F2023\u002F04\u002Fsecuring-node-red-in-production\u002F","blog post here",[15,25458,25459],{},"We now need to get that data back into a Node-RED instance. We can do that by editing a node and pasting in the data we got from the API. The flow below shows where you can paste in your data, you will then need to deploy and manually trigger 'import data'.",[15,25461,25462],{},[392,25463],{"alt":25464,"src":25465,"title":25464},"Inject the data in JSON format","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Finject-the-json.png",[15,25467,6389,25468,3830],{},[146,25469,25420,25470,25423,25472,25426,25474,25429,25476,25478,25479,25436,25481,25483,25484,25486,25487,25494],{},[146,25471],{},[146,25473,20525],{},[146,25475],{},[146,25477,25432],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":630,\"y\":580,\"wires\":[[]]},{\"id\":\"4dd97b29430ad2ba\",\"type\":\"http in\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"url\":\"\u002Fdata\",\"method\":\"get\",\"upload\":false,\"swaggerDoc\":\"\",\"x\":200,\"y\":660,\"wires\":[[\"b4f0eb085cc91834\"]]},{\"id\":\"7ca49e5465699355\",\"type\":\"http response\",\"z\":\"4767c2f7095bee53\",\"name\":\"\",\"statusCode\":\"\",\"headers\":{},\"x\":670,\"y\":660,\"wires\":",[146,25480],{},[146,25482,25439],{},",\"action\":\"\",\"property\":\"\",\"from\":\"\",\"to\":\"\",\"reg\":false,\"x\":440,\"y\":660,\"wires\":[[\"7ca49e5465699355\"]]},{\"id\":\"d13d16b33ec638b2\",\"type\":\"inject\",\"z\":\"4767c2f7095bee53\",\"name\":\"import data\",\"props\":",[146,25485,9126],{},",\"repeat\":\"\",\"crontab\":\"\",\"once\":true,\"onceDelay\":0.1,\"topic\":\"\",\"payload\":\"",[146,25488,25081,25489,25491,25492,1596],{},[146,25490,25092],{},",\"data\":[[{\"x\":1684841975036,\"y\":8},{\"x\":1684841976037,\"y\":6},{\"x\":1684841977038,\"y\":7},{\"x\":1684841978037,\"y\":7}]],\"labels\":",[146,25493,25092],{},"\",\"payloadType\":\"json\",\"x\":170,\"y\":600,\"wires\":[[\"427c60f2c4f523b7\"]]},{\"id\":\"af1535b39b74f94a\",\"type\":\"ui_group\",\"name\":\"Example\",\"tab\":\"14f1442eb7525190\",\"order\":1,\"disp\":true,\"width\":\"6\",\"collapse\":false,\"className\":\"\"},{\"id\":\"14f1442eb7525190\",\"type\":\"ui_tab\",\"name\":\"Home\",\"icon\":\"dashboard\",\"disabled\":false,\"hidden\":false}",[15,25496,25497],{},"Each of these solutions has strengths and weaknesses but there are many other ways to persist your chart data. You should consider which approach is the best fit for your needs. To be as confident as possible that you data is safe, you may decide to push your data to dedicate external storage such as a database or backup solution.",[996,25499,6518],{"id":6517},[15,25501,25502],{},"Node-RED Dashboard 1 allows you to easily make informative HMIs, but it's important to make sure the chart data you are showing is stored safely. The approaches we have discussed above should give you a good start in ensuring your charts are populated with the correct data, even if your Node-RED instance crashes or you need to move it to a new hosting location.",{"title":50,"searchDepth":250,"depth":250,"links":25504},[25505,25506,25507,25512],{"id":25244,"depth":221,"text":25245},{"id":25254,"depth":221,"text":25255},{"id":25279,"depth":221,"text":25280,"children":25508},[25509,25510,25511],{"id":25289,"depth":250,"text":25290},{"id":25351,"depth":250,"text":25352},{"id":25411,"depth":250,"text":25412},{"id":6517,"depth":221,"text":6518},"2023-05-25","Chart data in Node-RED can be stored directly in your flows but it's a good idea to also store data eleswhere. In this article we are looking at some easy ways to persist your historic chart data in Dashboard 1","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fpersisting-data-header.jpeg",{"excerpt":25517},{"type":12,"value":25518},[25519],[15,25520,25213,25521,167],{},[307,25522,24956],{"href":24954,"rel":25523},[311],"\u002Fblog\u002F2023\u002F05\u002Fpersisting-chart-data-in-node-red",{"title":25207,"description":25514},{"loc":25524},"blog\u002F2023\u002F05\u002Fpersisting-chart-data-in-node-red","Keep your historic chart data safe and available",[9832,6563,966,11070],"XOV8jlfg8Ym1N-NeSNj2So7YX36lMt0P73DX8r4OFxA",{"id":25532,"title":25533,"authors":25534,"body":25536,"cta":25744,"date":25747,"description":25748,"extension":946,"image":25749,"lastUpdated":948,"meta":25750,"navigation":253,"path":25756,"seo":25757,"sitemap":25758,"stem":25759,"subtitle":25760,"tags":25761,"tldr":25763,"video":3,"__hash__":25764},"blog\u002Fblog\u002F2023\u002F05\u002Fintegrating-modbus-with-node-red.md","Best Practices Integrating a Modbus Device With Node-RED (2026)",[25535],"andrew-lynch",{"type":12,"value":25537,"toc":25735},[25538,25541,25544,25547,25551,25554,25560,25563,25569,25572,25578,25582,25585,25591,25595,25598,25604,25607,25613,25616,25622,25625,25629,25642,25647,25650,25656,25659,25664,25667,25673,25680,25684,25687,25690,25703,25709,25715,25721,25723,25726,25732],[15,25539,25540],{},"The world of industrial automation is slow to adopt new technology. With legacy equipment already working and in place, paralyzing down-time costs, and fears of introducing instability into a plant, technology change has a cautious pace.",[15,25542,25543],{},"Node-RED provides a way to extend the capabilities of the simpler, proven technology, allowing connections with modern systems.  However, the same fundamentals that have made industrial equipment dependable, must be incorporated into your Node-RED architecture.  And, inversely, the same abstraction and simplicity that makes a low-code, Node-RED environment fast, and easy to work with, can also make it difficult to interface with a lower-level system.",[15,25545,25546],{},"Modbus is a widely adopted protocol for accessing data from existing legacy manufacturing equipment. Node-RED makes it very easy to connect to Modbus enabled equipment. However, there are some best practices we have developed to maintain system integrity when integrating Modbus devices with Node-RED:",[996,25548,25550],{"id":25549},"add-watchdogs-to-node-red-flows","Add Watchdogs to Node-RED Flows",[15,25552,25553],{},"To keep your automation system running with peace of mind and little human intervention, use a watchdog timer.  A watchdog timer is typically used to detect and recover from system malfunctions. In Node-RED a watchdog timer can be tied to a broadcast messaging system to get push alerts directly to cell phones, as well as to an auto-reset to try to get your Flows back online automatically.  A simple implementation of this is to just put two Trigger nodes in a loop, the first one to send an alert when it hasn’t seen any recent events, and the second to to reset the first one.",[15,25555,25556],{},[392,25557],{"alt":25558,"src":25559,"title":25558},"Example watchdog flow","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-3.png",[15,25561,25562],{},"Each of the trigger nodes are setup with similar parameters, only the delay values differ.",[15,25564,25565],{},[392,25566],{"alt":25567,"src":25568,"title":25567},"Trigger node configuration","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-14.png",[15,25570,25571],{},"Sending a payload of {\"connectorType\":\"TCP\"} to the Modbus Flex Connector is enough to reset the connection without needing to send all the other parameters.",[15,25573,25574],{},[392,25575],{"alt":25576,"src":25577,"title":25576},"Change node configuration","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-10.png",[996,25579,25581],{"id":25580},"choosing-a-safe-poll-rate","Choosing a Safe Poll Rate",[15,25583,25584],{},"Be careful when adding new traffic to an industrial network, which might not be able to handle the extra load.  Some networks have low-bandwidth, especially at large plants that might be using antiquated IP devices and long-distance, Wi-Fi bridges in electrically noisy environments.  Furthermore, some PLCs and other Modbus devices have limits as to how many other devices can connect to them, and a new connection might not work, or worse, bump off an old connection.  A PLC’s general operation is to poll its inputs every cycle and react accordingly, running logic and triggering outputs as quickly as possible. The general use-case for Node-RED with a PLC is to create an HMI or broader, SCADA system.  A poll rate of every second suffices for a simple HMI.  For dashboards published to a greater audience, rather than just the operator, poll rates of several minutes to hours might be adequate.  If this is a new integration, it’s better to start slow and make sure the current infrastructure can handle the extra load.",[15,25586,25587],{},[392,25588],{"alt":25589,"src":25590,"title":25589},"Modbus node configuration","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-7.png",[996,25592,25594],{"id":25593},"coilregister-grouping","Coil\u002FRegister Grouping",[15,25596,25597],{},"Modbus works more efficiently when it is reading and writing addresses as groups.  Creating banks of consecutively numbered coils or registers can help with this.  If Modbus is already in use, perhaps for an existing HMI, but for your Node-RED dashboard you just want a selection of these addresses and they are too scattered to be read all at once, pick a starting address numbered far higher than what you will use for any HMI work and create a new bank of coils or registers just for the Node-RED dashboard.  The PLC can handle this easily and it greatly simplifies the polling for your Node-RED dashboard.  On the Node-RED side of the connection, it’s a good idea to parse and to filter this data as soon as it enters the flow.  Below, the first node creates an appropriate message for each coil, with a topic name and a true\u002Ffalse payload.  This message is then filtered by the “block unless value changes” mode in the filter node and finally a switch (by topic) node separates out each message.  In this example, every Tag coming in from the PLC only triggers downstream nodes when there is a change and each Tag has its own output to connect a wire, and subsequent nodes to.",[15,25599,25600],{},[392,25601],{"alt":25602,"src":25603,"title":25602},"Using a switch node to separate the messages","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-5.png",[15,25605,25606],{},"The filter node blocks redundant data from triggering subsequent nodes.",[15,25608,25609],{},[392,25610],{"alt":25611,"src":25612,"title":25611},"Filter node configuration","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-12.png",[15,25614,25615],{},"The function node, “modbusMapArray,” creates messages that are much more user-friendly in the Node-RED environment.",[15,25617,25618],{},[392,25619],{"alt":25620,"src":25621,"title":25620},"Modbus function node","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-1.png",[15,25623,25624],{},"Quick tip: If your PLC environment has limits on the number of addresses allowed and you want to read a lot of coils, you can work with registers bitwise and stuff 16 coils into one register.",[996,25626,25628],{"id":25627},"reading-data-types","Reading Data Types",[15,25630,25631,25632,25635,25636,25641],{},"No matter what data type the PLC is sending over Modbus, it’s going to be sent using the 16-bit registers.  For example, to send 1234.5678 as a 32-bit Float (little-endian), the payload from the PLC will be a seemingly unhelpful array, ",[146,25633,25634],{},"21035,17562",".  I can simulate this with the ",[307,25637,25640],{"href":25638,"rel":25639},"https:\u002F\u002Fwww.automationdirect.com\u002Fadc\u002Foverview\u002Fcatalog\u002Fsoftware_products\u002Fprogrammable_controller_software\u002Fproductivity_suite_programming_software",[311],"Productivity Suite Programming Software"," from Automation Direct set to simulator mode.  Below, I have created a 32-bit float “Tag,” named “mySampleFloat32,” using modbus registers 40001 and 40002, and set the “Init Value” to 1234.5678.",[15,25643,25644],{},[392,25645],{"alt":25640,"src":25646,"title":25640},"\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-11.png",[15,25648,25649],{},"Node-RED is typically used at a much higher level, but luckily there is still a way to work with this low-level data.  Node-RED uses the Buffer Class to work with this type of data stream, but it’s a little tricky.  First the 16-bit registers have to be broken into 8-bit chunks, here we use msg.responseBuffer.buffer to retrieve each octet.  Once our buffer is properly filled, there are many built-in functions to reconstruct the data into the actual number you are looking for.",[15,25651,25652],{},[392,25653],{"alt":25654,"src":25655,"title":25654},"Using msg.responseBuffer.buffer to retrieve each octet","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-4.png",[15,25657,25658],{},"Notice below that to read these 2 registers at 400001 and 400002 I have set my Modbus-Read node to start at “Address” 0 and ready “Quantity” 2 registers.  Unfortunately, there are two different standards for writing Modbus addresses and my PLC uses the traditional convention (400001 to 465536) and this Modbus node uses the hexadecimal convention (4x0000 to 4xFFFF).",[15,25660,25661],{},[392,25662],{"alt":25589,"src":25663,"title":25589},"\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-8.png",[15,25665,25666],{},"In this example the byte order from msg.responseBuffer.buffer doesn’t quite match the data type, so we have to rebuild the buffer.",[15,25668,25669],{},[392,25670],{"alt":25671,"src":25672,"title":25671},"Mapping the data in a function node","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-13.png",[15,25674,25675,25676,167],{},"Amazingly after all that work we get the response: 1234.5677490234375.  This is the shortcoming of the 32-bit float data type.  Although it can handle a huge range of values, they aren’t very accurate.  For this reason, many times PLCs will send a number as an integer, say 12345678, and the documentation will prescribe 4 decimal place accuracy to bring the number back to 1234.5678.  Find out more ways to work with a Buffer at ",[307,25677,25678],{"href":25678,"rel":25679},"https:\u002F\u002Fnodejs.org\u002Fdist\u002Flatest-v10.x\u002Fdocs\u002Fapi\u002Fbuffer.html",[311],[996,25681,25683],{"id":25682},"general-architecture","General Architecture",[15,25685,25686],{},"Although you can off-load some of the higher-level logic from the PLC into Node-RED, it’s important to remember that Node-RED augments, but doesn’t create a replacement for a PLC’s IDE and the IEC 61131-3 suite of languages.  Make a conscious distinction between the type of work the PLC should handle and what you expect from Node-RED.  Any real-time responses to inputs should strictly be handled by the PLC.",[996,25688,22958],{"id":25689},"security",[15,25691,25692,25693,25698,25699],{},"Connecting Node-RED to your PLC also creates a larger attack surface for cyber threats.  Make sure that you follow the guidelines found on the Node-RED.org site at ",[307,25694,25697],{"href":25695,"rel":25696},"https:\u002F\u002Fnodered.org\u002Fdocs\u002Fuser-guide\u002Fruntime\u002Fsecuring-node-red",[311],"Securing Node-RED",".  Node-RED’s strength is its ability to make connections where they weren’t possible before, but this can be taken advantage of by a hacker.  For instance, maybe it’s tempting to make Node-RED a transparent gateway and make a RESTful API fully exposing a modbus-flex-write node.  This is amazingly easy and powerful with Node-RED, but anyone who can access your IP could send http:\u002F\u002F",[25700,25701,25702],"your-ip",{},":1880\u002Fcareful?value=true&fc=15&unitid=1&address=0&quantity=10 and remotely turn on and off whatever they wanted.",[15,25704,25705],{},[392,25706],{"alt":25707,"src":25708,"title":25707},"Example endpoint flow","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-6.png",[15,25710,25711,25712],{},"Instead, a better practice would be to more narrowly define what you want to accomplish and only allow Node-RED to do exactly that. In this case you might send http:\u002F\u002F",[25700,25713,25714],{},":1880\u002FhonkTheLunchHorn?honk=true",[15,25716,25717],{},[392,25718],{"alt":25719,"src":25720,"title":25719},"Locking down the endpoint","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-2.png",[996,25722,906],{"id":905},[15,25724,25725],{},"If not done right, there could be some hard lessons, so it’s best to monitor the processes to help track down bugs.  Add a log, keep your eyes out, and as a community let’s work to create stable systems.",[15,25727,25728],{},[392,25729],{"alt":25730,"src":25731,"title":25730},"Logging failures","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fintegrating-modbus-9.png",[15,25733,25734],{},"How readily upper management gives an “okay” to this new technology comes with how well it is implemented.  There will be some growing pains, but by the end, you will have supercharged your plant, bringing it into the 21st century.",{"title":50,"searchDepth":250,"depth":250,"links":25736},[25737,25738,25739,25740,25741,25742,25743],{"id":25549,"depth":221,"text":25550},{"id":25580,"depth":221,"text":25581},{"id":25593,"depth":221,"text":25594},{"id":25627,"depth":221,"text":25628},{"id":25682,"depth":221,"text":25683},{"id":25689,"depth":221,"text":22958},{"id":905,"depth":221,"text":906},{"type":941,"title":25745,"description":25746},"Connect Modbus Devices to the Cloud with FlowFuse","FlowFuse gives you a secure, managed platform for deploying Node-RED flows that connect your legacy Modbus equipment to modern systems, dashboards, and cloud services.","2023-05-16","Modbus is a widely adopted protocol for accessing data from existing legacy manufacturing equipment. Node-RED makes it very easy to connect to Modbus enabled equipment. However, there are some best practices we have developed to maintain system integrity when integrating Modbus devices with Node-RED","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fmodbus2.jpg",{"keywords":25751,"excerpt":25752},"modbus node-red, node-red modbus integration, node-red modbus tcp, modbus best practices, node-red industrial automation",{"type":12,"value":25753},[25754],[15,25755,25540],{},"\u002Fblog\u002F2023\u002F05\u002Fintegrating-modbus-with-node-red",{"title":25533,"description":25748},{"loc":25756},"blog\u002F2023\u002F05\u002Fintegrating-modbus-with-node-red","Integrate Modbus with Node-RED",[9832,6563,966,25762],"modbus","Integrating Modbus with Node-RED requires more than just connecting nodes industrial reliability demands watchdog timers for auto-recovery, conservative poll rates to avoid overloading legacy networks, grouped coil\u002Fregister addressing for efficiency, and correct handling of multi-register data types like 32-bit floats. This article walks through each best practice with example flows and node configurations.","77YhHHX8TKYaGPCWTj0LRN0T87yXH75wtlWMtlb58lM",{"id":25766,"title":25767,"authors":25768,"body":25769,"cta":3,"date":26075,"description":26076,"extension":946,"image":26077,"lastUpdated":8478,"meta":26078,"navigation":253,"path":26090,"seo":26091,"sitemap":26092,"stem":26095,"subtitle":26096,"tags":26097,"tldr":3,"video":3,"__hash__":26101},"blog\u002Fblog\u002F2023\u002F05\u002Fchatgpt-nodered-fcn-node.md","Chat GPT in Node-RED Function Nodes",[24736,5320],{"type":12,"value":25770,"toc":26070},[25771,25788,25792,25811,25823,25826,25829,25908,25914,25917,25967,25970,25975,25983,25987,25994,26031,26035,26041,26044,26064,26067],[15,25772,25773,25774,25779,25780,25784,25785,167],{},"Recently we ",[307,25775,25778],{"href":25776,"rel":25777},"https:\u002F\u002Fwww.linkedin.com\u002Fposts\u002Fflowforge_chatgpt-with-node-red-function-nodes-activity-7052725869684953088-2yOA?utm_source=share&utm_medium=member_desktop",[311],"posted a demo of ChatGPT integration in a Node-RED function node","\nonto our social media accounts. We have now ",[307,25781,25783],{"href":25782,"target":24745},"https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fnode-red-function-gpt","open-sourced"," this for all to play with, and ",[338,25786,25787],{},"welcome any and all contributions",[34,25789,25791],{"id":25790},"how-it-works-prompt-engineering","How it Works - Prompt Engineering",[15,25793,25794,25795,25800,25801,25806,25807,25810],{},"OpenAI make a collection of their ",[307,25796,25799],{"href":25797,"rel":25798},"https:\u002F\u002Fplatform.openai.com\u002Fdocs\u002Fmodels",[311],"Generative AI models"," available\nvia an API. We are wrapping OpenAI's ",[307,25802,25805],{"href":25803,"rel":25804},"https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fopenai",[311],"node.js module",", and in particular\nusing the ",[19,25808,25809],{},"openai.createChatCompletion()"," functionality. For this API, you provide a chat history, and ChatGPT will\nrespond with the next entry in that conversation.",[15,25812,25813,25814,1255,25817,736,25820,167],{},"In order to \"train\" ChatGPT for our use case of populating Node-RED function nodes, we first tried a collection of prompts, defining specific\nrequirements for the contents, e.g. ",[397,25815,25816],{},"\"Always write Javascript\"",[397,25818,25819],{},"\"Never include the wrapping function definition\"",[397,25821,25822],{},"\"Assume the input is always msg\"",[15,25824,25825],{},"It turns out though, that we were over-engineering it, we were not getting reliable results and ended up\nrealising that ChatGPT's existing knowledge of Node-RED was sufficient such that we could use that as a prompt:",[15,25827,25828],{},"Here's what we settled on:",[42,25830,25832],{"className":140,"code":25831,"language":142,"meta":50,"style":50},"messages: [\n    {role: \"system\", content: \"always respond with content for a Node-RED function node, and don't add any commentary, always use const or let instead of var. Always return msg, unless told otherwise.\"},\n    {role: \"user\", content: prompt}\n],\n",[19,25833,25834,25843,25877,25902],{"__ignoreMap":50},[146,25835,25836,25839,25841],{"class":148,"line":149},[146,25837,25838],{"class":2435},"messages",[146,25840,730],{"class":160},[146,25842,2422],{"class":156},[146,25844,25845,25848,25851,25853,25855,25858,25860,25862,25865,25867,25869,25872,25874],{"class":148,"line":185},[146,25846,25847],{"class":160},"    {",[146,25849,25850],{"class":1549},"role",[146,25852,730],{"class":160},[146,25854,1830],{"class":160},[146,25856,25857],{"class":1554},"system",[146,25859,727],{"class":160},[146,25861,276],{"class":160},[146,25863,25864],{"class":1549}," content",[146,25866,730],{"class":160},[146,25868,1830],{"class":160},[146,25870,25871],{"class":1554},"always respond with content for a Node-RED function node, and don't add any commentary, always use const or let instead of var. Always return msg, unless told otherwise.",[146,25873,727],{"class":160},[146,25875,25876],{"class":160},"},\n",[146,25878,25879,25881,25883,25885,25887,25889,25891,25893,25895,25897,25900],{"class":148,"line":221},[146,25880,25847],{"class":160},[146,25882,25850],{"class":1549},[146,25884,730],{"class":160},[146,25886,1830],{"class":160},[146,25888,15390],{"class":1554},[146,25890,727],{"class":160},[146,25892,276],{"class":160},[146,25894,25864],{"class":1549},[146,25896,730],{"class":160},[146,25898,25899],{"class":156}," prompt",[146,25901,754],{"class":160},[146,25903,25904,25906],{"class":148,"line":250},[146,25905,1536],{"class":156},[146,25907,736],{"class":160},[15,25909,25910,25911,25913],{},"Here we send a ",[19,25912,25857],{}," prompt in order to setup ChatGPT, and then follow that immediately with whatever the user has typed.\nFrom our (limited) testing, this has given us fairly reliable results.",[15,25915,25916],{},"Breaking this prompt down:",[100,25918,25919,25937,25945,25953],{},[103,25920,25921,25926,25927,25930,25931,25933,25934,25936],{},[397,25922,25923],{},[338,25924,25925],{},"\"Always respond with content for a Node-RED function node\"",": Ensured no surrounding ",[19,25928,25929],{},"function () {}"," definition and set expectations that the function would deal with a ",[19,25932,260],{}," and likely ",[19,25935,382],{}," object.",[103,25938,25939,25944],{},[397,25940,25941],{},[338,25942,25943],{},"\"Don't add any commentary\"",": ChatGPT likes to, well, chat. It would always return raw text justifying decisions, etc. Here, we just wanted the code.",[103,25946,25947,25952],{},[397,25948,25949],{},[338,25950,25951],{},"\"Always use const or let instead of var\"",": This was Steve being picky.",[103,25954,25955,25960,25961,25963,25964,25966],{},[397,25956,25957],{},[338,25958,25959],{},"\"Always return msg, unless told otherwise\"",": We found this wasn't mostly required, but occasionally it would try to return a different variable, and we'd lose context of ",[19,25962,382],{},", or other data stored in ",[19,25965,260],{},". So this just made sure we had the consistency.",[15,25968,25969],{},"The response from this API call is then populated into the contents of the active tab in the function node:",[392,25971],{"width":25972,"alt":25973,"src":25974},1728,"Screenshot 2023-04-21 at 16 08 47","https:\u002F\u002Fuser-images.githubusercontent.com\u002F99246719\u002F233671631-fefa36c1-6db4-4392-a057-314c16fd91b7.png",[15,25976,25977,25978,167],{},"In order to use it yourself, you will need a ",[307,25979,25982],{"href":25980,"rel":25981},"https:\u002F\u002Fplatform.openai.com\u002Faccount\u002Fapi-keys",[311],"valid API Key from OpenAI",[34,25984,25986],{"id":25985},"additional-features","Additional Features",[15,25988,25989,25990,25993],{},"This was built in about a day by Steve and Joe, and we had plenty of ideas on what we'd like to add to it. We've\n",[307,25991,25783],{"href":25782,"rel":25992},[311]," it, and will add these as issues to the repo, but if anyone want so take a stab at contributing - that'd be most welcome!",[100,25995,25996,26011,26021],{},[103,25997,25998,26007,26008,26010],{},[338,25999,26000,26001,26006],{},"Insert at Cursor (",[307,26002,26005],{"href":26003,"rel":26004},"https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fnode-red-function-gpt\u002Fissues\u002F11",[311],"issue","):"," Currently, the Ask GPT call will replace ",[397,26009,9951],{}," of the content of that tab. Would be great\nto have the code insert wherever the cursor last was in order to add to existing code.",[103,26012,26013,26020],{},[338,26014,26015,26016,26006],{},"Retain Conversation History (",[307,26017,26005],{"href":26018,"rel":26019},"https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fnode-red-function-gpt\u002Fissues\u002F12",[311]," Each time a new prompt is provided by the Node-RED user, we send a fresh conversation to OpenAI,\nmeaning that knowledge of previously asked questions are not retained.",[103,26022,26023,26030],{},[338,26024,26025,26026,26006],{},"Client side ChatGPT Config (",[307,26027,26005],{"href":26028,"rel":26029},"https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fnode-red-function-gpt\u002Fissues\u002F13",[311]," Currently, when you add a new \"function-gpt\" node you need to select the ChatGTP\nConfig node and click \"Deploy\" before you can ask it a question. Our ChatGPT interaction operates server-side (to\nprotect your API key), so Node-RED needs that in the runtime first, before a call to ChatGPT can be made. Ideally,\nwe'd be smarter here and pass client-side creds along with the call such that we can use any changes made by the\nuser at the time of the call.",[34,26032,26034],{"id":26033},"flowfuse-assistant-no-api-keys-required","FlowFuse Assistant - No API Keys Required!",[15,26036,26037,26038,26040],{},"Great news! You no longer need to manage OpenAI API keys or configure ChatGPT nodes. The ",[307,26039,1692],{"href":24907}," is now built directly into Node-RED on FlowFuse Cloud, making AI-powered development even easier.",[15,26042,26043],{},"Available on FlowFuse Cloud, the Assistant offers:",[100,26045,26046,26052,26058],{},[103,26047,26048,26051],{},[338,26049,26050],{},"Quick Function Node Creation",": Add function nodes to your flow without dragging from the palette",[103,26053,26054,26057],{},[338,26055,26056],{},"In-line Code Generation",": Generate JavaScript code for function nodes, JSON for JSON editors, and Vue.js for FlowFuse Dashboard ui-template widgets",[103,26059,26060,26063],{},[338,26061,26062],{},"Flow Explainer",": Select nodes and click \"Explain Flows\" to understand what they do",[15,26065,26066],{},"FlowFuse Assistant helps developers work faster and smarter with Node-RED. [Start your free trial]({% include \"sign-up-url.njk\" %}) to experience AI-powered Node-RED development on FlowFuse Cloud.",[924,26068,26069],{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":50,"searchDepth":250,"depth":250,"links":26071},[26072,26073,26074],{"id":25790,"depth":185,"text":25791},{"id":25985,"depth":185,"text":25986},{"id":26033,"depth":185,"text":26034},"2023-05-02","Discover how ChatGPT integrates with Node-RED function nodes, enabling automated code generation. Explore the prompt engineering process and additional features.","\u002Fimages\u002Fblog\u002Ftile-chatgpt-fcn-node.jpg",{"excerpt":26079},{"type":12,"value":26080},[26081],[15,26082,25773,26083,25779,26086,25784,26088,167],{},[307,26084,25778],{"href":25776,"rel":26085},[311],[307,26087,25783],{"href":25782,"target":24745},[338,26089,25787],{},"\u002Fblog\u002F2023\u002F05\u002Fchatgpt-nodered-fcn-node",{"title":25767,"description":26076},{"loc":26090,"images":26093},[26094],{"loc":25974},"blog\u002F2023\u002F05\u002Fchatgpt-nodered-fcn-node","New Node-RED function with embedded ChatGPT is now open-sourced and available to use!",[9832,6563,26098,966,26099,26100],"community","chatgpt","ai","Yu5S1tx3B1sfZdRnsMPUdMZVRQ1TuP3iVWY1HoBI-bU",{"id":26103,"title":26104,"authors":26105,"body":26106,"cta":3,"date":26075,"description":26251,"extension":946,"image":26252,"lastUpdated":3,"meta":26253,"navigation":253,"path":26260,"seo":26261,"sitemap":26262,"stem":26263,"subtitle":26264,"tags":26265,"tldr":3,"video":3,"__hash__":26267},"blog\u002Fblog\u002F2023\u002F05\u002Fdevice-agent-as-a-service.md","Running the FlowFuse Device Agent as a service on a Raspberry Pi",[7451],{"type":12,"value":26107,"toc":26241},[26108,26111,26114,26116,26122,26126,26130,26137,26142,26146,26149,26173,26181,26184,26188,26191,26196,26199,26204,26208,26211,26216,26219,26224,26227,26232,26234],[15,26109,26110],{},"FlowFuse's device agent allows you to manage and run your Node-RED instances on\nyour own hardware such as a Raspberry Pi. This can be very useful where an\napplication you've written needs to run flows with direct access to hardware sensors.",[15,26112,26113],{},"In this article I'm going to explain the steps to configure our device agent to run as a service in Raspbian OS,\nor any other OS that uses systemd.",[34,26115,18568],{"id":18567},[15,26117,18571,26118,26121],{},[19,26119,26120],{},"flowforge-device-agent",". This works fine for testing\nbut for long-term installations it's useful to run the device agent as a service.\nOnce running as a service, the device agent will continue to run even if your\ndevice is restarted or your SSH connection to your Pi fails.",[34,26123,26125],{"id":26124},"set-up-steps","Set up steps",[996,26127,26129],{"id":26128},"create-the-service-file","Create the Service File",[15,26131,26132,26133,26136],{},"The first step is creating the systemd unit file for your service. You can start by creating a new file in the\n",[19,26134,26135],{},"\u002Fetc\u002Fsystemd\u002Fsystem"," directory with a .service file extension:",[15,26138,26139],{},[19,26140,26141],{},"sudo nano \u002Fetc\u002Fsystemd\u002Fsystem\u002Fflowforge-device-agent.service",[996,26143,26145],{"id":26144},"define-the-service","Define the Service",[15,26147,26148],{},"In the service file, you'll need to define the following parameters:",[100,26150,26151,26156,26162,26168],{},[103,26152,26153,26155],{},[19,26154,2187],{},": A brief description of what the service does.",[103,26157,26158,26161],{},[19,26159,26160],{},"ExecStart",": The command(s) to execute to start the service.",[103,26163,26164,26167],{},[19,26165,26166],{},"User and Group",": The user and group that the service runs as.",[103,26169,26170,26172],{},[19,26171,521],{},": Whether the service is a simple or a forking type.",[15,26174,26175,26176,167],{},"We've created the content you'll need for this file and shared it via ",[307,26177,26180],{"href":26178,"rel":26179},"https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fdevice-agent\u002Fblob\u002Fmain\u002Fservice\u002Fflowfuse-device.service",[311],"this GitHub page",[15,26182,26183],{},"Copy the code from that page into the nano window you created in step 1, then save and exit out of nano.",[996,26185,26187],{"id":26186},"starting-the-service-on-boot-optional","Starting the service on boot (optional)",[15,26189,26190],{},"If you want Node-RED to run when the Pi is turned on, or re-booted, you can enable the service to autostart by running the command:",[15,26192,26193],{},[19,26194,26195],{},"sudo systemctl enable flowforge-device-agent.service",[15,26197,26198],{},"To disable the service, run the command:",[15,26200,26201],{},[19,26202,26203],{},"sudo systemctl disable flowforge-device-agent.service",[996,26205,26207],{"id":26206},"using-your-new-service","Using your new service",[15,26209,26210],{},"You can now start your service with the start command:",[15,26212,26213],{},[19,26214,26215],{},"sudo systemctl start flowforge-device-agent",[15,26217,26218],{},"You can check the current status with the status command:",[15,26220,26221],{},[19,26222,26223],{},"sudo systemctl status flowforge-device-agent",[15,26225,26226],{},"Finally, if you need to stop your agent you can do so with the command:",[15,26228,26229],{},[19,26230,26231],{},"sudo systemctl stop flowforge-device-agent",[34,26233,19544],{"id":19543},[15,26235,26236,26237,26240],{},"If you'd like to learn about using services via the systemctl command you can access\nthe help text by running ",[19,26238,26239],{},"systemctl -h"," from your Pi terminal.",{"title":50,"searchDepth":250,"depth":250,"links":26242},[26243,26244,26250],{"id":18567,"depth":185,"text":18568},{"id":26124,"depth":185,"text":26125,"children":26245},[26246,26247,26248,26249],{"id":26128,"depth":221,"text":26129},{"id":26144,"depth":221,"text":26145},{"id":26186,"depth":221,"text":26187},{"id":26206,"depth":221,"text":26207},{"id":19543,"depth":185,"text":19544},"Learn how to run the FlowFuse Device Agent as a service on your Raspberry Pi with this step-by-step guide. Ensure uninterrupted operation even after device restarts.","\u002Fblog\u002F2023\u002F05\u002Fimages\u002Fagent-on-pi.png",{"excerpt":26254},{"type":12,"value":26255},[26256,26258],[15,26257,26110],{},[15,26259,26113],{},"\u002Fblog\u002F2023\u002F05\u002Fdevice-agent-as-a-service",{"title":26104,"description":26251},{"loc":26260},"blog\u002F2023\u002F05\u002Fdevice-agent-as-a-service","Step by step guide to run the device agent as a service",[9832,965,966,26266],"raspberry pi","bUI55E-Vzo5k4uP7RMNIrAboyM3RtrgHmLc25cN5mOA",{"id":26269,"title":25154,"authors":26270,"body":26271,"cta":3,"date":26420,"description":26421,"extension":946,"image":25192,"lastUpdated":8478,"meta":26422,"navigation":253,"path":26427,"seo":26428,"sitemap":26429,"stem":26430,"subtitle":26431,"tags":26432,"tldr":3,"video":3,"__hash__":26434},"blog\u002Fblog\u002F2023\u002F04\u002F3-quick-node-red-tips-6.md",[7451],{"type":12,"value":26272,"toc":26412},[26273,26275,26279,26282,26286,26289,26295,26298,26302,26305,26311,26314,26320,26324,26327,26330,26333,26339,26342,26348,26351,26357,26361,26364,26370,26373,26379,26382,26388,26392],[15,26274,24948],{},[996,26276,26278],{"id":26277},"_1-subflows","1. Subflows",[15,26280,26281],{},"Subflows are a great way to reuse sections of your flows. Once you have created a subflow, it can easily be dropped into your workspace one or more times.",[4987,26283,26285],{"id":26284},"why-use-subflows","Why use subflows?",[15,26287,26288],{},"Without using a subflow, you can copy and paste a flow into each place you need to use it. This takes up quite a bit of workspace, and makes it harder to update your flow in the future as you'll have to update each copy.",[15,26290,26291],{},[392,26292],{"alt":26293,"src":26294,"title":26293},"Duplication of the flow","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fno-subflow.png",[15,26296,26297],{},"If we instead put the flow into a subflow we'll save a lot of workspace and it will be easier to update the reused sections of the flow if we need to in the future.",[4987,26299,26301],{"id":26300},"creating-a-subflow","Creating a subflow",[15,26303,26304],{},"You can create a subflow using the burger menu in the top right corner of Node-RED, select Subflows, then Create Subflow. Lay out your subflow, making sure you create an input and output. You can even have more than one output if you want.",[15,26306,26307],{},[392,26308],{"alt":26309,"src":26310,"title":26309},"Contents of the subflow","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fsubflow.png",[15,26312,26313],{},"You can now drop the subflow into your workspace as needed, saving space and making it easier to manage changes to your flow.",[15,26315,26316],{},[392,26317],{"alt":26318,"src":26319,"title":26318},"Using the subflow to reduce duplication of flows","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fusing-the-subflow.png",[996,26321,26323],{"id":26322},"_2-link-nodes","2. Link Nodes",[15,26325,26326],{},"Link Nodes allow you to separate your flows into distinct sections. The wires between the link nodes are not visible until you select that part of the flow. You can also link flows on different tabs together.",[15,26328,26329],{},"Formatting your flows into distinct sections using link nodes can make it easier to read and update your work.",[15,26331,26332],{},"To use the link node, drag a link in and out node into your flow's workspace. Now draw a wire as you usually would to link to two nodes together. You should see a link between the nodes but it only shows when you have the link nodes selected.",[15,26334,26335],{},[392,26336],{"alt":26337,"src":26338,"title":26337},"Linking two link nodes together","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fwiring-link-nodes.gif",[15,26340,26341],{},"In this example below, the first and second flows have the same nodes and functionality. In the second image of the workspace I've split the flow into specific groups of nodes.",[15,26343,26344],{},[392,26345],{"alt":26346,"src":26347,"title":26346},"A flow without link nodes","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fflow-without-link-nodes.png",[15,26349,26350],{},"It's easier to read and understand the flow once it's split up using the link nodes and groups.",[15,26352,26353],{},[392,26354],{"alt":26355,"src":26356,"title":26355},"The same flow as above, now split up using link nodes","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fflow-with-link-nodes.png",[996,26358,26360],{"id":26359},"_3-range-node","3. Range Node",[15,26362,26363],{},"Sometimes you might need to map one numbering scale onto another. For example, where a user has selected a value between 0 and 10 but you want to use and store their response as a percentage. The Range node makes this task very easy.",[15,26365,26366],{},[392,26367],{"alt":26368,"src":26369,"title":26368},"Example of using the range node","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fflow-using-range.png",[15,26371,26372],{},"To configure the node, set it up as follows:",[15,26374,26375],{},[392,26376],{"alt":26377,"src":26378,"title":26377},"Configuration of the range node","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Frange-config.png",[15,26380,26381],{},"You should now see that the input values are translated to the appropriate value out of 100.",[15,26383,26384],{},[392,26385],{"alt":26386,"src":26387,"title":26386},"The range note in use","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Frange-demo.gif",[15,26389,25143,26390,25148],{},[307,26391,25147],{"href":25146},[15,26393,26394,26396,26398,26400,26402,26404,26406,26408,26410],{},[307,26395,25160],{"href":25159},[1290,26397],{},[307,26399,25166],{"href":25165},[1290,26401],{},[307,26403,25172],{"href":25171},[1290,26405],{},[307,26407,25178],{"href":25177},[1290,26409],{},[307,26411,25184],{"href":25183},{"title":50,"searchDepth":250,"depth":250,"links":26413},[26414,26418,26419],{"id":26277,"depth":221,"text":26278,"children":26415},[26416,26417],{"id":26284,"depth":250,"text":26285},{"id":26300,"depth":250,"text":26301},{"id":26322,"depth":221,"text":26323},{"id":26359,"depth":221,"text":26360},"2023-04-20","Learn how to streamline your Node-RED projects with these essential tips: Subflows, Link Nodes, and Range Node for efficient workflow organization.",{"excerpt":26423},{"type":12,"value":26424},[26425],[15,26426,24948],{},"\u002Fblog\u002F2023\u002F04\u002F3-quick-node-red-tips-6",{"title":25154,"description":26421},{"loc":26427},"blog\u002F2023\u002F04\u002F3-quick-node-red-tips-6","Save yourself time when working on Node-RED with these three tips.",[6563,26433,966],"node-red subflow","zbjlyCGZrnt1FelOIp_DAi162HKXwthU3yVidwpm3ak",{"id":26436,"title":26437,"authors":26438,"body":26439,"cta":26750,"date":26753,"description":26754,"extension":946,"image":26755,"lastUpdated":948,"meta":26756,"navigation":253,"path":26762,"seo":26763,"sitemap":26764,"stem":26765,"subtitle":26766,"tags":26767,"tldr":26769,"video":3,"__hash__":26770},"blog\u002Fblog\u002F2023\u002F04\u002Fsecuring-node-red-in-production.md","Securing Node-RED (2026)",[7451],{"type":12,"value":26440,"toc":26744},[26441,26444,26447,26450,26454,26457,26462,26465,26468,26471,26476,26479,26486,26495,26500,26503,26509,26512,26515,26520,26523,26529,26532,26538,26541,26544,26549,26552,26558,26561,26564,26570,26573,26579,26582,26585,26589,26592,26595,26603,26612,26615,26618,26627,26635,26638,26643,26648,26653,26658,26663,26668,26673,26678,26681,26689,26692,26698,26701,26706,26709,26715,26723,26727,26730,26733,26736,26739,26741],[15,26442,26443],{},"Node-RED is very easy to get up and running. Whether you run it locally, in Docker, on a Raspberry Pi, or on a service such as FlowFuse Cloud you can have a project up and running in minutes.",[15,26445,26446],{},"One thing that can get overlooked is the security of Node-RED. From personal experience, the first few times I installed Node-RED I was more focussed on the possibilities of what I could do with this new tool than I was keeping my projects secure.",[15,26448,26449],{},"In this article I’m going to look at some easy ways to make your Node-RED project more secure, even when first learning about it in a hobby environment.",[34,26451,26453],{"id":26452},"protecting-access-from-your-lan-to-node-red","Protecting access from your LAN to Node-RED",[15,26455,26456],{},"Once you have an instance of Node-RED running it can usually be accessed from anywhere on your LAN (local area network) by pointing a web browser to the relevant IP address and port.",[15,26458,26459],{},[19,26460,26461],{},"http:\u002F\u002F192.168.0.3:1880",[15,26463,26464],{},"With a URL similar to the one above, depending on your specific network and Node-RED configuration, anyone on your LAN can view but more importantly edit your flows. This can be really useful when you are first learning about Node-RED but it’s always a good idea to get into the habit of locking down access to the editor, even if you trust everyone who can access your LAN.",[15,26466,26467],{},"One of the easiest ways to protect your flows is to add a username and password to your Node-RED instance.",[15,26469,26470],{},"The first step is to find your Node-RED settings.js file. It's not always in the same place but on a default Debian Linux installation it can be found in this directory.",[15,26472,26473],{},[19,26474,26475],{},"cd ~\u002F.node-red",[15,26477,26478],{},"If you list that directory you should now see something like this:",[15,26480,26481],{},[392,26482],{"alt":26483,"src":26484,"title":26485},"Where your settings file should show","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fls.png","Where your settings.js should show",[15,26487,26488,26489,26494],{},"We now need to edit settings.js, I'm going to use my favourite text editor, ",[307,26490,26493],{"href":26491,"rel":26492},"https:\u002F\u002Fwww.nano-editor.org\u002F",[311],"Nano"," to do that.",[15,26496,26497],{},[19,26498,26499],{},"nano settings.js",[15,26501,26502],{},"We now need to find and edit the following section of the settings file:",[15,26504,26505],{},[392,26506],{"alt":26507,"src":26508,"title":26507},"The settings file before being edited","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fwithout-password.png",[15,26510,26511],{},"For this example, I'm going to add a password and uncomment the relevant section of the settings file, you could also change the username for additional security.",[15,26513,26514],{},"To create the password we'll need to use a command line tool which is included in Node-RED. Open a second terminal then run this command:",[15,26516,26517],{},[19,26518,26519],{},"node-red admin hash-pw",[15,26521,26522],{},"Put in your new password, I'll use the password 'flowforge' in this example. The tool returns your password in a hashed format:",[15,26524,26525],{},[392,26526],{"alt":26527,"src":26528,"title":26527},"The Node-RED tool outputs the hashed password","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fpassword.png",[15,26530,26531],{},"We can now return to the other terminal window, uncomment the section then paste in the new password, this is how it looks for me:",[15,26533,26534],{},[392,26535],{"alt":26536,"src":26537,"title":26536},"The settings file with the relevant section uncommented and the password set","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fwith-password.png",[15,26539,26540],{},"We can now save and exit out of the settings file.",[15,26542,26543],{},"The last step is to restart Node-RED, I'm using Debian so the command is:",[15,26545,26546],{},[19,26547,26548],{},"node-red-restart",[15,26550,26551],{},"Now, when we try to access Node-RED I will need to provide a username and password.",[15,26553,26554],{},[392,26555],{"alt":26556,"src":26557,"title":26556},"Using the username and password to login to Node-RED","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Flogin.gif",[15,26559,26560],{},"You might also want to consider turning off the editor interface once you are happy with your flows. This can make it a little harder to make changes to your project but it also gives you peace of mind that nobody has accidentally or deliberately changed your flows. You can turn off the editor interface as follows.",[15,26562,26563],{},"Edit your settings.js file as explained above, look for the following section:",[15,26565,26566],{},[392,26567],{"alt":26568,"src":26569,"title":26568},"The setting file before turning off the editor","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Feditor-on.png",[15,26571,26572],{},"All you need to do is uncomment the bottom line then change the value from false to true, once done it should look something like this:",[15,26574,26575],{},[392,26576],{"alt":26577,"src":26578,"title":26577},"The setting file after turning off the editor","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Feditor-off.png",[15,26580,26581],{},"Now restart Node-RED as covered above, then try accessing your Node-RED instance again. You will no longer be able to edit or view your flows.",[15,26583,26584],{},"Using these two features, we now have much better control over who can access the design interface for Node-RED.",[34,26586,26588],{"id":26587},"traffic-to-your-node-red-instance-is-unencrypted","Traffic to your Node-RED instance is unencrypted",[15,26590,26591],{},"Hopefully, we all know the importance of encrypting your connections between devices to stop people intercepting your traffic. This isn't a huge concern when working on your home LAN but what if you want to access your Node-RED instance from a remote location?",[15,26593,26594],{},"There are two obvious options, HTTPS, and a VPN (Virtual Private Network).",[15,26596,26597,26598,26602],{},"We could setup your Node-RED traffic to run over HTTPS, this solution ensures that all traffic to and from your Node-RED is encrypted. The downside to this approach is it's quite complex to set up. We will need to have a domain name, open up ports on our LAN's firewall, use a HTTPS certificate provider and then make sure we remember to renew the certificates as needed. It's doable if you are comfortable with those concepts (I covered how to do this as part of my blog ",[307,26599,26601],{"href":26600},"\u002Fblog\u002F2022\u002F12\u002Fflowforge-gcp-https-set-up\u002F","hosting FlowFuse on Google Cloud",") but there is an easier way to get started, using a VPN.",[15,26604,26605,26606,26611],{},"A VPN provides a lot of security advantages depending on which you are using and how it is configured. To secure my traffic I'm going to use a great service call ",[307,26607,26610],{"href":26608,"rel":26609},"https:\u002F\u002Ftailscale.com\u002F",[311],"Tailscale"," which is free for personal projects.",[15,26613,26614],{},"I'm going to install Tailscale on the Raspberry Pi I'm running Node-RED on as well as any other devices I want to access my project from. Once that's done I can access Node-RED from anywhere with internet access but more importantly the traffic to and from my devices is also encrypted.",[15,26616,26617],{},"Before we start, it's important to remember that a VPN is only as secure as the company who runs it. You should always consider if you trust the VPN provider as they could potentially access your devices. I trust Tailscale but please do your own research before using a VPN provider.",[15,26619,26620,26621,26626],{},"The first step we need to take is creating a Tailscale account, ",[307,26622,26625],{"href":26623,"rel":26624},"https:\u002F\u002Flogin.tailscale.com\u002Fstart",[311],"you can sign up for free here",". We next need to add our devices to our VPN using their software, I'm installing Tailscale on my Apple laptop, Google phone as well as the Raspberry Pi I'm running Node-RED on.",[15,26628,26629,26630,167],{},"The install process is really easy, even on the Pi running Raspbian the steps you need to take are well explained in the ",[307,26631,26634],{"href":26632,"rel":26633},"https:\u002F\u002Ftailscale.com\u002Fdownload\u002Flinux\u002Fdebian-bullseye",[311],"Tailscale docs",[15,26636,26637],{},"For the Pi, these are the commands we need to run.",[326,26639,26640],{},[103,26641,26642],{},"Add Tailscale to the Apt package manager.",[15,26644,26645],{},[19,26646,26647],{},"curl -fsSL https:\u002F\u002Fpkgs.tailscale.com\u002Fstable\u002Fdebian\u002Fbullseye.noarmor.gpg | sudo tee \u002Fusr\u002Fshare\u002Fkeyrings\u002Ftailscale-archive-keyring.gpg >\u002Fdev\u002Fnull",[15,26649,26650],{},[19,26651,26652],{},"curl -fsSL https:\u002F\u002Fpkgs.tailscale.com\u002Fstable\u002Fdebian\u002Fbullseye.tailscale-keyring.list | sudo tee \u002Fetc\u002Fapt\u002Fsources.list.d\u002Ftailscale.list",[15,26654,26655],{},[19,26656,26657],{},"sudo apt-get update",[326,26659,26660],{"start":185},[103,26661,26662],{},"Install Tailscale",[15,26664,26665],{},[19,26666,26667],{},"sudo apt-get install tailscale",[326,26669,26670],{"start":221},[103,26671,26672],{},"Start Tailscale and connect your device",[15,26674,26675],{},[19,26676,26677],{},"sudo tailscale up",[15,26679,26680],{},"After running the last command, we need to follow the on screen prompts to link our devices to your VPN.",[15,26682,26683,26684,167],{},"One last thing which you might want to consider doing, every few months you will need to reconnect your devices to your VPN, if you are only going to be accessing your Node-RED device over the VPN you should consider ",[307,26685,26688],{"href":26686,"rel":26687},"https:\u002F\u002Ftailscale.com\u002Fkb\u002F1028\u002Fkey-expiry\u002F",[311],"disabling your Tailscale key expiry",[15,26690,26691],{},"OK, now we've got our devices all connected you should see something like this in the Tailscale dashboard.",[15,26693,26694],{},[392,26695],{"alt":26696,"src":26697,"title":26696},"Tailscale dashboard showing my three devices","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Ftailscale.png",[15,26699,26700],{},"I can now access Node-RED on my Pi from my laptop and phone by pointing a browser to the correct IP address (as shown in the image above) with the port for Node-RED:",[15,26702,26703],{},[19,26704,26705],{},"http:\u002F\u002F100.71.28.60:1880",[15,26707,26708],{},"I’ve now secured all traffic between my devices and Node-RED project, I can access Node-RED from anywhere on the internet.",[15,26710,26711],{},[392,26712],{"alt":26713,"src":26714,"title":26713},"Accessing Node-RED via the VPN","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fnr-via-vpn.png",[15,26716,26717,26718,26722],{},"If you follow these steps you should be on the right path to running a more secure Node-RED instance. There is a lot more you can do and I recommend you read the ",[307,26719,26721],{"href":25695,"rel":26720},[311],"relevant docs on the Node-RED"," website to gain some more ideas.",[34,26724,26726],{"id":26725},"what-about-hosting-node-red-on-a-cloud-solution-such-as-flowfuse","What about hosting Node-RED on a cloud solution such as FlowFuse?",[15,26728,26729],{},"In this article, I've focussed on hosting Node-RED on a Pi on your own LAN but if you use FlowFuse Cloud to host Node-RED the solutions discussed above are either ready out of the box or are not needed.",[15,26731,26732],{},"By default, the Node-RED editor is secured using your FlowFuse user credentials. You can also enable SSO to enhance account security and easily grant access to team members. With role-based access control, you can further protect your flows by managing who can view or edit them.",[15,26734,26735],{},"All traffic to FlowFuse and your Node-RED instances is protected by HTTPS. FlowFuse has set up the domain name and manages the certificates, so you can spend time on your flows rather than configuring security. Additionally, remote device access is secured through encrypted tunnels, providing comprehensive protection for your deployments.",[15,26737,26738],{},"FlowFuse has a [free trial]({% include \"sign-up-url.njk\" %}) if you'd like to see how we've made secure hosting of Node-RED easy.",[34,26740,6518],{"id":6517},[15,26742,26743],{},"How ever you host Node-RED, it's a great idea to get into good security practices as early as possible to ensure that no unsecured Node-RED instances are exposed to the internet. I hope some of the tips above help you get started down the path to creating more secure Node-RED projects.",{"title":50,"searchDepth":250,"depth":250,"links":26745},[26746,26747,26748,26749],{"id":26452,"depth":185,"text":26453},{"id":26587,"depth":185,"text":26588},{"id":26725,"depth":185,"text":26726},{"id":6517,"depth":185,"text":6518},{"type":941,"title":26751,"description":26752},"Secure Node-RED Without the Complexity","FlowFuse manages HTTPS, authentication, and remote access security for you out of the box, so you can focus on building flows instead of configuring infrastructure.","2023-04-05","Explore essential steps for securing Node-RED projects, from LAN access control to encrypted traffic, ensuring robust protection for your projects.","\u002Fblog\u002F2023\u002F04\u002Fimages\u002Fsecurity-header.png",{"keywords":26757,"excerpt":26758},"securing node-red, node-red security, node-red password, node-red https, node-red vpn, node-red authentication",{"type":12,"value":26759},[26760],[15,26761,26443],{},"\u002Fblog\u002F2023\u002F04\u002Fsecuring-node-red-in-production",{"title":26437,"description":26754},{"loc":26762},"blog\u002F2023\u002F04\u002Fsecuring-node-red-in-production","Step-by-step guide for securing your Node-RED projects.",[9832,6563,966,26768],"securing node-red","This step-by-step guide covers the essential security measures for Node-RED deployments: adding username\u002Fpassword authentication via settings.js, restricting LAN access, enabling HTTPS with TLS certificates, and using FlowFuse Cloud as a managed alternative that handles these concerns automatically. Even hobby deployments benefit from these protections to prevent unauthorized access to the Node-RED editor.","6BcWNGMuEIfrnHRu6Gshqk33JH8J5WF-g0yqHX1KP6k",{"id":26772,"title":25160,"authors":26773,"body":26774,"cta":3,"date":26947,"description":26948,"extension":946,"image":25192,"lastUpdated":8478,"meta":26949,"navigation":253,"path":26954,"seo":26955,"sitemap":26956,"stem":26957,"subtitle":26431,"tags":26958,"tldr":3,"video":3,"__hash__":26959},"blog\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-5.md",[7451],{"type":12,"value":26775,"toc":26941},[26776,26778,26782,26785,26788,26799,26802,26816,26822,26825,26829,26832,26835,26852,26855,26861,26864,26868,26871,26885,26891,26894,26898,26901,26919,26923,26928,26936],[15,26777,24948],{},[996,26779,26781],{"id":26780},"_1-copy-and-share-your-flows-using-export-and-import","1. Copy and share your flows using Export and Import",[15,26783,26784],{},"Node-RED provides both import and export features that allow you to save your flows and settings as compressed JSON files. You can use these features to easily move your flows between multiple Node-RED instances or to back up your work.",[15,26786,26787],{},"To import a flow, follow these steps:",[326,26789,26790,26793,26796],{},[103,26791,26792],{},"Click on the three horizontal lines in the upper right corner of the Node-RED editor and click on \"Import\".",[103,26794,26795],{},"Select the JSON file that contains the flow you want to import.",[103,26797,26798],{},"Click on \"Import\" and the flow will be imported into the current instance of Node-RED.",[15,26800,26801],{},"To export a flow, follow these steps:",[326,26803,26804,26807,26810,26813],{},[103,26805,26806],{},"Click on the three horizontal lines in the upper right corner of the Node-RED editor and click on \"Export\".",[103,26808,26809],{},"Select the type of export you want to perform - this can be either the \"Clipboard\" or \"File\".",[103,26811,26812],{},"If you chose \"File\" you will be prompted to save a compressed JSON file to your computer; if you chose \"Clipboard\" the flow JSON will be copied to your clipboard.",[103,26814,26815],{},"Use the exported file or clipboard content to import into a different instance of Node-RED.",[15,26817,26818],{},[392,26819],{"alt":26820,"src":26821,"title":26820},"Importing and exporting your flows","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fimport-export.gif",[15,26823,26824],{},"Keep in mind that some nodes or flows may require additional setup or node installation on the Node-RED instance you import your flow to.",[996,26826,26828],{"id":26827},"_2-import-helpful-example-flows-provided-with-custom-nodes","2. Import helpful example flows provided with custom nodes",[15,26830,26831],{},"In Node-RED, custom nodes can include examples that provide users with a starting point for using the node. These examples can help users understand how a node works and how it can be integrated into their flows.",[15,26833,26834],{},"To use example flows in custom nodes, follow these steps:",[326,26836,26837,26840,26843,26846,26849],{},[103,26838,26839],{},"Open the Node-RED editor and drag the custom node you want to use into your flow.",[103,26841,26842],{},"Double-click on the node to open its configuration panel.",[103,26844,26845],{},"Look for an \"Examples\" menu or button within the node configuration panel. The name and location of the Examples button can vary depending on the node.",[103,26847,26848],{},"Click on the \"Examples\" button to bring up a list of example flows included with the node.",[103,26850,26851],{},"Select the example flow you want to use and click on \"Import\" to add the example flow to your Node-RED workspace.",[15,26853,26854],{},"Once the example flow has been added to your workspace, you can modify it to fit your specific needs.",[15,26856,26857],{},[392,26858],{"alt":26859,"src":26860,"title":26859},"Using the example flow included in the moment node","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fexample.gif",[15,26862,26863],{},"It's important to note that while custom node examples can be a useful starting point, they may not always work seamlessly with your other flows or nodes. Be sure to thoroughly test any custom node examples before incorporating them into a production environment.",[996,26865,26867],{"id":26866},"_3-group-nodes-together-to-make-your-flows-easier-to-read","3. Group nodes together to make your flows easier to read",[15,26869,26870],{},"The group feature in Node-RED allows users to visually group nodes together within the workspace. This feature offers several benefits:",[326,26872,26873,26876,26879,26882],{},[103,26874,26875],{},"Improved organization: The group feature allows you to group related nodes visually, which can make your flow easier to understand and navigate. This can be particularly helpful for larger, more complex flows.",[103,26877,26878],{},"Simplified editing: When you group nodes together, you can edit or move them as a single unit, rather than individually. This can save time and reduce the chance of errors.",[103,26880,26881],{},"Easier sharing: When you share your flow with others, the group feature allows you to package related nodes together, making it easier for others to understand and use your flow.",[103,26883,26884],{},"Reduced clutter: Grouping nodes can help reduce the visual clutter in your workspace, making it easier to focus on key aspects of your flow.",[15,26886,26887],{},[392,26888],{"alt":26889,"src":26890,"title":26889},"Grouping your nodes to make them easier to read","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fgroups.gif",[15,26892,26893],{},"Overall, the group feature in Node-RED is a valuable tool that can help users better organise, edit, and share their flows.",[15,26895,25143,26896,167],{},[307,26897,25147],{"href":25146},[15,26899,26900],{},"You can read our previous Node-RED tips here.",[15,26902,26903,26906,26908,26910,26912,26914,26916],{},[307,26904,26905],{"href":25165},"Node-RED Tips - Smooth, Catch, and Math",[1290,26907],{},[307,26909,25172],{"href":25171},[1290,26911],{},[307,26913,25178],{"href":25177},[1290,26915],{},[307,26917,26918],{"href":25183},"Node-RED Tips - Wiring Shortcuts\n",[34,26920,26922],{"id":26921},"version-control-and-collaboration-for-your-node-red-flows","Version Control and Collaboration for Your Node-RED Flows",[15,26924,26925,26927],{},[307,26926,1155],{"href":213}," simplifies managing and collaborating on your Node-RED flows with seamless version control. You can easily track changes, take snapshots, and revert to previous versions, ensuring your work is always safe and recoverable.",[15,26929,26930,26931,26935],{},"With FlowFuse ",[307,26932,26934],{"href":26933},"\u002Fdocs\u002Fuser\u002Fshared-library\u002F#shared-team-library","Team Library",", sharing flows across different Node-RED instances is effortless. This Library feature allows you to organize and share flows among team members without the need for manual copying, making collaboration more efficient and effective.",[15,26937,26938,2755],{},[338,26939,26940],{},"[Sign up]({% include \"sign-up-url.njk\" %}) for a free trial today and discover how FlowFuse can enhance your Node",{"title":50,"searchDepth":250,"depth":250,"links":26942},[26943,26944,26945,26946],{"id":26780,"depth":221,"text":26781},{"id":26827,"depth":221,"text":26828},{"id":26866,"depth":221,"text":26867},{"id":26921,"depth":185,"text":26922},"2023-03-27","Learn how to save time on Node-RED with three essential techniques: exporting and importing flows, accessing example flows from custom nodes, and organizing nodes using groups for improved clarity and management.",{"excerpt":26950},{"type":12,"value":26951},[26952],[15,26953,24948],{},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-5",{"title":25160,"description":26948},{"loc":26954},"blog\u002F2023\u002F03\u002F3-quick-node-red-tips-5",[9832,6563,966,9324],"pWB70rderYipovtfUJmLU0l97JsyyMAfLCu4CbgYAQg",{"id":26961,"title":26962,"authors":26963,"body":26964,"cta":3,"date":27091,"description":27092,"extension":946,"image":27093,"lastUpdated":8478,"meta":27094,"navigation":253,"path":27099,"seo":27100,"sitemap":27101,"stem":27102,"subtitle":27103,"tags":27104,"tldr":3,"video":3,"__hash__":27105},"blog\u002Fblog\u002F2023\u002F03\u002Fwhy-should-you-use-node-red-function-nodes.md","The benefits and drawbacks of using Node-RED function nodes",[7451],{"type":12,"value":26965,"toc":27085},[26966,26969,26973,26979,27011,27015,27021,27053,27057,27060,27067,27073,27076,27079,27082],[15,26967,26968],{},"Function nodes are an essential part of Node-RED. They allow you to write custom JavaScript functions that can be used in your Node-RED flows. In this blog post, I will discuss some of the benefits and drawbacks of using Function nodes in your next project.",[34,26970,26972],{"id":26971},"_5-benefits-of-using-function-nodes","5 Benefits of using Function Nodes:",[15,26974,26975],{},[392,26976],{"alt":26977,"src":26978,"title":26977},"Example showing how to use the function node","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Ffunction-example.gif",[326,26980,26981,26987,26993,26999,27005],{},[103,26982,26983,26986],{},[338,26984,26985],{},"Customisation:"," Function nodes allow you to write custom JavaScript functions that can be tailored to your specific needs. You can create complex functions that perform a variety of tasks, the only limit is your programming skills.",[103,26988,26989,26992],{},[338,26990,26991],{},"Reusability:"," Function nodes can be reused in multiple flows, saving you time and effort. You can create a library of custom functions that can be easily accessed and reused in different flows.",[103,26994,26995,26998],{},[338,26996,26997],{},"Debugging:"," Function nodes provide an easy way to debug your code. You can use console.log statements to output debug information to the Node-RED debug panel, making it easier to identify and fix issues.",[103,27000,27001,27004],{},[338,27002,27003],{},"Performance:"," Function nodes can be more performant than using multiple nodes to achieve the same result. By combining multiple tasks into a single function, you can improve performance, assuming your code is efficient.",[103,27006,27007,27010],{},[338,27008,27009],{},"Flexibility:"," Function nodes provide a high degree of flexibility. You can use them to perform tasks that are not possible using a single, standard Node-RED node, such as complex data manipulation.",[34,27012,27014],{"id":27013},"_5-benefits-of-avoiding-function-nodes","5 Benefits of avoiding Function Nodes:",[15,27016,27017],{},[392,27018],{"alt":27019,"src":27020,"title":27019},"Example showing how to not use the function node","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fno-function-example.gif",[326,27022,27023,27029,27035,27041,27047],{},[103,27024,27025,27028],{},[338,27026,27027],{},"Simplicity:"," Not using function nodes can make your flows simpler and easier to understand. By using standard Node-RED nodes, you can create flows that are easy to follow and maintain for both you and your team.",[103,27030,27031,27034],{},[338,27032,27033],{},"Ease of Use:"," Standard Node-RED nodes are easy to use and require no programming knowledge. This makes it easier for non-technical users to create and maintain flows.",[103,27036,27037,27040],{},[338,27038,27039],{},"Modularity:"," By using standard Node-RED nodes, you can create modular flows that can be easily modified and extended. This makes it easier to add new functionality to your flows as your needs change.",[103,27042,27043,27046],{},[338,27044,27045],{},"Community Support:"," Standard Node-RED nodes have a large and active community, providing support and resources for users. This can make it easier to find solutions to common problems and share knowledge with others.",[103,27048,27049,27052],{},[338,27050,27051],{},"Compatibility:"," Standard Node-RED nodes are usually compatible with all versions of Node-RED, making it easier to migrate flows between different environments.",[34,27054,27056],{"id":27055},"how-to-easily-create-function-nodes-in-flowfuse","How to Easily Create Function Nodes in FlowFuse",[15,27058,27059],{},"FlowFuse offers a robust platform for building, scaling, and securing your Node-RED applications.",[15,27061,27062,27063,27066],{},"We are constantly adding new features to make it easy to use in the enterprise where you can rapidly improve your industrial processes. The ",[338,27064,27065],{},"\"FlowFuse Assistant.\""," for example is an AI-powered tool that simplifies the creation of Function nodes. You only need to provide a prompt, and the assistant generates the Function nodes for you.",[15,27068,27069,27070,167],{},"For more details on using the FlowFuse Assistant, visit ",[307,27071,27072],{"href":24907},"the Assistants Documentation",[34,27074,27075],{"id":6517},"Conclusion:",[15,27077,27078],{},"Function nodes are particularly valuable for users who possess JavaScript programming skills. They allow for complex tasks, advanced data manipulation, and integration with external APIs, providing a high level of customization and flexibility. However, they require a good understanding of JavaScript to implement effectively and can be more challenging to manage and debug compared to standard Node-RED nodes.",[15,27080,27081],{},"On the other hand, standard Node-RED nodes offer a simpler and more accessible approach, making it easy for users without programming expertise to create and maintain flows. They are designed for straightforward tasks and provide modularity, benefiting from a supportive community for troubleshooting and knowledge sharing.",[15,27083,27084],{},"Ultimately, the choice between using function nodes and standard nodes will depend on your project's requirements and your familiarity with JavaScript. If you seek deep customization and flexibility, function nodes, enhanced by tools like FlowFuse Assistant, might be the best choice. For those who value simplicity and ease of use, standard Node-RED nodes are a great fit.",{"title":50,"searchDepth":250,"depth":250,"links":27086},[27087,27088,27089,27090],{"id":26971,"depth":185,"text":26972},{"id":27013,"depth":185,"text":27014},{"id":27055,"depth":185,"text":27056},{"id":6517,"depth":185,"text":27075},"2023-03-20","Explore the benefits and drawbacks of Function nodes in Node-RED projects, balancing customizability with simplicity for optimal flow design.","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Ffunction-nodes.png",{"excerpt":27095},{"type":12,"value":27096},[27097],[15,27098,26968],{},"\u002Fblog\u002F2023\u002F03\u002Fwhy-should-you-use-node-red-function-nodes",{"title":26962,"description":27092},{"loc":27099},"blog\u002F2023\u002F03\u002Fwhy-should-you-use-node-red-function-nodes","In this blog post, I will discuss some of the benefits and drawbacks of using Function nodes in your next Node-RED project.",[9832,6563,966],"sELa27z-t_WQNyIFR8gEqzXN8aYk4pWj5Nm--rFjQZQ",{"id":27107,"title":25172,"authors":27108,"body":27109,"cta":3,"date":27202,"description":26431,"extension":946,"image":25192,"lastUpdated":8478,"meta":27203,"navigation":253,"path":27208,"seo":27209,"sitemap":27210,"stem":27211,"subtitle":26431,"tags":27212,"tldr":3,"video":3,"__hash__":27213},"blog\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-3.md",[7451],{"type":12,"value":27110,"toc":27196},[27111,27113,27117,27120,27127,27131,27134,27140,27143,27149,27153,27156,27162,27165,27171,27175,27179,27187,27190],[15,27112,24948],{},[996,27114,27116],{"id":27115},"_1-the-exec-node-allows-you-to-interact-with-bash-from-node-red","1. The Exec node allows you to interact with BASH from Node-RED",[15,27118,27119],{},"Exec allows you to run Shell commands and receive the value back into your flow. This opens up almost any command which can be run on the host devices CLI to your Node-RED flows.",[15,27121,27122],{},[392,27123],{"alt":27124,"src":27125,"title":27126},"\"Example flow using the Exec node\"","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fexec-example.gif","Example flow using the Exec node",[996,27128,27130],{"id":27129},"_2-the-filter-node-helps-you-discard-duplicate-messages","2. The Filter node helps you discard duplicate messages",[15,27132,27133],{},"It can be useful to only allow messages to proceed through a flow where their value is unique. Filter makes that task simple, no need to store the past values and check each new message against a list.",[15,27135,27136],{},[392,27137],{"alt":27138,"src":27139,"title":27138},"Configuring the Filter node to only allow unique payloads through","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Ffilter-config.png",[15,27141,27142],{},"Once your filter is configured as shown above, try sending different payloads through to see the outcome.",[15,27144,27145],{},[392,27146],{"alt":27147,"src":27148,"title":27147},"Demonstration showing the Filter node","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Ffilter-example.gif",[996,27150,27152],{"id":27151},"_3-counting-the-amount-of-messages-sent-to-a-debug-node","3. Counting the amount of messages sent to a Debug node",[15,27154,27155],{},"The Debug node has a lot of great features that we don't see used that often. One example is the ability to show a count of how many messages have been sent to that Debug node since the last deploy.",[15,27157,27158],{},[392,27159],{"alt":27160,"src":27161,"title":27160},"Setting up the debug to count messages","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fsetup-counting-debug.png",[15,27163,27164],{},"Once you've setup the node as shown above, you will see a counter under the debug.",[15,27166,27167],{},[392,27168],{"alt":27169,"src":27170,"title":27169},"Each message sent to the debug node is counted","\u002Fblog\u002F2023\u002F03\u002Fimages\u002Fcounting-debug.gif",[15,27172,25143,27173,167],{},[307,27174,25147],{"href":25146},[34,27176,27178],{"id":27177},"effortless-communication-between-node-red-instances-with-flowfuse-project-nodes","Effortless Communication Between Node-RED Instances with FlowFuse Project Nodes",[15,27180,27181,27182,27186],{},"Managing communication between multiple Node-RED instances can be a complex task, but FlowFuse ",[307,27183,27185],{"href":27184},"\u002Fdocs\u002Fuser\u002Fprojectnodes\u002F","Project Nodes"," simplify this process dramatically. With these nodes, you can easily send messages between different Node-RED instances without worrying about complex configurations or network setup.",[15,27188,27189],{},"All you need to do is select the target instance by name, and FlowFuse takes care of the rest. This makes it faster and more efficient to handle multi-instance environments, ensuring seamless communication between flows across different devices or locations. Whether you're managing multiple environments or working on large-scale projects, FlowFuse Project Nodes save you time and reduce the risk of errors.",[15,27191,27192,27193,167],{},"FlowFuse continues to innovate, making collaboration and scalability in Node-RED projects even easier. To learn more about these features, check out the ",[307,27194,27195],{"href":213},"FlowFuse website",{"title":50,"searchDepth":250,"depth":250,"links":27197},[27198,27199,27200,27201],{"id":27115,"depth":221,"text":27116},{"id":27129,"depth":221,"text":27130},{"id":27151,"depth":221,"text":27152},{"id":27177,"depth":185,"text":27178},"2023-03-07",{"excerpt":27204},{"type":12,"value":27205},[27206],[15,27207,24948],{},"\u002Fblog\u002F2023\u002F03\u002F3-quick-node-red-tips-3",{"title":25172,"description":26431},{"loc":27208},"blog\u002F2023\u002F03\u002F3-quick-node-red-tips-3",[9832,6563,966],"7KTqvDVUBaGvQdx41zIToO1NAGFSUjEyLTjqU8cz7Hk",{"id":27215,"title":25184,"authors":27216,"body":27217,"cta":3,"date":27279,"description":27280,"extension":946,"image":25192,"lastUpdated":8478,"meta":27281,"navigation":253,"path":27286,"seo":27287,"sitemap":27288,"stem":27289,"subtitle":26431,"tags":27290,"tldr":3,"video":3,"__hash__":27291},"blog\u002Fblog\u002F2023\u002F02\u002F3-quick-node-red-tips-1.md",[7451],{"type":12,"value":27218,"toc":27274},[27219,27222,27226,27229,27235,27239,27247,27250,27256,27260,27263,27270],[15,27220,27221],{},"There is usually more than one way to complete a given task in software and Node-RED is no exception. In this blog post we are going to share three useful tips to save yourself time when working on your flows.",[996,27223,27225],{"id":27224},"_1-use-controlleft-click-to-search-your-nodes","1. Use control+left-click to search your nodes",[15,27227,27228],{},"Sometimes it's quicker to search for a node using its name rather than scrolling through the palette. Simply hold control then left-click to bring up a searchable list.",[15,27230,27231],{},[392,27232],{"alt":27233,"src":27234,"title":27233},"Selecting a node without having to use the palette","\u002Fblog\u002F2023\u002F02\u002Fimages\u002Fload-node.gif",[996,27236,27238],{"id":27237},"_2-split-sections-of-your-code-using-the-link-nodes","2. Split sections of your code using the link nodes",[15,27240,27241,27242,27246],{},"If you want to separate your flow into two distinct sections, link nodes are a great way to format your work. As we covered in our blog on ",[307,27243,27245],{"href":27244},"\u002Fblog\u002F2022\u002F12\u002Fnode-red-flow-best-practice","Node-RED best practices",", the combination of link nodes and grouped flows is very powerful.",[15,27248,27249],{},"To split your flow select the input and output nodes then right click, select 'Show Action List' and then type 'split'. Select 'Split wire with link nodes'.",[15,27251,27252],{},[392,27253],{"alt":27254,"src":27255,"title":27254},"Spliting your nodes with link nodes","\u002Fblog\u002F2023\u002F02\u002Fimages\u002Fsplit-with-link.gif",[996,27257,27259],{"id":27258},"_3-link-multiple-inputs-and-outputs-in-one-command","3. Link multiple inputs and outputs in one command",[15,27261,27262],{},"Once a switch node has several outputs it can be slow to manually wire each to the new node. Using the action menu (right click), select 'Show Action List' then 'Wire Node to Multiple' this option will join everything up in one step.",[15,27264,27265],{},[392,27266],{"alt":27267,"src":27268,"title":27269},"\"Linking multiple inputs and outputs in one command\"","\u002Fblog\u002F2023\u002F02\u002Fimages\u002Fjoin-wires.gif","Linking multiple inputs and outputs in one command",[15,27271,25143,27272,167],{},[307,27273,25147],{"href":25146},{"title":50,"searchDepth":250,"depth":250,"links":27275},[27276,27277,27278],{"id":27224,"depth":221,"text":27225},{"id":27237,"depth":221,"text":27238},{"id":27258,"depth":221,"text":27259},"2023-02-07","Learn three valuable Node-RED tips to enhance your workflow: search nodes efficiently, split code sections with link nodes, and link multiple inputs\u002Foutputs in one command.",{"excerpt":27282},{"type":12,"value":27283},[27284],[15,27285,27221],{},"\u002Fblog\u002F2023\u002F02\u002F3-quick-node-red-tips-1",{"title":25184,"description":27280},{"loc":27286},"blog\u002F2023\u002F02\u002F3-quick-node-red-tips-1",[9832,6563,966,9324],"XLF-zCCv0yviIZ4gnJNUV4BvHgcZk5XZ7Pc2rJMUzGw",{"id":27293,"title":27294,"authors":27295,"body":27296,"cta":27463,"date":27466,"description":27467,"extension":946,"image":3,"lastUpdated":948,"meta":27468,"navigation":253,"path":12668,"seo":27474,"sitemap":27475,"stem":27476,"subtitle":27477,"tags":27478,"tldr":27479,"video":3,"__hash__":27480},"blog\u002Fblog\u002F2023\u002F01\u002Fenvironment-variables-in-node-red.md","Using Environment Variables in Node-RED (2026)",[17901],{"type":12,"value":27297,"toc":27456},[27298,27301,27311,27325,27331,27349,27357,27361,27372,27386,27390,27393,27400,27404,27407,27414,27418],[15,27299,27300],{},"Programs, written with Node-RED or otherwise, need to sometimes retrieve information that wasn’t decided on during the creation of the program.",[15,27302,27303,27304,27306,27307,27310],{},"Contextual data like configuration, which user is executing the code, differentiate based on what device is executing a flow, or sometimes secrets which shouldn’t be exposed in the code. This is usually done through environment variables. These are pairs of strings, a key with an attached value, which are accessed by their key. Say you want to access an API endpoint with a key, you’d save the key as ",[19,27305,14689],{}," with the value set to ",[19,27308,27309],{},"yoursupersecretkey",". FlowFuse allows setting environment variables. Let’s start using them to understand how they work.",[15,27312,27313,27314,27316,27317,27320,27321,27324],{},"One of the options for the ",[19,27315,1339],{}," node is to inject a ",[19,27318,27319],{},"env variable",", short for; you guessed it: Environment Variable. In this case we’re going to one that’s pre-defined by Node-RED: ",[19,27322,27323],{},"NR_FLOW_NAME",". The name of each variable is in all caps by convention. When connecting this inject to a debug it prints “Flow 1” for me.",[15,27326,27327],{},[392,27328],{"alt":27329,"src":27330,"title":27329},"Using an environment variable in Node-RED","\u002Fblog\u002F2023\u002F01\u002Fimages\u002Fnode-red-use-env-var.png",[15,27332,27333,27334,1255,27336,27338,27339,27341,27342,27345,27346,27348],{},"Leveraging environment variables can also be done with other nodes, like for example ",[19,27335,1405],{},[19,27337,8722],{},". Note however; you can set the ",[19,27340,1339],{}," node to output the value for ",[19,27343,27344],{},"FOO"," even when it doesn’t exist, but it doesn’t allow you to check in the switch node for example if ",[19,27347,27344],{}," exists.",[15,27350,27351,27352,27356],{},"Node-RED allows you to set environment variables, but not to change them when executing flows. If you want to update data during execution, look into using ",[307,27353,27355],{"href":27354},"\u002Fdocs\u002Fuser\u002Fpersistent-context\u002F","persistent context",". Node-RED doesn’t support Environment Variables like other programming environments do. When the flow is deployed the environment variables are replaced with the known values at that time. This is the biggest gotcha for most developers.",[996,27358,27360],{"id":27359},"predefined-variables","Predefined variables",[15,27362,27363,27364,1255,27367,1262,27370,167],{},"Our first example was using a predefined variable, exposed by Node-RED. As of 3.0 it exposes a few environment variables among which ",[19,27365,27366],{},"NR_NODE_NAME",[19,27368,27369],{},"NR_GROUP_NAME",[19,27371,27323],{},[15,27373,27374,27376,27377,27380,27381,27385],{},[307,27375,1155],{"href":213}," extends this list with for example a ",[19,27378,27379],{},"FF_PROJECT_ID"," allowing you to for example understand what group of instances sent a certain message, but also sets them for each ",[307,27382,27384],{"href":27383},"\u002Fdocs\u002Fdevice-agent\u002Fintroduction\u002F","device agent",". This allows users to pinpoint which device sent a message, for example to update a dashboard accordingly.",[996,27387,27389],{"id":27388},"managing-environments-variables","Managing environments variables",[15,27391,27392],{},"In FlowFuse it’s easy to manage variables set for instances. Under settings in the environment tab it’s a form to set them. You’ll have to restart your instances to make them available in the cloud, and update the target snapshot for devices. When done, these are available.",[15,27394,27395],{},[392,27396],{"alt":27397,"src":27398,"title":27399},"\"Setting a environment variable in FlowFuse\"","\u002Fblog\u002F2023\u002F01\u002Fimages\u002Fflowforge-set-env-var.png","Setting a environment variable in FlowFuse",[34,27401,27403],{"id":27402},"boost-your-node-red-security-with-flowfuse","Boost Your Node-RED Security with FlowFuse",[15,27405,27406],{},"FlowFuse provides a comprehensive platform for managing and securing your Node-RED solutions. It includes advanced security features such as role-based access control, Multi-factor Authentication (MFA), Single Sign-On (SSO), and encryption to protect your data and enhance operational efficiency.",[15,27408,27409,27410,167],{},"Learn how FlowFuse can boost your Node-RED security and streamline management through the ",[307,27411,27413],{"href":27412},"\u002Fplatform\u002Fsecurity\u002F#application","FlowFuse security statement",[996,27415,27417],{"id":27416},"explore-more-on-security","Explore More on Security",[100,27419,27420,27426,27432,27438,27444,27450],{},[103,27421,27422],{},[307,27423,27425],{"href":27424},"\u002Fblog\u002F2024\u002F04\u002Frole-based-access-control-rbac-for-node-red-with-flowfuse\u002F","Role-Based Access Control (RBAC) for Node-RED with FlowFuse",[103,27427,27428],{},[307,27429,27431],{"href":27430},"\u002Fdocs\u002Fuser\u002Fdevops-pipelines\u002F#protected-instances","Protecting Instances from Being Modified",[103,27433,27434],{},[307,27435,27437],{"href":27436},"\u002Fblog\u002F2024\u002F07\u002Fhow-to-setup-sso-ldap-for-the-node-red\u002F","How to Set Up SSO LDAP for Node-RED",[103,27439,27440],{},[307,27441,27443],{"href":27442},"\u002Fblog\u002F2024\u002F07\u002Fhow-to-setup-sso-saml-for-the-node-red\u002F","How to Set Up SSO SAML for Node-RED",[103,27445,27446],{},[307,27447,27449],{"href":27448},"\u002Fblog\u002F2024\u002F03\u002Fhttp-authentication-node-red-with-flowfuse\u002F","Securing HTTP Traffic for Node-RED with FlowFuse",[103,27451,27452],{},[307,27453,27455],{"href":27454},"\u002Fblog\u002F2024\u002F01\u002Fsoc2\u002F","FlowFuse is now SOC 2 Type 1 Compliant",{"title":50,"searchDepth":250,"depth":250,"links":27457},[27458,27459,27460],{"id":27359,"depth":221,"text":27360},{"id":27388,"depth":221,"text":27389},{"id":27402,"depth":185,"text":27403,"children":27461},[27462],{"id":27416,"depth":221,"text":27417},{"type":941,"title":27464,"description":27465},"Manage Node-RED Environment Variables with FlowFuse","FlowFuse makes it easy to set, update, and secure environment variables across all your Node-RED instances and devices, with role-based access control and centralized management from a single dashboard.","2023-01-27","Node-RED supports environment variables (env vars) slight different, how to use it and the gotcha's are explained in this article.",{"keywords":27469,"excerpt":27470},"node-red environment variables, env vars node-red, node-red env variable, flowfuse environment variables, node-red configuration",{"type":12,"value":27471},[27472],[15,27473,27300],{},{"title":27294,"description":27467},{"loc":12668},"blog\u002F2023\u002F01\u002Fenvironment-variables-in-node-red","Predefined data to be used in your Node-RED instance",[9832,6563,966],"Node-RED environment variables are read-only key-value pairs resolved at deploy time, useful for storing configuration and secrets without hardcoding them in flows. Node-RED exposes predefined variables like NR_FLOW_NAME, and FlowFuse extends this with instance and device identifiers. You can set and manage variables for all your instances directly from the FlowFuse Environment settings tab.","bM2-H7R1Nkz_vefyaDrHkJLmTkQBgVJNFLY5lp0MhU0",{"id":27482,"title":27483,"authors":27484,"body":27485,"cta":3,"date":27726,"description":27727,"extension":946,"image":27728,"lastUpdated":27729,"meta":27730,"navigation":253,"path":27735,"seo":27736,"sitemap":27737,"stem":27738,"subtitle":17584,"tags":27739,"tldr":3,"video":3,"__hash__":27740},"blog\u002Fblog\u002F2023\u002F01\u002Fgetting-started-with-node-red.md","Getting Started with Node-RED",[7451],{"type":12,"value":27486,"toc":27714},[27487,27490,27493,27497,27500,27502,27505,27508,27510,27513,27529,27532,27541,27555,27559,27572,27591,27598,27610,27619,27626,27634,27641,27649,27653,27666,27670,27673,27677,27680,27684,27687,27695,27699,27712],[15,27488,27489],{},"Node-RED is a visual programming tool for working with IoT devices and web services. It allows users to create flows using a drag-and-drop interface, making it easy to connect different nodes together to build powerful automations.",[15,27491,27492],{},"In this blog post, we'll take a look at how to get started with Node-RED and create some basic flows. We'll also explore the palette manager, a powerful feature that allows users to install and manage additional nodes for Node-RED.",[996,27494,27496],{"id":27495},"installing-node-red","Installing Node-RED",[15,27498,27499],{},"First, you'll need to get an installation of Node-RED up and running. There are several ways to do this. We suggest using FlowFuse as it's very easy to get Node-RED running. You can also install Node-RED locally using npm (Node Package Manager), which comes with Node.js.",[4987,27501,1155],{"id":965},[15,27503,27504],{},"To get Node-RED running on FlowFuse [sign up as a new user]({% include \"sign-up-url.njk\" %}). New users are enrolled in a trial and a Node-RED instance will be started for you within a minute.",[15,27506,27507],{},"Once that instance has booted up you can access Node-RED by pressing \"Open Editor\".",[4987,27509,19116],{"id":19116},[15,27511,27512],{},"To install Node-RED locally using npm, open up your terminal and type the following command:",[42,27514,27516],{"className":7487,"code":27515,"language":7489,"meta":50,"style":50},"npm install -g node-red\n",[19,27517,27518],{"__ignoreMap":50},[146,27519,27520,27522,27524,27526],{"class":148,"line":149},[146,27521,19116],{"class":2435},[146,27523,7534],{"class":1554},[146,27525,19203],{"class":1554},[146,27527,27528],{"class":1554}," node-red\n",[15,27530,27531],{},"Once Node-RED is installed, you can start it by running the following command:",[42,27533,27535],{"className":7487,"code":27534,"language":7489,"meta":50,"style":50},"node-red\n",[19,27536,27537],{"__ignoreMap":50},[146,27538,27539],{"class":148,"line":149},[146,27540,27534],{"class":2435},[15,27542,27543,27544,27549,27550,167],{},"This will start the Node-RED server and open up the ",[307,27545,27548],{"href":27546,"rel":27547},"http:\u002F\u002Flocalhost:1880",[311],"editor in your web browser",". You can also specify a different port or a settings file if you want to. If you want to run Node-RED locally but manage it remotely through FlowFuse, check out ",[307,27551,27554],{"href":27552,"rel":27553},"https:\u002F\u002Fflowfuse.com\u002Fblog\u002F2025\u002F09\u002Finstalling-node-red\u002F",[311],"this guide",[996,27556,27558],{"id":27557},"first-flow","First Flow",[15,27560,27561,27562,1255,27565,27568,27569,27571],{},"Now that you have Node-RED running, let's take a look at how to create a simple flow. In this example, we'll create a simple \"Hello World\" endpoint. To do this, we'll use the ",[19,27563,27564],{},"http in",[19,27566,27567],{},"http response",", and the ",[19,27570,1405],{}," nodes, which can be found in the common nodes menu on the left of Node-RED.",[15,27573,27574,27575,27577,27578,27580,27581,1358,27583,27585,27586,1358,27588,27590],{},"First, drag an ",[19,27576,27564],{}," node into the editor. This node will listen for incoming HTTP requests. Next drag in the \"change\" and the ",[19,27579,27567],{}," node into the editor. Connect the ",[19,27582,27564],{},[19,27584,1405],{}," node and connect the ",[19,27587,1405],{},[19,27589,27567],{}," node. Your flow should look similar to this:",[15,27592,27593],{},[392,27594],{"alt":27595,"src":27596,"title":27597},"\"Screenshot showing the HTTP-in, Change, and HTTP-response nodes that we will be using throughout this blog for demonstration.\"","\u002Fblog\u002F2023\u002F01\u002Fimages\u002Fthree-nodes.png","Screenshot showing the HTTP-in, Change, and HTTP-response nodes that we will be using throughout this blog for demonstration.",[15,27599,27600,27601,27603,27604,27607,27608,167],{},"To configure the ",[19,27602,27564],{}," node, double-click on it to open its properties. Here, you can set the URL that the node will listen to, as well as the method (GET, POST, etc.). In this example, we'll set the URL to ",[19,27605,27606],{},"\u002Fhello"," and the method to ",[19,27609,12656],{},[15,27611,27612,27613,27615,27616,27618],{},"Now we need to set what the endpoint will respond with, we will do that in the ",[19,27614,1405],{}," node. Double-click the ",[19,27617,1405],{}," node then add \"Hello World\" to the field which says \"to the value\". It should look like this:",[15,27620,27621],{},[392,27622],{"alt":27623,"src":27624,"title":27625},"\"Configuring the change node to set the payload to Hello World\"","\u002Fblog\u002F2023\u002F01\u002Fimages\u002Fset-reply.png","Configuring the change node to set the payload to Hello World",[15,27627,27600,27628,27630,27631,27633],{},[19,27629,27567],{}," node, double-click on it to open its properties. Here, you should set the \"Status Code\" to be 200. This is not vital for the demo to work but it's good practice to return the correct codes when something connects to an API. Status code 200 means the API responded OK. This is how your ",[19,27632,27567],{}," node should look:",[15,27635,27636],{},[392,27637],{"alt":27638,"src":27639,"title":27640},"\"Configuring the status node to set the response to 200\"","\u002Fblog\u002F2023\u002F01\u002Fimages\u002Fresponse-code.png","Configuring the status node to set the response to 200",[15,27642,27643,27644,167],{},"You can read more about HTTP response codes in ",[307,27645,27648],{"href":27646,"rel":27647},"https:\u002F\u002Fen.wikipedia.org\u002Fwiki\u002FList_of_HTTP_status_codes",[311],"this article",[996,27650,27652],{"id":27651},"testing-your-flow","Testing Your Flow",[15,27654,27655,27656,27660,27661,27665],{},"Now that we have our flow set up, we can deploy it by clicking the \"Deploy\" button in the top right corner of the editor. Once the flow is deployed, you can test it by opening up a web browser. If you installed Node-RED using npm navigate to \"",[307,27657,27658],{"href":27658,"rel":27659},"http:\u002F\u002Flocalhost:1880\u002Fhello",[311],"\". If you are working on FlowFuse and running cloud hosted instance, use your instance URL with \"\u002Fhello\" added to the end, it should look something like \"",[307,27662,27663],{"href":27663,"rel":27664},"https:\u002F\u002Fyour-instance-name.flowfuse.cloud\u002Fhello",[311],"\". You should see \"Hello World!\" displayed in the browser.",[996,27667,27669],{"id":27668},"debug-output","Debug Output",[15,27671,27672],{},"One of the most powerful features in Node-RED is the ability to debug your flow. This can be done by adding a debug node to your flow and connecting it to the nodes you want to debug. When a message is sent through the connected node, the debug node will print the message in the debug sidebar on the right side of the editor. This can be very helpful when trying to understand how a flow is working or troubleshoot any issues.",[996,27674,27676],{"id":27675},"the-palette-manager","The Palette Manager",[15,27678,27679],{},"In addition to the built-in nodes, Node-RED also has a palette manager feature which allows users to easily install and manage additional nodes from the community. To access the palette manager, go to the menu in the top right corner and select \"Manage Palette\". Here, you can search for and install new nodes, as well as update or remove existing ones. This is a great way to extend the functionality of Node-RED and add new capabilities to your flows.",[996,27681,27683],{"id":27682},"import-the-flow","Import the flow",[15,27685,27686],{},"If you want to view this flow you can import it using the code below. Copy the code then select Import from the top right menu in Node-RED. Paste the code into the field then press Import.",[15,27688,6389,27689,3830],{},[146,27690,27691,27692,27694],{},"\n{\n\"id\": \"a742e7a95697bb40\",\n\"type\": \"http in\",\n\"z\": \"9e9af3caa4dc14d3\",\n\"name\": \"\",\n\"url\": \"\u002Fhello\",\n\"method\": \"get\",\n\"upload\": false,\n\"swaggerDoc\": \"\",\n\"x\": 180,\n\"y\": 200,\n\"wires\": [\n[\n\"883e7d597f7c7c4b\"\n]\n]\n},\n{\n\"id\": \"aca024dcb79bdb92\",\n\"type\": \"http response\",\n\"z\": \"9e9af3caa4dc14d3\",\n\"name\": \"\",\n\"statusCode\": \"200\",\n\"headers\": {},\n\"x\": 500,\n\"y\": 200,\n\"wires\": ",[146,27693],{},"\n},\n{\n\"id\": \"883e7d597f7c7c4b\",\n\"type\": \"change\",\n\"z\": \"9e9af3caa4dc14d3\",\n\"name\": \"\",\n\"rules\": [\n{\n\"t\": \"set\",\n\"p\": \"payload\",\n\"pt\": \"msg\",\n\"to\": \"Hello World\",\n\"tot\": \"str\"\n}\n],\n\"action\": \"\",\n\"property\": \"\",\n\"from\": \"\",\n\"to\": \"\",\n\"reg\": false,\n\"x\": 340,\n\"y\": 200,\n\"wires\": [\n[\n\"aca024dcb79bdb92\"\n]\n]\n}\n",[996,27696,27698],{"id":27697},"whats-next","What's Next?",[15,27700,27701,27702,27706,27707,167],{},"Well done, you've now got your first flow up and running. Enjoy using Node-RED and thanks for reading. Now if you want to start with your first beginner friendly project, building a weather dashboard is great, read this ",[307,27703,27705],{"href":27704},"\u002Fblog\u002F2025\u002F12\u002Fgetting-weather-data-in-node-red\u002F","article for getting started",". If you'd like to dive deeper into more Node-RED capabilities and how it can help in an enterprise setting, check out our ",[307,27708,27711],{"href":27709,"rel":27710},"https:\u002F\u002Fflowfuse.com\u002Febooks\u002Fbeginner-guide-to-a-professional-nodered\u002F",[311],"eBook The Ultimate Beginner Guide to a Professional Node-RED",[924,27713,11436],{},{"title":50,"searchDepth":250,"depth":250,"links":27715},[27716,27720,27721,27722,27723,27724,27725],{"id":27495,"depth":221,"text":27496,"children":27717},[27718,27719],{"id":965,"depth":250,"text":1155},{"id":19116,"depth":250,"text":19116},{"id":27557,"depth":221,"text":27558},{"id":27651,"depth":221,"text":27652},{"id":27668,"depth":221,"text":27669},{"id":27675,"depth":221,"text":27676},{"id":27682,"depth":221,"text":27683},{"id":27697,"depth":221,"text":27698},"2023-01-23","In this article we are going to explain the first things you need to know to get started with Node-RED.","\u002Fblog\u002F2023\u002F01\u002Fimages\u002Fgetting-started-nr.png","2025-12-16",{"excerpt":27731},{"type":12,"value":27732},[27733],[15,27734,27489],{},"\u002Fblog\u002F2023\u002F01\u002Fgetting-started-with-node-red",{"title":27483,"description":27727},{"loc":27735},"blog\u002F2023\u002F01\u002Fgetting-started-with-node-red",[9832,6563,966],"FXta9iv2Aw_bg1VgvncZrBUPJ_mHWDSymDvjLwvcCGk",{"id":27742,"title":27743,"authors":27744,"body":27745,"cta":3,"date":28101,"description":28102,"extension":946,"image":3,"lastUpdated":3,"meta":28103,"navigation":253,"path":28110,"seo":28111,"sitemap":28112,"stem":28113,"subtitle":28114,"tags":28115,"tldr":3,"video":3,"__hash__":28117},"blog\u002Fblog\u002F2022\u002F12\u002Fflowforge-gcp-https-set-up.md","Configure FlowFuse in Docker to secure all traffic",[7451],{"type":12,"value":27746,"toc":28094},[27747,27755,27759,27762,27769,27778,27787,27790,27792,27799,27803,27806,27812,27815,27833,27849,27952,27955,27978,27981,27996,28012,28028,28031,28035,28038,28043,28046,28061,28063,28067,28070,28076,28079,28085,28088,28091],[15,27748,27749,27750,27754],{},"Following on from our ",[307,27751,27753],{"href":27752},"\u002Fblog\u002F2022\u002F10\u002Fff-docker-gcp\u002F","previous article"," in which we covered how to run FlowFuse in Docker on Google’s Cloud Platform, today we are going to look at how to secure HTTP traffic to your FlowFuse server.",[996,27756,27758],{"id":27757},"introduction","Introduction",[15,27760,27761],{},"When we wrote the first part of this series FlowFuse didn't have an easy path to secure HTTP traffic. Happily, two versions of FlowFuse later and at least partially inspired by these blogs, we have added the configuration you need in Docker to use HTTPS with minimal work.",[15,27763,27764,27765,167],{},"That addition makes our job of explaining this setup a lot easier, credit to our developers for seeing the value of having an easy implementation of HTTPS in FlowFuse as part of our ",[307,27766,27768],{"href":27767},"\u002Fblog\u002F2022\u002F10\u002Fflowforge-1-released\u002F","1.0 build",[15,27770,27771,27772,27777],{},"To achieve secure HTTPS traffic we are employing a great service called ",[307,27773,27776],{"href":27774,"rel":27775},"https:\u002F\u002Fletsencrypt.org\u002F",[311],"Let's Encrypt",". In their own words, \"Let’s Encrypt is a free, automated, and open certificate authority (CA), run for the public’s benefit\". In practice Let's Encrypt will allow us to easily secure HTTPS traffic.",[15,27779,27780,27781,27786],{},"We are also utilising a Docker image called ",[307,27782,27785],{"href":27783,"rel":27784},"https:\u002F\u002Fgithub.com\u002Fnginx-proxy\u002Facme-companion",[311],"acme-companion"," which makes the configuration of Let's Encrypt a breeze. To quote the project's own Github page \"acme-companion is a lightweight companion container for nginx-proxy. It handles the automated creation, renewal and use of SSL certificates for proxied Docker containers through the ACME protocol\". It's a great project and credit to the team over there for making it a lot easier to secure the internet.",[15,27788,27789],{},"Now we've covered our goals and the tools we are going to use let's configure our existing GCP VM to secure all web traffic.",[996,27791,1163],{"id":1162},[15,27793,27794,27795,167],{},"As mentioned above, you will need to be running FlowFuse version 1.0 or higher to follow this guide. If you are using an older version you can upgrade now using the ",[307,27796,27798],{"href":27797},"\u002Fdocs\u002Fupgrade\u002F","instructions here",[996,27800,27802],{"id":27801},"update-docker-compose","Update Docker Compose",[15,27804,27805],{},"The first step is to edit our Docker compose file. We're using Nano again to edit files so we will run this command:",[42,27807,27810],{"className":27808,"code":27809,"language":47},[45],"sudo nano \u002Fopt\u002Fflowforge\u002Fdocker-compose-1.1.1\u002Fdocker-compose.yml\n",[19,27811,27809],{"__ignoreMap":50},[15,27813,27814],{},"In the docker-compose.yml file, un-comment the following lines:",[42,27816,27820],{"className":27817,"code":27818,"language":27819,"meta":50,"style":50},"language-yaml shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","- \".\u002Fcerts:\u002Fetc\u002Fnginx\u002Fcerts\"\n","yaml",[19,27821,27822],{"__ignoreMap":50},[146,27823,27824,27826,27828,27831],{"class":148,"line":149},[146,27825,5653],{"class":160},[146,27827,1830],{"class":160},[146,27829,27830],{"class":1554},".\u002Fcerts:\u002Fetc\u002Fnginx\u002Fcerts",[146,27832,2561],{"class":160},[42,27834,27836],{"className":27817,"code":27835,"language":27819,"meta":50,"style":50},"- \"443:443\"\n",[19,27837,27838],{"__ignoreMap":50},[146,27839,27840,27842,27844,27847],{"class":148,"line":149},[146,27841,5653],{"class":160},[146,27843,1830],{"class":160},[146,27845,27846],{"class":1554},"443:443",[146,27848,2561],{"class":160},[42,27850,27852],{"className":27817,"code":27851,"language":27819,"meta":50,"style":50}," acme:\n    image: nginxproxy\u002Facme-companion\n    volumes:\n      - \"\u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock:ro\"\n      - \".\u002Facme:\u002Fetc\u002Facme.sh\"\n    volumes_from:\n      - nginx:rw\n    environment:\n      - \"DEFAULT_EMAIL=mail@example.com\"\n    depends_on:\n      - \"nginx\"\n",[19,27853,27854,27862,27872,27879,27891,27902,27909,27916,27923,27934,27941],{"__ignoreMap":50},[146,27855,27856,27859],{"class":148,"line":149},[146,27857,27858],{"class":1549}," acme",[146,27860,27861],{"class":160},":\n",[146,27863,27864,27867,27869],{"class":148,"line":185},[146,27865,27866],{"class":1549},"    image",[146,27868,730],{"class":160},[146,27870,27871],{"class":1554}," nginxproxy\u002Facme-companion\n",[146,27873,27874,27877],{"class":148,"line":221},[146,27875,27876],{"class":1549},"    volumes",[146,27878,27861],{"class":160},[146,27880,27881,27884,27886,27889],{"class":148,"line":250},[146,27882,27883],{"class":160},"      -",[146,27885,1830],{"class":160},[146,27887,27888],{"class":1554},"\u002Fvar\u002Frun\u002Fdocker.sock:\u002Fvar\u002Frun\u002Fdocker.sock:ro",[146,27890,2561],{"class":160},[146,27892,27893,27895,27897,27900],{"class":148,"line":257},[146,27894,27883],{"class":160},[146,27896,1830],{"class":160},[146,27898,27899],{"class":1554},".\u002Facme:\u002Fetc\u002Facme.sh",[146,27901,2561],{"class":160},[146,27903,27904,27907],{"class":148,"line":284},[146,27905,27906],{"class":1549},"    volumes_from",[146,27908,27861],{"class":160},[146,27910,27911,27913],{"class":148,"line":666},[146,27912,27883],{"class":160},[146,27914,27915],{"class":1554}," nginx:rw\n",[146,27917,27918,27921],{"class":148,"line":1603},[146,27919,27920],{"class":1549},"    environment",[146,27922,27861],{"class":160},[146,27924,27925,27927,27929,27932],{"class":148,"line":1608},[146,27926,27883],{"class":160},[146,27928,1830],{"class":160},[146,27930,27931],{"class":1554},"DEFAULT_EMAIL=mail@example.com",[146,27933,2561],{"class":160},[146,27935,27936,27939],{"class":148,"line":1624},[146,27937,27938],{"class":1549},"    depends_on",[146,27940,27861],{"class":160},[146,27942,27943,27945,27947,27950],{"class":148,"line":2546},[146,27944,27883],{"class":160},[146,27946,1830],{"class":160},[146,27948,27949],{"class":1554},"nginx",[146,27951,2561],{"class":160},[15,27953,27954],{},"We should also redirect all traffic to use HTTPS, to do that un-comment the following in the nginx service section:",[42,27956,27958],{"className":27817,"code":27957,"language":27819,"meta":50,"style":50},"Environment:\n      - \"HTTPS_METHOD=redirect\"\n",[19,27959,27960,27967],{"__ignoreMap":50},[146,27961,27962,27965],{"class":148,"line":149},[146,27963,27964],{"class":1549},"Environment",[146,27966,27861],{"class":160},[146,27968,27969,27971,27973,27976],{"class":148,"line":185},[146,27970,27883],{"class":160},[146,27972,1830],{"class":160},[146,27974,27975],{"class":1554},"HTTPS_METHOD=redirect",[146,27977,2561],{"class":160},[15,27979,27980],{},"We now need to add the configuration for LetsEncrypt, edit the following lines to include a valid email address and the correct domain for where you are hosting your FlowFuse server:",[42,27982,27984],{"className":27817,"code":27983,"language":27819,"meta":50,"style":50},"- \"DEFAULT_EMAIL=mail@example.com\"\n",[19,27985,27986],{"__ignoreMap":50},[146,27987,27988,27990,27992,27994],{"class":148,"line":149},[146,27989,5653],{"class":160},[146,27991,1830],{"class":160},[146,27993,27931],{"class":1554},[146,27995,2561],{"class":160},[42,27997,27999],{"className":27817,"code":27998,"language":27819,"meta":50,"style":50},"- \"LETSENCRYPT_HOST=mqtt.example.com\"\n",[19,28000,28001],{"__ignoreMap":50},[146,28002,28003,28005,28007,28010],{"class":148,"line":149},[146,28004,5653],{"class":160},[146,28006,1830],{"class":160},[146,28008,28009],{"class":1554},"LETSENCRYPT_HOST=mqtt.example.com",[146,28011,2561],{"class":160},[42,28013,28015],{"className":27817,"code":28014,"language":27819,"meta":50,"style":50},"- \"LETSENCRYPT_HOST=forge.example.com\"\n",[19,28016,28017],{"__ignoreMap":50},[146,28018,28019,28021,28023,28026],{"class":148,"line":149},[146,28020,5653],{"class":160},[146,28022,1830],{"class":160},[146,28024,28025],{"class":1554},"LETSENCRYPT_HOST=forge.example.com",[146,28027,2561],{"class":160},[15,28029,28030],{},"Save and exit from that file, in Nano you can do that by pressing ‘control x’ then ‘y’ then the Return key.",[996,28032,28034],{"id":28033},"update-flowforgeyml","Update flowforge.yml",[15,28036,28037],{},"Next, we need to edit the public_url for the MQTT broker:",[42,28039,28041],{"className":28040,"code":27809,"language":47},[45],[19,28042,27809],{"__ignoreMap":50},[15,28044,28045],{},"Then replace ws:\u002F\u002F with wss:\u002F\u002F",[42,28047,28049],{"className":27817,"code":28048,"language":27819,"meta":50,"style":50},"public_url: wss:\u002F\u002Fmqtt.flowforge-demo.com\n",[19,28050,28051],{"__ignoreMap":50},[146,28052,28053,28056,28058],{"class":148,"line":149},[146,28054,28055],{"class":1549},"public_url",[146,28057,730],{"class":160},[146,28059,28060],{"class":1554}," wss:\u002F\u002Fmqtt.flowforge-demo.com\n",[15,28062,28030],{},[996,28064,28066],{"id":28065},"restart-your-docker-containers","Restart your Docker containers",[15,28068,28069],{},"OK, we should be ready to restart the Docker containers, run the command:",[42,28071,28074],{"className":28072,"code":28073,"language":47},[45],"sudo docker compose -p flowforge up -d\n",[19,28075,28073],{"__ignoreMap":50},[15,28077,28078],{},"If you reload your FlowFuse root directory in a web browser you should now see that your traffic is encrypted using LetsEncypt.",[15,28080,28081],{},[392,28082],{"alt":28083,"src":28084,"title":28083},"A screenshot from Safari web browser showing that the traffic to FlowFuse is encrypted","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fhttps-working.png",[15,28086,28087],{},"Nice! That’s it, you can now access your FlowFuse installation securely.",[15,28089,28090],{},"In the next and final part of this series of articles, we are going to look at how we can actually use FlowFuse including how to build flows and deploy and update them on Devices linked to a project.",[924,28092,28093],{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":50,"searchDepth":250,"depth":250,"links":28095},[28096,28097,28098,28099,28100],{"id":27757,"depth":221,"text":27758},{"id":1162,"depth":221,"text":1163},{"id":27801,"depth":221,"text":27802},{"id":28033,"depth":221,"text":28034},{"id":28065,"depth":221,"text":28066},"2022-12-09","Discover how to effortlessly secure all web traffic for your FlowFuse server using Let's Encrypt and Acme Companion with Docker. Encrypt HTTP traffic for secure communication.",{"excerpt":28104},{"type":12,"value":28105},[28106],[15,28107,27749,28108,27754],{},[307,28109,27753],{"href":27752},"\u002Fblog\u002F2022\u002F12\u002Fflowforge-gcp-https-set-up",{"title":27743,"description":28102},{"loc":28110},"blog\u002F2022\u002F12\u002Fflowforge-gcp-https-set-up","Use Let's Encrypt and Acme Companion to quickly set up FlowFuse to encrypt all traffic",[9832,965,966,28116,27776],"Acme Companion","NqCq6gAJVtYu2lVQryttXRSR6-rEasylLMdgmegGbjI",{"id":28119,"title":28120,"authors":28121,"body":28122,"cta":3,"date":28210,"description":28211,"extension":946,"image":3,"lastUpdated":3,"meta":28212,"navigation":253,"path":28217,"seo":28218,"sitemap":28219,"stem":28220,"subtitle":28221,"tags":28222,"tldr":3,"video":3,"__hash__":28223},"blog\u002Fblog\u002F2022\u002F12\u002Fcreate-http-trigger-with-authentication.md","Create HTTP triggers with authentication",[7451],{"type":12,"value":28123,"toc":28206},[28124,28127,28130,28134,28141,28144,28150,28153,28159,28162,28168,28171,28175,28178,28181,28187,28197,28203],[15,28125,28126],{},"Having an HTTP endpoint trigger your flows is very useful. From any browser or command line you now have the ability to trigger your flows.",[15,28128,28129],{},"Doing so safely with authentication is slightly harder, but not a lot. FlowFuse makes it rather easy to accomplish.",[996,28131,28133],{"id":28132},"creating-the-http-flow","Creating the HTTP flow",[15,28135,28136,28137,28140],{},"When you start a project on FlowFuse, remember the project name. For this how-to we’ll use ",[19,28138,28139],{},"example",". Open the editor and drag in the HTTP In node as well as the HTTP response node. Connect them, and add a debug node, which is connected to the “HTTP in” node.",[15,28142,28143],{},"First off; let’s set the HTTP in node properties:",[15,28145,28146],{},[392,28147],{"alt":28148,"src":28149,"title":28148},"Shows the UI to edit the node's properties","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fedit-http-node.png",[15,28151,28152],{},"You can import this flow into your own project if you’d like:",[42,28154,28157],{"className":28155,"code":28156,"language":47},[45],"[{\"id\":\"4faa84d37a52bb28\",\"type\":\"group\",\"z\":\"3c6e2dc732ada815\",\"name\":\"Allow HTTP Post request to trigger a flow\",\"style\":{\"label\":!0},\"nodes\":[\"1fa26e0ed3ddec1a\",\"45a180052e1a2f43\",\"09347881f4fa4057\"],\"x\":34,\"y\":79,\"w\":472,\"h\":122},{\"id\":\"1fa26e0ed3ddec1a\",\"type\":\"http in\",\"z\":\"3c6e2dc732ada815\",\"g\":\"4faa84d37a52bb28\",\"name\":\"HTTP Trigger\",\"url\":\"\u002Fhttp-trigger\",\"method\":\"post\",\"upload\":!1,\"swaggerDoc\":\"\",\"x\":130,\"y\":120,\"wires\":[[\"45a180052e1a2f43\",\"09347881f4fa4057\"]]},{\"id\":\"45a180052e1a2f43\",\"type\":\"http response\",\"z\":\"3c6e2dc732ada815\",\"g\":\"4faa84d37a52bb28\",\"name\":\"Empty HTTP response\",\"statusCode\":\"200\",\"headers\":{},\"x\":360,\"y\":120,\"wires\":[]},{\"id\":\"09347881f4fa4057\",\"type\":\"debug\",\"z\":\"3c6e2dc732ada815\",\"g\":\"4faa84d37a52bb28\",\"name\":\"Print HTTP Request\",\"active\":!0,\"tosidebar\":!0,\"console\":!1,\"tostatus\":!1,\"complete\":\"payload\",\"targetType\":\"msg\",\"statusVal\":\"\",\"statusType\":\"auto\",\"x\":360,\"y\":160,\"wires\":[]}]\n",[19,28158,28156],{"__ignoreMap":50},[15,28160,28161],{},"Now I’ve opened a terminal and executed:",[42,28163,28166],{"className":28164,"code":28165,"language":47},[45],"curl -X POST https:\u002F\u002Fexample.flowforge.com\u002Fhttp-trigger\n",[19,28167,28165],{"__ignoreMap":50},[15,28169,28170],{},"When there’s no output, that means it’s all good! There should be an empty message in the debug console in the Node-RED editor though",[996,28172,28174],{"id":28173},"securing-the-http-trigger-with-a-username-and-password","Securing the HTTP trigger with a username and password",[15,28176,28177],{},"The problem with our trigger is that anyone with internet access could trigger it. That’s not a great idea. So let’s secure this endpoint with HTTP Basic Authentication. There are various ways to include a secure endpoint in Node-RED, we’ve built authentication directly into FlowFuse to make it easier for all users. On the FlowFuse project, go to settings and then to ‘Editor’. Under the section HTTP Auth you can set a username and password. You should generate both by a random string generator, and store the credentials somewhere safe.  Restart the project to have the runtime pick up the changes, and the endpoint is secured!",[15,28179,28180],{},"Let’s validate the endpoint that worked a minute ago doesn’t anymore:",[42,28182,28185],{"className":28183,"code":28184,"language":47},[45],"curl -X POST https:\u002F\u002Fexample.flowforge.cloud\u002Fhttp-trigger\n=> Unauthorized\n",[19,28186,28184],{"__ignoreMap":50},[15,28188,28189,28190],{},"Let’s get it working again: (replace ",[28191,28192,63,28193],"username",{},[28194,28195,28196],"password",{}," with the details from the sticky note)",[42,28198,28201],{"className":28199,"code":28200,"language":47},[45],"curl -X POST https:\u002F\u002F\u003Cusername>:\u003Cpassword>@example.flowforge.cloud\u002Fhttp-trigger\n",[19,28202,28200],{"__ignoreMap":50},[15,28204,28205],{},"That’s it! You now have a flow that’s protected by a username and password combination!",{"title":50,"searchDepth":250,"depth":250,"links":28207},[28208,28209],{"id":28132,"depth":221,"text":28133},{"id":28173,"depth":221,"text":28174},"2022-12-07","Learn how to create HTTP triggers with authentication in Node-RED using FlowFuse. Securely trigger flows from any browser or command line while safeguarding your endpoints with HTTP Basic Authentication.",{"excerpt":28213},{"type":12,"value":28214},[28215],[15,28216,28126],{},"\u002Fblog\u002F2022\u002F12\u002Fcreate-http-trigger-with-authentication",{"title":28120,"description":28211},{"loc":28217},"blog\u002F2022\u002F12\u002Fcreate-http-trigger-with-authentication","From any browser or command line you now have the ability to securely trigger your flows",[9832,6563,966],"LvBjij9rgFLTrm1-7c41ZgogAarP8Uy5Wajn2rsPh9Q",{"id":28225,"title":28226,"authors":28227,"body":28228,"cta":3,"date":28525,"description":28526,"extension":946,"image":3,"lastUpdated":3,"meta":28527,"navigation":253,"path":27244,"seo":28532,"sitemap":28533,"stem":28534,"subtitle":28535,"tags":28536,"tldr":3,"video":3,"__hash__":28537},"blog\u002Fblog\u002F2022\u002F12\u002Fnode-red-flow-best-practice.md","Format your Node-RED flows for better team collaboration",[7451],{"type":12,"value":28229,"toc":28492},[28230,28233,28236,28240,28249,28253,28260,28264,28271,28275,28278,28282,28289,28293,28300,28304,28307,28311,28318,28322,28329,28333,28336,28340,28347,28351,28358,28362,28365,28369,28376,28380,28387,28395,28398,28402,28409,28413,28420,28424,28427,28431,28438,28442,28449,28453,28462,28482,28487,28489],[15,28231,28232],{},"When it comes to working on Node-RED flows as part of a team, there are a few best practices that can make things go more smoothly.",[15,28234,28235],{},"From formatting your flows for readability to providing clear comments on nodes and groups, a little bit of effort upfront can save your team a lot of headaches down the road. In this post, we'll cover some of the main things to keep in mind when working on Node-RED flows as part of a team.",[996,28237,28239],{"id":28238},"give-your-groups-descriptive-names","Give your groups descriptive names",[15,28241,28242,28243,28248],{},"Let’s start with ",[307,28244,28247],{"href":28245,"rel":28246},"https:\u002F\u002Fnodered.org\u002Fdocs\u002Fuser-guide\u002Feditor\u002Fworkspace\u002Fgroups",[311],"grouping your flows"," and giving each group a clear explanation of what it does. Compare the first to the second example below and consider how much more quickly you can understand what the flow is doing.",[4987,28250,28252],{"id":28251},"this-is-not-helpful-time-doesnt-tell-you-enough-to-understand-the-flows-purpose","This is not helpful, 'Time' doesn't tell you enough to understand the flow's purpose.",[15,28254,28255],{},[392,28256],{"alt":28257,"src":28258,"title":28259},"\"Screenshot showing the example of flow having the bad group name\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fname-bad.png","Screenshot showing the example of flow having the bad group name",[4987,28261,28263],{"id":28262},"this-is-much-better-we-know-what-the-flow-is-doing-without-inspecting-the-nodes","This is much better, we know what the flow is doing without inspecting the nodes.",[15,28265,28266],{},[392,28267],{"alt":28268,"src":28269,"title":28270},"\"Screenshot showing the example of flow having the good group name\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fname-good.png","Screenshot showing the example of flow having the good group name",[996,28272,28274],{"id":28273},"explain-what-your-switches-do","Explain what your switches do",[15,28276,28277],{},"Try to make it obvious what each switch does without having to open the node editor. Ask a question in the switch's name and make a positive answer the top connection out.",[4987,28279,28281],{"id":28280},"this-is-not-easy-to-understand-what-does-the-switch-do","This is not easy to understand, what does the switch do?",[15,28283,28284],{},[392,28285],{"alt":28286,"src":28287,"title":28288},"\"Screenshot showing the example of flow having the switch with bad name\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fswitch-bad.png","Screenshot showing the example of flow having the switch with bad name",[4987,28290,28292],{"id":28291},"this-is-a-lot-better-we-can-see-that-the-top-debug-should-be-triggered","This is a lot better, we can see that the top debug should be triggered.",[15,28294,28295],{},[392,28296],{"alt":28297,"src":28298,"title":28299},"\"Screenshot showing the example of flow having the switch with good name\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fswitch-good.png","Screenshot showing the example of flow having the switch with good name",[996,28301,28303],{"id":28302},"where-possible-your-flows-should-work-down-the-canvas","Where possible your flows should work down the canvas",[15,28305,28306],{},"It makes it so much easier to understand what happens and in which order if your flows start at the top of the canvas and work down to the bottom.",[4987,28308,28310],{"id":28309},"this-is-almost-unreadable-its-very-hard-to-work-out-the-order-of-the-groups","This is almost unreadable, it's very hard to work out the order of the groups.",[15,28312,28313],{},[392,28314],{"alt":28315,"src":28316,"title":28317},"\"Screenshot showing an example of flow that doesn't work down the canvas\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fflowdown-bad.png","Screenshot showing an example of flow that doesn't work down the canvas",[4987,28319,28321],{"id":28320},"where-as-this-is-so-much-easier-to-understand","Where as this is so much easier to understand.",[15,28323,28324],{},[392,28325],{"alt":28326,"src":28327,"title":28328},"\"Screenshot showing an example of flow that works down the canvas\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fflowdown-good.png","Screenshot showing an example of flow that works down the canvas",[996,28330,28332],{"id":28331},"use-link-nodes-rather-than-wires-to-join-groups","Use link nodes rather than wires to join groups",[15,28334,28335],{},"Groups should not be joined using wires, it just looks untidy and quickly reduces readability of your flows.",[4987,28337,28339],{"id":28338},"the-wire-is-blocking-the-title-it-only-gets-worse-as-you-add-more-wires","The wire is blocking the title, it only gets worse as you add more wires.",[15,28341,28342],{},[392,28343],{"alt":28344,"src":28345,"title":28346},"\"Screenshot showing an example of flow with wires blocking group titles\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Flink-bad.png","Screenshot showing an example of flow with wires blocking group titles",[4987,28348,28350],{"id":28349},"you-can-see-the-group-titles-easily-now","You can see the group titles easily now.",[15,28352,28353],{},[392,28354],{"alt":28355,"src":28356,"title":28357},"\"Screenshot showing an example of flow with link nodes improving readability\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Flink-good.png","Screenshot showing an example of flow with link nodes improving readability",[996,28359,28361],{"id":28360},"keep-your-groups-compact","Keep your groups compact",[15,28363,28364],{},"Keeping your groups compact will save time when reading the flow. This is especially helpful if when viewed on a smaller screen.",[4987,28366,28368],{"id":28367},"consider-how-hard-a-flow-made-of-groups-spaced-out-like-this-would-be-to-read-on-a-smaller-laptop-screen","Consider how hard a flow made of groups spaced out like this would be to read on a smaller laptop screen.",[15,28370,28371],{},[392,28372],{"alt":28373,"src":28374,"title":28375},"\"Screenshot showing an example of flow with widely spaced groups\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fcompact-bad.png","Screenshot showing an example of flow with widely spaced groups",[4987,28377,28379],{"id":28378},"this-now-takes-up-less-space-and-is-arguably-easier-to-read-on-any-screen-size","This now takes up less space and is arguably easier to read on any screen size.",[15,28381,28382],{},[392,28383],{"alt":28384,"src":28385,"title":28386},"\"Screenshot showing an example of flow with compact groups\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fcompact-good.png","Screenshot showing an example of flow with compact groups",[996,28388,28390,28391,28394],{"id":28389},"dont-cross-beams-wires","Don’t cross ",[18945,28392,28393],{},"beams"," wires",[15,28396,28397],{},"Crossed wires are not only hard to read, they can lead to misinterpretation of what a flow actually does. Where possible don’t cross your wires, where you can’t avoid it try to make sure it’s easy for the reader to understand where wires cross as rather than join.",[4987,28399,28401],{"id":28400},"this-is-confusing-which-change-node-does-the-top-switch-output-link-to","This is confusing, which change node does the top switch output link to?",[15,28403,28404],{},[392,28405],{"alt":28406,"src":28407,"title":28408},"\"Screenshot showing an example of the flow with nodes having crossed beams\u002Fwires\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fwires-bad.png","Screenshot showing an example of the flow with nodes having crossed beams\u002Fwires",[4987,28410,28412],{"id":28411},"this-is-better-much-less-chance-of-confusing-the-change-nodes","This is better, much less chance of confusing the change nodes.",[15,28414,28415],{},[392,28416],{"alt":28417,"src":28418,"title":28419},"\"Screenshot showing an example of the flow with nodes having correctly linked beams\u002Fwires\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fwires-good.png","Screenshot showing an example of the flow with nodes having correctly linked beams\u002Fwires",[996,28421,28423],{"id":28422},"dont-use-link-nodes-in-groups-where-avoidable","Don’t use link nodes in groups where avoidable",[15,28425,28426],{},"Excessive link nodes within groups can make a flow much harder to understand, where possible use wires to join nodes within a group.",[4987,28428,28430],{"id":28429},"this-is-hard-to-read-and-you-will-end-up-checking-the-link-nodes-again-and-again","This is hard to read and you will end up checking the link nodes again and again.",[15,28432,28433],{},[392,28434],{"alt":28435,"src":28436,"title":28437},"\"Screenshot showing the example of flow having the uneccessary link nodes\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fgroupwires-bad.png","Screenshot showing the example of flow having the uneccessary link nodes",[4987,28439,28441],{"id":28440},"functionally-identical-to-the-example-above-it-should-only-take-a-few-seconds-to-understand-this-flow-now","Functionally identical to the example above, it should only take a few seconds to understand this flow now.",[15,28443,28444],{},[392,28445],{"alt":28446,"src":28447,"title":28448},"\"Screenshot showing an example of the flow with the avoided link nodes\"","\u002Fblog\u002F2022\u002F12\u002Fimages\u002Fgroupwires-good.png","Screenshot showing an example of the flow with the avoided link nodes",[996,28450,28452],{"id":28451},"boost-collaboration-with-flowfuse","Boost Collaboration with FlowFuse",[15,28454,28455,28457,28458,167],{},[307,28456,1155],{"href":213}," is a cloud-based platform that makes working together on Node-RED projects easier and more efficient. It’s trusted by industries like manufacturing and smart building management, as well as textiles, to improve their systems. For more information, refer to our ",[307,28459,28461],{"href":28460},"\u002Fcustomer-stories\u002F","customer stories",[15,28463,28464,28465,28469,28470,28475,28476,28481],{},"With FlowFuse, you can quickly ",[307,28466,28468],{"href":28467},"\u002Fdocs\u002Fuser\u002Fteam\u002F","set up and manage teams",", giving each member the right level of access. It keeps all your ",[307,28471,28474],{"href":28472,"rel":28473},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=KOnQnR7yfT0&list=PLpcyqc7kNgp3nRacWBJ9JUVUJqtTjXdvh&index=2",[311],"Node-RED instances organized in one place",", so your team can collaborate seamlessly. Plus, it features ",[307,28477,28480],{"href":28478,"rel":28479},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=m2Onip4Lf4w",[311],"snapshots",", which let you restore previous versions of your flows if something goes wrong. FlowFuse simplifies team collaboration, making it easier to manage and work on Node-RED projects.",[15,28483,28484],{},[338,28485,28486],{},"[Sign up]({% include \"sign-up-url.njk\" %}) now for a free trial and experience FlowFuse's powerful collaboration tools!",[996,28488,6518],{"id":6517},[15,28490,28491],{},"Working on Node-RED flows as part of a team doesn't have to be a headache. By following some simple best practices you can make collaboration smooth sailing for everyone involved. So next time you're starting work on a new Node-RED flow, remember these tips and make life easier for yourself and your teammates.",{"title":50,"searchDepth":250,"depth":250,"links":28493},[28494,28498,28502,28506,28510,28514,28519,28523,28524],{"id":28238,"depth":221,"text":28239,"children":28495},[28496,28497],{"id":28251,"depth":250,"text":28252},{"id":28262,"depth":250,"text":28263},{"id":28273,"depth":221,"text":28274,"children":28499},[28500,28501],{"id":28280,"depth":250,"text":28281},{"id":28291,"depth":250,"text":28292},{"id":28302,"depth":221,"text":28303,"children":28503},[28504,28505],{"id":28309,"depth":250,"text":28310},{"id":28320,"depth":250,"text":28321},{"id":28331,"depth":221,"text":28332,"children":28507},[28508,28509],{"id":28338,"depth":250,"text":28339},{"id":28349,"depth":250,"text":28350},{"id":28360,"depth":221,"text":28361,"children":28511},[28512,28513],{"id":28367,"depth":250,"text":28368},{"id":28378,"depth":250,"text":28379},{"id":28389,"depth":221,"text":28515,"children":28516},"Don’t cross beams wires",[28517,28518],{"id":28400,"depth":250,"text":28401},{"id":28411,"depth":250,"text":28412},{"id":28422,"depth":221,"text":28423,"children":28520},[28521,28522],{"id":28429,"depth":250,"text":28430},{"id":28440,"depth":250,"text":28441},{"id":28451,"depth":221,"text":28452},{"id":6517,"depth":221,"text":6518},"2022-12-05","Learn best practices for formatting Node-RED flows to enhance team collaboration. From descriptive group names to clear switch explanations, optimize your flows for readability and efficiency.",{"excerpt":28528},{"type":12,"value":28529},[28530],[15,28531,28232],{},{"title":28226,"description":28526},{"loc":27244},"blog\u002F2022\u002F12\u002Fnode-red-flow-best-practice","From formatting your flows for readability to providing clear comments on nodes and groups, a little bit of effort upfront can save your team a lot of headaches down the road",[9832,6563,966],"NMl9GCMXjWnUIs7QE2JKv88DWGjVTJx_uyZd3rhVUQ8",{"id":28539,"title":28540,"authors":28541,"body":28542,"cta":3,"date":28649,"description":28650,"extension":946,"image":3,"lastUpdated":3,"meta":28651,"navigation":253,"path":28656,"seo":28657,"sitemap":28658,"stem":28659,"subtitle":28660,"tags":28661,"tldr":3,"video":3,"__hash__":28662},"blog\u002Fblog\u002F2022\u002F11\u002Fscaling-node-red-with-diy-tooling.md","Challenges scaling Node-RED with DIY tooling",[17901],{"type":12,"value":28543,"toc":28642},[28544,28547,28550,28554,28563,28567,28574,28577,28581,28584,28587,28591,28597,28608,28617,28620,28622,28628,28635],[15,28545,28546],{},"In this post, I'm going to share some of the challenges customers face when\nscaling Node-RED with Do-It-Yourself tooling.",[15,28548,28549],{},"Specifically, we'll talk about\ncommon threads in their journey building their own tooling around Node-RED, its\nflows, and deploying them. Node-RED is a visual programming environment for\nwiring together hardware devices, APIs and online services in a single\napplication. It's great because it's flexible enough to be used by both\nbeginners and experts alike; however, going from one instance of Node-RED to 100\nisn't for the faint-hearted.",[996,28551,28553],{"id":28552},"zero-to-hero-in-a-few-days","Zero to hero in a few days",[15,28555,28556,28557,28562],{},"If you’re new to Node-RED and are just getting started, the first step is simple:\ngo to ",[307,28558,28561],{"href":28559,"rel":28560},"https:\u002F\u002Fnodered.org",[311],"nodered.org",", download, install and run. There’s no\nneed to configure anything or set up credentials or security or alerts. You can\nget started with Node-RED straight away by simply running it on your machine.\nThe guides and scripts provided on Node-RED or are more than enough to get\nstarted. You'll dive right in and start developing your flows.",[996,28564,28566],{"id":28565},"onto-the-second-instance","Onto the second instance",[15,28568,28569,28570,28573],{},"The next instance is a simple copy of the first. You'll need to make sure that\nyou are installing the same version on both instances, and that any\nconfiguration files (such as the ",[19,28571,28572],{},"settings.js",") are also in sync.",[15,28575,28576],{},"The second instance improves the first equally. Docs are read again,\nimprovements are made, and copied over. Life's good! Although there's a slight\nitch to start automating the setup, it's ignored. Just too many open questions\non how to achieve it: write scripts in bash? Or can we use Node-RED to manage\nNode-RED? Automation can wait, there's new flows to implement!",[996,28578,28580],{"id":28579},"wake-up-call-how-many-of-them-do-we-have","Wake-up call; how many of them do we have?",[15,28582,28583],{},"There's nothing like a good ol' wake-up call to get you back on track. One of\nour team members asked a questions about a buggy flow you've got no recollection\nof. During the investigation there's issues left and right; it's running a much\nolder version, missing standard packages, the settings are out of date, and the\ntimezone not set to UTC? Adoption of Node-RED is going quite well, it's useful\nand effective. Gets the job done without fuss. But now there's toil in\nmaintaining them; ensure the right tooling is build, properly documented, it\nneeds dashboards and overviews, lots of work to be done. Not quite business\nrelated, but Node-RED is important for the company, the bosses sure would\napprove spending 2 months building these tools!",[15,28585,28586],{},"But then something happened, there was a higher priority project to be picked up.\nSome tooling could be written for a couple of hours a month, but not two\ndedicated months to get it in tip top shape. Better than nothing! Built a\ndashboard in Node-RED to keep track of all devices, there's some scripts and a\nfew flows that aid in monitoring and maintenance. Scaling further is possible,\nbut confidence in the tooling isn't sky high.",[996,28588,28590],{"id":28589},"data-extraction-at-scale-now-theres-over-100","Data extraction at scale; now there's over 100",[15,28592,28593,28594,28596],{},"At ",[307,28595,1155],{"href":213}," we've got regular conversations with customers managing 100s of\ndevices. Scaling to that many devices and runtimes requires hours of development\neach week alongside monitoring, maintenance, and auditing.",[15,28598,28599,28600,28603,28604],{},"As a side effect of investing more time into Node-RED and its ecosystem the\norganization has developed a few standards. Standard custom nodes that are\npre-installed (👋 ",[19,28601,28602],{},"moment.js","), a style guide published for developing flows,\nmaybe even flow linting: ",[307,28605,28606],{"href":28606,"rel":28607},"https:\u002F\u002Fgithub.com\u002Fnode-red\u002Fnrlint",[311],[15,28609,28610,28611,28616],{},"The security model is fairly decent. One just hopes the\n",[307,28612,28615],{"href":28613,"rel":28614},"https:\u002F\u002Fnl.wikipedia.org\u002Fwiki\u002FChief_Information_Security_Officer",[311],"CISO"," doesn't\ninspects them, but it's a fair bet they won't; we're far away from the\nheadquarters, right?",[15,28618,28619],{},"Many other edge cases itch in the back of our heads, but we can't focus on those right now.",[996,28621,6518],{"id":6517},[15,28623,28624,28625,28627],{},"Node-RED is a great tool, it's got many built-in features that make it easy to\nget started with no coding experience necessary. Running it at scale, in a\nproduction environment can require a lot of sys-ops and dev-ops time and we\nthink ",[307,28626,1155],{"href":213}," is a great solution to keep that admin and tech debt in check.",[15,28629,28630,28631,167],{},"If you're running into these challenges we believe we can help, you can adopt\nour ",[307,28632,28634],{"href":28633},"\u002Fdocs\u002Finstall\u002F","free and open source edition",[15,28636,28637,28638,28641],{},"Additionally, to get a head start or enhance your current setup, explore our ",[307,28639,28640],{"href":13573},"Beginner's Guide to Professional Node-RED",". This comprehensive ebook offers a clear overview of Node-RED’s capabilities and practical tips for making the most of its features.",{"title":50,"searchDepth":250,"depth":250,"links":28643},[28644,28645,28646,28647,28648],{"id":28552,"depth":221,"text":28553},{"id":28565,"depth":221,"text":28566},{"id":28579,"depth":221,"text":28580},{"id":28589,"depth":221,"text":28590},{"id":6517,"depth":221,"text":6518},"2022-11-30","In this post, I'm going to share some of the challenges customers face when scaling Node-RED with Do-It-Yourself tooling",{"excerpt":28652},{"type":12,"value":28653},[28654],[15,28655,28546],{},"\u002Fblog\u002F2022\u002F11\u002Fscaling-node-red-with-diy-tooling",{"title":28540,"description":28650},{"loc":28656},"blog\u002F2022\u002F11\u002Fscaling-node-red-with-diy-tooling","Node-RED is very easy to get up and running for your first instance but what about your 100th?",[9832,6563,966],"HGhIh7rFl0dqN1uJQUp0ZRTepzg6rGqGXsuqF4gY6-w",{"id":28664,"title":28665,"authors":28666,"body":28667,"cta":3,"date":29029,"description":29030,"extension":946,"image":3,"lastUpdated":3,"meta":29031,"navigation":253,"path":29036,"seo":29037,"sitemap":29038,"stem":29039,"subtitle":29040,"tags":29041,"tldr":3,"video":3,"__hash__":29042},"blog\u002Fblog\u002F2022\u002F10\u002Fff-docker-gcp.md","Install FlowFuse Docker on Google Cloud",[7451],{"type":12,"value":28668,"toc":29027},[28669,28672,28675,28678,28694,28700,28703,28717,28721,28728,28731,28738,28741,28748,28751,28758,28761,28768,28771,28775,28778,28784,28791,28797,28800,28804,28807,28813,28816,28820,28826,28831,28836,28842,28846,28851,28858,28862,28865,28868,28873,28876,28881,28884,28889,28896,28903,28907,28910,28915,28918,28923,28926,28933,28936,28943,28949,28956,28959,28966,28971,28974,28981,28983,28987,28990,28995,28998,29003,29009,29012,29015,29021,29024],[15,28670,28671],{},"As part of our preparations for FlowFuse 1.0 we have been testing various real world scenarios to see where we can add to our documentation and where we might be able to improve our releases to make the install process easier for users. As a benefit of that testing we have been able to hone these installation processes and we wanted to share one of those with you today.",[15,28673,28674],{},"In this first of three articles, we are going to run through the process for installing FlowFuse on Google Cloud Platform (GCP) within a virtual machine (VM) using Docker.",[15,28676,28677],{},"We have set ourselves the goal of delivering a production environment. We want this installation benefit from:",[100,28679,28680,28683,28686],{},[103,28681,28682],{},"Email alerts (emails to users when they are added to teams etc)",[103,28684,28685],{},"HTTPS access to the install",[103,28687,28688,28689,28693],{},"FlowFuse ",[307,28690,28692],{"href":28691},"\u002Fdocs\u002Fuser\u002Fconcepts\u002F#device","Device"," deployment via the included MQTT server that comes in our Docker build",[15,28695,28696,28697,167],{},"We will follow up with a second article covering the process of getting HTTPS running then we will close out the series by covering how to use key features of FlowFuse including ",[307,28698,28699],{"href":28691},"Devices",[28701,28702,1163],"h1",{"id":1162},[100,28704,28705,28708,28711,28714],{},[103,28706,28707],{},"A domain name - We've registered flowforge-demo.com to demonstrate these steps",[103,28709,28710],{},"A DNS provider - Our Domain registrar provides a basic DNS service for free",[103,28712,28713],{},"A GCP account - Google will often give you free service credits on sign up so setting up FlowFuse on GCP should not cost you anything for at least a few weeks",[103,28715,28716],{},"An email provider which will allow SMTP connections to send email - To manage users on your FlowFuse platform you will need to be able to send emails to them. We have used a Google Workspace account for this purpose",[28701,28718,28720],{"id":28719},"gcp-vm-creation","GCP VM Creation",[15,28722,28723,28724,167],{},"Create a GCP account, once logged in navigate to Compute Engine then VM Instances. Select Create Instance you should now be ",[307,28725,9386],{"href":28726,"rel":28727},"https:\u002F\u002Fconsole.cloud.google.com\u002Fcompute\u002FinstancesAdd?project",[311],[15,28729,28730],{},"Give your instance a name, select a Region and Zone. I have found that the default machine configuration works fine but depending on your project you may wish to change the resources.",[15,28732,28733],{},[392,28734],{"alt":28735,"src":28736,"title":28737},"\"Screenshot showing the interface for creating GCP VM\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F1.png","Screenshot showing the interface for creating GCP VM",[15,28739,28740],{},"You now need to allow access to your FlowFuse installation from the internet. In the Firewall section tick Allow HTTP traffic and Allow HTTPS traffic.",[15,28742,28743],{},[392,28744],{"alt":28745,"src":28746,"title":28747},"\"Screenshot showing the firewall section in the interface for creating a GCP VM\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F2.png","Screenshot showing the firewall section in the interface for creating a GCP VM",[15,28749,28750],{},"Next up, assign a static IP address to the VM. Click Advanced options, then Networking. Now scroll down until you see Network interfaces and click on default to expand that section. In External IPv4 address select Create IP Address, give it a name than press Reserve.",[15,28752,28753],{},[392,28754],{"alt":28755,"src":28756,"title":28757},"\"Screenshot showing the network section in the interface for creating a GCP VM\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F3.png","Screenshot showing the network section in the interface for creating a GCP VM",[15,28759,28760],{},"Once you have reserved your IP it will be shown in the External IPv4 address field, write it down as we will need it later to create the DNS records. Our IP address was 34.125.156.130.",[15,28762,28763],{},[392,28764],{"alt":28765,"src":28766,"title":28767},"\"Screenshot showing your reserved IP in the External IPv4 address field\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F4.png","Screenshot showing your reserved IP in the External IPv4 address field",[15,28769,28770],{},"You are now ready to create and boot your VM, scroll to the bottom of the page and press Create. It can take a minute or two for the VM to be ready to use.",[28701,28772,28774],{"id":28773},"dns-set-up","DNS Set Up",[15,28776,28777],{},"So that you can run FlowFuse on your newly created GCP VM you will need to set up 2 DNS records. These records are slightly different to what is suggested in the FlowFuse install docs. We were keen to be able to run other services on this domain so we set up the following records.",[15,28779,28780],{},[392,28781],{"alt":28782,"src":28783,"title":28782},"Screenshot showing interface for setting DNS","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F5.png",[15,28785,28786,28787,28790],{},"DNS changes need to propagate, and depending on your DNS provider, ISP, and other factors, this can take anywhere between a few seconds to 4 hours. Our’s were in place very quickly. To validate the DNS records you can use ",[19,28788,28789],{},"dig"," on either a Mac or Linux.",[15,28792,28793],{},[392,28794],{"alt":28795,"src":28796,"title":28795},"Screenshot showing output of the dig command","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F6.png",[15,28798,28799],{},"The DNS records are set to the IP record we noted down earlier, so we're good to continue.",[28701,28801,28803],{"id":28802},"flowfuse-docker-installation","FlowFuse Docker Installation",[15,28805,28806],{},"The next step is to install Docker on our GCP VM. If you return to GCP you should see that your VM is now up and running, you can now click on SSH to connect to your VM. This will open up a browser based SSH session to your VM.",[15,28808,28809],{},[392,28810],{"alt":28811,"src":28812,"title":28811},"Screenshot showing access to SSH in GCP","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F7.png",[15,28814,28815],{},"Once you have a Secure Shell (SSH) session open, the first step is to install Docker using the following commands.",[15,28817,28818],{},[19,28819,26657],{},[42,28821,28824],{"className":28822,"code":28823,"language":47},[45],"sudo apt-get install \\\n   ca-certificates \\\n   curl \\\n   gnupg \\\n   lsb-release\n",[19,28825,28823],{"__ignoreMap":50},[15,28827,28828],{},[19,28829,28830],{},"sudo mkdir -p \u002Fetc\u002Fapt\u002Fkeyrings",[15,28832,28833],{},[19,28834,28835],{},"curl -fsSL https:\u002F\u002Fdownload.docker.com\u002Flinux\u002Fdebian\u002Fgpg | sudo gpg --dearmor -o \u002Fetc\u002Fapt\u002Fkeyrings\u002Fdocker.gpg",[42,28837,28840],{"className":28838,"code":28839,"language":47},[45],"echo \\\n  \"deb [arch=$(dpkg --print-architecture) signed-by=\u002Fetc\u002Fapt\u002Fkeyrings\u002Fdocker.gpg] https:\u002F\u002Fdownload.docker.com\u002Flinux\u002Fdebian \\\n  $(lsb_release -cs) stable\" | sudo tee \u002Fetc\u002Fapt\u002Fsources.list.d\u002Fdocker.list > \u002Fdev\u002Fnull\n",[19,28841,28839],{"__ignoreMap":50},[15,28843,28844],{},[19,28845,26657],{},[15,28847,28848],{},[19,28849,28850],{},"sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin",[15,28852,28853,28854,167],{},"You can read a lot more detail about what each these commands actually do ",[307,28855,9386],{"href":28856,"rel":28857},"https:\u002F\u002Fdocs.docker.com\u002Fengine\u002Finstall\u002Fdebian\u002F",[311],[28701,28859,28861],{"id":28860},"download-flowfuses-latest-docker-build","Download FlowFuse’s latest Docker build",[15,28863,28864],{},"The next step is to get the codebase for FlowFuse onto your VM, to do so you will need to run the following commands. Please note that we are working with our 0.10.0 build, you will need to update the version number in the commands below if you are working with a newer build.",[15,28866,28867],{},"Use curl to download the files we need.",[15,28869,28870],{},[19,28871,28872],{},"sudo curl -L https:\u002F\u002Fgithub.com\u002FFlowFuse\u002Fdocker-compose\u002Farchive\u002Frefs\u002Ftags\u002Fv0.10.1.tar.gz -o v0.10.1.tar.gz",[15,28874,28875],{},"Make the directory where we will store FlowFuse.",[15,28877,28878],{},[19,28879,28880],{},"sudo mkdir \u002Fopt\u002Fflowforge",[15,28882,28883],{},"Uncompress FlowFuse and save it to the directory.",[15,28885,28886],{},[19,28887,28888],{},"sudo tar zxf v0.10.1.tar.gz --directory \u002Fopt\u002Fflowforge",[15,28890,28891,28892,28895],{},"You should now have all the code you need for FlowFuse in the directory ",[19,28893,28894],{},"\u002Fopt\u002Fflowforge\u002Fdocker-compose-0.10.1",", it should look something like this.",[15,28897,28898],{},[392,28899],{"alt":28900,"src":28901,"title":28902},"\"Screenshot showing directory listing for FlowFuse\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F8.png","Screenshot showing directory listing for FlowFuse",[28701,28904,28906],{"id":28905},"configure-flowfuse","Configure FlowFuse",[15,28908,28909],{},"We can now configure FlowFuse on your VM. We are going to need to edit two files. Firstly we need to switch into the directory where we just placed FlowFuse.",[15,28911,28912],{},[19,28913,28914],{},"cd \u002Fopt\u002Fflowforge\u002Fdocker-compose-0.10.1",[15,28916,28917],{},"Then we need to edit the flowforge.yml file, we're using Nano to do that.",[15,28919,28920],{},[19,28921,28922],{},"sudo nano \u002Fopt\u002Fflowforge\u002Fdocker-compose-0.10.1\u002Fetc\u002Fflowforge.yml",[15,28924,28925],{},"At the top of the file you need to update the domain and base_url to match your domain",[15,28927,28928],{},[392,28929],{"alt":28930,"src":28931,"title":28932},"\"Screenshot showing domain configuration in flowforge.yml\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F9.png","Screenshot showing domain configuration in flowforge.yml",[15,28934,28935],{},"Next we will need to edit the Email Configuration section to match your SMTP provider. Set enabled to true then add in the details provider by your email provider. For example in this case I am using our Google Workspace account.",[15,28937,28938],{},[392,28939],{"alt":28940,"src":28941,"title":28942},"\"Screenshot showing email configuration in flowforge.yml\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F10.png","Screenshot showing email configuration in flowforge.yml",[15,28944,28945,28946,28948],{},"Finally, you need to update the ",[19,28947,28055],{}," for your mqtt broker to match your DNS record.",[15,28950,28951],{},[392,28952],{"alt":28953,"src":28954,"title":28955},"\"Screenshot showing MQTT configuration in flowforge.yml\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F11.png","Screenshot showing MQTT configuration in flowforge.yml",[15,28957,28958],{},"You can now save and close that file, in Nano you can do that by pressing ‘control x’ then ‘y’ then the Return key.",[15,28960,28961,28962,28965],{},"Now we need to edit the ",[19,28963,28964],{},"docker-compose.yml"," file. We will use Nano again to do that.",[15,28967,28968],{},[19,28969,28970],{},"sudo nano \u002Fopt\u002Fflowforge\u002Fdocker-compose-0.10.1\u002Fdocker-compose.yml",[15,28972,28973],{},"We need to edit the file to add in to the domain as follows.",[15,28975,28976],{},[392,28977],{"alt":28978,"src":28979,"title":28980},"\"Screenshot showing virtual hosts configuration in docker-compose.yml\"","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F12.png","Screenshot showing virtual hosts configuration in docker-compose.yml",[15,28982,28030],{},[28701,28984,28986],{"id":28985},"start-flowfuse","Start FlowFuse",[15,28988,28989],{},"We are now ready to start up FlowFuse for the first time, to do so we will use the following command.",[15,28991,28992],{},[19,28993,28994],{},"sudo docker compose -p flowforge up -d",[15,28996,28997],{},"The build process will take a few minutes, once it’s completed let’s make sure all the docker containers are running.",[15,28999,29000],{},[19,29001,29002],{},"sudo docker ps",[15,29004,29005],{},[392,29006],{"alt":29007,"src":29008},"Docker PS output","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F13.png",[15,29010,29011],{},"You should see 4 running Docker containers.",[15,29013,29014],{},"If everything went well you should now be able to access your FlowFuse server via the DNS record you created.",[15,29016,29017],{},[392,29018],{"alt":29019,"src":29020},"FF Login page","\u002Fblog\u002F2022\u002F10\u002Fimages\u002F14.png",[15,29022,29023],{},"Nice, you now have a working instance of FlowFuse running on GCP but remember that all traffic is currently running on HTTP so we still have some work to do.",[15,29025,29026],{},"In the next article we will cover how to add HTTPS support to this FlowFuse installation.",{"title":50,"searchDepth":250,"depth":250,"links":29028},[],"2022-10-14","Learn to deploy FlowFuse Docker on Google Cloud Platform. Achieve a production-ready environment with email alerts, HTTPS access, and MQTT server deployment.",{"excerpt":29032},{"type":12,"value":29033},[29034],[15,29035,28671],{},"\u002Fblog\u002F2022\u002F10\u002Fff-docker-gcp",{"title":28665,"description":29030},{"loc":29036},"blog\u002F2022\u002F10\u002Fff-docker-gcp","Step by step instructions to get FlowFuse Docker running on Google Cloud",[9832,965,966],"vpQgtMjZ0XPcg37TeXTpD8JGch9wW0g6ruDPh32IcGU",1785528669166]