fix: white-mix drift, WiZ speed band, optional CLI [ip], transport hardening#22
Merged
Conversation
- SPEED_MIN/SPEED_MAX were 1-100; the WiZ firmware band (per the official app and pywizlight) is 10-200 with 100 = normal. parsePilot was clamping a reported speed of e.g. 150 down to 100, so the UI misread it and a re-send actually slowed the animation - and the CLI's own documented example (--speed 120) failed its validator. - Add whiteMixedToRgb, the exact inverse of rgbToWhiteMixed: a pilot whose white channels carry an equal c/w split (the signature of our own mix) reconstructs the colour that was set. Folding the *perceived* colour back into state and re-sending it washed every colour toward pure white - the drift color.js explicitly warns about. - query() now ignores datagrams that didn't come from the queried host; the ephemeral port is otherwise open to any sender on the LAN. - A debounced WizLight.send armed before a direct sendNow is dropped when its window elapses, so a stale payload can't land after a forced send. - stateMatchesPreset never matches while the light is off or a scene runs. - discover() reports mac as '' (never undefined), matching the Swift side. - Regenerate the committed JSCore bundle from the changed engine.
The help has always promised "<ip> is optional everywhere - it falls back
to the last-used bulb", but every command with another positional (color,
temp, brightness, preset, scene, save) parsed its first argument as the IP
and errored: `wiz color ff0000` -> "Not a valid IPv4 address: ff0000".
- resolveTarget treats the first positional as the IP only when it parses
as one; a string merely shaped like an IP (10.0.0.999) still fails
loudly, and fixed-shape commands reject leftover arguments.
- Multi-word preset/save names ("Full White", "Living Room") now work
unquoted, like scene names already did.
- --speed help matches the corrected 10-200 band (the example works now).
- Preset listing is sorted by name, the same stable order the macOS app
shows, instead of raw file order.
- --timeout/--attempts floor before the bounds check so 0.5 is rejected
instead of becoming a 0 ms listen window.
- wiz discover skips the MAC segment instead of printing "undefined";
status/color compose the swatch without a double space when colour is
off; the wiz status swatch prefers the exact white-mix inverse over the
perceived approximation.
- README: document the leading-[ip] order (it previously showed a trailing
[ip] form that never worked) and the scenes/scene commands.
- foldedState inverts the bulb's equal c/w split exactly (the engine's new
whiteMixedToRgb) instead of folding the display-only perceived colour
into state.rgb. Previously: pick a pastel with "Brighter colours" on,
wait one health poll, touch any control - the light went pure white.
An uneven (foreign) split still falls back to the perceived fold, which
is now a stable fixed point rather than a feedback loop.
- WizClient mirrors the engine's send generation: a newer sendNow abandons
an in-flight retry loop, a debounced payload superseded by a direct send
is dropped, and sendPowerOffNow cancels pending sends synchronously - so
a colour edit from just before the lid closed can't fire after the off
and relight the bulb. apply(state:params:) drops its unused parameter
and becomes send(_:), matching the engine naming.
- query() accepts replies only from the queried host (mirrors the engine).
- Scene-speed sliders take their band from the engine (10-200, 100 =
normal) instead of a hard-coded 1-100.
- removeSavedLight clears last_ip via the new Stores.clearLastIp -
saveLastIp("") guards empty input and silently did nothing - and the
startup fallback light is picked deterministically by name (Dictionary
order is randomized per process).
- Stores documents the App Sandbox container location: the released app
and the CLI share the on-disk format but not the same files.
- Misc hardening: the stray-window guard no longer closes NSPanels (color
panels, alerts); the colour-wheel CGContext lives inside
withUnsafeMutableBytes (the &pixels pointer was only valid for a single
call); discovery skips point-to-point/VPN interfaces (IFF_BROADCAST);
discovery-sheet rows are identified by value so MAC-less replies can't
collide; dropped replies without a MAC render consistently.
Bump the root, core, and cli package versions and the app's CFBundleShortVersionString to 5.2.0 in lockstep (CFBundleVersion 19 -> 20), so the release workflow publishes on merge to main. Ships the white-mix read-back fix, the corrected 10-200 scene-speed band, the truly optional CLI [ip], and the transport/power-off hardening.
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.
Fixes from a full code review, verified end-to-end. Three scoped commits: core, cli, macos.
The four headline bugs
state.rgb(the send path) — pick a pastel with white-mix on, wait one health poll, touch any control, and the light went pure white. The engine now exposeswhiteMixedToRgb, the exact inverse ofrgbToWhiteMixed(our split always drives c/w equally), and the app/CLI invert first, falling back to the perceived approximation only for an uneven foreign split — now a stable fixed point. Verified: the previously drifting round trip is byte-identical.parsePilotclamped a reported 150 to 100 (UI misread it; a re-send slowed the animation), and the CLI help's own example--speed 120failed its own validator. Engine constants fixed; macOS sliders now read the band from the engine; help/docs updated.[ip]never worked for any CLI command with another positional —wiz color ff0000errored with "Not a valid IPv4 address: ff0000". The first positional is now the IP only when it parses as one; an IP-shaped typo (10.0.0.999) still fails loudly; leftover args are rejected. Bonus: multi-word preset/save names work unquoted, and the CLI README documented a third (trailing-ip) order that never worked — now matches--help.WizClientnow mirrors the engine's send generation (newer sends abandon in-flight retries; superseded debounced payloads are dropped) andsendPowerOffNowcancels pending sends synchronously first. Same supersede semantics added to the JSWizLight.Hardening + smaller fixes
stateMatchesPresetnever matches an off light or during a scene.removeSavedLightactually forgetslast_ip(newclearLastIp;saveLastIp("")guards empty and silently no-opped) and the startup fallback light is deterministic (Dictionary order is randomized per process).macis""(never undefined) in both implementations; point-to-point/VPN interfaces excluded from broadcast targets; discovery-sheet rows can't collide on a blank MAC.CGContextpointer UB fixed viawithUnsafeMutableBytes;--timeout 0.5rejected instead of becoming a 0 ms window; swatch spacing/doc fixes; preset listings sorted identically in both front-ends.Stores.swiftnow states the App Sandbox reality — the released app and the CLI share the on-disk format but not the same files (the app's copy lives in its container).Verification
swift build+ 17 Swift tests pass; JSCore bundle regenerated and verified in sync.