diff --git a/PROTOCOL.md b/PROTOCOL.md index 587bf0b..36213bb 100644 --- a/PROTOCOL.md +++ b/PROTOCOL.md @@ -55,7 +55,7 @@ The rest of the 64 bytes is zero-padded. | 0x2026 | headphone monitoring | 2000 = on, 3000 = off | | 0x2027 | noise cancellation | 0 / 1 | | 0x2028 | noise cancellation strength | 0-100 | -| 0x202A | RGB ring brightness | 0-100 (the app does not show it for this model) | +| 0x202A | labelled RGB ring brightness in the app's shared code | stores 0-100, but the LEDs do not change on firmware 2.1.2; not exposed | | 0x202C | vocal enhancement | 0 / 1 | | 0x204B | level-meter enable | not answered by firmware 2.1.2 | diff --git a/README.md b/README.md index a966666..2974d85 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,6 @@ this covers the same settings with a local web page and a command-line tool. - Vocal enhancement - Hear yourself (headphone monitoring) - Mute, kept in sync with the mic's touch pad -- RGB ring brightness - Factory reset Pure Python, no dependencies. Nothing leaves your machine: the page is served from your own @@ -80,7 +79,7 @@ keeps its current settings. dgm20-web --open # open the control page (http://localhost:8720) dgm20ctl # show current settings dgm20ctl set nc on nc-level 80 -dgm20ctl set monitor off enhance on brightness 40 +dgm20ctl set monitor off enhance on dgm20ctl watch # print changes made on the mic itself dgm20ctl info # model and firmware dgm20ctl factory-reset --yes @@ -93,7 +92,6 @@ dgm20ctl factory-reset --yes | `nc-level` | 0-100: noise cancellation strength | | `enhance` | on, off: vocal enhancement | | `monitor` | on, off: hear yourself in headphones plugged into the mic | -| `brightness` | 0-100: RGB ring | Settings are stored on the mic, so they stay the same after a replug and on other computers. @@ -124,6 +122,11 @@ model that isn't supported yet (see above). **`dgm20-web: cannot listen on 127.0.0.1:8720`**: it's already running, for example as the service. Open http://localhost:8720, or pick another port with `--port`. +**"the mic is connected but has stopped answering"**: the mic's settings channel froze. This can +happen if you hold or tap the mic's buttons while settings are being changed from the computer. +Unplug the mic and plug it back in; its settings are kept. A software USB reset is not enough, +the mic needs to lose power. + **Commands not found after installing**: `~/.local/bin` isn't on your `PATH`. Add `export PATH="$HOME/.local/bin:$PATH"` to your shell profile. diff --git a/docs/screenshot.png b/docs/screenshot.png index 458eed0..4784a4a 100644 Binary files a/docs/screenshot.png and b/docs/screenshot.png differ diff --git a/pyproject.toml b/pyproject.toml index 9dd91ec..9f464e8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta" [project] name = "dgm20-linux" version = "0.1.0" -description = "Control the Maono DGM20 USB microphone on Linux: noise cancellation, monitoring, vocal enhancement, mute, RGB brightness" +description = "Control the Maono DGM20 USB microphone on Linux: noise cancellation, monitoring, vocal enhancement, mute" readme = "README.md" license = "MIT" requires-python = ">=3.9" diff --git a/src/dgm20/device.py b/src/dgm20/device.py index e2e7c17..ace8d79 100644 --- a/src/dgm20/device.py +++ b/src/dgm20/device.py @@ -20,6 +20,7 @@ Both share one framing: Settings are 16-bit registers. Input gain and headphone volume are standard USB audio controls and are not part of this channel. """ +import fcntl import glob import os import select @@ -37,7 +38,6 @@ SETTINGS = { "nc-level": (0x2028, (0, 100), "Noise cancellation strength"), "enhance": (0x202C, "bool", "Vocal enhancement"), "monitor": (0x2026, {"on": 2000, "off": 3000}, "Hear yourself in headphones"), - "brightness": (0x202A, (0, 100), "RGB ring brightness"), } REG_FACTORY_RESET = 0x100A REG_DFU = 0x1008 @@ -48,6 +48,14 @@ class DeviceError(Exception): pass +class NotResponding(DeviceError): + """The mic is plugged in but its settings channel has stopped answering.""" + + def __init__(self): + super().__init__("the mic is connected but has stopped answering. Unplug it and plug it back in; " + "your settings are kept") + + def find_hidraw(): for node in sorted(glob.glob("/sys/class/hidraw/hidraw*")): try: @@ -152,8 +160,11 @@ class Mic: os.read(self.fd, 64) def _io(self, typ, words, accept, timeout=0.5): + # the flock makes other processes (CLI, web service) wait their turn, so nobody + # mistakes another process's reply or ack for their own with self.lock: try: + fcntl.flock(self.fd, fcntl.LOCK_EX) self._drain() os.write(self.fd, frame(typ, words)) while select.select([self.fd], [], [], timeout)[0]: @@ -162,6 +173,11 @@ class Mic: return p[1] except OSError as e: raise DeviceError(f"mic disconnected ({e.strerror})") from None + finally: + try: + fcntl.flock(self.fd, fcntl.LOCK_UN) + except OSError: + pass return None def read(self, addr): @@ -188,7 +204,10 @@ class Mic: return self.get(name) def state(self): - return {name: self.get(name) for name in SETTINGS} + state = {name: self.get(name) for name in SETTINGS} + if all(v is None for v in state.values()): + raise NotResponding() + return state def info(self): model = self.read_range(REG_MODEL, 8) diff --git a/src/dgm20/static/index.html b/src/dgm20/static/index.html index a007c6b..52308f8 100644 --- a/src/dgm20/static/index.html +++ b/src/dgm20/static/index.html @@ -179,11 +179,6 @@ footer { margin-top: 28px; display: flex; justify-content: space-between; flex-w

Plays the mic through headphones plugged into the DGM20.

-
  • - - - – -