Skip to content

Add support to Soomfon XF-CN001 - #15

Open
nos86 wants to merge 4 commits into
StreamController:masterfrom
nos86:feature/soomfon-1500-3003
Open

nos86 wants to merge 4 commits into
StreamController:masterfrom
nos86:feature/soomfon-1500-3003

Conversation

@nos86

@nos86 nos86 commented Nov 7, 2026

Copy link
Copy Markdown

Hi! This PR adds support for the Soomfon Stream Controller (XF-CN001), USB 1500:3003.

It's essentially a rebranded Mirabox 293S: same 6×3 layout, same key mapping and the same command set. The difference is that it runs a newer revision of the Mirabox protocol, which is why the existing Mirabox293S class doesn't work with it as-is (keys are read, but images never show up). The mirajazz / opendeck-akp153 projects classify this device as protocol version 3.

What's in it

The new SoomfonStreamController class subclasses Mirabox293S and only overrides what actually differs:

  • 1024-byte output reports instead of 512
  • 95×95 key images; the three tiles of the non-touch side display (keys 5, 11, 17) are 82×82, so the class resizes those internally and callers can keep using a single key image format
  • native press and release events, so there's no need for the press/release emulation used by the 293S
  • a periodic CONNECT heartbeat while the deck is open, sent under update_lock so it can't land in the middle of an image upload

The deck exposes two HID interfaces under the same VID/PID (the second one is a keyboard), so I registered it with interface 0, the same way the Ulanzi D200 is handled.

One small note: the payload builder in Mirabox293S produces one byte more than the packet size. I fixed that only in the new class and left the 293S untouched, since I can't test on that device.

I also added a device page to the docs, plus an example (example_soomfon.py) based on the Neo one that uses the side display as a clock. It uses key 16 as the exit button, because on this device the last index (17) is a side tile and can't be pressed. That also means example_basic.py can't be closed from the deck on this model.

Testing

  • test/test.py with the dummy transport passes for all models, including the new one
  • Tested on the real device on Windows and on a Raspberry Pi 4 (Home Assistant OS): images on all 18 tiles, correct press/release events, and no resets over time

Happy to adjust anything if you'd prefer a different approach.

Copilot AI balanced review requested due to automatic review settings November 7, 2026 17:57

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Copilot was unable to complete this review at the selected tier due to model policy or plan restrictions.

🟡 Changes recommended

The change still has an unguarded indexing risk in _read_control_states that could raise IndexError on short reports and crash the read thread, along with a minor documentation formatting nit.

2 open issues
What changed in this PR

This pull request adds first-class support for the Soomfon Stream Controller (XF-CN001, USB 1500:3003), a Mirabox-293S–family device using a newer protocol revision, by introducing a dedicated device implementation, wiring it into discovery, and adding docs plus an example.

Changes:

  • Add Soomfon VID/PID constants and register the device on HID interface 0 for correct enumeration.
  • Introduce SoomfonStreamController (derived from Mirabox293S) with updated report sizing, image sizing behavior (including side-tile resizing), native press/release handling, and a CONNECT heartbeat.
  • Add documentation pages and a new example_soomfon.py demonstrating usage (including side display tiles).

Outstanding review items to address before approval:

  • Moderate: Guard against short/incomplete HID reports in SoomfonStreamController._read_control_states to avoid potential IndexError (noted at the referenced locations).
  • Nit: Cache blank JPEG images in _blank_image to avoid repeated recompression work.
  • Nit: Remove trailing whitespace in the devices.rst section title to prevent awkward reStructuredText rendering.

Assessment: This pull request requires changes.

File Summary
src/​StreamDeck/​ProductIDs.py Adds Soomfon VID/PID constants for device discovery.
src/​StreamDeck/​Devices/​SoomfonStreamController.py Implements Soomfon protocol behavior; address the moderate-length guard in _read_control_states and the blank-image caching nit.
src/​StreamDeck/​DeviceManager.py Registers the new device and constrains enumeration to interface 0.
src/​example_soomfon.py Adds a Soomfon-specific example script (including side-tile usage).
README.md Updates supported-device documentation to include the Soomfon controller.
doc/​source/​modules/​devices.rst Adds the device to Sphinx docs; fix trailing space in the section title (nit).
doc/​source/​index.rst Updates docs index/toctree to include the new Soomfon example page.
doc/​source/​examples/​soomfon.rst Adds the Sphinx page embedding the new example.

Review effort: Balanced

Comment thread doc/source/modules/devices.rst Resolved
Update input validation in SoomfonStreamController and fix heading in documentation.

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants