-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #129 from JeremyYu-cn/universal_BLU_to_MQTT_script
feature: add a universal-blu-to-mqtt script example
- Loading branch information
Showing
3 changed files
with
191 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
/** | ||
* Universal BLU to MQTT Script | ||
Short description | ||
- This script is about shares any BLU product's complete payload to MQTT. | ||
- The script should not require an individual BLU device's MAC address, rather it will "listen" for any BLU device. | ||
- It would use the MAC address to create the topic, with a key-value for each data point in the payload. | ||
Running Process | ||
Here is the summary of how it works: | ||
- Scanning: The script will open a BLU passive scan to receive any BLU device data. Using the BLE.Scanner.Start and BLE.Scanner.Subscribe modules. | ||
- Data Extraction: For each device found, it checks for service data associated with the BTHome standard (Service ID: fcd2). | ||
- Decoding: The BTHomeDecoder decodes the service data into human-readable format, including details such as temperature, battery, etc. | ||
- MQTT Push: The MQTT uses each device MAC address as a topic, then publishes device service_data or any other data you want to the MQTT. | ||
*/ | ||
|
||
|
||
// *********************** Decoding Method *********************** | ||
const uint8 = 0; | ||
const int8 = 1; | ||
const uint16 = 2; | ||
const int16 = 3; | ||
const uint24 = 4; | ||
const int24 = 5; | ||
|
||
// The BTH object defines the structure of the BTHome data | ||
const BTH = { | ||
0x00: { n: "pid", t: uint8 }, | ||
0x01: { n: "battery", t: uint8, u: "%" }, | ||
0x02: { n: "temperature", t: int16, f: 0.01, u: "tC" }, | ||
0x03: { n: "humidity", t: uint16, f: 0.01, u: "%" }, | ||
0x05: { n: "illuminance", t: uint24, f: 0.01 }, | ||
0x21: { n: "motion", t: uint8 }, | ||
0x2d: { n: "window", t: uint8 }, | ||
0x3a: { n: "button", t: uint8 }, | ||
0x3f: { n: "rotation", t: int16, f: 0.1 }, | ||
}; | ||
|
||
function getByteSize(type) { | ||
if (type === uint8 || type === int8) return 1; | ||
if (type === uint16 || type === int16) return 2; | ||
if (type === uint24 || type === int24) return 3; | ||
//impossible as advertisements are much smaller; | ||
return 255; | ||
} | ||
|
||
// functions for decoding and unpacking the service data from Shelly BLU devices | ||
const BTHomeDecoder = { | ||
utoi: function (num, bitsz) { | ||
let mask = 1 << (bitsz - 1); | ||
return num & mask ? num - (1 << bitsz) : num; | ||
}, | ||
getUInt8: function (buffer) { | ||
return buffer.at(0); | ||
}, | ||
getInt8: function (buffer) { | ||
return this.utoi(this.getUInt8(buffer), 8); | ||
}, | ||
getUInt16LE: function (buffer) { | ||
return 0xffff & ((buffer.at(1) << 8) | buffer.at(0)); | ||
}, | ||
getInt16LE: function (buffer) { | ||
return this.utoi(this.getUInt16LE(buffer), 16); | ||
}, | ||
getUInt24LE: function (buffer) { | ||
return ( | ||
0x00ffffff & ((buffer.at(2) << 16) | (buffer.at(1) << 8) | buffer.at(0)) | ||
); | ||
}, | ||
getInt24LE: function (buffer) { | ||
return this.utoi(this.getUInt24LE(buffer), 24); | ||
}, | ||
getBufValue: function (type, buffer) { | ||
if (buffer.length < getByteSize(type)) return null; | ||
let res = null; | ||
if (type === uint8) res = this.getUInt8(buffer); | ||
if (type === int8) res = this.getInt8(buffer); | ||
if (type === uint16) res = this.getUInt16LE(buffer); | ||
if (type === int16) res = this.getInt16LE(buffer); | ||
if (type === uint24) res = this.getUInt24LE(buffer); | ||
if (type === int24) res = this.getInt24LE(buffer); | ||
return res; | ||
}, | ||
|
||
// Unpacks the service data buffer from a Shelly BLU device | ||
unpack: function (buffer) { | ||
//beacons might not provide BTH service data | ||
if (typeof buffer !== "string" || buffer.length === 0) return null; | ||
let result = {}; | ||
let _dib = buffer.at(0); | ||
result["encryption"] = _dib & 0x1 ? true : false; | ||
result["BTHome_version"] = _dib >> 5; | ||
if (result["BTHome_version"] !== 2) return null; | ||
//can not handle encrypted data | ||
if (result["encryption"]) return result; | ||
buffer = buffer.slice(1); | ||
|
||
let _bth; | ||
let _value; | ||
while (buffer.length > 0) { | ||
_bth = BTH[buffer.at(0)]; | ||
if (typeof _bth === "undefined") { | ||
// logger("unknown type", "BTH"); | ||
break; | ||
} | ||
buffer = buffer.slice(1); | ||
_value = this.getBufValue(_bth.t, buffer); | ||
if (_value === null) break; | ||
if (typeof _bth.f !== "undefined") _value = _value * _bth.f; | ||
result[_bth.n] = _value; | ||
buffer = buffer.slice(getByteSize(_bth.t)); | ||
} | ||
return result; | ||
}, | ||
}; | ||
|
||
// *********************** Main Methods *********************** | ||
|
||
const BTHOME_SVC_ID_STR = "fcd2"; | ||
|
||
// Bluetooth scan options | ||
const SCAN_OPTION = { | ||
duration_ms: BLE.Scanner.INFINITE_SCAN, // Scan duration | ||
active: false, | ||
} | ||
|
||
// Push BLE devices to MQTT | ||
function pushToMQ(addr, message) { | ||
if (!MQTT.isConnected()) return false; // Check the MQTT status | ||
|
||
MQTT.publish(addr, message); | ||
|
||
return true | ||
} | ||
|
||
// BLE scan callback | ||
function scanCB(ev, res) { | ||
if (ev !== BLE.Scanner.SCAN_RESULT) return; | ||
const addr = res.addr; // Get devive's MAC address | ||
if (typeof res.service_data === 'undefined' || typeof res.service_data[BTHOME_SVC_ID_STR] === 'undefined') return; | ||
if (typeof addr === 'undefined') return; // If the device addredd is empty, return | ||
|
||
try { | ||
const decodeData = BTHomeDecoder.unpack(res.service_data[BTHOME_SVC_ID_STR]); // Decode service data | ||
|
||
// Combine data | ||
const postMessage = { | ||
addr: addr, | ||
rssi: res.rssi, | ||
local_name: res.local_name || "", | ||
service_data: decodeData, | ||
}; | ||
|
||
// console.log(res.local_name, JSON.stringify(postMessage)); | ||
|
||
// post data to MQTT | ||
pushToMQ(addr, JSON.stringify(postMessage)); | ||
|
||
} catch(err) { | ||
console.log(err) | ||
} | ||
|
||
} | ||
|
||
// Start Scan BLE Devices | ||
function startBLEScan() { | ||
if (!BLE.Scanner.isRunning()) { | ||
BLE.Scanner.Start(SCAN_OPTION, scanCB); | ||
} else { | ||
// If scanner is running, create a subscribe method | ||
BLE.Scanner.Subscribe(scanCB); | ||
} | ||
} | ||
|
||
startBLEScan() |