Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The owner has run the image on a panel since. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Core v3.10.5, Audio v0.6.5 staged by shelf.sh from a sketch copy without patternflow_secrets.h; the previous folders retired to their tags. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes dev to main for v3.10.5.
Firmware
resetReasonin/api/statussaid"panic"or"task_wdt"and nothing else - not where, and not which of forty installed patterns was on. The SDK had been writing a core dump to its own flash partition on every panic all along, and nothing read it. At boot the firmware now reads that dump's summary - the task, the cause, the faulting address, the PC and up to sixteen return addresses, and the hash of the image that crashed - and it keeps a breadcrumb through the reset: the pattern's slug, which of its calls was running (loading, constructors,setup,update,draw, or none of them) and where the module's code was executing from. Status gains acrashobject with both. With the two together an address inside a module comes out as an offset ("+0x4e") that the.pfm's own symbol table resolves, whether the code ran from PSRAM or from internal RAM.DELETE /api/crashclears it, and the same record is printed on serial at boot ([CRASH] ...). It reports and does nothing else: no pattern is skipped, no reboot is forced, and what the panel does after a crash is what it did before. The breadcrumb covers the boot that follows a panic or a watchdog and is gone with the power; the dump stays in flash, so a board that panicked on an older firmware shows that dump, markedfromThisReset: false, until it is cleared. Checked on a board: a module stored through a null pointer indraw()while running from PSRAM, and after the reboot status named the pattern, the phase,pc+0x4eand the caller at+0xb6- the offsets the module's own symbols give for those two functions - with the dump read in 62 ms; a dump left by an earlier firmware appeared asfromThisReset: false. It costs 296 bytes of internal RAM.docs/rest-api.md("The crash record") says how to read one and decode it, andfirmware/toolchain/check_crash.py, a new host check, boots the logic through every reset reason against every state of the breadcrumb and the dump; CI runs it.check_blit.pycompares 70 million of them). Measured on two boards, alternating images:presentUs5,960 -> 3,496 µs on the default and Audio editions alike, so Origin goes from 12.1 to 9.6 ms a frame and a 30 ms pattern to 27.6. With white balance or gamma tuned away from the defaults it is 3.8 ms. It also takes 1 KB less internal RAM than the loop it replaces./api/statusgainsloopStackMin.0x06000000. The loader now places code in PSRAM, writes and relocates it through the heap's pointer, and runs it through the alias (src/core_module_memory.h,execAddress()insrc/core_module_loader.h). On the Audio edition a module with 23 KB or 32 KB of code was refused and now loads, and internal heap with a module resident is about 27.8 KB whatever the size of its code, where a 10 KB one used to leave 21-23 KB. On a second panel a community pattern with 8 KB of code was being refused one load in four for want of 528 bytes; it now loads every time and leaves 29 KB free instead of 22. Measured on two boards, same boot, alternating: catalogue and community modules within -0.1..+1.2% of their old frame time; modules built to be the worst case (10-32 KB of code, all of it executed every frame) 2.6-5.3% slower; 400 switches between modules of different sizes with no reset and no refusal. The code block is given whole cache lines to itself, the load reads it back through the instruction bus before calling it, and a unit on which that ever fails goes back to internal RAM until reboot (moduleMemory.codePolicy).load.codein/api/statussays where the code is. Module files and the ABI are unchanged.POSTthat ended after its first boundary - to any address, not only an upload - left the web server's form parser reading empty lines for ever with nothing to wait for, and the Core-0 watchdog reset the board five seconds later. The parser now knows a line that never ended from a blank one and gives the form up, tells an upload handler whose file had already arrived that it was aborted, and leaves none of the form's fields behind to answer for the next request (src/webserver/VENDORED.md, Fix 4). Reproduced on a panel before, gone after.curl -X POSTto/api/patternsor/updatewith no form in it called the upload handler with no upload to read and panicked the board; a multipart request naming a 9,000-character boundary overflowed the network task's stack; and a rawPUT /update?size=Nnever saw its ownsize. All three are fixed in the vendored server (VENDORED.md, Fix 5), and all three came out offirmware/toolchain/check_parser.py, a new host check that replays cut-off, stalled and malformed requests - 13,953 of them - through the real parser and fails if it ever spins or holds; CI runs it./api/patternswrote names into its reply unescaped, so one"or\in a title made the whole reply unparseable and the/patternspage could not list, select or delete anything - including the pattern responsible. Names are escaped now, a sidecar name is read as the JSON string it is - an escaped quote no longer ends it, and\uXXXXbecomes the character, so "Dynamic Moiré" is that before the pattern has ever been loaded and not only after - and a name too long for its slot is cut between characters rather than through one. The hotspot password got the same escaping in/api/hotspot.float* trail = PFMem::allocFloats(n);) crashed the panel every time it was picked, having built and uploaded cleanly. Constructors now run after the entry point and beforesetup().draw()runs on the render loop, and the render loop is on no watchdog: onewhile (a > PI) a -= 2 * PIon a value that has reached infinity - which float32 does where the browser's doubles did not - and the panel holds its last frame for good. The first console request that needed the loop then waited for it without limit, and the/patternspage opens with one; the server answers one connection at a time, so/api/status,/updateand Reboot, which need nothing from the loop, went silent behind it, and a frozen panel could be neither diagnosed nor restarted without pulling the plug. A request now gives the loop up once it has not reached its frame boundary for 20 s (PF_LOOP_STALL_MS- measured from the loop's own stamp, not from how long the request has waited, so an upload waiting out a slowsetup()is not mistaken for it) and answers503 render loop is not answering; the request is taken back with one atomic exchange, so the loop can never run it on a stack frame that has already returned./api/statuscarriesloopAgeMs,loopStalledandloopSyncGaveUp, read without the loop's help, and every handler in the core and in the features answers the refusal instead of using what its body never set. The Status page says "not answering for 47s" in red, where it would have gone on showing the frame rate from before the stop under "awake". Nothing restarts the panel by itself: this reports and survives, and the Reboot button does the rest. Twenty seconds is clear of one feature lookup with the internet down and not of two in a row, so on Performance with a dead uplink a healthy panel can read "not answering" for some seconds and then carry on - which is why it does not say "hung". The console has to have come up once for any of this: its routes are registered by the loop on the first network link. Found by reading - no stock pattern hangs - then reproduced on a panel with a module that hangs on purpose: before, the pattern list got no reply and after it neither did status, until the board was reset over USB; after, the list answered 503 in 18 s, sixty more requests were refused at once with the heap unchanged, the Status, Update and home pages still loaded, and Reboot brought the panel back. Held by a host test that races the two sides against each other;firmware/toolchain/tests/modules/_hang_probeis a module that hangs on purpose, for the bench (src/core_loop_sync.h).drawCenteredFit— "pattern" / "flow-a1b2", "pattern" / "flow.local" — keeping every line where it was./patterns?src=) took only.pfmand.jsonfrom a build's file list, so thecatalog.txtevery deck build carries was left behind and the deck landed in alphabetical order. It now takes exactly what a dropped zip does (catalog.txtand.pfstoo) - one rule,installable(), for every way in./updateconnection now waits up to two minutes for a stalled upload instead of five seconds, and the page gives up after five minutes with a message instead of waiting forever. Checked on a panel with a 12 s stall halfway through a flash.PUT /updatetakes a raw image, forcurl -T. From Simone Majocchi (#450).PANEL_GEOMETRYinconfig.hpicks the stock 128×64, one 64×64, or two 64×64 daisy-chained (thefirmware64x2PlatformIO env). The driver is told the module and the chain, and everything else sees the canvas; the stock build is unchanged. From Simone Majocchi (#446).patternflow-a1b2(its alias), WPA2, passwordpatternflowuntil changed, and the whole console is athttp://192.168.4.1/on it - patterns, knobs, the Wi-Fi page to add the next place's network, updates. Modes on/wifi:auto(default: up fifteen seconds after the last link, down once a network is joined and nobody is on it),always,off;GET/POST /api/hotspot, and ahotspotobject in status. The NETWORK screen shows the name while the hotspot is what there is. What the bench decided (src/core_hotspot.hsays why, line by line): the channel comes from a scan - the least loaded of 1/6/11 - never a fixed one; alone, the radio runs AP-only, and the station comes back only for a rare probe with nobody connected or when credentials arrive; a DNS responder on the hotspot resolves every name to the panel and the phone's internet probe fails within a second instead of timing out for twenty; the hotspot is 20 MHz. Pretending to be the internet was tried and put a Samsung into its limited-connectivity state, which drops the network.WL_CONNECTEDbefore registering - on the hotspot a phone got an address and port 80 never opened.PatternflowWifi::linkUp()is the condition now, the station or the hotspot, and the hotspot raises the same link edge the station does.PF_WIFI_TX_POWERis the radio's 19.5 dBm again). Measured on the hotspot: a phone next to the panel took 4-9 s per 10 KB page at 13 dBm and under a second at full power - the phone's own transmitter had hidden the asymmetry, and a router's antenna had hidden it on the home network. Owner's decision./patterns?v=2a535f30), and a page asked for under the running build is cached for good - a second visit to a tab costs no request at all (measured on a panel:/patterns15.5 KB in 335 ms the first time, nothing and 16 ms after). A new build is a new address, so an update is never answered from an old copy; an open console notices the newbuildin/api/statusand moves to it by itself, unless you are in the middle of typing or an upload, when it offers a reload instead. Pages withoutvrevalidate every time (ETag+304), an oldvgets a302to the current one, and the shared header script is addressed by its own checksum (/pf-console.js?h=…, stamped in byconsole_pages.py). Only names that can only be the panel - an IP, a bare name,.local- are ever given a cache lifetime: the hotspot answers every DNS name, and a phone must not keep the console as someone else's site. On a home network the other tabs are fetched in the background once the page is idle, so even a first visit is instant; not on the hotspot, where the link is the scarce thing./api/statusgainsbuild,viaHotspotandbusy, and escapes the network's and pattern's names (an SSID with a quote in it made the reply invalid JSON).window.PF: polls wait for the previous reply, back off on a slow link, pause in a hidden tab and stop the moment you tap another tab, so the next page is first in the one-connection server's queue; the page's own requests go before the header's status request; uploads hold everything else back and ask before you leave. A small fallback is stamped into every page, so a page still works if the header script fails to arrive.console_serve.py --slowmakes the desk as slow as the hotspot (round trips, a 5.7 KB TCP window, one request at a time) to design against./wifiputs Add a network first when you are on the hotspot or nothing is saved, lists the networks the panel's channel scan saw so you can tap one instead of typing it, stops a case-only typo with an inline "did you mean" before it is saved, and reports what became of the network: joined, with the address to open, or why not - wrong password, not found, refused, no answer - on the page and on the panel's LEDs. On the hotspot with no station link, a network sent from the page is tried at once. The console's pages were gone over for phones on the way: sliders that let the page scroll, inputs that do not zoom iOS,accept="*/*"for the Android file picker, the hotspot shown as what it is instead of "offline", a status page that copies its diagnostics, an update page that confirms the new version after the reboot.alwaysno longer keeps the panel off its network after a boot. Raising the hotspot starts with a channel scan, and the scan cuts the station's first connection attempt short; with the hotspot up the station is retried only every five minutes, so a panel set toalwayswith good credentials sat off its network for five minutes after every boot (the console looked dead from the LAN). The hotspot now gives the station one attempt the moment it is up - on the bench it joined 1.4 s later - and goes AP-only only if that fails. The console also stopped calling a slow link dead: one reply over the timeout (now 12 s at least, not 5) no longer turns the header's state to offline, two failures in a row do - with the hotspot and the station both up, pages took 4-5 s and the console read "offline" after the first one.Web
/guideis where you pick one by where your Patternflow is, each with its chapters, in order. Build, at /guide/build, is for soldering one from bare parts: 01 Gather (the parts list isbom_v3.9.csvitself), 02 Order & print, 03 Solder, 04 Into the case, 05 Wire & power, 06 Firmware (which hands over to Play's 01 Flash) and 07 Check & close, each step played on the real v3.9 board and case in 3D, in the order the build happens; the board's known issues stay inBUILD_GUIDE.md§10, linked. Play, at /guide/play, is the first hour with a built one, 01 Flash, 02 Knobs, 03 Patterns, 04 Console, on the real v3.9 hardware in 3D, running a port of the firmware's input logic and screens, with the real flasher dialogs and a live copy of the device console. Make, at /guide/make, is 01 Community and 02 Pattern Lab, done by hand: on a big enough screen your own Pattern Lab, a practice community (placeholder patterns, connected to nothing) and a practice AI with set answers sit beside the steps, and a pointer shows each move and waits for yours; phones get the real screens. Sound, MIDI & OSC, MQTT, Clock, Performance and Editions join Make as they are written. Each guide numbers its own chapters from 01. Links into the first version, when Play was/guideitself (/guide#flash,/guide#knobs-3), land on the same step under/guide/play. English and Korean. Every step has a "Stuck here?" link to a GitHub issue that already says which guide, chapter and step (guide_stuck.yml).0025addshidden_atto patterns and decks;check:hidemoddrives it all through the real routes.useSubmitLatch), and a published pattern keeps its button down until the page changes; a thread whose files failed to attach offers Open the thread instead of posting it again. Behind them the server answers an identical submission from the same account within a minute with the post it already made, looked up and inserted in one synchronous transaction so two requests arriving together cannot both get through.check:publishfires two identical publishes into the route at the same instant; a version that awaits between the lookup and the insert fails it.🤖 Generated with Claude Code