diff --git a/CHANGELOG.md b/CHANGELOG.md index e627937f0..54144e094 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ## [9.2.0.4] ### Added - Function ``AddLog`` to provide logging for up to 128 (LOGSZ) characters to save stack space +- Commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively ### Changed - Maximum chars in ``AddLog_P`` logging restored from 128 to 700 (MAX_LOGSZ) to solve broken error messages diff --git a/RELEASENOTES.md b/RELEASENOTES.md index baf6f75b9..121264b82 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -65,6 +65,7 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota - Command ``SetOption43 1..255`` to control Rotary step (#10407) - Command ``SetOption118 1`` to move ZbReceived from JSON message and into the subtopic replacing "SENSOR" default [#10353](https://github.com/arendst/Tasmota/issues/10353) - Command ``SetOption119 1`` to remove the device addr from json payload, can be used with zb_topic_fname where the addr is already known from the topic [#10355](https://github.com/arendst/Tasmota/issues/10355) +- Commands ``ChannelRemap``, ``MultiPWM``, ``AlexaCTRange``, ``PowerOnFade``, ``PWMCT``, ``WhiteBlend``, ``VirtualCT`` as synonyms for ``SetOption37, 68, 82, 91, 92, 105 and 106`` respectively - Milliseconds to console output [#10152](https://github.com/arendst/Tasmota/issues/10152) - Gpio ``Option_a1`` enabling PWM2 high impedance if powered off as used by Wyze bulbs [#10196](https://github.com/arendst/Tasmota/issues/10196) - Rotary No Pullup GPIO selection ``Rotary A/B_n`` [#10407](https://github.com/arendst/Tasmota/issues/10407) diff --git a/lib/lib_basic/TasmotaModbus-1.2.0/src/TasmotaModbus.cpp b/lib/lib_basic/TasmotaModbus-1.2.0/src/TasmotaModbus.cpp index c124d611d..8c2180505 100644 --- a/lib/lib_basic/TasmotaModbus-1.2.0/src/TasmotaModbus.cpp +++ b/lib/lib_basic/TasmotaModbus-1.2.0/src/TasmotaModbus.cpp @@ -93,15 +93,18 @@ uint8_t TasmotaModbus::ReceiveBuffer(uint8_t *buffer, uint8_t register_count) buffer[mb_len++] = data; if (3 == mb_len) { if (buffer[1] & 0x80) { // 01 84 02 f2 f1 + if (0 == buffer[2]) { + return 3; // 3 = Illegal Data Value, + } return buffer[2]; // 1 = Illegal Function, - // 2 = Illegal Data Address, - // 3 = Illegal Data Value, - // 4 = Slave Error - // 5 = Acknowledge but not finished (no error) - // 6 = Slave Busy - // 8 = Memory Parity error - // 10 = Gateway Path Unavailable - // 11 = Gateway Target device failed to respond + // 2 = Illegal Data Address, + // 3 = Illegal Data Value, + // 4 = Slave Error + // 5 = Acknowledge but not finished (no error) + // 6 = Slave Busy + // 8 = Memory Parity error + // 10 = Gateway Path Unavailable + // 11 = Gateway Target device failed to respond } } } diff --git a/lib/libesp32/NimBLE-Arduino/CHANGELOG.md b/lib/libesp32/NimBLE-Arduino/CHANGELOG.md index 128d3c93d..e763a3c6d 100644 --- a/lib/libesp32/NimBLE-Arduino/CHANGELOG.md +++ b/lib/libesp32/NimBLE-Arduino/CHANGELOG.md @@ -2,7 +2,7 @@ All notable changes to this project will be documented in this file. -## [Unreleased] +## [1.1.0] - 2021-01-20 ### Added - `NimBLEDevice::setOwnAddrType` added to enable the use of random and random-resolvable addresses, by asukiaaa diff --git a/lib/libesp32/NimBLE-Arduino/README.md b/lib/libesp32/NimBLE-Arduino/README.md index ea28b8811..08e2a69c0 100644 --- a/lib/libesp32/NimBLE-Arduino/README.md +++ b/lib/libesp32/NimBLE-Arduino/README.md @@ -1,5 +1,7 @@ [Latest release ![Release Version](https://img.shields.io/github/release/h2zero/NimBLE-Arduino.svg?style=plastic) ![Release Date](https://img.shields.io/github/release-date/h2zero/NimBLE-Arduino.svg?style=plastic)](https://github.com/h2zero/NimBLE-Arduino/releases/latest/) + +Need help? Have questions or suggestions? Join the [![Gitter](https://badges.gitter.im/NimBLE-Arduino/community.svg)](https://gitter.im/NimBLE-Arduino/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
# NimBLE-Arduino @@ -57,6 +59,8 @@ Also see [Improvements_and_updates](docs/Improvements_and_updates.md) for inform [Full API documentation and class list can be found here.](https://h2zero.github.io/esp-nimble-cpp/) +For added performance and optimizations see [Usage tips](docs/Usage_tips.md). + Check the Refactored_original_examples in the examples folder for highlights of the differences with the original library. More advanced examples highlighting many available features are in examples/ NimBLE_Server, NimBLE_Client. diff --git a/lib/libesp32/NimBLE-Arduino/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino b/lib/libesp32/NimBLE-Arduino/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino index fea6b7d50..4161aabf7 100644 --- a/lib/libesp32/NimBLE-Arduino/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino +++ b/lib/libesp32/NimBLE-Arduino/examples/BLE_Beacon_Scanner/BLE_Beacon_Scanner.ino @@ -79,27 +79,24 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks return; } - uint8_t *payLoad = advertisedDevice->getPayload(); + BLEUUID eddyUUID = (uint16_t)0xfeaa; - BLEUUID checkUrlUUID = (uint16_t)0xfeaa; - - if (advertisedDevice->getServiceUUID().equals(checkUrlUUID)) + if (advertisedDevice->getServiceUUID().equals(eddyUUID)) { - if (payLoad[11] == 0x10) + std::string serviceData = advertisedDevice->getServiceData(eddyUUID); + if (serviceData[0] == 0x10) { Serial.println("Found an EddystoneURL beacon!"); BLEEddystoneURL foundEddyURL = BLEEddystoneURL(); - std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct! - foundEddyURL.setData(eddyContent); + foundEddyURL.setData(serviceData); std::string bareURL = foundEddyURL.getURL(); if (bareURL[0] == 0x00) { - size_t payLoadLen = advertisedDevice->getPayloadLength(); Serial.println("DATA-->"); - for (int idx = 0; idx < payLoadLen; idx++) + for (int idx = 0; idx < serviceData.length(); idx++) { - Serial.printf("0x%08X ", payLoad[idx]); + Serial.printf("0x%08X ", serviceData[idx]); } Serial.println("\nInvalid Data"); return; @@ -110,23 +107,15 @@ class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks Serial.printf("TX power %d\n", foundEddyURL.getPower()); Serial.println("\n"); } - else if (payLoad[11] == 0x20) + else if (serviceData[0] == 0x20) { Serial.println("Found an EddystoneTLM beacon!"); BLEEddystoneTLM foundEddyURL = BLEEddystoneTLM(); - std::string eddyContent((char *)&payLoad[11]); // incomplete EddystoneURL struct! + foundEddyURL.setData(serviceData); - eddyContent = "01234567890123"; - - for (int idx = 0; idx < 14; idx++) - { - eddyContent[idx] = payLoad[idx + 11]; - } - - foundEddyURL.setData(eddyContent); Serial.printf("Reported battery voltage: %dmV\n", foundEddyURL.getVolt()); Serial.printf("Reported temperature from TLM class: %.2fC\n", (double)foundEddyURL.getTemp()); - int temp = (int)payLoad[16] + (int)(payLoad[15] << 8); + int temp = (int)serviceData[5] + (int)(serviceData[4] << 8); float calcTemp = temp / 256.0f; Serial.printf("Reported temperature from data: %.2fC\n", calcTemp); Serial.printf("Reported advertise count: %d\n", foundEddyURL.getCount()); diff --git a/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Client/NimBLE_Client.ino b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Client/NimBLE_Client.ino index 9ef41b91c..a3899cddc 100644 --- a/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Client/NimBLE_Client.ino +++ b/lib/libesp32/NimBLE-Arduino/examples/NimBLE_Client/NimBLE_Client.ino @@ -2,10 +2,10 @@ /** NimBLE_Server Demo: * * Demonstrates many of the available features of the NimBLE client library. - * + * * Created: on March 24 2020 * Author: H2zero - * + * */ #include @@ -19,15 +19,15 @@ static uint32_t scanTime = 0; /** 0 = scan forever */ /** None of these are required as they will be handled by the library with defaults. ** - ** Remove as you see fit for your needs */ + ** Remove as you see fit for your needs */ class ClientCallbacks : public NimBLEClientCallbacks { void onConnect(NimBLEClient* pClient) { Serial.println("Connected"); /** After connection we should change the parameters if we don't need fast response times. - * These settings are 150ms interval, 0 latency, 450ms timout. + * These settings are 150ms interval, 0 latency, 450ms timout. * Timeout should be a multiple of the interval, minimum is 100ms. * I find a multiple of 3-5 * the interval works best for quick response/reconnect. - * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout + * Min interval: 120 * 1.25ms = 150, Max interval: 120 * 1.25ms = 150, 0 latency, 60 * 10ms = 600ms timeout */ pClient->updateConnParams(120,120,0,60); }; @@ -37,9 +37,9 @@ class ClientCallbacks : public NimBLEClientCallbacks { Serial.println(" Disconnected - Starting scan"); NimBLEDevice::getScan()->start(scanTime, scanEndedCB); }; - + /** Called when the peripheral requests a change to the connection parameters. - * Return true to accept and apply them or false to reject and keep + * Return true to accept and apply them or false to reject and keep * the currently used parameters. Default will return true. */ bool onConnParamsUpdateRequest(NimBLEClient* pClient, const ble_gap_upd_params* params) { @@ -55,7 +55,7 @@ class ClientCallbacks : public NimBLEClientCallbacks { return true; }; - + /********************* Security handled here ********************** ****** Note: these are the same return values as defaults ********/ uint32_t onPassKeyRequest(){ @@ -85,7 +85,7 @@ class ClientCallbacks : public NimBLEClientCallbacks { /** Define a class to handle the callbacks when advertisments are received */ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { - + void onResult(NimBLEAdvertisedDevice* advertisedDevice) { Serial.print("Advertised Device found: "); Serial.println(advertisedDevice->toString().c_str()); @@ -94,9 +94,9 @@ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { Serial.println("Found Our Service"); /** stop scan before connecting */ NimBLEDevice::getScan()->stop(); - /** Save the device reference in a global for the client to use*/ + /** Save the device reference in a global for the client to use*/ advDevice = advertisedDevice; - /** Ready to connect now */ + /** Ready to connect now */ doConnect = true; } }; @@ -105,7 +105,7 @@ class AdvertisedDeviceCallbacks: public NimBLEAdvertisedDeviceCallbacks { /** Notification / Indication receiving handler callback */ void notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pData, size_t length, bool isNotify){ - std::string str = (isNotify == true) ? "Notification" : "Indication"; + std::string str = (isNotify == true) ? "Notification" : "Indication"; str += " from "; /** NimBLEAddress and NimBLEUUID have std::string operators */ str += std::string(pRemoteCharacteristic->getRemoteService()->getClient()->getPeerAddress()); @@ -128,10 +128,10 @@ static ClientCallbacks clientCB; /** Handles the provisioning of clients and connects / interfaces with the server */ bool connectToServer() { NimBLEClient* pClient = nullptr; - + /** Check if we have a client we should reuse first **/ if(NimBLEDevice::getClientListSize()) { - /** Special case when we already know this device, we send false as the + /** Special case when we already know this device, we send false as the * second argument in connect() to prevent refreshing the service database. * This saves considerable time and power. */ @@ -142,7 +142,7 @@ bool connectToServer() { return false; } Serial.println("Reconnected client"); - } + } /** We don't already have a client that knows this device, * we will check for a client that is disconnected that we can use. */ @@ -150,28 +150,28 @@ bool connectToServer() { pClient = NimBLEDevice::getDisconnectedClient(); } } - + /** No client to reuse? Create a new one. */ if(!pClient) { if(NimBLEDevice::getClientListSize() >= NIMBLE_MAX_CONNECTIONS) { Serial.println("Max clients reached - no more connections available"); return false; } - + pClient = NimBLEDevice::createClient(); - + Serial.println("New client created"); - + pClient->setClientCallbacks(&clientCB, false); - /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. - * These settings are safe for 3 clients to connect reliably, can go faster if you have less + /** Set initial connection parameters: These settings are 15ms interval, 0 latency, 120ms timout. + * These settings are safe for 3 clients to connect reliably, can go faster if you have less * connections. Timeout should be a multiple of the interval, minimum is 100ms. - * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout + * Min interval: 12 * 1.25ms = 15, Max interval: 12 * 1.25ms = 15, 0 latency, 51 * 10ms = 510ms timeout */ pClient->setConnectionParams(12,12,0,51); /** Set how long we are willing to wait for the connection to complete (seconds), default is 30. */ pClient->setConnectTimeout(5); - + if (!pClient->connect(advDevice)) { /** Created a client but failed to connect, don't need to keep it as it has no data */ @@ -179,149 +179,147 @@ bool connectToServer() { Serial.println("Failed to connect, deleted client"); return false; } - } - + } + if(!pClient->isConnected()) { if (!pClient->connect(advDevice)) { Serial.println("Failed to connect"); return false; } } - + Serial.print("Connected to: "); Serial.println(pClient->getPeerAddress().toString().c_str()); Serial.print("RSSI: "); Serial.println(pClient->getRssi()); - + /** Now we can read/write/subscribe the charateristics of the services we are interested in */ NimBLERemoteService* pSvc = nullptr; NimBLERemoteCharacteristic* pChr = nullptr; NimBLERemoteDescriptor* pDsc = nullptr; - + pSvc = pClient->getService("DEAD"); if(pSvc) { /** make sure it's not null */ pChr = pSvc->getCharacteristic("BEEF"); - } - if(pChr) { /** make sure it's not null */ - if(pChr->canRead()) { - Serial.print(pChr->getUUID().toString().c_str()); - Serial.print(" Value: "); - Serial.println(pChr->readValue().c_str()); - } - - if(pChr->canWrite()) { - if(pChr->writeValue("Tasty")) { - Serial.print("Wrote new value to: "); - Serial.println(pChr->getUUID().toString().c_str()); - } - else { - /** Disconnect if write failed */ - pClient->disconnect(); - return false; - } - + if(pChr) { /** make sure it's not null */ if(pChr->canRead()) { - Serial.print("The value of: "); Serial.print(pChr->getUUID().toString().c_str()); - Serial.print(" is now: "); + Serial.print(" Value: "); Serial.println(pChr->readValue().c_str()); } - } - - /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). - * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. - * Unsubscribe parameter defaults are: response=false. - */ - if(pChr->canNotify()) { - //if(!pChr->registerForNotify(notifyCB)) { - if(!pChr->subscribe(true, notifyCB)) { - /** Disconnect if subscribe failed */ - pClient->disconnect(); - return false; + + if(pChr->canWrite()) { + if(pChr->writeValue("Tasty")) { + Serial.print("Wrote new value to: "); + Serial.println(pChr->getUUID().toString().c_str()); + } + else { + /** Disconnect if write failed */ + pClient->disconnect(); + return false; + } + + if(pChr->canRead()) { + Serial.print("The value of: "); + Serial.print(pChr->getUUID().toString().c_str()); + Serial.print(" is now: "); + Serial.println(pChr->readValue().c_str()); + } + } + + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if(pChr->canNotify()) { + //if(!pChr->registerForNotify(notifyCB)) { + if(!pChr->subscribe(true, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + else if(pChr->canIndicate()) { + /** Send false as first argument to subscribe to indications instead of notifications */ + //if(!pChr->registerForNotify(notifyCB, false)) { + if(!pChr->subscribe(false, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } } } - else if(pChr->canIndicate()) { - /** Send false as first argument to subscribe to indications instead of notifications */ - //if(!pChr->registerForNotify(notifyCB, false)) { - if(!pChr->subscribe(false, notifyCB)) { - /** Disconnect if subscribe failed */ - pClient->disconnect(); - return false; - } - } - } - - else{ + + } else { Serial.println("DEAD service not found."); } - + pSvc = pClient->getService("BAAD"); if(pSvc) { /** make sure it's not null */ pChr = pSvc->getCharacteristic("F00D"); - } - if(pChr) { /** make sure it's not null */ - if(pChr->canRead()) { - Serial.print(pChr->getUUID().toString().c_str()); - Serial.print(" Value: "); - Serial.println(pChr->readValue().c_str()); - } - - pDsc = pChr->getDescriptor(NimBLEUUID("C01D")); - if(pDsc) { /** make sure it's not null */ - Serial.print("Descriptor: "); - Serial.print(pDsc->getUUID().toString().c_str()); - Serial.print(" Value: "); - Serial.println(pDsc->readValue().c_str()); - } - - if(pChr->canWrite()) { - if(pChr->writeValue("No tip!")) { - Serial.print("Wrote new value to: "); - Serial.println(pChr->getUUID().toString().c_str()); - } - else { - /** Disconnect if write failed */ - pClient->disconnect(); - return false; - } - + if(pChr) { /** make sure it's not null */ if(pChr->canRead()) { - Serial.print("The value of: "); Serial.print(pChr->getUUID().toString().c_str()); - Serial.print(" is now: "); + Serial.print(" Value: "); Serial.println(pChr->readValue().c_str()); } - } - - /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). - * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. - * Unsubscribe parameter defaults are: response=false. - */ - if(pChr->canNotify()) { - //if(!pChr->registerForNotify(notifyCB)) { - if(!pChr->subscribe(true, notifyCB)) { - /** Disconnect if subscribe failed */ - pClient->disconnect(); - return false; - } - } - else if(pChr->canIndicate()) { - /** Send false as first argument to subscribe to indications instead of notifications */ - //if(!pChr->registerForNotify(notifyCB, false)) { - if(!pChr->subscribe(false, notifyCB)) { - /** Disconnect if subscribe failed */ - pClient->disconnect(); - return false; - } - } - } - else{ + pDsc = pChr->getDescriptor(NimBLEUUID("C01D")); + if(pDsc) { /** make sure it's not null */ + Serial.print("Descriptor: "); + Serial.print(pDsc->getUUID().toString().c_str()); + Serial.print(" Value: "); + Serial.println(pDsc->readValue().c_str()); + } + + if(pChr->canWrite()) { + if(pChr->writeValue("No tip!")) { + Serial.print("Wrote new value to: "); + Serial.println(pChr->getUUID().toString().c_str()); + } + else { + /** Disconnect if write failed */ + pClient->disconnect(); + return false; + } + + if(pChr->canRead()) { + Serial.print("The value of: "); + Serial.print(pChr->getUUID().toString().c_str()); + Serial.print(" is now: "); + Serial.println(pChr->readValue().c_str()); + } + } + + /** registerForNotify() has been deprecated and replaced with subscribe() / unsubscribe(). + * Subscribe parameter defaults are: notifications=true, notifyCallback=nullptr, response=false. + * Unsubscribe parameter defaults are: response=false. + */ + if(pChr->canNotify()) { + //if(!pChr->registerForNotify(notifyCB)) { + if(!pChr->subscribe(true, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + else if(pChr->canIndicate()) { + /** Send false as first argument to subscribe to indications instead of notifications */ + //if(!pChr->registerForNotify(notifyCB, false)) { + if(!pChr->subscribe(false, notifyCB)) { + /** Disconnect if subscribe failed */ + pClient->disconnect(); + return false; + } + } + } + + } else { Serial.println("BAAD service not found."); } - + Serial.println("Done with this device!"); return true; } @@ -331,7 +329,7 @@ void setup (){ Serial.println("Starting NimBLE Client"); /** Initialize NimBLE, no device name spcified as we are not advertising */ NimBLEDevice::init(""); - + /** Set the IO capabilities of the device, each option will trigger a different pairing method. * BLE_HS_IO_KEYBOARD_ONLY - Passkey pairing * BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing @@ -339,37 +337,37 @@ void setup (){ */ //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_KEYBOARD_ONLY); // use passkey //NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison - + /** 2 different ways to set security - both calls achieve the same result. * no bonding, no man in the middle protection, secure connections. - * - * These are the default values, only shown here for demonstration. - */ - //NimBLEDevice::setSecurityAuth(false, false, true); + * + * These are the default values, only shown here for demonstration. + */ + //NimBLEDevice::setSecurityAuth(false, false, true); NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC); - + /** Optional: set the transmit power, default is 3db */ NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */ - + /** Optional: set any devices you don't want to get advertisments from */ - // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); - - /** create new scan */ - NimBLEScan* pScan = NimBLEDevice::getScan(); - + // NimBLEDevice::addIgnored(NimBLEAddress ("aa:bb:cc:dd:ee:ff")); + + /** create new scan */ + NimBLEScan* pScan = NimBLEDevice::getScan(); + /** create a callback that gets called when advertisers are found */ pScan->setAdvertisedDeviceCallbacks(new AdvertisedDeviceCallbacks()); - + /** Set scan interval (how often) and window (how long) in milliseconds */ pScan->setInterval(45); pScan->setWindow(15); - + /** Active scan will gather scan response data from advertisers * but will use more energy from both devices */ pScan->setActiveScan(true); /** Start scanning for advertisers for the scan time specified (in seconds) 0 = forever - * Optional callback for when scanning stops. + * Optional callback for when scanning stops. */ pScan->start(scanTime, scanEndedCB); } @@ -380,15 +378,15 @@ void loop (){ while(!doConnect){ delay(1); } - + doConnect = false; - + /** Found a device we want to connect to, do it now */ if(connectToServer()) { Serial.println("Success! we should now be getting notifications, scanning for more!"); } else { Serial.println("Failed to connect, starting scan"); } - + NimBLEDevice::getScan()->start(scanTime,scanEndedCB); } diff --git a/lib/libesp32/NimBLE-Arduino/library.properties b/lib/libesp32/NimBLE-Arduino/library.properties index 156c098c0..7f2508333 100644 --- a/lib/libesp32/NimBLE-Arduino/library.properties +++ b/lib/libesp32/NimBLE-Arduino/library.properties @@ -1,5 +1,5 @@ name=NimBLE-Arduino -version=1.0.2 +version=1.1.0 author=h2zero maintainer=h2zero sentence=Bluetooth low energy (BLE) library for arduino-esp32 based on NimBLE. diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp index ddd3abecc..ddeb1de70 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEClient.cpp @@ -100,9 +100,10 @@ NimBLEClient::~NimBLEClient() { * be called to reset the host in the case of a stalled controller. */ void NimBLEClient::dcTimerCb(ble_npl_event *event) { - NimBLEClient *pClient = (NimBLEClient*)event->arg; + /* NimBLEClient *pClient = (NimBLEClient*)event->arg; NIMBLE_LOGC(LOG_TAG, "Timed out disconnecting from %s - resetting host", std::string(pClient->getPeerAddress()).c_str()); + */ ble_hs_sched_reset(BLE_HS_ECONTROLLER); } @@ -182,25 +183,30 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { return false; } - if(isConnected() || m_pTaskData != nullptr) { + if(isConnected() || m_connEstablished || m_pTaskData != nullptr) { NIMBLE_LOGE(LOG_TAG, "Client busy, connected to %s, id=%d", std::string(m_peerAddress).c_str(), getConnId()); return false; } + ble_addr_t peerAddr_t; + memcpy(&peerAddr_t.val, address.getNative(),6); + peerAddr_t.type = address.getType(); + if(ble_gap_conn_find_by_addr(&peerAddr_t, NULL) == 0) { + NIMBLE_LOGE(LOG_TAG, "A connection to %s already exists", + address.toString().c_str()); + return false; + } + if(address == NimBLEAddress("")) { NIMBLE_LOGE(LOG_TAG, "Invalid peer address;(NULL)"); return false; - } else if(m_peerAddress != address) { + } else { m_peerAddress = address; } - ble_addr_t peerAddr_t; - memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6); - peerAddr_t.type = m_peerAddress.getType(); - - ble_task_data_t taskData = {this, xTaskGetCurrentTaskHandle(), 0, nullptr}; + m_pTaskData = &taskData; int rc = 0; /* Try to connect the the advertiser. Allow 30 seconds (30000 ms) for @@ -213,13 +219,12 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { NimBLEClient::handleGapEvent, this); switch (rc) { case 0: - m_pTaskData = &taskData; break; case BLE_HS_EBUSY: // Scan was still running, stop it and try again if (!NimBLEDevice::getScan()->stop()) { - return false; + rc = BLE_HS_EUNKNOWN; } break; @@ -227,7 +232,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { // A connection to this device already exists, do not connect twice. NIMBLE_LOGE(LOG_TAG, "Already connected to device; addr=%s", std::string(m_peerAddress).c_str()); - return false; + break; case BLE_HS_EALREADY: // Already attemting to connect to this device, cancel the previous @@ -235,17 +240,22 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { NIMBLE_LOGE(LOG_TAG, "Already attempting to connect to %s - cancelling", std::string(m_peerAddress).c_str()); ble_gap_conn_cancel(); - return false; + break; default: NIMBLE_LOGE(LOG_TAG, "Failed to connect to %s, rc=%d; %s", std::string(m_peerAddress).c_str(), rc, NimBLEUtils::returnCodeToString(rc)); - return false; + break; } } while (rc == BLE_HS_EBUSY); + if(rc != 0) { + m_pTaskData = nullptr; + return false; + } + // Wait for the connect timeout time +1 second for the connection to complete if(ulTaskNotifyTake(pdTRUE, pdMS_TO_TICKS(m_connectTimeout + 1000)) == pdFALSE) { m_pTaskData = nullptr; @@ -255,7 +265,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { disconnect(); } else { // workaround; if the controller doesn't cancel the connection - // at the timeout cancel it here. + // at the timeout, cancel it here. NIMBLE_LOGE(LOG_TAG, "Connect timeout - cancelling"); ble_gap_conn_cancel(); } @@ -269,7 +279,7 @@ bool NimBLEClient::connect(const NimBLEAddress &address, bool deleteAttibutes) { // If the failure was not a result of a disconnection // make sure we disconnect now to avoid dangling connections if(isConnected()) { - ble_gap_terminate(m_conn_id, BLE_ERR_REM_USER_CONN_TERM); + disconnect(); } return false; } else { @@ -325,26 +335,35 @@ bool NimBLEClient::secureConnection() { */ int NimBLEClient::disconnect(uint8_t reason) { NIMBLE_LOGD(LOG_TAG, ">> disconnect()"); - int rc = 0; - if(isConnected()){ - rc = ble_gap_terminate(m_conn_id, reason); - if (rc == 0) { - ble_addr_t peerAddr_t; - memcpy(&peerAddr_t.val, m_peerAddress.getNative(),6); - peerAddr_t.type = m_peerAddress.getType(); + if(isConnected()) { + // If the timer was already started, ignore this call. + if(ble_npl_callout_is_active(&m_dcTimer)) { + NIMBLE_LOGI(LOG_TAG, "Already disconnecting, timer started"); + return BLE_HS_EALREADY; + } - // Set the disconnect timeout to the supervison timeout time + 1 second - // In case the event triggers shortly after the supervision timeout. - // We don't want to prematurely reset the host. - ble_gap_conn_desc desc; - if(ble_gap_conn_find_by_addr(&peerAddr_t, &desc) == 0){ - ble_npl_time_t ticks; - ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks); - ble_npl_callout_reset(&m_dcTimer, ticks); - NIMBLE_LOGD(LOG_TAG, "DC TIMEOUT = %dms", (desc.supervision_timeout + 100) * 10); + ble_gap_conn_desc desc; + if(ble_gap_conn_find(m_conn_id, &desc) != 0){ + NIMBLE_LOGI(LOG_TAG, "Connection ID not found"); + return BLE_HS_EALREADY; + } + + // We use a timer to detect a controller error in the event that it does + // not inform the stack when disconnection is complete. + // This is a common error in certain esp-idf versions. + // The disconnect timeout time is the supervison timeout time + 1 second. + // In the case that the event happenss shortly after the supervision timeout + // we don't want to prematurely reset the host. + ble_npl_time_t ticks; + ble_npl_time_ms_to_ticks((desc.supervision_timeout + 100) * 10, &ticks); + ble_npl_callout_reset(&m_dcTimer, ticks); + + rc = ble_gap_terminate(m_conn_id, reason); + if (rc != 0) { + if(rc != BLE_HS_EALREADY) { + ble_npl_callout_stop(&m_dcTimer); } - } else if (rc != BLE_HS_EALREADY) { NIMBLE_LOGE(LOG_TAG, "ble_gap_terminate failed: rc=%d %s", rc, NimBLEUtils::returnCodeToString(rc)); } @@ -359,12 +378,12 @@ int NimBLEClient::disconnect(uint8_t reason) { /** * @brief Set the connection paramaters to use when connecting to a server. - * @param [in] minInterval minimum connection interval in 0.625ms units. - * @param [in] maxInterval maximum connection interval in 0.625ms units. - * @param [in] latency number of packets allowed to skip (extends max interval) - * @param [in] timeout the timeout time in 10ms units before disconnecting - * @param [in] scanInterval the scan interval to use when attempting to connect in 0.625ms units. - * @param [in] scanWindow the scan window to use when attempting to connect in 0.625ms units. + * @param [in] minInterval The minimum connection interval in 1.25ms units. + * @param [in] maxInterval The maximum connection interval in 1.25ms units. + * @param [in] latency The number of packets allowed to skip (extends max interval). + * @param [in] timeout The timeout time in 10ms units before disconnecting. + * @param [in] scanInterval The scan interval to use when attempting to connect in 0.625ms units. + * @param [in] scanWindow The scan window to use when attempting to connect in 0.625ms units. */ void NimBLEClient::setConnectionParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout, @@ -391,10 +410,10 @@ void NimBLEClient::setConnectionParams(uint16_t minInterval, uint16_t maxInterva /** * @brief Update the connection parameters: * * Can only be used after a connection has been established. - * @param [in] minInterval minimum connection interval in 0.625ms units. - * @param [in] maxInterval maximum connection interval in 0.625ms units. - * @param [in] latency number of packets allowed to skip (extends max interval) - * @param [in] timeout the timeout time in 10ms units before disconnecting + * @param [in] minInterval The minimum connection interval in 1.25ms units. + * @param [in] maxInterval The maximum connection interval in 1.25ms units. + * @param [in] latency The number of packets allowed to skip (extends max interval). + * @param [in] timeout The timeout time in 10ms units before disconnecting. */ void NimBLEClient::updateConnParams(uint16_t minInterval, uint16_t maxInterval, uint16_t latency, uint16_t timeout) @@ -797,14 +816,15 @@ uint16_t NimBLEClient::getMTU() { break; } - client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; - // Stop the disconnect timer since we are now disconnected. ble_npl_callout_stop(&client->m_dcTimer); // Remove the device from ignore list so we will scan it again NimBLEDevice::removeIgnored(client->m_peerAddress); + // No longer connected, clear the connection ID. + client->m_conn_id = BLE_HS_CONN_HANDLE_NONE; + // If we received a connected event but did not get established (no PDU) // then a disconnect event will be sent but we should not send it to the // app for processing. Instead we will ensure the task is released diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp index 39f07dede..a06b75360 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.cpp @@ -20,6 +20,10 @@ #include "NimBLEHIDDevice.h" #include "NimBLE2904.h" +/** + * @brief Construct a default NimBLEHIDDevice object. + * @param [in] server A pointer to the server instance this HID Device will use. + */ NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) { /* * Here we create mandatory services described in bluetooth specification @@ -61,15 +65,18 @@ NimBLEHIDDevice::NimBLEHIDDevice(NimBLEServer* server) { NimBLEHIDDevice::~NimBLEHIDDevice() { } -/* - * @brief +/** + * @brief Set the report map data formatting information. + * @param [in] map A pointer to an array with the values to set. + * @param [in] size The number of values in the array. */ void NimBLEHIDDevice::reportMap(uint8_t* map, uint16_t size) { m_reportMapCharacteristic->setValue(map, size); } -/* - * @brief This function suppose to be called at the end, when we have created all characteristics we need to build HID service +/** + * @brief Start the HID device services.\n + * This function called when all the services have been created. */ void NimBLEHIDDevice::startServices() { m_deviceInfoService->start(); @@ -77,41 +84,47 @@ void NimBLEHIDDevice::startServices() { m_batteryService->start(); } -/* - * @brief Create manufacturer characteristic (this characteristic is optional) +/** + * @brief Create a manufacturer characteristic (this characteristic is optional). */ NimBLECharacteristic* NimBLEHIDDevice::manufacturer() { m_manufacturerCharacteristic = m_deviceInfoService->createCharacteristic((uint16_t) 0x2a29, NIMBLE_PROPERTY::READ); return m_manufacturerCharacteristic; } -/* +/** * @brief Set manufacturer name - * @param [in] name manufacturer name + * @param [in] name The manufacturer name of this HID device. */ void NimBLEHIDDevice::manufacturer(std::string name) { m_manufacturerCharacteristic->setValue(name); } -/* - * @brief +/** + * @brief Sets the Plug n Play characterisc value. + * @param [in] sig The vendor ID source number. + * @param [in[ vid The vendor ID number. + * @param [in] pid The product ID number. + * @param [in] version The produce version number. */ void NimBLEHIDDevice::pnp(uint8_t sig, uint16_t vid, uint16_t pid, uint16_t version) { uint8_t pnp[] = { sig, (uint8_t) (vid >> 8), (uint8_t) vid, (uint8_t) (pid >> 8), (uint8_t) pid, (uint8_t) (version >> 8), (uint8_t) version }; m_pnpCharacteristic->setValue(pnp, sizeof(pnp)); } -/* - * @brief +/** + * @brief Sets the HID Information characteristic value. + * @param [in] country The country code for the device. + * @param [in] flags The HID Class Specification release number to use. */ void NimBLEHIDDevice::hidInfo(uint8_t country, uint8_t flags) { uint8_t info[] = { 0x11, 0x1, country, flags }; m_hidInfoCharacteristic->setValue(info, sizeof(info)); } -/* - * @brief Create input report characteristic that need to be saved as new characteristic object so can be further used - * @param [in] reportID input report ID, the same as in report map for input object related to created characteristic +/** + * @brief Create input report characteristic + * @param [in] reportID input report ID, the same as in report map for input object related to the characteristic * @return pointer to new input report characteristic */ NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) { @@ -124,9 +137,9 @@ NimBLECharacteristic* NimBLEHIDDevice::inputReport(uint8_t reportID) { return inputReportCharacteristic; } -/* - * @brief Create output report characteristic that need to be saved as new characteristic object so can be further used - * @param [in] reportID Output report ID, the same as in report map for output object related to created characteristic +/** + * @brief Create output report characteristic + * @param [in] reportID Output report ID, the same as in report map for output object related to the characteristic * @return Pointer to new output report characteristic */ NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) { @@ -139,9 +152,9 @@ NimBLECharacteristic* NimBLEHIDDevice::outputReport(uint8_t reportID) { return outputReportCharacteristic; } -/* - * @brief Create feature report characteristic that need to be saved as new characteristic object so can be further used - * @param [in] reportID Feature report ID, the same as in report map for feature object related to created characteristic +/** + * @brief Create feature report characteristic. + * @param [in] reportID Feature report ID, the same as in report map for feature object related to the characteristic * @return Pointer to new feature report characteristic */ NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) { @@ -154,34 +167,38 @@ NimBLECharacteristic* NimBLEHIDDevice::featureReport(uint8_t reportID) { return featureReportCharacteristic; } -/* - * @brief +/** + * @brief Creates a keyboard boot input report characteristic */ NimBLECharacteristic* NimBLEHIDDevice::bootInput() { return m_hidService->createCharacteristic((uint16_t) 0x2a22, NIMBLE_PROPERTY::NOTIFY); } -/* - * @brief +/** + * @brief Create a keyboard boot output report characteristic */ NimBLECharacteristic* NimBLEHIDDevice::bootOutput() { return m_hidService->createCharacteristic((uint16_t) 0x2a32, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_NR); } -/* - * @brief +/** + * @brief Returns a pointer to the HID control point characteristic. */ NimBLECharacteristic* NimBLEHIDDevice::hidControl() { return m_hidControlCharacteristic; } -/* - * @brief +/** + * @brief Returns a pointer to the protocol mode characteristic. */ NimBLECharacteristic* NimBLEHIDDevice::protocolMode() { return m_protocolModeCharacteristic; } +/** + * @brief Set the battery level characteristic value. + * @param [in] level The battery level value. + */ void NimBLEHIDDevice::setBatteryLevel(uint8_t level) { m_batteryLevelCharacteristic->setValue(&level, 1); } @@ -208,22 +225,23 @@ BLECharacteristic* BLEHIDDevice::hidInfo() { return m_hidInfoCharacteristic; } */ -/* - * @brief + +/** + * @brief Returns a pointer to the device information service. */ NimBLEService* NimBLEHIDDevice::deviceInfo() { return m_deviceInfoService; } -/* - * @brief +/** + * @brief Returns a pointer to the HID service. */ NimBLEService* NimBLEHIDDevice::hidService() { return m_hidService; } -/* - * @brief +/** + * @brief @brief Returns a pointer to the battery service. */ NimBLEService* NimBLEHIDDevice::batteryService() { return m_batteryService; diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h index 3e7a6f759..6ed7c2bde 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEHIDDevice.h @@ -36,6 +36,10 @@ #define HID_DIGITAL_PEN 0x03C7 #define HID_BARCODE 0x03C8 + +/** + * @brief A model of a %BLE Human Interface Device. + */ class NimBLEHIDDevice { public: NimBLEHIDDevice(NimBLEServer*); diff --git a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp index 655511aea..fd5d8266b 100644 --- a/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp +++ b/lib/libesp32/NimBLE-Arduino/src/NimBLEServer.cpp @@ -621,7 +621,13 @@ uint16_t NimBLEServer::getPeerMTU(uint16_t conn_id) { /** - * Update connection parameters can be called only after connection has been established + * @brief Request an Update the connection parameters: + * * Can only be used after a connection has been established. + * @param [in] conn_handle The connection handle of the peer to send the request to. + * @param [in] minInterval The minimum connection interval in 1.25ms units. + * @param [in] maxInterval The maximum connection interval in 1.25ms units. + * @param [in] latency The number of packets allowed to skip (extends max interval). + * @param [in] timeout The timeout time in 10ms units before disconnecting. */ void NimBLEServer::updateConnParams(uint16_t conn_handle, uint16_t minInterval, uint16_t maxInterval, diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 0c7438907..70329cfa2 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -420,6 +420,13 @@ #define D_JSON_MAXENERGYREACHED "MaxEnergyReached" // Commands xdrv_04_light.ino +#define D_SO_CHANNELREMAP "ChannelRemap" // SO37 +#define D_SO_MULTIPWM "MultiPWM" // SO68 +#define D_SO_ALEXACTRANGE "AlexaCTRange" // SO82 +#define D_SO_POWERONFADE "PowerOnFade" // SO91 +#define D_SO_PWMCT "PWMCT" // SO92 +#define D_SO_WHITEBLEND "WhiteBlend" // SO105 +#define D_SO_VIRTUALCT "VirtualCT" // SO106 #define D_CMND_CHANNEL "Channel" #define D_CMND_COLOR "Color" #define D_CMND_COLORTEMPERATURE "CT" @@ -522,6 +529,12 @@ // Commands xdrv_23_zigbee.ino #define D_PRFX_ZB "Zb" +#define D_SO_ZIGBEE_NAMEKEY "NameKey" +#define D_SO_ZIGBEE_DEVICETOPIC "DeviceTopic" +#define D_SO_ZIGBEE_NOPREFIX "NoPrefix" +#define D_SO_ZIGBEE_ENDPOINTSUFFIX "EndpointSuffix" +#define D_SO_ZIGBEE_NOAUTOBIND "NoAutoBind" +#define D_SO_ZIGBEE_NAMETOPIC "NameTopic" #define D_ZIGBEE_NOT_STARTED "Zigbee not started" #define D_CMND_ZIGBEE_PERMITJOIN "PermitJoin" #define D_CMND_ZIGBEE_STATUS "Status" @@ -739,6 +752,7 @@ const char S_JSON_DRIVER_INDEX_SVALUE[] PROGMEM = "{\"" D_CMND_DRIVE const char S_JSON_SVALUE_ACTION_SVALUE[] PROGMEM = "{\"%s\":{\"Action\":\"%s\"}}"; +const char JSON_SNS_F_TEMP[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f}"; const char JSON_SNS_TEMP[] PROGMEM = ",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"; const char JSON_SNS_ILLUMINANCE[] PROGMEM = ",\"%s\":{\"" D_JSON_ILLUMINANCE "\":%d}"; @@ -771,7 +785,9 @@ const float kSpeedConversionFactor[] = {1, // none // xdrv_02_webserver.ino #ifdef USE_WEBSERVER // {s} = , {m} = , {e} = -const char HTTP_SNS_TEMP[] PROGMEM = "{s}%s " D_TEMPERATURE "{m}%s " D_UNIT_DEGREE "%c{e}"; +const char HTTP_SNS_F_TEMP[] PROGMEM = "{s}%s " D_TEMPERATURE "{m}%*_f " D_UNIT_DEGREE "%c{e}"; +//const char HTTP_SNS_TEMP[] PROGMEM = "{s}%s " D_TEMPERATURE "{m}%s " D_UNIT_DEGREE "%c{e}"; + const char HTTP_SNS_HUM[] PROGMEM = "{s}%s " D_HUMIDITY "{m}%s " D_UNIT_PERCENT "{e}"; const char HTTP_SNS_DEW[] PROGMEM = "{s}%s " D_DEWPOINT "{m}%s " D_UNIT_DEGREE "%c{e}"; const char HTTP_SNS_PRESSURE[] PROGMEM = "{s}%s " D_PRESSURE "{m}%s " "%s{e}"; diff --git a/tasmota/support.ino b/tasmota/support.ino index 67113d2e8..38e0266b2 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -916,8 +916,8 @@ int GetCommandCode(char* destination, size_t destination_size, const char* needl return result; } -bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void)) -{ +bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void), const uint8_t *synonyms = nullptr); +bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void), const uint8_t *synonyms) { GetTextIndexed(XdrvMailbox.command, CMDSZ, 0, haystack); // Get prefix if available int prefix_length = strlen(XdrvMailbox.command); if (prefix_length) { @@ -927,10 +927,18 @@ bool DecodeCommand(const char* haystack, void (* const MyCommand[])(void)) return false; // Prefix not in command } } + size_t syn_count = synonyms ? pgm_read_byte(synonyms) : 0; int command_code = GetCommandCode(XdrvMailbox.command + prefix_length, CMDSZ, XdrvMailbox.topic + prefix_length, haystack); if (command_code > 0) { // Skip prefix - XdrvMailbox.command_code = command_code -1; - MyCommand[XdrvMailbox.command_code](); + if (command_code > syn_count) { + // We passed the synonyms zone, it's a regular command + XdrvMailbox.command_code = command_code - 1 - syn_count; + MyCommand[XdrvMailbox.command_code](); + } else { + // We have a SetOption synonym + XdrvMailbox.index = pgm_read_byte(synonyms + command_code); + CmndSetoptionBase(0); + } return true; } return false; diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 1dd0cac0a..0c0797d5e 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -83,9 +83,7 @@ void ResponseCmndNumber(int value) void ResponseCmndFloat(float value, uint32_t decimals) { - char stemp1[TOPSZ]; - dtostrfd(value, decimals, stemp1); - Response_P(S_JSON_COMMAND_XVALUE, XdrvMailbox.command, stemp1); // Return float value without quotes + Response_P(PSTR("{\"%s\":%*_f}"), XdrvMailbox.command, decimals, &value); // Return float value without quotes } void ResponseCmndIdxNumber(int value) @@ -507,12 +505,21 @@ void CmndStatus(void) } if ((0 == payload) || (5 == payload)) { +/* Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"" D_JSON_GATEWAY "\":\"%s\",\"" D_JSON_SUBNETMASK "\":\"%s\",\"" D_JSON_DNSSERVER "\":\"%s\",\"" D_JSON_MAC "\":\"%s\",\"" D_CMND_WEBSERVER "\":%d,\"" D_CMND_WIFICONFIG "\":%d,\"" D_CMND_WIFIPOWER "\":%s}}"), NetworkHostname(), NetworkAddress().toString().c_str(), IPAddress(Settings.ipv4_address[1]).toString().c_str(), IPAddress(Settings.ipv4_address[2]).toString().c_str(), IPAddress(Settings.ipv4_address[3]).toString().c_str(), NetworkMacAddress().c_str(), Settings.webserver, Settings.sta_config, WifiGetOutputPower().c_str()); +*/ + Response_P(PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"" + D_JSON_GATEWAY "\":\"%_I\",\"" D_JSON_SUBNETMASK "\":\"%_I\",\"" D_JSON_DNSSERVER "\":\"%_I\",\"" + D_JSON_MAC "\":\"%s\",\"" D_CMND_WEBSERVER "\":%d,\"" D_CMND_WIFICONFIG "\":%d,\"" D_CMND_WIFIPOWER "\":%s}}"), + NetworkHostname(), NetworkAddress().toString().c_str(), + Settings.ipv4_address[1], Settings.ipv4_address[2], Settings.ipv4_address[3], + NetworkMacAddress().c_str(), Settings.webserver, Settings.sta_config, WifiGetOutputPower().c_str()); + MqttPublishPrefixTopic_P(STAT, PSTR(D_CMND_STATUS "5")); } @@ -835,10 +842,17 @@ void CmndSavedata(void) ResponseCmndChar((Settings.save_data > 1) ? stemp1 : GetStateText(Settings.save_data)); } -void CmndSetoption(void) -{ +void CmndSetoption(void) { snprintf_P(XdrvMailbox.command, CMDSZ, PSTR(D_CMND_SETOPTION)); // Rename result shortcut command SO to SetOption + CmndSetoptionBase(1); +} +void CmndSetoptionBase(bool indexed) { + // Allow a command to access a single SetOption by it's command name + // indexed = 0 : No index will be returned attached to the command + // {"ClockDirection":"OFF"} + // indexed = 1 : The SetOption index will be returned with the command + // {"SetOption16":"OFF"} if (XdrvMailbox.index < 146) { uint32_t ptype; uint32_t pindex; @@ -981,7 +995,11 @@ void CmndSetoption(void) if (ptype < 99) { if (1 == ptype) { - ResponseCmndIdxNumber(Settings.param[pindex]); + if (indexed) { + ResponseCmndIdxNumber(Settings.param[pindex]); + } else { + ResponseCmndNumber(Settings.param[pindex]); + } } else { uint32_t flag = Settings.flag.data; if (3 == ptype) { @@ -993,7 +1011,11 @@ void CmndSetoption(void) else if (5 == ptype) { flag = Settings.flag5.data; } - ResponseCmndIdxChar(GetStateText(bitRead(flag, pindex))); + if (indexed) { + ResponseCmndIdxChar(GetStateText(bitRead(flag, pindex))); + } else { + ResponseCmndChar(GetStateText(bitRead(flag, pindex))); + } } } } diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 2c2838ac7..0f7a14476 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -475,17 +475,15 @@ void StartWebserver(int type, IPAddress ipweb) #if LWIP_IPV6 String ipv6_addr = WifiGetIPv6(); if (ipv6_addr!="") { - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s and IPv6 global address %s "), - NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", ipweb.toString().c_str(), ipv6_addr.c_str()); + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I and IPv6 global address %s "), + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb, ipv6_addr.c_str()); } else { - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), - NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", ipweb.toString().c_str()); + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I"), + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb); } #else - AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), - NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", ipweb.toString().c_str()); -// AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I"), -// NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb); + AddLog(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %_I"), + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", (uint32_t)ipweb); #endif // LWIP_IPV6 = 1 TasmotaGlobal.rules_flag.http_init = 1; } @@ -785,11 +783,15 @@ void WSContentSpaceButton(uint32_t title_index) WSContentButton(title_index); } +void WSContentSend_Temp(const char *types, float f_temperature) { + WSContentSend_PD(HTTP_SNS_F_TEMP, types, Settings.flag2.temperature_resolution, &f_temperature, TempUnit()); +} + void WSContentSend_THD(const char *types, float f_temperature, float f_humidity) { + WSContentSend_Temp(types, f_temperature); + char parameter[FLOATSZ]; - dtostrfd(f_temperature, Settings.flag2.temperature_resolution, parameter); - WSContentSend_PD(HTTP_SNS_TEMP, types, parameter, TempUnit()); dtostrfd(f_humidity, Settings.flag2.humidity_resolution, parameter); WSContentSend_PD(HTTP_SNS_HUM, types, parameter); dtostrfd(CalcTempHumToDew(f_temperature, f_humidity), Settings.flag2.temperature_resolution, parameter); diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 5effa7f8d..b3d014b21 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -129,6 +129,10 @@ enum LightSchemes { LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_M const uint8_t LIGHT_COLOR_SIZE = 25; // Char array scolor size const char kLightCommands[] PROGMEM = "|" // No prefix + // SetOptions synonyms + D_SO_CHANNELREMAP "|" D_SO_MULTIPWM "|" D_SO_ALEXACTRANGE "|" D_SO_POWERONFADE "|" D_SO_PWMCT "|" + D_SO_WHITEBLEND "|" D_SO_VIRTUALCT "|" + // Other commands D_CMND_COLOR "|" D_CMND_COLORTEMPERATURE "|" D_CMND_DIMMER "|" D_CMND_DIMMER_RANGE "|" D_CMND_DIMMER_STEP "|" D_CMND_LEDTABLE "|" D_CMND_FADE "|" D_CMND_RGBWWTABLE "|" D_CMND_SCHEME "|" D_CMND_SPEED "|" D_CMND_WAKEUP "|" D_CMND_WAKEUPDURATION "|" D_CMND_WHITE "|" D_CMND_CHANNEL "|" D_CMND_HSBCOLOR @@ -144,6 +148,12 @@ const char kLightCommands[] PROGMEM = "|" // No prefix #endif // USE_DGR_LIGHT_SEQUENCE "|UNDOCA" ; +const uint8_t kLightSynonyms[] PROGMEM = { + 7, // number of entries + 37, 68, 82, 91, 92, + 105, 106, +}; + void (* const LightCommand[])(void) PROGMEM = { &CmndColor, &CmndColorTemperature, &CmndDimmer, &CmndDimmerRange, &CmndDimmerStep, &CmndLedTable, &CmndFade, &CmndRgbwwTable, &CmndScheme, &CmndSpeed, &CmndWakeup, &CmndWakeupDuration, @@ -3078,7 +3088,7 @@ bool Xdrv04(uint8_t function) LightSetPower(); break; case FUNC_COMMAND: - result = DecodeCommand(kLightCommands, LightCommand); + result = DecodeCommand(kLightCommands, LightCommand, kLightSynonyms); if (!result) { result = XlgtCall(FUNC_COMMAND); } diff --git a/tasmota/xdrv_16_tuyamcu.ino b/tasmota/xdrv_16_tuyamcu.ino index 8dcddb380..1f8da77e1 100644 --- a/tasmota/xdrv_16_tuyamcu.ino +++ b/tasmota/xdrv_16_tuyamcu.ino @@ -1303,7 +1303,7 @@ void TuyaSensorsShow(bool json) if (TuyaGetDpId(sensor) != 0) { switch (sensor) { case 71: - WSContentSend_PD(HTTP_SNS_TEMP, "", dtostrfd(Tuya.Sensors[0], Settings.flag2.temperature_resolution, tempval), TempUnit()); + WSContentSend_Temp("", Tuya.Sensors[0]); break; case 72: WSContentSend_PD(PSTR("{s}" D_TEMPERATURE " Set{m}%s " D_UNIT_DEGREE "%c{e}"), diff --git a/tasmota/xdrv_23_zigbee_1_headers.ino b/tasmota/xdrv_23_zigbee_1_headers.ino index 203c16d5e..d88bd80d9 100644 --- a/tasmota/xdrv_23_zigbee_1_headers.ino +++ b/tasmota/xdrv_23_zigbee_1_headers.ino @@ -107,6 +107,7 @@ public: #ifdef USE_ZIGBEE_EZSP uint32_t permit_end_time = 0; // timestamp when permit join ends + uint16_t ezsp_version = 0; #elif defined(USE_ZIGBEE_ZNP) bool permit_end_time = false; // in ZNP mode it's only a boolean #endif diff --git a/tasmota/xdrv_23_zigbee_3_hue.ino b/tasmota/xdrv_23_zigbee_3_hue.ino index b8f678d9e..e2a060ef8 100644 --- a/tasmota/xdrv_23_zigbee_3_hue.ino +++ b/tasmota/xdrv_23_zigbee_3_hue.ino @@ -151,59 +151,94 @@ void ZigbeeHueGroups(String * lights) { } } +void ZigbeeSendHue(uint16_t shortaddr, uint16_t cluster, uint8_t cmd, const SBuffer & s) { + zigbeeZCLSendCmd(ZigbeeZCLSendMessage({ + shortaddr, + 0 /* groupaddr */, + cluster /*cluster*/, + 0 /* endpoint */, + cmd /* cmd */, + 0, /* manuf */ + true /* cluster specific */, + true /* response */, + false /* discover route */, + 0, /* zcl transaction id */ + (&s != nullptr) ? s.getBuffer() : nullptr, + (&s != nullptr) ? s.len() : 0 + })); +} + // Send commands // Power On/Off void ZigbeeHuePower(uint16_t shortaddr, bool power) { - zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, ""); + ZigbeeSendHue(shortaddr, 0x0006, power ? 1 : 0, *(SBuffer*)nullptr); +// zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0006, power ? 1 : 0, ""); zigbee_devices.getShortAddr(shortaddr).setPower(power, 0); } // Dimmer void ZigbeeHueDimmer(uint16_t shortaddr, uint8_t dimmer) { if (dimmer > 0xFE) { dimmer = 0xFE; } - char param[8]; - snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer); - zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0008, 0x04, param); + SBuffer s(4); + s.add8(dimmer); + s.add16(0x000A); // transition time = 1s + ZigbeeSendHue(shortaddr, 0x0008, 0x04, s); + // char param[8]; + // snprintf_P(param, sizeof(param), PSTR("%02X0A00"), dimmer); + // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0008, 0x04, param); zigbee_devices.getLight(shortaddr).setDimmer(dimmer); } // CT void ZigbeeHueCT(uint16_t shortaddr, uint16_t ct) { if (ct > 0xFEFF) { ct = 0xFEFF; } - AddLog(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct); - char param[12]; - snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8); - uint8_t colormode = 2; // "ct" - zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x0A, param); + // AddLog(LOG_LEVEL_INFO, PSTR("ZigbeeHueCT 0x%04X - %d"), shortaddr, ct); + SBuffer s(4); + s.add16(ct); + s.add16(0x000A); // transition time = 1s + ZigbeeSendHue(shortaddr, 0x0300, 0x0A, s); + // char param[12]; + // snprintf_P(param, sizeof(param), PSTR("%02X%02X0A00"), ct & 0xFF, ct >> 8); + // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x0A, param); Z_Data_Light & light = zigbee_devices.getLight(shortaddr); - light.setColorMode(colormode); + light.setColorMode(2); // "ct" light.setCT(ct); } // XY void ZigbeeHueXY(uint16_t shortaddr, uint16_t x, uint16_t y) { - char param[16]; if (x > 0xFEFF) { x = 0xFEFF; } if (y > 0xFEFF) { y = 0xFEFF; } - snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8); - uint8_t colormode = 1; // "xy" - zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x07, param); + SBuffer s(8); + s.add16(x); + s.add16(y); + s.add16(0x000A); // transition time = 1s + ZigbeeSendHue(shortaddr, 0x0300, 0x07, s); + // char param[16]; + // snprintf_P(param, sizeof(param), PSTR("%02X%02X%02X%02X0A00"), x & 0xFF, x >> 8, y & 0xFF, y >> 8); + // uint8_t colormode = 1; // "xy" + // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x07, param); Z_Data_Light & light = zigbee_devices.getLight(shortaddr); - light.setColorMode(colormode); + light.setColorMode(1); // "xy" light.setX(x); light.setY(y); } // HueSat void ZigbeeHueHS(uint16_t shortaddr, uint16_t hue, uint8_t sat) { - char param[16]; uint8_t hue8 = changeUIntScale(hue, 0, 360, 0, 254); if (sat > 0xFE) { sat = 0xFE; } - snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat); - uint8_t colormode = 0; // "hs" - zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x06, param); + SBuffer s(4); + s.add8(hue); + s.add8(sat); + s.add16(0); + ZigbeeSendHue(shortaddr, 0x0300, 0x06, s); + // char param[16]; + // snprintf_P(param, sizeof(param), PSTR("%02X%02X0000"), hue8, sat); + // uint8_t colormode = 0; // "hs" + // zigbeeZCLSendStr(shortaddr, 0, 0, true, 0, 0x0300, 0x06, param); Z_Data_Light & light = zigbee_devices.getLight(shortaddr); - light.setColorMode(colormode); + light.setColorMode(0); // "hs" light.setSat(sat); light.setHue(hue); } diff --git a/tasmota/xdrv_23_zigbee_4_persistence.ino b/tasmota/xdrv_23_zigbee_4_persistence.ino index 42549ad6b..5f21c3103 100644 --- a/tasmota/xdrv_23_zigbee_4_persistence.ino +++ b/tasmota/xdrv_23_zigbee_4_persistence.ino @@ -436,9 +436,7 @@ void restoreDumpAllDevices(void) { for (const auto & device : zigbee_devices.getDevices()) { const SBuffer buf = hibernateDevicev2(device); if (buf.len() > 0) { - char hex_char[buf.len()*2+2]; - Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_RESTORE "\":\"ZbRestore %s\"}"), - ToHex_P(buf.buf(0), buf.len(), hex_char, sizeof(hex_char))); + Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_RESTORE "\":\"ZbRestore %_B\"}"), &buf); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_DATA)); } } diff --git a/tasmota/xdrv_23_zigbee_4a_nano_fs.ino b/tasmota/xdrv_23_zigbee_4a_nano_fs.ino index 007615c1b..54cbb3b54 100644 --- a/tasmota/xdrv_23_zigbee_4a_nano_fs.ino +++ b/tasmota/xdrv_23_zigbee_4a_nano_fs.ino @@ -20,7 +20,7 @@ #ifdef USE_ZIGBEE #ifdef USE_ZIGBEE_EZSP -#define Z_EEPROM_DEBUG +// #define Z_EEPROM_DEBUG // The EEPROM is 64KB in size with individually writable bytes. // They are conveniently organized in pages of 128 bytes to accelerate @@ -291,7 +291,7 @@ bool ZFS::findFileEntry(uint32_t name, ZFS_File_Entry & entry, uint8_t * _entry_ #ifdef Z_EEPROM_DEBUG // { // char hex_char[(sizeof(ZFS_File_Entry) * 2) + 2]; - // AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Read entry %d at address 0x%04X contains %s"), entry_idx, entry_addr, ToHex_P((uint8_t*)&entry, sizeof(entry), hex_char, sizeof(hex_char))); + // AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "Read entry %d at address 0x%04X contains %*_H"), entry_idx, entry_addr, sizeof(entry), &entry); // } #endif if (entry.name == name) { diff --git a/tasmota/xdrv_23_zigbee_4b_eeprom.ino b/tasmota/xdrv_23_zigbee_4b_eeprom.ino index 68f768783..b42b26a75 100644 --- a/tasmota/xdrv_23_zigbee_4b_eeprom.ino +++ b/tasmota/xdrv_23_zigbee_4b_eeprom.ino @@ -89,8 +89,7 @@ int32_t hydrateSingleDevice(const SBuffer & buf, size_t start, size_t len) { #ifdef Z_EEPROM_DEBUG { if (segment_len > 3) { - char hex_char[((segment_len+1) * 2) + 2]; - AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%s"), shortaddr, ToHex_P(buf.buf(start+3), segment_len+1-3, hex_char, sizeof(hex_char))); + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%*_H"), shortaddr, segment_len+1-3, buf.buf(start+3)); } } #endif @@ -182,16 +181,14 @@ SBuffer hibernateDeviceData(const struct Z_Device & device, bool mqtt = false) { buf.set8(0, buf.len() - 1); { - size_t buf_len = buf.len() - 3; - char hex[2*buf_len + 1]; // skip first 3 bytes - ToHex_P(buf.buf(3), buf_len, hex, sizeof(hex)); + size_t buf_len = buf.len() - 3; if (mqtt) { - Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_DATA "\":\"ZbData 0x%04X,%s\"}"), device.shortaddr, hex); + Response_P(PSTR("{\"" D_PRFX_ZB D_CMND_ZIGBEE_DATA "\":\"ZbData 0x%04X,%*_H\"}"), device.shortaddr, buf_len, buf.buf(3)); MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_STAT, PSTR(D_PRFX_ZB D_CMND_ZIGBEE_DATA)); } else { - AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%s"), device.shortaddr, hex); + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_ZIGBEE "ZbData 0x%04X,%*_H"), device.shortaddr, buf_len, buf.buf(3)); } } } diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index b1e94a84c..f91b50498 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -723,8 +723,6 @@ public: void log(void) { - char hex_char[_payload.len()*2+2]; - ToHex_P((unsigned char*)_payload.getBuffer(), _payload.len(), hex_char, sizeof(hex_char)); Response_P(PSTR("{\"" D_JSON_ZIGBEEZCL_RECEIVED "\":{" "\"groupid\":%d," "\"clusterid\":\"0x%04X\"," "\"srcaddr\":\"0x%04X\"," "\"srcendpoint\":%d," "\"dstendpoint\":%d," "\"wasbroadcast\":%d," @@ -732,14 +730,14 @@ public: "\"fc\":\"0x%02X\"," "\"frametype\":%d,\"direction\":%d,\"disableresp\":%d," "\"manuf\":\"0x%04X\",\"transact\":%d," - "\"cmdid\":\"0x%02X\",\"payload\":\"%s\"}}"), + "\"cmdid\":\"0x%02X\",\"payload\":\"%_B\"}}"), _groupaddr, _cluster_id, _srcaddr, _srcendpoint, _dstendpoint, _wasbroadcast, _linkquality, _securityuse, _seqnumber, _frame_control, _frame_control.b.frame_type, _frame_control.b.direction, _frame_control.b.disable_def_resp, _manuf_code, _transact_seq, _cmd_id, - hex_char); + &_payload); if (Settings.flag3.tuya_serial_mqtt_publish) { MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR)); } else { diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index 696c7ad1a..c805eab13 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -415,16 +415,16 @@ int32_t ZNP_ReceiveCheckVersion(int32_t res, SBuffer &buf) { int32_t EZ_ReceiveCheckVersion(int32_t res, SBuffer &buf) { uint8_t protocol_version = buf.get8(2); uint8_t stack_type = buf.get8(3); - uint16_t stack_version = buf.get16(4); + zigbee.ezsp_version = buf.get16(4); Response_P(PSTR("{\"" D_JSON_ZIGBEE_STATE "\":{" "\"Status\":%d,\"Version\":\"%d.%d.%d.%d\",\"Protocol\":%d" ",\"Stack\":%d}}"), ZIGBEE_STATUS_EZ_VERSION, - (stack_version & 0xF000) >> 12, - (stack_version & 0x0F00) >> 8, - (stack_version & 0x00F0) >> 4, - stack_version & 0x000F, + (zigbee.ezsp_version & 0xF000) >> 12, + (zigbee.ezsp_version & 0x0F00) >> 8, + (zigbee.ezsp_version & 0x00F0) >> 4, + zigbee.ezsp_version & 0x000F, protocol_version, stack_type ); @@ -432,7 +432,7 @@ int32_t EZ_ReceiveCheckVersion(int32_t res, SBuffer &buf) { MqttPublishPrefixTopicRulesProcess_P(RESULT_OR_TELE, PSTR(D_JSON_ZIGBEE_STATE)); if (0x08 == protocol_version) { - if ((stack_version & 0xFF00) == 0x6700) { + if ((zigbee.ezsp_version & 0xFF00) == 0x6700) { // If v6.7 there is a bug so we need to change the response ZBW(ZBR_SET_OK2, 0x00, 0x00 /*high*/, 0x00 /*ok*/) } diff --git a/tasmota/xdrv_23_zigbee_9_serial.ino b/tasmota/xdrv_23_zigbee_9_serial.ino index ccd8d2da9..84a560703 100644 --- a/tasmota/xdrv_23_zigbee_9_serial.ino +++ b/tasmota/xdrv_23_zigbee_9_serial.ino @@ -222,9 +222,6 @@ void ZigbeeInputLoop(void) { uint32_t frame_len = zigbee_buffer->len(); if (frame_complete || (frame_len && (millis() > (zigbee_polling_window + ZIGBEE_POLLING)))) { - char hex_char[frame_len * 2 + 2]; - ToHex_P((unsigned char*)zigbee_buffer->getBuffer(), zigbee_buffer->len(), hex_char, sizeof(hex_char)); - // AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "Bytes follow_read_metric = %0d"), ZigbeeSerial->getLoopReadMetric()); if ((frame_complete) && (frame_len >= 3)) { // frame received and has at least 3 bytes (without EOF), checking CRC @@ -246,7 +243,7 @@ void ZigbeeInputLoop(void) { // remove 2 last bytes if (crc_received != crc) { - AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %s"), crc_received, crc, hex_char); + AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": bad crc (received 0x%04X, computed 0x%04X) %_B"), crc_received, crc, &zigbee_buffer); } else { // copy buffer SBuffer ezsp_buffer = zigbee_buffer->subBuffer(0, frame_len - 2); // CRC @@ -262,14 +259,13 @@ void ZigbeeInputLoop(void) { } } - ToHex_P((unsigned char*)ezsp_buffer.getBuffer(), ezsp_buffer.len(), hex_char, sizeof(hex_char)); - AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "2\":\"%s\"}"), hex_char); + AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE "{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "2\":\"%_B\"}"), &ezsp_buffer); // now process the message ZigbeeProcessInputRaw(ezsp_buffer); } } else { // the buffer timed-out, print error and discard - AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %d"), hex_char); + AddLog_P(LOG_LEVEL_INFO, PSTR(D_JSON_ZIGBEE_EZSP_RECEIVED ": time-out, discarding %s, %_B"), &zigbee_buffer); } zigbee_buffer->setLen(0); // empty buffer escape = false; @@ -352,9 +348,7 @@ void ZigbeeZNPSend(const uint8_t *msg, size_t len) { //AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("ZNPSend FCS %02X"), fcs); } // Now send a MQTT message to report the sent message - char hex_char[(len * 2) + 2]; - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %s"), - ToHex_P(msg, len, hex_char, sizeof(hex_char))); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZNPSENT " %*_H"), len, msg); } // @@ -486,17 +480,13 @@ void ZigbeeEZSPSendRaw(const uint8_t *msg, size_t len, bool send_cancel) { } // Now send a MQTT message to report the sent message - char hex_char[(len * 2) + 2]; - AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT_RAW " %s"), - ToHex_P(msg, len, hex_char, sizeof(hex_char))); + AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEE_EZSP_SENT_RAW " %*_H"), len, msg); } // Send an EZSP command and data // Ex: Version with min v8 = 000008 void ZigbeeEZSPSendCmd(const uint8_t *msg, size_t len) { - char hex_char[len*2 + 2]; - ToHex_P(msg, len, hex_char, sizeof(hex_char)); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbEZSPSend %s"), hex_char); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE "ZbEZSPSend %*_H"), len, msg); SBuffer cmd(len+3); // prefix with seq number (1 byte) and frame control bytes (2 bytes) @@ -567,11 +557,8 @@ void ZigbeeProcessInputEZSP(SBuffer &buf) { } buf.setLen(buf.len() - 3); - char hex_char[buf.len()*2 + 2]; - // log message - ToHex_P((unsigned char*)buf.getBuffer(), buf.len(), hex_char, sizeof(hex_char)); - Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%s\"}"), hex_char); + Response_P(PSTR("{\"" D_JSON_ZIGBEE_EZSP_RECEIVED "\":\"%_B\"}"), &buf); if (Settings.flag3.tuya_serial_mqtt_publish) { MqttPublishPrefixTopicRulesProcess_P(TELE, PSTR(D_RSLT_SENSOR)); } else { diff --git a/tasmota/xdrv_23_zigbee_9a_upload.ino b/tasmota/xdrv_23_zigbee_9a_upload.ino index 79131078a..0bc60223d 100644 --- a/tasmota/xdrv_23_zigbee_9a_upload.ino +++ b/tasmota/xdrv_23_zigbee_9a_upload.ino @@ -264,9 +264,7 @@ bool ZigbeeUploadBootloaderPrompt(void) { } if (buf_len) { - char hex_char[256]; - ToHex_P(serial_buffer, buf_len, hex_char, 256); - AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("XMD: Rcvd %s"), hex_char); + AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("XMD: Rcvd %*_H"), buf_len, serial_buffer); } return ((4 == ZbUpload.byte_counter) && (millis() > XModem.flush_delay)); diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index 4455c5b0a..b2209d2f0 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -24,6 +24,9 @@ #include "UnishoxStrings.h" const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix + // SetOption synonyms + D_SO_ZIGBEE_NAMEKEY "|" D_SO_ZIGBEE_DEVICETOPIC "|" D_SO_ZIGBEE_NOPREFIX "|" D_SO_ZIGBEE_ENDPOINTSUFFIX "|" D_SO_ZIGBEE_NOAUTOBIND "|" + D_SO_ZIGBEE_NAMETOPIC "|" #ifdef USE_ZIGBEE_ZNP D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEEZNPRECEIVE "|" #endif // USE_ZIGBEE_ZNP @@ -39,6 +42,12 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEE_CONFIG "|" D_CMND_ZIGBEE_DATA ; +const uint8_t kZbSynonyms[] PROGMEM = { + 6, // number of synonyms + 83, 89, 100, 101, 110, + 112, +}; + void (* const ZigbeeCommand[])(void) PROGMEM = { #ifdef USE_ZIGBEE_ZNP &CmndZbZNPSend, &CmndZbZNPReceive, @@ -183,23 +192,7 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, } } - if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) { - // endpoint is not specified, let's try to find it from shortAddr, unless it's a group address - endpoint = zigbee_devices.findFirstEndpoint(shortaddr); - //AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); - } - AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %s"), - shortaddr, groupaddr, cluster, endpoint, cmd, param); - - if ((0 == endpoint) && (BAD_SHORTADDR != shortaddr)) { // endpoint null is ok for group address - AddLog_P(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); - return; - } - - // everything is good, we can send the command - - uint8_t seq = zigbee_devices.getNextSeqNumber(shortaddr); - ZigbeeZCLSend_Raw(ZigbeeZCLSendMessage({ + zigbeeZCLSendCmd(ZigbeeZCLSendMessage({ shortaddr, groupaddr, cluster /*cluster*/, @@ -209,14 +202,37 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, clusterSpecific /* not cluster specific */, true /* response */, false /* discover route */, - seq, /* zcl transaction id */ + 0, /* zcl transaction id */ buf.getBuffer(), buf.len() })); +} + +void zigbeeZCLSendCmd(const class ZigbeeZCLSendMessage &msg_const) { + ZigbeeZCLSendMessage msg = msg_const; // copy to a modifiable variable + + if ((0 == msg.endpoint) && (BAD_SHORTADDR != msg.shortaddr)) { + // endpoint is not specified, let's try to find it from shortAddr, unless it's a group address + msg.endpoint = zigbee_devices.findFirstEndpoint(msg.shortaddr); + //AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: guessing endpoint 0x%02X"), endpoint); + } + + AddLog_P(LOG_LEVEL_DEBUG, PSTR("ZbSend: shortaddr 0x%04X, groupaddr 0x%04X, cluster 0x%04X, endpoint 0x%02X, cmd 0x%02X, data %*_H"), + msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint, msg.cmd, msg.len, msg.msg); + + if ((0 == msg.endpoint) && (BAD_SHORTADDR != msg.shortaddr)) { // endpoint null is ok for group address + AddLog_P(LOG_LEVEL_INFO, PSTR("ZbSend: unspecified endpoint")); + return; + } + + // everything is good, we can send the command + + msg.transacId = zigbee_devices.getNextSeqNumber(msg.shortaddr); + ZigbeeZCLSend_Raw(msg); // now set the timer, if any, to read back the state later - if (clusterSpecific) { + if (msg.clusterSpecific) { if (!Settings.flag5.zb_disable_autoquery) { // read back attribute value unless it is disabled - sendHueUpdate(shortaddr, groupaddr, cluster, endpoint); + sendHueUpdate(msg.shortaddr, msg.groupaddr, msg.cluster, msg.endpoint); } } } @@ -463,7 +479,7 @@ void ZbSendSend(class JsonParserToken val_cmd, uint16_t device, uint16_t groupad const char *cmd_s = ""; // pointer to payload string bool clusterSpecific = true; - static char delim[] = ", "; // delimiters for parameters + static const char delim[] = ", "; // delimiters for parameters // probe the type of the argument // If JSON object, it's high level commands // If String, it's a low level command @@ -2175,7 +2191,7 @@ bool Xdrv23(uint8_t function) ZigbeeInit(); break; case FUNC_COMMAND: - result = DecodeCommand(kZbCommands, ZigbeeCommand); + result = DecodeCommand(kZbCommands, ZigbeeCommand, kZbSynonyms); break; case FUNC_SAVE_BEFORE_RESTART: #ifdef USE_ZIGBEE_EZSP diff --git a/tasmota/xdrv_41_tcp_bridge.ino b/tasmota/xdrv_41_tcp_bridge.ino index 9cc0b7942..aeadfde61 100644 --- a/tasmota/xdrv_41_tcp_bridge.ino +++ b/tasmota/xdrv_41_tcp_bridge.ino @@ -90,9 +90,7 @@ void TCPLoop(void) } } if (buf_len > 0) { - char hex_char[TCP_BRIDGE_BUF_SIZE+1]; - ToHex_P(tcp_buf, buf_len, hex_char, 256); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "from MCU: %s"), hex_char); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "from MCU: %*_H"), buf_len, tcp_buf); for (uint32_t i=0; i 0) { - char hex_char[TCP_BRIDGE_BUF_SIZE+1]; - ToHex_P(tcp_buf, buf_len, hex_char, 256); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "to MCU/%d: %s"), i+1, hex_char); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_TCP "to MCU/%d: %*_H"), i+1, buf_len, tcp_buf); TCPSerial->write(tcp_buf, buf_len); } } diff --git a/tasmota/xdrv_52_BLE_ESP32.ino b/tasmota/xdrv_52_BLE_ESP32.ino index e542ba427..162c434f9 100644 --- a/tasmota/xdrv_52_BLE_ESP32.ino +++ b/tasmota/xdrv_52_BLE_ESP32.ino @@ -279,7 +279,7 @@ const char * getStateString(int state); //int SafeAddLog_P(uint32_t loglevel, PGM_P formatP, ...); static void BLEDiag(); -const char *getAlias(uint8_t *addr); +const char *getAlias(const uint8_t *addr); //void BLEAliasMqttList(); void BLEAliasListResp(); //////////////////////////////////////////////////////////////////////// @@ -320,7 +320,7 @@ static void BLEGenNotifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, ui void BLEPostAdvert(ble_advertisment_t *Advertisment); static void BLEPostMQTTSeenDevices(int type); -static void BLEShow(bool json); +static void BLEShowStats(); static void BLEPostMQTT(bool json); static void BLEStartOperationTask(); @@ -390,7 +390,7 @@ uint8_t BLEAliasListTrigger = 0; // triggers send for ALL operations known about uint8_t BLEPostMQTTTrigger = 0; int BLEMaxAge = 60*10; // 10 minutes -int BLEAddressFilter = 3; +int BLEAddressFilter = 0; ////////////////////////////////////////////////// @@ -2181,6 +2181,7 @@ static void BLEEverySecond(bool restart){ if (BLEPublishDevices){ BLEPostMQTTSeenDevices(BLEPublishDevices); + BLEShowStats(); BLEPublishDevices = 0; } @@ -2403,7 +2404,7 @@ static const char *noAlias = PSTR(""); //////////////////////////////////////////// // use to display the alias name if required -const char *getAlias(uint8_t *addr){ +const char *getAlias(const uint8_t *addr){ if (!addr){ return noAlias; } @@ -3209,26 +3210,15 @@ static void mainThreadOpCallbacks() { } } - -static void BLEShow(bool json) -{ - if (json){ -#ifdef BLE_ESP32_DEBUG - if (BLEDebugMode > 0) AddLog(LOG_LEVEL_INFO,PSTR("BLE: show json %d"),json); -#endif - uint32_t totalCount = BLEAdvertisment.totalCount; - uint32_t deviceCount = seenDevices.size(); - - ResponseAppend_P(PSTR(",\"BLE\":{\"scans\":%u,\"adverts\":%u,\"devices\":%u,\"resets\":%u}"), BLEScanCount, totalCount, deviceCount, BLEResets); - } -#ifdef USE_WEBSERVER - else { - //WSContentSend_PD(HTTP_MI32, i+1,stemp,MIBLEsensors.size()); - } -#endif // USE_WEBSERVER - +static void BLEShowStats(){ + uint32_t totalCount = BLEAdvertisment.totalCount; + uint32_t deviceCount = seenDevices.size(); + ResponseTime_P(PSTR("")); + ResponseAppend_P(PSTR(",\"BLE\":{\"scans\":%u,\"adverts\":%u,\"devices\":%u,\"resets\":%u}}"), BLEScanCount, totalCount, deviceCount, BLEResets); + MqttPublishPrefixTopic_P(TELE, PSTR("BLE"), 0); } + /*void BLEAliasMqttList(){ ResponseTime_P(PSTR(",\"BLEAlias\":[")); for (int i = 0; i < aliases.size(); i++){ @@ -3495,7 +3485,6 @@ bool Xdrv52(uint8_t function) result = DecodeCommand(BLE_ESP32::kBLE_Commands, BLE_ESP32::BLE_Commands); break; case FUNC_JSON_APPEND: - BLE_ESP32::BLEShow(1); break; // next second, we will publish to our MQTT topic. @@ -3510,10 +3499,6 @@ bool Xdrv52(uint8_t function) case FUNC_WEB_ADD_HANDLER: WebServer_on(PSTR("/" WEB_HANDLE_BLE), BLE_ESP32::HandleBleConfiguration); break; - - case FUNC_WEB_SENSOR: - BLE_ESP32::BLEShow(0); - break; #endif // USE_WEBSERVER } return result; diff --git a/tasmota/xnrg_12_solaxX1.ino b/tasmota/xnrg_12_solaxX1.ino index c6af1f770..76b699b03 100644 --- a/tasmota/xnrg_12_solaxX1.ino +++ b/tasmota/xnrg_12_solaxX1.ino @@ -435,8 +435,6 @@ void solaxX1Show(bool json) char pv2_power[33]; dtostrfd(solaxX1.dc2_power, Settings.flag2.wattage_resolution, pv2_power); #endif - char temperature[33]; - dtostrfd(solaxX1.temperature, Settings.flag2.temperature_resolution, temperature); char runtime[33]; dtostrfd(solaxX1.runtime_total, 0, runtime); char status[33]; @@ -450,12 +448,12 @@ void solaxX1Show(bool json) ResponseAppend_P(PSTR(",\"" D_JSON_PV2_VOLTAGE "\":%s,\"" D_JSON_PV2_CURRENT "\":%s,\"" D_JSON_PV2_POWER "\":%s"), pv2_voltage, pv2_current, pv2_power); #endif - ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RUNTIME "\":%s,\"" D_JSON_STATUS "\":\"%s\",\"" D_JSON_ERROR "\":%d"), - temperature, runtime, status, solaxX1.errorCode); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_RUNTIME "\":%s,\"" D_JSON_STATUS "\":\"%s\",\"" D_JSON_ERROR "\":%d"), + Settings.flag2.temperature_resolution, &solaxX1.temperature, runtime, status, solaxX1.errorCode); #ifdef USE_DOMOTICZ // Avoid bad temperature report at beginning of the day (spikes of 1200 celsius degrees) - if (0 == TasmotaGlobal.tele_period && solaxX1.temperature < 100) { DomoticzSensor(DZ_TEMP, temperature); } + if (0 == TasmotaGlobal.tele_period && solaxX1.temperature < 100) { DomoticzFloatSensor(DZ_TEMP, solaxX1.temperature); } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER @@ -464,7 +462,7 @@ void solaxX1Show(bool json) #ifdef SOLAXX1_PV2 WSContentSend_PD(HTTP_SNS_solaxX1_DATA2, pv2_voltage, pv2_current, pv2_power); #endif - WSContentSend_PD(HTTP_SNS_TEMP, D_SOLAX_X1, temperature, TempUnit()); + WSContentSend_Temp(D_SOLAX_X1, solaxX1.temperature); char errorCodeString[33]; WSContentSend_PD(HTTP_SNS_solaxX1_DATA3, runtime, status, GetTextIndexed(errorCodeString, sizeof(errorCodeString), solaxX1_ParseErrorCode(solaxX1.errorCode), kSolaxError)); diff --git a/tasmota/xnrg_14_bl0940.ino b/tasmota/xnrg_14_bl0940.ino index 20df87f1a..81a9d4791 100644 --- a/tasmota/xnrg_14_bl0940.ino +++ b/tasmota/xnrg_14_bl0940.ino @@ -268,14 +268,11 @@ bool Bl0940Command(void) { } void Bl0940Show(bool json) { - char temperature[33]; - dtostrfd(Bl0940.temperature, Settings.flag2.temperature_resolution, temperature); - if (json) { - ResponseAppend_P(JSON_SNS_TEMP, "BL0940", temperature); + ResponseAppend_P(JSON_SNS_F_TEMP, "BL0940", Settings.flag2.temperature_resolution, &Bl0940.temperature); if (0 == TasmotaGlobal.tele_period) { #ifdef USE_DOMOTICZ - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, Bl0940.temperature); #endif // USE_DOMOTICZ #ifdef USE_KNX KnxSensor(KNX_TEMPERATURE, Bl0940.temperature); @@ -283,8 +280,9 @@ void Bl0940Show(bool json) { } #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, "", temperature, TempUnit()); + WSContentSend_Temp("", Bl0940.temperature); #endif // USE_WEBSERVER + } } diff --git a/tasmota/xsns_02_analog.ino b/tasmota/xsns_02_analog.ino index c0db2c1e5..2c8c4a4fa 100644 --- a/tasmota/xsns_02_analog.ino +++ b/tasmota/xsns_02_analog.ino @@ -457,15 +457,12 @@ void AdcShow(bool json) { break; } case ADC_TEMP: { - char temperature[33]; - dtostrfd(Adc[idx].temperature, Settings.flag2.temperature_resolution, temperature); - if (json) { AdcShowContinuation(&jsonflg); - ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "%s\":%s"), adc_idx, temperature); + ResponseAppend_P(PSTR("\"" D_JSON_TEMPERATURE "%s\":%*_f"), adc_idx, Settings.flag2.temperature_resolution, &Adc[idx].temperature); if ((0 == TasmotaGlobal.tele_period) && (!domo_flag[ADC_TEMP])) { #ifdef USE_DOMOTICZ - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, Adc[idx].temperature); domo_flag[ADC_TEMP] = true; #endif // USE_DOMOTICZ #ifdef USE_KNX @@ -474,7 +471,7 @@ void AdcShow(bool json) { } #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, adc_name, temperature, TempUnit()); + WSContentSend_Temp(adc_name, Adc[idx].temperature); #endif // USE_WEBSERVER } break; diff --git a/tasmota/xsns_05_ds18x20.ino b/tasmota/xsns_05_ds18x20.ino index 876d328be..f5cd6a3f9 100644 --- a/tasmota/xsns_05_ds18x20.ino +++ b/tasmota/xsns_05_ds18x20.ino @@ -499,9 +499,6 @@ void Ds18x20Show(bool json) uint8_t index = ds18x20_sensor[i].index; if (ds18x20_sensor[index].valid) { // Check for valid temperature - char temperature[33]; - dtostrfd(ds18x20_sensor[index].temperature, Settings.flag2.temperature_resolution, temperature); - Ds18x20Name(i); if (json) { @@ -509,10 +506,11 @@ void Ds18x20Show(bool json) for (uint32_t j = 0; j < 6; j++) { sprintf(address+2*j, "%02X", ds18x20_sensor[index].address[6-j]); // Skip sensor type and crc } - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, address, temperature); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%*_f}"), + ds18x20_types, address, Settings.flag2.temperature_resolution, &ds18x20_sensor[index].temperature); #ifdef USE_DOMOTICZ if ((0 == TasmotaGlobal.tele_period) && (0 == i)) { - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, ds18x20_sensor[index].temperature); } #endif // USE_DOMOTICZ #ifdef USE_KNX @@ -522,7 +520,7 @@ void Ds18x20Show(bool json) #endif // USE_KNX #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, ds18x20_types, temperature, TempUnit()); + WSContentSend_Temp(ds18x20_types, ds18x20_sensor[index].temperature); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_05_ds18x20_esp32.ino b/tasmota/xsns_05_ds18x20_esp32.ino index 8e2b20504..4f591b36a 100644 --- a/tasmota/xsns_05_ds18x20_esp32.ino +++ b/tasmota/xsns_05_ds18x20_esp32.ino @@ -200,9 +200,6 @@ void Ds18x20Show(bool json) uint8_t dsxflg = 0; for (uint32_t i = 0; i < ds18x20_sensors; i++) { if (Ds18x20Read(i, t)) { // Check if read failed - char temperature[33]; - dtostrfd(t, Settings.flag2.temperature_resolution, temperature); - Ds18x20Name(i); if (json) { @@ -210,11 +207,12 @@ void Ds18x20Show(bool json) for (uint32_t j = 0; j < 6; j++) { sprintf(address+2*j, "%02X", ds18x20_address[ds18x20_index[i]][6-j]); // Skip sensor type and crc } - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%s}"), ds18x20_types, address, temperature); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_ID "\":\"%s\",\"" D_JSON_TEMPERATURE "\":%*_f}"), + ds18x20_types, address, Settings.flag2.temperature_resolution, &t); dsxflg++; #ifdef USE_DOMOTICZ if ((0 == TasmotaGlobal.tele_period) && (1 == dsxflg)) { - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, t); } #endif // USE_DOMOTICZ #ifdef USE_KNX @@ -224,7 +222,7 @@ void Ds18x20Show(bool json) #endif // USE_KNX #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, ds18x20_types, temperature, TempUnit()); + WSContentSend_Temp(ds18x20_types, t); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_09_bmp.ino b/tasmota/xsns_09_bmp.ino index b30615107..3b472dceb 100644 --- a/tasmota/xsns_09_bmp.ino +++ b/tasmota/xsns_09_bmp.ino @@ -527,8 +527,6 @@ void BmpShow(bool json) snprintf_P(name, sizeof(name), PSTR("%s%c%02X"), name, IndexSeparator(), bmp_sensors[bmp_idx].bmp_address); // BMXXXX-XX } - char temperature[33]; - dtostrfd(bmp_temperature, Settings.flag2.temperature_resolution, temperature); char pressure[33]; dtostrfd(bmp_pressure, Settings.flag2.pressure_resolution, pressure); char sea_pressure[33]; @@ -554,16 +552,16 @@ void BmpShow(bool json) char json_gas[40]; snprintf_P(json_gas, sizeof(json_gas), PSTR(",\"" D_JSON_GAS "\":%s"), gas_resistance); - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s%s}"), + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f%s,\"" D_JSON_PRESSURE "\":%s%s%s}"), name, - temperature, + Settings.flag2.temperature_resolution, &bmp_temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : "", (bmp_sensors[bmp_idx].bmp_model >= 3) ? json_gas : ""); #else - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s,\"" D_JSON_PRESSURE "\":%s%s}"), - name, temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : ""); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f%s,\"" D_JSON_PRESSURE "\":%s%s}"), + name, Settings.flag2.temperature_resolution, &bmp_temperature, (bmp_sensors[bmp_idx].bmp_model >= 2) ? json_humidity : "", pressure, (Settings.altitude != 0) ? json_sealevel : ""); #endif // USE_BME680 #ifdef USE_DOMOTICZ @@ -584,7 +582,7 @@ void BmpShow(bool json) #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, name, temperature, TempUnit()); + WSContentSend_Temp(name, bmp_temperature); if (bmp_sensors[bmp_idx].bmp_model >= 2) { WSContentSend_PD(HTTP_SNS_HUM, name, humidity); WSContentSend_PD(HTTP_SNS_DEW, name, dewpoint, TempUnit()); diff --git a/tasmota/xsns_15_mhz19.ino b/tasmota/xsns_15_mhz19.ino index bfde603a1..1536fbd25 100644 --- a/tasmota/xsns_15_mhz19.ino +++ b/tasmota/xsns_15_mhz19.ino @@ -338,23 +338,22 @@ void MhzInit(void) void MhzShow(bool json) { char types[7] = "MHZ19B"; // MHZ19B for legacy reasons. Prefered is MHZ19 - char temperature[33]; - dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature); char model[3]; GetTextIndexed(model, sizeof(model), mhz_type -1, kMhzModels); if (json) { - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s}"), types, model, mhz_last_ppm, temperature); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_MODEL "\":\"%s\",\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%*_f}"), + types, model, mhz_last_ppm, Settings.flag2.temperature_resolution, &mhz_temperature); #ifdef USE_DOMOTICZ if (0 == TasmotaGlobal.tele_period) { DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm); - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, mhz_temperature); } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_CO2, types, mhz_last_ppm); - WSContentSend_PD(HTTP_SNS_TEMP, types, temperature, TempUnit()); + WSContentSend_Temp(types, mhz_temperature); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_26_lm75ad.ino b/tasmota/xsns_26_lm75ad.ino index 4f65f3d51..73c6140cc 100644 --- a/tasmota/xsns_26_lm75ad.ino +++ b/tasmota/xsns_26_lm75ad.ino @@ -85,17 +85,15 @@ float LM75ADGetTemp(void) void LM75ADShow(bool json) { float t = LM75ADGetTemp(); - char temperature[33]; - dtostrfd(t, Settings.flag2.temperature_resolution, temperature); if (json) { - ResponseAppend_P(JSON_SNS_TEMP, "LM75AD", temperature); + ResponseAppend_P(JSON_SNS_F_TEMP, "LM75AD", Settings.flag2.temperature_resolution, &t); #ifdef USE_DOMOTICZ - if (0 == TasmotaGlobal.tele_period) DomoticzSensor(DZ_TEMP, temperature); + if (0 == TasmotaGlobal.tele_period) DomoticzFloatSensor(DZ_TEMP, t); #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, "LM75AD", temperature, TempUnit()); + WSContentSend_Temp("LM75AD", t); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_32_mpu6050.ino b/tasmota/xsns_32_mpu6050.ino index b8d4c4d62..e0e738417 100644 --- a/tasmota/xsns_32_mpu6050.ino +++ b/tasmota/xsns_32_mpu6050.ino @@ -182,8 +182,6 @@ void MPU_6050Show(bool json) MPU_6050PerformReading(); float tempConv = ConvertTemp(MPU_6050_temperature / 340.0 + 35.53); - char temperature[33]; - dtostrfd(tempConv, Settings.flag2.temperature_resolution, temperature); char axis_ax[33]; dtostrfd(MPU_6050_ax, Settings.flag2.axis_resolution, axis_ax); char axis_ay[33]; @@ -225,19 +223,19 @@ void MPU_6050Show(bool json) snprintf_P(json_ypr_p, sizeof(json_ypr_p), PSTR(",\"" D_JSON_PITCH "\":%s"), axis_pitch); char json_ypr_r[25]; snprintf_P(json_ypr_r, sizeof(json_ypr_r), PSTR(",\"" D_JSON_ROLL "\":%s"), axis_roll); - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s%s%s%s}"), - D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz, + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f%s%s%s%s%s%s%s%s%s}"), + D_SENSOR_MPU6050, Settings.flag2.temperature_resolution, &tempConv, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz, json_ypr_y, json_ypr_p, json_ypr_r); #else - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s%s%s%s%s%s%s}"), - D_SENSOR_MPU6050, temperature, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f%s%s%s%s%s%s}"), + D_SENSOR_MPU6050, Settings.flag2.temperature_resolution, &tempConv, json_axis_ax, json_axis_ay, json_axis_az, json_axis_gx, json_axis_gy, json_axis_gz); #endif // USE_MPU6050_DMP #ifdef USE_DOMOTICZ - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, tempConv); #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, D_SENSOR_MPU6050, temperature, TempUnit()); + WSContentSend_Temp(D_SENSOR_MPU6050, tempConv); WSContentSend_PD(HTTP_SNS_AXIS, axis_ax, axis_ay, axis_az, axis_gx, axis_gy, axis_gz); #ifdef USE_MPU6050_DMP WSContentSend_PD(HTTP_SNS_YPR, axis_yaw, axis_pitch, axis_roll); diff --git a/tasmota/xsns_35_tx20.ino b/tasmota/xsns_35_tx20.ino index bf3e4a8ef..87c707350 100644 --- a/tasmota/xsns_35_tx20.ino +++ b/tasmota/xsns_35_tx20.ino @@ -80,16 +80,16 @@ extern "C" { #define D_TX20_WIND_ANGLE "∠" #define D_TX20_WIND_DEGREE "°" const char HTTP_SNS_TX2X[] PROGMEM = - "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED "{m}%s %s{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED "{m}%1_f %s{e}" #ifndef USE_TX2X_WIND_SENSOR_NOSTATISTICS - "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED " " D_TX20_WIND_AVG "{m}%s %s{e}" - "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MIN "{m}%s %s{e}" - "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MAX "{m}%s %s{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED " " D_TX20_WIND_AVG "{m}%1_f %s{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MIN "{m}%1_f %s{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_SPEED_MAX "{m}%1_f %s{e}" #endif // USE_TX2X_WIND_SENSOR_NOSTATISTICS - "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s %s" D_TX20_WIND_DEGREE "{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION "{m}%s %1_f" D_TX20_WIND_DEGREE "{e}" #ifndef USE_TX2X_WIND_SENSOR_NOSTATISTICS - "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION " " D_TX20_WIND_AVG "{m}%s %s" D_TX20_WIND_DEGREE "{e}" - "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION " " D_TX20_WIND_ANGLE "{m}%s" D_TX20_WIND_DEGREE " (%s,%s)" D_TX20_WIND_DEGREE; + "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION " " D_TX20_WIND_AVG "{m}%s %1_f" D_TX20_WIND_DEGREE "{e}" + "{s}" D_TX2x_NAME " " D_TX20_WIND_DIRECTION " " D_TX20_WIND_ANGLE "{m}%1_f" D_TX20_WIND_DEGREE " (%1_f,%1_f)" D_TX20_WIND_DEGREE; #endif // USE_TX2X_WIND_SENSOR_NOSTATISTICS ; #endif // USE_WEBSERVER @@ -412,19 +412,13 @@ void Tx2xRead(void) } #ifdef DEBUG_TASMOTA_SENSOR - char diravg[FLOATSZ]; - dtostrfd(tx2x_wind_direction_avg, 1, diravg); - char cosx[FLOATSZ]; - dtostrfd(tx2x_wind_direction_avg_x, 1, cosx); - char siny[FLOATSZ]; - dtostrfd(tx2x_wind_direction_avg_y, 1, siny); - DEBUG_SENSOR_LOG(PSTR(D_TX2x_NAME ": dir stat - counter=%ld, actint=%ld, avgint=%ld, avg=%s (cosx=%s, siny=%s), min %d, max %d"), + DEBUG_SENSOR_LOG(PSTR(D_TX2x_NAME ": dir stat - counter=%ld, actint=%ld, avgint=%ld, avg=%1_f (cosx=%1_f, siny=%1_f), min %d, max %d"), (TasmotaGlobal.uptime-tx2x_last_uptime), tx2x_wind_direction, tx2x_wind_direction_avg_int, - diravg, - cosx, - siny, + &tx2x_wind_direction_avg, + &tx2x_wind_direction_avg_x, + &tx2x_wind_direction_avg_y, tx2x_wind_direction_min, tx2x_wind_direction_max ); @@ -484,86 +478,77 @@ void Tx2xShow(bool json) { if (!Tx2xAvailable()) { return; } - char wind_speed_string[FLOATSZ]; - dtostrfd(ConvertSpeed(tx2x_wind_speed)/10, 1, wind_speed_string); - char wind_direction_string[FLOATSZ]; - dtostrfd(tx2x_wind_direction*22.5, 1, wind_direction_string); + float wind_speed_float = ConvertSpeed(tx2x_wind_speed) / 10; + float wind_direction_float = tx2x_wind_direction * 22.5; char wind_direction_cardinal_string[TX2X_DIRECTIONS_MAXSIZE+1]; GetTextIndexed(wind_direction_cardinal_string, sizeof(wind_direction_cardinal_string), tx2x_wind_direction, kTx2xDirections); #ifndef USE_TX2X_WIND_SENSOR_NOSTATISTICS - char wind_speed_min_string[FLOATSZ]; - dtostrfd(ConvertSpeed(tx2x_wind_speed_min)/10, 1, wind_speed_min_string); - char wind_speed_max_string[FLOATSZ]; - dtostrfd(ConvertSpeed(tx2x_wind_speed_max)/10, 1, wind_speed_max_string); - char wind_speed_avg_string[FLOATSZ]; - dtostrfd(ConvertSpeed(tx2x_wind_speed_avg)/10, 1, wind_speed_avg_string); - char wind_direction_avg_string[FLOATSZ]; - dtostrfd(tx2x_wind_direction_avg, 1, wind_direction_avg_string); + float wind_speed_min_float = ConvertSpeed(tx2x_wind_speed_min) / 10; + float wind_speed_max_float = ConvertSpeed(tx2x_wind_speed_max) / 10; + float wind_speed_avg_float = ConvertSpeed(tx2x_wind_speed_avg) / 10; + float wind_direction_avg_float = tx2x_wind_direction_avg; char wind_direction_avg_cardinal_string[4]; GetTextIndexed(wind_direction_avg_cardinal_string, sizeof(wind_direction_avg_cardinal_string), int((tx2x_wind_direction_avg/22.5f)+0.5f) % 16, kTx2xDirections); - char wind_direction_range_string[FLOATSZ]; - dtostrfd(Tx2xNormalize(tx2x_wind_direction_max-tx2x_wind_direction_min)*22.5, 1, wind_direction_range_string); - char wind_direction_min_string[FLOATSZ]; - dtostrfd(Tx2xNormalize(tx2x_wind_direction_min)*22.5, 1, wind_direction_min_string); - char wind_direction_max_string[FLOATSZ]; - dtostrfd(Tx2xNormalize(tx2x_wind_direction_max)*22.5, 1, wind_direction_max_string); + float wind_direction_range_float = (tx2x_wind_direction_max-tx2x_wind_direction_min) * 22.5; + float wind_direction_min_float = Tx2xNormalize(tx2x_wind_direction_min) * 22.5; + float wind_direction_max_float = tx2x_wind_direction_max * 22.5; #endif // USE_TX2X_WIND_SENSOR_NOSTATISTICS if (json) { #ifndef USE_TX2X_WIND_SENSOR_NOSTATISTICS #ifdef USE_TX2x_LEGACY_JSON - ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":%s,\"SpeedAvg\":%s,\"SpeedMax\":%s,\"Direction\":\"%s\",\"Degree\":%s}"), - wind_speed_string, - wind_speed_avg_string, - wind_speed_max_string, + ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":%1_f,\"SpeedAvg\":%1_f,\"SpeedMax\":%1_f,\"Direction\":\"%s\",\"Degree\":%1_f}"), + &wind_speed_float, + &wind_speed_avg_float, + &wind_speed_max_float, wind_direction_cardinal_string, - wind_direction_string + &wind_direction_float ); #else // USE_TX2x_LEGACY_JSON - ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":{\"Act\":%s,\"Avg\":%s,\"Min\":%s,\"Max\":%s},\"Dir\":{\"Card\":\"%s\",\"Deg\":%s,\"Avg\":%s,\"AvgCard\":\"%s\",\"Min\":%s,\"Max\":%s,\"Range\":%s}}"), - wind_speed_string, - wind_speed_avg_string, - wind_speed_min_string, - wind_speed_max_string, + ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":{\"Act\":%1_f,\"Avg\":%1_f,\"Min\":%1_f,\"Max\":%1_f},\"Dir\":{\"Card\":\"%s\",\"Deg\":%1_f,\"Avg\":%1_f,\"AvgCard\":\"%s\",\"Min\":%1_f,\"Max\":%1_f,\"Range\":%1_f}}"), + &wind_speed_float, + &wind_speed_avg_float, + &wind_speed_min_float, + &wind_speed_max_float, wind_direction_cardinal_string, - wind_direction_string, - wind_direction_avg_string, + &wind_direction_float, + &wind_direction_avg_float, wind_direction_avg_cardinal_string, - wind_direction_min_string, - wind_direction_max_string, - wind_direction_range_string + &wind_direction_min_float, + &wind_direction_max_float, + &wind_direction_range_float ); #endif // USE_TX2x_LEGACY_JSON #else // USE_TX2X_WIND_SENSOR_NOSTATISTICS #ifdef USE_TX2x_LEGACY_JSON - ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":%s,\"Direction\":\"%s\",\"Degree\":%s}"), - wind_speed_string, wind_direction_cardinal_string, wind_direction_string); + ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":%1_f,\"Direction\":\"%s\",\"Degree\":%1_f}"), + &wind_speed_float, wind_direction_cardinal_string, &wind_direction_float); #else // USE_TX2x_LEGACY_JSON - ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":{\"Act\":%s},\"Dir\":{\"Card\":\"%s\",\"Deg\":%s}}"), - wind_speed_string, wind_direction_cardinal_string, wind_direction_string); + ResponseAppend_P(PSTR(",\"" D_TX2x_NAME "\":{\"" D_JSON_SPEED "\":{\"Act\":%1_f},\"Dir\":{\"Card\":\"%s\",\"Deg\":%1_f}}"), + &wind_speed_float, wind_direction_cardinal_string, &wind_direction_float); #endif // USE_TX2x_LEGACY_JSON #endif // USE_TX2X_WIND_SENSOR_NOSTATISTICS #ifdef USE_WEBSERVER } else { WSContentSend_PD(HTTP_SNS_TX2X, - wind_speed_string, + &wind_speed_float, SpeedUnit().c_str(), #ifndef USE_TX2X_WIND_SENSOR_NOSTATISTICS - wind_speed_avg_string, + &wind_speed_avg_float, SpeedUnit().c_str(), - wind_speed_min_string, + &wind_speed_min_float, SpeedUnit().c_str(), - wind_speed_max_string, + &wind_speed_max_float, SpeedUnit().c_str(), #endif // USE_TX2X_WIND_SENSOR_NOSTATISTICS wind_direction_cardinal_string, - wind_direction_string + &wind_direction_float #ifndef USE_TX2X_WIND_SENSOR_NOSTATISTICS ,wind_direction_avg_cardinal_string, - wind_direction_avg_string, - wind_direction_range_string, - wind_direction_min_string, - wind_direction_max_string + &wind_direction_avg_float, + &wind_direction_range_float, + &wind_direction_min_float, + &wind_direction_max_float #endif // USE_TX2X_WIND_SENSOR_NOSTATISTICS ); #endif // USE_WEBSERVER diff --git a/tasmota/xsns_37_rfsensor.ino b/tasmota/xsns_37_rfsensor.ino index 66dcf17d4..424928a4a 100644 --- a/tasmota/xsns_37_rfsensor.ino +++ b/tasmota/xsns_37_rfsensor.ino @@ -272,22 +272,21 @@ void RfSnsTheoV2Show(bool json) sensor, GetDT(rfsns_theo_v2_t1[i].time).c_str(), voltage); } } else { - char temperature[33]; - dtostrfd(ConvertTemp((float)rfsns_theo_v2_t1[i].temp / 100), Settings.flag2.temperature_resolution, temperature); + float temp = ConvertTemp((float)rfsns_theo_v2_t1[i].temp / 100) if (json) { - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_VOLTAGE "\":%s}"), - sensor, temperature, rfsns_theo_v2_t1[i].lux, voltage); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_VOLTAGE "\":%s}"), + sensor, Settings.flag2.temperature_resolution, &temp, rfsns_theo_v2_t1[i].lux, voltage); #ifdef USE_DOMOTICZ if ((0 == TasmotaGlobal.tele_period) && !sensor_once) { - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, temp); DomoticzSensor(DZ_ILLUMINANCE, rfsns_theo_v2_t1[i].lux); sensor_once = true; } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, sensor, temperature, TempUnit()); + WSContentSend_Temp(sensor, temp); WSContentSend_PD(HTTP_SNS_ILLUMINANCE, sensor, rfsns_theo_v2_t1[i].lux); #endif // USE_WEBSERVER } diff --git a/tasmota/xsns_39_max31855.ino b/tasmota/xsns_39_max31855.ino index 2a6304ce1..fafa9f0de 100644 --- a/tasmota/xsns_39_max31855.ino +++ b/tasmota/xsns_39_max31855.ino @@ -139,20 +139,18 @@ void MAX31855_GetResult(void) { } void MAX31855_Show(bool Json) { - char probetemp[33]; - char referencetemp[33]; - dtostrfd(MAX31855_Result.ProbeTemperature, Settings.flag2.temperature_resolution, probetemp); - dtostrfd(MAX31855_Result.ReferenceTemperature, Settings.flag2.temperature_resolution, referencetemp); - char sensor_name[10]; GetTextIndexed(sensor_name, sizeof(sensor_name), Settings.flag4.max6675, kMax31855Types); if (Json) { - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_PROBETEMPERATURE "\":%s,\"" D_JSON_REFERENCETEMPERATURE "\":%s,\"" D_JSON_ERROR "\":%d}"), \ - sensor_name, probetemp, referencetemp, MAX31855_Result.ErrorCode); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_PROBETEMPERATURE "\":%*_f,\"" D_JSON_REFERENCETEMPERATURE "\":%*_f,\"" D_JSON_ERROR "\":%d}"), \ + sensor_name, + Settings.flag2.temperature_resolution, &MAX31855_Result.ProbeTemperature, + Settings.flag2.temperature_resolution, &MAX31855_Result.ReferenceTemperature, + MAX31855_Result.ErrorCode); #ifdef USE_DOMOTICZ if (0 == TasmotaGlobal.tele_period) { - DomoticzSensor(DZ_TEMP, probetemp); + DomoticzFloatSensor(DZ_TEMP, MAX31855_Result.ProbeTemperature); } #endif // USE_DOMOTICZ #ifdef USE_KNX @@ -162,7 +160,7 @@ void MAX31855_Show(bool Json) { #endif // USE_KNX #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, probetemp, TempUnit()); + WSContentSend_Temp(sensor_name, MAX31855_Result.ProbeTemperature); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_47_max31865.ino b/tasmota/xsns_47_max31865.ino index 388da057f..544778d92 100644 --- a/tasmota/xsns_47_max31865.ino +++ b/tasmota/xsns_47_max31865.ino @@ -88,18 +88,15 @@ void MAX31865_Show(bool Json) { uint8_t report_once = 0; for (uint32_t i = 0; i < MAX_MAX31865S; i++) { if (max31865_pins_used & (1 << i)) { - char temperature[33]; - char resistance[33]; - - dtostrfd(MAX31865_Result[i].PtdResistance, Settings.flag2.temperature_resolution, resistance); - dtostrfd(MAX31865_Result[i].PtdTemp, Settings.flag2.temperature_resolution, temperature); - if (Json) { - ResponseAppend_P(PSTR(",\"MAX31865%c%d\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_RESISTANCE "\":%s,\"" D_JSON_ERROR "\":%d}"), \ - IndexSeparator(), i, temperature, resistance, MAX31865_Result[i].ErrorCode); + ResponseAppend_P(PSTR(",\"MAX31865%c%d\":{\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_RESISTANCE "\":%*_f,\"" D_JSON_ERROR "\":%d}"), \ + IndexSeparator(), i, + Settings.flag2.temperature_resolution, &MAX31865_Result[i].PtdTemp, + Settings.flag2.temperature_resolution, &MAX31865_Result[i].PtdResistance, + MAX31865_Result[i].ErrorCode); if ((0 == TasmotaGlobal.tele_period) && (!report_once)) { #ifdef USE_DOMOTICZ - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, MAX31865_Result[i].PtdTemp); #endif // USE_DOMOTICZ #ifdef USE_KNX KnxSensor(KNX_TEMPERATURE, MAX31865_Result[i].PtdTemp); @@ -110,7 +107,7 @@ void MAX31865_Show(bool Json) { } else { char sensorname[33]; sprintf(sensorname, "MAX31865%c%d", IndexSeparator(), i); - WSContentSend_PD(HTTP_SNS_TEMP, sensorname, temperature, TempUnit()); + WSContentSend_Temp(sensorname, MAX31865_Result[i].PtdTemp); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_48_chirp.ino b/tasmota/xsns_48_chirp.ino index bb887cf85..bfe1dc5a5 100644 --- a/tasmota/xsns_48_chirp.ino +++ b/tasmota/xsns_48_chirp.ino @@ -417,10 +417,8 @@ void ChirpShow(bool json) { for (uint32_t i = 0; i < chirp_found_sensors; i++) { if (chirp_sensor[i].version) { - // convert double values to string - char str_temperature[33]; - double t_temperature = ((double) chirp_sensor[i].temperature )/10.0; - dtostrfd(t_temperature, Settings.flag2.temperature_resolution, str_temperature); + float t_temperature = ConvertTemp(((float)chirp_sensor[i].temperature )/10.0); + char str_light[33]; dtostrfd(chirp_sensor[i].light, 0, str_light); char str_version[7]; @@ -435,7 +433,7 @@ void ChirpShow(bool json) if(!chirp_sensor[i].explicitSleep) { ResponseAppend_P(PSTR(",\"%s%u\":{\"" D_JSON_MOISTURE "\":%d"), chirp_name, i, chirp_sensor[i].moisture); if(chirp_sensor[i].temperature!=-1){ // this is the error code -> no temperature - ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"),str_temperature); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"), Settings.flag2.temperature_resolution, &t_temperature); } ResponseAppend_P(PSTR(",\"" D_JSON_DARKNESS "\":%s}"),str_light); } @@ -458,7 +456,7 @@ void ChirpShow(bool json) WSContentSend_PD(HTTP_SNS_MOISTURE, "", chirp_sensor[i].moisture); WSContentSend_PD(HTTP_SNS_DARKNESS, str_light); if (chirp_sensor[i].temperature!=-1) { // this is the error code -> no temperature - WSContentSend_PD(HTTP_SNS_TEMP, "", str_temperature, TempUnit()); + WSContentSend_Temp("", t_temperature); } } diff --git a/tasmota/xsns_59_ds1624.ino b/tasmota/xsns_59_ds1624.ino index 90633724b..7afb4e679 100644 --- a/tasmota/xsns_59_ds1624.ino +++ b/tasmota/xsns_59_ds1624.ino @@ -176,18 +176,16 @@ void DS1624EverySecond(void) void DS1624Show(bool json) { - char temperature[33]; bool once = true; for (uint32_t i = 0; i < DS1624_MAX_SENSORS; i++) { if (!ds1624_sns[i].valid) { continue; } - dtostrfd(ds1624_sns[i].value, Settings.flag2.temperature_resolution, temperature); if (json) { - ResponseAppend_P(JSON_SNS_TEMP, ds1624_sns[i].name, temperature); + ResponseAppend_P(JSON_SNS_F_TEMP, ds1624_sns[i].name, Settings.flag2.temperature_resolution, &ds1624_sns[i].value); if ((0 == TasmotaGlobal.tele_period) && once) { #ifdef USE_DOMOTICZ - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, ds1624_sns[i].value); #endif // USE_DOMOTICZ #ifdef USE_KNX KnxSensor(KNX_TEMPERATURE, ds1624_sns[i].value); @@ -196,7 +194,7 @@ void DS1624Show(bool json) } #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, ds1624_sns[i].name, temperature, TempUnit()); + WSContentSend_Temp(ds1624_sns[i].name, ds1624_sns[i].value); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_61_MI_NRF24.ino b/tasmota/xsns_61_MI_NRF24.ino index 01dc309be..f66d2de35 100644 --- a/tasmota/xsns_61_MI_NRF24.ino +++ b/tasmota/xsns_61_MI_NRF24.ino @@ -1738,9 +1738,8 @@ void MINRFShow(bool json) ||(hass_mode==2) #endif //USE_HOME_ASSISTANT ) { - char temperature[FLOATSZ]; - dtostrfd(MIBLEsensors[i].temp, Settings.flag2.temperature_resolution, temperature); - ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"), temperature); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"), + Settings.flag2.temperature_resolution, &MIBLEsensors[i].temp); } } } @@ -1897,9 +1896,7 @@ void MINRFShow(bool json) if (MIBLEsensors[i].type==YEERC) continue; if (MIBLEsensors[i].type==FLORA){ if(!isnan(MIBLEsensors[i].temp)){ - char temperature[FLOATSZ]; - dtostrfd(MIBLEsensors[i].temp, Settings.flag2.temperature_resolution, temperature); - WSContentSend_PD(HTTP_SNS_TEMP, kMINRFDeviceType[MIBLEsensors[i].type-1], temperature, TempUnit()); + WSContentSend_Temp(kMINRFDeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp); } if(MIBLEsensors[i].lux!=0xffffffff){ // this is the error code -> no valid value WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kMINRFDeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux); diff --git a/tasmota/xsns_62_MI_ESP32.ino b/tasmota/xsns_62_MI_ESP32.ino index 8bb595625..9e1b423d1 100644 --- a/tasmota/xsns_62_MI_ESP32.ino +++ b/tasmota/xsns_62_MI_ESP32.ino @@ -2052,9 +2052,8 @@ void MI32Show(bool json) ||(hass_mode!=-1) #endif //USE_HOME_ASSISTANT ) { - char temperature[FLOATSZ]; - dtostrfd(MIBLEsensors[i].temp, Settings.flag2.temperature_resolution, temperature); - ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"), temperature); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"), + Settings.flag2.temperature_resolution, &MIBLEsensors[i].temp); } } } @@ -2211,9 +2210,7 @@ void MI32Show(bool json) WSContentSend_PD(HTTP_RSSI, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].RSSI); if (MIBLEsensors[i].type==FLORA) { if (!isnan(MIBLEsensors[i].temp)) { - char temperature[FLOATSZ]; - dtostrfd(MIBLEsensors[i].temp, Settings.flag2.temperature_resolution, temperature); - WSContentSend_PD(HTTP_SNS_TEMP, kMI32DeviceType[MIBLEsensors[i].type-1], temperature, TempUnit()); + WSContentSend_Temp(kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp); } if (MIBLEsensors[i].moisture!=0xff) { WSContentSend_PD(HTTP_SNS_MOISTURE, kMI32DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].moisture); diff --git a/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino b/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino index 2843102d9..f563e9f1a 100644 --- a/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino +++ b/tasmota/xsns_62_MI_ESP32_BLE_ESP32.ino @@ -79,6 +79,7 @@ void MI32notifyCB(NimBLERemoteCharacteristic* pRemoteCharacteristic, uint8_t* pD struct { uint16_t perPage = 4; uint8_t mqttCurrentSlot = 0; + uint8_t mqttCurrentSingleSlot = 0; uint32_t period; // set manually in addition to TELE-period, is set to TELE-period after start int secondsCounter = 0; // counts up in MI32EverySecond to period int secondsCounter2 = 0; // counts up in MI32EverySecond to period @@ -116,6 +117,7 @@ struct { uint32_t showRSSI:1; uint32_t ignoreBogusBattery:1; uint32_t minimalSummary:1; // DEPRECATED!! + uint32_t onlyAliased:1; // only include sensors that are aliased } option; } MI32; @@ -255,6 +257,8 @@ struct mi_sensor_t{ uint8_t type; //MI_Flora = 1; MI_MI-HT_V1=2; MI_LYWSD02=3; MI_LYWSD03=4; MI_CGG1=5; MI_CGD1=6 uint8_t needkey; // tells http to display needkey message with link uint8_t lastCnt; //device generated counter of the packet + uint8_t nextDiscoveryData; // used to lkimit discovery to one MQTT per sec + uint8_t shallSendMQTT; uint8_t MAC[6]; union { @@ -918,6 +922,12 @@ int MI32advertismentCallback(BLE_ESP32::ble_advertisment_t *pStruct) int RSSI = pStruct->RSSI; const uint8_t *addr = pStruct->addr; if(MI32isInBlockList(addr) == true) return 0; + if (MI32.option.onlyAliased){ + const char *alias = BLE_ESP32::getAlias(addr); + if (!alias || !(*alias)){ + return 0; + } + } int svcdataCount = advertisedDevice->getServiceDataCount(); @@ -1880,6 +1890,9 @@ void MI32EverySecond(bool restart){ MI32ShowSomeSensors(); + MI32DiscoveryOneMISensor(); + MI32ShowOneMISensor(); + // read a battery if // MI32.batteryreader.slot < filled and !MI32.batteryreader.active readOneBat(); @@ -1906,10 +1919,12 @@ void MI32EverySecond(bool restart){ AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Kick off tele sending")); MI32.mqttCurrentSlot = 0; MI32.secondsCounter2 = 0; + MI32.mqttCurrentSingleSlot = 0; } else { AddLog(LOG_LEVEL_DEBUG,PSTR("M32: Hit tele time, restarted but not finished last - lost from slot %d")+MI32.mqttCurrentSlot); MI32.mqttCurrentSlot = 0; MI32.secondsCounter2 = 0; + MI32.mqttCurrentSingleSlot = 0; } } MI32.secondsCounter2++; @@ -2112,6 +2127,13 @@ void CmndMi32Option(void){ case 4:{ MI32.option.ignoreBogusBattery = onOff; } break; + case 5:{ + MI32.option.onlyAliased = onOff; + if (MI32.option.onlyAliased){ + // discard all sensors for a restart + MIBLEsensors.clear(); + } + } break; } ResponseCmndDone(); } @@ -2302,15 +2324,18 @@ void MI32TimeoutSensors(){ // this assumes that we're adding to a ResponseTime_P -void MI32GetOneSensorJson(int slot){ +void MI32GetOneSensorJson(int slot, int hidename){ mi_sensor_t *p; p = &MIBLEsensors[slot]; - ResponseAppend_P(PSTR(",\"%s-%02x%02x%02x\":{"), - kMI32DeviceType[p->type-1], - p->MAC[3], p->MAC[4], p->MAC[5]); + // remove hyphen - make it difficult to configure HASS + if (!hidename) { + ResponseAppend_P(PSTR("\"%s%02x%02x%02x\":{"), + kMI32DeviceType[p->type-1], + p->MAC[3], p->MAC[4], p->MAC[5]); + } - ResponseAppend_P(PSTR("\"MAC\":\"%02x%02x%02x%02x%02x%02x\""), + ResponseAppend_P(PSTR("\"mac\":\"%02x%02x%02x%02x%02x%02x\""), p->MAC[0], p->MAC[1], p->MAC[2], p->MAC[3], p->MAC[4], p->MAC[5]); @@ -2336,9 +2361,8 @@ void MI32GetOneSensorJson(int slot){ ||(hass_mode!=-1) #endif //USE_HOME_ASSISTANT ) { - char temperature[FLOATSZ]; - dtostrfd(p->temp, Settings.flag2.temperature_resolution, temperature); - ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"), temperature); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"), + Settings.flag2.temperature_resolution, &p->temp); } } } @@ -2449,7 +2473,9 @@ void MI32GetOneSensorJson(int slot){ } if (MI32.option.showRSSI) ResponseAppend_P(PSTR(",\"RSSI\":%d"), p->RSSI); - ResponseAppend_P(PSTR("}")); + if (!hidename) { + ResponseAppend_P(PSTR("}")); + } p->eventType.raw = 0; p->shallSendMQTT = 0; @@ -2486,7 +2512,8 @@ void MI32ShowSomeSensors(){ ResponseTime_P(PSTR("")); int cnt = 0; for (; (MI32.mqttCurrentSlot < numsensors) && (cnt < 4); MI32.mqttCurrentSlot++, cnt++) { - MI32GetOneSensorJson(MI32.mqttCurrentSlot); + ResponseAppend_P(PSTR(",")); + MI32GetOneSensorJson(MI32.mqttCurrentSlot, 0); int mlen = strlen(TasmotaGlobal.mqtt_data); // if we ran out of room, leave here. @@ -2511,6 +2538,185 @@ void MI32ShowSomeSensors(){ #endif //USE_HOME_ASSISTANT } + +/////////////////////////////////////////////// +// starts a completely fresh MQTT message. +// sends ONE sensor on a dedicated topic NOT related to this TAS +// triggered by setting MI32.mqttCurrentSingleSlot = 0 +void MI32ShowOneMISensor(){ + // don't detect half-added ones here + int numsensors = MIBLEsensors.size(); + if (MI32.mqttCurrentSingleSlot >= numsensors){ + // if we got to the end of the sensors, then don't send more + return; + } + +#ifdef USE_HOME_ASSISTANT + if(Settings.flag.hass_discovery){ + + ResponseTime_P(PSTR(",")); + MI32GetOneSensorJson(MI32.mqttCurrentSingleSlot, 1); + mi_sensor_t *p; + p = &MIBLEsensors[MI32.mqttCurrentSingleSlot]; + + ResponseAppend_P(PSTR("}")); + + char idstr[32]; + const char *alias = BLE_ESP32::getAlias(p->MAC); + const char *id = idstr; + if (alias && *alias){ + id = alias; + } else { + sprintf(idstr, PSTR("%s%02x%02x%02x"), + kMI32DeviceType[p->type-1], + p->MAC[3], p->MAC[4], p->MAC[5]); + } + char SensorTopic[60]; + sprintf(SensorTopic, "tele/tasmota_ble/%s", + id); + + MqttPublish(SensorTopic); + //AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, TasmotaGlobal.mqtt_data); + } +#endif //USE_HOME_ASSISTANT + MI32.mqttCurrentSingleSlot++; +} + + +/////////////////////////////////////////////// +// starts a completely fresh MQTT message. +// sends ONE sensor's worth of HA discovery msg +const char MI_HA_DISCOVERY_TEMPLATE[] PROGMEM = + "{\"availability\":[],\"device\":" + "{\"identifiers\":[\"BLE%s\"]," + "\"name\":\"%s\"," + "\"manufacturer\":\"tas\"," + "\"model\":\"%s\"," + "\"via_device\":\"%s\"" + "}," + "\"dev_cla\":\"%s\"," + "\"expire_after\":600," + "\"json_attr_t\":\"%s\"," + "\"name\":\"%s_%s\"," + "\"state_topic\":\"%s\"," + "\"uniq_id\":\"%s_%s\"," + "\"unit_of_meas\":\"%s\"," + "\"val_tpl\":\"{{ value_json.%s }}\"}"; + +void MI32DiscoveryOneMISensor(){ + // don't detect half-added ones here + int numsensors = MIBLEsensors.size(); + if (MI32.mqttCurrentSingleSlot >= numsensors){ + // if we got to the end of the sensors, then don't send more + return; + } + +#ifdef USE_HOME_ASSISTANT + if(Settings.flag.hass_discovery){ + mi_sensor_t *p; + p = &MIBLEsensors[MI32.mqttCurrentSingleSlot]; + + + // careful - a missing comma causes a crash!!!! + // because of the way we loop? + const char *classes[] = { + "temperature", + "Temperature", + "°C", + "humidity", + "Humidity", + "%", + "temperature", + "DewPoint", + "°C", + "battery", + "Battery", + "%", + "signal_strength", + "RSSI", + "dB" + }; + + int datacount = (sizeof(classes)/sizeof(*classes))/3; + + if (p->nextDiscoveryData >= datacount){ + p->nextDiscoveryData = 0; + } + + //int i = p->nextDiscoveryData*3; + for (int i = 0; i < datacount*3; i += 3){ + if (!classes[i] || !classes[i+1] || !classes[i+2]){ + return; + } + + char idstr[32]; + const char *alias = BLE_ESP32::getAlias(p->MAC); + const char *id = idstr; + if (alias && *alias){ + id = alias; + } else { + sprintf(idstr, PSTR("%s%02x%02x%02x"), + kMI32DeviceType[p->type-1], + p->MAC[3], p->MAC[4], p->MAC[5]); + } + + char SensorTopic[60]; + sprintf(SensorTopic, "tele/tasmota_ble/%s", + id); + + + ResponseClear(); + + /* + {"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"],"manufacturer":"simon","model":"someBLEsensor","name":"TASBLEa4c1387fc1e1","sw_version":"0.0.0"},"dev_cla":"temperature","json_attr_t":"tele/tasmota_esp32/SENSOR","name":"TASLYWSD037fc1e1Temp","state_topic":"tele/tasmota_esp32/SENSOR","uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C","val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"} + {"availability":[],"device":{"identifiers":["TasmotaBLEa4c1387fc1e1"], + "name":"TASBLEa4c1387fc1e1"},"dev_cla":"temperature", + "json_attr_t":"tele/tasmota_esp32/SENSOR", + "name":"TASLYWSD037fc1e1Temp","state_topic": "tele/tasmota_esp32/SENSOR", + "uniq_id":"Tasmotaa4c1387fc1e1temp","unit_of_meas":"°C", + "val_tpl":"{{ value_json.LYWSD037fc1e1.Temperature }}"} + */ + + ResponseAppend_P(MI_HA_DISCOVERY_TEMPLATE, + //"{\"identifiers\":[\"BLE%s\"]," + id, + //"\"name\":\"%s\"}," + id, + //\"model\":\"%s\", + kMI32DeviceType[p->type-1], + //\"via_device\":\"%s\" + NetworkHostname(), + //"\"dev_cla\":\"%s\"," + classes[i], + //"\"json_attr_t\":\"%s\"," - the topic the sensor publishes on + SensorTopic, + //"\"name\":\"%s_%s\"," - the name of this DATA + id, classes[i+1], + //"\"state_topic\":\"%s\"," - the topic the sensor publishes on? + SensorTopic, + //"\"uniq_id\":\"%s_%s\"," - unique for this data, + id, classes[i+1], + //"\"unit_of_meas\":\"%s\"," - the measure of this type of data + classes[i+2], + //"\"val_tpl\":\"{{ value_json.%s }}") // e.g. Temperature + classes[i+1] + // + ); + + char DiscoveryTopic[80]; + sprintf(DiscoveryTopic, "homeassistant/sensor/%s/%s/config", + id, classes[i+1]); + + MqttPublish(DiscoveryTopic); + p->nextDiscoveryData++; + //vTaskDelay(100/ portTICK_PERIOD_MS); + } + } // end if hass discovery + //AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: %s: show some %d %s"),D_CMND_MI32, MI32.mqttCurrentSlot, TasmotaGlobal.mqtt_data); +#endif //USE_HOME_ASSISTANT + +} + /////////////////////////////////////////////// // starts a completely fresh MQTT message. // sends up to 4 sensors pe5r msg @@ -2535,7 +2741,8 @@ void MI32ShowTriggeredSensors(){ if(p->shallSendMQTT==0) continue; cnt++; - MI32GetOneSensorJson(sensor); + ResponseAppend_P(PSTR(",")); + MI32GetOneSensorJson(sensor, 0); int mlen = strlen(TasmotaGlobal.mqtt_data); // if we ran out of room, leave here. @@ -2607,9 +2814,7 @@ void MI32Show(bool json) switch(p->type){ case MI_FLORA:{ if (!isnan(p->temp)) { - char temperature[FLOATSZ]; - dtostrfd(p->temp, Settings.flag2.temperature_resolution, temperature); - WSContentSend_PD(HTTP_SNS_TEMP, typeName, temperature, TempUnit()); + WSContentSend_Temp(typeName, p->temp); } if (p->moisture!=0xff) { WSContentSend_PD(HTTP_SNS_MOISTURE, typeName, p->moisture); diff --git a/tasmota/xsns_62_MI_HM10.ino b/tasmota/xsns_62_MI_HM10.ino index 48057240d..02298380e 100644 --- a/tasmota/xsns_62_MI_HM10.ino +++ b/tasmota/xsns_62_MI_HM10.ino @@ -1812,9 +1812,8 @@ void HM10Show(bool json) ||(hass_mode!=-1) #endif //USE_HOME_ASSISTANT ) { - char temperature[FLOATSZ]; - dtostrfd(MIBLEsensors[i].temp, Settings.flag2.temperature_resolution, temperature); - ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%s"), temperature); + ResponseAppend_P(PSTR(",\"" D_JSON_TEMPERATURE "\":%*_f"), + Settings.flag2.temperature_resolution, &MIBLEsensors[i].temp); } } } @@ -1967,9 +1966,7 @@ void HM10Show(bool json) WSContentSend_PD(HTTP_HM10_MAC, kHM10DeviceType[MIBLEsensors[i].type-1], D_MAC_ADDRESS, _MAC); if (MIBLEsensors[i].type==FLORA){ if(!isnan(MIBLEsensors[i].temp)){ - char temperature[FLOATSZ]; - dtostrfd(MIBLEsensors[i].temp, Settings.flag2.temperature_resolution, temperature); - WSContentSend_PD(HTTP_SNS_TEMP, kHM10DeviceType[MIBLEsensors[i].type-1], temperature, TempUnit()); + WSContentSend_Temp(kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].temp); } if(MIBLEsensors[i].lux!=0x00ffffff){ // this is the error code -> no valid value WSContentSend_PD(HTTP_SNS_ILLUMINANCE, kHM10DeviceType[MIBLEsensors[i].type-1], MIBLEsensors[i].lux); diff --git a/tasmota/xsns_72_mcp9808.ino b/tasmota/xsns_72_mcp9808.ino index 98fc48066..4b1c15696 100644 --- a/tasmota/xsns_72_mcp9808.ino +++ b/tasmota/xsns_72_mcp9808.ino @@ -75,9 +75,6 @@ void MCP9808EverySecond(void) { void MCP9808Show(bool json) { for (uint32_t i = 0; i < mcp9808_cfg.count; i++) { - char temperature[33]; - dtostrfd(mcp9808_sensors[i].temperature, Settings.flag2.temperature_resolution, temperature); - char sensor_name[11]; strlcpy(sensor_name, mcp9808_cfg.types, sizeof(sensor_name)); if (mcp9808_cfg.count > 1) { @@ -85,10 +82,10 @@ void MCP9808Show(bool json) { } if (json) { - ResponseAppend_P(JSON_SNS_TEMP, sensor_name, temperature); + ResponseAppend_P(JSON_SNS_F_TEMP, sensor_name, Settings.flag2.temperature_resolution, &mcp9808_sensors[i].temperature); if ((0 == TasmotaGlobal.tele_period) && (0 == i)) { #ifdef USE_DOMOTICZ - DomoticzSensor(DZ_TEMP, temperature); + DomoticzFloatSensor(DZ_TEMP, mcp9808_sensors[i].temperature); #endif // USE_DOMOTICZ #ifdef USE_KNX KnxSensor(KNX_TEMPERATURE, mcp9808_sensors[i].temperature); @@ -96,7 +93,7 @@ void MCP9808Show(bool json) { } #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, temperature, TempUnit()); + WSContentSend_Temp(sensor_name, mcp9808_sensors[i].temperature); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index 50589e4d5..c04758190 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -107,15 +107,14 @@ void HP303B_Show(bool json) { float sealevel = ConvertPressureForSeaLevel(hp303b_sensor[i].pressure); - char str_temperature[33]; - dtostrfd(hp303b_sensor[i].temperature, Settings.flag2.temperature_resolution, str_temperature); char str_pressure[33]; dtostrfd(hp303b_sensor[i].pressure, Settings.flag2.pressure_resolution, str_pressure); char sea_pressure[33]; dtostrfd(sealevel, Settings.flag2.pressure_resolution, sea_pressure); if (json) { - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_PRESSURE "\":%s"), sensor_name, str_temperature, str_pressure); + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_PRESSURE "\":%s"), + sensor_name, Settings.flag2.temperature_resolution, &hp303b_sensor[i].temperature, str_pressure); if (Settings.altitude != 0) { ResponseAppend_P(PSTR(",\"" D_JSON_PRESSUREATSEALEVEL "\":%s"), sea_pressure); } @@ -123,12 +122,12 @@ void HP303B_Show(bool json) { #ifdef USE_DOMOTICZ // Domoticz and knx only support one temp sensor if ((0 == TasmotaGlobal.tele_period) && (0 == i)) { - DomoticzSensor(DZ_TEMP, hp303b_sensor[i].temperature); + DomoticzFloatSensor(DZ_TEMP, hp303b_sensor[i].temperature); } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, str_temperature, TempUnit()); + WSContentSend_Temp(sensor_name, hp303b_sensor[i].temperature); WSContentSend_PD(HTTP_SNS_PRESSURE, sensor_name, str_pressure, PressureUnit().c_str()); if (Settings.altitude != 0) { WSContentSend_PD(HTTP_SNS_SEAPRESSURE, sensor_name, sea_pressure, PressureUnit().c_str()); diff --git a/tasmota/xsns_74_lmt01.ino b/tasmota/xsns_74_lmt01.ino index 8c6cfdb92..cce7288db 100644 --- a/tasmota/xsns_74_lmt01.ino +++ b/tasmota/xsns_74_lmt01.ino @@ -85,14 +85,11 @@ int LMT01_getPulses(void) { } void LMT01_Show(bool Json) { - char temp[33]; - dtostrfd(lmt01_temperature, Settings.flag2.temperature_resolution, temp); - if (Json) { - ResponseAppend_P(JSON_SNS_TEMP, "LMT01", temp); + ResponseAppend_P(JSON_SNS_F_TEMP, "LMT01", Settings.flag2.temperature_resolution, &lmt01_temperature); #ifdef USE_DOMOTICZ if (0 == TasmotaGlobal.tele_period) { - DomoticzSensor(DZ_TEMP, temp); + DomoticzFloatSensor(DZ_TEMP, lmt01_temperature); } #endif // USE_DOMOTICZ #ifdef USE_KNX @@ -102,7 +99,7 @@ void LMT01_Show(bool Json) { #endif // USE_KNX #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_TEMP, "LMT01", temp, TempUnit()); + WSContentSend_Temp("LMT01", lmt01_temperature); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_78_ezortd.ino b/tasmota/xsns_78_ezortd.ino index cf731a346..c9d37fa68 100644 --- a/tasmota/xsns_78_ezortd.ino +++ b/tasmota/xsns_78_ezortd.ino @@ -35,15 +35,14 @@ struct EZORTD : public EZOStruct { virtual void Show(bool json, const char *name) { - char str[10]; - dtostrfd(ConvertTemp(temperature), Settings.flag2.temperature_resolution, str); + float temp = ConvertTemp(temperature); if (json) { - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"), name, str); + ResponseAppend_P(JSON_SNS_F_TEMP, name, Settings.flag2.temperature_resolution, &temp); } #ifdef USE_WEBSERVER else { - WSContentSend_PD(HTTP_SNS_TEMP, name, str, TempUnit()); + WSContentSend_Temp(name, temp); #endif // USE_WEBSERVER } } diff --git a/tasmota/xsns_81_seesaw_soil.ino b/tasmota/xsns_81_seesaw_soil.ino index e18a5db5c..23962275d 100644 --- a/tasmota/xsns_81_seesaw_soil.ino +++ b/tasmota/xsns_81_seesaw_soil.ino @@ -272,11 +272,9 @@ void seeSoilEverySecond(void) { // update sensor values and publ #endif // SEESAW_SOIL_PUBLISH void seeSoilShow(bool json) { - char temperature[FLOATSZ]; char sensor_name[sizeof(SeeSoil.name) + 3]; for (uint32_t i = 0; i < SeeSoil.count; i++) { - dtostrfd(SeeSoilSNS[i].temperature, Settings.flag2.temperature_resolution, temperature); seeSoilName(i, sensor_name, sizeof(sensor_name)); if (json) { ResponseAppend_P(PSTR(",")); // compose tele json @@ -296,20 +294,20 @@ void seeSoilShow(bool json) { WSContentSend_PD(HTTP_SNS_ANALOG, sensor_name, 0, SeeSoilSNS[i].capacitance); #endif // SEESAW_SOIL_RAW WSContentSend_PD(HTTP_SNS_MOISTURE, sensor_name, (uint32_t) SeeSoilSNS[i].moisture); - WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, temperature, TempUnit()); + WSContentSend_Temp(sensor_name, SeeSoilSNS[i].temperature); #endif // USE_WEBSERVER } } // for each sensor connected } void seeSoilJson(int no) { // common json - char temperature[FLOATSZ]; char sensor_name[sizeof(SeeSoil.name) + 3]; - seeSoilName(no, sensor_name, sizeof(sensor_name)); - dtostrfd(SeeSoilSNS[no].temperature, Settings.flag2.temperature_resolution, temperature); - ResponseAppend_P(PSTR ("\"%s\":{\"" D_JSON_ID "\":\"%02X\",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_MOISTURE "\":%u}"), - sensor_name, SeeSoilSNS[no].address, temperature, (uint32_t) SeeSoilSNS[no].moisture); + + ResponseAppend_P(PSTR ("\"%s\":{\"" D_JSON_ID "\":\"%02X\",\"" D_JSON_TEMPERATURE "\":%*_f,\"" D_JSON_MOISTURE "\":%u}"), + sensor_name, SeeSoilSNS[no].address, + Settings.flag2.temperature_resolution, &SeeSoilSNS[no].temperature, + (uint32_t) SeeSoilSNS[no].moisture); } void seeSoilName(int no, char *name, int len) // generates a sensor name diff --git a/tools/fw_SonoffZigbeeBridge_ezsp/readme.txt b/tools/fw_SonoffZigbeeBridge_ezsp/readme.txt index a8191b352..99a0b5c56 100644 --- a/tools/fw_SonoffZigbeeBridge_ezsp/readme.txt +++ b/tools/fw_SonoffZigbeeBridge_ezsp/readme.txt @@ -2,9 +2,12 @@ ## EmberZNet NCP UART EZSP firmware signed for Sonoff ZBBridge -- `ncp-uart-sw_6.7.6_115200.ota` - recommended stable version for EZSP v6, EZSP v7, and EZSP v8 compatible hosts. -- `ncp-uart-sw-6.7.8_115200.ota` - latest version for EZSP v6, EZSP v7, and EZSP v8 compatible hosts. -- `ncp-uart-sw_6.5.5_115200.ota` - legacy version for EZSP v4, EZSP v5, EZSP v6, or EZSP v7 compatible hosts. +- `ncp-uart-sw_6.7.6_115200.ota` - original stable version for EZSP v8 compatible hosts, contains a bug that can cause battery of IKEA and Philips remotes to drain +- `ncp-uart-sw-6.7.8_115200.ota` - NEW: recommended stable version for EZSP v8 compatible hosts. + +## Archived + +- `ncp-uart-sw_6.5.5_115200.ota` - legacy version for EZSP v7 compatible hosts. ## EmberZNet and EZSP Protocol Versions