Repository navigation
Releases: rdlu/sofleii
Release list
v3.1.0 — right-half battery, build info over RPC, sofleii CLI
This release adds two things to the left half for desktop tools: it reports the right half's battery to the host, and it reports which build it runs over Studio RPC. It also adds the first release of sofleii, the CLI that reads both. The keymap is unchanged.
Upgrading from v3.0.0
No settings reset or re-pairing is needed. Only the left half's firmware changed: copy eyelash_sofle_studio_left.uf2 to the left half. mise run flash-release flashes both halves from the latest main build, which also works; reflashing the right half does no harm.
Firmware
- Right half's battery reaches the host (#45, PR #65). The left (central) half fetches the right half's level over the split link and exposes it as a second Battery Service. Its
0x2901description is "Peripheral 0". BlueZ'sBattery1and upower still only see the left half's level;sofleii batteryreads both. - Build info over Studio RPC (#46, PR #70), from
cormoran/zmk-feature-device-infoat8cedd2dd, left half only. It reports this repo's version, the zmk version, the Zephyr version, the board, the build time and hash, the device id and the reset cause. Reads work without unlocking Studio.- A CI build reports the bare short SHA of the commit it was built from. This one reports
5c53b30. A local build reportsgit describeoutput, with-dirtyif tracked files had uncommitted changes.
- A CI build reports the bare short SHA of the commit it was built from. This one reports
- Our own physical layout (PR #57), adapted from ZMK's MIT Sofle layout. It replaces the vendor's unlicensed layout files.
config/eyelash_sofle.jsonis now generated from the shield's.dtsi(mise run layout).
sofleii CLI (new)
A Rust CLI in sofleii/ that talks to the keyboard over USB or Bluetooth, with no browser and no Chrome flags. Run it with mise run sofleii -- <command>.
| Command | What it shows |
|---|---|
sofleii status |
transport, active BLE profile, output priority, both batteries; --format waybar for a bar module |
sofleii battery |
both halves' battery levels, read over BlueZ GATT |
sofleii info |
device, build info, hardware, Studio subsystems |
sofleii profiles |
the 5 BLE profiles (active, connected, bonded, address, name) |
--transport auto(the default) uses USB when the keyboard's serial port is present and answers, and otherwise uses Bluetooth.- Every command takes
--json, with a stable output format ("schema": 1), and returns documented exit codes (#44). - The commands only read for now. Profile switching, output priority, update checks and a ratatui TUI are planned (#44, #47, #48).
Studio over Bluetooth only answers while Bluetooth is the selected output. While USB is plugged in and output priority is USB, Bluetooth requests time out; auto already handles this. Docs: https://sofleii.dlu.one/docs/sofleii/
Docs site
- Printable keymap PDF: four A4 pages, in colour carried by lines and text, not fills. https://sofleii.dlu.one/sofleii-keymap.pdf, and attached to this release.
- The keymap is now its own section of the site, and the site has a new magenta theme (#54, #59).
Memory budget
The left half uses 460 KB of 792 KB flash (58.1%) and 124 KB of 256 KB RAM. That's about 3.3 KB more flash and 0.8 KB more RAM than v3.0.0, for the battery proxy and device info. The right half is unchanged at 323 KB / 77 KB.
Full changelog: v3.0.0...v3.1.0
v3.0.0 — ZMK main / Zephyr 4.1
The firmware moves off ZMK v0.3 / Zephyr 3.5 onto ZMK main (Zephyr 4.1), via cormoran's main+dya fork. The keymap is unchanged: same layers, home-row mods, combos, game mode and underglow. This is a major version because flashing it needs a settings reset and re-pairing.
⚠️ Upgrading from v2.x
- Flash
settings_reset.uf2on both halves. - Flash the new left and right firmware.
- Power both off, then turn on the right half first, then the left.
- On each computer, remove the old "Eyelash Sofle" pairing (for example
bluetoothctl remove <MAC>), then pair again fromSYS|NUM+ BT0–BT4.
mise run flash-release-reset # once per half
mise run flash-release # left, then rightHighlights
- ZMK main + Zephyr 4.1 (cormoran
main+dya@e5c9b691, Zephyrv4.1.0+zmk-fixes@10ba6d0c, both SHA-pinned). Upstream ZMK has no v0.4 release. This is the line cormoran's "v4" modules target. - Studio still works over USB and Bluetooth (locking off). The custom-RPC plumbing is kept:
cormoran_ble(BLE profile list/switch/rename/unpair, output priority) andcormoran_rsr(runtime encoder binding). That's the groundwork for sofleii, a planned Rust/ratatui status + control TUI. - Leaner module set. Removed
battery-historyandsettings-rpc(still v0.3-only upstream) andruntime-input-processor(only the vendor overlay used it). Their DYA Studio panels are gone. - Shield vendored in-repo at
config/boards/shields/eyelash_sofle/. The vendor module only supported v0.3. While copying it, a duplicateext-powernode was removed (the v4 board already drives P0.13), along with a deprecatedBT_CTLRdefault.
Breaking / renamed
- Board:
nice_nano_v2→nice_nano//zmk. - Reset artifact:
settings_reset-nice_nano_v2-zmk.uf2→settings_reset.uf2. CONFIG_WS2812_STRIPremoved (no longer exists on Zephyr 4.1).
Tested on hardware (2026-10-17)
Typing on both halves, split link, volume encoder, joystick mouse, per-layer underglow, all layers, game mode, nice!view, soft-off (Q+S+Z, 2 s; wake with one reset press), BLE host pairing, and Studio over USB (raw RPC: device info, custom subsystems, BLE profiles). Studio over BLE in the browser wasn't re-tested.
Memory budget
Left 457 KB / 792 KB flash (57.7%), 123 KB / 256 KB RAM. Right 323 KB (40.8%) / 77 KB. The newer Zephyr/BLE stack costs about 47–53 KB of flash per half versus v2.8.0, even after dropping three modules. Plenty of headroom remains.
Known issue (upstream)
cormoran_ble.get_split_info returns an empty reply. The module never sets nanopb's has_info, and it hardcodes peripheral_connected = false anyway. This doesn't affect the keyboard; it only matters to RPC clients.
Docs
CLAUDE.md said the soft-off combo was Q+A+Z. It's Q+S+Z (positions 14 28 40).
Full changelog: v2.8.0...v3.0.0
v2.8.0 — No Man's Sky gaming profile + Delete restored on BASE
A third gaming profile for No Man's Sky, and Delete comes back to the base layer — it had gone missing in v2.7.0.
Highlights
-
NOMANSKY gaming layer (8) — pick it from the game launcher with joystick ← (↑ is still Minecraft, ↓ generic, center cancels). Same locked chassis as the other game layers: no home-row mods, no hold-taps, pure
&kpso every hold works for movement.LSHIFTandLCTRLare swapped versus the Minecraft/GAME layers — Shift (run) sits on the pinky at pos 39, Ctrl on the left thumb at pos 56. NMS runs on Shift and leaves Ctrl unused.- pos 0 =
F12for Steam screenshots, replacing Minecraft'sF3debug screen and GAME's`console. - Underglow: white/silver. The OLED reads
NOMANSKY. - Both game combos work here too —
0+12exits to BASE,0+5re-opens the picker.
-
Delete is back on BASE:
mo(CODE)+BSPC. v2.7.0 swapped the right-thumb< 2 DELfor a plain&mo 2(to remove the hold-tap lag entering CODE) and relocated DEL to the MEDIA layer, but the docs never caught up — so it read as a broken key rather than a moved one. DEL now lives on the CODE layer at the left-thumbBSPCposition: hold CODE with the right thumb, tapBSPCwith the left. Cross-hand, so no same-hand roll, and CODE stays instant with no hold-tap cost reintroduced. MEDIA + left-index still types DEL as well.
Docs
KEYMAP.md and CLAUDE.md updated for both changes, plus two pre-existing errors fixed: the game-chassis thumb positions were documented off by one (LSHIFT is @56 with SPACE @57+59), and the gaming RGB note still claimed AUTO_OFF_USB=y when it has been =n since v2.6.0.
Memory budget
Nine layers now. Left half 410 KB / 792 KB flash (51.8%) and 109 KB / 256 KB RAM — the new layer cost about 1 KB flash and 2 KB RAM. Right half unchanged at 270 KB / 62 KB. Plenty of headroom.
Fixed after release: local builds
The release notes originally shipped a "known issue" here claiming config/west.yml pinned zmk to a moving branch that had advanced to Zephyr 4.1. That diagnosis was wrong. The manifest was correct; the local west workspace had drifted from it — the zmk/ clone's git remote still pointed at zmkfirmware/zmk from before the switch to cormoran's fork, so west update silently could not fetch the pinned revision and left the tree on an unrelated commit. CI was never affected, and this release was built from CI.
Fixed in #41, which also adds mise run doctor / doctor-fix / doctor-ci to catch that class of drift automatically.
Flashing
mise run flash-release # double-tap reset: left, then rightIf a previous ZMK Studio edit is stuck, run mise run flash-release-reset first.
Full changelog: v2.7.0...v2.8.0
v2.7.0 — RGB layer module: moods, master brightness, persistence
The macro-based per-layer underglow from v2.6.0 is replaced by an event-driven firmware module (zmk-rgb-layer/). Lower latency, switchable resting "moods", a brightness that every colour respects, and settings that survive a reboot.
Highlights
- Per-layer colour — the underglow shows the highest active layer (NAV indigo, CODE magenta, MEDIA amber, SYS red, gaming green/blue). A diff-cache emits a single
&rgb_ugcall per change, so it's noticeably snappier than the old macros. - Three base moods — on the SYS layer,
Z= dark (off),X= dim teal (default),C= cycle effects (breathe → spectrum → swirl). Live preview while you hold SYS. - Master brightness (day/night) — SYS
Q/Abrighten/dim everything; the level is applied to every layer colour. - Persists across reboots — your mood + brightness are saved and restored on boot, so a power-cycle or soft-off comes back the way you left it instead of resetting to the default teal.
- Both halves stay in sync — if one half powers on later than the other, the central re-asserts the correct colour once they reconnect.
Under the hood
- Central-only Zephyr module; the peripheral strip updates via the forwarded global
&rgb_ugbehaviour. - Now built in CI (the in-repo module is wired into
build.yamlviaZMK_EXTRA_MODULES). - Keymap cleanup: CODE is a plain
&mo 2(no hold-tap), and DEL moved to the MEDIA layer.
Not included (tracked separately)
- A reactive "typed-column lights up" effect — the underglow is per-column-addressable, but it needs a custom strip-driving engine. Follow-up issue filed.
Flashing
mise run flash-release # double-tap reset: left, then rightIf a previous ZMK Studio edit is stuck, run mise run flash-release-reset first. See the README for details.
Closes #33.
v2.6.0 — Per-layer underglow colour feedback (smart-off)
Per-layer underglow colour feedback (smart-off)
The base layer rests dark; holding a layer lights the underglow with that layer's colour, and releasing turns it off again — the strip glows only while you're in a non-base layer. Works the same on USB and Bluetooth.
| Layer | Colour |
|---|---|
| BASE (resting) | off / dark |
| NAV | indigo |
| CODE | magenta |
| MEDIA | amber |
| SYS|NUM | red |
| MINECRFT / GAME | green / blue (unchanged) |
Implemented entirely in the keymap by wrapping each momentary-layer key in an &rgb_ug macro (RGB_ON + solid + colour on hold, RGB_OFF on release). CODE uses a custom <_code hold-tap so tap=DEL is preserved.
Also in this release
- Corrected
AUTO_OFF_USBhandling — it's the battery-saver "off when USB disconnected" (not "off on USB"); now disabled so the base stays dark on USB. - Fixed a latent
mise run drawbug (parse step was missing-c, silently dropping keymap-drawer icon/label maps). - KEYMAP.md underglow section + README memory-budget refresh.
Known issue
- Colour-change lag on layer holds — structural to the stateless keymap approach (three sequential split-forwarded
&rgb_ugcalls + 50 ms render tick). A proper fix (event-driven listener, optional stateful base moods) is tracked in #33.
Not possible on this hardware
- A QMK-style per-key reactive "ripple" effect — the board has only 7 sparse underglow LEDs per half (not per-key) and ZMK underglow has no keypress-reactive engine.
Full changelog: PR #32
v2.5.0 — ZMK Studio over USB + Bluetooth (cormoran fork)
Live keymap editing via ZMK Studio over USB and Bluetooth, built on cormoran's ZMK-Studio fork. Hardware-verified on the Eyelash Sofle (nice!nano v2). The Studio web app lives in a separate project (#30); this release is firmware.
Highlights
- USB + BLE keymap editing — connect ZMK Studio over
/dev/ttyACM(CDC-ACM) or Bluetooth (GATT). BLE editing is the headline feature. - Encoder is web-configurable — switched to
runtime-sensor-rotate(volume by default) on all 8 layers; reassign it live in Studio. - Studio locking off (
CONFIG_ZMK_STUDIO_LOCKING=n) — no unlock key needed.
Under the hood
- ZMK →
cormoran/zmk@v0.3-branch+dya(v0.3 base) + 5 cormoran DYA modules;eyelash_soflepinned to dya commit4848e21f. - Board → shield migration: builds are now
-DSHIELD="eyelash_sofle_* nice_view";.uf2names and flash tasks unchanged. - Custom keymap (layers, HRM, gaming) otherwise untouched.
Memory budget (CI build, both transports compiled in)
| Build | Flash | RAM |
|---|---|---|
| Left (Studio + nice_view) | 51.5% | 41.9% |
| Right (nice_view) | 34.2% | 24.3% |
| Settings reset | 5.7% | 4.4% |
Flashing & use
- Flash:
mise run flash-release(download from CI) — or build locally withmise run build-flash. - Studio: connect the left half via USB, or pair over BT, then open the Studio app. On Linux, BLE editing needs Chrome's
chrome://flags/#enable-experimental-web-platform-featuresflag (Web Bluetooth) — not a firmware limitation. - Rollback:
mise run flash-releaseafter reverting, or reflash a prior release.
See PR #31 for full detail and the hardware verification log.
v2.0.0 — Gaming mode (picker + Minecraft + generic GAME layers)
Adds a dedicated gaming mode (closes #11) and supersedes the v2.0.0-rc1 prototype, re-applied onto current main (was v1.6.0).
Gaming mode (layers 5–7)
Locked layers that strip all home-row-mods / hold-tap / caps_word — WASD, SPACE and modifiers are pure keys.
- Enter: hold
mo(SYS|NUM)(left thumb) → tap the right-thumb outer key → GAME? picker. Joystick ↑ = Minecraft, ↓ = generic GAME (FPS/MOBA), center = cancel. - Chassis: plain WASD/QWER + plain right half for chat;
LCTRL(pinky),LSHIFT(left thumb),SPACEon both thumbs,F13push-to-talk,mo(MEDIA)reachable. Joystick = audio cluster + center Enter. - Exit/switch: top corners (
0 + 12) → exit to BASE (RGB off); top-left pair (0 + 5) → re-pick. - Indicator: OLED shows
MINECRFT/GAME. Per-game RGB underglow shows on battery (suppressed on USB byAUTO_OFF_USB).
The original entry (SYS joystick-center) became the mouse left-click in v1.5.0, so entry moved to a free SYS thumb key; the joystick-mouse is preserved.
Tooling
- Printable PDF now paginates —
mise run draw-pdfputs one A4 page per group of layers (LAYERS_PER_PAGE, default 4 → 2 pages) instead of cramming all layers onto one.
Firmware budget
Left 380 KB / 792 KB (48.0%), right 269 KB (33.9%) — gaming adds ~4 KB.
Flashing
mise run flash-release # downloads the latest main build and flashes both halvesFull details in PR #27. See KEYMAP.md for the layer reference.
v1.6.0 — Printable keymap PDF + build-flash memory-budget summary
Summary
Tooling-only release. No firmware changes — v1.5.0 firmware remains the latest functional build (this release does not affect the .uf2 artifacts).
New mise tasks — printable keymap layout
Generate a portrait A4 PDF of the keymap from the keymap-drawer SVG. One task per source — mirrors the mental model of "which SVG do I want printed?":
| Task | Source SVG |
|---|---|
mise run draw-pdf |
The existing local file (no regen, no fetch). |
mise run draw-pdf-fresh |
Regenerate locally (mise run draw) first. |
mise run draw-pdf-ci |
Fetch the CI-rendered SVG from origin/main (generally crisper than the local cache). |
mise run open-pdf |
Generate from local SVG + open in the default viewer (xdg-open). |
PDF layout: A4 portrait, image centered, UTF-8-safe header (Eyelash Sofle — Keymap Layout · v1.6.0) and footer (Generated 2026-04-25 · github.com/rdlu/zmk-sofle · Page 1 / 1). Output goes to keymap-drawer/eyelash_sofle.pdf (gitignored).
build-flash improvement
The canonical build+flash task now prints a memory budget summary block at the end:
===============================================
Firmware memory budget
===============================================
Left (Studio + nice_view):
FLASH: 385400 B 792 KB 47.52%
RAM: 98346 B 256 KB 37.52%
Right (nice_view) :
FLASH: 274972 B 792 KB 33.90%
RAM: 62908 B 256 KB 24.00%
✅ Both halves built and flashed. They will auto-reset and re-pair over BT.
Tool management
weasyprint is declared in [tools] as pipx:weasyprint = "latest" — provision with mise install, upgrade with mise upgrade pipx:weasyprint. Each PDF task fails fast with a friendly error if the binary is missing.
PR: #26
v1.5.0 — Central media keys + dedicated mouse fallback layer
Summary
Reorganizes the central joystick column on the BASE/NAV layers and consolidates mouse keys onto a single fallback layer.
Changes
BASE
- Central joystick column (top→bottom):
Vol↑ · Vol↓ · Play/Pause · Next · Mute— direct media access without holdingmo(MEDIA). Encoder rotation continues Vol↑/Vol↓.
NAV
- Central joystick column:
↑ · ↓ · ← · → · Enter— secondary arrow cluster (the right-side HJKL arrows on row 2 stay). - Mouse keys removed from this layer entirely.
SYS|NUM — emergency mouse fallback
| Key | Action |
|---|---|
F / G / V |
Mouse 1 / 2 / 3 (LCLK / RCLK / MCLK) |
R / T |
Mouse 4 / 5 (MB4 / MB5 — back / forward) |
| Central joystick column | Pointer move (↑/↓/←/→) |
| Central joystick row 4 | Left click |
MEDIA
- Central column kept duplicating BASE (Vol↑/Vol↓/PP/Next/Mute) intentionally for clarity when reading the layer.
Memory budget (local v1.5.0 build)
| Build | Flash | RAM |
|---|---|---|
| Left (Studio + nice_view) | 376 KB / 792 KB (47.5%) | 96 KB / 256 KB (37.5%) |
| Right (nice_view) | 269 KB / 792 KB (33.9%) | 61 KB / 256 KB (24.0%) |
Byte-identical to v1.4.2 — the rearrangement is structure-neutral (same count of mkp/mmv behaviors, just different positions and layers).
Flashing
mise run flash-release will download these artifacts and flash both halves.
PR: #25
v1.4.2 — AltGr moves to right-thumb F12
Summary
Maintenance release: AltGr (RALT) moves off the home/index R and U keys onto a single right-thumb key.
Changes
RandU— restored to plain&kp(no moreralt_mthold-tap on the alpha block)- Right-thumb
F12— nowralt_mt RALT F12: tap sendsF12, hold activatesRALT(AltGr) - Behavior block unchanged:
tapping-term-ms=280,require-prior-idle-ms=230,quick-tap-ms=175,flavor=balanced, no positional restriction (AltGr combos still span both hands)
Why
The two alpha-key ralt_mt instances added a hold-tap context to commonly-typed letters. Holding R or U for any reason silently armed RALT, and slow deliberate single presses occasionally felt sluggish. Moving AltGr to a thumb key — where holds are natural — removes that friction from the alpha block entirely while keeping AltGr easily reachable for accent combos like é, ó, ñ, ü.
Memory budget (local v1.4.2 build)
| Build | Flash | RAM |
|---|---|---|
| Left (Studio + nice_view) | 376 KB / 792 KB (47.5%) | 96 KB / 256 KB (37.5%) |
| Right (nice_view) | 269 KB / 792 KB (33.9%) | 61 KB / 256 KB (24.0%) |
Left flash dropped ~7 KB vs v1.4.1 from removing the two alpha-key hold-tap contexts.
Flashing
Use mise run flash-release to download these artifacts and flash both halves interactively.
PR: #24