Moin Kai,
genau, es werden die MQTT Parameter des Shellies (Gateway) genutzt, um auch diese Daten weiterzuleiten.
- „info/windowStateID“ ist der Zustand des Sensors (offen/geschlossen)
- „info/device“ habe ich mal rausgenommen und das im MainTopic besser umgesetzt
Nun sieht das Ergebnis bei mir so aus, die MAC ist natürlich die des BT-Devices:
Ich habe versucht mich an dem Schema von Shelly zu orientierten, sollte es von Dir anders gewünscht sein (Du hattest es etwas anders beschrieben) kann ich das gerne auch noch anpassen.
Viele Grüße
Dennis
– Aktuelles Script:
/* ============================= Config CHANGE HERE =============================== */
let mqttID = "shelly"; //MQTT Topic ID, (behind topicPrefix)
let sendMQTT = true; //send Button Data also per MQTT, true/false
let topicPrefix = ""; //Here you can set your own mqtt Topic Prefix
let vSwitch = false; // create MQTT virtual Switch states, true/false
let debug = false; //show debug msg in log, true/false, warning setting this to 'true' will delay reaktion speed a lot
/* ============================== STOP CHANGING HERE =============================== */
//========= Skript Setup =========
let activeScan = true; //active or passiv Bluetooth Scan, true/false
let _cid = "0ba9"; //Allterco, Company ID(MFD)
let uuid = "fcd2"; //BTHome, Service ID
let devID1 = "SBBT"; // BluButton1, deviceID, --> SBBT-002C evt. 002C = charge?
let devID2 = "SBDW"; // Blu Door/Window, deviceID
let bthObjectIDs = []; //Used BTHome Service.Payload ObjectID List for BluButton1,(here you can find a full List --> " https://bthome.io/format ")
bthObjectIDs[0x00] = { key: 'pid', dataType: 'uint8' };
bthObjectIDs[0x01] = { key: 'battery', dataType: 'uint8', unit: '%' };
bthObjectIDs[0x3a] = { key: 'buttonID', dataType: 'uint8' };
bthObjectIDs[0x05] = { key: 'illuminance', dataType: 'uint24', factor: 0.01 };
bthObjectIDs[0x1a] = { key: 'doorStateID', dataType: 'uint8' };
bthObjectIDs[0x20] = { key: 'moistureLvl', dataType: 'uint8' };
bthObjectIDs[0x2d] = { key: 'windowStateID', dataType: 'uint8' };
bthObjectIDs[0x3f] = { key: 'rotationLvl', dataType: 'int16', factor: 0.1 };
let vSwitchCache = {}, last_packet_id = 0x100, last_addr = 0, scanDuration = BLE.Scanner.INFINITE_SCAN;
//=========== End Setup ===========
function SendMQTTmsg(mac,event){
mac = mac.toUpperCase();
mac = mac.substr(0, 2) + mac.substr(3, 2) + mac.substr(6, 2) + mac.substr(9, 2) + mac.substr(12, 2) + mac.substr(15, 2);
if(MQTT.isConnected()){
let mainTopic = mqttID + event.info.data.device.toLowerCase() + '-'+ mac + '/', inputID = JSON.stringify(event.info.data.inputID);
if(topicPrefix.length !== 0) mainTopic = topicPrefix +'/'+ mqttID +'-'+ mac + '/';
if(inputID !== '0' && inputID !== '254' && vSwitch){ // do not trigger virtual switch logik for beacon or hold events.
if(vSwitchCache['s'+inputID] === undefined || vSwitchCache['s'+inputID] === 'false'){
vSwitchCache['s'+inputID] = 'true';
}else{
vSwitchCache['s'+inputID] = 'false';
}
}
if(debug) print('MQTT Publishing: _Topic: ', mainTopic, 'events/rpc _Payload: ', JSON.stringify(event));
MQTT.publish(mainTopic + 'events/rpc', JSON.stringify(event), 1, false);
//MQTT.publish(mainTopic + 'status/input:' + inputID, JSON.stringify(event.info.ts), 1, false);
if(inputID !== '0' && inputID !== '254' && vSwitch) MQTT.publish(mainTopic + 'status/switch:' + inputID, vSwitchCache['s'+inputID], 1, false);
MQTT.publish(mainTopic + 'info/battery', JSON.stringify(event.info.data.battery_value), 1, true);
MQTT.publish(mainTopic + 'info/rssi', JSON.stringify(event.info.data.rssi), 1, true);
MQTT.publish(mainTopic + 'info/lastTimeStamp', JSON.stringify(event.info.ts), 1, true);
MQTT.publish(mainTopic + 'info/lastAction', event.info.event, 1, true);
//MQTT.publish(mainTopic + 'info/lastAktionID', inputID, 1, true);
//MQTT.publish(mainTopic + 'info/mac', event.info.data.mac, 1, true);
//MQTT.publish(mainTopic + 'info/device', event.info.data.device, 1, true);
//MQTT.publish(mainTopic + 'info/encryption', JSON.stringify(event.info.data.encryption), 1, true);
MQTT.publish(mainTopic + 'info/gateway', event.info.data.gateway, 1, true);
MQTT.publish(mainTopic + 'info/windowStateID', JSON.stringify(event.info.data.windowStateID), 1, true);
MQTT.publish(mainTopic + 'info/rotationLvl', JSON.stringify(event.info.data.rotationLvl), 1, true);
MQTT.publish(mainTopic + 'info/illuminance', JSON.stringify(event.info.data.illuminance), 1, true);
}else{
print('Error: MQTT is still not ready, cant send msg');
}
}
function FilterEvents(obj){
if(obj.info === undefined || obj.info.data === undefined || obj.info.data.generation !== 'BLU') return;
SendMQTTmsg(obj.info.data.mac,obj);
}
let BTHomeDecoder = {
UInt_To_Int: function (uint, bitsz) {
let mask = 1 << (bitsz - 1); //create mask to filter last bit
if (uint & mask) {
return uint - (1 << bitsz); //convert uint to negative int
} else {
return uint; //uint is positive, no change needed.
}
},
GetMaxBytes: function (dataType) {
if (dataType === 'uint8' || dataType === 'int8') return 1;
if (dataType === 'uint16' || dataType === 'int16') return 2;
if (dataType === 'uint24' || dataType === 'int24') return 3;
return 255;
},
GetValue: function (dataType, buffer) {
let maxBytes = this.GetMaxBytes(dataType);
if (buffer.length < maxBytes) {
print('Error: ValueBuffer has, ', buffer.length, ' Bytes with DataType, ', dataType);
return null;
}
let data = null;
let _1stByte = buffer.at(0); //get 1.Byte of Buffer
let _2ndByte = null;
if (maxBytes > 1) _2ndByte = buffer.at(1); //get 2.Byte of Buffer
let _3rdByte = null;
if (maxBytes > 2) _3rdByte = buffer.at(2); //get 3.Byte of Buffer
if (dataType === 'uint8') data = _1stByte;
if (dataType === 'int8') data = this.UInt_To_Int(_1stByte, 8);
if (dataType === 'uint16') data = 0xffff & ((_2ndByte << 8) | _1stByte);
if (dataType === 'int16') data = this.UInt_To_Int(0xffff & ((_2ndByte << 8) | _1stByte), 16);
if (dataType === 'uint24') data = 0x00ffffff & ((_3rdByte << 16) | (_2ndByte << 8) | _1stByte);
if (dataType === 'int24') data = this.UInt_To_Int(0x00ffffff & ((_3rdByte << 16) | (_2ndByte << 8) | _1stByte), 24);
return data;
},
Unpack: function (payload) {
if (typeof payload !== "string" || payload.length === 0) return null;
let btHomeObj = {}, byte = payload.at(0), value, objectID; //byte = payload.at(0) --> get 1.Byte of Payload
if (byte & 0x1) {
btHomeObj['encryption'] = true; //analyze 1. Bit and set btHomeObj encryption boolean
} else {
btHomeObj['encryption'] = false;
}
btHomeObj['version'] = byte >> 5; //cut,move to 6.Bit, save the rest as version
if (btHomeObj['version'] !== 2) return null; //analyse the rest Bits 6,7,8 inside version, exit if BTHome Version is not 2
if (btHomeObj['encryption']) return btHomeObj; //exit if payload is encrypted
payload = payload.slice(1); //remove used 1.Byte from payload
while (payload.length > 0) {
byte = payload.at(0); //get 1.Byte of Payload
objectID = bthObjectIDs[byte]; //compare 1.Byte with known BTHome objectIds list, add parameter if known object
if (typeof objectID === 'undefined') {
print('Error: Unknown BThome Object, decimal_ID: ', byte, ' convert to hex and compare hex_ID "0x.." with full objID list --> https://bthome.io/format');
break; //exit loop, if objectID is unknown
}
payload = payload.slice(1); //remove used 1.Byte from payload
value = this.GetValue(objectID.dataType, payload);
if (value === null) break; //exit loof, if value null
if (typeof objectID.factor !== 'undefined') value = (value * objectID.factor); //add factor
btHomeObj[objectID.key] = value;
if (typeof objectID.unit !== 'undefined') value = (JSON.stringify(value) + objectID.unit); //add unit
if (typeof objectID.unit !== 'undefined') btHomeObj[(objectID.key+"String")] = value;
payload = payload.slice(this.GetMaxBytes(objectID.dataType)); //remove used value Bytes from payload
}
return btHomeObj;
},
};
function ScanCB(status, response) {
if (status !== BLE.Scanner.SCAN_RESULT) return; //exit if Scan status is stopped or unknown
if (response.service_data === undefined || response.service_data[uuid] === undefined) return; //Filter only BThome Responses
let BTHomeObj = BTHomeDecoder.Unpack(response.service_data[uuid]); //decode Sevice Data
if (BTHomeObj === null) {print('Error: Failed to Unpack service_data of, ', response.addr); return;}
if (last_packet_id === BTHomeObj.pid && last_addr === response.addr) return; //exit if packet is already known.
if (debug) print('Received packet, raw Data:', JSON.stringify(response));
if (response.local_name !== undefined) BTHomeObj.device = 'Unknown-Device--> ' + response.local_name;
if (response.local_name !== undefined && response.local_name.indexOf(devID1) >= 0) BTHomeObj.device = 'BluButton1'; //search for Blu Button1
if (response.local_name !== undefined && response.local_name.indexOf(devID2) >= 0) BTHomeObj.device = 'BluDoorWindow'; //search for Blu Door/Window
if (response.local_name === undefined) BTHomeObj.device = 'Hidden-Device';
last_packet_id = BTHomeObj.pid, last_addr = response.addr, BTHomeObj.addr = response.addr, BTHomeObj.rssi = response.rssi, BTHomeObj.gateway = Shelly.getDeviceInfo().id;
if (debug) print('Received BTHome packet: ', JSON.stringify(BTHomeObj));
if (typeof BTHomeObj.buttonID === 'number' && BTHomeObj.illuminance === undefined) { //somehow filter for blu Button
let buttonEventMap = ['wake_up','single_push','double_push','triple_push','long_push','pairing_push','default_reset_push'], buttonInput = BTHomeObj.buttonID;
if(buttonInput > 6 && buttonInput !== 254)buttonInput = 'unknown_push';
if(buttonInput < 7)buttonInput = buttonEventMap[buttonInput];
if(buttonInput === 254)buttonInput = 'hold_push';
Shelly.emitEvent(buttonInput, {
generation: 'BLU',
gateway: BTHomeObj.gateway,
device: BTHomeObj.device,
battery_value: BTHomeObj.battery,
battery_string: BTHomeObj.batteryString,
inputID: BTHomeObj.buttonID,
mac: BTHomeObj.addr,
rssi: BTHomeObj.rssi,
pid: BTHomeObj.pid,
encryption: BTHomeObj.encryption
});
}
if (typeof BTHomeObj.illuminance === 'number'){ //somehow, filter for blu door/window
Shelly.emitEvent('blu_DW_ChangedStatus', {
generation: 'BLU',
gateway: BTHomeObj.gateway,
device: BTHomeObj.device,
battery_value: BTHomeObj.battery,
battery_string: BTHomeObj.batteryString,
doorStateID: BTHomeObj.doorStateID,
windowStateID: BTHomeObj.windowStateID,
illuminance: BTHomeObj.illuminance,
moistureLvl: BTHomeObj.moistureLvl,
rotationLvl: BTHomeObj.rotationLvl,
buttonID: BTHomeObj.buttonID,
mac: BTHomeObj.addr,
rssi: BTHomeObj.rssi,
pid: BTHomeObj.pid,
encryption: BTHomeObj.encryption
});
}
}
function Start_BLE_Scan() {
let bleScanStarted = BLE.Scanner.Start({ duration_ms: scanDuration, active: activeScan }, ScanCB);
if (bleScanStarted === false) {
print('Error: BTHome Scanner could not be started, will try again in 5sek.');
Timer.set(5000, false, Start_BLE_Scan);
} else {
let scanType = 'Passiv';
if (activeScan) scanType = 'Active';
print('Success: BTHome ', scanType, ' Scanner running in Background');
}
}
//__Main__
Start_BLE_Scan();
if(sendMQTT) Shelly.addEventHandler(FilterEvents);