[{"data":1,"prerenderedAt":2095},["ShallowReactive",2],{"navigation_docs_en":3,"-en-cloud-firmware-over-rdm-implementation-guide":317,"-en-cloud-firmware-over-rdm-implementation-guide-surround":2092},[4,19,74],{"title":5,"path":6,"stem":7,"children":8,"page":18},"Public","\u002Fen\u002Fpublic","en\u002F1.public",[9],{"title":10,"icon":11,"path":12,"stem":13,"children":14,"page":18},"Welcome","i-lucide-sparkles","\u002Fen\u002Fpublic\u002Fwelcome","en\u002F1.public\u002F1.welcome",[15],{"title":10,"path":16,"stem":17},"\u002Fen\u002Fpublic\u002Fwelcome\u002Foverview","en\u002F1.public\u002F1.welcome\u002F1.overview",false,{"title":20,"path":21,"stem":22,"children":23,"page":18},"Cloud","\u002Fen\u002Fcloud","en\u002F2.cloud",[24,33,48],{"title":10,"icon":25,"path":26,"stem":27,"children":28,"page":18},"i-lucide-lock","\u002Fen\u002Fcloud\u002Fwelcome","en\u002F2.cloud\u002F1.welcome",[29],{"title":30,"path":31,"stem":32},"Cloud Docs","\u002Fen\u002Fcloud\u002Fwelcome\u002Foverview","en\u002F2.cloud\u002F1.welcome\u002F1.overview",{"title":34,"icon":35,"path":36,"stem":37,"children":38,"page":18},"Firmware over RDM","i-lucide-radio-tower","\u002Fen\u002Fcloud\u002Ffirmware-over-rdm","en\u002F2.cloud\u002F2.firmware-over-rdm",[39,43],{"title":40,"path":41,"stem":42,"icon":35},"Firmware Update over RDM","\u002Fen\u002Fcloud\u002Ffirmware-over-rdm\u002Foverview","en\u002F2.cloud\u002F2.firmware-over-rdm\u002F1.overview",{"title":44,"path":45,"stem":46,"icon":47},"Implementation Guide","\u002Fen\u002Fcloud\u002Ffirmware-over-rdm\u002Fimplementation-guide","en\u002F2.cloud\u002F2.firmware-over-rdm\u002F2.implementation-guide","i-lucide-list-checks",{"title":49,"icon":50,"path":51,"stem":52,"children":53,"page":18},"KLSTR.rdm","i-lucide-cable","\u002Fen\u002Fcloud\u002Fklstr-rdm","en\u002F2.cloud\u002F3.klstr-rdm",[54,59,64,69],{"title":55,"path":56,"stem":57,"icon":58},"Introduction","\u002Fen\u002Fcloud\u002Fklstr-rdm\u002Fintroduction","en\u002F2.cloud\u002F3.klstr-rdm\u002F1.introduction","i-lucide-circuit-board",{"title":60,"path":61,"stem":62,"icon":63},"Use KLSTR.dev hardware","\u002Fen\u002Fcloud\u002Fklstr-rdm\u002Fuse-klstr-dev-hardware","en\u002F2.cloud\u002F3.klstr-rdm\u002F2.use-klstr-dev-hardware","i-lucide-wrench",{"title":65,"path":66,"stem":67,"icon":68},"Special firmware","\u002Fen\u002Fcloud\u002Fklstr-rdm\u002Fspecial-firmware","en\u002F2.cloud\u002F3.klstr-rdm\u002F3.special-firmware","i-lucide-download",{"title":70,"path":71,"stem":72,"icon":73},"RDM-Compliance Testing","\u002Fen\u002Fcloud\u002Fklstr-rdm\u002Frdm-compliance-testing","en\u002F2.cloud\u002F3.klstr-rdm\u002F4.rdm-compliance-testing","i-lucide-clipboard-check",{"title":75,"path":76,"stem":77,"children":78,"page":18},"Internal","\u002Fen\u002Finternal","en\u002F3.internal",[79,250,264,278],{"title":80,"icon":81,"path":82,"stem":83,"children":84,"page":18},"Review (ex-public)","i-lucide-inbox","\u002Fen\u002Finternal\u002Freview","en\u002F3.internal\u002F0.review",[85,102,120,138,187,205],{"title":86,"icon":87,"path":88,"stem":89,"children":90,"page":18},"Getting Started","i-lucide-rocket","\u002Fen\u002Finternal\u002Freview\u002Ftutorials","en\u002F3.internal\u002F0.review\u002F1.tutorials",[91,94,98],{"title":86,"path":92,"stem":93},"\u002Fen\u002Finternal\u002Freview\u002Ftutorials\u002Fgetting-started","en\u002F3.internal\u002F0.review\u002F1.tutorials\u002F1.getting-started",{"title":95,"path":96,"stem":97},"Setup KLSTR.one","\u002Fen\u002Finternal\u002Freview\u002Ftutorials\u002Fsetup-klstr-one","en\u002F3.internal\u002F0.review\u002F1.tutorials\u002F2.setup-klstr-one",{"title":99,"path":100,"stem":101},"Setup KLSTR.nano","\u002Fen\u002Finternal\u002Freview\u002Ftutorials\u002Fsetup-klstr-nano","en\u002F3.internal\u002F0.review\u002F1.tutorials\u002F3.setup-klstr-nano",{"title":103,"icon":104,"path":105,"stem":106,"children":107,"page":18},"KLSTR.one","i-lucide-cpu","\u002Fen\u002Finternal\u002Freview\u002Fklstr-one","en\u002F3.internal\u002F0.review\u002F2.klstr-one",[108,112,116],{"title":109,"path":110,"stem":111},"KLSTR.one Overview","\u002Fen\u002Finternal\u002Freview\u002Fklstr-one\u002Foverview","en\u002F3.internal\u002F0.review\u002F2.klstr-one\u002F1.overview",{"title":113,"path":114,"stem":115},"KLSTR.one Installation","\u002Fen\u002Finternal\u002Freview\u002Fklstr-one\u002Finstallation","en\u002F3.internal\u002F0.review\u002F2.klstr-one\u002F2.installation",{"title":117,"path":118,"stem":119},"KLSTR.one Specifications","\u002Fen\u002Finternal\u002Freview\u002Fklstr-one\u002Fspecifications","en\u002F3.internal\u002F0.review\u002F2.klstr-one\u002F3.specifications",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":18},"KLSTR.nano","i-lucide-microchip","\u002Fen\u002Finternal\u002Freview\u002Fklstr-nano","en\u002F3.internal\u002F0.review\u002F4.klstr-nano",[126,130,134],{"title":127,"path":128,"stem":129},"KLSTR.nano Overview","\u002Fen\u002Finternal\u002Freview\u002Fklstr-nano\u002Foverview","en\u002F3.internal\u002F0.review\u002F4.klstr-nano\u002F1.overview",{"title":131,"path":132,"stem":133},"KLSTR.nano Installation","\u002Fen\u002Finternal\u002Freview\u002Fklstr-nano\u002Finstallation","en\u002F3.internal\u002F0.review\u002F4.klstr-nano\u002F2.installation",{"title":135,"path":136,"stem":137},"KLSTR.nano Specifications","\u002Fen\u002Finternal\u002Freview\u002Fklstr-nano\u002Fspecifications","en\u002F3.internal\u002F0.review\u002F4.klstr-nano\u002F3.specifications",{"title":139,"icon":140,"path":141,"stem":142,"children":143,"page":18},"Developer Guide","i-lucide-code","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper","en\u002F3.internal\u002F0.review\u002F5.developer",[144,147,151,155,159,175],{"title":139,"path":145,"stem":146},"\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Foverview","en\u002F3.internal\u002F0.review\u002F5.developer\u002F0.overview",{"title":148,"path":149,"stem":150},"Choosing a Module","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fchoosing-a-module","en\u002F3.internal\u002F0.review\u002F5.developer\u002F1.choosing-a-module",{"title":152,"path":153,"stem":154},"Dual-Mode Operation","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fdual-mode","en\u002F3.internal\u002F0.review\u002F5.developer\u002F2.dual-mode",{"title":156,"path":157,"stem":158},"Fleet Management","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Ffleet-management","en\u002F3.internal\u002F0.review\u002F5.developer\u002F3.fleet-management",{"title":103,"path":160,"stem":161,"children":162,"page":18},"\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-one","en\u002F3.internal\u002F0.review\u002F5.developer\u002F4.klstr-one",[163,167,171],{"title":164,"path":165,"stem":166},"KLSTR.one: Hardware Integration","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-one\u002Fhardware-integration","en\u002F3.internal\u002F0.review\u002F5.developer\u002F4.klstr-one\u002F1.hardware-integration",{"title":168,"path":169,"stem":170},"KLSTR.one: Provisioning","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-one\u002Fprovisioning","en\u002F3.internal\u002F0.review\u002F5.developer\u002F4.klstr-one\u002F2.provisioning",{"title":172,"path":173,"stem":174},"KLSTR.one: RDM Protocol","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-one\u002Frdm-protocol","en\u002F3.internal\u002F0.review\u002F5.developer\u002F4.klstr-one\u002F3.rdm-protocol",{"title":121,"path":176,"stem":177,"children":178,"page":18},"\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-nano","en\u002F3.internal\u002F0.review\u002F5.developer\u002F5.klstr-nano",[179,183],{"title":180,"path":181,"stem":182},"KLSTR.nano: Hardware Integration","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-nano\u002Fhardware-integration","en\u002F3.internal\u002F0.review\u002F5.developer\u002F5.klstr-nano\u002F1.hardware-integration",{"title":184,"path":185,"stem":186},"KLSTR.nano: Provisioning","\u002Fen\u002Finternal\u002Freview\u002Fdeveloper\u002Fklstr-nano\u002Fprovisioning","en\u002F3.internal\u002F0.review\u002F5.developer\u002F5.klstr-nano\u002F2.provisioning",{"title":188,"icon":189,"path":190,"stem":191,"children":192,"page":18},"FAQ","i-lucide-circle-help","\u002Fen\u002Finternal\u002Freview\u002Ffaq","en\u002F3.internal\u002F0.review\u002F6.faq",[193,197,201],{"title":194,"path":195,"stem":196},"General Questions","\u002Fen\u002Finternal\u002Freview\u002Ffaq\u002Fgeneral","en\u002F3.internal\u002F0.review\u002F6.faq\u002F1.general",{"title":198,"path":199,"stem":200},"Connectivity","\u002Fen\u002Finternal\u002Freview\u002Ffaq\u002Fconnectivity","en\u002F3.internal\u002F0.review\u002F6.faq\u002F2.connectivity",{"title":202,"path":203,"stem":204},"Troubleshooting","\u002Fen\u002Finternal\u002Freview\u002Ffaq\u002Ftroubleshooting","en\u002F3.internal\u002F0.review\u002F6.faq\u002F3.troubleshooting",{"title":206,"icon":207,"path":208,"stem":209,"children":210,"page":18},"Articles","i-lucide-newspaper","\u002Fen\u002Finternal\u002Freview\u002Farticles","en\u002F3.internal\u002F0.review\u002F7.articles",[211,215,219,223,227,231,235,239,243,247],{"title":212,"path":213,"stem":214},"Overview","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Foverview","en\u002F3.internal\u002F0.review\u002F7.articles\u002F0.overview",{"title":216,"path":217,"stem":218},"KLSTR Integration Guide for Fixture Manufacturers","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fintegration-guide","en\u002F3.internal\u002F0.review\u002F7.articles\u002F1.integration-guide",{"title":220,"path":221,"stem":222},"RDM Protocol Reference for KLSTR.one","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Frdm-protocol","en\u002F3.internal\u002F0.review\u002F7.articles\u002F2.rdm-protocol",{"title":224,"path":225,"stem":226},"KLSTR.one Provisioning Guide","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fklstr-one-provisioning","en\u002F3.internal\u002F0.review\u002F7.articles\u002F3.klstr-one-provisioning",{"title":228,"path":229,"stem":230},"KLSTR.one Integration Guide","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fklstr-one-plug-in-integration","en\u002F3.internal\u002F0.review\u002F7.articles\u002F4.klstr-one-plug-in-integration",{"title":232,"path":233,"stem":234},"KLSTR.nano Provisioning Guide","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fklstr-nano-provisioning","en\u002F3.internal\u002F0.review\u002F7.articles\u002F5.klstr-nano-provisioning",{"title":236,"path":237,"stem":238},"KLSTR.nano Plug-In Integration","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fklstr-nano-plug-in-integration","en\u002F3.internal\u002F0.review\u002F7.articles\u002F6.klstr-nano-plug-in-integration",{"title":240,"path":241,"stem":242},"Hello","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fhello","en\u002F3.internal\u002F0.review\u002F7.articles\u002F7.hello",{"title":244,"path":245,"stem":246},"TestNew file","\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Fiwantmd","en\u002F3.internal\u002F0.review\u002F7.articles\u002Fiwantmd",{"title":244,"path":248,"stem":249},"\u002Fen\u002Finternal\u002Freview\u002Farticles\u002Ftestnew","en\u002F3.internal\u002F0.review\u002F7.articles\u002Ftestnew",{"title":251,"icon":252,"path":253,"stem":254,"children":255,"page":18},"Admin","i-lucide-shield-check","\u002Fen\u002Finternal\u002Fadmin","en\u002F3.internal\u002F3.admin",[256,260],{"title":257,"path":258,"stem":259},"Admin Dashboard","\u002Fen\u002Finternal\u002Fadmin\u002Fdashboard","en\u002F3.internal\u002F3.admin\u002F1.dashboard",{"title":261,"path":262,"stem":263},"Internal Release Notes","\u002Fen\u002Finternal\u002Fadmin\u002Frelease-notes","en\u002F3.internal\u002F3.admin\u002F2.release-notes",{"title":265,"path":266,"stem":267,"children":268,"page":18},"Developer","\u002Fen\u002Finternal\u002Fdeveloper","en\u002F3.internal\u002F5.developer",[269],{"title":270,"path":271,"stem":272,"children":273,"page":18},"Klstr One","\u002Fen\u002Finternal\u002Fdeveloper\u002Fklstr-one","en\u002F3.internal\u002F5.developer\u002F4.klstr-one",[274],{"title":275,"path":276,"stem":277},"Internal (Partner Only)","\u002Fen\u002Finternal\u002Fdeveloper\u002Fklstr-one\u002Finternal","en\u002F3.internal\u002F5.developer\u002F4.klstr-one\u002F4.internal",{"title":279,"icon":280,"path":281,"stem":282,"children":283,"page":18},"Authoring Guide","i-lucide-pen-line","\u002Fen\u002Finternal\u002Fauthoring","en\u002F3.internal\u002F9.authoring",[284,288,293,297,302,307,312],{"title":285,"path":286,"stem":287,"icon":11},"Overview & MDC Basics","\u002Fen\u002Finternal\u002Fauthoring\u002Foverview","en\u002F3.internal\u002F9.authoring\u002F1.overview",{"title":289,"path":290,"stem":291,"icon":292},"Text & Prose","\u002Fen\u002Finternal\u002Fauthoring\u002Ftext-and-prose","en\u002F3.internal\u002F9.authoring\u002F2.text-and-prose","i-lucide-type",{"title":294,"path":295,"stem":296,"icon":140},"Code Blocks","\u002Fen\u002Finternal\u002Fauthoring\u002Fcode","en\u002F3.internal\u002F9.authoring\u002F3.code",{"title":298,"path":299,"stem":300,"icon":301},"Callouts & Cards","\u002Fen\u002Finternal\u002Fauthoring\u002Fcallouts-and-cards","en\u002F3.internal\u002F9.authoring\u002F4.callouts-and-cards","i-lucide-message-square-warning",{"title":303,"path":304,"stem":305,"icon":306},"Layout & Interactivity","\u002Fen\u002Finternal\u002Fauthoring\u002Flayout-and-interactivity","en\u002F3.internal\u002F9.authoring\u002F5.layout-and-interactivity","i-lucide-layout-panel-left",{"title":308,"path":309,"stem":310,"icon":311},"Visuals — Images, Icons & Diagrams","\u002Fen\u002Finternal\u002Fauthoring\u002Fvisuals","en\u002F3.internal\u002F9.authoring\u002F6.visuals","i-lucide-image",{"title":313,"path":314,"stem":315,"icon":316},"Design System & Configuration","\u002Fen\u002Finternal\u002Fauthoring\u002Fdesign-system","en\u002F3.internal\u002F9.authoring\u002F7.design-system","i-lucide-palette",{"id":318,"title":44,"body":319,"description":2085,"extension":2086,"links":2087,"meta":2088,"navigation":2089,"path":45,"seo":2090,"stem":46,"__hash__":2091},"docs_en\u002Fen\u002F2.cloud\u002F2.firmware-over-rdm\u002F2.implementation-guide.md",{"type":320,"value":321,"toc":2062},"minimark",[322,350,357,408,441,446,1957,1961,2046,2058],[323,324,325,326,331,332,336,337,341,342,345,346,349],"p",{},"This guide is a single build-and-verify sequence for implementing ",[327,328,330],"a",{"href":329},"\u002Fen\u002Ffirmware-over-rdm\u002Foverview","firmware update\nover RDM"," on ",[333,334,335],"strong",{},"any"," microcontroller. It\nassumes you already have a working RDM responder (discovery, ",[338,339,340],"code",{},"DEVICE_INFO",", the\nstandard ",[338,343,344],{},"GET","\u002F",[338,347,348],{},"SET"," dispatch) and a dual-slot (\"A\u002FB\") flash layout.",[323,351,352,353,356],{},"Code is ",[333,354,355],{},"pseudocode"," distilled from the KLSTR.nano (STM32F103) reference. Where a\nmore capable MCU can do better, that's called out inline - but build the reference\nmodel first; it works everywhere.",[358,359,360,380,388,400],"field-group",{},[361,362,366,367,370,371,375,376,379],"field",{"name":363,"type":364,":required":365},"Dual-slot flash","prerequisite","true","Two firmware partitions (A and B) plus a small ",[333,368,369],{},"trampoline"," boot-loader that picks\nthe highest-priority ",[372,373,374],"em",{},"valid"," slot. You write the slot you are ",[333,377,378],{},"not"," running from.",[361,381,383,384,387],{"name":382,"type":364,":required":365},"RDM responder","A working RDM stack with a parameter\u002FPID dispatch table you can extend, and access\nto whether an incoming request was ",[333,385,386],{},"broadcast"," (so you can suppress the reply).",[361,389,391,392,395,396,399],{"name":390,"type":364,":required":365},"CRC32","An incremental CRC32 routine (",[338,393,394],{},"crc = crc32_update(crc, bytes, len)","). The reference\nuses the standard reflected polynomial ",[338,397,398],{},"0xEDB88320",".",[361,401,403,404,407],{"name":402,"type":364},"A byte you own","One RDM manufacturer ID for your device family. Its broadcast UID\n(",[338,405,406],{},"mmmm:FFFFFFFF",") becomes your isolated \"flash everyone\" group.",[409,410,411,412,415,416,419,420,423,424,423,427,419,430,423,433,436,437,440],"warning",{},"Pick your ",[333,413,414],{},"six PID numbers"," up front and keep them identical on the device and in\nthe controller. This guide uses the reference set - ",[338,417,418],{},"file_setup 0xc8d7",",\n",[338,421,422],{},"file_chunk 0xc8cf",", ",[338,425,426],{},"file_report 0xc8cd",[338,428,429],{},"partition_info 0xc8ce",[338,431,432],{},"control 0xc8c8",[338,434,435],{},"firmware_state 0xc8f9"," - reused under ",[333,438,439],{},"your"," manufacturer ID.",[442,443,445],"h2",{"id":444},"build-sequence","Build sequence",[447,448,450,455,458,541,545,548,583,595,603,613,658,666,679,909,932,947,955,961,1004,1012,1030,1087,1091,1097,1111,1114,1118,1353,1357,1360,1875,1888,1901,1905,1915,1919,1926,1930,1936,1940,1946,1950],"steps",{"level":449},"3",[451,452,454],"h3",{"id":453},"step-1-define-the-session-state","Step 1 · Define the session state",[323,456,457],{},"Keep a single in-flight session - one update at a time is fine, RDM is a single\ntransfer bus.",[459,460,465],"pre",{"className":461,"code":462,"language":463,"meta":464,"style":464},"language-c shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","typedef enum { IDLE, RECEIVING } fw_state_t;\n\ntypedef struct {\n    fw_state_t state;\n    uint32_t   next_chunk;     \u002F\u002F the chunk_nb we expect next\n    uint32_t   crc_acc;        \u002F\u002F running CRC32 (init 0xFFFFFFFF)\n    slot_t     slot;           \u002F\u002F the inactive partition we write\n    uint8_t    listen_bc;      \u002F\u002F accept broadcast chunks? (set by file_setup)\n    \u002F\u002F + decryptor state, if images are encrypted\n} fw_session_t;\n\nstatic fw_session_t fw;   \u002F\u002F one global session\n","c","",[338,466,467,475,482,488,494,500,506,512,518,524,530,535],{"__ignoreMap":464},[468,469,472],"span",{"class":470,"line":471},"line",1,[468,473,474],{},"typedef enum { IDLE, RECEIVING } fw_state_t;\n",[468,476,478],{"class":470,"line":477},2,[468,479,481],{"emptyLinePlaceholder":480},true,"\n",[468,483,485],{"class":470,"line":484},3,[468,486,487],{},"typedef struct {\n",[468,489,491],{"class":470,"line":490},4,[468,492,493],{},"    fw_state_t state;\n",[468,495,497],{"class":470,"line":496},5,[468,498,499],{},"    uint32_t   next_chunk;     \u002F\u002F the chunk_nb we expect next\n",[468,501,503],{"class":470,"line":502},6,[468,504,505],{},"    uint32_t   crc_acc;        \u002F\u002F running CRC32 (init 0xFFFFFFFF)\n",[468,507,509],{"class":470,"line":508},7,[468,510,511],{},"    slot_t     slot;           \u002F\u002F the inactive partition we write\n",[468,513,515],{"class":470,"line":514},8,[468,516,517],{},"    uint8_t    listen_bc;      \u002F\u002F accept broadcast chunks? (set by file_setup)\n",[468,519,521],{"class":470,"line":520},9,[468,522,523],{},"    \u002F\u002F + decryptor state, if images are encrypted\n",[468,525,527],{"class":470,"line":526},10,[468,528,529],{},"} fw_session_t;\n",[468,531,533],{"class":470,"line":532},11,[468,534,481],{"emptyLinePlaceholder":480},[468,536,538],{"class":470,"line":537},12,[468,539,540],{},"static fw_session_t fw;   \u002F\u002F one global session\n",[451,542,544],{"id":543},"step-2-register-the-six-pids","Step 2 · Register the six PIDs",[323,546,547],{},"Add each PID to your responder's dispatch table with its command class.",[459,549,551],{"className":461,"code":550,"language":463,"meta":464,"style":464},"rdm_register(0xc8d7, RDM_SET, handle_file_setup);\nrdm_register(0xc8cf, RDM_SET, handle_file_chunk);\nrdm_register(0xc8cd, RDM_GET, handle_file_report);\nrdm_register(0xc8ce, RDM_GET, handle_partition_info);   \u002F\u002F optional but useful\nrdm_register(0xc8c8, RDM_SET, handle_control);\nrdm_register(0xc8f9, RDM_GET, handle_firmware_state);   \u002F\u002F optional\n",[338,552,553,558,563,568,573,578],{"__ignoreMap":464},[468,554,555],{"class":470,"line":471},[468,556,557],{},"rdm_register(0xc8d7, RDM_SET, handle_file_setup);\n",[468,559,560],{"class":470,"line":477},[468,561,562],{},"rdm_register(0xc8cf, RDM_SET, handle_file_chunk);\n",[468,564,565],{"class":470,"line":484},[468,566,567],{},"rdm_register(0xc8cd, RDM_GET, handle_file_report);\n",[468,569,570],{"class":470,"line":490},[468,571,572],{},"rdm_register(0xc8ce, RDM_GET, handle_partition_info);   \u002F\u002F optional but useful\n",[468,574,575],{"class":470,"line":496},[468,576,577],{},"rdm_register(0xc8c8, RDM_SET, handle_control);\n",[468,579,580],{"class":470,"line":502},[468,581,582],{},"rdm_register(0xc8f9, RDM_GET, handle_firmware_state);   \u002F\u002F optional\n",[584,585,586,587,590,591,594],"note",{},"Every handler should reject any sub-device other than ",[333,588,589],{},"root"," - firmware lives on\nthe root device. NACK with ",[338,592,593],{},"SUB_DEVICE_OUT_OF_RANGE"," otherwise.",[451,596,598,599,602],{"id":597},"step-3-implement-file_setup-arm-the-receiver","Step 3 · Implement ",[338,600,601],{},"file_setup"," (arm the receiver)",[323,604,605,606,609,610,612],{},"The only per-device step. Bit 0 of ",[338,607,608],{},"flags"," tells the device to accept the\n",[372,611,386],{}," chunks that follow.",[459,614,616],{"className":461,"code":615,"language":463,"meta":464,"style":464},"\u002F\u002F SET 0xc8d7  {u32 flags} -> {u32 status}\nstatus_t handle_file_setup(const uint8_t *req, uint8_t *resp) {\n    uint32_t flags = rd_u32(req);\n    fw.listen_bc = flags & 1u;\n    \u002F\u002F (do NOT begin a session here; chunk 0 does that - see Step 4)\n    wr_u32(resp, 0);          \u002F\u002F status = 0\n    return ACK;\n}\n",[338,617,618,623,628,633,638,643,648,653],{"__ignoreMap":464},[468,619,620],{"class":470,"line":471},[468,621,622],{},"\u002F\u002F SET 0xc8d7  {u32 flags} -> {u32 status}\n",[468,624,625],{"class":470,"line":477},[468,626,627],{},"status_t handle_file_setup(const uint8_t *req, uint8_t *resp) {\n",[468,629,630],{"class":470,"line":484},[468,631,632],{},"    uint32_t flags = rd_u32(req);\n",[468,634,635],{"class":470,"line":490},[468,636,637],{},"    fw.listen_bc = flags & 1u;\n",[468,639,640],{"class":470,"line":496},[468,641,642],{},"    \u002F\u002F (do NOT begin a session here; chunk 0 does that - see Step 4)\n",[468,644,645],{"class":470,"line":502},[468,646,647],{},"    wr_u32(resp, 0);          \u002F\u002F status = 0\n",[468,649,650],{"class":470,"line":508},[468,651,652],{},"    return ACK;\n",[468,654,655],{"class":470,"line":514},[468,656,657],{},"}\n",[451,659,661,662,665],{"id":660},"step-4-implement-file_chunk-receive-the-stream","Step 4 · Implement ",[338,663,664],{},"file_chunk"," (receive the stream)",[323,667,668,669,423,672,675,676,399],{},"The heart of it. Three responsibilities: ",[333,670,671],{},"validate the sequence",[333,673,674],{},"handle chunk\n0 as a header",", and ",[333,677,678],{},"write data + fold the CRC",[459,680,682],{"className":461,"code":681,"language":463,"meta":464,"style":464},"\u002F\u002F SET 0xc8cf  {u32 chunk_nb}{\u003C=128 B data} -> {u32 status}\nstatus_t handle_file_chunk(const uint8_t *req, uint16_t len, bool is_broadcast) {\n    uint32_t chunk_nb = rd_u32(req);\n    const uint8_t *data = req + 4;\n    uint16_t dlen = len - 4;\n\n    \u002F\u002F Ignore broadcast chunks unless armed (Step 3)\n    if (is_broadcast && !fw.listen_bc) return NO_REPLY;\n\n    \u002F\u002F --- chunk 0: self-describing header -------------------------------\n    if (chunk_nb == 0) {\n        fw_header_t h = parse_header(data, dlen);   \u002F\u002F flash range, ctrl block, seed\n        fw.slot       = other_partition();          \u002F\u002F never the running slot!\n        fw.next_chunk = 1;                          \u002F\u002F data starts at chunk 1\n        fw.crc_acc    = 0xFFFFFFFF;\n        fw.state      = RECEIVING;\n        decryptor_init(&fw, &h);                    \u002F\u002F if encrypted\n        flash_erase_slot(fw.slot);                  \u002F\u002F or erase-on-write per sector\n        return reply(is_broadcast, \u002F*status*\u002F0);\n    }\n\n    if (fw.state != RECEIVING) return reply(is_broadcast, ERR_NOT_ARMED);\n\n    \u002F\u002F --- sequence validation ------------------------------------------\n    if (chunk_nb \u003C fw.next_chunk) {                 \u002F\u002F duplicate \u002F retransmit\n        return reply(is_broadcast, 0);              \u002F\u002F idempotent ACK, no rewrite\n    }\n    if (chunk_nb > fw.next_chunk) {                  \u002F\u002F gap → out of order\n        return reply(is_broadcast, ERR_OUT_OF_ORDER);\n    }\n\n    \u002F\u002F --- write + checksum ---------------------------------------------\n    uint8_t plain[128];\n    decrypt_chunk(&fw, chunk_nb, data, plain);      \u002F\u002F no-op if unencrypted\n    uint32_t addr = slot_start(fw.slot) + 128 * (chunk_nb - 1);\n    flash_write(addr, plain, dlen);                 \u002F\u002F BLOCKING on the reference MCU\n    fw.crc_acc = crc32_update(fw.crc_acc, plain, dlen);\n    fw.next_chunk = chunk_nb + 1;\n\n    return reply(is_broadcast, 0);\n}\n",[338,683,684,689,694,699,704,709,713,718,723,727,732,737,742,748,754,760,766,772,778,784,790,795,801,806,812,818,824,829,835,841,846,851,857,863,869,875,881,887,893,898,904],{"__ignoreMap":464},[468,685,686],{"class":470,"line":471},[468,687,688],{},"\u002F\u002F SET 0xc8cf  {u32 chunk_nb}{\u003C=128 B data} -> {u32 status}\n",[468,690,691],{"class":470,"line":477},[468,692,693],{},"status_t handle_file_chunk(const uint8_t *req, uint16_t len, bool is_broadcast) {\n",[468,695,696],{"class":470,"line":484},[468,697,698],{},"    uint32_t chunk_nb = rd_u32(req);\n",[468,700,701],{"class":470,"line":490},[468,702,703],{},"    const uint8_t *data = req + 4;\n",[468,705,706],{"class":470,"line":496},[468,707,708],{},"    uint16_t dlen = len - 4;\n",[468,710,711],{"class":470,"line":502},[468,712,481],{"emptyLinePlaceholder":480},[468,714,715],{"class":470,"line":508},[468,716,717],{},"    \u002F\u002F Ignore broadcast chunks unless armed (Step 3)\n",[468,719,720],{"class":470,"line":514},[468,721,722],{},"    if (is_broadcast && !fw.listen_bc) return NO_REPLY;\n",[468,724,725],{"class":470,"line":520},[468,726,481],{"emptyLinePlaceholder":480},[468,728,729],{"class":470,"line":526},[468,730,731],{},"    \u002F\u002F --- chunk 0: self-describing header -------------------------------\n",[468,733,734],{"class":470,"line":532},[468,735,736],{},"    if (chunk_nb == 0) {\n",[468,738,739],{"class":470,"line":537},[468,740,741],{},"        fw_header_t h = parse_header(data, dlen);   \u002F\u002F flash range, ctrl block, seed\n",[468,743,745],{"class":470,"line":744},13,[468,746,747],{},"        fw.slot       = other_partition();          \u002F\u002F never the running slot!\n",[468,749,751],{"class":470,"line":750},14,[468,752,753],{},"        fw.next_chunk = 1;                          \u002F\u002F data starts at chunk 1\n",[468,755,757],{"class":470,"line":756},15,[468,758,759],{},"        fw.crc_acc    = 0xFFFFFFFF;\n",[468,761,763],{"class":470,"line":762},16,[468,764,765],{},"        fw.state      = RECEIVING;\n",[468,767,769],{"class":470,"line":768},17,[468,770,771],{},"        decryptor_init(&fw, &h);                    \u002F\u002F if encrypted\n",[468,773,775],{"class":470,"line":774},18,[468,776,777],{},"        flash_erase_slot(fw.slot);                  \u002F\u002F or erase-on-write per sector\n",[468,779,781],{"class":470,"line":780},19,[468,782,783],{},"        return reply(is_broadcast, \u002F*status*\u002F0);\n",[468,785,787],{"class":470,"line":786},20,[468,788,789],{},"    }\n",[468,791,793],{"class":470,"line":792},21,[468,794,481],{"emptyLinePlaceholder":480},[468,796,798],{"class":470,"line":797},22,[468,799,800],{},"    if (fw.state != RECEIVING) return reply(is_broadcast, ERR_NOT_ARMED);\n",[468,802,804],{"class":470,"line":803},23,[468,805,481],{"emptyLinePlaceholder":480},[468,807,809],{"class":470,"line":808},24,[468,810,811],{},"    \u002F\u002F --- sequence validation ------------------------------------------\n",[468,813,815],{"class":470,"line":814},25,[468,816,817],{},"    if (chunk_nb \u003C fw.next_chunk) {                 \u002F\u002F duplicate \u002F retransmit\n",[468,819,821],{"class":470,"line":820},26,[468,822,823],{},"        return reply(is_broadcast, 0);              \u002F\u002F idempotent ACK, no rewrite\n",[468,825,827],{"class":470,"line":826},27,[468,828,789],{},[468,830,832],{"class":470,"line":831},28,[468,833,834],{},"    if (chunk_nb > fw.next_chunk) {                  \u002F\u002F gap → out of order\n",[468,836,838],{"class":470,"line":837},29,[468,839,840],{},"        return reply(is_broadcast, ERR_OUT_OF_ORDER);\n",[468,842,844],{"class":470,"line":843},30,[468,845,789],{},[468,847,849],{"class":470,"line":848},31,[468,850,481],{"emptyLinePlaceholder":480},[468,852,854],{"class":470,"line":853},32,[468,855,856],{},"    \u002F\u002F --- write + checksum ---------------------------------------------\n",[468,858,860],{"class":470,"line":859},33,[468,861,862],{},"    uint8_t plain[128];\n",[468,864,866],{"class":470,"line":865},34,[468,867,868],{},"    decrypt_chunk(&fw, chunk_nb, data, plain);      \u002F\u002F no-op if unencrypted\n",[468,870,872],{"class":470,"line":871},35,[468,873,874],{},"    uint32_t addr = slot_start(fw.slot) + 128 * (chunk_nb - 1);\n",[468,876,878],{"class":470,"line":877},36,[468,879,880],{},"    flash_write(addr, plain, dlen);                 \u002F\u002F BLOCKING on the reference MCU\n",[468,882,884],{"class":470,"line":883},37,[468,885,886],{},"    fw.crc_acc = crc32_update(fw.crc_acc, plain, dlen);\n",[468,888,890],{"class":470,"line":889},38,[468,891,892],{},"    fw.next_chunk = chunk_nb + 1;\n",[468,894,896],{"class":470,"line":895},39,[468,897,481],{"emptyLinePlaceholder":480},[468,899,901],{"class":470,"line":900},40,[468,902,903],{},"    return reply(is_broadcast, 0);\n",[468,905,907],{"class":470,"line":906},41,[468,908,657],{},[323,910,911,912,915,916,919,920,923,924,927,928,931],{},"Two rules do the heavy lifting: the flash offset is derived from ",[338,913,914],{},"chunk_nb",", never\nsent explicitly - reject any gap, ACK a duplicate without rewriting, which makes the\nstream ",[333,917,918],{},"idempotent",". And ",[338,921,922],{},"reply(is_broadcast, …)"," sends ",[333,925,926],{},"no bytes"," on a\nbroadcast request (RDM forbids it) but ",[333,929,930],{},"still runs the handler"," - that's what\nmakes vendorcast fire-and-forget.",[933,934,935,938,939,942,943,946],"caution",{},[333,936,937],{},"Blocking write vs. the RDM deadline."," On an MCU that halts the core during flash\nerase\u002Fwrite (STM32F1 and friends), ",[338,940,941],{},"flash_write()"," blows the ~3 ms RDM reply\ndeadline - that's ",[372,944,945],{},"expected",": the controller paces the stream (55 ms) and relaxes\nits unicast timeout so the device finishes before the next chunk. If your MCU can\nrun while flash erases (e.g. ESP32-S3), don't block: copy the chunk into a RAM ring\nbuffer, fold the CRC, and ACK immediately in the callback; drain the buffer to flash\nin a background worker task in large (e.g. 4 KB) blocks, erasing one sector at a\ntime rather than the whole slot up front. Build and verify the blocking path first.",[451,948,950,951,954],{"id":949},"step-5-implement-file_report-confirm","Step 5 · Implement ",[338,952,953],{},"file_report"," (confirm)",[323,956,957,958,960],{},"A ",[338,959,344],{},", so always unicast. Return the device's verdict and accumulated CRC so the\ncontroller can cross-check.",[459,962,964],{"className":461,"code":963,"language":463,"meta":464,"style":464},"\u002F\u002F GET 0xc8cd  {} -> {u32 status, u32 crc}\nstatus_t handle_file_report(uint8_t *resp) {\n    uint32_t crc = crc32_final(fw.crc_acc);   \u002F\u002F one's-complement\n    wr_u32(resp + 0, fw.image_valid ? 0 : ERR_BAD_REPORT);\n    wr_u32(resp + 4, crc);\n    fw.listen_bc = 0;                          \u002F\u002F disarm broadcast - avoid surprises\n    return ACK;\n}\n",[338,965,966,971,976,981,986,991,996,1000],{"__ignoreMap":464},[468,967,968],{"class":470,"line":471},[468,969,970],{},"\u002F\u002F GET 0xc8cd  {} -> {u32 status, u32 crc}\n",[468,972,973],{"class":470,"line":477},[468,974,975],{},"status_t handle_file_report(uint8_t *resp) {\n",[468,977,978],{"class":470,"line":484},[468,979,980],{},"    uint32_t crc = crc32_final(fw.crc_acc);   \u002F\u002F one's-complement\n",[468,982,983],{"class":470,"line":490},[468,984,985],{},"    wr_u32(resp + 0, fw.image_valid ? 0 : ERR_BAD_REPORT);\n",[468,987,988],{"class":470,"line":496},[468,989,990],{},"    wr_u32(resp + 4, crc);\n",[468,992,993],{"class":470,"line":502},[468,994,995],{},"    fw.listen_bc = 0;                          \u002F\u002F disarm broadcast - avoid surprises\n",[468,997,998],{"class":470,"line":508},[468,999,652],{},[468,1001,1002],{"class":470,"line":514},[468,1003,657],{},[451,1005,1007,1008,1011],{"id":1006},"step-6-implement-controlreset_device-reboot","Step 6 · Implement ",[338,1009,1010],{},"control{reset_device}"," (reboot)",[323,1013,1014,1017,1018,1021,1022,1025,1026,1029],{},[338,1015,1016],{},"control"," carries a 16-bit index; ",[338,1019,1020],{},"0x0019"," = ",[338,1023,1024],{},"reset_device",". ",[333,1027,1028],{},"Reply first, then\nreboot",", so the ACK is never lost.",[459,1031,1033],{"className":461,"code":1032,"language":463,"meta":464,"style":464},"\u002F\u002F SET 0xc8c8  {u16 index, u32 value} -> {u32 status}\nstatus_t handle_control(const uint8_t *req, uint8_t *resp) {\n    uint16_t index = rd_u16(req);\n    if (index == 0x0019) {                 \u002F\u002F reset_device\n        wr_u32(resp, 0);\n        schedule_reboot_after_reply();     \u002F\u002F defer to post-response callback\n        return ACK;\n    }\n    wr_u32(resp, 0);                        \u002F\u002F ignore unknown indices, still ACK\n    return ACK;\n}\n",[338,1034,1035,1040,1045,1050,1055,1060,1065,1070,1074,1079,1083],{"__ignoreMap":464},[468,1036,1037],{"class":470,"line":471},[468,1038,1039],{},"\u002F\u002F SET 0xc8c8  {u16 index, u32 value} -> {u32 status}\n",[468,1041,1042],{"class":470,"line":477},[468,1043,1044],{},"status_t handle_control(const uint8_t *req, uint8_t *resp) {\n",[468,1046,1047],{"class":470,"line":484},[468,1048,1049],{},"    uint16_t index = rd_u16(req);\n",[468,1051,1052],{"class":470,"line":490},[468,1053,1054],{},"    if (index == 0x0019) {                 \u002F\u002F reset_device\n",[468,1056,1057],{"class":470,"line":496},[468,1058,1059],{},"        wr_u32(resp, 0);\n",[468,1061,1062],{"class":470,"line":502},[468,1063,1064],{},"        schedule_reboot_after_reply();     \u002F\u002F defer to post-response callback\n",[468,1066,1067],{"class":470,"line":508},[468,1068,1069],{},"        return ACK;\n",[468,1071,1072],{"class":470,"line":514},[468,1073,789],{},[468,1075,1076],{"class":470,"line":520},[468,1077,1078],{},"    wr_u32(resp, 0);                        \u002F\u002F ignore unknown indices, still ACK\n",[468,1080,1081],{"class":470,"line":526},[468,1082,652],{},[468,1084,1085],{"class":470,"line":532},[468,1086,657],{},[451,1088,1090],{"id":1089},"step-7-wire-the-trampoline-boot-logic","Step 7 · Wire the trampoline boot logic",[323,1092,1093,1094,1096],{},"You do ",[333,1095,378],{}," boot the new slot from application code:",[1098,1099,1100,1104],"ol",{},[1101,1102,1103],"li",{},"When the image is complete and its CRC matches, raise the new slot's boot\npriority to one above the running slot.",[1101,1105,1106,1107,1110],{},"On reset, the trampoline picks the highest-priority slot ",[333,1108,1109],{},"that passes its own\nindependent CRC check"," and jumps to it.",[323,1112,1113],{},"A half-written slot fails that check and is skipped - the device falls back to the\nprevious good image. This is your brick-safety guarantee; do not shortcut it.",[451,1115,1117],{"id":1116},"step-8-controller-slice-the-image","Step 8 · Controller: slice the image",[459,1119,1123],{"className":1120,"code":1121,"language":1122,"meta":464,"style":464},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","const CHUNK_SIZE = 128\nconst PACING_MS  = 55          \u002F\u002F inter-chunk delay for the blocking device model\n\nfunction* chunks(image: Uint8Array) {\n  \u002F\u002F chunk 0 is the self-describing header, then 1..N are data\n  yield { nb: 0, data: buildHeader(image) }\n  for (let i = 0, nb = 1; i \u003C image.length; i += CHUNK_SIZE, nb++) {\n    yield { nb, data: image.subarray(i, i + CHUNK_SIZE) }\n  }\n}\n","ts",[338,1124,1125,1143,1159,1163,1195,1200,1238,1304,1344,1349],{"__ignoreMap":464},[468,1126,1127,1131,1135,1139],{"class":470,"line":471},[468,1128,1130],{"class":1129},"spNyl","const",[468,1132,1134],{"class":1133},"sTEyZ"," CHUNK_SIZE ",[468,1136,1138],{"class":1137},"sMK4o","=",[468,1140,1142],{"class":1141},"sbssI"," 128\n",[468,1144,1145,1147,1150,1152,1155],{"class":470,"line":477},[468,1146,1130],{"class":1129},[468,1148,1149],{"class":1133}," PACING_MS  ",[468,1151,1138],{"class":1137},[468,1153,1154],{"class":1141}," 55",[468,1156,1158],{"class":1157},"sHwdD","          \u002F\u002F inter-chunk delay for the blocking device model\n",[468,1160,1161],{"class":470,"line":484},[468,1162,481],{"emptyLinePlaceholder":480},[468,1164,1165,1168,1171,1175,1178,1182,1185,1189,1192],{"class":470,"line":490},[468,1166,1167],{"class":1129},"function",[468,1169,1170],{"class":1137},"*",[468,1172,1174],{"class":1173},"s2Zo4"," chunks",[468,1176,1177],{"class":1137},"(",[468,1179,1181],{"class":1180},"sHdIc","image",[468,1183,1184],{"class":1137},":",[468,1186,1188],{"class":1187},"sBMFI"," Uint8Array",[468,1190,1191],{"class":1137},")",[468,1193,1194],{"class":1137}," {\n",[468,1196,1197],{"class":470,"line":496},[468,1198,1199],{"class":1157},"  \u002F\u002F chunk 0 is the self-describing header, then 1..N are data\n",[468,1201,1202,1206,1209,1213,1215,1218,1221,1224,1226,1229,1231,1233,1236],{"class":470,"line":502},[468,1203,1205],{"class":1204},"s7zQu","  yield",[468,1207,1208],{"class":1137}," {",[468,1210,1212],{"class":1211},"swJcz"," nb",[468,1214,1184],{"class":1137},[468,1216,1217],{"class":1141}," 0",[468,1219,1220],{"class":1137},",",[468,1222,1223],{"class":1211}," data",[468,1225,1184],{"class":1137},[468,1227,1228],{"class":1173}," buildHeader",[468,1230,1177],{"class":1211},[468,1232,1181],{"class":1133},[468,1234,1235],{"class":1211},") ",[468,1237,657],{"class":1137},[468,1239,1240,1243,1246,1249,1252,1255,1257,1259,1261,1263,1266,1269,1271,1274,1277,1279,1282,1284,1286,1289,1292,1294,1296,1299,1301],{"class":470,"line":508},[468,1241,1242],{"class":1204},"  for",[468,1244,1245],{"class":1211}," (",[468,1247,1248],{"class":1129},"let",[468,1250,1251],{"class":1133}," i",[468,1253,1254],{"class":1137}," =",[468,1256,1217],{"class":1141},[468,1258,1220],{"class":1137},[468,1260,1212],{"class":1133},[468,1262,1254],{"class":1137},[468,1264,1265],{"class":1141}," 1",[468,1267,1268],{"class":1137},";",[468,1270,1251],{"class":1133},[468,1272,1273],{"class":1137}," \u003C",[468,1275,1276],{"class":1133}," image",[468,1278,399],{"class":1137},[468,1280,1281],{"class":1133},"length",[468,1283,1268],{"class":1137},[468,1285,1251],{"class":1133},[468,1287,1288],{"class":1137}," +=",[468,1290,1291],{"class":1133}," CHUNK_SIZE",[468,1293,1220],{"class":1137},[468,1295,1212],{"class":1133},[468,1297,1298],{"class":1137},"++",[468,1300,1235],{"class":1211},[468,1302,1303],{"class":1137},"{\n",[468,1305,1306,1309,1311,1313,1315,1317,1319,1321,1323,1326,1328,1331,1333,1335,1338,1340,1342],{"class":470,"line":514},[468,1307,1308],{"class":1204},"    yield",[468,1310,1208],{"class":1137},[468,1312,1212],{"class":1133},[468,1314,1220],{"class":1137},[468,1316,1223],{"class":1211},[468,1318,1184],{"class":1137},[468,1320,1276],{"class":1133},[468,1322,399],{"class":1137},[468,1324,1325],{"class":1173},"subarray",[468,1327,1177],{"class":1211},[468,1329,1330],{"class":1133},"i",[468,1332,1220],{"class":1137},[468,1334,1251],{"class":1133},[468,1336,1337],{"class":1137}," +",[468,1339,1291],{"class":1133},[468,1341,1235],{"class":1211},[468,1343,657],{"class":1137},[468,1345,1346],{"class":470,"line":520},[468,1347,1348],{"class":1137},"  }\n",[468,1350,1351],{"class":470,"line":526},[468,1352,657],{"class":1137},[451,1354,1356],{"id":1355},"step-9-controller-drive-the-transfer","Step 9 · Controller: drive the transfer",[323,1358,1359],{},"Arm every target, stream once to the whole family, confirm each device, retry\nstragglers, then reboot everyone.",[459,1361,1363],{"className":1120,"code":1362,"language":1122,"meta":464,"style":464},"async function flashOverRdm(node, port, targets: Uid[], image: Uint8Array) {\n  const BROADCAST = uid(MANUFACTURER, 0xffffffff)   \u002F\u002F mmmm:FFFFFFFF\n\n  \u002F\u002F 1 · Arm every target (unicast) so they accept broadcast chunks\n  for (const uid of targets) {\n    await rdmSet(node, port, uid, 0xc8d7, u32(1))   \u002F\u002F flags: listen=1\n  }\n\n  \u002F\u002F 2 · Stream the image once, to the whole family (vendorcast, no ACK)\n  for (const { nb, data } of chunks(image)) {\n    rdmSetNoReply(node, port, BROADCAST, 0xc8cf, concat(u32(nb), data))\n    await sleep(PACING_MS)                            \u002F\u002F pace for the slowest device\n  }\n\n  \u002F\u002F 3 · Confirm each device individually (unicast GET)\n  const failed: Uid[] = []\n  for (const uid of targets) {\n    const { status, crc } = await rdmGet(node, port, uid, 0xc8cd)\n    if (status !== 0 || crc !== crc32(image)) failed.push(uid)\n  }\n\n  \u002F\u002F 4 · Retry stragglers by unicast, then reboot everyone\n  for (const uid of failed) await unicastFlash(node, port, uid, image)\n  for (const uid of targets) {\n    await rdmSet(node, port, uid, 0xc8c8, control(0x0019))   \u002F\u002F reset_device\n  }\n}\n",[338,1364,1365,1411,1440,1444,1449,1468,1509,1513,1517,1522,1554,1600,1618,1622,1626,1631,1650,1668,1713,1759,1763,1767,1772,1812,1830,1867,1871],{"__ignoreMap":464},[468,1366,1367,1370,1373,1376,1378,1381,1383,1386,1388,1391,1393,1396,1399,1401,1403,1405,1407,1409],{"class":470,"line":471},[468,1368,1369],{"class":1129},"async",[468,1371,1372],{"class":1129}," function",[468,1374,1375],{"class":1173}," flashOverRdm",[468,1377,1177],{"class":1137},[468,1379,1380],{"class":1180},"node",[468,1382,1220],{"class":1137},[468,1384,1385],{"class":1180}," port",[468,1387,1220],{"class":1137},[468,1389,1390],{"class":1180}," targets",[468,1392,1184],{"class":1137},[468,1394,1395],{"class":1187}," Uid",[468,1397,1398],{"class":1133},"[]",[468,1400,1220],{"class":1137},[468,1402,1276],{"class":1180},[468,1404,1184],{"class":1137},[468,1406,1188],{"class":1187},[468,1408,1191],{"class":1137},[468,1410,1194],{"class":1137},[468,1412,1413,1416,1419,1421,1424,1426,1429,1431,1434,1437],{"class":470,"line":477},[468,1414,1415],{"class":1129},"  const",[468,1417,1418],{"class":1133}," BROADCAST",[468,1420,1254],{"class":1137},[468,1422,1423],{"class":1173}," uid",[468,1425,1177],{"class":1211},[468,1427,1428],{"class":1133},"MANUFACTURER",[468,1430,1220],{"class":1137},[468,1432,1433],{"class":1141}," 0xffffffff",[468,1435,1436],{"class":1211},")   ",[468,1438,1439],{"class":1157},"\u002F\u002F mmmm:FFFFFFFF\n",[468,1441,1442],{"class":470,"line":484},[468,1443,481],{"emptyLinePlaceholder":480},[468,1445,1446],{"class":470,"line":490},[468,1447,1448],{"class":1157},"  \u002F\u002F 1 · Arm every target (unicast) so they accept broadcast chunks\n",[468,1450,1451,1453,1455,1457,1459,1462,1464,1466],{"class":470,"line":496},[468,1452,1242],{"class":1204},[468,1454,1245],{"class":1211},[468,1456,1130],{"class":1129},[468,1458,1423],{"class":1133},[468,1460,1461],{"class":1137}," of",[468,1463,1390],{"class":1133},[468,1465,1235],{"class":1211},[468,1467,1303],{"class":1137},[468,1469,1470,1473,1476,1478,1480,1482,1484,1486,1488,1490,1493,1495,1498,1500,1503,1506],{"class":470,"line":502},[468,1471,1472],{"class":1204},"    await",[468,1474,1475],{"class":1173}," rdmSet",[468,1477,1177],{"class":1211},[468,1479,1380],{"class":1133},[468,1481,1220],{"class":1137},[468,1483,1385],{"class":1133},[468,1485,1220],{"class":1137},[468,1487,1423],{"class":1133},[468,1489,1220],{"class":1137},[468,1491,1492],{"class":1141}," 0xc8d7",[468,1494,1220],{"class":1137},[468,1496,1497],{"class":1173}," u32",[468,1499,1177],{"class":1211},[468,1501,1502],{"class":1141},"1",[468,1504,1505],{"class":1211},"))   ",[468,1507,1508],{"class":1157},"\u002F\u002F flags: listen=1\n",[468,1510,1511],{"class":470,"line":508},[468,1512,1348],{"class":1137},[468,1514,1515],{"class":470,"line":514},[468,1516,481],{"emptyLinePlaceholder":480},[468,1518,1519],{"class":470,"line":520},[468,1520,1521],{"class":1157},"  \u002F\u002F 2 · Stream the image once, to the whole family (vendorcast, no ACK)\n",[468,1523,1524,1526,1528,1530,1532,1534,1536,1538,1541,1543,1545,1547,1549,1552],{"class":470,"line":526},[468,1525,1242],{"class":1204},[468,1527,1245],{"class":1211},[468,1529,1130],{"class":1129},[468,1531,1208],{"class":1137},[468,1533,1212],{"class":1133},[468,1535,1220],{"class":1137},[468,1537,1223],{"class":1133},[468,1539,1540],{"class":1137}," }",[468,1542,1461],{"class":1137},[468,1544,1174],{"class":1173},[468,1546,1177],{"class":1211},[468,1548,1181],{"class":1133},[468,1550,1551],{"class":1211},")) ",[468,1553,1303],{"class":1137},[468,1555,1556,1559,1561,1563,1565,1567,1569,1571,1573,1576,1578,1581,1583,1586,1588,1591,1593,1595,1597],{"class":470,"line":532},[468,1557,1558],{"class":1173},"    rdmSetNoReply",[468,1560,1177],{"class":1211},[468,1562,1380],{"class":1133},[468,1564,1220],{"class":1137},[468,1566,1385],{"class":1133},[468,1568,1220],{"class":1137},[468,1570,1418],{"class":1133},[468,1572,1220],{"class":1137},[468,1574,1575],{"class":1141}," 0xc8cf",[468,1577,1220],{"class":1137},[468,1579,1580],{"class":1173}," concat",[468,1582,1177],{"class":1211},[468,1584,1585],{"class":1173},"u32",[468,1587,1177],{"class":1211},[468,1589,1590],{"class":1133},"nb",[468,1592,1191],{"class":1211},[468,1594,1220],{"class":1137},[468,1596,1223],{"class":1133},[468,1598,1599],{"class":1211},"))\n",[468,1601,1602,1604,1607,1609,1612,1615],{"class":470,"line":537},[468,1603,1472],{"class":1204},[468,1605,1606],{"class":1173}," sleep",[468,1608,1177],{"class":1211},[468,1610,1611],{"class":1133},"PACING_MS",[468,1613,1614],{"class":1211},")                            ",[468,1616,1617],{"class":1157},"\u002F\u002F pace for the slowest device\n",[468,1619,1620],{"class":470,"line":744},[468,1621,1348],{"class":1137},[468,1623,1624],{"class":470,"line":750},[468,1625,481],{"emptyLinePlaceholder":480},[468,1627,1628],{"class":470,"line":756},[468,1629,1630],{"class":1157},"  \u002F\u002F 3 · Confirm each device individually (unicast GET)\n",[468,1632,1633,1635,1638,1640,1642,1645,1647],{"class":470,"line":762},[468,1634,1415],{"class":1129},[468,1636,1637],{"class":1133}," failed",[468,1639,1184],{"class":1137},[468,1641,1395],{"class":1187},[468,1643,1644],{"class":1211},"[] ",[468,1646,1138],{"class":1137},[468,1648,1649],{"class":1211}," []\n",[468,1651,1652,1654,1656,1658,1660,1662,1664,1666],{"class":470,"line":768},[468,1653,1242],{"class":1204},[468,1655,1245],{"class":1211},[468,1657,1130],{"class":1129},[468,1659,1423],{"class":1133},[468,1661,1461],{"class":1137},[468,1663,1390],{"class":1133},[468,1665,1235],{"class":1211},[468,1667,1303],{"class":1137},[468,1669,1670,1673,1675,1678,1680,1683,1685,1687,1690,1693,1695,1697,1699,1701,1703,1705,1707,1710],{"class":470,"line":774},[468,1671,1672],{"class":1129},"    const",[468,1674,1208],{"class":1137},[468,1676,1677],{"class":1133}," status",[468,1679,1220],{"class":1137},[468,1681,1682],{"class":1133}," crc",[468,1684,1540],{"class":1137},[468,1686,1254],{"class":1137},[468,1688,1689],{"class":1204}," await",[468,1691,1692],{"class":1173}," rdmGet",[468,1694,1177],{"class":1211},[468,1696,1380],{"class":1133},[468,1698,1220],{"class":1137},[468,1700,1385],{"class":1133},[468,1702,1220],{"class":1137},[468,1704,1423],{"class":1133},[468,1706,1220],{"class":1137},[468,1708,1709],{"class":1141}," 0xc8cd",[468,1711,1712],{"class":1211},")\n",[468,1714,1715,1718,1720,1723,1726,1728,1731,1733,1735,1738,1740,1742,1744,1747,1749,1752,1754,1757],{"class":470,"line":780},[468,1716,1717],{"class":1204},"    if",[468,1719,1245],{"class":1211},[468,1721,1722],{"class":1133},"status",[468,1724,1725],{"class":1137}," !==",[468,1727,1217],{"class":1141},[468,1729,1730],{"class":1137}," ||",[468,1732,1682],{"class":1133},[468,1734,1725],{"class":1137},[468,1736,1737],{"class":1173}," crc32",[468,1739,1177],{"class":1211},[468,1741,1181],{"class":1133},[468,1743,1551],{"class":1211},[468,1745,1746],{"class":1133},"failed",[468,1748,399],{"class":1137},[468,1750,1751],{"class":1173},"push",[468,1753,1177],{"class":1211},[468,1755,1756],{"class":1133},"uid",[468,1758,1712],{"class":1211},[468,1760,1761],{"class":470,"line":786},[468,1762,1348],{"class":1137},[468,1764,1765],{"class":470,"line":792},[468,1766,481],{"emptyLinePlaceholder":480},[468,1768,1769],{"class":470,"line":797},[468,1770,1771],{"class":1157},"  \u002F\u002F 4 · Retry stragglers by unicast, then reboot everyone\n",[468,1773,1774,1776,1778,1780,1782,1784,1786,1788,1791,1794,1796,1798,1800,1802,1804,1806,1808,1810],{"class":470,"line":803},[468,1775,1242],{"class":1204},[468,1777,1245],{"class":1211},[468,1779,1130],{"class":1129},[468,1781,1423],{"class":1133},[468,1783,1461],{"class":1137},[468,1785,1637],{"class":1133},[468,1787,1235],{"class":1211},[468,1789,1790],{"class":1204},"await",[468,1792,1793],{"class":1173}," unicastFlash",[468,1795,1177],{"class":1211},[468,1797,1380],{"class":1133},[468,1799,1220],{"class":1137},[468,1801,1385],{"class":1133},[468,1803,1220],{"class":1137},[468,1805,1423],{"class":1133},[468,1807,1220],{"class":1137},[468,1809,1276],{"class":1133},[468,1811,1712],{"class":1211},[468,1813,1814,1816,1818,1820,1822,1824,1826,1828],{"class":470,"line":808},[468,1815,1242],{"class":1204},[468,1817,1245],{"class":1211},[468,1819,1130],{"class":1129},[468,1821,1423],{"class":1133},[468,1823,1461],{"class":1137},[468,1825,1390],{"class":1133},[468,1827,1235],{"class":1211},[468,1829,1303],{"class":1137},[468,1831,1832,1834,1836,1838,1840,1842,1844,1846,1848,1850,1853,1855,1858,1860,1862,1864],{"class":470,"line":814},[468,1833,1472],{"class":1204},[468,1835,1475],{"class":1173},[468,1837,1177],{"class":1211},[468,1839,1380],{"class":1133},[468,1841,1220],{"class":1137},[468,1843,1385],{"class":1133},[468,1845,1220],{"class":1137},[468,1847,1423],{"class":1133},[468,1849,1220],{"class":1137},[468,1851,1852],{"class":1141}," 0xc8c8",[468,1854,1220],{"class":1137},[468,1856,1857],{"class":1173}," control",[468,1859,1177],{"class":1211},[468,1861,1020],{"class":1141},[468,1863,1505],{"class":1211},[468,1865,1866],{"class":1157},"\u002F\u002F reset_device\n",[468,1868,1869],{"class":470,"line":820},[468,1870,1348],{"class":1137},[468,1872,1873],{"class":470,"line":826},[468,1874,657],{"class":1137},[323,1876,1877,1880,1881,1883,1884,1887],{},[338,1878,1879],{},"unicastFlash"," is the same loop addressed to one UID, checking each chunk's\n",[338,1882,1722],{}," reply - the controller must ",[333,1885,1886],{},"relax its RDM reply timeout",", since the\ndevice answers late while flash is busy. Reserve it for single-fixture targeting\nand CRC-failure recovery.",[323,1889,1890,1891,1896,1897,1900],{},"You address every message as ",[333,1892,1893],{},[338,1894,1895],{},"(gateway-node, output-port, responder-UID)"," - no\nper-device routing needed: cut-through forwarding carries frames down the chain, and\nthe manufacturer broadcast reaches every device of your family behind the node. The\ncontroller only needs topology to ",[333,1898,1899],{},"order reboots"," (far end of the chain first).",[451,1902,1904],{"id":1903},"step-10-bring-up-unicast-single-device-plaintext","Step 10 · Bring-up: unicast, single device, plaintext",[323,1906,1907,1908,1910,1911,1914],{},"Flash one device by unicast with a plaintext image and relaxed timeouts. Verify\n",[338,1909,953],{}," returns ",[338,1912,1913],{},"status = 0"," and a CRC matching your host-side CRC. Reboot and\nconfirm the new image runs.",[451,1916,1918],{"id":1917},"step-11-bring-up-interrupt-mid-transfer","Step 11 · Bring-up: interrupt mid-transfer",[323,1920,1921,1922,1925],{},"Pull power (or the cable) partway through. Confirm the device ",[333,1923,1924],{},"still boots the old\nimage"," - the trampoline must reject the half-written slot. This proves\nbrick-safety before you trust broadcast.",[451,1927,1929],{"id":1928},"step-12-bring-up-vendorcast-single-device","Step 12 · Bring-up: vendorcast, single device",[323,1931,1932,1933,1935],{},"Switch to the broadcast UID with 55 ms pacing and no per-chunk ACK. Confirm the\narmed device writes correctly and the final unicast ",[338,1934,953],{}," passes.",[451,1937,1939],{"id":1938},"step-13-bring-up-vendorcast-full-chain","Step 13 · Bring-up: vendorcast, full chain",[323,1941,1942,1943,1945],{},"Flash several devices at once. Confirm every device's ",[338,1944,953],{}," passes;\ndeliberately induce a drop and confirm your unicast fallback re-flashes only the\nfailed device.",[451,1947,1949],{"id":1948},"step-14-bring-up-encrypted-images-if-applicable","Step 14 · Bring-up: encrypted images (if applicable)",[323,1951,1952,1953,1956],{},"Ship an encrypted image and confirm it's accepted ",[333,1954,1955],{},"only"," when read-out protection\nis locked, and rejected otherwise.",[442,1958,1960],{"id":1959},"porting-notes","Porting notes",[1962,1963,1964,1969,1999,2010,2028],"accordion",{},[1965,1966,1968],"accordion-item",{"label":1967},"My MCU can run while flash erases - should I still block?","No - use the decoupled ring-buffer + worker variant (Step 4). You can then pace\nfaster than 55 ms. But implement and verify the blocking path first; it's simpler\nand is your reference for \"correct\".",[1965,1970,1972,1973,1976,1977,1987,1988,1991,1992,1995,1996],{"label":1971},"I broadcast to a fleet with mixed hardware variants","Vendorcast delivers the ",[333,1974,1975],{},"same image to every device of your manufacturer",". If your\nfamily spans incompatible hardware, add a ",[333,1978,1979,1982,1983,1986],{},[338,1980,1981],{},"hw_type"," \u002F ",[338,1984,1985],{},"hw_revision"," field"," to the\nchunk-0 header and have each device ",[333,1989,1990],{},"reject a mismatched image"," (refuse to begin\nthe write); treat ",[338,1993,1994],{},"0"," as a wildcard. Without this guard, one broadcast can brick the\nwrong variant. ",[372,1997,1998],{},"(This is the one safety the ESP32-S3 implementation adds over the\nnano, precisely because it broadcasts to a mixed fleet.)",[1965,2000,2002,2003,2006,2007,2009],{"label":2001},"How big should chunks be?","Stick with ",[333,2004,2005],{},"128 bytes"," - the largest power of two that fits an RDM payload\ncomfortably, keeping offset math trivial. Smaller works but is slower; larger risks\nexceeding the 232-byte RDM parameter limit once you add the ",[338,2008,914],{}," header.",[1965,2011,2013,2014,1245,2017,2020,2021,1245,2024,2027],{"label":2012},"Do I need partition_info and firmware_state?","No - both optional. ",[338,2015,2016],{},"partition_info",[338,2018,2019],{},"0xc8ce",") lets the controller introspect A\u002FB\nslot addresses and CRCs; ",[338,2022,2023],{},"firmware_state",[338,2025,2026],{},"0xc8f9",") returns a human-readable state\nstring. Implement them only if you want richer tooling and diagnostics.",[1965,2029,2031,2032,2034,2035,2037,2038,2041,2042,2045],{"label":2030},"Is broadcasting firmware chunks actually spec-legal?","Yes. ANSI E1.20 §5.3 permits ",[338,2033,348],{}," commands addressed to a manufacturer-specific\nbroadcast UID, and requires responders ",[333,2036,378],{}," to reply to them. The prohibition in\n§6.2.10.2 only concerns the ",[372,2039,2040],{},"all-manufacturer"," broadcast (",[338,2043,2044],{},"FFFF:FFFFFFFF","), where PID\nnumbers would be ambiguous - not the manufacturer-specific one used here.",[2047,2048,2050,2053,2054,2057],"tip",{"icon":2049},"i-lucide-flag",[333,2051,2052],{},"Ship it as a fallback."," RDM flashing takes minutes and lacks per-chunk\nretransmit. Keep network OTA as the default and present RDM update to operators as\nthe deliberate ",[372,2055,2056],{},"no-network recovery"," path it is.",[2059,2060,2061],"style",{},"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 .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 .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}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 .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 .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}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 .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}",{"title":464,"searchDepth":477,"depth":477,"links":2063},[2064,2084],{"id":444,"depth":477,"text":445,"children":2065},[2066,2067,2068,2070,2072,2074,2076,2077,2078,2079,2080,2081,2082,2083],{"id":453,"depth":484,"text":454},{"id":543,"depth":484,"text":544},{"id":597,"depth":484,"text":2069},"Step 3 · Implement file_setup (arm the receiver)",{"id":660,"depth":484,"text":2071},"Step 4 · Implement file_chunk (receive the stream)",{"id":949,"depth":484,"text":2073},"Step 5 · Implement file_report (confirm)",{"id":1006,"depth":484,"text":2075},"Step 6 · Implement control{reset_device} (reboot)",{"id":1089,"depth":484,"text":1090},{"id":1116,"depth":484,"text":1117},{"id":1355,"depth":484,"text":1356},{"id":1903,"depth":484,"text":1904},{"id":1917,"depth":484,"text":1918},{"id":1928,"depth":484,"text":1929},{"id":1938,"depth":484,"text":1939},{"id":1948,"depth":484,"text":1949},{"id":1959,"depth":477,"text":1960},"A step-by-step, MCU-agnostic guide to implementing firmware update over RDM - the responder on your device and the streaming logic on the controller.","md",null,{},{"icon":47},{"title":44,"description":2085},"r1ZmAMdkiWFD5SJuxlJFDbUS_bW0tmMHnSD1WJDlgCA",[2093,2087],{"title":40,"path":41,"stem":42,"description":2094,"icon":35,"children":-1},"How KLSTR flashes a device's firmware over the DMX cable using the RDM bus - the protocol, the delivery model, and the safety mechanics, abstracted for any MCU.",1787149428897]