From 00692b78d70838bb594ca2ca9f5a4ece6eec1ed8 Mon Sep 17 00:00:00 2001 From: patman15 <14628713+patman15@users.noreply.github.com> Date: Thu, 4 Jul 2024 18:41:29 +0200 Subject: [PATCH] Created emulated device --- emu/PV_BLE_cover/PV_BLE_cover.ino | 291 ++++++++++++++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 emu/PV_BLE_cover/PV_BLE_cover.ino diff --git a/emu/PV_BLE_cover/PV_BLE_cover.ino b/emu/PV_BLE_cover/PV_BLE_cover.ino new file mode 100644 index 0000000..3f7e733 --- /dev/null +++ b/emu/PV_BLE_cover/PV_BLE_cover.ino @@ -0,0 +1,291 @@ +/** + * Emulate a Hunter Douglas PowerView cover device using ESP32 + * + * TODO: + * - adding device to appartement does only work after long timeout, + * as some feedback to "reset scene automations" is expected + * + * AUTHOR: patman15 + * LICENSE: GPLv2 + */ + +#include +#include +#include +#include + +#include +#include + +#define NAME "myPVcover" + +#define COVER_SERVICE_UUID "0000FDC1-0000-1000-8000-00805f9b34fb" +#define COVER_CHAR_UUID "CAFE1001-C0FF-EE01-8000-A110CA7AB1E0" +#define XXX_CHAR_UUID "CAFE1002-C0FF-EE01-8000-A110CA7AB1E0" + +//#define FW_SERVICE_UUID "CAFE8000-C0FF-EE01-8000-A110CA7AB1E0" + +#define BAT_SERVICE_UUID BLEUUID("180F") +#define BAT_CHAR_UUID BLEUUID("2A19") + + +#pragma pack(1) +struct header { + uint8_t serviceID; + uint8_t cmdID; + uint8_t sequence; + uint8_t data_len; +}; + +struct position { + uint16_t pos1; + uint16_t pos2; + uint16_t pos3; + uint16_t tilt; + uint8_t velocity; +}; + +BLECharacteristic *pCharacteristic_cover, *pCharacteristic_fw, *pCharacteristic_unknown, *pCharacteristic_bat; +BLEServer *pServer = NULL; +bool deviceConnected = false; +bool oldDeviceConnected = false; + +void Serialprintln(const char* input...) { + va_list args; + va_start(args, input); + for(const char* i=input; *i!=0; ++i) { + if(*i!='%') { Serial.print(*i); continue; } + switch(*(++i)) { + case '%': Serial.print('%'); break; + case 's': Serial.print(va_arg(args, char*)); break; + case 'd': Serial.print(va_arg(args, int), DEC); break; + case 'b': Serial.print(va_arg(args, int), BIN); break; + case 'x': Serial.print(va_arg(args, int), HEX); break; + case 'f': Serial.print(va_arg(args, double), 2); break; + } + } + Serial.println(); + va_end(args); +} + +const char* decode_cmd(uint16_t cmd) { + switch(cmd) { + case 0x01: + return "set position"; + case 0xBA: + return "activate scene"; + default: + return "ERR"; + } + +} + +void print_hex(uint8_t *value, uint8_t len) { + for (int i = 0; i < len; i++) { + Serial.print("0x"); + Serial.print(value[i], HEX); + Serial.print(" "); + } + Serial.println(""); +} + +class MyServerCallbacks: public BLEServerCallbacks { + void onConnect(BLEServer* pServer) { + Serial.print("connect ID: "); + Serial.println(pServer->getConnId()); + /*pServer->updatePeerMTU(pServer->getConnId(), 310); + Serial.print("MTU: "); + Serial.println(pServer->getPeerMTU(pServer->getConnId()));*/ + deviceConnected = true; + BLEDevice::startAdvertising(); + }; + + void onDisconnect(BLEServer* pServer) { + Serial.println("disconnect."); + deviceConnected = false; + } + + void onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { + Serialprintln("MTU changed: %d", pServer->getPeerMTU(pServer->getConnId())); + } +}; + +class coverCallbacks: public BLECharacteristicCallbacks { + void onWrite(BLECharacteristic *pCharacteristic) { + uint8_t *value = pCharacteristic->getData(); + + Serialprintln("Cover write %s:", pCharacteristic->toString().c_str()); + print_hex(value, pCharacteristic->getLength()); + + struct header data; + memcpy((void *) &data, value, 4); + Serialprintln("SRV: %x, CMD %x, SEQ %x, LEN %x", data.serviceID, data.cmdID, data.sequence, data.data_len); + + switch ((data.serviceID << 8) | data.cmdID) { + case 0xF701: + // set position + struct position pos; + memcpy((void *) &pos, &value[4], data.data_len); + Serialprintln("\tset position\tpos1 %f%%, pos2 %d, pos3 %d, tilt %d, velocity %d", pos.pos1/100.0, pos.pos2, pos.pos3, pos.tilt, pos.velocity); + break; + case 0xF7B8: + // stop movement + Serial.println("\tstop"); + break; + case 0xF7BA: + // activate scene + Serialprintln("\tactivate scene\tscene #%d", (uint16_t) value[4]); + break; + case 0xFA5B: + // get scene + Serialprintln("\tget scene\tscene #%d", (uint16_t) value[4]); + break; + case 0xFAEA: + // Reset Scene Automations + // FIXME! wrong return value! + Serialprintln("\treset scene automations\t"); + uint8_t ret[]={0xFA, 0xEA, data.sequence, 0x1, 0x0}; + Serial.print("ret: "); + print_hex(ret, 5); + pCharacteristic->setValue(ret, 5); + pCharacteristic->indicate(); + break; + } + Serial.println(); + } + + void onRead(BLECharacteristic *pCharacteristic) { + Serialprintln("Cover read: %s", pCharacteristic->toString().c_str()); + Serial.println(); + } +}; + +class batteryCallbacks: public BLECharacteristicCallbacks { + + void onWrite(BLECharacteristic *pCharacteristic) { + uint8_t *value = pCharacteristic->getData(); + + Serialprintln("Battery write: %s:", pCharacteristic->toString().c_str()); + print_hex(value, pCharacteristic->getLength()); + Serial.println(); + } + + void onRead(BLECharacteristic *pCharacteristic) { + Serialprintln("Battery read: %s", pCharacteristic->toString().c_str()); + Serial.println(); + } +}; + +class genericCallbacks: public BLECharacteristicCallbacks { + + void onWrite(BLECharacteristic *pCharacteristic) { + uint8_t *value = pCharacteristic->getData(); + + Serialprintln("generic write %s:", pCharacteristic->toString().c_str()); + print_hex(value, pCharacteristic->getLength()); + Serial.println(); + } + + void onRead(BLECharacteristic *pCharacteristic) { + Serialprintln("generic read %s.", pCharacteristic->toString().c_str()); + Serial.println(); + } +}; + +void setup() { + Serial.begin(115200); + Serial.println(NAME " initializing ..."); + + BLEDevice::init(NAME); + Serialprintln("MTU: %d", BLEDevice::getMTU()); + + // Create the BLE Server + pServer = BLEDevice::createServer(); + pServer->setCallbacks(new MyServerCallbacks()); + + // Create the BLE Service + BLEService *pCovService = pServer->createService(COVER_SERVICE_UUID); + // Create a BLE Characteristic + pCharacteristic_cover = pCovService->createCharacteristic( + COVER_CHAR_UUID, + BLECharacteristic::PROPERTY_NOTIFY | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_WRITE_NR + ); + pCharacteristic_cover->setCallbacks(new coverCallbacks()); + // Create a BLE Descriptor + BLEDescriptor *pDesc1 = new BLEDescriptor("2901", 10); + pDesc1->setValue("cover"); + pCharacteristic_cover->addDescriptor(new BLE2902()); + pCharacteristic_cover->addDescriptor(pDesc1); + + pCharacteristic_unknown = pCovService->createCharacteristic( + XXX_CHAR_UUID, + BLECharacteristic::PROPERTY_INDICATE | + BLECharacteristic::PROPERTY_WRITE | + BLECharacteristic::PROPERTY_WRITE_NR + ); + pCharacteristic_unknown->setCallbacks(new genericCallbacks()); + + + BLEService *pBatService = pServer->createService(BAT_SERVICE_UUID); + + pCharacteristic_bat = pBatService->createCharacteristic( + BAT_CHAR_UUID, + BLECharacteristic::PROPERTY_READ + ); + pCharacteristic_bat->setCallbacks(new batteryCallbacks()); + pBatService->addCharacteristic(pCharacteristic_bat); + uint8_t battery_level = 42; + pCharacteristic_bat->setValue(&battery_level, 1); + pCharacteristic_bat->addDescriptor(new BLE2902()); + + // BLEService *pFWService = pServer->createService(FW_SERVICE_UUID); + // pCharacteristic_fw = pCovService->createCharacteristic( + // CHAR_FW_UUID, + // BLECharacteristic::PROPERTY_READ | + // BLECharacteristic::PROPERTY_WRITE | + // BLECharacteristic::PROPERTY_WRITE_NR + // ); + // pCharacteristic_fw->setCallbacks(new genericCallbacks()); + // pCharacteristic_fw->addDescriptor(new BLE2902()); + // pCharacteristic_fw->addDescriptor(pDesc2); + //BLEDescriptor *pDesc2 = new BLEDescriptor("2901", 10); + //pDesc2->setValue("firmware"); + + // Start the service + pCovService->start(); + //pFWService->start(); + + // Start advertising + BLEAdvertisementData AdvertisementData; + const String manufacturerData = String("\x19\x08\x00\x00\x2A\x00\x00\x00\x00\x00\xA2",11); + // Hunter Douglas ^^--^^ ^^ ID-Type + AdvertisementData.setManufacturerData(manufacturerData); + AdvertisementData.setPartialServices(BLEUUID(COVER_SERVICE_UUID)); + AdvertisementData.setFlags((1 << 2) | (1 << 1)); // [BR/EDR Not Supported] | [LE General Discoverable Mode] + + BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); + + pAdvertising->setAdvertisementData(AdvertisementData); + + BLEDevice::startAdvertising(); + + Serial.println("Device " NAME " ready."); +} + +void loop() { + // put your main code here, to run repeatedly: + // disconnecting + if (!deviceConnected && oldDeviceConnected) { + delay(500); // give the bluetooth stack the chance to get things ready + pServer->startAdvertising(); // restart advertising + Serial.println("start advertising"); + oldDeviceConnected = deviceConnected; + } + // connecting + if (deviceConnected && !oldDeviceConnected) { + // do stuff here on connecting + oldDeviceConnected = deviceConnected; + } +}