npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wiim2mqtt

v1.1.0

Published

Interface between WiiM (LinkPlay) audio streamers and MQTT

Readme

wiim2mqtt

npm CI License

Control WiiM audio streamers via MQTT

Connects to a WiiM (Pro, Pro Plus, Mini, Amp, Ultra — all LinkPlay based) over UPnP events and the LinkPlay HTTP API and bridges it to an MQTT broker, following the mqtt-smarthome architecture. Announces the device to Home Assistant via MQTT discovery.

State changes (play/pause, track, volume, …) arrive as UPnP events within milliseconds; the HTTP API is used for commands, for everything UPnP does not expose (presets, device info, multiroom) and as a polling fallback when events are not available.

Install

npm install -g wiim2mqtt

Requires Node.js ^20.19, ^22.12 or >= 24. wiim2mqtt 1.x is built on mqtt-interfaces-core (mqtt-smarthome spec 2.x) like the author's other xyz2mqtt adapters; see "Upgrading from 0.1" below.

Usage

wiim2mqtt --address <wiim-ip> --mqtt-url mqtt://<broker>

| option | default | description | | --------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | -a, --address | | ip or hostname of the wiim, required | | --api-url | | base url of the http api (default https://; e.g. http:// for other linkplay devices) | | --upnp | on | subscribe to upnp events (use --no-upnp for polling only, no listener) | | --upnp-port | 49152 | port of the upnp description/control/event interface on the device | | --callback-host | | address the wiim uses to reach the upnp event listener (docker/wsl/multi-homed hosts; default: auto) | | --callback-port | 0 | fixed port of the upnp event listener (0 = random free port; --install assigns one per instance) | | --poll-interval | 1 | seconds between http status polls while playing when upnp events are unavailable | | --position-interval | 1 | seconds between status/position updates while playing (0 = off) | | --album-art-data | off | also publish the cover image bytes on /status/album_art_data | | --raw-set | off | accept raw http api commands on /set/cmd (unrestricted remote api!) | | -u, --mqtt-url | mqtt://localhost | mqtt broker url, e.g. mqtt://broker or mqtts://user:pass@broker:8883 | | --mqtt-username | | mqtt broker username | | --mqtt-password | | mqtt broker password | | --mqtt-client-id-prefix | | prefix for the mqtt client id (_) | | --mqtt-tls-ca | | path to a CA certificate file for mqtts:// connections | | -n, --name | wiim | instance name. used as mqtt client id and as prefix for topics | | --json-payloads | on | publish status as JSON {"val": ..., "ts": ..., "lc": ...} (use --no-json-payloads for plain values) | | --ha-discovery | on | publish Home Assistant MQTT discovery (use --no-ha-discovery to disable and clear) | | --ha-prefix | homeassistant | Home Assistant discovery prefix | | --maintenance | on | accept /maintenance/set/loglevel and /restart over mqtt (use --no-maintenance to disable) | | -v, --verbosity | info | log level (error, warn, info, debug) | | --install / --uninstall | | install / remove the systemd service wiim2mqtt@<name> (needs root) | | --config-schema | | print the JSON Schema of all options and exit |

Every option can also be set via environment variable with the prefix WIIM2MQTT_, e.g. WIIM2MQTT_ADDRESS=192.168.1.30 WIIM2MQTT_MQTT_URL=mqtt://broker wiim2mqtt; the broker settings fall back to the unprefixed MQTT_URL, MQTT_USERNAME, MQTT_PASSWORD, MQTT_TLS_CA. Precedence: command line > environment > defaults. --config-schema prints a JSON Schema of all options (with x-env and x-secret annotations) for management UIs.

UPnP events and the callback listener

The WiiM pushes state changes to an HTTP listener that wiim2mqtt opens (UPnP GENA). The device must be able to reach it:

  • the listener address is detected automatically (the interface that routes to the WiiM); override it with --callback-host on multi-homed hosts, WSL or Docker bridge networks.
  • the port is random unless --callback-port is given. --install assigns a fixed port per instance (49200, 49201, …) so firewall rules stay stable.
  • If no event arrives within 30 s of subscribing, wiim2mqtt logs a warning naming the callback URL, keeps working with HTTP polling (status/upnp = polling) and retries the subscription in the background. --no-upnp turns the listener off altogether.

Run as a systemd service

sudo wiim2mqtt --install --name wiim --address 192.168.1.30 --mqtt-url mqtt://192.168.1.2

--install creates a system user wiim2mqtt, writes the given options to /etc/wiim2mqtt/<name>.env (WIIM2MQTT_* variables — edit and systemctl restart wiim2mqtt@<name> to change), installs the template unit /etc/systemd/system/[email protected] and enables + starts wiim2mqtt@<name>. The instance name is the --name option, i.e. the MQTT topic prefix. Broker settings shared by all mqtt-interfaces adapters on the host can go to /etc/mqtt-interfaces/broker.env (MQTT_URL, MQTT_USERNAME, MQTT_PASSWORD). Logs: journalctl -u wiim2mqtt@<name> -f.

Several devices: run --install once per device with a different --name — each becomes its own instance with its own config, topic prefix and callback port, sharing one template unit and one system user:

sudo wiim2mqtt --install --name wiim-living  --address 192.168.1.30 --mqtt-url mqtt://broker
sudo wiim2mqtt --install --name wiim-kitchen --address 192.168.1.31 --mqtt-url mqtt://broker
systemctl status 'wiim2mqtt@*'

sudo wiim2mqtt --uninstall --name wiim-kitchen removes one instance (the template unit goes with the last one). she can install, configure, update and restart instances from its Services page; the config form comes from --config-schema.

Docker

docker run -d --name wiim2mqtt --network host --restart unless-stopped \
  -e WIIM2MQTT_ADDRESS=192.168.1.30 \
  -e WIIM2MQTT_MQTT_URL=mqtt://broker \
  ghcr.io/hobbyquaker/wiim2mqtt

--network host lets the WiiM reach the event listener. With a bridge network instead, publish the port and tell the adapter the host's address: -p 49200:49200 -e WIIM2MQTT_CALLBACK_PORT=49200 -e WIIM2MQTT_CALLBACK_HOST=<docker-host-ip>. --restart unless-stopped brings the container back after maintenance/set/restart.

Deploy to a host (developers)

deploy.sh [user@host] (or npm run deploy) runs the tests, packs the module, copies it to the host, installs it under /usr/local/lib/node_modules/wiim2mqtt and restarts every wiim2mqtt@<name> service there (SERVICE, REMOTE_DIR, SKIP_TESTS env vars, see the script). Install the service once with --install first.

Finding the device

wiim2mqtt --discover

sends an SSDP M-SEARCH for UPnP media renderers and reads the description each answer points at, keeping the ones LinkPlay built:

172.16.23.189  Hobbyraum-WiiM  WiiM Pro Plus Receiver  [upnp]  (ssdp)

The name is the one you gave the device in the WiiM app. --discover-json prints the same as JSON (with the UDN and the description URL). -a auto runs the scan at start and uses what it found, refusing to start when none or more than one device answers rather than bridging the wrong speaker:

wiim2mqtt -a auto -u mqtt://broker

A neighbour's TV or a Sonos answers the same M-SEARCH; only a description naming LinkPlay (or WiiM) is kept, so those are dropped rather than reported.

SSDP is link-local — it does not cross a router. For a WiiM on another VLAN, name it or the range it is in; port 49152 (the description) is checked over TCP, which routes fine:

wiim2mqtt --discover --discover-address 172.16.20.30
wiim2mqtt --discover --discover-address 172.16.20.0/24

The scanning itself lives in mqtt-interfaces-core; this adapter only declares the search and what counts as a WiiM (lib/discovery.js).

Topics

<name> defaults to wiim.

<name>/connected

Retained. 0 = not connected to the broker (set via last will), 1 = connected to the broker but the WiiM's HTTP API is unreachable, 2 = connected to both. Whether UPnP events are flowing is reported separately in status/upnp.

<name>/info and <name>/maintenance/set/…

<name>/info (retained JSON) describes the running instance: package name and version, mqtt-smarthome spec version, node version, host, pid, start time, the device address and api url, and a device block with the WiiM's name, model, firmware, uuid, mac, ip and the current state source (upnp: events, polling or off). <name>/maintenance/set/loglevel (error|warn|info|debug) changes the log level at runtime, <name>/maintenance/set/restart exits cleanly so the service manager restarts the process; --no-maintenance turns both off.

<name>/status/<item>

Retained status reports, published on start and whenever the device reports a change (also when changed via the WiiM app, AirPlay, …). Every status is {"val": <value>, "ts": <ms received>, "lc": <ms last changed>}; with --no-json-payloads the plain value (lists as JSON arrays).

| item | type | set | notes | | ----------------------------------------------------------------------------------- | -------- | --- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | play_state | string | yes | playing, paused, stopped, loading | | source | string | yes | wifi, airplay, dlna, spotify, tidal, bluetooth, line_in, optical, usb, hdmi, coaxial, phono, multiroom; only the ones in source_list can be set | | source_list | string[] | | selectable sources | | volume | int | yes | 0–100 | | mute | bool | yes | | | title, artist, album, album_art | string | | empty while stopped; album_art is the URL the device reports | | album_art_data | bytes | | the cover image itself (raw bytes, not JSON), only with --album-art-data | | quality, sample_rate, bit_depth, bitrate, origin_source | mixed | | e.g. LOSSLESS, 44100, 16, 1411, Tidal (depends on the source) | | duration | int | | seconds | | position | int | yes | seconds, not retained, published every --position-interval s while playing (interpolated locally) | | repeat | string | yes | off, one, all | | shuffle | bool | yes | | | queue_index, queue_length | int | | position in / length of the device's play queue | | preset_list, preset_max | JSON | | [{number, name, source, pic}] and the number of preset slots | | group_role, group_master, group_slaves | mixed | | standalone / master / slave, the master's ip, [{name, ip, uuid}] | | device_name, model, firmware, update_available, rssi, ip, mac, uuid | mixed | | device info, refreshed every 60 s | | firmware_update | JSON | | {installed_version, latest_version} for the Home Assistant update entity | | upnp | string | | events (UPnP events drive the state), polling (events unavailable, polling), off (--no-upnp) | | cmd_result | string | | response of a raw cmd, not retained, only with --raw-set |

<name>/set/<item>

Change requests. Payload is a plain value or mqtt-smarthome style JSON ({"val": 12}). Booleans accept true/false, 1/0, on/off.

| item | payload | notes | | ----------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ | | volume | 0–100 | | | volume_up, volume_down | optional step (default 5) | | | mute | bool | | | play | empty, or a stream URL | resume, or play the given URL | | pause, stop, next, prev, toggle | any | | | play_state | playing, paused, stopped | | | seek / position | seconds or mm:ss | | | source | name from source_list | | | repeat | off, one, all | | | shuffle | bool | | | preset | number or name | 1..preset_max; names from preset_list (case-insensitive) | | join | ip of the master | this device joins the master's group | | leave | any | this device leaves its group (sent to the master) | | ungroup | any | dissolve the group this device masters | | kick | ip or name of a slave | | | reboot | true | | | refresh | status (default), presets, device, group, metadata | explicit re-read of the device state | | cmd | raw http api command | only with --raw-set; the response goes to status/cmd_result (not retained) |

mosquitto_pub -t wiim/set/volume -m 12
mosquitto_pub -t wiim/set/preset -m "Radio One"
mosquitto_pub -t wiim/set/source -m bluetooth
mosquitto_pub -t wiim/set/repeat -m all
mosquitto_pub -t wiim/set/refresh -m presets

Every set is verified by re-reading the affected items after 500 ms; a set that changed nothing is logged as a warning.

Home Assistant

MQTT discovery is on by default (HA ≥ 2024.11, device-based discovery). The WiiM appears as one device with: volume (number), mute and shuffle (switches), source / repeat / preset (selects), play/pause/stop/next/previous buttons, sensors for play state, title, artist, album, quality, sample rate, bit depth, bitrate, an album art image entity, a firmware update entity and diagnostic sensors. Availability follows <name>/connected.

--no-ha-discovery disables discovery and removes the device announcement on startup; --ha-prefix changes the discovery prefix. HA has no MQTT media player platform; a payload for a community media player component is planned.

Upgrading from 0.1

wiim2mqtt 1.0 moves to mqtt-interfaces-core. Status and set items are unchanged; what changed:

| 0.1 | 1.0 | | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | plain status payloads, --json-payloads opt-in | {val, ts, lc} JSON by default; --no-json-payloads for plain values | | <name>/get/<what> for an explicit refresh | <name>/set/refresh with the same payload (status, presets, device, group, metadata) | | <name>/info with uptime, re-published every minute | <name>/info per the core convention (spec, started, maintenance, …), re-published when the device facts change | | --probe | removed; use node scripts/dump.js <address> from a checkout | | --api-url alone | --address is always required, --api-url only overrides the url | | — | <name>/maintenance/set/{loglevel,restart}, --config-schema, --mqtt-client-id-prefix, --mqtt-tls-ca, MQTT_* fallback variables | | /etc/wiim2mqtt/<name>.env + own template unit | same file (the callback port is kept); the template unit is rewritten by --install (shared broker.env, state directory /var/lib/wiim2mqtt/<name>, Restart=always) — run sudo wiim2mqtt --install --name <n> … once per instance |

Home Assistant: the device id (wiim2mqtt_<name>) and entity unique ids are unchanged, so the device and its history are kept; the discovery payload is re-published on start.

Verification status

Developed against a WiiM Pro Plus (firmware 4.8.814734). Other models share the platform but may differ in available sources (hdmi, phono, coaxial on Amp/Ultra) and in details of the API. node scripts/dump.js <address> --listen 20 prints a read-only dump of your device (api responses, UPnP description, 20 s of events) — attach it to issues about missing or wrong items.

License

MIT © Sebastian Raff