[{"data":1,"prerenderedAt":1367},["ShallowReactive",2],{"blog-\u002Fblog\u002F2026\u002F03\u002Fhow-to-parse-binary-data-serial-devices":3},{"id":4,"title":5,"body":6,"description":12,"extension":1357,"meta":1358,"navigation":439,"path":1363,"seo":1364,"stem":1365,"__hash__":1366},"blog\u002Fblog\u002F2026\u002F03\u002Fhow-to-parse-binary-data-serial-devices.md","How to Parse Binary Data from Serial Devices",{"type":7,"value":8,"toc":1340},"minimark",[9,13,16,19,24,27,34,40,46,52,55,59,62,65,174,183,186,196,199,288,292,301,325,330,333,362,365,369,400,403,407,413,677,680,684,697,702,729,734,754,759,780,785,804,809,829,840,850,854,868,871,891,894,912,919,980,985,989,992,996,999,1168,1171,1175,1178,1181,1184,1187,1212,1216,1219,1226,1282,1285,1293,1301,1309,1314,1317,1320,1323,1327,1330,1333,1336],[10,11,12],"p",{},"Your device is sending bytes. You don't know what they mean. The device manual is a 40-page PDF from 2003, and page 12 has a table of numbers with no explanation of what to do with them.",[10,14,15],{},"This is the situation most engineers hit when they connect a legacy serial device for the first time. The serial connection works. The bytes arrive. But nothing tells you what those bytes represent, and there is no standard to fall back on the way there is with Modbus.",[10,17,18],{},"This article gives you a repeatable method for decoding binary output from any serial device. We'll use an industrial weighing scale as the example. By the end, you'll know exactly how to approach your own device, regardless of what it sends.",[20,21,23],"h2",{"id":22},"the-method","The Method",[10,25,26],{},"Every binary parsing problem follows the same four steps.",[10,28,29,33],{},[30,31,32],"strong",{},"Read the manual."," Find the communication protocol section, not the installation guide. You are looking for the frame structure, the byte order, and the data types for each field. If the manual calls it a \"data format\" or \"output format\" section, that is the one. Everything else depends on this.",[10,35,36,39],{},[30,37,38],{},"Identify the frame type."," Frames from serial devices arrive in one of three structures. Fixed length frames are the simplest — every message is always the same number of bytes. Delimiter terminated frames end with a specific byte or sequence. Length prefixed frames include a byte early in the message that tells you how many bytes follow. Knowing which type your device uses determines how you validate and reassemble frames before parsing.",[10,41,42,45],{},[30,43,44],{},"Validate before you parse."," Every frame should pass a basic sanity check before your parsing logic runs. At minimum, verify the frame length and any start or end markers your device includes. If your device provides a checksum, verify that too. A frame that fails validation gets dropped. A corrupted value that passes through and ends up on a dashboard or in a database is far more damaging.",[10,47,48,51],{},[30,49,50],{},"Map the bytes."," Once a frame passes validation, assign every byte a meaning based on the manual. Name, type, offset, endianness, scale. This is where FlowFuse's Buffer Parser node does its work, visually, without code, with every field visible at a glance.",[10,53,54],{},"The rest of this article walks through all four steps using a concrete example.",[20,56,58],{"id":57},"the-example-industrial-weighing-scale","The Example: Industrial Weighing Scale",[10,60,61],{},"We'll use a common industrial weighing scale that outputs data over RS-232. You'll find this type of device in packaging lines, logistics warehouses, and food processing plants. It has no Modbus support. It sends its own fixed binary frame every time a stable reading is available.",[10,63,64],{},"The manual specifies a fixed-length frame of exactly 10 bytes:",[66,67,68,84],"table",{},[69,70,71],"thead",{},[72,73,74,78,81],"tr",{},[75,76,77],"th",{},"Byte",[75,79,80],{},"Value",[75,82,83],{},"Description",[85,86,87,102,113,129,140,151,161],"tbody",{},[72,88,89,93,99],{},[90,91,92],"td",{},"0",[90,94,95],{},[96,97,98],"code",{},"0x02",[90,100,101],{},"STX, start of frame",[72,103,104,107,110],{},[90,105,106],{},"1–4",[90,108,109],{},"32-bit float",[90,111,112],{},"Weight value, little-endian",[72,114,115,118,126],{},[90,116,117],{},"5",[90,119,120,123,124],{},[96,121,122],{},"0x01"," or ",[96,125,98],{},[90,127,128],{},"Unit indicator, 1 = kg, 2 = lb",[72,130,131,134,137],{},[90,132,133],{},"6",[90,135,136],{},"Bitfield",[90,138,139],{},"Status flags",[72,141,142,145,148],{},[90,143,144],{},"7",[90,146,147],{},"—",[90,149,150],{},"Reserved",[72,152,153,156,158],{},[90,154,155],{},"8",[90,157,77],{},[90,159,160],{},"Checksum, sum of bytes 1–7 truncated to one byte",[72,162,163,166,171],{},[90,164,165],{},"9",[90,167,168],{},[96,169,170],{},"0x03",[90,172,173],{},"ETX, end of frame",[10,175,176,177,179,180,182],{},"Byte 0 is always ",[96,178,98],{},". Byte 9 is always ",[96,181,170],{},". These are your frame markers. Bytes 1 through 4 carry the weight as a 32-bit IEEE 754 float in little-endian order, meaning the least significant byte comes first. Byte 5 tells you whether the scale is set to kilograms or pounds. Byte 6 is a status bitfield where individual bits carry meaning — bit 0 is stable, bit 1 is overload, bit 2 is zero.",[10,184,185],{},"This is the raw buffer the scale sends for a 23.5 kg stable reading:",[187,188,193],"pre",{"className":189,"code":191,"language":192},[190],"language-text","\u003CBuffer 02 00 00 bb 41 01 03 00 5f 03>\n","text",[96,194,191],{"__ignoreMap":195},"",[10,197,198],{},"By the end of this article, that buffer becomes:",[187,200,204],{"className":201,"code":202,"language":203,"meta":195,"style":195},"language-json shiki shiki-themes github-light github-dark","{\n  \"weight\": 23.5,\n  \"unit\": \"kg\",\n  \"stable\": true,\n  \"overload\": false,\n  \"zero\": false\n}\n","json",[96,205,206,215,231,245,258,271,282],{"__ignoreMap":195},[207,208,211],"span",{"class":209,"line":210},"line",1,[207,212,214],{"class":213},"sVt8B","{\n",[207,216,218,222,225,228],{"class":209,"line":217},2,[207,219,221],{"class":220},"sj4cs","  \"weight\"",[207,223,224],{"class":213},": ",[207,226,227],{"class":220},"23.5",[207,229,230],{"class":213},",\n",[207,232,234,237,239,243],{"class":209,"line":233},3,[207,235,236],{"class":220},"  \"unit\"",[207,238,224],{"class":213},[207,240,242],{"class":241},"sZZnC","\"kg\"",[207,244,230],{"class":213},[207,246,248,251,253,256],{"class":209,"line":247},4,[207,249,250],{"class":220},"  \"stable\"",[207,252,224],{"class":213},[207,254,255],{"class":220},"true",[207,257,230],{"class":213},[207,259,261,264,266,269],{"class":209,"line":260},5,[207,262,263],{"class":220},"  \"overload\"",[207,265,224],{"class":213},[207,267,268],{"class":220},"false",[207,270,230],{"class":213},[207,272,274,277,279],{"class":209,"line":273},6,[207,275,276],{"class":220},"  \"zero\"",[207,278,224],{"class":213},[207,280,281],{"class":220},"false\n",[207,283,285],{"class":209,"line":284},7,[207,286,287],{"class":213},"}\n",[20,289,291],{"id":290},"building-the-flow-in-flowfuse","Building the Flow in FlowFuse",[10,293,294,295,300],{},"We'll simulate the device output using an Inject node so you can follow along without hardware. In a real deployment, the Inject node is replaced by a Serial In node receiving bytes directly from the device. The parsing logic, the validation, the Buffer Parser configuration — all of it stays identical. The only difference is where the bytes come from. If you want to set up the actual serial connection, ",[296,297,299],"a",{"href":298},"\u002Fblog\u002F2025\u002F07\u002Fconnect-legacy-equipment-serial-flowfuse\u002F","this article covers that",".",[10,302,303,304,307,308,307,311,307,314,317,318,321,322],{},"The flow is: ",[30,305,306],{},"Inject node"," connected to a ",[30,309,310],{},"Function node",[30,312,313],{},"Buffer Parser node",[30,315,316],{},"Switch node",", each output of the Switch connected to its own ",[30,319,320],{},"Change node",", both Change nodes connected to a ",[30,323,324],{},"Debug node.",[326,327,329],"h3",{"id":328},"step-1-install-the-buffer-parser-node","Step 1: Install the Buffer Parser Node",[10,331,332],{},"The Buffer Parser node is not included in Node-RED by default. To install it:",[334,335,336,340,343,354,357],"ol",{},[337,338,339],"li",{},"Open your Node-RED editor",[337,341,342],{},"Click the hamburger menu (top right)",[337,344,345,346,349,350,353],{},"Go to ",[30,347,348],{},"Manage palette"," → ",[30,351,352],{},"Install"," tab",[337,355,356],{},"Search for node-red-contrib-buffer-parser",[337,358,359,360],{},"Click ",[30,361,352],{},[10,363,364],{},"Once installed, the node will appear in your palette and you can proceed with building the flow. If you're on FlowFuse, your administrator may have already added it to your shared palette — check before installing.",[326,366,368],{"id":367},"step-2-simulate-the-device","Step 2: Simulate the Device",[334,370,371,378,389,395],{},[337,372,373,374,377],{},"Drag an ",[30,375,376],{},"Inject"," node onto the canvas and double-click it",[337,379,380,381,384,385,388],{},"Set ",[30,382,383],{},"msg.payload"," type to ",[30,386,387],{},"Buffer"," from the dropdown",[337,390,391,392],{},"Enter: ",[96,393,394],{},"[2,0,0,188,65,1,3,0,1,3]",[337,396,359,397],{},[30,398,399],{},"Done",[10,401,402],{},"Every time you click the Inject button, the flow receives these 10 bytes exactly as it would from a live serial connection.",[326,404,406],{"id":405},"step-3-validate-the-frame","Step 3: Validate the Frame",[10,408,409,410,412],{},"Add a ",[30,411,310],{}," and paste in the following. This is the only JavaScript in the entire flow.",[187,414,418],{"className":415,"code":416,"language":417,"meta":195,"style":195},"language-javascript shiki shiki-themes github-light github-dark","const buf = msg.payload;\n\n\u002F\u002F Check frame length\nif (buf.length !== 10) {\n    return null;\n}\n\n\u002F\u002F Check start and end markers\nif (buf[0] !== 0x02 || buf[9] !== 0x03) {\n    return null;\n}\n\n\u002F\u002F Validate checksum\nlet sum = 0;\nfor (let i = 1; i \u003C= 7; i++) {\n    sum += buf[i];\n}\nif ((sum & 0xFF) !== buf[8]) {\n    return null;\n}\n\nreturn msg;\n","javascript",[96,419,420,435,441,447,467,478,482,486,492,528,537,542,547,553,570,606,618,623,649,658,663,668],{"__ignoreMap":195},[207,421,422,426,429,432],{"class":209,"line":210},[207,423,425],{"class":424},"szBVR","const",[207,427,428],{"class":220}," buf",[207,430,431],{"class":424}," =",[207,433,434],{"class":213}," msg.payload;\n",[207,436,437],{"class":209,"line":217},[207,438,440],{"emptyLinePlaceholder":439},true,"\n",[207,442,443],{"class":209,"line":233},[207,444,446],{"class":445},"sJ8bj","\u002F\u002F Check frame length\n",[207,448,449,452,455,458,461,464],{"class":209,"line":247},[207,450,451],{"class":424},"if",[207,453,454],{"class":213}," (buf.",[207,456,457],{"class":220},"length",[207,459,460],{"class":424}," !==",[207,462,463],{"class":220}," 10",[207,465,466],{"class":213},") {\n",[207,468,469,472,475],{"class":209,"line":260},[207,470,471],{"class":424},"    return",[207,473,474],{"class":220}," null",[207,476,477],{"class":213},";\n",[207,479,480],{"class":209,"line":273},[207,481,287],{"class":213},[207,483,484],{"class":209,"line":284},[207,485,440],{"emptyLinePlaceholder":439},[207,487,489],{"class":209,"line":488},8,[207,490,491],{"class":445},"\u002F\u002F Check start and end markers\n",[207,493,495,497,500,502,505,508,511,514,517,519,521,523,526],{"class":209,"line":494},9,[207,496,451],{"class":424},[207,498,499],{"class":213}," (buf[",[207,501,92],{"class":220},[207,503,504],{"class":213},"] ",[207,506,507],{"class":424},"!==",[207,509,510],{"class":220}," 0x02",[207,512,513],{"class":424}," ||",[207,515,516],{"class":213}," buf[",[207,518,165],{"class":220},[207,520,504],{"class":213},[207,522,507],{"class":424},[207,524,525],{"class":220}," 0x03",[207,527,466],{"class":213},[207,529,531,533,535],{"class":209,"line":530},10,[207,532,471],{"class":424},[207,534,474],{"class":220},[207,536,477],{"class":213},[207,538,540],{"class":209,"line":539},11,[207,541,287],{"class":213},[207,543,545],{"class":209,"line":544},12,[207,546,440],{"emptyLinePlaceholder":439},[207,548,550],{"class":209,"line":549},13,[207,551,552],{"class":445},"\u002F\u002F Validate checksum\n",[207,554,556,559,562,565,568],{"class":209,"line":555},14,[207,557,558],{"class":424},"let",[207,560,561],{"class":213}," sum ",[207,563,564],{"class":424},"=",[207,566,567],{"class":220}," 0",[207,569,477],{"class":213},[207,571,573,576,579,581,584,586,589,592,595,598,601,604],{"class":209,"line":572},15,[207,574,575],{"class":424},"for",[207,577,578],{"class":213}," (",[207,580,558],{"class":424},[207,582,583],{"class":213}," i ",[207,585,564],{"class":424},[207,587,588],{"class":220}," 1",[207,590,591],{"class":213},"; i ",[207,593,594],{"class":424},"\u003C=",[207,596,597],{"class":220}," 7",[207,599,600],{"class":213},"; i",[207,602,603],{"class":424},"++",[207,605,466],{"class":213},[207,607,609,612,615],{"class":209,"line":608},16,[207,610,611],{"class":213},"    sum ",[207,613,614],{"class":424},"+=",[207,616,617],{"class":213}," buf[i];\n",[207,619,621],{"class":209,"line":620},17,[207,622,287],{"class":213},[207,624,626,628,631,634,637,640,642,644,646],{"class":209,"line":625},18,[207,627,451],{"class":424},[207,629,630],{"class":213}," ((sum ",[207,632,633],{"class":424},"&",[207,635,636],{"class":220}," 0xFF",[207,638,639],{"class":213},") ",[207,641,507],{"class":424},[207,643,516],{"class":213},[207,645,155],{"class":220},[207,647,648],{"class":213},"]) {\n",[207,650,652,654,656],{"class":209,"line":651},19,[207,653,471],{"class":424},[207,655,474],{"class":220},[207,657,477],{"class":213},[207,659,661],{"class":209,"line":660},20,[207,662,287],{"class":213},[207,664,666],{"class":209,"line":665},21,[207,667,440],{"emptyLinePlaceholder":439},[207,669,671,674],{"class":209,"line":670},22,[207,672,673],{"class":424},"return",[207,675,676],{"class":213}," msg;\n",[10,678,679],{},"Three checks: frame length, start and end markers, checksum. If any fail, the message is dropped. Only clean, verified frames reach the Buffer Parser.",[326,681,683],{"id":682},"step-4-parse-the-bytes","Step 4: Parse the Bytes",[10,685,409,686,688,689,692,693,696],{},[30,687,313],{}," and double-click it. Set ",[30,690,691],{},"Output"," to ",[30,694,695],{},"key\u002Fvalue",". Then add one row for each field.",[10,698,699],{},[30,700,701],{},"Weight",[703,704,705,711,718,724],"ul",{},[337,706,707,708],{},"Name: ",[96,709,710],{},"weight",[337,712,713,714,717],{},"Type: ",[96,715,716],{},"floatle"," (32-bit IEEE 754 float, little-endian)",[337,719,720,721],{},"Offset: ",[96,722,723],{},"1",[337,725,726,727],{},"Length: ",[96,728,723],{},[10,730,731],{},[30,732,733],{},"Unit",[703,735,736,741,746,750],{},[337,737,707,738],{},[96,739,740],{},"unit",[337,742,713,743],{},[96,744,745],{},"uint8",[337,747,720,748],{},[96,749,117],{},[337,751,726,752],{},[96,753,723],{},[10,755,756],{},[30,757,758],{},"Status flag — stable",[703,760,761,766,771,775],{},[337,762,707,763],{},[96,764,765],{},"stable",[337,767,713,768],{},[96,769,770],{},"bool",[337,772,720,773],{},[96,774,133],{},[337,776,777,778],{},"Bit Offset: ",[96,779,92],{},[10,781,782],{},[30,783,784],{},"Status flag — overload",[703,786,787,792,796,800],{},[337,788,707,789],{},[96,790,791],{},"overload",[337,793,713,794],{},[96,795,770],{},[337,797,720,798],{},[96,799,133],{},[337,801,777,802],{},[96,803,723],{},[10,805,806],{},[30,807,808],{},"Status flag — zero",[703,810,811,816,820,824],{},[337,812,707,813],{},[96,814,815],{},"zero",[337,817,713,818],{},[96,819,770],{},[337,821,720,822],{},[96,823,133],{},[337,825,777,826],{},[96,827,828],{},"2",[10,830,831,836],{},[832,833],"img",{"alt":834,"src":835},"Buffer Parser node configuration showing weight, unit, and status flag fields mapped to their respective byte offsets","\u002Fblog\u002F2026\u002F03\u002Fimages\u002Fbuffer-parser.png",[837,838,839],"em",{},"Configuring the Buffer Parser node to extract weight, unit, and status flags from the 10-byte frame",[10,841,842,843,845,846,300],{},"The float conversion, the byte reading, the individual bit extraction — all handled visually without code. The ",[96,844,770],{}," type with Bit Offset is what makes bitfield parsing possible here. Each status flag is packed into a single byte, and the Buffer Parser pulls them out one bit at a time. If you want a deeper walkthrough of every Buffer Parser field and configuration option, ",[296,847,849],{"href":848},"\u002Fblog\u002F2025\u002F12\u002Fnode-red-buffer-parser-industrial-data\u002F","this article covers it in full",[326,851,853],{"id":852},"step-5-map-the-unit-value","Step 5: Map the Unit Value",[10,855,856,857,123,859,861,862,864,865,867],{},"The Buffer Parser gives you ",[96,858,723],{},[96,860,828],{}," for the unit byte. A ",[30,863,316],{}," routes the message based on that value, and a ",[30,866,320],{}," on each route sets the correct label.",[10,869,870],{},"Configure the Switch node:",[703,872,873,879,885],{},[337,874,875,876],{},"Property: ",[96,877,878],{},"msg.payload.unit",[337,880,881,882,884],{},"Rule 1: equals ",[96,883,723],{}," → output 1",[337,886,887,888,890],{},"Rule 2: equals ",[96,889,828],{}," → output 2",[10,892,893],{},"Connect each output to its own Change node:",[703,895,896,904],{},[337,897,898,899,901,902],{},"Output 1 Change node: set ",[96,900,878],{}," to the string ",[96,903,242],{},[337,905,906,907,901,909],{},"Output 2 Change node: set ",[96,908,878],{},[96,910,911],{},"\"lb\"",[10,913,914,915,918],{},"Connect both Change nodes to the Debug node. Click ",[30,916,917],{},"Deploy",", then click the Inject button. Your debug panel will show:",[187,920,922],{"className":201,"code":921,"language":203,"meta":195,"style":195},"{\n  \"weight\": 23.5,\n  \"unit\": \"kg\",\n  \"stable\": true,\n  \"overload\": true,\n  \"zero\": false\n}\n",[96,923,924,928,938,948,958,968,976],{"__ignoreMap":195},[207,925,926],{"class":209,"line":210},[207,927,214],{"class":213},[207,929,930,932,934,936],{"class":209,"line":217},[207,931,221],{"class":220},[207,933,224],{"class":213},[207,935,227],{"class":220},[207,937,230],{"class":213},[207,939,940,942,944,946],{"class":209,"line":233},[207,941,236],{"class":220},[207,943,224],{"class":213},[207,945,242],{"class":241},[207,947,230],{"class":213},[207,949,950,952,954,956],{"class":209,"line":247},[207,951,250],{"class":220},[207,953,224],{"class":213},[207,955,255],{"class":220},[207,957,230],{"class":213},[207,959,960,962,964,966],{"class":209,"line":260},[207,961,263],{"class":220},[207,963,224],{"class":213},[207,965,255],{"class":220},[207,967,230],{"class":213},[207,969,970,972,974],{"class":209,"line":273},[207,971,276],{"class":220},[207,973,224],{"class":213},[207,975,281],{"class":220},[207,977,978],{"class":209,"line":284},[207,979,287],{"class":213},[981,982],"render-flow",{":height":983,"flow":984},"300","W3siaWQiOiJkOGJjMTc5ZDY5OGYzOWEyIiwidHlwZSI6Imdyb3VwIiwieiI6IkZGRjAwMDAwMDAwMDAwMDEiLCJzdHlsZSI6eyJzdHJva2UiOiIjYjJiM2JkIiwic3Ryb2tlLW9wYWNpdHkiOiIxIiwiZmlsbCI6IiNmMmYzZmIiLCJmaWxsLW9wYWNpdHkiOiIwLjUiLCJsYWJlbCI6dHJ1ZSwibGFiZWwtcG9zaXRpb24iOiJudyIsImNvbG9yIjoiIzMyMzMzYiJ9LCJub2RlcyI6WyI1ZWU3NTVkMjBkMzAwYjYwIiwiZTA3ZTkzNzE2MThmOTI2ZiIsImFlZGZhMDZmYjcxMDlmMTgiLCIwNDU5NTk1NjllODRhMDk4IiwiOTQ3ZGIwMDNkYmQzMjEyNyIsIjIzYzI1Y2E1OGEwNzg0YjAiLCJlNTQxMjYwYjNiNTgxZWFjIl0sIngiOjEyOTQsInkiOjcxOSwidyI6MTAxMiwiaCI6MTIyfSx7ImlkIjoiNWVlNzU1ZDIwZDMwMGI2MCIsInR5cGUiOiJpbmplY3QiLCJ6IjoiRkZGMDAwMDAwMDAwMDAwMSIsImciOiJkOGJjMTc5ZDY5OGYzOWEyIiwibmFtZSI6IiIsInByb3BzIjpbeyJwIjoicGF5bG9hZCJ9XSwicmVwZWF0IjoiIiwiY3JvbnRhYiI6IiIsIm9uY2UiOmZhbHNlLCJvbmNlRGVsYXkiOjAuMSwidG9waWMiOiIiLCJwYXlsb2FkIjoiWzIsMCwwLDE4OCw2NSwxLDMsMCwxLDNdIiwicGF5bG9hZFR5cGUiOiJiaW4iLCJ4IjoxMzkwLCJ5Ijo3ODAsIndpcmVzIjpbWyJlMDdlOTM3MTYxOGY5MjZmIl1dfSx7ImlkIjoiZTA3ZTkzNzE2MThmOTI2ZiIsInR5cGUiOiJmdW5jdGlvbiIsInoiOiJGRkYwMDAwMDAwMDAwMDAxIiwiZyI6ImQ4YmMxNzlkNjk4ZjM5YTIiLCJuYW1lIjoiVmFsaWRhdGUgRnJhbWUiLCJmdW5jIjoiY29uc3QgYnVmID0gbXNnLnBheWxvYWQ7XG5cbi8vIENoZWNrIGZyYW1lIGxlbmd0aFxuaWYgKGJ1Zi5sZW5ndGggIT09IDEwKSB7XG4gICAgcmV0dXJuIG51bGw7XG59XG5cbi8vIENoZWNrIHN0YXJ0IGFuZCBlbmQgbWFya2Vyc1xuaWYgKGJ1ZlswXSAhPT0gMHgwMiB8fCBidWZbOV0gIT09IDB4MDMpIHtcbiAgICByZXR1cm4gbnVsbDtcbn1cblxuLy8gVmFsaWRhdGUgY2hlY2tzdW1cbmxldCBzdW0gPSAwO1xuZm9yIChsZXQgaSA9IDE7IGkgPD0gNzsgaSsrKSB7XG4gICAgc3VtICs9IGJ1ZltpXTtcbn1cbmlmICgoc3VtICYgMHhGRikgIT09IGJ1Zls4XSkge1xuICAgIHJldHVybiBudWxsO1xufVxuXG5yZXR1cm4gbXNnOyIsIm91dHB1dHMiOjEsInRpbWVvdXQiOjAsIm5vZXJyIjowLCJpbml0aWFsaXplIjoiIiwiZmluYWxpemUiOiIiLCJsaWJzIjpbXSwieCI6MTU2MCwieSI6NzgwLCJ3aXJlcyI6W1siYWVkZmEwNmZiNzEwOWYxOCJdXX0seyJpZCI6ImFlZGZhMDZmYjcxMDlmMTgiLCJ0eXBlIjoiYnVmZmVyLXBhcnNlciIsInoiOiJGRkYwMDAwMDAwMDAwMDAxIiwiZyI6ImQ4YmMxNzlkNjk4ZjM5YTIiLCJuYW1lIjoiUGFyc2UgRnJhbWUiLCJkYXRhIjoicGF5bG9hZCIsImRhdGFUeXBlIjoibXNnIiwic3BlY2lmaWNhdGlvbiI6InNwZWMiLCJzcGVjaWZpY2F0aW9uVHlwZSI6InVpIiwiaXRlbXMiOlt7InR5cGUiOiJmbG9hdGxlIiwibmFtZSI6IndlaWdodCIsIm9mZnNldCI6MSwibGVuZ3RoIjoxLCJvZmZzZXRiaXQiOjAsInNjYWxlIjoiMSIsIm1hc2siOiIifSx7InR5cGUiOiJ1aW50OCIsIm5hbWUiOiJ1bml0Iiwib2Zmc2V0Ijo1LCJsZW5ndGgiOjEsIm9mZnNldGJpdCI6MCwic2NhbGUiOiIxIiwibWFzayI6IiJ9LHsidHlwZSI6ImJvb2wiLCJuYW1lIjoic3RhYmxlIiwib2Zmc2V0Ijo2LCJsZW5ndGgiOjEsIm9mZnNldGJpdCI6MCwic2NhbGUiOiIxIiwibWFzayI6IiJ9LHsidHlwZSI6ImJvb2wiLCJuYW1lIjoib3ZlcmxvYWQiLCJvZmZzZXQiOjYsImxlbmd0aCI6MSwib2Zmc2V0Yml0IjoxLCJzY2FsZSI6IjEiLCJtYXNrIjoiIn0seyJ0eXBlIjoiYm9vbCIsIm5hbWUiOiJ6ZXJvIiwib2Zmc2V0Ijo2LCJsZW5ndGgiOjEsIm9mZnNldGJpdCI6Miwic2NhbGUiOiIxIiwibWFzayI6IiJ9XSwic3dhcDEiOiIiLCJzd2FwMiI6IiIsInN3YXAzIjoiIiwic3dhcDFUeXBlIjoic3dhcCIsInN3YXAyVHlwZSI6InN3YXAiLCJzd2FwM1R5cGUiOiJzd2FwIiwibXNnUHJvcGVydHkiOiJwYXlsb2FkIiwibXNnUHJvcGVydHlUeXBlIjoic3RyIiwicmVzdWx0VHlwZSI6ImtleXZhbHVlIiwicmVzdWx0VHlwZVR5cGUiOiJyZXR1cm4iLCJtdWx0aXBsZVJlc3VsdCI6ZmFsc2UsImZhbk91dE11bHRpcGxlUmVzdWx0IjpmYWxzZSwic2V0VG9waWMiOnRydWUsIm91dHB1dHMiOjEsIngiOjE3NTAsInkiOjc4MCwid2lyZXMiOltbIjA0NTk1OTU2OWU4NGEwOTgiXV19LHsiaWQiOiIwNDU5NTk1NjllODRhMDk4IiwidHlwZSI6InN3aXRjaCIsInoiOiJGRkYwMDAwMDAwMDAwMDAxIiwiZyI6ImQ4YmMxNzlkNjk4ZjM5YTIiLCJuYW1lIjoiTWFwIFVuaXQiLCJwcm9wZXJ0eSI6Im1zZy5wYXlsb2FkLnVuaXQiLCJwcm9wZXJ0eVR5cGUiOiJtc2ciLCJydWxlcyI6W3sidCI6ImVxIiwidiI6IjEiLCJ2dCI6Im51bSJ9LHsidCI6ImVxIiwidiI6IjIiLCJ2dCI6Im51bSJ9XSwiY2hlY2thbGwiOiJ0cnVlIiwicmVwYWlyIjpmYWxzZSwib3V0cHV0cyI6MiwieCI6MTkyMCwieSI6NzgwLCJ3aXJlcyI6W1siOTQ3ZGIwMDNkYmQzMjEyNyJdLFsiMjNjMjVjYTU4YTA3ODRiMCJdXX0seyJpZCI6Ijk0N2RiMDAzZGJkMzIxMjciLCJ0eXBlIjoiY2hhbmdlIiwieiI6IkZGRjAwMDAwMDAwMDAwMDEiLCJnIjoiZDhiYzE3OWQ2OThmMzlhMiIsIm5hbWUiOiJTZXQga2ciLCJydWxlcyI6W3sidCI6InNldCIsInAiOiJwYXlsb2FkLnVuaXQiLCJwdCI6Im1zZyIsInRvIjoia2ciLCJ0b3QiOiJzdHIifV0sImFjdGlvbiI6IiIsInByb3BlcnR5IjoiIiwiZnJvbSI6IiIsInRvIjoiIiwicmVnIjpmYWxzZSwieCI6MjA3MCwieSI6NzYwLCJ3aXJlcyI6W1siZTU0MTI2MGIzYjU4MWVhYyJdXX0seyJpZCI6IjIzYzI1Y2E1OGEwNzg0YjAiLCJ0eXBlIjoiY2hhbmdlIiwieiI6IkZGRjAwMDAwMDAwMDAwMDEiLCJnIjoiZDhiYzE3OWQ2OThmMzlhMiIsIm5hbWUiOiJTZXQgbGIiLCJydWxlcyI6W3sidCI6InNldCIsInAiOiJwYXlsb2FkLnVuaXQiLCJwdCI6Im1zZyIsInRvIjoibGIiLCJ0b3QiOiJzdHIifV0sImFjdGlvbiI6IiIsInByb3BlcnR5IjoiIiwiZnJvbSI6IiIsInRvIjoiIiwicmVnIjpmYWxzZSwieCI6MjA3MCwieSI6ODAwLCJ3aXJlcyI6W1siZTU0MTI2MGIzYjU4MWVhYyJdXX0seyJpZCI6ImU1NDEyNjBiM2I1ODFlYWMiLCJ0eXBlIjoiZGVidWciLCJ6IjoiRkZGMDAwMDAwMDAwMDAwMSIsImciOiJkOGJjMTc5ZDY5OGYzOWEyIiwibmFtZSI6IlJlc3VsdCIsImFjdGl2ZSI6dHJ1ZSwidG9zaWRlYmFyIjp0cnVlLCJjb25zb2xlIjpmYWxzZSwidG9zdGF0dXMiOmZhbHNlLCJjb21wbGV0ZSI6InBheWxvYWQiLCJ0YXJnZXRUeXBlIjoibXNnIiwic3RhdHVzVmFsIjoiIiwic3RhdHVzVHlwZSI6ImF1dG8iLCJ4IjoyMjEwLCJ5Ijo3ODAsIndpcmVzIjpbXX0seyJpZCI6ImYwYTM5NTE5MjQ2OTIwMTAiLCJ0eXBlIjoiZ2xvYmFsLWNvbmZpZyIsImVudiI6W10sIm1vZHVsZXMiOnsibm9kZS1yZWQtY29udHJpYi1idWZmZXItcGFyc2VyIjoiMy4yLjIifX1d",[20,986,988],{"id":987},"when-things-get-harder","When Things Get Harder",[10,990,991],{},"The weighing scale had a clean, fixed-length frame. Not every device will.",[326,993,995],{"id":994},"frames-arriving-split-across-multiple-messages","Frames Arriving Split Across Multiple Messages",[10,997,998],{},"Depending on baud rate and buffer size, a single frame can arrive as two or more separate messages. If you try to parse a partial frame, you get garbage. The fix is a reassembly Function node placed before the validation step, using node context to accumulate bytes until a full frame is available:",[187,1000,1002],{"className":415,"code":1001,"language":417,"meta":195,"style":195},"let buffer = context.get(\"buffer\") || Buffer.alloc(0);\nbuffer = Buffer.concat([buffer, msg.payload]);\n\nif (buffer.length \u003C 10) {\n    context.set(\"buffer\", buffer);\n    return null;\n}\n\nmsg.payload = buffer.slice(0, 10);\ncontext.set(\"buffer\", buffer.slice(10));\n\nreturn msg;\n",[96,1003,1004,1044,1059,1063,1079,1094,1102,1106,1110,1135,1158,1162],{"__ignoreMap":195},[207,1005,1006,1008,1011,1013,1016,1020,1023,1026,1028,1031,1034,1037,1039,1041],{"class":209,"line":210},[207,1007,558],{"class":424},[207,1009,1010],{"class":213}," buffer ",[207,1012,564],{"class":424},[207,1014,1015],{"class":213}," context.",[207,1017,1019],{"class":1018},"sScJk","get",[207,1021,1022],{"class":213},"(",[207,1024,1025],{"class":241},"\"buffer\"",[207,1027,639],{"class":213},[207,1029,1030],{"class":424},"||",[207,1032,1033],{"class":213}," Buffer.",[207,1035,1036],{"class":1018},"alloc",[207,1038,1022],{"class":213},[207,1040,92],{"class":220},[207,1042,1043],{"class":213},");\n",[207,1045,1046,1049,1051,1053,1056],{"class":209,"line":217},[207,1047,1048],{"class":213},"buffer ",[207,1050,564],{"class":424},[207,1052,1033],{"class":213},[207,1054,1055],{"class":1018},"concat",[207,1057,1058],{"class":213},"([buffer, msg.payload]);\n",[207,1060,1061],{"class":209,"line":233},[207,1062,440],{"emptyLinePlaceholder":439},[207,1064,1065,1067,1070,1072,1075,1077],{"class":209,"line":247},[207,1066,451],{"class":424},[207,1068,1069],{"class":213}," (buffer.",[207,1071,457],{"class":220},[207,1073,1074],{"class":424}," \u003C",[207,1076,463],{"class":220},[207,1078,466],{"class":213},[207,1080,1081,1084,1087,1089,1091],{"class":209,"line":260},[207,1082,1083],{"class":213},"    context.",[207,1085,1086],{"class":1018},"set",[207,1088,1022],{"class":213},[207,1090,1025],{"class":241},[207,1092,1093],{"class":213},", buffer);\n",[207,1095,1096,1098,1100],{"class":209,"line":273},[207,1097,471],{"class":424},[207,1099,474],{"class":220},[207,1101,477],{"class":213},[207,1103,1104],{"class":209,"line":284},[207,1105,287],{"class":213},[207,1107,1108],{"class":209,"line":488},[207,1109,440],{"emptyLinePlaceholder":439},[207,1111,1112,1115,1117,1120,1123,1125,1127,1130,1133],{"class":209,"line":494},[207,1113,1114],{"class":213},"msg.payload ",[207,1116,564],{"class":424},[207,1118,1119],{"class":213}," buffer.",[207,1121,1122],{"class":1018},"slice",[207,1124,1022],{"class":213},[207,1126,92],{"class":220},[207,1128,1129],{"class":213},", ",[207,1131,1132],{"class":220},"10",[207,1134,1043],{"class":213},[207,1136,1137,1140,1142,1144,1146,1149,1151,1153,1155],{"class":209,"line":530},[207,1138,1139],{"class":213},"context.",[207,1141,1086],{"class":1018},[207,1143,1022],{"class":213},[207,1145,1025],{"class":241},[207,1147,1148],{"class":213},", buffer.",[207,1150,1122],{"class":1018},[207,1152,1022],{"class":213},[207,1154,1132],{"class":220},[207,1156,1157],{"class":213},"));\n",[207,1159,1160],{"class":209,"line":539},[207,1161,440],{"emptyLinePlaceholder":439},[207,1163,1164,1166],{"class":209,"line":544},[207,1165,673],{"class":424},[207,1167,676],{"class":213},[10,1169,1170],{},"For delimiter-terminated frames, replace the length check with a search for your end byte. For length-prefixed frames, read the length byte first and use that as your target size.",[326,1172,1174],{"id":1173},"when-buffer-parser-is-not-enough","When Buffer Parser Is Not Enough",[10,1176,1177],{},"Buffer Parser handles fixed-structure protocols well. Two situations require a Function node instead.",[10,1179,1180],{},"The first is conditional structure — where the layout of later bytes depends on the value of an earlier byte. Buffer Parser parses a fixed specification every time. If your frame changes shape based on its own content, you need code.",[10,1182,1183],{},"The second is complex computed fields. If your device sends a raw ADC value that requires a multi-step calibration formula, Buffer Parser's scale field won't cover it. Add a Function node after Buffer Parser to handle only that calculation.",[10,1185,1186],{},"In both cases: let Buffer Parser do what it can, and add a Function node only for the parts it cannot.",[1188,1189,1190,1198,1203,1209],"blockquote",{},[10,1191,1192,1193,1197],{},"If you're on FlowFuse, you don't need to write that JavaScript yourself. Describe what you need to the ",[296,1194,1196],{"href":1195},"\u002Fdocs\u002Fuser\u002Fexpert\u002Fnode-red-embedded-ai\u002F#function-code-generation","FlowFuse Expert"," in plain English and paste the relevant section of your device manual. It will generate the Function node directly on your canvas.",[10,1199,1200],{},[30,1201,1202],{},"Example prompt you can use:",[187,1204,1207],{"className":1205,"code":1206,"language":192},[190],"I have a serial device sending binary data with this frame structure:\n- Byte 0: 0x02 (start)\n- Byte 1: message type\n- Bytes 2–5: 32-bit float (little-endian)\n- Byte 6: status bitfield\n- Last byte: checksum (sum of bytes)\n\nThe frame structure changes based on the message type.\n\nGenerate a Node-RED Function node that:\n1. Validates the frame (start, length, checksum)\n2. Parses fields based on message type\n3. Extracts bitfield values into booleans\n",[96,1208,1206],{"__ignoreMap":195},[10,1210,1211],{},"Paste your device manual section along with the prompt for best results.",[20,1213,1215],{"id":1214},"applying-this-to-your-own-device","Applying This to Your Own Device",[10,1217,1218],{},"The weighing scale is one device. To show the method transfers, here is how it applies to a completely different device — an industrial barcode scanner.",[10,1220,1221,1222,1225],{},"The scanner sends a variable-length frame every time it reads a label. The manual specifies a delimiter-terminated structure ending with ",[96,1223,1224],{},"0x0D"," (carriage return). Here is the frame layout:",[66,1227,1228,1238],{},[69,1229,1230],{},[72,1231,1232,1234,1236],{},[75,1233,77],{},[75,1235,80],{},[75,1237,83],{},[85,1239,1240,1250,1259,1270],{},[72,1241,1242,1244,1248],{},[90,1243,92],{},[90,1245,1246],{},[96,1247,98],{},[90,1249,101],{},[72,1251,1252,1254,1256],{},[90,1253,723],{},[90,1255,745],{},[90,1257,1258],{},"Scanner ID",[72,1260,1261,1264,1267],{},[90,1262,1263],{},"2–N",[90,1265,1266],{},"ASCII bytes",[90,1268,1269],{},"Barcode data, variable length",[72,1271,1272,1275,1279],{},[90,1273,1274],{},"N+1",[90,1276,1277],{},[96,1278,1224],{},[90,1280,1281],{},"CR, end of frame",[10,1283,1284],{},"The device and the frame look nothing like the weighing scale. The method is identical.",[10,1286,1287,1289,1290,1292],{},[30,1288,32],{}," Frame structure: delimiter terminated, ends with ",[96,1291,1224],{},". Data types: ASCII bytes for the barcode value, uint8 for the scanner ID.",[10,1294,1295,1297,1298,1300],{},[30,1296,38],{}," Delimiter terminated. Your reassembly logic searches for ",[96,1299,1224],{}," instead of checking a fixed length.",[10,1302,1303,1305,1306,1308],{},[30,1304,44],{}," Check for the STX start byte at position 0 and the ",[96,1307,1224],{}," end byte at the last position. No checksum on this device, so those two markers are your full validation.",[10,1310,1311,1313],{},[30,1312,50],{}," In the Buffer Parser, one row for the scanner ID at offset 1 as uint8. The barcode data is ASCII from offset 2 to the end of the frame, which you read as a string type with the appropriate length.",[10,1315,1316],{},"Four steps. Different device, different frame structure, same process. The Buffer Parser configuration looks different because the bytes are different. The thinking behind it is the same.",[10,1318,1319],{},"This is what the method gives you. Not a recipe for one specific device, but a way of reading any device manual and turning what you find there into a working flow.",[10,1321,1322],{},"If you are managing flows across multiple edge devices, FlowFuse deploys the same configuration everywhere through its remote deployment pipeline, with snapshots to roll back if anything changes on the device side.",[20,1324,1326],{"id":1325},"final-thoughts","Final Thoughts",[10,1328,1329],{},"Modbus made binary parsing approachable because someone defined the rules in advance. Raw serial devices hand you a PDF instead. That is the real difficulty, not the parsing itself.",[10,1331,1332],{},"The method in this article works for weighing scales, barcode scanners, RFID readers, CNC machines, and whatever proprietary hardware is sitting on your factory floor with no documentation beyond a table of hex values. Once you have applied it once, you will recognize the pattern in every device you connect after it.",[10,1334,1335],{},"The factories with the most valuable operational data are often the ones running the oldest hardware. That data is accessible. It just requires knowing how to read it.",[1337,1338,1339],"style",{},"html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}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 .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":195,"searchDepth":217,"depth":217,"links":1341},[1342,1343,1344,1351,1355,1356],{"id":22,"depth":217,"text":23},{"id":57,"depth":217,"text":58},{"id":290,"depth":217,"text":291,"children":1345},[1346,1347,1348,1349,1350],{"id":328,"depth":233,"text":329},{"id":367,"depth":233,"text":368},{"id":405,"depth":233,"text":406},{"id":682,"depth":233,"text":683},{"id":852,"depth":233,"text":853},{"id":987,"depth":217,"text":988,"children":1352},[1353,1354],{"id":994,"depth":233,"text":995},{"id":1173,"depth":233,"text":1174},{"id":1214,"depth":217,"text":1215},{"id":1325,"depth":217,"text":1326},"md",{"navTitle":5,"excerpt":1359},{"type":7,"value":1360},[1361],[10,1362,12],{},"\u002Fblog\u002F2026\u002F03\u002Fhow-to-parse-binary-data-serial-devices",{"title":5,"description":12},"blog\u002F2026\u002F03\u002Fhow-to-parse-binary-data-serial-devices","n05dQtxoQ5UyMjv08Utr0ZIJbMhJ0CThk6ZHM5zQ91Y",1780070554434]