From 091c8c053614bf63cf359ae22bb2526dcb82fb67 Mon Sep 17 00:00:00 2001 From: Felix Biego Date: Sat, 4 Jan 2025 10:20:36 +0300 Subject: [PATCH] v1.6.0 - update NimBLE dependency version (2.1.0) - add uv & pressure data - add function to check whether is subscribed to notifications - add chunked data transfer mode (enable/disabled) --- .gitignore | 2 +- README.md | 2 + examples/watch/watch.ino | 7 ++ library.json | 6 +- library.properties | 2 +- platformio.ini | 3 +- scripts/copy.py | 34 ++++++++ src/ChronosESP32.cpp | 183 ++++++++++++++++++++++++++++----------- src/ChronosESP32.h | 16 +++- 9 files changed, 194 insertions(+), 61 deletions(-) create mode 100644 scripts/copy.py diff --git a/.gitignore b/.gitignore index 79b7753..dc3ab21 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -*.py + .pio .vscode diff --git a/README.md b/README.md index 8c8be01..11f96e1 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,8 @@ void loop(); // handles routine functions bool isRunning(); // check whether BLE server is inited and running void setName(String name); // set the BLE name (call before begin) void setScreen(ChronosScreen screen); // set the screen config (call before begin) +void setChunkedTransfer(bool chunked); +bool isSubscribed(); // watch bool isConnected(); diff --git a/examples/watch/watch.ino b/examples/watch/watch.ino index bf64380..b6cca44 100644 --- a/examples/watch/watch.ino +++ b/examples/watch/watch.ino @@ -270,6 +270,13 @@ void configCallback(Config config, uint32_t a, uint32_t b) Serial.print("\tLow:"); Serial.print(w.low); Serial.println("°C"); + if (i == 0) + { + Serial.print("Pressure: "); + Serial.print(w.pressure); + Serial.print("\tUV: "); + Serial.println(w.uv); + } } } } diff --git a/library.json b/library.json index 1616e69..e9fa12e 100644 --- a/library.json +++ b/library.json @@ -1,6 +1,6 @@ { "name": "ChronosESP32", - "version": "1.5.1", + "version": "1.6.0", "keywords": "Arduino, ESP32, Time, BLE, Watch", "description": "A library for ESP32 to interface with Chronos app over BLE", "repository": @@ -19,8 +19,8 @@ "frameworks": "arduino", "platforms": "espressif32", "dependencies": { - "h2zero/NimBLE-Arduino": "*", - "fbiego/ESP32Time": "*" + "h2zero/NimBLE-Arduino": "^2.1.0", + "fbiego/ESP32Time": "^2.0.6" }, "headers": "ChronosESP32.h" } diff --git a/library.properties b/library.properties index 9c8cb27..22af9c3 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=ChronosESP32 -version=1.5.1 +version=1.6.0 author=fbiego maintainer=fbiego sentence=Setup your ESP32 as a smartwatch and connect to Chronos app over BLE. diff --git a/platformio.ini b/platformio.ini index 0e17ca0..6e48432 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,13 +22,14 @@ src_dir = examples/watch [env] platform = espressif32 framework = arduino +extra_scripts = post:scripts/copy.py lib_deps = ; use src folder as library file://./src ; external library dependencies fbiego/ESP32Time@^2.0.6 - h2zero/NimBLE-Arduino@^1.4.1 + h2zero/NimBLE-Arduino@^2.1.0 [env:esp32dev] board = esp32dev diff --git a/scripts/copy.py b/scripts/copy.py new file mode 100644 index 0000000..2055b35 --- /dev/null +++ b/scripts/copy.py @@ -0,0 +1,34 @@ + +import shutil +import os + +Import("env") + +def copy_files_recursive(src_dir, dest_dir): + """Recursively copies files and directories from the source to the destination. + + Args: + src_dir: The source directory. + dest_dir: The destination directory. + """ + print(f"Updating source files to {dest_dir}") + + for item in os.listdir(src_dir): + src_item = os.path.join(src_dir, item) + dst_item = os.path.join(dest_dir, item) + + if os.path.isdir(src_item): + shutil.copytree(src_item, dst_item, dirs_exist_ok=True) + else: + shutil.copy2(src_item, dst_item) + +try: + pioenv = env._dict['PIOENV'] + print(f"PlatformIO environment: {pioenv}") +except (KeyError, AttributeError): + print("PlatformIO environment not found in the SCons Environment.") + pioenv = "devkit" + +sep = os.sep +copy_files_recursive(f"src{sep}", f".pio{sep}libdeps{sep}{pioenv}{sep}src{sep}") + diff --git a/src/ChronosESP32.cpp b/src/ChronosESP32.cpp index 37b4e21..f1f5fb3 100644 --- a/src/ChronosESP32.cpp +++ b/src/ChronosESP32.cpp @@ -51,8 +51,8 @@ ChronosESP32::ChronosESP32() _notifications[0].app = "Chronos"; _notifications[0].message = "Download from Google Play to sync time and receive notifications"; - _infoTimer.duration = 3 * 1000; // 3 seconds for info timer - _findTimer.duration = 30 * 1000; // 30 seconds for find phone + _infoTimer.duration = 3 * 1000; // 3 seconds for info timer + _findTimer.duration = 30 * 1000; // 30 seconds for find phone _ringerTimer.duration = 30 * 1000; // 30 seconds for ringer alert } @@ -109,10 +109,10 @@ void ChronosESP32::begin() BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); - pAdvertising->setScanResponse(true); - pAdvertising->setMinPreferred(0x06); // functions that help with iPhone connections issue - pAdvertising->setMinPreferred(0x12); - BLEDevice::startAdvertising(); + pAdvertising->enableScanResponse(true); + pAdvertising->setPreferredParams(0x06, 0x12); // functions that help with iPhone connections issue + pAdvertising->setName(_watchName.c_str()); + pAdvertising->start(); _address = BLEDevice::getAddress().toString().c_str(); @@ -141,7 +141,8 @@ bool ChronosESP32::isRunning() */ void ChronosESP32::loop() { - if (!_inited){ + if (!_inited) + { // begin not called. do nothing return; } @@ -174,7 +175,8 @@ void ChronosESP32::loop() _batteryChanged = false; sendBattery(); } - if (_sendESP){ + if (_sendESP) + { _sendESP = false; sendESP(); } @@ -194,6 +196,16 @@ void ChronosESP32::loop() } } +/*! + @brief set whether to split transferred bytes + @param chunked + enable or disable state +*/ +void ChronosESP32::setChunkedTransfer(bool chunked) +{ + _chunked = chunked; +} + /*! @brief check whether the device is connected */ @@ -202,6 +214,14 @@ bool ChronosESP32::isConnected() return _connected; } +/*! + @brief check whether the device is subcribed to ble notifications +*/ +bool ChronosESP32::isSubscribed() +{ + return _subscribed; +} + /*! @brief set the clock to 24 hour mode @param mode @@ -418,14 +438,52 @@ void ChronosESP32::setAlarm(int index, Alarm alarm) */ void ChronosESP32::sendCommand(uint8_t *command, size_t length) { - if (!_inited){ + if (!_inited) + { // begin not called. do nothing return; } - pCharacteristicTX->setValue(command, length); - pCharacteristicTX->notify(); - vTaskDelay(200); + if (length <= 20 || !_chunked) + { + // Send the entire command if it fits in one packet + pCharacteristicTX->setValue(command, length); + pCharacteristicTX->notify(); + vTaskDelay(200 / portTICK_PERIOD_MS); + } + else + { + // Send the first 20 bytes as is (no header) + pCharacteristicTX->setValue(command, 20); + pCharacteristicTX->notify(); + vTaskDelay(200 / portTICK_PERIOD_MS); + + // Send the remaining bytes with a header + const size_t maxPayloadSize = 19; // Payload size excluding header + uint8_t chunk[20]; // Buffer for chunks with header + size_t offset = 20; // Start after the first 20 bytes + uint8_t sequenceNumber = 0; // Sequence number for headers + + while (offset < length) + { + // Add the header (sequence number) + chunk[0] = sequenceNumber++; + + // Calculate how many bytes to send in this chunk + size_t bytesToSend = min(maxPayloadSize, length - offset); + + // Copy data to chunk, leaving space for the header + memcpy(chunk + 1, command + offset, bytesToSend); + + // Send the chunk + pCharacteristicTX->setValue(chunk, bytesToSend + 1); + pCharacteristicTX->notify(); + vTaskDelay(200 / portTICK_PERIOD_MS); + + // Update offset + offset += bytesToSend; + } + } } /*! @@ -493,14 +551,7 @@ int ChronosESP32::getHourC() */ String ChronosESP32::getHourZ() { - if (_hour24) - { - return this->getTime("%H"); - } - else - { - return this->getTime("%I"); - } + return this->getTime(_hour24 ? "%H" : "%I"); } /*! @@ -510,15 +561,7 @@ String ChronosESP32::getHourZ() */ String ChronosESP32::getAmPmC(bool caps) { - if (_hour24) - { - return ""; - } - else - { - return this->getAmPm(!caps); // esp32time is getAmPm(bool lowercase); - } - return ""; + return _hour24 ? "" : this->getAmPm(!caps); // on esp32time it's getAmPm(bool lowercase); } /*! @@ -607,7 +650,7 @@ void ChronosESP32::sendInfo() void ChronosESP32::sendESP() { String espInfo; - espInfo += "ChronosESP32 v" + String(CHRONOSESP_VERSION_MAJOR) + "." + String(CHRONOSESP_VERSION_MINOR) + "." + String(CHRONOSESP_VERSION_PATCH); + espInfo += "ChronosESP32 v" + String(CHRONOSESP_VERSION_MAJOR) + "." + String(CHRONOSESP_VERSION_MINOR) + "." + String(CHRONOSESP_VERSION_PATCH); espInfo += "\n" + String(ESP.getChipModel()); espInfo += " @" + String(ESP.getCpuFreqMHz()) + "Mhz"; espInfo += " Cores:" + String(ESP.getChipCores()); @@ -623,16 +666,15 @@ void ChronosESP32::sendESP() espInfo += "\nSDK: " + String(ESP.getSdkVersion()); espInfo += "\nSketch: " + String((ESP.getSketchSize() / (1024.0)), 0) + "kB"; - char info[512]; uint16_t len = espInfo.length(); - info[0] = 0xAB; - info[1] = highByte(len + 3); - info[2] = lowByte(len + 3); - info[3] = 0xFE; - info[4] = 0x92; - info[5] = 0x80; - espInfo.toCharArray(info + 6, 506); - sendCommand((uint8_t *)info, 6 + len); + _outgoingData.data[0] = 0xAB; + _outgoingData.data[1] = highByte(len + 3); + _outgoingData.data[2] = lowByte(len + 3); + _outgoingData.data[3] = 0xFE; + _outgoingData.data[4] = 0x92; + _outgoingData.data[5] = 0x80; + espInfo.toCharArray((char *)_outgoingData.data + 6, 506); + sendCommand((uint8_t *)_outgoingData.data, 6 + len); } /*! @@ -782,12 +824,12 @@ String ChronosESP32::appName(int id) @brief onConnect from BLEServerCallbacks @param pServer BLE server object + @param connInfo + connection information */ -void ChronosESP32::onConnect(BLEServer *pServer) +void ChronosESP32::onConnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo) { _connected = true; - _infoTimer.time = millis(); - _infoTimer.active = true; if (connectionChangeCallback != nullptr) { connectionChangeCallback(true); @@ -798,8 +840,12 @@ void ChronosESP32::onConnect(BLEServer *pServer) @brief onDisconnect from BLEServerCallbacks @param pServer BLE server object + @param connInfo + connection information + @param reason + disconnect reason */ -void ChronosESP32::onDisconnect(BLEServer *pServer) +void ChronosESP32::onDisconnect(NimBLEServer *pServer, NimBLEConnInfo &connInfo, int reason) { _connected = false; _cameraReady = false; @@ -821,12 +867,37 @@ void ChronosESP32::onDisconnect(BLEServer *pServer) } } +/*! + @brief onSubscribe to BLECharacteristicCallbacks + @param pCharacteristic + the BLECharacteristic object + @param connInfo + connection information + @param subValue + subcribe value +*/ +void ChronosESP32::onSubscribe(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo, uint16_t subValue) +{ + if (pCharacteristic == pCharacteristicTX) + { + _subscribed = subValue == 1; + + if (_subscribed) + { + _infoTimer.time = millis(); + _infoTimer.active = true; + } + } +} + /*! @brief onWrite from BLECharacteristicCallbacks @param pCharacteristic the BLECharacteristic object + @param connInfo + connection information */ -void ChronosESP32::onWrite(BLECharacteristic *pCharacteristic) +void ChronosESP32::onWrite(NimBLECharacteristic *pCharacteristic, NimBLEConnInfo &connInfo) { std::string pData = pCharacteristic->getValue(); int len = pData.length(); @@ -879,7 +950,6 @@ void ChronosESP32::onWrite(BLECharacteristic *pCharacteristic) } } - if (pData[0] == 0xB0) { // bin watchface chunk info @@ -889,7 +959,6 @@ void ChronosESP32::onWrite(BLECharacteristic *pCharacteristic) { // bin watchface chunk data } - } } @@ -1123,6 +1192,12 @@ void ChronosESP32::dataReceived() } } break; + case 0x8A: + { + _weather[0].uv = _incomingData.data[6]; + _weather[0].pressure = (_incomingData.data[7] * 256) + _incomingData.data[8]; + } + break; case 0x7F: if (configurationReceivedCallback != nullptr) { @@ -1173,7 +1248,7 @@ void ChronosESP32::dataReceived() _contacts[pos].name += (char)_incomingData.data[i]; } } - break; + break; case 0xA3: { int pos = _incomingData.data[5]; @@ -1187,18 +1262,18 @@ void ChronosESP32::dataReceived() digit[2] = digit[0]; // save digit at 0 to 2 digit[0] = digit[1]; // swap 1 to 0 digit[1] = digit[2]; // swap saved 2 to 1 - digit[2] = 0; // null termination character + digit[2] = 0; // null termination character _contacts[pos].number += digit; } _contacts[pos].number.replace("A", "+"); _contacts[pos].number = _contacts[pos].number.substring(0, nSize); - + if (configurationReceivedCallback != nullptr && pos == (_contactSize - 1)) { configurationReceivedCallback(CF_CONTACT, 1, uint32_t(_sosContact << 8) | uint32_t(_contactSize)); } } - break; + break; case 0xA5: _sosContact = _incomingData.data[6]; _contactSize = _incomingData.data[7]; @@ -1221,7 +1296,7 @@ void ChronosESP32::dataReceived() { // receiving qr data int index = _incomingData.data[5]; // index of the curent link - _qrLinks[index] = ""; // clear existing + _qrLinks[index] = ""; // clear existing for (int i = 6; i < len; i++) { _qrLinks[index] += (char)_incomingData.data[i]; @@ -1256,6 +1331,12 @@ void ChronosESP32::dataReceived() _sendESP = true; } break; + case 0xCC: + if (_incomingData.data[3] == 0xFE) + { + setChunkedTransfer(_incomingData.data[5] != 0x00); + } + break; case 0xEE: if (_incomingData.data[3] == 0xFE) { diff --git a/src/ChronosESP32.h b/src/ChronosESP32.h index 51dbe74..cd37aaa 100644 --- a/src/ChronosESP32.h +++ b/src/ChronosESP32.h @@ -38,7 +38,7 @@ #include #define CHRONOSESP_VERSION_MAJOR 1 -#define CHRONOSESP_VERSION_MINOR 5 +#define CHRONOSESP_VERSION_MINOR 6 #define CHRONOSESP_VERSION_PATCH 0 #define CHRONOSESP_VERSION F(CHRONOSESP_VERSION_MAJOR "." CHRONOSESP_VERSION_MINOR "." CHRONOSESP_VERSION_PATCH) @@ -85,6 +85,8 @@ struct Weather int temp; int high; int low; + int pressure; + int uv; }; struct HourlyForecast @@ -234,6 +236,8 @@ class ChronosESP32 : public BLEServerCallbacks, public BLECharacteristicCallback bool isRunning(); // check whether BLE server is inited and running void setName(String name); // set the BLE name (call before begin) void setScreen(ChronosScreen screen); // set the screen config (call before begin) + void setChunkedTransfer(bool chunked); + bool isSubscribed(); // watch bool isConnected(); @@ -316,6 +320,7 @@ class ChronosESP32 : public BLEServerCallbacks, public BLECharacteristicCallback String _watchName = "Chronos ESP32"; String _address; bool _inited; + bool _subscribed; uint8_t _batteryLevel; bool _isCharging; bool _connected; @@ -327,6 +332,7 @@ class ChronosESP32 : public BLEServerCallbacks, public BLECharacteristicCallback bool _phoneCharging; bool _notifyPhone = true; bool _sendESP; + bool _chunked; Notification _notifications[NOTIF_SIZE]; int _notificationIndex; @@ -356,6 +362,7 @@ class ChronosESP32 : public BLEServerCallbacks, public BLECharacteristicCallback ChronosTimer _ringerTimer; ChronosData _incomingData; + ChronosData _outgoingData; ChronosScreen _screenConf = CS_240x240_128_CTF; @@ -376,11 +383,12 @@ class ChronosESP32 : public BLEServerCallbacks, public BLECharacteristicCallback String flashMode(FlashMode_t mode); // from BLEServerCallbacks - virtual void onConnect(BLEServer *pServer); - virtual void onDisconnect(BLEServer *pServer); + virtual void onConnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo) override; + virtual void onDisconnect(NimBLEServer* pServer, NimBLEConnInfo& connInfo, int reason) override; // from BLECharacteristicCallbacks - virtual void onWrite(BLECharacteristic *pCharacteristic); + virtual void onWrite(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo) override; + virtual void onSubscribe(NimBLECharacteristic* pCharacteristic, NimBLEConnInfo& connInfo, uint16_t subValue) override; void dataReceived();