Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions .github/workflows/PR_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,38 @@ jobs:
with:
name: python-package
path: python/dist/*

nodejs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
- name: Set up Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
version: 3.1.61
actions-cache-folder: emsdk-cache
- name: Build WebAssembly module
working-directory: nodejs/theengs-decoder
run: |
npm install --ignore-scripts
npm run build
- name: Run base package tests
working-directory: nodejs/theengs-decoder
run: npm test
- name: Run Node-RED wrapper tests
working-directory: nodejs/node-red-contrib-theengs-decoder
run: |
# The wrapper's only dependency is the locally-built base package,
# which isn't on npm yet at PR time, so install it directly.
npm install --no-save ../theengs-decoder
npm test
- uses: actions/upload-artifact@v4
with:
name: nodejs-wasm
path: nodejs/theengs-decoder/dist/*
81 changes: 81 additions & 0 deletions .github/workflows/publish_npm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Publish to npm

on:
workflow_dispatch:
inputs:
version:
description: 'Version to publish (e.g. 2.2.0). Defaults to the release tag.'
required: false
release:
types: [published]

jobs:
publish:
name: Build and publish Node packages to npm
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'

- name: Set up Emscripten
uses: mymindstorm/setup-emsdk@v14
with:
version: 3.1.61
actions-cache-folder: emsdk-cache

- name: Resolve publish version
id: version
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
REF_NAME: ${{ github.ref_name }}
run: |
if [ -n "$INPUT_VERSION" ]; then
VERSION="$INPUT_VERSION"
else
VERSION="${REF_NAME#v}"
fi
echo "Publishing version: $VERSION"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"

- name: Stamp package versions
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
cd nodejs/theengs-decoder
npm version "$VERSION" --no-git-tag-version --allow-same-version
cd ../node-red-contrib-theengs-decoder
npm version "$VERSION" --no-git-tag-version --allow-same-version
npm pkg set "dependencies.theengs-decoder=$VERSION"

- name: Build WebAssembly module
working-directory: nodejs/theengs-decoder
run: |
npm install --ignore-scripts
npm run build

- name: Run base package tests
working-directory: nodejs/theengs-decoder
run: npm test

- name: Publish theengs-decoder to npm
working-directory: nodejs/theengs-decoder
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --provenance --access public

- name: Publish node-red-contrib-theengs-decoder to npm
working-directory: nodejs/node-red-contrib-theengs-decoder
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --provenance --access public
21 changes: 21 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,27 @@ if(SKBUILD)
target_compile_features(_decoder PRIVATE cxx_std_11)
install(TARGETS _decoder LIBRARY DESTINATION TheengsDecoder)

elseif(BUILD_WASM)
message(STATUS "Building WebAssembly module via Emscripten")

add_executable(theengs_decoder_wasm
nodejs/theengs-decoder/src/wasm_bindings.cc
src/decoder.cpp
)

target_include_directories(theengs_decoder_wasm
PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src/arduino_json/src
${CMAKE_CURRENT_SOURCE_DIR}/src
)

target_compile_features(theengs_decoder_wasm PRIVATE cxx_std_17)

set_target_properties(theengs_decoder_wasm PROPERTIES
SUFFIX ".js"
LINK_FLAGS "-lembind -sMODULARIZE=1 -sEXPORT_NAME=createTheengsDecoderModule -sENVIRONMENT=node -sALLOW_MEMORY_GROWTH=1 -sSINGLE_FILE=1 -sNODEJS_CATCH_EXIT=0 -sNODEJS_CATCH_REJECTION=0"
)

else()

add_library(decoder
Expand Down
4 changes: 3 additions & 1 deletion docs/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ export default defineConfig({
items: [
{ text: 'Using the library in a project', link: '/use/include' },
{ text: 'Using with ESP32', link: '/use/ESP32' },
{ text: 'Using with Python', link: '/use/python' }
{ text: 'Using with Python', link: '/use/python' },
{ text: 'Using with Node.js', link: '/use/nodejs' },
{ text: 'Using with Node-RED', link: '/use/node-red' }
]
},
{
Expand Down
84 changes: 84 additions & 0 deletions docs/use/node-red.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Using with Node-RED

The `node-red-contrib-theengs-decoder` package adds a **theengs decode**
node to the Node-RED palette. Wire it after any node that produces BLE
advertisement messages — for example an MQTT input from a Theengs Gateway,
or a BLE-scanner node — and it will replace the raw advertisement on
`msg.payload` with the decoded device information.

The decoder runs as WebAssembly, so installation does not require a C++
toolchain on the host running Node-RED.

## Installing

From inside Node-RED, open the palette manager (☰ → Manage palette →
Install) and search for `node-red-contrib-theengs-decoder`. Click
**Install**.

Or from the command line in your Node-RED user directory (typically
`~/.node-red`):

```sh
npm install node-red-contrib-theengs-decoder
```

Then restart Node-RED.

## Wiring it up

The node accepts `msg.payload` as either an object or a JSON string with
at least one of:

- `servicedata` — hex string of the BLE service data
- `manufacturerdata` — hex string of the BLE manufacturer data
- `id` — device identifier (MAC), optional, used by some decoders
- `name` — advertised local name, optional, used by some decoders

A typical flow looks like:

```
[ MQTT in (BTtoMQTT/+) ] → [ theengs decode ] → [ debug ]
```

When a decoder matches, `msg.payload` becomes an object with `brand`,
`model`, `model_id`, and any device-specific readings (`tempc`, `hum`,
`batt`, …).

## Configuration

- **Name** — optional label shown in the editor.
- **Pass through on no match** — when checked (default), advertisements
that no decoder recognizes are forwarded unchanged so the rest of your
flow can still see them. When unchecked, unmatched messages are
dropped.

## Example flow

The package ships an importable example flow at
[`examples/decode-flow.json`](https://github.com/theengs/decoder/blob/development/nodejs/node-red-contrib-theengs-decoder/examples/decode-flow.json).
In Node-RED: ☰ → Import → paste the JSON → **Import**. Deploy, click the
inject node, and the debug pane should show the decoded miflora reading.

## Building a development version

The Node-RED node depends on the
[`theengs-decoder`](./nodejs) base package. To work on both from a
checkout, build the base package locally first:

```sh
git clone --recursive https://github.com/theengs/decoder.git
cd decoder/nodejs/theengs-decoder
npm install
npm run build

cd ../node-red-contrib-theengs-decoder
npm install
npm install --no-save ../theengs-decoder
```

Then point Node-RED at the local checkout:

```sh
cd ~/.node-red
npm install /path/to/decoder/nodejs/node-red-contrib-theengs-decoder
```
78 changes: 78 additions & 0 deletions docs/use/nodejs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# Using with Node.js

The `theengs-decoder` package is a Node.js binding for the Theengs Decoder
library. It runs as WebAssembly, so installation does not require a C++
toolchain on the user's machine.

## Installing from npm

```sh
npm install theengs-decoder
```

Requires Node.js 18 or newer.

## Using

```js
const { decodeBLE, getProperties, getAttribute } = require('theengs-decoder');

const decoded = await decodeBLE({
servicedata: '71205d0183d20c6d8d7cc40d08100103',
});
// → { brand: 'Xiaomi', model: 'RoPot', model_id: 'HHCCPOT002', moi: 3, mac: 'C4:7C:8D:6D:0C:D2', ... }
```

If the decoder doesn't recognize the advertisement, `decodeBLE` returns
`null`. The input may be either an object or a JSON string — both work.

The package also exposes `getProperties(model_id)` and
`getAttribute(model_id, attribute)`, mirroring the Python binding:

```js
const props = await getProperties('HHCCPOT002');
// → { properties: { moi: { unit: '%', name: 'moisture' }, ... } }

const brand = await getAttribute('HHCCPOT002', 'brand');
// → 'Xiaomi'
```

These are useful for forwarding decoded values to Home Assistant or other
automation systems with the right unit and naming metadata.

## Pre-loading the WebAssembly module

The first call to any of the three functions implicitly loads the
WebAssembly module. To pay that cost at startup instead of on the first
hot-path call, await `ready()` once:

```js
const { ready, decodeBLE } = require('theengs-decoder');

await ready();
// subsequent decodeBLE() calls don't pay the load cost
```

## Building a development version

Building the module from source requires an
[Emscripten](https://emscripten.org/) toolchain (`emcc`, `emcmake`,
`emmake`).

```sh
git clone --recursive https://github.com/theengs/decoder.git
cd decoder/nodejs/theengs-decoder
npm install
npm run build
npm test
```

## Methods

- `ready()` — Resolves once the WebAssembly module is loaded. Optional.
- `decodeBLE(input)` — Returns the decoded object, or `null` if no
decoder matched. Input can be an object or a JSON string.
- `getProperties(modelId)` — Returns the properties object for a known
model id, or `null`.
- `getAttribute(modelId, attribute)` — Returns the attribute value as a
string, or `null`.
2 changes: 2 additions & 0 deletions nodejs/node-red-contrib-theengs-decoder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules/
package-lock.json
30 changes: 30 additions & 0 deletions nodejs/node-red-contrib-theengs-decoder/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# node-red-contrib-theengs-decoder

A Node-RED node that decodes BLE advertisement messages using
[Theengs Decoder](https://decoder.theengs.io/).

## Install

From the Node-RED palette manager, search for
`node-red-contrib-theengs-decoder` and click install. Or, from the
command line in your Node-RED user directory (typically `~/.node-red`):

```sh
npm install node-red-contrib-theengs-decoder
```

The decoder is shipped as WebAssembly — no native toolchain is required.

## Usage

Wire any BLE-scanner node (or an MQTT input from a Theengs Gateway) into
the **theengs decode** node. The input `msg.payload` should be an object
with at least one of `servicedata` or `manufacturerdata` (hex string).
The output `msg.payload` will be the decoded device information.

An importable example flow is included under
[`examples/`](https://github.com/theengs/decoder/tree/development/nodejs/node-red-contrib-theengs-decoder/examples).

## License

GPL-3.0-only — same as the underlying Theengs Decoder.
Loading
Loading