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.
- Windows-native BLE transport
- TypeScript implementation of Bluetti MODBUS command framing and CRC handling
- Typed field parsing for supported Bluetti devices
- MQTT state publishing
- MQTT command-topic ingestion for writable fields
- Live polling, logging, and probe CLIs
Included:
- Windows runtime
- MQTT bridge
- BLE polling and command dispatch
Explicitly out of scope:
- Home Assistant discovery/config publishing
- Linux-first or macOS-first BLE support
The TypeScript registry currently supports the same models ported from the Python library:
- AC200M
- AC300
- AC500
- AC60
- EB3A
- EP500
- EP500P
- EP600
The project is split into a few clear layers:
src/core- MODBUS commands, CRC, shared types, event bus
src/devices- device models and typed field parsing
src/bluetooth- Windows-helper transport abstraction, session state machine, helper client, mock transport
src/app- polling orchestration and runtime wiring
src/mqtt- MQTT publishing and command-topic handling
src/cli- user-facing command entrypoints
helper/BluettiMqtt.BluetoothHelper- Windows-native BLE helper implemented in .NET / WinRT
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:
- native Windows BLE APIs for scan/connect/GATT operations
- a simpler Node runtime surface
- a cleaner separation between transport concerns and protocol logic
- fewer issues with
node-gypand Windows BLE adapter quirks
The helper communicates with Node over line-delimited JSON on stdio.
Runtime requirements:
- Windows
- Node.js 22+ recommended
- npm
- .NET SDK 6.0+ if you are building the Windows helper from source
- Bluetooth adapter supported by Windows BLE APIs
- MQTT broker reachable from the machine running this project
Development requirements:
- Docker optional, but useful for spinning up a local MQTT broker for testing
Install Node dependencies:
npm installBuild the TypeScript project:
npm run buildBuild the Windows helper:
dotnet build helper\BluettiMqtt.BluetoothHelper\BluettiMqtt.BluetoothHelper.csprojPublish a self-contained Windows helper for distribution:
npm run helper:publishPublish a smaller framework-dependent helper instead:
npm run helper:publish:portableRun the full local validation suite:
npm run validateIf 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-probe
The 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_PATHif you set it- published helper artifact at
artifacts/helper/win-x64/BluettiMqtt.BluetoothHelper.exe - source fallback through
dotnet run --project helper/BluettiMqtt.BluetoothHelper/BluettiMqtt.BluetoothHelper.csproj
That means:
- contributors can work directly from source without changing anything
- release builds can ship a published helper executable
- end users can override the helper path explicitly when needed
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-discoveryThis 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:
- connects to the device
- reads the device name characteristic
- executes
ReadHoldingRegisters(10, 40) - parses and prints the result
npm run poll -- <BLUETOOTH_MAC>This runs the device's pollingCommands set and prints:
- per-command responses
- parsed output for each command
- a merged view of the parsed state
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>--interval <seconds>--log-level <level>--once
Example:
npm run bluetti-mqtt -- --broker mqtt://127.0.0.1:1883 --interval 5 24:4C:AB:2C:24:8EConfig-file example:
npm run bluetti-mqtt -- --config .\config.example.jsonThe config file is JSON and supports:
brokerusernamepasswordintervaloncelogLeveladdresses
CLI flags override config-file values when both are provided.
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:
- booleans:
ON/OFF - integer fields: numeric string
- enum fields: enum name string
This project has been validated live on this machine against:
- AC500
24:4C:AB:2C:24:8E
Successful live validation includes:
- BLE scan
- connect + device-name read
- characteristic read/write/subscribe path
probepoll- one-shot MQTT publish to a local broker
- Windows is the only runtime this repository is currently designed for.
- The Windows helper is the only supported BLE runtime path in this repository.
- MQTT command handling is implemented, but broader live validation of writable fields is still lighter than read-path validation.
Useful commands:
npm run typecheck
npm test
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.csprojGitHub Actions validates:
npm run typechecknpm testnpm run helper:build
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 publishIf you are distributing outside npm, the simplest layout is:
dist/artifacts/helper/win-x64/BluettiMqtt.BluetoothHelper.exeREADME.md
The CLI will automatically use the published helper artifact when it exists.
There are two practical helper distribution modes:
- self-contained single-file publish
- biggest artifact
- no separate .NET runtime install required on the target machine
- this is the default npm package artifact
- framework-dependent single-file publish
- much smaller artifact
- requires a compatible .NET runtime on the target machine
- useful when you control the installation environment
On the current implementation, the framework-dependent helper is roughly one quarter the size of the self-contained helper.