Skip to content

Migrating

The log of breaking changes, what changed between versions, and the action to take.

The firmware ships no migration code, with two temporary exceptions, each because a device that loses its config cannot be reached to fix anything else: it moves 6.0's network settings onto the Ethernet and WiFi cards at the first boot (see the WiFi entry), and it converts config files from before they were state documents (see that entry). Its persistence layer is robust by default: an absent key keeps the control's default, a stale value clamps to the new bounds, and an unknown key is ignored. That absorbs almost all schema drift, and the rare change a robust reader cannot absorb is documented here instead of migrated.

The File Manager's Backup (⤓) / Restore (⟲) carries config across these breaks. src/ui/migrate.js is the authoritative, dated log of every machine-mappable break: renames, controls moved onto a child module, and older preset files converted to documents. Restore applies it in the browser and reports what did not carry over, as Backup and restore shows. The entries below therefore describe only what a map cannot express: behavior changes, semantics to re-check, and erase-flash moves. It works even on a freshly erased device: join its MM-XXXX access point, whose UI opens by itself as a sign-in screen or at http://4.3.2.1, restore there, and take the offered restart. The bundle carries the WiFi credentials, so the device comes back on your network. For a device still on old firmware (no Backup button yet), the installer page offers the same backup as a bookmarklet.

Read this when upgrading a device that already holds persisted state. Entries are newest first. Each says what changed and what to do; most need nothing at all, because the lost value re-populates on next use.

Action legend, how much work an entry costs you:

Action Meaning
nothing Self-heals. The value re-populates on next use, or the default is correct.
re-set a control One value resets to its default; set it again in the UI if you had changed it.
re-add a module The module vanishes from the tree on boot; add it again and re-enter its controls.
update a file An on-device file must be edited or replaced.
install over USB The update cannot come over the air: one install from the installer page with a cable, after which updates over the air work again.
erase flash A full flash erase is required (the heaviest, a full reconfigure follows).

v7.1.0

A desktop updates itself as a board does, and the Debian package installs a service

Action: nothing on most desktops; update a file on a Linux install from the .deb with a systemd unit written from the install guide.

A desktop build names its firmware desktop-<os>-<arch>, such as desktop-linux-arm64, where it said unknown, and installs firmware-<firmware>-v<version>.bin from a release through the Firmware card, the Devices map and /api/firmware/url, as a board does. /api/reboot restarts a desktop where it ended the process: in place, in its terminal window, under systemd or in a container alike. A script that stopped a desktop through /api/reboot stops it with Ctrl+C or its service manager instead.

The package now installs and starts moonlight.service, run as its own moonlight user from /var/lib/moonlight/bin, a folder that user owns, so the Devices map updates it like a board. The program moves from /usr/bin/MoonLight to /usr/lib/moonlight/MoonLight, the copy the service starts from, and the menu entry opens http://localhost:8080/.

A hand-written /etc/systemd/system/moonlight.service, or a drop-in in /etc/systemd/system/moonlight.service.d/ that sets ExecStart, overrides the packaged one and names the old path, so remove it before upgrading:

sudo systemctl disable --now moonlight
sudo rm /etc/systemd/system/moonlight.service
sudo rm -r /etc/systemd/system/moonlight.service.d   # where a drop-in sets ExecStart

Settings now live in /var/lib/moonlight/MoonLight/. Copy the old user's across once, pi here, after the upgrade:

sudo systemctl stop moonlight
sudo cp -a /home/pi/.local/share/MoonLight/. /var/lib/moonlight/MoonLight/
sudo chown -R moonlight:moonlight /var/lib/moonlight/MoonLight
sudo systemctl start moonlight

A Backup taken before the upgrade and restored after it carries them across too.

The P4's radio chip status moves from System to the Firmware card

Action: nothing, unless a script reads it; read version on the Firmware module's WiFi coproc tab instead of wifiCoproc on System.

On an ESP32-P4 with WiFi, the read-only line about the separate radio chip is now the WiFi coproc tab of the Firmware card, which also updates the chip's firmware. It reads link not started while Ethernet carries the device, link down until the chip completes its handshake, then the chip's firmware version, or C6 too old to report when the chip's firmware predates the version query. It is read-only and unsaved.

A page from outside the local network is refused

Action: nothing, unless a page served from the internet drives your devices; serve it from the local network, or call the device from a script, which sends no Origin.

A browser request whose page comes from beyond the local network, by its Origin header, now gets 403 from MoonLight and MoonBase, reads and the WebSocket included. Before, any website open in a browser on the network could install firmware on a device or read its saved WiFi passwords. Pages served by a device, the desktop app, MoonDeck, Home Assistant and scripts are unaffected. A device answers by its address, localhost, a local name, or its own name under any domain, such as mm-acfd.fritz.box; under another name, such as a dynamic DNS name, it answers 403: open it by one of those instead.

Discovery moves from Network to Devices

Action: re-set a control if you had changed Discovery's addressing; otherwise nothing.

The device list is the Discovery card in a top-level Devices container, where it was a card under Network. The device reads Discovery's settings from the Devices file, so addressing starts at its default, multicast and broadcast. The list itself is not saved: it fills within seconds of boot as devices announce themselves, and a saved devices entry is ignored. It has no fixed size: it holds as many devices as memory allows, where it stopped at 32.

A MoonLight or MoonBase board appends its page's port and its base MAC to its presence packet, so a desktop or a Pi on 8080 is linked at its port and a board moving from WiFi to Ethernet stays one device on the map. A board on older firmware ignores the longer packet and stops listing updated boards until it is updated too; WLED keeps listing them.

mDNS is always on

Action: nothing.

The Network card's mDNS switch is gone: a board always answers on <device name>.local while it is on a WiFi network or Ethernet, since its name is its address. On its own access point it does not, and a desktop answers by its computer's own name. A saved mDNS setting is ignored, so a device that had it off is found by name again.

The level spans from where the gate opens to its top

Action: nothing, or re-set a control on an effect whose look you tuned to the level.

The audio level, which volume-driven effects and AudioSpectrum's meter read, now maps from where the gate opens to the level's top, where it mapped from the room's floor: while music played the level never went under the gate, so the lower part of its scale stayed unused, about half of it at the default squelch. Effects driven by the level now swing over the whole scale and look livelier; a soft passage barely over the gate reads near zero. AudioSpectrum's level meter drops the gate's tick, its place now the meter's left edge. The frequency bands keep their window.

The notes run while something reads them, and harmony is gone

Action: none: a saved harmony is ignored.

The Audio card's harmony switch is gone: the notes, the pitch classes, the key and the chord run while something reads them, a note effect, a bar effect showing the keys, AudioSpectrum's pitch classes or note names, an effect script calling a note builtin, or this board sending the MoonLight format. A board showing none of them saves the semitone bank's share of a core, 6 to 15% depending on the chip.

Audio sync sends the whole frame, in MoonLight's own format

Action: re-set a control: syncFormat to WLED on the Audio card of a board whose audio feeds only WLED devices, or to both when it feeds MoonLight boards too.

A sending board now sends the MoonLight format by default: the whole frame, with the third octaves, the drums, the harmony and the keys, which a receiving MoonLight board shows as the sender does. The MoonLight format multicasts to its own group, 239.0.0.2, and WLED's stays on 239.0.0.1. WLED does not read the MoonLight format, so a board feeding WLED devices picks WLED or both; WLED's packet now also carries which drum was hit. A receiving board reads either format and needs no setting.

Keyboard and Strings show the notes a player could play

Action: re-set a control: notes to all on a Keyboard or Strings that should show every note the engine hears.

Keyboard and Strings gain a notes select, hands by default: a weak note an octave, a twelfth or two octaves above a louder one is dropped as its overtone, and one a semitone or two beside a louder one as its leak, Keyboard keeps what two hands of five fingers within a tenth could play, its top half following the left hand and its bottom half the right in place of the octaves and the circles, and Strings plays each note at one string and fret within the fretting hand's reach, one note a string, with a strum flashing across the strings by the bridge and a bow line on a bowed violin or cello string. no overtones drops the overtones and the leaks without the hands, and both levels drop a weak key heard for a single frame, an attack's burst rather than a note; all shows every note as before, unless chord is on. A new chord checkbox, off by default, keeps only the chord being played: on Keyboard under hands for a hand holding three keys or more, a hand playing a line keeping its notes, on Strings under hands as the chord's shape; the note names now show the chord beside the key. Keyboard's auto, the detected key picking the shown octaves, is on by default.

Freq Matrix spreads the pitch over the whole palette

Action: re-set a control: lowHz and highHz on a Freq Matrix whose lowBin or highBin you tuned.

Freq Matrix colors each moment by its dominant pitch, now on a log scale from lowHz to highHz, 50 Hz to 5 kHz by default, so the bass, the voice and the cymbals each take their own part of the palette, where WLED's linear window from 134 Hz to 2 kHz showed mostly its first colors. lowBin, highBin and sensitivity are gone: the level is already scaled to its range, and fx sets the brightness alone. speed is now a rate, 255 scrolling about 60 rows a second at any frame rate, where it scrolled once a frame.

Fireworks burst at a share of the grid's height

Action: re-set a control: burstHeight on a Fireworks effect whose launchSpeed you tuned.

Fireworks fill any grid: the shells burst at burstHeight, a share of the grid's height, where launchSpeed threw them a fixed distance that reached the top of a 16-row grid and a fifth of a 128-row one. gravity now sets only how fast the shells rise and fall, from 1 to 32; a saved value above 32 is clamped.

The audio effects read the drums, and four bar effects default to the third octaves

Action: nothing, or re-set a control to keep a look you tuned.

An audio effect's hit is now a drum's: onset fires on the kick, the snare or a hat from their own detectors, once a drum, where it fired on any rise across the spectrum, and the level-against-its-average beat test is gone. Moving Head rolls its gobo on a hit, Space Invaders marches on the kick, Radial Spectrum flashes the sectors of the range that was hit, and MoonLive's audioBeat() reads the same hit. Paint Brush follows troyhacks's original again: up to 16 strokes, one a band, where it drew 255, scattered by the bass, anti-aliased by default, at his rates and fade, with minLength on the endpoints' span from 0 to 31; a new glide sweeps the strokes smoothly instead, and a new surge stretches them on each drum hit; a saved numLines, oscillatorOffset, fadeRate or minLength above its new range is clamped into it. GEQ, GEQ 3D, Spectrum and Freq Saws gain AudioSpectrum's bands select and show the third octaves by default, the sixteen bands on a frame synced in the WLED format: set bands to 16 for the old bars. GEQ 3D shows all the bars the source has across the full width, where it showed sixteen, and its side faces show brighter through the new sideFill: set numBands to 16 for the old count. The sprite effects driven by sound slow with the music's level, so a quiet moment pauses them, where any sound over the floor kept them at the bands' speed.

ESP32-P4 boards read their flash over four lines

Action: install over USB once, on every ESP32-P4 device (esp32p4rev1-eth, esp32p4rev1-eth-wifi, esp32p4rev3-eth, esp32p4rev3-eth-wifi).

The P4 firmware images read code from flash over four data lines at 80 MHz (QIO) where they used two at 40 MHz (DIO), as the S3 images did in v7.0.0. The flash mode is the bootloader's, and an update over the air replaces only the app, so ESP-IDF refuses the new app and the Firmware card reports ESP_ERR_OTA_SPI_MODE_MISMATCH. Install once over USB from the installer page, which writes the bootloader too; the partition table is unchanged, so with Erase chip first left unticked the config stays. Take a backup first anyway.

Audio levels set themselves: floor, gain and levels give way to squelch

Action: check squelch on the Audio card if a quiet room lights up or a quiet source stays dark; the default of 10 dB suits a MEMS microphone.

The service learns the room's noise floor, gates silence and holds the loudest band at the top of the display, so levels, gain and the per-band learner are gone. floor, the threshold, is now squelch: how many dB above the learned floor sound must rise, in place of a fixed level; a stored floor is not carried over.

AudioSpectrum's levelBar is readouts

Action: nothing; a restored backup or preset carries the setting over.

The switch that drew the level bar now draws every readout beside the bars: the dominant frequency, the gate, the bass, mids and treble, and the twelve pitch classes. A stored levelBar comes back as readouts, and the new bands starts on the 31 third octaves.

The audio sample rate is 24 kHz by default, and stored by name

Action: check the Audio card's sampleRate on a board that analyzes its own microphone, and pick 24000 unless the board needs the bands above 10 kHz.

The audio measures third-octave bands, and 24 kHz carries the 28 of them up to 10 kHz, the 16 WLED bars among them, at half the cost of 48 kHz. The list gained 24000 between 22050 and 44100, and the setting is now stored by its name; one stored as a position in the old list comes back one rate lower, 44100 as 24000 and 48000 as 44100, and a board that never stored one moves from 48000 to 24000.

E1.31 numbers its universes from 1 when universe_start is 0

Action: nothing for most setups. Where a MoonLight Network Send feeds a MoonLight Network Receive over E1.31 and only one end runs this version, update the other too. Where an E1.31 sender or receiver was set one universe off to make up for MoonLight's numbering, set it back.

E1.31 has no universe 0, and standard senders and receivers such as xLights and FPP start at 1. With universe_start 0, Network Send sent E1.31 universe 0, which those receivers drop, and Network Receive placed an E1.31 sender's universe 1 one universe in, shifting the image. Both now start E1.31 at universe 1 when universe_start is 0; a start of 1 or more maps as it is, and Art-Net keeps its universe 0.

The Adafruit MatrixPortal S3 has its own firmware, esp32s3-n8r2

Action: nothing; to use the board's whole 8 MB, install over USB once with Erase chip first ticked.

A MatrixPortal ran the esp32s3-zero image, which keeps working on it but uses 4 MB of its 8. The esp32s3-n8r2 image has a 3 MB app slot and 4 MB of filesystem. Its partition table differs from the Zero's, which an update over the air never rewrites, so moving takes one install over USB, and the device comes back unprovisioned: take a backup first and restore it after.

v7.0.0

Network Send fills each universe with channels_per_universe channels exactly

Action: on a Network Send driving fixtures whose channels do not divide 510 over Art-Net or E1.31, such as RGBW strips (4) or a 13-channel moving head, set channels_per_universe to a multiple of the fixture's channels, 508 or 507, where the receiver needs each fixture inside one universe. RGB, RGBWW and 6-channel fixtures need nothing.

A universe carries exactly channels_per_universe channels, 510 by default, as Network Receive and xLights read them, so a fixture can straddle two universes. It was cut down to whole fixtures, 508 channels for RGBW, which a receiver reading 510 placed one channel off from the second universe on. RGB at 510 is unchanged.

A driver's count of 0 reads to the last light, and the window counts past 65535

Action: re-set a control on a display of more than 65535 lights. Where a driver's count is 65535, the old default reading to the last light, set it to 0. A 65535 set as a window's length stays.

A driver's start and count are 32-bit, so a window starts or ends past light 65535, which two panel cards splitting a large wall need. A count of 0 reads every light from start to the last, and 0 is the default; 65535 was the default and meant the same. A saved 65535 is now a plain count, which on a display of more than 65535 lights leaves the rest dark until count is set to 0.

A module whose type was renamed is dropped at boot, and File Manager says so

Action: after an update over the air, when File Manager shows skipped at boot, take a Backup and Restore it before changing anything under the module named.

The board migrates nothing, so a module whose type a release renamed, such as OscModule becoming OscService, is skipped when its config file loads, and File Manager names the first one: Services.Osc skipped at boot: unknown type. The same line names a type this firmware does not carry at all, such as an SD card service on a board built without one, which no restore brings back. The file still holds the module until that part of the tree is next saved, so a Backup taken first carries it, and Restore migrates it in the browser.

The StadBeest autopilot builds its looks, a layer each

Action: nothing, unless a desk or a preset switched the looks on switch3 to switch5: use encoder6 to encoder8.

stadbeest-autopilot.mls pushes every part its look at start, a layer per look. It crossfades between them through encoder6 to encoder8, which set the three layers' opacity, where switch3 to switch5 turned three effects in one layer on and off, and it clears those three switch targets. The eyes and the mouth are reached by name, MM-eye1.local, MM-eye2.local, MM-mouth.local and MM-mouth2.local; a part named otherwise is renamed on its System card, or the names are edited in the script. The StadBeest has the table.

LCD-MM's ring is smaller, and refuses PSRAM

Action: nothing, unless ringSnapshot was off on a board with PSRAM: turn it on.

ringAuto now sizes the ring by the lead the wire needs, 3-row buffers, where it took every byte of internal RAM above 64 KB. On a 3,840-light S3 that is 21 small buffers instead of 28 large ones, a ring of 37 KB where it took about 112 KB. Turn ringAuto off to set ringRows and ringBufs by hand as before. The ring refuses to run when its refill would read PSRAM, since a flash save can cut PSRAM off in the middle of a refill and crash the board. With ringSnapshot off it reads the live frame, which a board with PSRAM keeps there, so the card shows ring refused: … turn ringSnapshot on.

ESP32-S3 boards read their flash over four lines

Action: install over USB once, on an esp32s3-n16r8, esp32s3-n8r8 or esp32s3-zero device; the others need nothing.

These three firmware images read code from flash over four data lines (QIO) where they used two (DIO), which makes an S3's tick about 16% faster. The flash mode is the bootloader's, and an update over the air replaces only the app, so ESP-IDF refuses an app whose mode differs from the running one. The Firmware card then reports ESP_ERR_OTA_SPI_MODE_MISMATCH. Install this release once over USB from the installer page, which writes the bootloader too. The partition table is unchanged, so with Erase chip first left unticked the config stays; take a backup first anyway.

Rings 241 places a few lights one unit over

Action: nothing.

Every ring is now drawn by one ring geometry, the Ring layout's. So the Rings 241 disc places a few lights one unit from where they were, 7 of 241 on the default disc. Its count and wiring order are unchanged.

A /ws client subscribes to the state it reads

Action: nothing for the web interface and the WLED app; a script reading /ws sends one message.

The control socket sends no module state until the client sends {"subscribe":["<module>", …]}, naming modules by name or type; {"subscribe":["*"]} receives the whole tree as before. A module outside the subscription arrives as an outline, without its controls, and its values are not patched. The WLED {state,info} frame goes only to a client that has not subscribed. {"resync":n} asks for the subscribed view again, and the answering document ends with "resync":n. /api/state keeps its shape, with three read-only gauges changed: a Progress control carries "unit":"B" or "KiB" where it carried "bytes":true or false, the File Manager's filesystem gauge is flash, and SdCard's card (KiB) is card.

MoonLive scripts: colors read as one RGB, and struct, arithmetic and builtin names are reserved

Action: update a file, only for a user script that uses one of these names or calls.

getR, getG and getB are gone: getRGB(i) answers the whole color, and getRGB(i).r one channel. paletteR, paletteG and paletteB are gone the same way: paletteRGB(i, bri), and paletteRGB(i, bri).r for one channel. The shipped effects use the new calls.

RGB and Coord3D are types, and abs, min, max, clamp, getRGB and paletteRGB are builtins, so a script variable, member or function of one of those names stops compiling, with the reason as its error. A script function named after any builtin is refused too: the builtin was found first, so the function never ran. Rename it.

The access point carries the first known network's password, under an anonymous name

Action: nothing, unless someone joins the access point with a password of its own: they use the home WiFi's password instead.

The Access Point card's password is gone, and a saved one is ignored. The access point carries the first known WiFi network's password, so only someone who knows the home WiFi joins it. It broadcasts as MM- and four MAC digits rather than the device name, shown as name on its card. It is open only on a first setup or with an open home network. A client on the access point never receives a stored password, and the config files are not served to it, so make backups from the home network. While a browser is open through the access point, the password fields of browsers at home are empty too, since one live update serves them all. MoonBase follows the same rule.

never is first setup only, and switching on and off opens the access point

Action: nothing.

The Access Point card's never (not recommended) is named first setup only, which is what it did: the access point opens until a network or Ethernet is configured, and stays closed after. Switching a device on and off four times, each within a few seconds, opens its access point for that boot without a password, in safe mode, and every setting stays. factory reset on the System card deletes every setting and preset.

Every factory palette has a fixed number

Action: nothing.

The palette picker lists every factory palette at its place in the catalog, held or not, and your own scripted palettes after them. A saved selection is kept by name. A knob or a slot that stepped to a scripted palette by number may land on another one. stadbeest-fire is 65 and temperature 64, since the StadBeest palette sits in its project folder. One the device does not hold keeps the last palette until the UI downloads it.

stadbeest-legs is stadbeest-walk, stadbeest-eyes is stadbeest-frog-eye

Action: pick the new name on a StadBeest effect's script, and move a slot target that names the old module.

The StadBeest's three looks each pair a legs effect with an eye effect, so the first pair is named after what it does.

Safe mode after two failed boots in a row

Action: nothing.

Two failed boots in a row, crashes, brownouts or power losses within a minute of starting, start the next boot in safe mode. Every boot raises the lights to their brightness over five seconds. The lights stay dark, no MoonLive script runs, and a layout places its first 1,024 lights; from four failed boots the output drivers stay off as well. A restart from the UI starts normally.

Config files are state documents

Action: nothing.

Each /.config/<TypeName>.json holds its module's state document, the shape a preset and PATCH /api/state carry, with modules keyed by name, in place of the flat keys with numbered children. The first boot after the update reads each older file as the document it describes and saves it as one, naming each module as the device names it; the log says which files it converted. That conversion is temporary and leaves in the release after next, so a device skipping that release starts from its defaults: back it up first and restore the backup, which converts an older backup's files the same way. A MoonBase from this release reads both formats until then. An older MoonBase still joins the known WiFi networks and finds the Ethernet wiring, but misses the Ethernet card's static address, so on an Ethernet board with a static address, install the matching MoonBase from the Firmware card.

A preset is applied through its list row

Action: a script or integration that applies a preset calls the row's action instead.

POST /api/list/Control/presets/<name>/apply applies a preset, <name> being its name or its row id (the REST API). It takes the place of the Control module's preset control and of the row's activate and apply fields.

Adding a module under a name in use is refused

Action: a client that re-sends an add treats 409 as "already there", or applies a state document instead.

POST /api/modules with an id some module already holds answers 409, where it answered 200 with "note":"already exists". A state document applied twice changes nothing, so a client that wants an idempotent setup sends one through PATCH /api/state (Setting everything at once).

The MIDI service's usb switch is its source

Action: nothing when restoring a backup, which maps it; otherwise, on a device with a desk on its USB port, set the MIDI service's source to USB.

source names where a desk is plugged in: the computer showing the interface (browser), the device's own USB port (USB), or the network (network).

A desk on its own device is a MIDI bridge over RTP-MIDI

Action: re-set a control, on a MIDI-OSC bridge device: remove its OSC service and turn its MIDI service's share on. On the device the desk drives, set the MIDI service's source to network and host to the bridge's name or address.

The bridge now carries the desk over RTP-MIDI, so the device it drives treats the desk as its own, and the OSC follow setting is gone. A device that listened to another with follow on needs nothing: a leader sends its preset pad states on /mm/padstate/N, an address apart from the press. An OSC app that lit its pads from /mm/pad/N listens on /mm/padstate/N instead.

The P4 and S31 Ethernet presets are named by their wiring

Action: re-set a control, only where a script, a document or an API call sets ethBoard to P4-NANO or S31 CoreBoard.

A preset is the wiring several boards follow, so P4-NANO is now P4 RMII and S31 CoreBoard is now S31 RGMII, beside Classic RMII. A saved device opens on its chip's one preset as before: an unknown label leaves the selection at its default, which is that preset. An old name sent to ethBoard matches no preset and changes nothing.

An audio codec is the device model's setting

Action: on an ESP32-S31 CoreBoard, apply its device model again, or set the I2C bus to SDA 51 / SCL 50 under System and AudioService's codec to ES8311 with mclkPin 52.

The codec in front of a microphone is now AudioService's runtime configuration, where the S31 firmware used to assume it. One P4 firmware so serves every P4 board, with or without a codec. A board configured before this change has no codec set, and its microphone stays silent until it does.

The I2C scan is the board's I2C bus

Action: nothing for a restored backup, which carries the pins over; on a running device, set sda and scl again under System, I2cBus.

The scan module became the owner of the board's I2C bus, which every device on it shares. A codec now names only its address, and the wires are stated once. A device's saved scan pins sit under the old module type and are not read at boot.

The access point always broadcasts its name and follows the radio's channel

Action: nothing.

The access point always broadcasts its name, and it takes channel 1 when it runs alone; beside a joined network it shares that network's channel, as one radio must. A hidden name was no protection, since any scanner shows it, and made first setup harder. A saved channel or hidden is ignored.

A driver's fixture profile is saved by name

Action: re-set a control, only where a script, a document or an API call sets fixture by number.

The built-in profiles gained the three missing color orders and three 16-bit rows, which moved RGBW and every row after it down the list. A driver now saves fixture as the profile's name, and a device's config from before the update boots on the profile it had. A number sent as fixture now picks a different row; send the profile's name instead, such as "fixture": "GRBW".

Wave's type is waveform

Action: re-set a control.

A control named type collided with a module's own type. A config file wrote both under one key, and a state document or a preset read the shape as the type. The Wave effect's shape is waveform, at its default, Sine, after the update; pick it again. A restore cannot carry the old shape, since the file held it under the type's key.

A preset is a state document

Action: update a file, which a backup and restore does for you.

A preset file holds the document PATCH /api/state applies, so a preset can hold any part of the state, such as a palette alone. A preset saved before this release is listed on its pad but not applied, and the card says so. Back up the device and restore the backup: Restore rewrites each older preset as a document.

A MoonBase from before this release misses the Ethernet wiring and the power cap

Action: nothing on WiFi; on an Ethernet-only or power-capped board, install the matching MoonBase.

MoonBase reads the app's saved network settings, and the Ethernet wiring and the WiFi power cap moved under the new Ethernet and WiFi cards. A MoonBase from before this release still joins the first known WiFi network, but finds neither of those. On an Ethernet-only board it then opens an open access point named MoonBase. An update started from a browser on the cable loses the device until you join that network. On a board the device catalog caps, it runs the radio at full power, which can brown out a weak supply. Before the next firmware update, install the matching MoonBase from the Firmware card's MoonBase tab, which writes it over the network.

WiFi has its own card under Network, with a list of known networks

Action: nothing; update to this release before a later one.

The WiFi settings moved from the Network card to a new WiFi card below it: the network becomes the first row of known, with its IP settings, and txPowerSetting moved with it. A backup restored through the File Manager carries the network and the power cap across. Updated in place, the device moves them itself at its first boot and saves the new layout. That step is temporary, and a later release drops it. A device updated straight from 6.0 to that release knows no network, so it opens its access point, where the network is entered again. A MoonBase from before this release reads part of the new layout: see the MoonBase entry above.

Ethernet has its own card under Network

Action: nothing; update to this release before a later one.

The Ethernet settings moved from the Network card to a new Ethernet card below it: ethBoard, ethType, ethPhyAddr and the pins. The wired interface has its own IP settings: ipSettings (DHCP or Static) with ip, gateway, subnet and dns. Each known WiFi network has its own as well. A backup restored through the File Manager carries the Ethernet settings across, and gives the wired interface the IP settings the device had. Updated in place, the device moves the Ethernet settings and Network's static address onto the Ethernet card at its first boot, the same temporary step as WiFi's. A device model from the web installer sets Ethernet on the new card.

Light presets are fixture profiles

Action: re-set a control.

The library of named channel wirings (RGB, GRB, RGBW, a moving head's channels) takes the DMX industry term, a name apart from the Control card's presets. The module LightPresets is FixtureProfiles, its list presets is profiles, each driver's lightPreset control is fixture, and its presetRef is fixtureRef. A backup restored through the File Manager carries all of it. Updated in place without a restore, the saved LightPresetsModule entry names a type this firmware does not have, so it is skipped. The built-in profiles are back and your custom rows are gone. Each driver's saved choice is ignored too, so it falls back to its default, GRB for strips and RGB for network sinks. Re-add the custom profiles and re-pick each driver's fixture. An API client or an automation that sets lightPreset sets fixture.

The palette is saved and reported by name

Action: nothing.

Drivers.palette is saved by its name, such as Ocean or fire.mlp, so adding a palette script leaves a scripted choice in place after a reboot. A saved number still loads, and a write takes a number or a name. A client reading the value through the API gets the name where it got a number.

Network services share addressing and hosts

Action: re-set a control.

Every service that sends to many receivers names the choice the same way: addressing (unicast, multicast, broadcast, as the protocol allows) and hosts, a list of addresses and names such as panel-01.local. The Network Send driver's ips and lightsPerIp become hosts and lightsPerHost; OSC's feedbackTo becomes hosts; Devices' wledCompatible becomes addressing (on is multicast + broadcast). A backup restored through the File Manager carries all of these over; on a device updated in place, enter the hosts and the addressing again. The E1.31 multicast protocol option is gone: such an output comes back as E1.31, so set its addressing to multicast. Presence packets now mark which copy they are. A device on an older firmware therefore lists a newer one as MoonLight and as WLED in turn, so update every board in one go. Devices' addressing defaults to multicast + broadcast, so WLED devices list MoonLight devices without a setting.

Inputs target the control surface only

Action: re-set a control.

A button, analog, infrared or gamepad row names a surface control (Control.switch1, encoder3, fader2, pad5); the Control card decides what that control drives. A saved row that named another control directly, such as Drivers.on, comes back unassigned, and the service's status says how many. Point the row at a surface control and give that control the target the row had. OSC's /mm/control/<Module>/<control> address is gone for the same reason; send to /mm/fader/N, /mm/encoder/N or /mm/switch/N.

Pong keeps its own clock

Action: nothing.

Pong's audioReactive is gone, since a ball that moved only on the beat read as a stalled game. The rally always runs on its clock, and a saved value is ignored.

A v5 device updates through v6.0.0

Action: nothing on a device already on v6.0.0. A device on v5 installs v6.0.0 first, then this release.

The firmware image now carries the name MoonLight where it carried projectMM, and a device checks that name before it installs anything. v5 knows only the old name, so it refuses this release with "that image is MoonLight, not this project"; v6.0.0 knows both, which is what it shipped for. Update a v5 device to v6.0.0 from the Firmware card first, then to this release, or install this release over USB from the installer page.

The recovery image carries the new name too

Action: nothing, unless a device came from v5 over the air: install MoonBase images only from the Firmware card or the installer page, never from MoonBase's own page.

MoonBase, the recovery image on 4 MB devices, is now MoonLight-moonbase where it was projectMM-moonbase. An update over the air replaces only the app. A device that went from v5 to v6 that way still carries the v5 MoonBase, which knows only the old name. Its own install page refuses a recovery image by that old name, but it takes the new one for an app and writes it over the app. After that, only a USB cable brings the device back. The Firmware card's MoonBase update and the installer page both recognize the new name. Either one also replaces the old MoonBase, which closes this for good on that device.

v6.0.0

The desktop settings folder and the installer's device list carry the new name

Action: update a file, on a desktop install: copy the settings folder across. Bookmarks on the installer page are added again.

A desktop install keeps its config, presets and scripts in a folder named after the product, and that folder is now MoonLight where it was projectMM:

  • Windows: %LOCALAPPDATA%\MoonLight
  • macOS: ~/Library/Application Support/MoonLight
  • Linux: $XDG_DATA_HOME/MoonLight or ~/.local/share/MoonLight
  • the container: /data/MoonLight

The old folder is left alone rather than read, so the app starts on its defaults. The app itself installs beside an existing projectMM on every desktop platform, and the two compete for port 8080, so uninstall projectMM first. Copy the contents across to keep what you had. A Backup taken in the old install and restored into the new one carries it across too.

The installer page's saved device list moves the same way, from projectMM.devices.v1 to MoonLight.devices.v1 in the browser's local storage. The page opens with no remembered devices, and each one is added again by address.

The Debian package, the container image and the macOS bundle carry the new name

Action: update a file, on a Linux install from the .deb or a container deployment.

The Debian package is now moonlight where it was projectmm, so apt treats it as a different package and installs it beside the old one. Remove the old one first with sudo apt remove projectmm, then install moonlight_X.Y.Z_<arch>.deb. A systemd unit written from the install guide names the old binary: point its ExecStart at /usr/bin/MoonLight.

The container image is ghcr.io/moonmodules/moonlight, and the compose file's service, container and volume are named moonlight and moonlight-data. A deployment pulling the old image name stays on v5.0.0, so change the image name. Then copy /data/projectMM to /data/MoonLight inside the existing volume, or Restore a Backup into a new one.

The macOS bundle identifier is org.moonmodules.moonlight, which macOS reads as a new app. A permission granted to the old one, such as the microphone, is asked for again.

MQTT topics and the Home Assistant entity carry the product's new name

Action: update an automation, on a device you drive over MQTT or through Home Assistant.

The topic root is now MoonLight/<last6-of-MAC> where it was projectMM/<last6-of-MAC>. The Home Assistant discovery object, its unique id and the client id follow the same root. A broker subscription or an automation written against the old root stops matching, and Home Assistant keeps the old retained config, so the previous entity goes unavailable while a new one appears alongside it.

Delete the stale entity in Home Assistant and repoint any automation or dashboard at the new one, then rewrite subscriptions and publishes to the new root. Nothing on the device needs changing: the root is derived from a single constant, so every topic moves together.

Audio arrives on every device, and its modes are reordered

Action: re-set a control, on a device whose Audio you had configured. Restore maps the value for you, so this asks something only of a device upgraded in place.

Audio is now wired at boot rather than added by hand, because the default effect reacts to sound. A device without the module showed none of that: the lights moved without answering whether anything was heard. It defaults to simulate, a synthesized signal, so a device demonstrates the behavior before a microphone is wired to it. A board that has a microphone selects local audio in its catalog entry, the way it already names its pins.

The mode options are reordered to run simple to advanced: simulate, receive network, local audio, where the order was local, receive, simulate. The default is now the first entry rather than an index that depended on whether the platform had a network. The selection is persisted as that index, so every saved value moves: what read 0 for local audio now reads 2, and what read 2 for simulate now reads 0. Restore carries both. A device with no network has two options rather than three, and its old 1 is ambiguous, so re-pick that one by hand.

v5.0.0

The last release under the projectMM name. Its release notes summarize what upgrading to it asks of you, and Restore carries its renames.