
Security News
GitHub Actions Adds cache-mode to Limit Cache Poisoning Risk
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.
bluetti-mqtt-node
Advanced tools
Windows-first TypeScript Bluetti BLE to MQTT bridge with MQTT TLS, structured logging, native Windows BLE helper, and data-driven device registry
Windows-first TypeScript port of the Python bluetti_mqtt library.
This project connects to Bluetti power stations over Bluetooth Low Energy, polls device state through the Bluetti MODBUS-over-BLE protocol, and publishes that state to MQTT.
The repository is designed around Windows as the primary runtime. BLE access is handled by a small .NET helper using native Windows Bluetooth APIs, while the protocol logic, parsing, polling, and MQTT bridge live in TypeScript.
c8 coverage reporting and a single npm run validate CI contractIncluded:
Explicitly out of scope:
The TypeScript registry currently supports the same models ported from the Python library:
The project is split into a few clear layers:
src/core
src/devices
src/bluetooth
src/app
src/broker
src/cli
helper/BluettiMqtt.BluetoothHelper
Node-native BLE libraries on Windows are workable in some environments, but they tend to be more fragile than the native Windows Bluetooth stack and often require native addon toolchains. This project uses a small .NET helper process instead, and the old experimental noble path has been removed from the supported runtime surface.
That gives us:
node-gyp and Windows BLE adapter quirksThe helper communicates with Node over line-delimited JSON on stdio.
For local development with a reachable broker and a known Bluetti BLE address:
npm ci
npm run build
npm run helper:build
npm run bluetti-mqtt -- --broker mqtt://127.0.0.1:1883 --once 24:4C:AB:2C:24:8E
For a packaged/runtime-style setup, publish the helper first so the CLI can
resolve artifacts/helper/win-x64/BluettiMqtt.BluetoothHelper.exe without
falling back to dotnet run:
npm run helper:publish
npm run bluetti-mqtt -- --broker mqtt://127.0.0.1:1883 --interval 5 24:4C:AB:2C:24:8E
Runtime requirements:
Development requirements:
Install Node dependencies:
npm ci
Build the TypeScript project:
npm run build
Build the Windows helper:
dotnet build helper\BluettiMqtt.BluetoothHelper\BluettiMqtt.BluetoothHelper.csproj
Publish a self-contained Windows helper for distribution:
npm run helper:publish
Publish a smaller framework-dependent helper instead:
npm run helper:publish:portable
Run the full local validation suite:
npm run validate
npm run validate is the same command used by CI. It runs TypeScript
typechecking, Biome linting, the test suite, c8 coverage, and the Windows
helper build.
If this package is installed as a CLI package, the declared executable names are:
bluetti-mqtt-nodebluetti-mqtt-node-discoverybluetti-mqtt-node-loggerbluetti-mqtt-node-pollbluetti-mqtt-node-probeThe repo-local npm run ... scripts remain the easiest way to use the commands during development.
The Node runtime resolves the Windows helper in this order:
BLUETTI_HELPER_PATH if you set itartifacts/helper/win-x64/BluettiMqtt.BluetoothHelper.exedotnet run --project helper/BluettiMqtt.BluetoothHelper/BluettiMqtt.BluetoothHelper.csprojThat means:
Example override:
$env:BLUETTI_HELPER_PATH = "C:\tools\BluettiMqtt.BluetoothHelper.exe"
If you publish the framework-dependent helper, point BLUETTI_HELPER_PATH at artifacts/helper/win-x64-fdd/BluettiMqtt.BluetoothHelper.exe.
npm run bluetti-discovery
This scans nearby BLE devices through the Windows helper and prints discovered devices as JSON.
Use --help with any CLI to print its usage text.
npm run probe -- <BLUETOOTH_MAC>
This:
ReadHoldingRegisters(10, 40)npm run poll -- <BLUETOOTH_MAC>
This runs the device's pollingCommands set and prints:
npm run bluetti-logger -- <BLUETOOTH_MAC>
This runs the broader loggingCommands set for the device and prints the parsed output.
npm run bluetti-mqtt -- --broker mqtt://127.0.0.1:1883 --once <BLUETOOTH_MAC>
This performs one poll/publish cycle, publishes MQTT state topics, and exits.
npm run bluetti-mqtt -- --broker mqtt://127.0.0.1:1883 --interval 5 <BLUETOOTH_MAC>
Supported flags:
--broker <mqtt-url>--config <path>--username <username>--password <password>--mqtt-ca <path>--mqtt-cert <path>--mqtt-key <path>--mqtt-servername <name>--mqtt-insecure--interval <seconds>--log-level <level>--onceExample:
npm run bluetti-mqtt -- --broker mqtt://127.0.0.1:1883 --interval 5 24:4C:AB:2C:24:8E
Config-file example:
npm run bluetti-mqtt -- --config .\config.example.json
The config file is JSON and supports:
brokerusernamepasswordtls.caPathtls.certPathtls.keyPathtls.servernametls.rejectUnauthorizedintervaloncelogLeveladdressesCLI flags override config-file values when both are provided.
Use an mqtts:// broker URL with TLS options when the broker requires a custom
trust chain, client certificate authentication, or a specific TLS server name.
CA-only example:
npm run bluetti-mqtt -- `
--broker mqtts://broker.local:8883 `
--mqtt-ca .\certs\ca.pem `
--interval 5 `
24:4C:AB:2C:24:8E
Mutual TLS example:
npm run bluetti-mqtt -- `
--broker mqtts://broker.local:8883 `
--mqtt-ca .\certs\ca.pem `
--mqtt-cert .\certs\client.pem `
--mqtt-key .\certs\client-key.pem `
24:4C:AB:2C:24:8E
For local labs with self-signed certificates, --mqtt-insecure sets
rejectUnauthorized to false. Prefer a CA file for normal use.
Config-file TLS fields live under tls:
{
"broker": "mqtts://broker.local:8883",
"tls": {
"caPath": "./certs/ca.pem",
"certPath": "./certs/client.pem",
"keyPath": "./certs/client-key.pem",
"servername": "broker.local",
"rejectUnauthorized": true
},
"addresses": ["24:4C:AB:2C:24:8E"]
}
State topics:
bluetti/state/<MODEL>-<SERIAL>/<FIELD>
Examples:
bluetti/state/AC500-2237000003358/ac_input_power
bluetti/state/AC500-2237000003358/total_battery_percent
Each parser message also publishes a raw JSON snapshot to:
bluetti/state/<MODEL>-<SERIAL>/_raw
Command topics:
bluetti/command/<MODEL>-<SERIAL>/<FIELD>
Payload expectations:
ON / OFFThis project has been validated live on this machine against:
24:4C:AB:2C:24:8ESuccessful live validation includes:
probepollUseful commands:
npm run typecheck
npm run lint
npm test
npm run coverage
npm run build
npm run helper:publish
npm run helper:publish:portable
npm run pack:dry-run
npm run validate
dotnet build helper\BluettiMqtt.BluetoothHelper\BluettiMqtt.BluetoothHelper.csproj
GitHub Actions validates:
npm run validate on Node.js 22.x, 24.x, and 26.xAdditional development docs:
For npm packaging, prepack builds the TypeScript output and publishes a self-contained Windows helper into artifacts/helper/win-x64.
That gives the package a better installation story than requiring every user to run the helper from source. A typical release flow is:
npm run validate
npm run pack:dry-run
npm publish
If you are distributing outside npm, the simplest layout is:
dist/artifacts/helper/win-x64/BluettiMqtt.BluetoothHelper.exeREADME.mdThe CLI will automatically use the published helper artifact when it exists.
There are two practical helper distribution modes:
On the current implementation, the framework-dependent helper is roughly one quarter the size of the self-contained helper.
FAQs
TypeScript Bluetti BLE to MQTT bridge with MQTT TLS, structured logging, a native Windows BLE helper, simulated devices for hardware-free testing, and a data-driven device registry
The npm package bluetti-mqtt-node receives a total of 7 weekly downloads. As such, bluetti-mqtt-node popularity was classified as not popular.
We found that bluetti-mqtt-node demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.

Security News
GitHub Actions now supports cache-mode, a least-privilege control on the Actions cache aimed at the cache poisoning technique behind recent compromises.

Company News
Allow myself to introduce... myself.

Research
/Security News
A Twitch browser extension on Chrome and Firefox forwards users’ live OAuth session tokens through proxies controlled by a Russian bot service.