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

lgsb2mqtt

v2.1.1

Published

Interface between LG soundbars and MQTT

Readme

lgsb2mqtt

npm License

Control LG soundbars via MQTT

Connects to the TCP control interface of LG soundbars (the same one the "LG Sound Bar" app uses) and bridges it to an MQTT broker, following the mqtt-smarthome architecture.

Install

npm install -g lgsb2mqtt

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

Usage

lgsb2mqtt --address <soundbar-ip> --mqtt-url mqtt://<broker>

| option | default | description | | ------------------------------------ | ------------------ | -------------------------------------------------------------------------------- | | -a, --address | | soundbar address (ip or hostname), required | | --port | 9741 | soundbar control port | | --publish-raw | off | additionally publish every raw protocol field as <name>/status/<MSG>/<key> | | --raw-set | off | accept raw protocol sets on <name>/set/<MSG>/<key> (unrestricted!) | | -u, --mqtt-url | mqtt://localhost | broker URL, see MQTT.js | | --mqtt-username, --mqtt-password | | broker credentials | | -n, --name | soundbar | instance name, used as topic prefix | | --json-payloads | on | status as {"val", "ts", "lc"} JSON; --no-json-payloads for plain values | | --ha-discovery | on | Home Assistant MQTT discovery (--no-ha-discovery disables and clears it) | | --ha-prefix | homeassistant | discovery prefix | | --maintenance | on | accept <name>/maintenance/set/{loglevel,restart}; --no-maintenance disables | | -v, --verbosity | info | error, warn, info, debug | | --install / --uninstall | | install/remove the systemd service lgsb2mqtt@<name> | | --config-schema | | print the JSON Schema of all options and exit |

Every option can also be set via environment variable with the prefix LGSB2MQTT_, e.g. LGSB2MQTT_ADDRESS=192.168.1.50 LGSB2MQTT_MQTT_URL=mqtt://broker lgsb2mqtt; the broker settings fall back to the unprefixed MQTT_URL, MQTT_USERNAME, MQTT_PASSWORD.

Run as a systemd service

sudo lgsb2mqtt --install --name soundbar --address 192.168.1.50 --mqtt-url mqtt://192.168.1.2

--install creates a system user lgsb2mqtt, writes the given options to /etc/lgsb2mqtt/<name>.env (LGSB2MQTT_* variables — edit and systemctl restart lgsb2mqtt@<name> to change), installs the template unit /etc/systemd/system/[email protected] and enables + starts lgsb2mqtt@<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 lgsb2mqtt@<name> -f.

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

sudo lgsb2mqtt --install --name sb-living  --address 192.168.1.50 --mqtt-url mqtt://broker
sudo lgsb2mqtt --install --name sb-bedroom --address 192.168.1.51 --mqtt-url mqtt://broker
systemctl status 'lgsb2mqtt@*'

sudo lgsb2mqtt --uninstall --name sb-bedroom removes one instance (the template unit goes with the last one). she can install, configure and update instances from its Services page.

Docker

docker run -d --name lgsb2mqtt \
  -e LGSB2MQTT_ADDRESS=192.168.1.50 \
  -e LGSB2MQTT_MQTT_URL=mqtt://broker \
  ghcr.io/hobbyquaker/lgsb2mqtt

Finding the soundbar

lgsb2mqtt --discover

browses the network for Chromecast devices — LG soundbars have Chromecast built in — and keeps the ones that also answer on the temescal control port (9741, --port), which is what tells a soundbar apart from a speaker, a TV or a Nest:

172.16.20.180  Wohnzimmer-Soundbar  LG S90Q Soundbar  [control]  (mdns)

The name and model come from the Chromecast TXT record. --discover-json prints the same as JSON. -a auto runs the scan at start and uses what it found, refusing to start when none or more than one soundbar answers rather than bridging the wrong one:

lgsb2mqtt -a auto -u mqtt://broker

When the soundbar is on another VLAN

mDNS is link-local: a browse does not cross a router on its own. An mDNS reflector (avahi with enable-reflector) does bridge it — the output above was produced through one — but the reflected answers arrive late and not on every attempt: measured over 20 second windows, the soundbar showed up in one browse and not in the next. Give the scan --discover-timeout 20, and when you want it to work every time, name the soundbar or the range it is in instead. The control port is probed over TCP, which routes fine:

lgsb2mqtt --discover --discover-address 172.16.20.180      # this device
lgsb2mqtt --discover --discover-address 172.16.20.0/24     # sweep the range for port 9741
lgsb2mqtt -a auto --discover-address 172.16.20.0/24 -u mqtt://broker

A soundbar found that way has no Chromecast labels — nothing answered the browse — just its address and the open port:

172.16.20.180  [control]  (sweep)

--discover-timeout (default 5 s) is how long the scan listens — enough on the local link, too short through a reflector.

The scanning itself lives in mqtt-interfaces-core; this adapter only declares the browse and the port (lib/discovery.js).

Topics

<name> defaults to soundbar.

<name>/connected

Retained. 0 = not connected to the broker (set via last will), 1 = connected to the broker but not to the soundbar, 2 = connected to both.

<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 soundbar address. <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 connect and whenever the soundbar reports a change (also when changed via remote or app). 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 | | --------------------------------------------------------------------------------- | ----------- | --- | ---------------------------------------------------------------------- | | volume | int | yes | range in volume/min, volume/max | | mute | bool | yes | | | input | string | yes | e.g. E-ARC, HDMI, Bluetooth, Wifi, Optical/HDMI ARC | | input_list | string[] | | inputs available on this model | | eq | string | yes | sound mode, e.g. AI Sound Pro, Standard, Cinema, Music, Game | | eq_list | string[] | | sound modes available on this model | | bass, treble | int | yes | | | woofer, rear_level, top_level, center_level, side_level, dialog_level | int | yes | in dB, range in <item>/min, <item>/max (channels depend on model) | | night_mode, auto_volume, drc, auto_power, tv_remote, neuralx, rear | bool | yes | | | av_sync | int | yes | ms | | power | bool | | the soundbar is unreachable while off — see connected | | audio_source | string | | e.g. DOLBY AUDIO | | play/state, play/position, play/duration, play/stream_type | int | | play/position is not retained | | play/title, play/artist, play/album | string | | only while streaming | | name, model, firmware, uuid, ip, bluetooth_name, update_available | string/bool | | device info |

Sound modes or inputs without a known name are published as unknown_<n> and can be set using that form. Verified on a DS90QY; other models may lack some items.

<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; input and eq accept a name (case-insensitive) or the numeric protocol index. Numeric values are checked against the known range.

mosquitto_pub -t soundbar/set/volume -m 12
mosquitto_pub -t soundbar/set/mute -m true
mosquitto_pub -t soundbar/set/input -m HDMI
mosquitto_pub -t soundbar/set/eq -m "Cinema"
mosquitto_pub -t soundbar/set/woofer -m -3
mosquitto_pub -t soundbar/set/night_mode -m on

Raw protocol topics

With --publish-raw every protocol field is additionally published as <name>/status/<MSG>/<key> (e.g. soundbar/status/SPK_LIST_VIEW_INFO/i_vol), useful to explore what your model supports. With --raw-set raw sets are accepted: soundbar/set/SPK_LIST_VIEW_INFO/i_vol 12 (off by default — it is unrestricted device access). scripts/dump.js <address> prints every known info message of a device.

Home Assistant

MQTT discovery is on by default (HA ≥ 2024.11, device-based discovery). After connecting, the soundbar appears as one device with entities: volume (number), mute and settings (switches: night mode, auto volume, DRC, auto power, TV remote, Neural:X, rear speakers), input and sound mode (selects, options from what your model reports), speaker levels (numbers in dB), power (binary sensor), audio source / firmware / IP (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 1.x

lgsb2mqtt 2.0 moves to mqtt-interfaces-core. Friendly items and topics are unchanged; what changed:

| 1.x | 2.0 | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | plain status payloads, --json-payloads opt-in | {val, ts, lc} JSON by default; --no-json-payloads for plain values | | raw sets <name>/set/<MSG>/<key> always accepted | opt-in with --raw-set | | — | <name>/info, <name>/maintenance/set/{loglevel,restart}, --config-schema, --port | | /etc/lgsb2mqtt/<name>.env + own template unit | same file; the template unit is rewritten by --install (shared broker.env, state directory /var/lib/lgsb2mqtt/<name>, Restart=always) — run sudo lgsb2mqtt --install --name <n> … once per instance | | Node >= 20 | Node ^20.19, ^22.12 or >= 24 |

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

License

MIT © Sebastian Raff