diff --git a/BUILDS.md b/BUILDS.md index ec1720d44..7e642de4e 100644 --- a/BUILDS.md +++ b/BUILDS.md @@ -120,6 +120,9 @@ | USE_WEMOS_MOTOR_V1 | - | - | - | - | x | - | - | | USE_IAQ | - | - | - | - | x | - | - | | USE_AS3935 | - | - | - | - | x | - | - | +| USE_VEML6075 | - | - | - | - | - | - | - | +| USE_VEML7700 | - | - | - | - | - | - | - | +| USE_MCP9808 | - | - | - | - | - | - | - | | | | | | | | | | | Feature or Sensor | minimal | lite | tasmota | knx | sensors | ir | display | Remarks | USE_SPI | - | - | - | - | - | - | x | diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 788145d3e..4f6718b90 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -71,4 +71,5 @@ Index | Define | Driver | Device | Address(es) | Description 47 | USE_DISPLAY_SEVENSEG| xdsp_11 | HT16K33 | 0x70 - 0x77 | Seven segment LED 48 | USE_AS3935 | xsns_67 | AS3935 | 0x03 | Franklin Lightning Sensor 49 | USE_VEML6075 | xsns_70 | VEML6075 | 0x10 | UVA/UVB/UVINDEX Sensor - 50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor \ No newline at end of file + 50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor + 51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor \ No newline at end of file diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 7fb0366a3..5e4429dde 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -61,6 +61,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Fix escape of non-JSON received serial data (#8329) - Add command ``Rule0`` to change global rule parameters - Add command ``Time 4`` to display timestamp using milliseconds (#8537) +- Add command ``SetOption94 0/1`` to select MAX31855 or MAX6675 thermocouple support (#8616) - Add commands ``LedPwmOn 0..255``, ``LedPwmOff 0..255`` and ``LedPwmMode1 0/1`` to control led brightness by George (#8491) - Add support for unique MQTTClient (and inherited fallback topic) by full Mac address using ``mqttclient DVES_%12X`` (#8300) - Add more functionality to ``Switchmode`` 11 and 12 (#8450) @@ -68,3 +69,9 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for VEML6075 UVA/UVB/UVINDEX Sensor by device111 (#8432) - Add support for VEML7700 Ambient light intensity Sensor by device111 (#8432) - Add Three Phase Export Active Energy to SDM630 driver +- Add Zigbee options to ``ZbSend`` to write and report attributes +- Add Zigbee auto-responder for common attributes +- Add ``CpuFrequency`` to ``status 2`` +- Add ``FlashFrequency`` to ``status 4`` +- Add support for up to two BH1750 sensors controlled by commands ``BH1750Resolution`` and ``BH1750MTime`` (#8139) +- Add support for up to eight MCP9808 temperature sensors by device111 (#8594) diff --git a/lib/Adafruit_MCP9808_Tasmota/.github/ISSUE_TEMPLATE.md b/lib/Adafruit_MCP9808_Tasmota/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..f0e26146f --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,46 @@ +Thank you for opening an issue on an Adafruit Arduino library repository. To +improve the speed of resolution please review the following guidelines and +common troubleshooting steps below before creating the issue: + +- **Do not use GitHub issues for troubleshooting projects and issues.** Instead use + the forums at http://forums.adafruit.com to ask questions and troubleshoot why + something isn't working as expected. In many cases the problem is a common issue + that you will more quickly receive help from the forum community. GitHub issues + are meant for known defects in the code. If you don't know if there is a defect + in the code then start with troubleshooting on the forum first. + +- **If following a tutorial or guide be sure you didn't miss a step.** Carefully + check all of the steps and commands to run have been followed. Consult the + forum if you're unsure or have questions about steps in a guide/tutorial. + +- **For Arduino projects check these very common issues to ensure they don't apply**: + + - For uploading sketches or communicating with the board make sure you're using + a **USB data cable** and **not** a **USB charge-only cable**. It is sometimes + very hard to tell the difference between a data and charge cable! Try using the + cable with other devices or swapping to another cable to confirm it is not + the problem. + + - **Be sure you are supplying adequate power to the board.** Check the specs of + your board and plug in an external power supply. In many cases just + plugging a board into your computer is not enough to power it and other + peripherals. + + - **Double check all soldering joints and connections.** Flakey connections + cause many mysterious problems. See the [guide to excellent soldering](https://learn.adafruit.com/adafruit-guide-excellent-soldering/tools) for examples of good solder joints. + + - **Ensure you are using an official Arduino or Adafruit board.** We can't + guarantee a clone board will have the same functionality and work as expected + with this code and don't support them. + +If you're sure this issue is a defect in the code and checked the steps above +please fill in the following fields to provide enough troubleshooting information. +You may delete the guideline and text above to just leave the following details: + +- Arduino board: **INSERT ARDUINO BOARD NAME/TYPE HERE** + +- Arduino IDE version (found in Arduino -> About Arduino menu): **INSERT ARDUINO + VERSION HERE** + +- List the steps to reproduce the problem below (if possible attach a sketch or + copy the sketch code in too): **LIST REPRO STEPS BELOW** diff --git a/lib/Adafruit_MCP9808_Tasmota/.github/PULL_REQUEST_TEMPLATE.md b/lib/Adafruit_MCP9808_Tasmota/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..7b641eb86 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,26 @@ +Thank you for creating a pull request to contribute to Adafruit's GitHub code! +Before you open the request please review the following guidelines and tips to +help it be more easily integrated: + +- **Describe the scope of your change--i.e. what the change does and what parts + of the code were modified.** This will help us understand any risks of integrating + the code. + +- **Describe any known limitations with your change.** For example if the change + doesn't apply to a supported platform of the library please mention it. + +- **Please run any tests or examples that can exercise your modified code.** We + strive to not break users of the code and running tests/examples helps with this + process. + +Thank you again for contributing! We will try to test and integrate the change +as soon as we can, but be aware we have many GitHub repositories to manage and +can't immediately respond to every request. There is no need to bump or check in +on a pull request (it will clutter the discussion of the request). + +Also don't be worried if the request is closed or not integrated--sometimes the +priorities of Adafruit's GitHub code (education, ease of use) might not match the +priorities of the pull request. Don't fret, the open source community thrives on +forks and GitHub makes it easy to keep your changes in a forked repo. + +After reviewing the guidelines above you can delete this text from the pull request. diff --git a/lib/Adafruit_MCP9808_Tasmota/.github/workflows/githubci.yml b/lib/Adafruit_MCP9808_Tasmota/.github/workflows/githubci.yml new file mode 100644 index 000000000..cb226da94 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/.github/workflows/githubci.yml @@ -0,0 +1,32 @@ +name: Arduino Library CI + +on: [pull_request, push, repository_dispatch] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@v1 + with: + python-version: '3.x' + - uses: actions/checkout@v2 + - uses: actions/checkout@v2 + with: + repository: adafruit/ci-arduino + path: ci + + - name: pre-install + run: bash ci/actions_install.sh + + - name: test platforms + run: python3 ci/build_platform.py main_platforms + + - name: clang + run: python3 ci/run-clang-format.py -e "ci/*" -e "bin/*" -r . + + - name: doxygen + env: + GH_REPO_TOKEN: ${{ secrets.GH_REPO_TOKEN }} + PRETTYNAME : "Adafruit MCP9808 Arduino Library" + run: bash ci/doxy_gen_and_deploy.sh diff --git a/lib/Adafruit_MCP9808_Tasmota/.gitignore b/lib/Adafruit_MCP9808_Tasmota/.gitignore new file mode 100644 index 000000000..542d266a9 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/.gitignore @@ -0,0 +1,8 @@ +# osx +.DS_Store + +# doxygen +Doxyfile* +doxygen_sqlite3.db +html +*.tmp diff --git a/lib/Adafruit_MCP9808_Tasmota/Adafruit_MCP9808.cpp b/lib/Adafruit_MCP9808_Tasmota/Adafruit_MCP9808.cpp new file mode 100644 index 000000000..db3fa9f86 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/Adafruit_MCP9808.cpp @@ -0,0 +1,273 @@ +/*! + * @file Adafruit_MCP9808.cpp + * + * @mainpage Adafruit MCP9808 I2C Temp Sensor + * + * @section intro_sec Introduction + * + * I2C Driver for Microchip's MCP9808 I2C Temp sensor + * + * This is a library for the Adafruit MCP9808 breakout: + * http://www.adafruit.com/products/1782 + * + * Adafruit invests time and resources providing this open source code, + * please support Adafruit and open-source hardware by purchasing products from + * Adafruit! + * + * @section author Author + * + * K.Townsend (Adafruit Industries) + * + * @section license License + * + * BSD (see license.txt) + * + * @section HISTORY + * + * v1.0 - First release + * + * changes by Martin Wagner for tasmota project: + * + * - the libary supports variabel I2C address + * + */ + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#ifdef __AVR_ATtiny85__ +#include "TinyWireM.h" +#define Wire TinyWireM +#else +#include +#endif + +#include "Adafruit_MCP9808.h" + +/*! + * @brief Instantiates a new MCP9808 class + */ +Adafruit_MCP9808::Adafruit_MCP9808() {} + +/*! + * @brief Setups the HW + * @param *theWire + * @return True if initialization was successful, otherwise false. + */ +bool Adafruit_MCP9808::begin(TwoWire *theWire) { + _wire = theWire; + _i2caddr = MCP9808_I2CADDR_DEFAULT; + return init(); +} + +/*! + * @brief Setups the HW + * @param addr + * @return True if initialization was successful, otherwise false. + */ +bool Adafruit_MCP9808::begin(uint8_t addr) { + _i2caddr = addr; + _wire = &Wire; + return init(); +} + +/*! + * @brief Setups the HW + * @param addr + * @param *theWire + * @return True if initialization was successful, otherwise false. + */ +bool Adafruit_MCP9808::begin(uint8_t addr, TwoWire *theWire) { + _i2caddr = addr; + _wire = theWire; + return init(); +} + +/*! + * @brief Setups the HW with default address + * @return True if initialization was successful, otherwise false. + */ +bool Adafruit_MCP9808::begin() { + _i2caddr = MCP9808_I2CADDR_DEFAULT; + _wire = &Wire; + return init(); +} + +/*! + * @brief init function + * @return True if initialization was successful, otherwise false. + */ +bool Adafruit_MCP9808::init() { + _wire->begin(); + + if (read16(MCP9808_REG_MANUF_ID) != 0x0054) + return false; + if (read16(MCP9808_REG_DEVICE_ID) != 0x0400) + return false; + + write16(MCP9808_REG_CONFIG, 0x0); + return true; +} + +/*! + * @brief Reads the 16-bit temperature register and returns the Centigrade + * temperature as a float. + * @return Temperature in Centigrade. + */ +float Adafruit_MCP9808::readTempC(uint8_t addr) { + _i2caddr = addr; + float temp = NAN; + uint16_t t = read16(MCP9808_REG_AMBIENT_TEMP); + + if (t != 0xFFFF) { + temp = t & 0x0FFF; + temp /= 16.0; + if (t & 0x1000) + temp -= 256; + } + + return temp; +} + +/*! + * @brief Reads the 16-bit temperature register and returns the Fahrenheit + * temperature as a float. + * @return Temperature in Fahrenheit. + */ +float Adafruit_MCP9808::readTempF(uint8_t addr) { + _i2caddr = addr; + float temp = NAN; + uint16_t t = read16(MCP9808_REG_AMBIENT_TEMP); + + if (t != 0xFFFF) { + temp = t & 0x0FFF; + temp /= 16.0; + if (t & 0x1000) + temp -= 256; + + temp = temp * 9.0 / 5.0 + 32; + } + + return temp; +} + +/*! + * @brief Set Sensor to Shutdown-State or wake up (Conf_Register BIT8) + * @param sw true = shutdown / false = wakeup + */ +void Adafruit_MCP9808::shutdown_wake(uint8_t addr, boolean sw) { + _i2caddr = addr; + uint16_t conf_shutdown; + uint16_t conf_register = read16(MCP9808_REG_CONFIG); + if (sw == true) { + conf_shutdown = conf_register | MCP9808_REG_CONFIG_SHUTDOWN; + write16(MCP9808_REG_CONFIG, conf_shutdown); + } + if (sw == false) { + conf_shutdown = conf_register & ~MCP9808_REG_CONFIG_SHUTDOWN; + write16(MCP9808_REG_CONFIG, conf_shutdown); + } +} + +/*! + * @brief Shutdown MCP9808 + */ +void Adafruit_MCP9808::shutdown(uint8_t addr) { shutdown_wake(addr, true); } + +/*! + * @brief Wake up MCP9808 + */ +void Adafruit_MCP9808::wake(uint8_t addr) { + shutdown_wake(addr, false); + delay(250); +} + +/*! + * @brief Get Resolution Value + * @return Resolution value + */ +uint8_t Adafruit_MCP9808::getResolution(uint8_t addr) { + _i2caddr = addr; + return read8(MCP9808_REG_RESOLUTION); +} + +/*! + * @brief Set Resolution Value + * @param value + */ +void Adafruit_MCP9808::setResolution(uint8_t addr, uint8_t value) { + _i2caddr = addr; + write8(MCP9808_REG_RESOLUTION, value & 0x03); +} + +/*! + * @brief Low level 16 bit write procedures + * @param reg + * @param value + */ +void Adafruit_MCP9808::write16(uint8_t reg, uint16_t value) { + _wire->beginTransmission(_i2caddr); + _wire->write((uint8_t)reg); + _wire->write(value >> 8); + _wire->write(value & 0xFF); + _wire->endTransmission(); +} + +/*! + * @brief Low level 16 bit read procedure + * @param reg + * @return value + */ +uint16_t Adafruit_MCP9808::read16(uint8_t reg) { + uint16_t val = 0xFFFF; + uint8_t state; + + _wire->beginTransmission(_i2caddr); + _wire->write((uint8_t)reg); + state = _wire->endTransmission(); + + if (state == 0) { + _wire->requestFrom((uint8_t)_i2caddr, (uint8_t)2); + val = _wire->read(); + val <<= 8; + val |= _wire->read(); + } + + return val; +} + +/*! + * @brief Low level 8 bit write procedure + * @param reg + * @param value + */ +void Adafruit_MCP9808::write8(uint8_t reg, uint8_t value) { + _wire->beginTransmission(_i2caddr); + _wire->write((uint8_t)reg); + _wire->write(value); + _wire->endTransmission(); +} + +/*! + * @brief Low level 8 bit read procedure + * @param reg + * @return value + */ +uint8_t Adafruit_MCP9808::read8(uint8_t reg) { + uint8_t val = 0xFF; + uint8_t state; + + _wire->beginTransmission(_i2caddr); + _wire->write((uint8_t)reg); + state = _wire->endTransmission(); + + if (state == 0) { + _wire->requestFrom((uint8_t)_i2caddr, (uint8_t)1); + val = _wire->read(); + } + + return val; +} diff --git a/lib/Adafruit_MCP9808_Tasmota/Adafruit_MCP9808.h b/lib/Adafruit_MCP9808_Tasmota/Adafruit_MCP9808.h new file mode 100644 index 000000000..c93351ca1 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/Adafruit_MCP9808.h @@ -0,0 +1,87 @@ +/*! + * @file Adafruit_MCP9808.h + * + * I2C Driver for Microchip's MCP9808 I2C Temp sensor + * + * This is a library for the Adafruit MCP9808 breakout: + * http://www.adafruit.com/products/1782 + * + * Adafruit invests time and resources providing this open source code, + *please support Adafruit and open-source hardware by purchasing products from + * Adafruit! + * + * + * BSD license (see license.txt) + * + * changes by Martin Wagner for tasmota project: + * + * - the libary supports variabel I2C address + * + */ + +#ifndef _ADAFRUIT_MCP9808_H +#define _ADAFRUIT_MCP9808_H + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include + +#define MCP9808_I2CADDR_DEFAULT 0x18 ///< I2C address +#define MCP9808_REG_CONFIG 0x01 ///< MCP9808 config register + +#define MCP9808_REG_CONFIG_SHUTDOWN 0x0100 ///< shutdown config +#define MCP9808_REG_CONFIG_CRITLOCKED 0x0080 ///< critical trip lock +#define MCP9808_REG_CONFIG_WINLOCKED 0x0040 ///< alarm window lock +#define MCP9808_REG_CONFIG_INTCLR 0x0020 ///< interrupt clear +#define MCP9808_REG_CONFIG_ALERTSTAT 0x0010 ///< alert output status +#define MCP9808_REG_CONFIG_ALERTCTRL 0x0008 ///< alert output control +#define MCP9808_REG_CONFIG_ALERTSEL 0x0004 ///< alert output select +#define MCP9808_REG_CONFIG_ALERTPOL 0x0002 ///< alert output polarity +#define MCP9808_REG_CONFIG_ALERTMODE 0x0001 ///< alert output mode + +#define MCP9808_REG_UPPER_TEMP 0x02 ///< upper alert boundary +#define MCP9808_REG_LOWER_TEMP 0x03 ///< lower alert boundery +#define MCP9808_REG_CRIT_TEMP 0x04 ///< critical temperature +#define MCP9808_REG_AMBIENT_TEMP 0x05 ///< ambient temperature +#define MCP9808_REG_MANUF_ID 0x06 ///< manufacture ID +#define MCP9808_REG_DEVICE_ID 0x07 ///< device ID +#define MCP9808_REG_RESOLUTION 0x08 ///< resolutin + +/*! + * @brief Class that stores state and functions for interacting with + * MCP9808 Temp Sensor + */ +class Adafruit_MCP9808 { +public: + Adafruit_MCP9808(); + bool begin(); + bool begin(TwoWire *theWire); + bool begin(uint8_t addr); + bool begin(uint8_t addr, TwoWire *theWire); + + bool init(); + float readTempC(uint8_t addr); + float readTempF(uint8_t addr); + uint8_t getResolution(uint8_t addr); + void setResolution(uint8_t addr, uint8_t value); + + void shutdown_wake(uint8_t addr, boolean sw); + void shutdown(uint8_t addr); + void wake(uint8_t addr); + + void write16(uint8_t reg, uint16_t val); + uint16_t read16(uint8_t reg); + + void write8(uint8_t reg, uint8_t val); + uint8_t read8(uint8_t reg); + +private: + TwoWire *_wire; + uint8_t _i2caddr; +}; + +#endif diff --git a/lib/Adafruit_MCP9808_Tasmota/README.md b/lib/Adafruit_MCP9808_Tasmota/README.md new file mode 100644 index 000000000..0623f214f --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/README.md @@ -0,0 +1,18 @@ +# Adafruit MCP9808 Library [![Build Status](https://github.com/adafruit/Adafruit_MCP9808_Library/workflows/Arduino%20Library%20CI/badge.svg)](https://github.com/adafruit/Adafruit_MCP9808_Library/actions)[![Documentation](https://github.com/adafruit/ci-arduino/blob/master/assets/doxygen_badge.svg)](http://adafruit.github.io/Adafruit_MCP9808_Library/html/index.html) + + + +This is the Adafruit MCP9808 Precision I2C Temperature sensor library + +Tested and works great with the Adafruit MCP9808 Breakout Board +* http://www.adafruit.com/products/1782 + +This chip uses I2C to communicate, 2 pins are required to interface + +Adafruit invests time and resources providing this open source code, please support Adafruit and open-source hardware by purchasing products from Adafruit! + +Written by Kevin Townsend/Limor Fried for Adafruit Industries. +BSD license, check license.txt for more information +All text above must be included in any redistribution + +To install, use the Arduino Library Manager and search for "Adafruit MCP9808" and install the library. diff --git a/lib/Adafruit_MCP9808_Tasmota/assets/board.jpg b/lib/Adafruit_MCP9808_Tasmota/assets/board.jpg new file mode 100644 index 000000000..69644e1c5 Binary files /dev/null and b/lib/Adafruit_MCP9808_Tasmota/assets/board.jpg differ diff --git a/lib/Adafruit_MCP9808_Tasmota/code-of-conduct.md b/lib/Adafruit_MCP9808_Tasmota/code-of-conduct.md new file mode 100644 index 000000000..8ee6e4498 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/code-of-conduct.md @@ -0,0 +1,127 @@ +# Adafruit Community Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and leaders pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level or type of +experience, education, socio-economic status, nationality, personal appearance, +race, religion, or sexual identity and orientation. + +## Our Standards + +We are committed to providing a friendly, safe and welcoming environment for +all. + +Examples of behavior that contributes to creating a positive environment +include: + +* Be kind and courteous to others +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Collaborating with other community members +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and sexual attention or advances +* The use of inappropriate images, including in a community member's avatar +* The use of inappropriate language, including in a community member's nickname +* Any spamming, flaming, baiting or other attention-stealing behavior +* Excessive or unwelcome helping; answering outside the scope of the question + asked +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate + +The goal of the standards and moderation guidelines outlined here is to build +and maintain a respectful community. We ask that you don’t just aim to be +"technically unimpeachable", but rather try to be your best self. + +We value many things beyond technical expertise, including collaboration and +supporting others within our community. Providing a positive experience for +other community members can have a much more significant impact than simply +providing the correct answer. + +## Our Responsibilities + +Project leaders are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project leaders have the right and responsibility to remove, edit, or +reject messages, comments, commits, code, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any community member for other behaviors that they deem +inappropriate, threatening, offensive, or harmful. + +## Moderation + +Instances of behaviors that violate the Adafruit Community Code of Conduct +may be reported by any member of the community. Community members are +encouraged to report these situations, including situations they witness +involving other community members. + +You may report in the following ways: + +In any situation, you may send an email to . + +On the Adafruit Discord, you may send an open message from any channel +to all Community Helpers by tagging @community helpers. You may also send an +open message from any channel, or a direct message to @kattni#1507, +@tannewt#4653, @Dan Halbert#1614, @cater#2442, @sommersoft#0222, or +@Andon#8175. + +Email and direct message reports will be kept confidential. + +In situations on Discord where the issue is particularly egregious, possibly +illegal, requires immediate action, or violates the Discord terms of service, +you should also report the message directly to Discord. + +These are the steps for upholding our community’s standards of conduct. + +1. Any member of the community may report any situation that violates the +Adafruit Community Code of Conduct. All reports will be reviewed and +investigated. +2. If the behavior is an egregious violation, the community member who +committed the violation may be banned immediately, without warning. +3. Otherwise, moderators will first respond to such behavior with a warning. +4. Moderators follow a soft "three strikes" policy - the community member may +be given another chance, if they are receptive to the warning and change their +behavior. +5. If the community member is unreceptive or unreasonable when warned by a +moderator, or the warning goes unheeded, they may be banned for a first or +second offense. Repeated offenses will result in the community member being +banned. + +## Scope + +This Code of Conduct and the enforcement policies listed above apply to all +Adafruit Community venues. This includes but is not limited to any community +spaces (both public and private), the entire Adafruit Discord server, and +Adafruit GitHub repositories. Examples of Adafruit Community spaces include +but are not limited to meet-ups, audio chats on the Adafruit Discord, or +interaction at a conference. + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. As a community +member, you are representing our community, and are expected to behave +accordingly. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 1.4, available at +, +and the [Rust Code of Conduct](https://www.rust-lang.org/en-US/conduct.html). + +For other projects adopting the Adafruit Community Code of +Conduct, please contact the maintainers of those projects for enforcement. +If you wish to use this code of conduct for your own project, consider +explicitly mentioning your moderation policy or making a copy with your +own moderation policy so as to avoid confusion. diff --git a/lib/Adafruit_MCP9808_Tasmota/examples/mcp9808test/mcp9808test.ino b/lib/Adafruit_MCP9808_Tasmota/examples/mcp9808test/mcp9808test.ino new file mode 100644 index 000000000..6002487e9 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/examples/mcp9808test/mcp9808test.ino @@ -0,0 +1,69 @@ + +/**************************************************************************/ +/*! +This is a demo for the Adafruit MCP9808 breakout +----> http://www.adafruit.com/products/1782 +Adafruit invests time and resources providing this open source code, +please support Adafruit and open-source hardware by purchasing +products from Adafruit! +*/ +/**************************************************************************/ + +#include +#include "Adafruit_MCP9808.h" + +// Create the MCP9808 temperature sensor object +Adafruit_MCP9808 tempsensor = Adafruit_MCP9808(); + +void setup() { + Serial.begin(9600); + while (!Serial); //waits for serial terminal to be open, necessary in newer arduino boards. + Serial.println("MCP9808 demo"); + + // Make sure the sensor is found, you can also pass in a different i2c + // address with tempsensor.begin(0x19) for example, also can be left in blank for default address use + // Also there is a table with all addres possible for this sensor, you can connect multiple sensors + // to the same i2c bus, just configure each sensor with a different address and define multiple objects for that + // A2 A1 A0 address + // 0 0 0 0x18 this is the default address + // 0 0 1 0x19 + // 0 1 0 0x1A + // 0 1 1 0x1B + // 1 0 0 0x1C + // 1 0 1 0x1D + // 1 1 0 0x1E + // 1 1 1 0x1F + if (!tempsensor.begin(0x18)) { + Serial.println("Couldn't find MCP9808! Check your connections and verify the address is correct."); + while (1); + } + + Serial.println("Found MCP9808!"); + + tempsensor.setResolution(3); // sets the resolution mode of reading, the modes are defined in the table bellow: + // Mode Resolution SampleTime + // 0 0.5°C 30 ms + // 1 0.25°C 65 ms + // 2 0.125°C 130 ms + // 3 0.0625°C 250 ms +} + +void loop() { + Serial.println("wake up MCP9808.... "); // wake up MCP9808 - power consumption ~200 mikro Ampere + tempsensor.wake(); // wake up, ready to read! + + // Read and print out the temperature, also shows the resolution mode used for reading. + Serial.print("Resolution in mode: "); + Serial.println (tempsensor.getResolution()); + float c = tempsensor.readTempC(); + float f = tempsensor.readTempF(); + Serial.print("Temp: "); + Serial.print(c, 4); Serial.print("*C\t and "); + Serial.print(f, 4); Serial.println("*F."); + + delay(2000); + Serial.println("Shutdown MCP9808.... "); + tempsensor.shutdown_wake(1); // shutdown MSP9808 - power consumption ~0.1 mikro Ampere, stops temperature sampling + Serial.println(""); + delay(200); +} diff --git a/lib/Adafruit_MCP9808_Tasmota/library.properties b/lib/Adafruit_MCP9808_Tasmota/library.properties new file mode 100644 index 000000000..3c4ee86ce --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/library.properties @@ -0,0 +1,10 @@ +name=Adafruit MCP9808 Library +version=1.1.2 +author=Adafruit +maintainer=Adafruit +sentence=Arduino library for the MCP9808 sensors in the Adafruit shop +paragraph=Arduino library for the MCP9808 sensors in the Adafruit shop +category=Sensors +url=https://github.com/adafruit/Adafruit_MCP9808_Library +architectures=* +depends=Adafruit Unified Sensor diff --git a/lib/Adafruit_MCP9808_Tasmota/license.txt b/lib/Adafruit_MCP9808_Tasmota/license.txt new file mode 100644 index 000000000..f6a0f22b8 --- /dev/null +++ b/lib/Adafruit_MCP9808_Tasmota/license.txt @@ -0,0 +1,26 @@ +Software License Agreement (BSD License) + +Copyright (c) 2012, Adafruit Industries +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright +notice, this list of conditions and the following disclaimer in the +documentation and/or other materials provided with the distribution. +3. Neither the name of the copyright holders nor the +names of its contributors may be used to endorse or promote products +derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY +EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY +DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/lib/JaretBurkett_ILI9488-gemu-1.0/ILI9488.cpp b/lib/JaretBurkett_ILI9488-gemu-1.0/ILI9488.cpp index 3e0aca9ee..495c18ad8 100644 --- a/lib/JaretBurkett_ILI9488-gemu-1.0/ILI9488.cpp +++ b/lib/JaretBurkett_ILI9488-gemu-1.0/ILI9488.cpp @@ -19,7 +19,7 @@ #include // if using software spi this optimizes the code -#define SWSPI_OPTMODE + #define ILI9488_START start(); #define ILI9488_STOP stop(); @@ -37,9 +37,6 @@ ILI9488::ILI9488(int8_t cs,int8_t mosi,int8_t sclk,int8_t bp) : Renderer(ILI9488 _hwspi = 0; } - -#include "spi_register.h" - /* CPU Clock = 80 Mhz @@ -73,6 +70,13 @@ GPIO15: PERIPHS_IO_MUX_MTDO_U uint8_t ili9488_start; +#ifndef ESP32 +// ESP8266 +#include "spi_register.h" +#define SWSPI_OPTMODE +// this enables the 27 bit packed mode +#define RGB_PACK_MODE + uint32_t ili9488_clock; uint32_t ili9488_usr; uint32_t ili9488_usr1; @@ -192,32 +196,6 @@ void ILI9488::stop(void) { ili9488_start=0; } - -#if 0 -// code from espressif SDK -/****************************************************************************** - * FunctionName : spi_lcd_9bit_write - * Description : SPI 9bits transmission function for driving LCD TM035PDZV36 - * Parameters : uint8 spi_no - SPI module number, Only "SPI" and "HSPI" are valid - * uint8 high_bit - first high bit of the data, 0 is for "0",the other value 1-255 is for "1" - * uint8 low_8bit- the rest 8bits of the data. -*******************************************************************************/ -void spi_lcd_9bit_write(uint8_t high_bit,uint8_t low_8bit) -{ - uint32_t regvalue; - uint8_t bytetemp; - - if(high_bit) bytetemp=(low_8bit>>1)|0x80; - else bytetemp=(low_8bit>>1)&0x7f; - - regvalue= ((8&SPI_USR_COMMAND_BITLEN)<>= 1) { + WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_sclk); + if(d&bit) WRITE_PERI_REG( PIN_OUT_SET, 1<<_mosi); + else WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_mosi); + WRITE_PERI_REG( PIN_OUT_SET, 1<<_sclk); } + WRITE_PERI_REG( PIN_OUT_SET, 1<<_cs); } -*/ +#else +// ESP32 section +void ILI9488::writedata(uint8_t d) { + fastSPIwrite(d,1); +} + +void ILI9488::writecommand(uint8_t c) { + fastSPIwrite(c,0); +} + +#include "soc/spi_reg.h" +#include "soc/spi_struct.h" +#include "esp32-hal-spi.h" +#include "esp32-hal.h" +#include "soc/spi_struct.h" + +#define RGB_PACK_MODE + +// since ardunio transferBits ia completely disfunctional +// we use our own hardware driver for 9 bit spi +void ILI9488::fastSPIwrite(uint8_t d,uint8_t dc) { + digitalWrite( _cs, LOW); + + uint32_t regvalue=d>>1; + if (dc) regvalue|=0x80; + else regvalue&=0x7f; + if (d&1) regvalue|=0x8000; + + REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI); + REG_WRITE(SPI_MOSI_DLEN_REG(3), 9 - 1); + uint32_t *dp=(uint32_t*)SPI_W0_REG(3); + *dp=regvalue; + REG_SET_BIT(SPI_CMD_REG(3), SPI_USR); + while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR)); + + digitalWrite( _cs, HIGH); +} + +SPISettings ili9488_spiSettings; + +void ILI9488::start(void) { + if (ili9488_start) return; + SPI.beginTransaction(ili9488_spiSettings); + ili9488_start=1; +} +void ILI9488::stop(void) { + if (!ili9488_start) return; + SPI.endTransaction(); + ili9488_start=0; +} +#endif + uint16_t ILI9488::GetColorFromIndex(uint8_t index) { if (index>=sizeof(ili9488_colors)/2) index=0; @@ -339,14 +349,23 @@ void ILI9488::begin(void) { pinMode(_bp, OUTPUT); digitalWrite(_bp,HIGH); } + +#ifndef ESP32 if ((_sclk==14) && (_mosi==13) && (_cs==15)) { // we use hardware spi + SPI.begin(); _hwspi=1; spi_lcd_mode_init(); } else { // we must use software spi _hwspi=0; } +#else + SPI.begin(_sclk,-1,_mosi, -1); + ili9488_spiSettings = SPISettings(10000000, MSBFIRST, SPI_MODE3); + _hwspi=1; +#endif + ILI9488_START delay(1); @@ -817,8 +836,7 @@ void ILI9488::fillScreen(uint16_t color) { //#define WRITE_SPI_REG -// this enables the 27 bit packed mode -#define RGB_PACK_MODE + // extremely strange => if this code is merged into pack_rgb() the software crashes // swap bytes @@ -844,9 +862,9 @@ uint32_t pack_rgb(uint32_t r, uint32_t g, uint32_t b) { return ulswap(data); } +#ifndef ESP32 // fill a rectangle -void ILI9488::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, - uint16_t color) { +void ILI9488::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { ILI9488_START // rudimentary clipping (drawChar w/big text requires this) @@ -990,6 +1008,56 @@ void ILI9488::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, #endif } +#else +// ESP32 +void ILI9488::fillRect(int16_t x, int16_t y, int16_t w, int16_t h, uint16_t color) { + + // rudimentary clipping (drawChar w/big text requires this) + if((x >= _width) || (y >= _height)) return; + if((x + w - 1) >= _width) w = _width - x; + if((y + h - 1) >= _height) h = _height - y; + + setAddrWindow(x, y, x+w-1, y+h-1); + + uint8_t r = (color & 0xF800) >> 11; + uint8_t g = (color & 0x07E0) >> 5; + uint8_t b = color & 0x001F; + + r = (r * 255) / 31; + g = (g * 255) / 63; + b = (b * 255) / 31; + +#ifdef RGB_PACK_MODE + // init 27 bit mode + uint32_t data=pack_rgb(r,g,b); + REG_SET_BIT(SPI_USER_REG(3), SPI_USR_MOSI); + REG_WRITE(SPI_MOSI_DLEN_REG(3), 27 - 1); + uint32_t *dp=(uint32_t*)SPI_W0_REG(3); + digitalWrite( _cs, LOW); +#endif + + for(y=h; y>0; y--) { + for(x=w; x>0; x--) { + #ifndef RGB_PACK_MODE + writedata(r); + writedata(g); + writedata(b); + #else + while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR)); + *dp=data; + REG_SET_BIT(SPI_CMD_REG(3), SPI_USR); + #endif + } + } + +#ifdef RGB_PACK_MODE + while (REG_GET_FIELD(SPI_CMD_REG(3), SPI_USR)); + digitalWrite( _cs, HIGH); +#endif + + ILI9488_STOP +} +#endif // Pass 8-bit (each) R,G,B, get back 16-bit packed color @@ -1040,65 +1108,3 @@ void ILI9488::invertDisplay(boolean i) { writecommand(i ? ILI9488_INVON : ILI9488_INVOFF); ILI9488_STOP } - -void ICACHE_RAM_ATTR ILI9488::fastSPIwrite(uint8_t d,uint8_t dc) { - - WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_cs); - WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_sclk); - if(dc) WRITE_PERI_REG( PIN_OUT_SET, 1<<_mosi); - else WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_mosi); - WRITE_PERI_REG( PIN_OUT_SET, 1<<_sclk); - - for(uint8_t bit = 0x80; bit; bit >>= 1) { - WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_sclk); - if(d&bit) WRITE_PERI_REG( PIN_OUT_SET, 1<<_mosi); - else WRITE_PERI_REG( PIN_OUT_CLEAR, 1<<_mosi); - WRITE_PERI_REG( PIN_OUT_SET, 1<<_sclk); - } - WRITE_PERI_REG( PIN_OUT_SET, 1<<_cs); -} - -/* - - uint16_t ILI9488::readcommand16(uint8_t c) { - digitalWrite(_dc, LOW); - if (_cs) - digitalWrite(_cs, LOW); - - spiwrite(c); - pinMode(_sid, INPUT); // input! - uint16_t r = spiread(); - r <<= 8; - r |= spiread(); - if (_cs) - digitalWrite(_cs, HIGH); - - pinMode(_sid, OUTPUT); // back to output - return r; - } - - uint32_t ILI9488::readcommand32(uint8_t c) { - digitalWrite(_dc, LOW); - if (_cs) - digitalWrite(_cs, LOW); - spiwrite(c); - pinMode(_sid, INPUT); // input! - - dummyclock(); - dummyclock(); - - uint32_t r = spiread(); - r <<= 8; - r |= spiread(); - r <<= 8; - r |= spiread(); - r <<= 8; - r |= spiread(); - if (_cs) - digitalWrite(_cs, HIGH); - - pinMode(_sid, OUTPUT); // back to output - return r; - } - - */ diff --git a/lib/TasmotaSerial-3.0.0/src/TasmotaSerial.cpp b/lib/TasmotaSerial-3.0.0/src/TasmotaSerial.cpp index 6b41d068c..6982779d5 100644 --- a/lib/TasmotaSerial-3.0.0/src/TasmotaSerial.cpp +++ b/lib/TasmotaSerial-3.0.0/src/TasmotaSerial.cpp @@ -174,6 +174,9 @@ bool TasmotaSerial::begin(long speed, int stop_bits) { m_uart = tasmota_serial_index; tasmota_serial_index--; TSerial = new HardwareSerial(m_uart); + if (serial_buffer_size > 256) { + TSerial->setRxBufferSize(serial_buffer_size); + } if (2 == m_stop_bits) { TSerial->begin(speed, SERIAL_8N2, m_rx_pin, m_tx_pin); } else { diff --git a/lib/UdpListener/library.properties b/lib/UdpListener/library.properties new file mode 100644 index 000000000..1d453bc6c --- /dev/null +++ b/lib/UdpListener/library.properties @@ -0,0 +1,7 @@ +name=UdpListener +version=1.0 +author=Ivan Grokhotkov, Stephan Hadinger +maintainer=Stephan +sentence=UdpListener optimized for static and limite memory allocation, to reduce memory footprint of receiving SSDP request, as a replacement for WifiUdp. +paragraph=This class only handles receiving UDP Multicast packets. For sending packets, use WifiUdp. +architectures=esp8266 diff --git a/lib/UdpListener/src/UdpListener.h b/lib/UdpListener/src/UdpListener.h new file mode 100644 index 000000000..a369a5d81 --- /dev/null +++ b/lib/UdpListener/src/UdpListener.h @@ -0,0 +1,209 @@ +/* + UdpListener.h - webserver for Tasmota + + Copyright (C) 2020 Theo Arends & Stephan Hadinger + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see .@ +*/ + +// adapted from: +/* + UdpContext.h - UDP connection handling on top of lwIP + + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +/* + * This is a stripped down version of Udp handler to avoid overflowing + * memory when lots of multicast SSDP packets arrive. + * The pbuf is freed immediately upon arrival of the packet. + * + * Packet data are kept in a statically area in RAM and keeps + * only the first bytes (200 by default) of each packet. + * The number of packets treated is limited (3 by default), any + * new packet arriving is dropped. + * + * This class does only receiving multicast packets for LWIP2 +*/ + +#ifndef UDPMULTICASTLISTENER_H +#define UDPMULTICASTLISTENER_H + +#ifdef ESP8266 +// #include + +extern "C" { +#include +#include +} + +template +struct UdpPacket { + IPAddress srcaddr; + IPAddress dstaddr; + int16_t srcport; + netif* input_netif; + size_t len; + uint8_t buf[PACKET_SIZE]; +}; + +template +class UdpListener +{ +public: + + typedef std::function rxhandler_t; + + UdpListener(size_t packet_number) + : _pcb(0) + , _packet_number(packet_number) + , _buffers(nullptr) + , _udp_packets(0) + , _udp_ready(false) + , _udp_index(0) + { + _packet_number = packet_number; + _buffers = new UdpPacket[_packet_number]; + _pcb = udp_new(); + } + + ~UdpListener() + { + udp_remove(_pcb); + _pcb = 0; + delete[] _buffers; + _buffers = nullptr; + } + + void reset(void) + { + _udp_packets = 0; + _udp_index = 0; + } + + bool listen(const IPAddress& addr, uint16_t port) + { + if (!_buffers) { return false; } + udp_recv(_pcb, &_s_recv, (void *) this); + err_t err = udp_bind(_pcb, addr, port); + return err == ERR_OK; + } + + void disconnect() + { + udp_disconnect(_pcb); + } + + bool next() + { + if (!_buffers) { return false; } + if (_udp_packets > 0) { + if (!_udp_ready) { + // we just consume the first packet + _udp_ready = true; + } else { + _udp_packets--; + _udp_index = (_udp_index + 1) % _packet_number; // advance to next buffer index in ring + if (_udp_packets == 0) { + _udp_ready = false; + } + } + } else { + _udp_ready = false; + } + return _udp_ready; + } + + UdpPacket * read(void) + { + if (!_buffers) { return nullptr; } + if (_udp_ready) { // we have a packet ready to consume + return &_buffers[_udp_index]; + } else { + return nullptr; + } + } + +private: + + void _recv(udp_pcb *upcb, pbuf *pb, + const ip_addr_t *srcaddr, u16_t srcport) + { + if (!_buffers) { pbuf_free(pb); return; } + // Serial.printf(">>> _recv: _udp_packets = %d, _udp_index = %d, tot_len = %d\n", _udp_packets, _udp_index, pb->tot_len); + if (_udp_packets >= _packet_number) { + // we don't have slots anymore, drop packet + pbuf_free(pb); + return; + } + + uint8_t next_slot = (_udp_index + _udp_packets) % _packet_number; + + size_t packet_len = pb->tot_len; + if (packet_len > PACKET_SIZE) { packet_len = PACKET_SIZE; } + + uint8_t * dst = &_buffers[next_slot].buf[0]; + void* buf = pbuf_get_contiguous(pb, dst, PACKET_SIZE, packet_len, 0); + if (buf) { + + if (buf != dst) + memcpy(dst, buf, packet_len); + _buffers[next_slot].len = packet_len; + + _buffers[next_slot].srcaddr = srcaddr; + _buffers[next_slot].dstaddr = ip_current_dest_addr(); + _buffers[next_slot].srcport = srcport; + _buffers[next_slot].input_netif = ip_current_input_netif(); + _udp_packets++; // we have one packet ready + } + pbuf_free(pb); // free memory immediately + } + + static void _s_recv(void *arg, + udp_pcb *upcb, pbuf *p, + CONST ip_addr_t *srcaddr, u16_t srcport) + { + reinterpret_cast(arg)->_recv(upcb, p, srcaddr, srcport); + } + +private: + udp_pcb* _pcb; + uint8_t _packet_number; + + UdpPacket * _buffers; + + // how many packets are ready. + int8_t _udp_packets; // number of udp packets ready to consume + bool _udp_ready; // is a packet currenlty consumed after a call to next() + // ring buffer ranges from 0..(_packet_number-1) + int8_t _udp_index; // current index in the ring buffer +}; + +#endif // ESP8266 +#endif //UDPMULTICASTLISTENER_H \ No newline at end of file diff --git a/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp b/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp index 1540f6327..1695a5177 100644 --- a/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp +++ b/libesp32/NimBLE-Arduino/src/NimBLEDevice.cpp @@ -40,7 +40,7 @@ static const char* LOG_TAG = "NimBLEDevice"; /** * Singletons for the NimBLEDevice. */ -bool initialized = false; +static bool initialized = false; #if defined(CONFIG_BT_NIMBLE_ROLE_OBSERVER) NimBLEScan* NimBLEDevice::m_pScan = nullptr; #endif diff --git a/platformio.ini b/platformio.ini index c9eb6d5f4..04835d6c0 100755 --- a/platformio.ini +++ b/platformio.ini @@ -74,6 +74,7 @@ build_flags = ${core_active.build_flags} build_unflags = -Wall board_build.f_cpu = 80000000L +board_build.f_flash = 40000000L monitor_speed = 115200 upload_speed = 115200 ; *** Upload Serial reset method for Wemos and NodeMCU diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 695973c2e..2becf05f7 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -52,6 +52,11 @@ build_flags = ${core_active.build_flags} ; set CPU frequency to 80MHz (default) or 160MHz ;board_build.f_cpu = 160000000L +; set Flash chip frequency to 40MHz (default), 20MHz, 26Mhz, 80Mhz +;board_build.f_flash = 20000000L +;board_build.f_flash = 26000000L +;board_build.f_flash = 80000000L + ; *** Upload Serial reset method for Wemos and NodeMCU upload_port = COM5 @@ -165,6 +170,7 @@ board = esp32dev board_build.ldscript = esp32_out.ld board_build.partitions = esp32_partition_app1984k_spiffs64k.csv board_build.flash_mode = ${common.board_build.flash_mode} +board_build.f_flash = ${common.board_build.f_flash} board_build.f_cpu = ${common.board_build.f_cpu} build_unflags = ${common.build_unflags} -Wpointer-arith @@ -192,6 +198,6 @@ lib_extra_dirs = libesp32 lib_ignore = - ILI9488 + ; ILI9488 ; SSD3115 cc1101 diff --git a/platformio_tasmota_env.ini b/platformio_tasmota_env.ini index dbf79d288..3d08d5cd6 100644 --- a/platformio_tasmota_env.ini +++ b/platformio_tasmota_env.ini @@ -5,6 +5,7 @@ framework = ${common.framework} board = ${common.board} board_build.ldscript = ${common.board_build.ldscript} board_build.flash_mode = ${common.board_build.flash_mode} +board_build.f_flash = ${common.board_build.f_flash} board_build.f_cpu = ${common.board_build.f_cpu} build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} diff --git a/platformio_tasmota_env32.ini b/platformio_tasmota_env32.ini index f4f99282c..4398dcb04 100644 --- a/platformio_tasmota_env32.ini +++ b/platformio_tasmota_env32.ini @@ -6,6 +6,7 @@ board = ${common32.board} board_build.ldscript = ${common32.board_build.ldscript} board_build.partitions = ${common32.board_build.partitions} board_build.flash_mode = ${common32.board_build.flash_mode} +board_build.f_flash = ${common32.board_build.f_flash} board_build.f_cpu = ${common32.board_build.f_cpu} monitor_speed = ${common32.monitor_speed} upload_port = ${common32.upload_port} diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 487c94e64..54e1a5f63 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -7,10 +7,16 @@ - Change Adafruit_SGP30 library from v1.0.3 to v1.2.0 (#8519) - Fix escape of non-JSON received serial data (#8329) - Add command ``Time 4`` to display timestamp using milliseconds (#8537) +- Add command ``SetOption94 0/1`` to select MAX31855 or MAX6675 thermocouple support (#8616) - Add commands ``LedPwmOn 0..255``, ``LedPwmOff 0..255`` and ``LedPwmMode1 0/1`` to control led brightness by George (#8491) - Add Three Phase Export Active Energy to SDM630 driver - Add wildcard pattern ``?`` for JSON matching in rules - Add support for unique MQTTClient (and inherited fallback topic) by full Mac address using ``mqttclient DVES_%12X`` (#8300) +- Add Zigbee options to ``ZbSend`` to write and report attributes +- Add ``CpuFrequency`` to ``status 2`` +- Add ``FlashFrequency`` to ``status 4`` +- Add support for up to two BH1750 sensors controlled by commands ``BH1750Resolution`` and ``BH1750MTime`` (#8139) +- Add Zigbee auto-responder for common attributes ### 8.3.1.1 20200518 diff --git a/tasmota/Parsing.cpp b/tasmota/Parsing.cpp deleted file mode 100644 index a7665d7b1..000000000 --- a/tasmota/Parsing.cpp +++ /dev/null @@ -1,627 +0,0 @@ -/* - Parsing.cpp - HTTP request parsing. - - Copyright (c) 2015 Ivan Grokhotkov. All rights reserved. - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - Modified 8 May 2015 by Hristo Gochkov (proper post and file upload handling) -*/ - -#ifdef ESP8266 - -// Use patched Parsing.cpp to fix ALEXA parsing issue in v2.4.2 -#include -#if defined(ARDUINO_ESP8266_RELEASE_2_4_2) -#warning **** Tasmota is using v2.4.2 patched Parsing.cpp as planned **** - -#include -#include "WiFiServer.h" -#include "WiFiClient.h" -#include "ESP8266WebServer.h" -#include "detail/mimetable.h" - -//#define DEBUG_ESP_HTTP_SERVER -#ifdef DEBUG_ESP_PORT -#define DEBUG_OUTPUT DEBUG_ESP_PORT -#else -#define DEBUG_OUTPUT Serial -#endif - -static const char Content_Type[] PROGMEM = "Content-Type"; -static const char filename[] PROGMEM = "filename"; - -static char* readBytesWithTimeout(WiFiClient& client, size_t maxLength, size_t& dataLength, int timeout_ms) -{ - char *buf = nullptr; - dataLength = 0; - while (dataLength < maxLength) { - int tries = timeout_ms; - size_t newLength; - while (!(newLength = client.available()) && tries--) delay(1); - if (!newLength) { - break; - } - if (!buf) { - buf = (char *) malloc(newLength + 1); - if (!buf) { - return nullptr; - } - } - else { - char* newBuf = (char *) realloc(buf, dataLength + newLength + 1); - if (!newBuf) { - free(buf); - return nullptr; - } - buf = newBuf; - } - client.readBytes(buf + dataLength, newLength); - dataLength += newLength; - buf[dataLength] = '\0'; - } - return buf; -} - -bool ESP8266WebServer::_parseRequest(WiFiClient& client) { - // Read the first line of HTTP request - String req = client.readStringUntil('\r'); - client.readStringUntil('\n'); - //reset header value - for (int i = 0; i < _headerKeysCount; ++i) { - _currentHeaders[i].value =String(); - } - - // First line of HTTP request looks like "GET /path HTTP/1.1" - // Retrieve the "/path" part by finding the spaces - int addr_start = req.indexOf(' '); - int addr_end = req.indexOf(' ', addr_start + 1); - if (addr_start == -1 || addr_end == -1) { -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("Invalid request: "); - DEBUG_OUTPUT.println(req); -#endif - return false; - } - - String methodStr = req.substring(0, addr_start); - String url = req.substring(addr_start + 1, addr_end); - String versionEnd = req.substring(addr_end + 8); - _currentVersion = atoi(versionEnd.c_str()); - String searchStr = ""; - int hasSearch = url.indexOf('?'); - if (hasSearch != -1){ - searchStr = url.substring(hasSearch + 1); - url = url.substring(0, hasSearch); - } - _currentUri = url; - _chunked = false; - - HTTPMethod method = HTTP_GET; - if (methodStr == F("POST")) { - method = HTTP_POST; - } else if (methodStr == F("DELETE")) { - method = HTTP_DELETE; - } else if (methodStr == F("OPTIONS")) { - method = HTTP_OPTIONS; - } else if (methodStr == F("PUT")) { - method = HTTP_PUT; - } else if (methodStr == F("PATCH")) { - method = HTTP_PATCH; - } - _currentMethod = method; - -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("method: "); - DEBUG_OUTPUT.print(methodStr); - DEBUG_OUTPUT.print(" url: "); - DEBUG_OUTPUT.print(url); - DEBUG_OUTPUT.print(" search: "); - DEBUG_OUTPUT.println(searchStr); -#endif - - //attach handler - RequestHandler* handler; - for (handler = _firstHandler; handler; handler = handler->next()) { - if (handler->canHandle(_currentMethod, _currentUri)) - break; - } - _currentHandler = handler; - - String formData; - // below is needed only when POST type request - if (method == HTTP_POST || method == HTTP_PUT || method == HTTP_PATCH || method == HTTP_DELETE){ - String boundaryStr; - String headerName; - String headerValue; - bool isForm = false; - bool isEncoded = false; - uint32_t contentLength = 0; - //parse headers - while(1){ - req = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (req == "") break;//no moar headers - int headerDiv = req.indexOf(':'); - if (headerDiv == -1){ - break; - } - headerName = req.substring(0, headerDiv); - headerValue = req.substring(headerDiv + 1); - headerValue.trim(); - _collectHeader(headerName.c_str(),headerValue.c_str()); - - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("headerName: "); - DEBUG_OUTPUT.println(headerName); - DEBUG_OUTPUT.print("headerValue: "); - DEBUG_OUTPUT.println(headerValue); - #endif - - if (headerName.equalsIgnoreCase(FPSTR(Content_Type))){ - using namespace mime; - if (headerValue.startsWith(FPSTR(mimeTable[txt].mimeType))){ - isForm = false; - } else if (headerValue.startsWith(F("application/x-www-form-urlencoded"))){ - isForm = false; - isEncoded = true; - } else if (headerValue.startsWith(F("multipart/"))){ - boundaryStr = headerValue.substring(headerValue.indexOf('=') + 1); - boundaryStr.replace("\"",""); - isForm = true; - } - } else if (headerName.equalsIgnoreCase(F("Content-Length"))){ - contentLength = headerValue.toInt(); - } else if (headerName.equalsIgnoreCase(F("Host"))){ - _hostHeader = headerValue; - } - } - - if (!isForm){ - size_t plainLength; - char* plainBuf = readBytesWithTimeout(client, contentLength, plainLength, HTTP_MAX_POST_WAIT); - if (plainLength < contentLength) { - free(plainBuf); - return false; - } - if (contentLength > 0) { - if(isEncoded){ - //url encoded form - if (searchStr != "") searchStr += '&'; - searchStr += plainBuf; - } - _parseArguments(searchStr); - if(!isEncoded||(0==_currentArgCount)){ // @20180124OF01: Workarround for Alexa Bug - //plain post json or other data - RequestArgument& arg = _currentArgs[_currentArgCount++]; - arg.key = F("plain"); - arg.value = String(plainBuf); - } - - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("Plain: "); - DEBUG_OUTPUT.println(plainBuf); - #endif - free(plainBuf); - } else { - // No content - but we can still have arguments in the URL. - _parseArguments(searchStr); - } - } - - if (isForm){ - _parseArguments(searchStr); - if (!_parseForm(client, boundaryStr, contentLength)) { - return false; - } - } - } else { - String headerName; - String headerValue; - //parse headers - while(1){ - req = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (req == "") break;//no moar headers - int headerDiv = req.indexOf(':'); - if (headerDiv == -1){ - break; - } - headerName = req.substring(0, headerDiv); - headerValue = req.substring(headerDiv + 2); - _collectHeader(headerName.c_str(),headerValue.c_str()); - - #ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("headerName: "); - DEBUG_OUTPUT.println(headerName); - DEBUG_OUTPUT.print("headerValue: "); - DEBUG_OUTPUT.println(headerValue); - #endif - - if (headerName.equalsIgnoreCase("Host")){ - _hostHeader = headerValue; - } - } - _parseArguments(searchStr); - } - client.flush(); - -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("Request: "); - DEBUG_OUTPUT.println(url); - DEBUG_OUTPUT.print(" Arguments: "); - DEBUG_OUTPUT.println(searchStr); -#endif - - return true; -} - -bool ESP8266WebServer::_collectHeader(const char* headerName, const char* headerValue) { - for (int i = 0; i < _headerKeysCount; i++) { - if (_currentHeaders[i].key.equalsIgnoreCase(headerName)) { - _currentHeaders[i].value=headerValue; - return true; - } - } - return false; -} - -void ESP8266WebServer::_parseArguments(String data) { -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("args: "); - DEBUG_OUTPUT.println(data); -#endif - if (_currentArgs) - delete[] _currentArgs; - _currentArgs = 0; - if (data.length() == 0) { - _currentArgCount = 0; - _currentArgs = new RequestArgument[1]; - return; - } - _currentArgCount = 1; - - for (int i = 0; i < (int)data.length(); ) { - i = data.indexOf('&', i); - if (i == -1) - break; - ++i; - ++_currentArgCount; - } -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("args count: "); - DEBUG_OUTPUT.println(_currentArgCount); -#endif - - _currentArgs = new RequestArgument[_currentArgCount+1]; - int pos = 0; - int iarg; - for (iarg = 0; iarg < _currentArgCount;) { - int equal_sign_index = data.indexOf('=', pos); - int next_arg_index = data.indexOf('&', pos); -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("pos "); - DEBUG_OUTPUT.print(pos); - DEBUG_OUTPUT.print("=@ "); - DEBUG_OUTPUT.print(equal_sign_index); - DEBUG_OUTPUT.print(" &@ "); - DEBUG_OUTPUT.println(next_arg_index); -#endif - if ((equal_sign_index == -1) || ((equal_sign_index > next_arg_index) && (next_arg_index != -1))) { -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("arg missing value: "); - DEBUG_OUTPUT.println(iarg); -#endif - if (next_arg_index == -1) - break; - pos = next_arg_index + 1; - continue; - } - RequestArgument& arg = _currentArgs[iarg]; - arg.key = urlDecode(data.substring(pos, equal_sign_index)); - arg.value = urlDecode(data.substring(equal_sign_index + 1, next_arg_index)); -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("arg "); - DEBUG_OUTPUT.print(iarg); - DEBUG_OUTPUT.print(" key: "); - DEBUG_OUTPUT.print(arg.key); - DEBUG_OUTPUT.print(" value: "); - DEBUG_OUTPUT.println(arg.value); -#endif - ++iarg; - if (next_arg_index == -1) - break; - pos = next_arg_index + 1; - } - _currentArgCount = iarg; -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("args count: "); - DEBUG_OUTPUT.println(_currentArgCount); -#endif - -} - -void ESP8266WebServer::_uploadWriteByte(uint8_t b){ - if (_currentUpload->currentSize == HTTP_UPLOAD_BUFLEN){ - if(_currentHandler && _currentHandler->canUpload(_currentUri)) - _currentHandler->upload(*this, _currentUri, *_currentUpload); - _currentUpload->totalSize += _currentUpload->currentSize; - _currentUpload->currentSize = 0; - } - _currentUpload->buf[_currentUpload->currentSize++] = b; -} - -uint8_t ESP8266WebServer::_uploadReadByte(WiFiClient& client){ - int res = client.read(); - if(res == -1){ - while(!client.available() && client.connected()) - yield(); - res = client.read(); - } - return (uint8_t)res; -} - -bool ESP8266WebServer::_parseForm(WiFiClient& client, String boundary, uint32_t len){ - (void) len; -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("Parse Form: Boundary: "); - DEBUG_OUTPUT.print(boundary); - DEBUG_OUTPUT.print(" Length: "); - DEBUG_OUTPUT.println(len); -#endif - String line; - int retry = 0; - do { - line = client.readStringUntil('\r'); - ++retry; - } while (line.length() == 0 && retry < 3); - - client.readStringUntil('\n'); - //start reading the form - if (line == ("--"+boundary)){ - RequestArgument* postArgs = new RequestArgument[32]; - int postArgsLen = 0; - while(1){ - String argName; - String argValue; - String argType; - String argFilename; - bool argIsFile = false; - - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (line.length() > 19 && line.substring(0, 19).equalsIgnoreCase(F("Content-Disposition"))){ - int nameStart = line.indexOf('='); - if (nameStart != -1){ - argName = line.substring(nameStart+2); - nameStart = argName.indexOf('='); - if (nameStart == -1){ - argName = argName.substring(0, argName.length() - 1); - } else { - argFilename = argName.substring(nameStart+2, argName.length() - 1); - argName = argName.substring(0, argName.indexOf('"')); - argIsFile = true; -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("PostArg FileName: "); - DEBUG_OUTPUT.println(argFilename); -#endif - //use GET to set the filename if uploading using blob - if (argFilename == F("blob") && hasArg(FPSTR(filename))) - argFilename = arg(FPSTR(filename)); - } -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("PostArg Name: "); - DEBUG_OUTPUT.println(argName); -#endif - using namespace mime; - argType = FPSTR(mimeTable[txt].mimeType); - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (line.length() > 12 && line.substring(0, 12).equalsIgnoreCase(FPSTR(Content_Type))){ - argType = line.substring(line.indexOf(':')+2); - //skip next line - client.readStringUntil('\r'); - client.readStringUntil('\n'); - } -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("PostArg Type: "); - DEBUG_OUTPUT.println(argType); -#endif - if (!argIsFile){ - while(1){ - line = client.readStringUntil('\r'); - client.readStringUntil('\n'); - if (line.startsWith("--"+boundary)) break; - if (argValue.length() > 0) argValue += "\n"; - argValue += line; - } -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("PostArg Value: "); - DEBUG_OUTPUT.println(argValue); - DEBUG_OUTPUT.println(); -#endif - - RequestArgument& arg = postArgs[postArgsLen++]; - arg.key = argName; - arg.value = argValue; - - if (line == ("--"+boundary+"--")){ -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Done Parsing POST"); -#endif - break; - } - } else { - _currentUpload.reset(new HTTPUpload()); - _currentUpload->status = UPLOAD_FILE_START; - _currentUpload->name = argName; - _currentUpload->filename = argFilename; - _currentUpload->type = argType; - _currentUpload->totalSize = 0; - _currentUpload->currentSize = 0; -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("Start File: "); - DEBUG_OUTPUT.print(_currentUpload->filename); - DEBUG_OUTPUT.print(" Type: "); - DEBUG_OUTPUT.println(_currentUpload->type); -#endif - if(_currentHandler && _currentHandler->canUpload(_currentUri)) - _currentHandler->upload(*this, _currentUri, *_currentUpload); - _currentUpload->status = UPLOAD_FILE_WRITE; - uint8_t argByte = _uploadReadByte(client); -readfile: - while(argByte != 0x0D){ - if (!client.connected()) return _parseFormUploadAborted(); - _uploadWriteByte(argByte); - argByte = _uploadReadByte(client); - } - - argByte = _uploadReadByte(client); - if (!client.connected()) return _parseFormUploadAborted(); - if (argByte == 0x0A){ - argByte = _uploadReadByte(client); - if (!client.connected()) return _parseFormUploadAborted(); - if ((char)argByte != '-'){ - //continue reading the file - _uploadWriteByte(0x0D); - _uploadWriteByte(0x0A); - goto readfile; - } else { - argByte = _uploadReadByte(client); - if (!client.connected()) return _parseFormUploadAborted(); - if ((char)argByte != '-'){ - //continue reading the file - _uploadWriteByte(0x0D); - _uploadWriteByte(0x0A); - _uploadWriteByte((uint8_t)('-')); - goto readfile; - } - } - - uint8_t endBuf[boundary.length()]; - client.readBytes(endBuf, boundary.length()); - - if (strstr((const char*)endBuf, boundary.c_str()) != nullptr){ - if(_currentHandler && _currentHandler->canUpload(_currentUri)) - _currentHandler->upload(*this, _currentUri, *_currentUpload); - _currentUpload->totalSize += _currentUpload->currentSize; - _currentUpload->status = UPLOAD_FILE_END; - if(_currentHandler && _currentHandler->canUpload(_currentUri)) - _currentHandler->upload(*this, _currentUri, *_currentUpload); -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("End File: "); - DEBUG_OUTPUT.print(_currentUpload->filename); - DEBUG_OUTPUT.print(" Type: "); - DEBUG_OUTPUT.print(_currentUpload->type); - DEBUG_OUTPUT.print(" Size: "); - DEBUG_OUTPUT.println(_currentUpload->totalSize); -#endif - line = client.readStringUntil(0x0D); - client.readStringUntil(0x0A); - if (line == "--"){ -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.println("Done Parsing POST"); -#endif - break; - } - continue; - } else { - _uploadWriteByte(0x0D); - _uploadWriteByte(0x0A); - _uploadWriteByte((uint8_t)('-')); - _uploadWriteByte((uint8_t)('-')); - uint32_t i = 0; - while(i < boundary.length()){ - _uploadWriteByte(endBuf[i++]); - } - argByte = _uploadReadByte(client); - goto readfile; - } - } else { - _uploadWriteByte(0x0D); - goto readfile; - } - break; - } - } - } - } - - int iarg; - int totalArgs = ((32 - postArgsLen) < _currentArgCount)?(32 - postArgsLen):_currentArgCount; - for (iarg = 0; iarg < totalArgs; iarg++){ - RequestArgument& arg = postArgs[postArgsLen++]; - arg.key = _currentArgs[iarg].key; - arg.value = _currentArgs[iarg].value; - } - if (_currentArgs) delete[] _currentArgs; - _currentArgs = new RequestArgument[postArgsLen]; - for (iarg = 0; iarg < postArgsLen; iarg++){ - RequestArgument& arg = _currentArgs[iarg]; - arg.key = postArgs[iarg].key; - arg.value = postArgs[iarg].value; - } - _currentArgCount = iarg; - if (postArgs) - delete[] postArgs; - return true; - } -#ifdef DEBUG_ESP_HTTP_SERVER - DEBUG_OUTPUT.print("Error: line: "); - DEBUG_OUTPUT.println(line); -#endif - return false; -} - -String ESP8266WebServer::urlDecode(const String& text) -{ - String decoded = ""; - char temp[] = "0x00"; - unsigned int len = text.length(); - unsigned int i = 0; - while (i < len) - { - char decodedChar; - char encodedChar = text.charAt(i++); - if ((encodedChar == '%') && (i + 1 < len)) - { - temp[2] = text.charAt(i++); - temp[3] = text.charAt(i++); - - decodedChar = strtol(temp, NULL, 16); - } - else { - if (encodedChar == '+') - { - decodedChar = ' '; - } - else { - decodedChar = encodedChar; // normal ascii char - } - } - decoded += decodedChar; - } - return decoded; -} - -bool ESP8266WebServer::_parseFormUploadAborted(){ - _currentUpload->status = UPLOAD_FILE_ABORTED; - if(_currentHandler && _currentHandler->canUpload(_currentUri)) - _currentHandler->upload(*this, _currentUri, *_currentUpload); - return false; -} - -#endif // ARDUINO_ESP8266_RELEASE - -#endif // ESP8266 diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 89d931908..01122fabd 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -147,6 +147,7 @@ #define D_JSON_SPEED "Speed" #define D_JSON_SPEED_UNIT "SpeedUnit" #define D_JSON_SSID "SSId" +#define D_JSON_STAGE "Stage" #define D_JSON_STARTDST "StartDST" // Start Daylight Savings Time #define D_JSON_STARTED "Started" #define D_JSON_STARTUPUTC "StartupUTC" @@ -319,6 +320,11 @@ #define D_CMND_HUMOFFSET "HumOffset" #define D_CMND_GLOBAL_TEMP "GlobalTemp" #define D_CMND_GLOBAL_HUM "GlobalHum" +#ifdef ESP32 +#define D_CMND_TOUCH_CAL "TouchCal" +#define D_CMND_TOUCH_THRES "TouchThres" +#define D_CMND_TOUCH_NUM "TouchNum" +#endif //ESP32 // Commands xdrv_01_mqtt.ino #define D_CMND_MQTTLOG "MqttLog" @@ -513,10 +519,16 @@ #define D_CMND_ZIGBEE_FORGET "Forget" #define D_CMND_ZIGBEE_SAVE "Save" #define D_CMND_ZIGBEE_LINKQUALITY "LinkQuality" + #define D_CMND_ZIGBEE_CLUSTER "Cluster" #define D_CMND_ZIGBEE_ENDPOINT "Endpoint" #define D_CMND_ZIGBEE_GROUP "Group" + #define D_CMND_ZIGBEE_MANUF "Manuf" + #define D_CMND_ZIGBEE_DEVICE "Device" #define D_CMND_ZIGBEE_READ "Read" #define D_CMND_ZIGBEE_SEND "Send" +#define D_CMND_ZIGBEE_WRITE "Write" +#define D_CMND_ZIGBEE_REPORT "Report" +#define D_CMND_ZIGBEE_RESPONSE "Response" #define D_JSON_ZIGBEE_ZCL_SENT "ZbZCLSent" #define D_JSON_ZIGBEE_RECEIVED "ZbReceived" #define D_CMND_ZIGBEE_BIND "Bind" diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index 9f676f101..aa83ec0c2 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -107,7 +107,7 @@ #define D_HOST "Host" #define D_HOSTNAME "Hostname" #define D_HUMIDITY "Feuchtigkeit" -#define D_ILLUMINANCE "Beleuchtungsintensität" +#define D_ILLUMINANCE "Beleuchtungsstärke" #define D_IMMEDIATE "direkt" // Button immediate #define D_INDEX "Index" #define D_INFO "Info" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 46d88e0d7..050c2e390 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -518,6 +518,7 @@ // #define USE_AS3935 // [I2cDriver48] Enable AS3935 Franklin Lightning Sensor (I2C address 0x03) (+5k4 code) // #define USE_VEML6075 // [I2cDriver49] Enable VEML6075 UVA/UVB/UVINDEX Sensor (I2C address 0x10) (+2k1 code) // #define USE_VEML7700 // [I2cDriver50] Enable VEML7700 Ambient Light sensor (I2C addresses 0x10) (+4k5 code) +// #define USE_MCP9808 // [I2cDriver51] Enable MCP9808 temperature sensor (I2C addresses 0x18 - 0x1F) (+0k9 code) // #define USE_DISPLAY // Add I2C Display Support (+2k code) #define USE_DISPLAY_MODES1TO5 // Enable display mode 1 to 5 in addition to mode 0 @@ -611,12 +612,12 @@ // -- Low level interface devices ----------------- #define USE_DHT // Add support for DHT11, AM2301 (DHT21, DHT22, AM2302, AM2321) and SI7021 Temperature and Humidity sensor (1k6 code) -//#define USE_MAX31855 // Add support for MAX31855 K-Type thermocouple sensor using softSPI +//#define USE_MAX31855 // Add support for MAX31855/MAX6675 K-Type thermocouple sensor using softSPI //#define USE_MAX31865 // Add support for MAX31865 RTD sensors using softSPI - #define MAX31865_PTD_WIRES 2 // PTDs come in several flavors. Pick yours - #define MAX31865_PTD_RES 100 // Nominal PTD resistance at 0°C (100Ω for a PT100, 1000Ω for a PT1000, YMMV!) - #define MAX31865_REF_RES 430 // Reference resistor (Usually 430Ω for a PT100, 4300Ω for a PT1000) - #define MAX31865_PTD_BIAS 0 // To calibrate your not-so-good PTD + #define MAX31865_PTD_WIRES 2 // PTDs come in several flavors. Pick yours + #define MAX31865_PTD_RES 100 // Nominal PTD resistance at 0°C (100Ω for a PT100, 1000Ω for a PT1000, YMMV!) + #define MAX31865_REF_RES 430 // Reference resistor (Usually 430Ω for a PT100, 4300Ω for a PT1000) + #define MAX31865_PTD_BIAS 0 // To calibrate your not-so-good PTD // -- IR Remote features - all protocols from IRremoteESP8266 -------------------------- // IR Full Protocols mode is activated through platform.io only. diff --git a/tasmota/settings.h b/tasmota/settings.h index accf21842..1b0cc0951 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -113,7 +113,7 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint32_t fade_at_startup : 1; // bit 9 (v8.2.0.3) - SetOption91 - Enable light fading at start/power on uint32_t pwm_ct_mode : 1; // bit 10 (v8.2.0.4) - SetOption92 - Set PWM Mode from regular PWM to ColorTemp control (Xiaomi Philips ...) uint32_t compress_rules_cpu : 1; // bit 11 (v8.2.0.6) - SetOption93 - Keep uncompressed rules in memory to avoid CPU load of uncompressing at each tick - uint32_t spare12 : 1; + uint32_t max6675 : 1; // bit 12 (v8.3.1.2) - SetOption94 - Implement simpler MAX6675 protocol instead of MAX31855 uint32_t spare13 : 1; uint32_t spare14 : 1; uint32_t spare15 : 1; @@ -232,9 +232,8 @@ typedef union { struct { uint8_t spare0 : 1; uint8_t spare1 : 1; - uint8_t spare2 : 1; - uint8_t spare3 : 1; - uint8_t bh1750_resolution : 2; // Sensor10 1,2,3 + uint8_t bh1750_2_resolution : 2; + uint8_t bh1750_1_resolution : 2; // Sensor10 1,2,3 uint8_t hx711_json_weight_change : 1; // Sensor34 8,x - Enable JSON message on weight change uint8_t mhz19b_abc_disable : 1; // Disable ABC (Automatic Baseline Correction for MHZ19(B) (0 = Enabled (default), 1 = Disabled with Sensor15 command) }; diff --git a/tasmota/support.ino b/tasmota/support.ino index 9d1ff07f4..839e126f6 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -1459,6 +1459,7 @@ bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size) } retry--; } + if (!retry) Wire.endTransmission(); return status; } diff --git a/tasmota/support_button.ino b/tasmota/support_button.ino index 96dfaf73a..bf058db4e 100644 --- a/tasmota/support_button.ino +++ b/tasmota/support_button.ino @@ -44,7 +44,7 @@ struct BUTTON { uint8_t dual_receive_count = 0; // Sonoff dual input flag uint8_t no_pullup_mask = 0; // key no pullup flag (1 = no pullup) uint8_t inverted_mask = 0; // Key inverted flag (1 = inverted) -#ifdef ESP32 +#ifdef ESP32 uint8_t touch_mask = 0; // Touch flag (1 = inverted) uint8_t touch_hits[MAX_KEYS] = { 0 }; // Hits in a row to filter out noise #endif // ESP32 @@ -52,6 +52,14 @@ struct BUTTON { uint8_t adc = 99; // ADC0 button number } Button; +#ifdef ESP32 +struct TOUCH_BUTTON { + uint8_t pin_threshold = TOUCH_PIN_THRESHOLD; + uint8_t hit_threshold = TOUCH_HIT_THRESHOLD; + uint8_t calibration = 0; // Bitfield +} TOUCH_BUTTON; +#endif // ESP32 + /********************************************************************************************/ void ButtonPullupFlag(uint8 button_bit) @@ -72,6 +80,11 @@ void ButtonTouchFlag(uint8 button_bit) void ButtonInit(void) { Button.present = 0; +#ifdef ESP8266 + if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { + Button.present++; + } +#endif // ESP8266 for (uint32_t i = 0; i < MAX_KEYS; i++) { if (PinUsed(GPIO_KEY1, i)) { Button.present++; @@ -143,29 +156,35 @@ void ButtonHandler(void) Button.dual_code = 0; } } - else - if (PinUsed(GPIO_KEY1, button_index)) { - button_present = 1; - button = (digitalRead(Pin(GPIO_KEY1, button_index)) != bitRead(Button.inverted_mask, button_index)); + else { + if (PinUsed(GPIO_KEY1, button_index)) { + button_present = 1; + button = (digitalRead(Pin(GPIO_KEY1, button_index)) != bitRead(Button.inverted_mask, button_index)); + } } #else if (PinUsed(GPIO_KEY1, button_index)) { button_present = 1; - if (bitRead(Button.touch_mask, button_index)){ // Touch + if (bitRead(Button.touch_mask, button_index)) { // Touch uint32_t _value = touchRead(Pin(GPIO_KEY1, button_index)); button = NOT_PRESSED; - if (_value != 0){ // probably read-error - if(_value < TOUCH_PIN_THRESHOLD){ - if(++Button.touch_hits[button_index]>TOUCH_HIT_THRESHOLD){ - button = PRESSED; - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Touch value: %u hits: %u"), _value, Button.touch_hits[button_index]); + if (_value != 0) { // Probably read-error + if (_value < TOUCH_BUTTON.pin_threshold) { + if (++Button.touch_hits[button_index] > TOUCH_BUTTON.hit_threshold) { + if (!bitRead(TOUCH_BUTTON.calibration, button_index+1)) { + button = PRESSED; + } } + } else { + Button.touch_hits[button_index] = 0; } - else Button.touch_hits[button_index] = 0; + } else { + Button.touch_hits[button_index] = 0; } - else Button.touch_hits[button_index] = 0; - } - else{ // Normal button + if (bitRead(TOUCH_BUTTON.calibration, button_index+1)) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("PLOT: %u, %u, %u,"), button_index+1, _value, Button.touch_hits[button_index]); // Button number (1..4), value, continuous hits under threshold + } + } else { // Normal button button = (digitalRead(Pin(GPIO_KEY1, button_index)) != bitRead(Button.inverted_mask, button_index)); } } @@ -202,12 +221,12 @@ void ButtonHandler(void) if (!Button.hold_timer[button_index]) { button_pressed = true; } // Do not allow within 1 second } if (button_pressed) { - if (!Settings.flag3.mqtt_buttons) { + if (!Settings.flag3.mqtt_buttons) { // SetOption73 (0) - Decouple button from relay and send just mqtt topic if (!SendKey(KEY_BUTTON, button_index +1, POWER_TOGGLE)) { // Execute Toggle command via MQTT if ButtonTopic is set ExecuteCommandPower(button_index +1, POWER_TOGGLE, SRC_BUTTON); // Execute Toggle command internally } } else { - MqttButtonTopic(button_index +1, 1, 0); // SetOption73 (0) - Decouple button from relay and send just mqtt topic + MqttButtonTopic(button_index +1, 1, 0); // SetOption73 (0) - Decouple button from relay and send just mqtt topic } } } @@ -236,7 +255,7 @@ void ButtonHandler(void) Button.hold_timer[button_index] = 0; } else { Button.hold_timer[button_index]++; - if (Settings.flag.button_single) { // SetOption13 (0) - Allow only single button press for immediate action + if (Settings.flag.button_single) { // SetOption13 (0) - Allow only single button press for immediate action if (Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10) { // SetOption32 (40) - Button held for factor times longer snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_SETOPTION "13 0")); // Disable single press only ExecuteCommand(scmnd, SRC_BUTTON); @@ -244,13 +263,13 @@ void ButtonHandler(void) } else { if (Button.hold_timer[button_index] == loops_per_second * Settings.param[P_HOLD_TIME] / 10) { // SetOption32 (40) - Button hold Button.press_counter[button_index] = 0; - if (Settings.flag3.mqtt_buttons) { // SetOption73 (0) - Decouple button from relay and send just mqtt topic + if (Settings.flag3.mqtt_buttons) { // SetOption73 (0) - Decouple button from relay and send just mqtt topic MqttButtonTopic(button_index +1, 3, 1); } else { SendKey(KEY_BUTTON, button_index +1, POWER_HOLD); // Execute Hold command via MQTT if ButtonTopic is set } } else { - if (!Settings.flag.button_restrict) { + if (!Settings.flag.button_restrict) { // SetOption1 - Control button multipress if ((Button.hold_timer[button_index] == loops_per_second * hold_time_extent * Settings.param[P_HOLD_TIME] / 10)) { // SetOption32 (40) - Button held for factor times longer Button.press_counter[button_index] = 0; snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); @@ -261,14 +280,14 @@ void ButtonHandler(void) } } - if (!Settings.flag.button_single) { // SetOption13 (0) - Allow multi-press + if (!Settings.flag.button_single) { // SetOption13 (0) - Allow multi-press if (Button.window_timer[button_index]) { Button.window_timer[button_index]--; } else { if (!restart_flag && !Button.hold_timer[button_index] && (Button.press_counter[button_index] > 0) && (Button.press_counter[button_index] < 7)) { bool single_press = false; - if (Button.press_counter[button_index] < 3) { // Single or Double press + if (Button.press_counter[button_index] < 3) { // Single or Double press #ifdef ESP8266 if ((SONOFF_DUAL_R2 == my_module_type) || (SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { single_press = true; @@ -293,15 +312,21 @@ void ButtonHandler(void) if (WifiState() > WIFI_RESTART) { // Wifimanager active restart_flag = 1; } - if (!Settings.flag3.mqtt_buttons) { - if (Button.press_counter[button_index] == 1) { // By default first press always send a TOGGLE (2) + if (!Settings.flag3.mqtt_buttons) { // SetOption73 - Detach buttons from relays and enable MQTT action state for multipress + if (Button.press_counter[button_index] == 1) { // By default first press always send a TOGGLE (2) ExecuteCommandPower(button_index + Button.press_counter[button_index], POWER_TOGGLE, SRC_BUTTON); } else { SendKey(KEY_BUTTON, button_index +1, Button.press_counter[button_index] +9); // 2,3,4 and 5 press send just the key value (11,12,13 and 14) for rules - if (0 == button_index) { // BUTTON1 can toggle up to 5 relays if present. If a relay is not present will send out the key value (2,11,12,13 and 14) for rules - if ((Button.press_counter[button_index] > 1 && PinUsed(GPIO_REL1, Button.press_counter[button_index]-1)) && Button.press_counter[button_index] <= MAX_RELAY_BUTTON1) { + if (0 == button_index) { // BUTTON1 can toggle up to 5 relays if present. If a relay is not present will send out the key value (2,11,12,13 and 14) for rules + bool valid_relay = PinUsed(GPIO_REL1, Button.press_counter[button_index]-1); +#ifdef ESP8266 + if ((SONOFF_DUAL == my_module_type) || (CH4 == my_module_type)) { + valid_relay = (Button.press_counter[button_index] <= devices_present); + } +#endif // ESP8266 + if ((Button.press_counter[button_index] > 1) && valid_relay && (Button.press_counter[button_index] <= MAX_RELAY_BUTTON1)) { ExecuteCommandPower(button_index + Button.press_counter[button_index], POWER_TOGGLE, SRC_BUTTON); // Execute Toggle command internally - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: Relay%d found on GPIO%d"), Button.press_counter[button_index], Pin(GPIO_REL1, Button.press_counter[button_index]-1)); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: Relay%d found on GPIO%d"), Button.press_counter[button_index], Pin(GPIO_REL1, Button.press_counter[button_index]-1)); } } } diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 9d9fbfa19..f143a9316 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -38,7 +38,11 @@ const char kTasmotaCommands[] PROGMEM = "|" // No prefix #endif // USE_DEVICE_GROUPS_SEND D_CMND_DEVGROUP_SHARE "|" D_CMND_DEVGROUPSTATUS "|" #endif // USE_DEVICE_GROUPS - D_CMND_SENSOR "|" D_CMND_DRIVER; + D_CMND_SENSOR "|" D_CMND_DRIVER +#ifdef ESP32 + "|" D_CMND_TOUCH_CAL "|" D_CMND_TOUCH_THRES "|" D_CMND_TOUCH_NUM +#endif //ESP32 + ; void (* const TasmotaCommand[])(void) PROGMEM = { &CmndBacklog, &CmndDelay, &CmndPower, &CmndStatus, &CmndState, &CmndSleep, &CmndUpgrade, &CmndUpgrade, &CmndOtaUrl, @@ -61,7 +65,11 @@ void (* const TasmotaCommand[])(void) PROGMEM = { #endif // USE_DEVICE_GROUPS_SEND &CmndDevGroupShare, &CmndDevGroupStatus, #endif // USE_DEVICE_GROUPS - &CmndSensor, &CmndDriver }; + &CmndSensor, &CmndDriver +#ifdef ESP32 + ,&CmndTouchCal, &CmndTouchThres, &CmndTouchNum +#endif //ESP32 + }; const char kWifiConfig[] PROGMEM = D_WCFG_0_RESTART "||" D_WCFG_2_WIFIMANAGER "||" D_WCFG_4_RETRY "|" D_WCFG_5_WAIT "|" D_WCFG_6_SERIAL "|" D_WCFG_7_WIFIMANAGER_RESET_ONLY; @@ -436,14 +444,14 @@ void CmndStatus(void) ",\"" D_JSON_BOOTVERSION "\":%d" #endif ",\"" D_JSON_COREVERSION "\":\"" ARDUINO_CORE_RELEASE "\",\"" D_JSON_SDKVERSION "\":\"%s\"," - "\"Hardware\":\"%s\"" + "\"CpuFrequency\":%d,\"Hardware\":\"%s\"" "%s}}"), my_version, my_image, GetBuildDateAndTime().c_str() #ifdef ESP8266 , ESP.getBootVersion() #endif , ESP.getSdkVersion(), - GetDeviceHardware().c_str(), + ESP.getCpuFreqMHz(), GetDeviceHardware().c_str(), GetStatistics().c_str()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "2")); } @@ -468,7 +476,7 @@ void CmndStatus(void) #ifdef ESP8266 ",\"" D_JSON_FLASHCHIPID "\":\"%06X\"" #endif - ",\"" D_JSON_FLASHMODE "\":%d,\"" + ",\"FlashFrequency\":%d,\"" D_JSON_FLASHMODE "\":%d,\"" D_JSON_FEATURES "\":[\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\",\"%08X\"]"), ESP_getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP_getFreeHeap()/1024, #ifdef ESP32 @@ -478,7 +486,7 @@ void CmndStatus(void) #ifdef ESP8266 , ESP.getFlashChipId() #endif - , ESP.getFlashChipMode(), + , ESP.getFlashChipSpeed()/1000000, ESP.getFlashChipMode(), LANGUAGE_LCID, feature_drv1, feature_drv2, feature_sns1, feature_sns2, feature5, feature6); XsnsDriverState(); ResponseAppend_P(PSTR(",\"Sensors\":")); @@ -1946,3 +1954,41 @@ void CmndDriver(void) { XdrvCall(FUNC_COMMAND_DRIVER); } + +#ifdef ESP32 +void CmndTouchCal(void) +{ + if (XdrvMailbox.payload >= 0) { + if (XdrvMailbox.payload < MAX_KEYS + 1) TOUCH_BUTTON.calibration = bitSet(TOUCH_BUTTON.calibration, XdrvMailbox.payload); + if (XdrvMailbox.payload == 0) TOUCH_BUTTON.calibration = 0; + if (XdrvMailbox.payload == 255) TOUCH_BUTTON.calibration = 255; // all pinss + } + Response_P(PSTR("{\"" D_CMND_TOUCH_CAL "\": %u"), TOUCH_BUTTON.calibration); + ResponseJsonEnd(); + AddLog_P2(LOG_LEVEL_INFO, PSTR("Button Touchvalue Hits,")); +} + +void CmndTouchThres(void) +{ + if (XdrvMailbox.payload >= 0) { + if (XdrvMailbox.payload<256){ + TOUCH_BUTTON.pin_threshold = XdrvMailbox.payload; + } + } + Response_P(PSTR("{\"" D_CMND_TOUCH_THRES "\": %u"), TOUCH_BUTTON.pin_threshold); + ResponseJsonEnd(); +} + +void CmndTouchNum(void) +{ + if (XdrvMailbox.payload >= 0) { + if (XdrvMailbox.payload<32){ + TOUCH_BUTTON.hit_threshold = XdrvMailbox.payload; + } + } + Response_P(PSTR("{\"" D_CMND_TOUCH_NUM "\": %u"), TOUCH_BUTTON.hit_threshold); + ResponseJsonEnd(); + +} + +#endif //ESP32 \ No newline at end of file diff --git a/tasmota/support_device_groups.ino b/tasmota/support_device_groups.ino index 9572c9a6a..3e40f189f 100644 --- a/tasmota/support_device_groups.ino +++ b/tasmota/support_device_groups.ino @@ -387,7 +387,7 @@ void SendReceiveDeviceGroupMessage(struct device_group * device_group, struct de case DGR_ITEM_POWER: if (Settings.flag4.remote_device_mode) { // SetOption88 - Enable relays in separate device groups bool on = (value & 1); - if (on != (power & 1)) ExecuteCommandPower(device_group_index + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE); + if (on != (power & (1 << device_group_index))) ExecuteCommandPower(device_group_index + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE); } else if (device_group->local) { uint8_t mask_devices = value >> 24; @@ -396,7 +396,6 @@ void SendReceiveDeviceGroupMessage(struct device_group * device_group, struct de uint32_t mask = 1 << i; bool on = (value & mask); if (on != (power & mask)) ExecuteCommandPower(i + 1, (on ? POWER_ON : POWER_OFF), SRC_REMOTE); - if (Settings.flag4.remote_device_mode) break; // SetOption88 - Enable relays in separate device groups } } break; diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index ef757e5b6..5622bcde9 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -569,8 +569,10 @@ void GetFeatures(void) #ifdef USE_VEML7700 feature6 |= 0x00001000; // xsns_71_veml7700.ino #endif +#ifdef USE_MCP9808 + feature6 |= 0x00002000; // xsns_72_mcp9808.ino +#endif -// feature6 |= 0x00002000; // feature6 |= 0x00004000; // feature6 |= 0x00008000; diff --git a/tasmota/support_json.ino b/tasmota/support_json.ino index 51fa0e6a3..902a6e926 100644 --- a/tasmota/support_json.ino +++ b/tasmota/support_json.ino @@ -89,7 +89,7 @@ const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle // key can be in PROGMEM // if needle == "?" then we return the first valid key bool wildcard = strcmp_P("?", needle) == 0; - if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle))) { + if ((nullptr == &json) || (nullptr == needle) || (0 == pgm_read_byte(needle)) || (!json.success())) { return *(JsonVariant*)nullptr; } @@ -104,3 +104,9 @@ const JsonVariant &GetCaseInsensitive(const JsonObject &json, const char *needle // if not found return *(JsonVariant*)nullptr; } + +// This function returns true if the JsonObject contains the specified key +// It's just a wrapper to the previous function but it can be tricky to test nullptr on an object ref +bool HasKeyCaseInsensitive(const JsonObject &json, const char *needle) { + return &GetCaseInsensitive(json, needle) != nullptr; +} diff --git a/tasmota/support_rotary.ino b/tasmota/support_rotary.ino index 25c064a18..6d79744f4 100644 --- a/tasmota/support_rotary.ino +++ b/tasmota/support_rotary.ino @@ -117,7 +117,7 @@ void RotaryHandler(void) Rotary.changed = 1; // button1 is pressed: set color temperature int16_t t = LightGetColorTemp(); - t = t + (Rotary.position - Rotary.last_position); + t = t + ((Rotary.position - Rotary.last_position) * 4); if (t < 153) { t = 153; } diff --git a/tasmota/support_switch.ino b/tasmota/support_switch.ino index 46b642e5b..9fc3b57c3 100644 --- a/tasmota/support_switch.ino +++ b/tasmota/support_switch.ino @@ -26,6 +26,8 @@ \*********************************************************************************************/ const uint8_t SWITCH_PROBE_INTERVAL = 10; // Time in milliseconds between switch input probe +const uint8_t SWITCH_FAST_PROBE_INTERVAL =2;// Time in milliseconds between switch input probe for AC detection +const uint8_t AC_PERIOD = (20 + SWITCH_FAST_PROBE_INTERVAL - 1) / SWITCH_FAST_PROBE_INTERVAL; // Duration of an AC wave in probe intervals #include @@ -38,6 +40,7 @@ struct SWITCH { uint8_t last_state[MAX_SWITCHES]; // Last wall switch states uint8_t hold_timer[MAX_SWITCHES] = { 0 }; // Timer for wallswitch push button hold uint8_t virtual_state[MAX_SWITCHES]; // Virtual switch states + uint8_t first_change = 0; uint8_t present = 0; } Switch; @@ -81,60 +84,136 @@ void SwitchProbe(void) { if (uptime < 4) { return; } // Block GPIO for 4 seconds after poweron to workaround Wemos D1 / Obi RTS circuit - uint8_t state_filter = Settings.switch_debounce / SWITCH_PROBE_INTERVAL; // 5, 10, 15 - uint8_t force_high = (Settings.switch_debounce % 50) &1; // 51, 101, 151 etc - uint8_t force_low = (Settings.switch_debounce % 50) &2; // 52, 102, 152 etc + uint8_t state_filter; + uint8_t debounce_flags = Settings.switch_debounce % 10; + uint8_t force_high = debounce_flags &1; // 51, 101, 151 etc + uint8_t force_low = debounce_flags &2; // 52, 102, 152 etc + uint8_t ac_detect = debounce_flags == 9; + uint8_t switch_probe_interval; + uint8_t first_change = Switch.first_change; + + if (ac_detect) { + switch_probe_interval = SWITCH_FAST_PROBE_INTERVAL; + if (Settings.switch_debounce < 2 * AC_PERIOD * SWITCH_FAST_PROBE_INTERVAL + 9) { + state_filter = 2 * AC_PERIOD; + } else if (Settings.switch_debounce > (0x7f - 2 * AC_PERIOD) * SWITCH_FAST_PROBE_INTERVAL) { + state_filter = 0x7f; + } else { + state_filter = (Settings.switch_debounce - 9) / SWITCH_FAST_PROBE_INTERVAL; + } + } else { + switch_probe_interval = SWITCH_PROBE_INTERVAL; + state_filter = Settings.switch_debounce / SWITCH_PROBE_INTERVAL; // 5, 10, 15 + } for (uint32_t i = 0; i < MAX_SWITCHES; i++) { if (PinUsed(GPIO_SWT1, i)) { // Olimex user_switch2.c code to fix 50Hz induced pulses if (1 == digitalRead(Pin(GPIO_SWT1, i))) { - if (force_high) { // Enabled with SwitchDebounce x1 - if (1 == Switch.virtual_state[i]) { - Switch.state[i] = state_filter; // With noisy input keep current state 1 unless constant 0 + if (ac_detect) { // Enabled with SwitchDebounce x9 + Switch.state[i] |= 0x80; + if (Switch.state[i] > 0x80) { + Switch.state[i]--; + if (0x80 == Switch.state[i]) { + Switch.virtual_state[i] = 0; + Switch.first_change = false; + } } - } + } else { - if (Switch.state[i] < state_filter) { - Switch.state[i]++; - if (state_filter == Switch.state[i]) { - Switch.virtual_state[i] = 1; + if (force_high) { // Enabled with SwitchDebounce x1 + if (1 == Switch.virtual_state[i]) { + Switch.state[i] = state_filter; // With noisy input keep current state 1 unless constant 0 + } + } + + if (Switch.state[i] < state_filter) { + Switch.state[i]++; + if (state_filter == Switch.state[i]) { + Switch.virtual_state[i] = 1; + } } } } else { - if (force_low) { // Enabled with SwitchDebounce x2 - if (0 == Switch.virtual_state[i]) { - Switch.state[i] = 0; // With noisy input keep current state 0 unless constant 1 + if (ac_detect) { // Enabled with SwitchDebounce x9 + /* + * Moes MS-104B and similar devices using an AC detection circuitry + * on their switch inputs generating an ~4 ms long low pulse every + * AC wave. We start the time measurement on the falling edge. + * + * state: bit7: previous state, bit6..0: counter + */ + if (Switch.state[i] & 0x80) { + Switch.state[i] &= 0x7f; + if (Switch.state[i] < state_filter - 2 * AC_PERIOD) { + Switch.state[i] += 2 * AC_PERIOD; + } else { + Switch.state[i] = state_filter; + Switch.virtual_state[i] = 1; + if (first_change) { + Switch.last_state[i] = 1; + Switch.first_change = false; + } + } + } else { + if (Switch.state[i] > 0x00) { + Switch.state[i]--; + if (0x00 == Switch.state[i]) { + Switch.virtual_state[i] = 0; + Switch.first_change = false; + } + } } - } + } else { - if (Switch.state[i] > 0) { - Switch.state[i]--; - if (0 == Switch.state[i]) { - Switch.virtual_state[i] = 0; + if (force_low) { // Enabled with SwitchDebounce x2 + if (0 == Switch.virtual_state[i]) { + Switch.state[i] = 0; // With noisy input keep current state 0 unless constant 1 + } + } + + if (Switch.state[i] > 0) { + Switch.state[i]--; + if (0 == Switch.state[i]) { + Switch.virtual_state[i] = 0; + } } } } } } - TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); // Re-arm as core 2.3.0 does only support ONCE mode + TickerSwitch.attach_ms(switch_probe_interval, SwitchProbe); // Re-arm as core 2.3.0 does only support ONCE mode } void SwitchInit(void) { + uint8_t ac_detect = Settings.switch_debounce % 10 == 9; + Switch.present = 0; for (uint32_t i = 0; i < MAX_SWITCHES; i++) { Switch.last_state[i] = 1; // Init global to virtual switch state; if (PinUsed(GPIO_SWT1, i)) { Switch.present++; pinMode(Pin(GPIO_SWT1, i), bitRead(Switch.no_pullup_mask, i) ? INPUT : ((16 == Pin(GPIO_SWT1, i)) ? INPUT_PULLDOWN_16 : INPUT_PULLUP)); - Switch.last_state[i] = digitalRead(Pin(GPIO_SWT1, i)); // Set global now so doesn't change the saved power state on first switch check + if (ac_detect) { + Switch.state[i] = 0x80 + 2 * AC_PERIOD; + Switch.last_state[i] = 0; // Will set later in the debouncing code + } else { + Switch.last_state[i] = digitalRead(Pin(GPIO_SWT1, i)); // Set global now so doesn't change the saved power state on first switch check + } } Switch.virtual_state[i] = Switch.last_state[i]; } - if (Switch.present) { TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); } + if (Switch.present) { + if (ac_detect) { + TickerSwitch.attach_ms(SWITCH_FAST_PROBE_INTERVAL, SwitchProbe); + Switch.first_change = true; + } else { + TickerSwitch.attach_ms(SWITCH_PROBE_INTERVAL, SwitchProbe); + } + } } /*********************************************************************************************\ diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 26341b416..3cbfb497c 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -573,7 +573,7 @@ void ExecuteCommandPower(uint32_t device, uint32_t state, uint32_t source) #ifdef USE_DEVICE_GROUPS if (SRC_REMOTE != source && SRC_RETRY != source) { if (Settings.flag4.remote_device_mode) // SetOption88 - Enable relays in separate device groups - SendDeviceGroupMessage(device - 1, DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, (power >> device - 1) & 1 | 0x01000000); // Explicitly set number of relays to one + SendDeviceGroupMessage(device - 1, DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, (power >> (device - 1)) & 1 | 0x01000000); // Explicitly set number of relays to one else SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_POWER, power); } diff --git a/tasmota/support_udp.ino b/tasmota/support_udp.ino index 451135e6a..cccb64b4e 100644 --- a/tasmota/support_udp.ino +++ b/tasmota/support_udp.ino @@ -19,7 +19,9 @@ #ifdef USE_EMULATION -#define UDP_BUFFER_SIZE 200 // Max UDP buffer size needed for M-SEARCH message +#ifndef UDP_BUFFER_SIZE +#define UDP_BUFFER_SIZE 120 // Max UDP buffer size needed for M-SEARCH message +#endif #define UDP_MSEARCH_SEND_DELAY 1500 // Delay in ms before M-Search response is send #include @@ -31,6 +33,15 @@ uint16_t udp_remote_port; // M-Search remote port bool udp_connected = false; bool udp_response_mutex = false; // M-Search response mutex to control re-entry +#ifdef ESP8266 +#ifndef UDP_MAX_PACKETS +#define UDP_MAX_PACKETS 3 // we support x more packets than the current one +#endif + +#include "UdpListener.h" +UdpListener UdpCtx(UDP_MAX_PACKETS); +#endif + /*********************************************************************************************\ * UPNP/SSDP search targets \*********************************************************************************************/ @@ -48,10 +59,16 @@ const char SSDP_ALL[] PROGMEM = "ssdp:all"; bool UdpDisconnect(void) { if (udp_connected) { + // flush any outgoing packet PortUdp.flush(); +#ifdef ESP8266 + UdpCtx.disconnect(); +#endif #ifdef USE_DEVICE_GROUPS + // stop PortUdp.stop(); #else // USE_DEVICE_GROUPS + // stop all WiFiUDP::stopAll(); #endif // !USE_DEVICE_GROUPS AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_MULTICAST_DISABLED)); @@ -64,13 +81,25 @@ bool UdpConnect(void) { if (!udp_connected && !restart_flag) { // Simple Service Discovery Protocol (SSDP) +#ifdef ESP8266 + UdpCtx.reset(); + if (igmp_joingroup(WiFi.localIP(), IPAddress(239,255,255,250)) == ERR_OK) { // addr 239.255.255.250 + ip_addr_t addr = IPADDR4_INIT(INADDR_ANY); + if (UdpCtx.listen(&addr, 1900)) { // port 1900 + // OK + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); + udp_response_mutex = false; + udp_connected = true; + } +#else // ESP32 if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); udp_response_mutex = false; udp_connected = true; - } else { +#endif + } + if (!udp_connected) { // if connection failed AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_JOIN_FAILED)); - udp_connected = false; } } return udp_connected; @@ -79,14 +108,25 @@ bool UdpConnect(void) void PollUdp(void) { if (udp_connected) { +#ifdef ESP8266 + while (UdpCtx.next()) { + UdpPacket *packet; + packet = UdpCtx.read(); + if (packet->len >= UDP_BUFFER_SIZE) { + packet->len--; // leave space for NULL terminator + } + packet->buf[packet->len] = 0; // add NULL at the end of the packer + char * packet_buffer = (char*) &packet->buf; + int32_t len = packet->len; +#else // ESP32 while (PortUdp.parsePacket()) { char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet - int len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); + int32_t len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); packet_buffer[len] = 0; - +#endif AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); + // AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); // Simple Service Discovery Protocol (SSDP) if (Settings.flag2.emulation) { @@ -97,11 +137,16 @@ void PollUdp(void) #endif udp_response_mutex = true; +#ifdef ESP8266 + udp_remote_ip = packet->srcaddr; + udp_remote_port = packet->srcport; +#else udp_remote_ip = PortUdp.remoteIP(); udp_remote_port = PortUdp.remotePort(); +#endif -// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"), -// udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer); + // AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: M-SEARCH Packet from %s:%d\n%s"), + // udp_remote_ip.toString().c_str(), udp_remote_port, packet_buffer); uint32_t response_delay = UDP_MSEARCH_SEND_DELAY + ((millis() &0x7) * 100); // 1500 - 2200 msec diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index 8ba54e60b..811076d8f 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -154,6 +154,7 @@ #define USE_HRXL // Add support for MaxBotix HRXL-MaxSonar ultrasonic range finders (+0k7) //#define USE_TASMOTA_SLAVE // Add support for Arduino Uno/Pro Mini via serial interface including flashing (+2k3 code, 44 mem) //#define USE_OPENTHERM // Add support for OpenTherm (+15k code) +//#define USE_MCP9808 // Add support for MCP9808 temperature sensor (+0k9 code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) diff --git a/tasmota/tasmota_globals.h b/tasmota/tasmota_globals.h index 37e39e451..44826fa64 100644 --- a/tasmota/tasmota_globals.h +++ b/tasmota/tasmota_globals.h @@ -300,7 +300,7 @@ const char kWebColors[] PROGMEM = #undef USE_HM10 // Disable support for HM-10 as a BLE-bridge as an alternative is using the internal ESP32 BLE #undef USE_KEELOQ // Disable support for Jarolift rollers by Keeloq algorithm as it's library cc1101 is not compatible with ESP32 -#undef USE_DISPLAY_ILI9488 // Disable as it's library JaretBurkett_ILI9488-gemu-1.0 is not compatible with ESP32 +//#undef USE_DISPLAY_ILI9488 // Disable as it's library JaretBurkett_ILI9488-gemu-1.0 is not compatible with ESP32 //#undef USE_DISPLAY_SSD1351 // Disable as it's library Adafruit_SSD1351_gemu-1.0 is not compatible with ESP32 #endif // ESP32 @@ -335,7 +335,7 @@ const char kWebColors[] PROGMEM = #ifdef USE_DEVICE_GROUPS #define SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, ...) _SendDeviceGroupMessage(DEVICE_INDEX, REQUEST_TYPE, __VA_ARGS__, 0) #define SendLocalDeviceGroupMessage(REQUEST_TYPE, ...) _SendDeviceGroupMessage(0, REQUEST_TYPE, __VA_ARGS__, 0) -uint8_t device_group_count = 0; +uint8_t device_group_count = 1; #endif // USE_DEVICE_GROUPS #ifdef DEBUG_TASMOTA_CORE diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index e4f2bd4a4..6bf15230e 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -34,8 +34,8 @@ const uint16_t CHUNKED_BUFFER_SIZE = (MESSZ / 2) - 100; // Chunk buffer size (should be smaller than half mqtt_data size = MESSZ) const uint16_t HTTP_REFRESH_TIME = 2345; // milliseconds -#define HTTP_RESTART_RECONNECT_TIME 9000 // milliseconds -#define HTTP_OTA_RESTART_RECONNECT_TIME 20000 // milliseconds +const uint16_t HTTP_RESTART_RECONNECT_TIME = 9000; // milliseconds - Allow time for restart and wifi reconnect +const uint16_t HTTP_OTA_RESTART_RECONNECT_TIME = 28000; // milliseconds - Allow time for uploading binary, unzip/write to final destination and wifi reconnect #include #include @@ -170,12 +170,8 @@ const char HTTP_SCRIPT_WIFI[] PROGMEM = "eb('p1').focus();" "}"; -const char HTTP_SCRIPT_RELOAD[] PROGMEM = - "setTimeout(function(){location.href='.';}," STR(HTTP_RESTART_RECONNECT_TIME) ");"; - -// Local OTA upgrade requires more time to complete cp: before web ui should be reloaded -const char HTTP_SCRIPT_RELOAD_OTA[] PROGMEM = - "setTimeout(function(){location.href='.';}," STR(HTTP_OTA_RESTART_RECONNECT_TIME) ");"; +const char HTTP_SCRIPT_RELOAD_TIME[] PROGMEM = + "setTimeout(function(){location.href='.';},%d);"; const char HTTP_SCRIPT_CONSOL[] PROGMEM = "var sn=0,id=0;" // Scroll position, Get most of weblog initially @@ -980,7 +976,7 @@ void WebRestart(uint32_t type) bool reset_only = (HTTP_MANAGER_RESET_ONLY == Web.state); WSContentStart_P((type) ? S_SAVE_CONFIGURATION : S_RESTART, !reset_only); - WSContentSend_P(HTTP_SCRIPT_RELOAD); + WSContentSend_P(HTTP_SCRIPT_RELOAD_TIME, HTTP_RESTART_RECONNECT_TIME); WSContentSendStyle(); if (type) { WSContentSend_P(PSTR("
" D_CONFIGURATION_SAVED "
")); @@ -2346,7 +2342,7 @@ void HandleUpgradeFirmwareStart(void) } WSContentStart_P(S_INFORMATION); - WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA); + WSContentSend_P(HTTP_SCRIPT_RELOAD_TIME, HTTP_OTA_RESTART_RECONNECT_TIME); WSContentSendStyle(); WSContentSend_P(PSTR("
" D_UPGRADE_STARTED " ...
")); WSContentSend_P(HTTP_MSG_RSTRT); @@ -2371,7 +2367,7 @@ void HandleUploadDone(void) WSContentStart_P(S_INFORMATION); if (!Web.upload_error) { - WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA); // Refesh main web ui after OTA upgrade + WSContentSend_P(HTTP_SCRIPT_RELOAD_TIME, HTTP_OTA_RESTART_RECONNECT_TIME); // Refesh main web ui after OTA upgrade } WSContentSendStyle(); WSContentSend_P(PSTR("
" D_UPLOAD " open(str,FILE_READ); if (glob_script_mem.files[cnt].isDirectory()) { glob_script_mem.files[cnt].rewindDirectory(); glob_script_mem.file_flags[cnt].is_dir=1; @@ -1314,9 +1350,15 @@ chknext: } else { if (mode==1) { - glob_script_mem.files[cnt]=FS_USED.open(str,FILE_WRITE); + glob_script_mem.files[cnt]=fsp->open(str,FILE_WRITE); +#ifdef DEBUG_FS + AddLog_P2(LOG_LEVEL_INFO,PSTR("open file for write %d"),cnt); +#endif } else { - glob_script_mem.files[cnt]=FS_USED.open(str,FILE_APPEND); + glob_script_mem.files[cnt]=fsp->open(str,FILE_APPEND); +#ifdef DEBUG_FS + AddLog_P2(LOG_LEVEL_INFO,PSTR("open file for append %d"),cnt); +#endif } } if (glob_script_mem.files[cnt]) { @@ -1335,10 +1377,15 @@ chknext: if (!strncmp(vname,"fc(",3)) { lp+=3; lp=GetNumericResult(lp,OPER_EQU,&fvar,0); - uint8_t ind=fvar; - if (ind>=SFS_MAX) ind=SFS_MAX-1; - glob_script_mem.files[ind].close(); - glob_script_mem.file_flags[ind].is_open=0; + if (fvar>=0) { + uint8_t ind=fvar; + if (ind>=SFS_MAX) ind=SFS_MAX-1; +#ifdef DEBUG_FS + AddLog_P2(LOG_LEVEL_INFO,PSTR("closing file %d"),ind); +#endif + glob_script_mem.files[ind].close(); + glob_script_mem.file_flags[ind].is_open=0; + } fvar=0; lp++; len=0; @@ -1450,7 +1497,7 @@ chknext: lp+=3; char str[glob_script_mem.max_ssize+1]; lp=GetStringResult(lp,OPER_EQU,str,0); - FS_USED.remove(str); + fsp->remove(str); lp++; len=0; goto exit; @@ -1490,7 +1537,7 @@ chknext: char str[glob_script_mem.max_ssize+1]; lp=GetStringResult(lp,OPER_EQU,str,0); // execute script - File ef=FS_USED.open(str); + File ef=fsp->open(str,FILE_READ); if (ef) { uint16_t fsiz=ef.size(); if (fsiz<2048) { @@ -1512,7 +1559,7 @@ chknext: lp+=4; char str[glob_script_mem.max_ssize+1]; lp=GetStringResult(lp,OPER_EQU,str,0); - fvar=FS_USED.mkdir(str); + fvar=fsp->mkdir(str); lp++; len=0; goto exit; @@ -1521,7 +1568,7 @@ chknext: lp+=4; char str[glob_script_mem.max_ssize+1]; lp=GetStringResult(lp,OPER_EQU,str,0); - fvar=FS_USED.rmdir(str); + fvar=fsp->rmdir(str); lp++; len=0; goto exit; @@ -1530,7 +1577,7 @@ chknext: lp+=3; char str[glob_script_mem.max_ssize+1]; lp=GetStringResult(lp,OPER_EQU,str,0); - if (FS_USED.exists(str)) fvar=1; + if (fsp->exists(str)) fvar=1; else fvar=0; lp++; len=0; @@ -2951,7 +2998,6 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { toLogEOL("for error",lp); } } else if (!strncmp(lp,"next",4)) { - lp+=4; lp_next=lp; if (floop>0) { // for next loop @@ -3700,7 +3746,7 @@ void ListDir(char *path, uint8_t depth) { char format[12]; sprintf(format,"%%-%ds",24-depth); - File dir=FS_USED.open(path); + File dir=fsp->open(path, FILE_READ); if (dir) { dir.rewindDirectory(); if (strlen(path)>1) { @@ -3822,8 +3868,8 @@ void script_upload(void) { if (upload.status == UPLOAD_FILE_START) { char npath[48]; sprintf(npath,"%s/%s",path,upload.filename.c_str()); - FS_USED.remove(npath); - upload_file=FS_USED.open(npath,FILE_WRITE); + fsp->remove(npath); + upload_file=fsp->open(npath,FILE_WRITE); if (!upload_file) Web.upload_error=1; } else if(upload.status == UPLOAD_FILE_WRITE) { if (upload_file) upload_file.write(upload.buf,upload.currentSize); @@ -3842,12 +3888,12 @@ uint8_t DownloadFile(char *file) { File download_file; WiFiClient download_Client; - if (!FS_USED.exists(file)) { + if (!fsp->exists(file)) { AddLog_P(LOG_LEVEL_INFO,PSTR("file not found")); return 0; } - download_file=FS_USED.open(file,FILE_READ); + download_file=fsp->open(file,FILE_READ); if (!download_file) { AddLog_P(LOG_LEVEL_INFO,PSTR("could not open file")); return 0; @@ -4027,16 +4073,16 @@ void ScriptSaveSettings(void) { #if !defined(USE_24C256) && defined(USE_SCRIPT_FATFS) if (glob_script_mem.flags&1) { - FS_USED.remove(FAT_SCRIPT_NAME); - File file=FS_USED.open(FAT_SCRIPT_NAME,FILE_WRITE); + fsp->remove(FAT_SCRIPT_NAME); + File file=fsp->open(FAT_SCRIPT_NAME,FILE_WRITE); file.write((const uint8_t*)glob_script_mem.script_ram,FAT_SCRIPT_SIZE); file.close(); } #endif -#if defined(ESP32) && defined(ESP32_SCRIPT_SIZE) && !defined(USE_24C256) && !defined(USE_SCRIPT_FATFS) +#if defined(LITTLEFS_SCRIPT_SIZE) && !defined(USE_24C256) && !defined(USE_SCRIPT_FATFS) if (glob_script_mem.flags&1) { - SaveFile("/script.txt",(uint8_t*)glob_script_mem.script_ram,ESP32_SCRIPT_SIZE); + SaveFile("/script.txt",(uint8_t*)glob_script_mem.script_ram,LITTLEFS_SCRIPT_SIZE); } #endif } @@ -4051,7 +4097,7 @@ void ScriptSaveSettings(void) { #ifdef USE_SCRIPT_COMPRESSION #ifndef USE_24C256 #ifndef USE_SCRIPT_FATFS -#ifndef ESP32_SCRIPT_SIZE +#ifndef LITTLEFS_SCRIPT_SIZE //AddLog_P2(LOG_LEVEL_INFO,PSTR("in string: %s len = %d"),glob_script_mem.script_ram,strlen(glob_script_mem.script_ram)); uint32_t len_compressed = SCRIPT_COMPRESS(glob_script_mem.script_ram, strlen(glob_script_mem.script_ram), Settings.rules[0], MAX_SCRIPT_SIZE-1); @@ -4300,8 +4346,7 @@ void Script_Check_Hue(String *response) { uint8_t hue_script_found=Run_Scripter(">H",-2,0); if (hue_script_found!=99) return; - char line[128]; - char tmp[128]; + char tmp[256]; uint8_t hue_devs=0; uint8_t vindex=0; char *cp; @@ -4316,17 +4361,7 @@ void Script_Check_Hue(String *response) { } if (*lp!=';') { // check this line - memcpy(line,lp,sizeof(line)); - line[sizeof(line)-1]=0; - cp=line; - for (uint32_t i=0; im",-2,0); if (msect==99) { - char line[128]; - char tmp[128]; + char tmp[256]; char *lp=glob_script_mem.section_ptr+2; while (lp) { while (*lp==SCRIPT_EOL) { @@ -5530,17 +5564,7 @@ uint8_t msect=Run_Scripter(">m",-2,0); } if (*lp!=';') { // send this line to smtp - memcpy(line,lp,sizeof(line)); - line[sizeof(line)-1]=0; - char *cp=line; - for (uint32_t i=0; iprintln(tmp); func(tmp); } @@ -5563,8 +5587,7 @@ uint8_t msect=Run_Scripter(">m",-2,0); void ScriptJsonAppend(void) { uint8_t web_script=Run_Scripter(">J",-2,0); if (web_script==99) { - char line[128]; - char tmp[128]; + char tmp[256]; char *lp=glob_script_mem.section_ptr+2; while (lp) { while (*lp==SCRIPT_EOL) { @@ -5575,17 +5598,7 @@ void ScriptJsonAppend(void) { } if (*lp!=';') { // send this line to mqtt - memcpy(line,lp,sizeof(line)); - line[sizeof(line)-1]=0; - char *cp=line; - for (uint32_t i=0; it1",3,0); + } +} + +void script_task2(void *arg) { + //uint32_t lastms=millis(); + //uint32_t time; + while (1) { + //time=millis()-lastms; + //lastms=millis(); + //time=esp32_tasks[1].task_timer-time; + //if (timet2",3,0); + } +} +uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core) { + //return 0; + BaseType_t res = 0; + if (core > 1) { core = 1; } + if (num == 1) { + if (esp32_tasks[0].task_t) { vTaskDelete(esp32_tasks[0].task_t); } + res = xTaskCreatePinnedToCore(script_task1, "T1", STASK_STACK, NULL, STASK_PRIO, &esp32_tasks[0].task_t, core); + esp32_tasks[0].task_timer = time; + } else { + if (esp32_tasks[1].task_t) { vTaskDelete(esp32_tasks[1].task_t); } + res = xTaskCreatePinnedToCore(script_task2, "T2", STASK_STACK, NULL, STASK_PRIO, &esp32_tasks[1].task_t, core); + esp32_tasks[1].task_timer = time; + } + return res; +} +#else + uint16_t task_timer1; uint16_t task_timer2; TaskHandle_t task_t1; @@ -5625,13 +5699,6 @@ void script_task2(void *arg) { Run_Scripter(">t2",3,0); } } -#ifndef STASK_STACK -#define STASK_STACK 4096 -#endif - -#ifndef STASK_PRIO -#define STASK_PRIO 5 -#endif uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core) { //return 0; @@ -5648,6 +5715,8 @@ uint32_t scripter_create_task(uint32_t num, uint32_t time, uint32_t core) { } return res; } +#endif + #endif // USE_SCRIPT_TASK #endif // ESP32 /*********************************************************************************************\ @@ -5673,7 +5742,7 @@ bool Xdrv10(uint8_t function) #ifdef USE_SCRIPT_COMPRESSION #ifndef USE_24C256 #ifndef USE_SCRIPT_FATFS -#ifndef ESP32_SCRIPT_SIZE +#ifndef LITTLEFS_SCRIPT_SIZE int32_t len_decompressed; sprt=(char*)calloc(UNISHOXRSIZE+8,1); if (!sprt) { break; } @@ -5719,10 +5788,14 @@ bool Xdrv10(uint8_t function) #endif #endif + #ifdef USE_SCRIPT_FATFS +#if USE_SCRIPT_FATFS>=0 + fsp = &SD; + #ifdef USE_MMC - if (FS_USED.begin()) { + if (fsp->begin()) { #else #ifdef ESP32 @@ -5730,10 +5803,15 @@ bool Xdrv10(uint8_t function) SPI.begin(Pin(GPIO_SPI_CLK),Pin(GPIO_SPI_MISO),Pin(GPIO_SPI_MOSI), -1); } #endif - if (FS_USED.begin(USE_SCRIPT_FATFS)) { + if (SD.begin(USE_SCRIPT_FATFS)) { #endif - //FS_USED.dateTimeCallback(dateTime); +#else + fsp = &LittleFS; + if (fsp->begin()) { +#endif + + //fsp->dateTimeCallback(dateTime); glob_script_mem.script_sd_found=1; char *script; @@ -5741,8 +5819,8 @@ bool Xdrv10(uint8_t function) if (!script) break; glob_script_mem.script_ram=script; glob_script_mem.script_size=FAT_SCRIPT_SIZE; - if (FS_USED.exists(FAT_SCRIPT_NAME)) { - File file=FS_USED.open(FAT_SCRIPT_NAME,FILE_READ); + if (fsp->exists(FAT_SCRIPT_NAME)) { + File file=fsp->open(FAT_SCRIPT_NAME,FILE_READ); file.read((uint8_t*)script,FAT_SCRIPT_SIZE); file.close(); } @@ -5759,15 +5837,21 @@ bool Xdrv10(uint8_t function) #endif -#if defined(ESP32) && defined(ESP32_SCRIPT_SIZE) && !defined(USE_24C256) && !defined(USE_SCRIPT_FATFS) +#if defined(LITTLEFS_SCRIPT_SIZE) && !defined(USE_24C256) && !defined(USE_SCRIPT_FATFS) + +#ifdef ESP32 + fsp = &SPIFFS; +#else + fsp = &LittleFS; +#endif char *script; - script=(char*)calloc(ESP32_SCRIPT_SIZE+4,1); + script=(char*)calloc(LITTLEFS_SCRIPT_SIZE+4,1); if (!script) break; - LoadFile("/script.txt",(uint8_t*)script,ESP32_SCRIPT_SIZE); + LoadFile("/script.txt",(uint8_t*)script,LITTLEFS_SCRIPT_SIZE); glob_script_mem.script_ram=script; - glob_script_mem.script_size=ESP32_SCRIPT_SIZE; - script[ESP32_SCRIPT_SIZE-1]=0; + glob_script_mem.script_size=LITTLEFS_SCRIPT_SIZE; + script[LITTLEFS_SCRIPT_SIZE-1]=0; // use rules storage for permanent vars glob_script_mem.script_pram=(uint8_t*)Settings.rules[0]; glob_script_mem.script_pram_size=MAX_SCRIPT_SIZE; diff --git a/tasmota/xdrv_13_display.ino b/tasmota/xdrv_13_display.ino index d2466d051..43245c794 100644 --- a/tasmota/xdrv_13_display.ino +++ b/tasmota/xdrv_13_display.ino @@ -505,7 +505,7 @@ void DisplayText(void) cp += var; linebuf[fill] = 0; break; -#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) +#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) && USE_SCRIPT_FATFS>=0 case 'P': { char *ep=strchr(cp,':'); if (ep) { @@ -1510,8 +1510,13 @@ void rgb888_to_565(uint8_t *in, uint16_t *out, uint32_t len); #endif #endif +#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) && USE_SCRIPT_FATFS>=0 -#if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) +#ifdef ESP32 +extern FS *fsp; +#else +extern SDClass *fsp; +#endif #define XBUFF_LEN 128 void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp) { if (!renderer) return; @@ -1527,7 +1532,7 @@ void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp) { if (!strcmp(estr,"rgb")) { // special rgb format - fp=SD.open(file,FILE_READ); + fp=fsp->open(file,FILE_READ); if (!fp) return; uint16_t xsize; fp.read((uint8_t*)&xsize,2); @@ -1564,7 +1569,7 @@ void Draw_RGB_Bitmap(char *file,uint16_t xp, uint16_t yp) { #ifdef ESP32 #ifdef JPEG_PICTS if (psramFound()) { - fp=SD.open(file,FILE_READ); + fp=fsp->open(file,FILE_READ); if (!fp) return; uint32_t size = fp.size(); uint8_t *mem = (uint8_t *)heap_caps_malloc(size+4, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT); @@ -1850,7 +1855,9 @@ void DisplayCheckGraph() { #if defined(USE_SCRIPT_FATFS) && defined(USE_SCRIPT) +#ifdef ESP32 #include +#endif void Save_graph(uint8_t num, char *path) { if (!renderer) return; @@ -1858,8 +1865,8 @@ void Save_graph(uint8_t num, char *path) { struct GRAPH *gp=graph[index]; if (!gp) return; File fp; - SD.remove(path); - fp=SD.open(path,FILE_WRITE); + fsp->remove(path); + fp=fsp->open(path,FILE_WRITE); if (!fp) return; char str[32]; sprintf_P(str,PSTR("%d\t%d\t%d\t"),gp->xcnt,gp->xs,gp->ys); @@ -1884,7 +1891,7 @@ void Restore_graph(uint8_t num, char *path) { struct GRAPH *gp=graph[index]; if (!gp) return; File fp; - fp=SD.open(path,FILE_READ); + fp=fsp->open(path,FILE_READ); if (!fp) return; char vbuff[32]; char *cp=vbuff; diff --git a/tasmota/xdrv_23_zigbee_0_constants.ino b/tasmota/xdrv_23_zigbee_0_constants.ino index 28628f93c..57bf22499 100644 --- a/tasmota/xdrv_23_zigbee_0_constants.ino +++ b/tasmota/xdrv_23_zigbee_0_constants.ino @@ -384,7 +384,6 @@ enum ZCL_Global_Commands { ZCL_DEFAULT_RESPONSE = 0x0b, ZCL_DISCOVER_ATTRIBUTES = 0x0c, ZCL_DISCOVER_ATTRIBUTES_RESPONSE = 0x0d - }; #define ZF(s) static const char ZS_ ## s[] PROGMEM = #s; diff --git a/tasmota/xdrv_23_zigbee_2_devices.ino b/tasmota/xdrv_23_zigbee_2_devices.ino index c115052f7..23a9343d4 100644 --- a/tasmota/xdrv_23_zigbee_2_devices.ino +++ b/tasmota/xdrv_23_zigbee_2_devices.ino @@ -26,6 +26,24 @@ #endif const uint16_t kZigbeeSaveDelaySeconds = ZIGBEE_SAVE_DELAY_SECONDS; // wait for x seconds +/*********************************************************************************************\ + * Structures for Rules variables related to the last received message +\*********************************************************************************************/ + +typedef struct Z_LastMessageVars { + uint16_t device; // device short address + uint16_t groupaddr; // group address + uint16_t cluster; // cluster id + uint8_t endpoint; // source endpoint +} Z_LastMessageVars; + +Z_LastMessageVars gZbLastMessage; + +uint16_t Z_GetLastDevice(void) { return gZbLastMessage.device; } +uint16_t Z_GetLastGroup(void) { return gZbLastMessage.groupaddr; } +uint16_t Z_GetLastCluster(void) { return gZbLastMessage.cluster; } +uint8_t Z_GetLastEndpoint(void) { return gZbLastMessage.endpoint; } + /*********************************************************************************************\ * Structures for device configuration \*********************************************************************************************/ @@ -256,7 +274,7 @@ int32_t Z_Devices::findEndpointInVector(const std::vector & vecOfElements, u // entry with same shortaddr or longaddr exists. // Z_Device & Z_Devices::createDeviceEntry(uint16_t shortaddr, uint64_t longaddr) { - if (!shortaddr && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create an enrty with both short/long addr null + if ((BAD_SHORTADDR == shortaddr) && !longaddr) { return *(Z_Device*) nullptr; } // it is not legal to create this entry //Z_Device* device_alloc = (Z_Device*) malloc(sizeof(Z_Device)); Z_Device* device_alloc = new Z_Device{ longaddr, @@ -340,7 +358,7 @@ int32_t Z_Devices::findFriendlyName(const char * name) const { if (name_len) { for (auto &elem : _devices) { if (elem->friendlyName) { - if (strcmp(elem->friendlyName, name) == 0) { return found; } + if (strcasecmp(elem->friendlyName, name) == 0) { return found; } } found++; } @@ -508,6 +526,8 @@ void Z_Devices::addEndpoint(uint16_t shortaddr, uint8_t endpoint) { // Find the first endpoint of the device uint8_t Z_Devices::findFirstEndpoint(uint16_t shortaddr) const { + // When in router of end-device mode, the coordinator was not probed, in this case always talk to endpoint 1 + if (0x0000 == shortaddr) { return 1; } int32_t found = findShortAddr(shortaddr); if (found < 0) return 0; // avoid creating an entry if the device was never seen const Z_Device &device = devicesAt(found); @@ -860,21 +880,21 @@ const JsonObject *Z_Devices::jsonGet(uint16_t shortaddr) { void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { Z_Device & device = getShortAddr(shortaddr); if (&device == nullptr) { return; } // don't crash if not found - JsonObject * json = device.json; - if (json == nullptr) { return; } // abort if nothing in buffer + JsonObject & json = *device.json; + if (&json == nullptr) { return; } // abort if nothing in buffer const char * fname = zigbee_devices.getFriendlyName(shortaddr); bool use_fname = (Settings.flag4.zigbee_use_names) && (fname); // should we replace shortaddr with friendlyname? - // Remove redundant "Name" or "Device" - if (use_fname) { - json->remove(F(D_JSON_ZIGBEE_NAME)); - } else { - json->remove(F(D_JSON_ZIGBEE_DEVICE)); - } + // save parameters is global variables to be used by Rules + gZbLastMessage.device = shortaddr; // %zbdevice% + gZbLastMessage.groupaddr = json[F(D_CMND_ZIGBEE_GROUP)]; // %zbgroup% + gZbLastMessage.cluster = json[F(D_CMND_ZIGBEE_CLUSTER)]; // %zbcluster% + gZbLastMessage.endpoint = json[F(D_CMND_ZIGBEE_ENDPOINT)]; // %zbendpoint% + // dump json in string String msg = ""; - json->printTo(msg); + json.printTo(msg); zigbee_devices.jsonClear(shortaddr); if (use_fname) { @@ -889,7 +909,7 @@ void Z_Devices::jsonPublishFlush(uint16_t shortaddr) { } else { MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); } - XdrvRulesProcess(); + XdrvRulesProcess(); // apply rules } void Z_Devices::jsonPublishNow(uint16_t shortaddr, JsonObject & values) { @@ -923,7 +943,7 @@ uint16_t Z_Devices::parseDeviceParam(const char * param, bool short_must_be_know if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 99)) { shortaddr = zigbee_devices.isKnownIndex(XdrvMailbox.payload - 1); } - } else if ((dataBuf[0] == '0') && (dataBuf[1] == 'x')) { + } else if ((dataBuf[0] == '0') && ((dataBuf[1] == 'x') || (dataBuf[1] == 'X'))) { // starts with 0x if (strlen(dataBuf) < 18) { // expect a short address diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index dfb69d4a7..bf508d9c2 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -79,6 +79,502 @@ uint8_t Z_getDatatypeLen(uint8_t t) { } } + +// return value: +// 0 = keep initial value +// 1 = remove initial value +typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); +typedef struct Z_AttributeConverter { + uint8_t type; + uint8_t cluster_short; + uint16_t attribute; + const char * name; + int16_t multiplier; // multiplier for numerical value, (if > 0 multiply by x, if <0 device by x) + uint8_t cb; // callback func from Z_ConvOperators + // Z_AttrConverter func; +} Z_AttributeConverter; + +// Cluster numbers are store in 8 bits format to save space, +// the following tables allows the conversion from 8 bits index Cx... +// to the 16 bits actual cluster number +enum Cx_cluster_short { + Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007, + Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F, + Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0100, + Cx0101, Cx0102, Cx0300, Cx0400, Cx0401, Cx0402, Cx0403, Cx0404, + Cx0405, Cx0406, Cx0B01, Cx0B05, +}; + +const uint16_t Cx_cluster[] PROGMEM = { + 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, + 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, + 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0100, + 0x0101, 0x0102, 0x0300, 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, + 0x0405, 0x0406, 0x0B01, 0x0B05, +}; + +uint16_t CxToCluster(uint8_t cx) { + if (cx < ARRAY_SIZE(Cx_cluster)) { + return pgm_read_word(&Cx_cluster[cx]); + } + return 0xFFFF; +} + +enum Z_ConvOperators { + Z_Nop, // copy value + Z_AddPressureUnit, // add pressure unit attribute (non numerical) + Z_ManufKeep, // copy and record Manufacturer attribute + Z_ModelKeep, // copy and record ModelId attribute + Z_AqaraSensor, // decode prioprietary Aqara Sensor message + Z_AqaraVibration, // decode Aqara vibration modes + Z_AqaraCube, // decode Aqara cube +}; + +ZF(ZCLVersion) ZF(AppVersion) ZF(StackVersion) ZF(HWVersion) ZF(Manufacturer) ZF(ModelId) +ZF(DateCode) ZF(PowerSource) ZF(SWBuildID) ZF(Power) ZF(SwitchType) ZF(Dimmer) +ZF(MainsVoltage) ZF(MainsFrequency) ZF(BatteryVoltage) ZF(BatteryPercentage) +ZF(CurrentTemperature) ZF(MinTempExperienced) ZF(MaxTempExperienced) ZF(OverTempTotalDwell) +ZF(SceneCount) ZF(CurrentScene) ZF(CurrentGroup) ZF(SceneValid) +ZF(AlarmCount) ZF(Time) ZF(TimeStatus) ZF(TimeZone) ZF(DstStart) ZF(DstEnd) +ZF(DstShift) ZF(StandardTime) ZF(LocalTime) ZF(LastSetTime) ZF(ValidUntilTime) + +ZF(LocationType) ZF(LocationMethod) ZF(LocationAge) ZF(QualityMeasure) ZF(NumberOfDevices) + +ZF(AnalogInActiveText) ZF(AnalogInDescription) ZF(AnalogInInactiveText) ZF(AnalogInMaxValue) +ZF(AnalogInMinValue) ZF(AnalogInOutOfService) ZF(AqaraRotate) ZF(AnalogInPriorityArray) +ZF(AnalogInReliability) ZF(AnalogInRelinquishDefault) ZF(AnalogInResolution) ZF(AnalogInStatusFlags) +ZF(AnalogInEngineeringUnits) ZF(AnalogInApplicationType) ZF(Aqara_FF05) + +ZF(AnalogOutDescription) ZF(AnalogOutMaxValue) ZF(AnalogOutMinValue) ZF(AnalogOutOutOfService) +ZF(AnalogOutValue) ZF(AnalogOutPriorityArray) ZF(AnalogOutReliability) ZF(AnalogOutRelinquishDefault) +ZF(AnalogOutResolution) ZF(AnalogOutStatusFlags) ZF(AnalogOutEngineeringUnits) ZF(AnalogOutApplicationType) + +ZF(AnalogDescription) ZF(AnalogOutOfService) ZF(AnalogValue) ZF(AnalogPriorityArray) ZF(AnalogReliability) +ZF(AnalogRelinquishDefault) ZF(AnalogStatusFlags) ZF(AnalogEngineeringUnits) ZF(AnalogApplicationType) + +ZF(BinaryInActiveText) ZF(BinaryInDescription) ZF(BinaryInInactiveText) ZF(BinaryInOutOfService) +ZF(BinaryInPolarity) ZF(BinaryInValue) ZF(BinaryInPriorityArray) ZF(BinaryInReliability) +ZF(BinaryInStatusFlags) ZF(BinaryInApplicationType) + +ZF(BinaryOutActiveText) ZF(BinaryOutDescription) ZF(BinaryOutInactiveText) ZF(BinaryOutMinimumOffTime) +ZF(BinaryOutMinimumOnTime) ZF(BinaryOutOutOfService) ZF(BinaryOutPolarity) ZF(BinaryOutValue) +ZF(BinaryOutPriorityArray) ZF(BinaryOutReliability) ZF(BinaryOutRelinquishDefault) ZF(BinaryOutStatusFlags) +ZF(BinaryOutApplicationType) + +ZF(BinaryActiveText) ZF(BinaryDescription) ZF(BinaryInactiveText) ZF(BinaryMinimumOffTime) +ZF(BinaryMinimumOnTime) ZF(BinaryOutOfService) ZF(BinaryValue) ZF(BinaryPriorityArray) ZF(BinaryReliability) +ZF(BinaryRelinquishDefault) ZF(BinaryStatusFlags) ZF(BinaryApplicationType) + +ZF(MultiInStateText) ZF(MultiInDescription) ZF(MultiInNumberOfStates) ZF(MultiInOutOfService) +ZF(MultiInValue) ZF(MultiInReliability) ZF(MultiInStatusFlags) ZF(MultiInApplicationType) + +ZF(MultiOutStateText) ZF(MultiOutDescription) ZF(MultiOutNumberOfStates) ZF(MultiOutOutOfService) +ZF(MultiOutValue) ZF(MultiOutPriorityArray) ZF(MultiOutReliability) ZF(MultiOutRelinquishDefault) +ZF(MultiOutStatusFlags) ZF(MultiOutApplicationType) + +ZF(MultiStateText) ZF(MultiDescription) ZF(MultiNumberOfStates) ZF(MultiOutOfService) ZF(MultiValue) +ZF(MultiReliability) ZF(MultiRelinquishDefault) ZF(MultiStatusFlags) ZF(MultiApplicationType) + +ZF(TotalProfileNum) ZF(MultipleScheduling) ZF(EnergyFormatting) ZF(EnergyRemote) ZF(ScheduleMode) + +ZF(CheckinInterval) ZF(LongPollInterval) ZF(ShortPollInterval) ZF(FastPollTimeout) ZF(CheckinIntervalMin) +ZF(LongPollIntervalMin) ZF(FastPollTimeoutMax) + +ZF(PhysicalClosedLimit) ZF(MotorStepSize) ZF(Status) ZF(ClosedLimit) ZF(Mode) + +ZF(LockState) ZF(LockType) ZF(ActuatorEnabled) ZF(DoorState) ZF(DoorOpenEvents) +ZF(DoorClosedEvents) ZF(OpenPeriod) + +ZF(AqaraVibrationMode) ZF(AqaraVibrationsOrAngle) ZF(AqaraVibration505) ZF(AqaraAccelerometer) + +ZF(WindowCoveringType) ZF(PhysicalClosedLimitLift) ZF(PhysicalClosedLimitTilt) ZF(CurrentPositionLift) +ZF(CurrentPositionTilt) ZF(NumberofActuationsLift) ZF(NumberofActuationsTilt) ZF(ConfigStatus) +ZF(CurrentPositionLiftPercentage) ZF(CurrentPositionTiltPercentage) ZF(InstalledOpenLimitLift) +ZF(InstalledClosedLimitLift) ZF(InstalledOpenLimitTilt) ZF(InstalledClosedLimitTilt) ZF(VelocityLift) +ZF(AccelerationTimeLift) ZF(DecelerationTimeLift) ZF(IntermediateSetpointsLift) +ZF(IntermediateSetpointsTilt) + +ZF(Hue) ZF(Sat) ZF(RemainingTime) ZF(X) ZF(Y) ZF(DriftCompensation) ZF(CompensationText) ZF(CT) +ZF(ColorMode) ZF(NumberOfPrimaries) ZF(Primary1X) ZF(Primary1Y) ZF(Primary1Intensity) ZF(Primary2X) +ZF(Primary2Y) ZF(Primary2Intensity) ZF(Primary3X) ZF(Primary3Y) ZF(Primary3Intensity) ZF(WhitePointX) +ZF(WhitePointY) ZF(ColorPointRX) ZF(ColorPointRY) ZF(ColorPointRIntensity) ZF(ColorPointGX) ZF(ColorPointGY) +ZF(ColorPointGIntensity) ZF(ColorPointBX) ZF(ColorPointBY) ZF(ColorPointBIntensity) + +ZF(Illuminance) ZF(IlluminanceMinMeasuredValue) ZF(IlluminanceMaxMeasuredValue) ZF(IlluminanceTolerance) +ZF(IlluminanceLightSensorType) ZF(IlluminanceLevelStatus) ZF(IlluminanceTargetLevel) + +ZF(Temperature) ZF(TemperatureMinMeasuredValue) ZF(TemperatureMaxMeasuredValue) ZF(TemperatureTolerance) + +ZF(PressureUnit) ZF(Pressure) ZF(PressureMinMeasuredValue) ZF(PressureMaxMeasuredValue) ZF(PressureTolerance) +ZF(PressureScaledValue) ZF(PressureMinScaledValue) ZF(PressureMaxScaledValue) ZF(PressureScaledTolerance) +ZF(PressureScale) + +ZF(FlowRate) ZF(FlowMinMeasuredValue) ZF(FlowMaxMeasuredValue) ZF(FlowTolerance) + +ZF(Humidity) ZF(HumidityMinMeasuredValue) ZF(HumidityMaxMeasuredValue) ZF(HumidityTolerance) + +ZF(Occupancy) ZF(OccupancySensorType) + +ZF(CompanyName) ZF(MeterTypeID) ZF(DataQualityID) ZF(CustomerName) ZF(Model) ZF(PartNumber) +ZF(SoftwareRevision) ZF(POD) ZF(AvailablePower) ZF(PowerThreshold) ZF(ProductRevision) ZF(UtilityName) + +ZF(NumberOfResets) ZF(PersistentMemoryWrites) ZF(LastMessageLQI) ZF(LastMessageRSSI) +// list of post-processing directives +const Z_AttributeConverter Z_PostProcess[] PROGMEM = { + { Zuint8, Cx0000, 0x0000, Z(ZCLVersion), 1, Z_Nop }, + { Zuint8, Cx0000, 0x0001, Z(AppVersion), 1, Z_Nop }, + { Zuint8, Cx0000, 0x0002, Z(StackVersion), 1, Z_Nop }, + { Zuint8, Cx0000, 0x0003, Z(HWVersion), 1, Z_Nop }, + { Zstring, Cx0000, 0x0004, Z(Manufacturer), 1, Z_ManufKeep }, // record Manufacturer + { Zstring, Cx0000, 0x0005, Z(ModelId), 1, Z_ModelKeep }, // record Model + { Zstring, Cx0000, 0x0006, Z(DateCode), 1, Z_Nop }, + { Zenum8, Cx0000, 0x0007, Z(PowerSource), 1, Z_Nop }, + { Zstring, Cx0000, 0x4000, Z(SWBuildID), 1, Z_Nop }, + { Zunk, Cx0000, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary + { Zmap8, Cx0000, 0xFF01, nullptr, 0, Z_AqaraSensor }, // Occupancy (map8) + + // Power Configuration cluster + { Zuint16, Cx0001, 0x0000, Z(MainsVoltage), 1, Z_Nop }, + { Zuint8, Cx0001, 0x0001, Z(MainsFrequency), 1, Z_Nop }, + { Zuint8, Cx0001, 0x0020, Z(BatteryVoltage), -10,Z_Nop }, // divide by 10 + { Zuint8, Cx0001, 0x0021, Z(BatteryPercentage), -2, Z_Nop }, // divide by 2 + + // Device Temperature Configuration cluster + { Zint16, Cx0002, 0x0000, Z(CurrentTemperature), 1, Z_Nop }, + { Zint16, Cx0002, 0x0001, Z(MinTempExperienced), 1, Z_Nop }, + { Zint16, Cx0002, 0x0002, Z(MaxTempExperienced), 1, Z_Nop }, + { Zuint16, Cx0002, 0x0003, Z(OverTempTotalDwell), 1, Z_Nop }, + + // Scenes cluster + { Zuint8, Cx0005, 0x0000, Z(SceneCount), 1, Z_Nop }, + { Zuint8, Cx0005, 0x0001, Z(CurrentScene), 1, Z_Nop }, + { Zuint16, Cx0005, 0x0002, Z(CurrentGroup), 1, Z_Nop }, + { Zbool, Cx0005, 0x0003, Z(SceneValid), 1, Z_Nop }, + //{ Zmap8, Cx0005, 0x0004, Z(NameSupport), 1, Z_Nop }, + + // On/off cluster + { Zbool, Cx0006, 0x0000, Z(Power), 1, Z_Nop }, + { Zbool, Cx0006, 0x8000, Z(Power), 1, Z_Nop }, // See 7280 + + // On/Off Switch Configuration cluster + { Zenum8, Cx0007, 0x0000, Z(SwitchType), 1, Z_Nop }, + + // Level Control cluster + { Zuint8, Cx0008, 0x0000, Z(Dimmer), 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0001, Z(RemainingTime", 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0010, Z(OnOffTransitionTime", 1, Z_Nop }, + // { Zuint8, Cx0008, 0x0011, Z(OnLevel", 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0012, Z(OnTransitionTime", 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0013, Z(OffTransitionTime", 1, Z_Nop }, + // { Zuint16, Cx0008, 0x0014, Z(DefaultMoveRate", 1, Z_Nop }, + + // Alarms cluster + { Zuint16, Cx0009, 0x0000, Z(AlarmCount), 1, Z_Nop }, + + // Time cluster + { ZUTC, Cx000A, 0x0000, Z(Time), 1, Z_Nop }, + { Zmap8, Cx000A, 0x0001, Z(TimeStatus), 1, Z_Nop }, + { Zint32, Cx000A, 0x0002, Z(TimeZone), 1, Z_Nop }, + { Zuint32, Cx000A, 0x0003, Z(DstStart), 1, Z_Nop }, + { Zuint32, Cx000A, 0x0004, Z(DstEnd), 1, Z_Nop }, + { Zint32, Cx000A, 0x0005, Z(DstShift), 1, Z_Nop }, + { Zuint32, Cx000A, 0x0006, Z(StandardTime), 1, Z_Nop }, + { Zuint32, Cx000A, 0x0007, Z(LocalTime), 1, Z_Nop }, + { ZUTC, Cx000A, 0x0008, Z(LastSetTime), 1, Z_Nop }, + { ZUTC, Cx000A, 0x0009, Z(ValidUntilTime), 1, Z_Nop }, + + // RSSI Location cluster + { Zdata8, Cx000B, 0x0000, Z(LocationType), 1, Z_Nop }, + { Zenum8, Cx000B, 0x0001, Z(LocationMethod), 1, Z_Nop }, + { Zuint16, Cx000B, 0x0002, Z(LocationAge), 1, Z_Nop }, + { Zuint8, Cx000B, 0x0003, Z(QualityMeasure), 1, Z_Nop }, + { Zuint8, Cx000B, 0x0004, Z(NumberOfDevices), 1, Z_Nop }, + + // Analog Input cluster + // { 0xFF, Cx000C, 0x0004, Z(AnalogInActiveText), 1, Z_Nop }, + { Zstring, Cx000C, 0x001C, Z(AnalogInDescription), 1, Z_Nop }, + // { 0xFF, Cx000C, 0x002E, Z(AnalogInInactiveText), 1, Z_Nop }, + { Zsingle, Cx000C, 0x0041, Z(AnalogInMaxValue), 1, Z_Nop }, + { Zsingle, Cx000C, 0x0045, Z(AnalogInMinValue), 1, Z_Nop }, + { Zbool, Cx000C, 0x0051, Z(AnalogInOutOfService), 1, Z_Nop }, + { Zsingle, Cx000C, 0x0055, Z(AqaraRotate), 1, Z_Nop }, + // { 0xFF, Cx000C, 0x0057, Z(AnalogInPriorityArray),1, Z_Nop }, + { Zenum8, Cx000C, 0x0067, Z(AnalogInReliability), 1, Z_Nop }, + // { 0xFF, Cx000C, 0x0068, Z(AnalogInRelinquishDefault),1, Z_Nop }, + { Zsingle, Cx000C, 0x006A, Z(AnalogInResolution), 1, Z_Nop }, + { Zmap8, Cx000C, 0x006F, Z(AnalogInStatusFlags), 1, Z_Nop }, + { Zenum16, Cx000C, 0x0075, Z(AnalogInEngineeringUnits),1, Z_Nop }, + { Zuint32, Cx000C, 0x0100, Z(AnalogInApplicationType),1, Z_Nop }, + { Zuint16, Cx000C, 0xFF05, Z(Aqara_FF05), 1, Z_Nop }, + + // Analog Output cluster + { Zstring, Cx000D, 0x001C, Z(AnalogOutDescription), 1, Z_Nop }, + { Zsingle, Cx000D, 0x0041, Z(AnalogOutMaxValue), 1, Z_Nop }, + { Zsingle, Cx000D, 0x0045, Z(AnalogOutMinValue), 1, Z_Nop }, + { Zbool, Cx000D, 0x0051, Z(AnalogOutOutOfService),1, Z_Nop }, + { Zsingle, Cx000D, 0x0055, Z(AnalogOutValue), 1, Z_Nop }, + // { Zunk, Cx000D, 0x0057, Z(AnalogOutPriorityArray),1, Z_Nop }, + { Zenum8, Cx000D, 0x0067, Z(AnalogOutReliability), 1, Z_Nop }, + { Zsingle, Cx000D, 0x0068, Z(AnalogOutRelinquishDefault),1, Z_Nop }, + { Zsingle, Cx000D, 0x006A, Z(AnalogOutResolution), 1, Z_Nop }, + { Zmap8, Cx000D, 0x006F, Z(AnalogOutStatusFlags), 1, Z_Nop }, + { Zenum16, Cx000D, 0x0075, Z(AnalogOutEngineeringUnits),1, Z_Nop }, + { Zuint32, Cx000D, 0x0100, Z(AnalogOutApplicationType),1, Z_Nop }, + + // Analog Value cluster + { Zstring, Cx000E, 0x001C, Z(AnalogDescription), 1, Z_Nop }, + { Zbool, Cx000E, 0x0051, Z(AnalogOutOfService), 1, Z_Nop }, + { Zsingle, Cx000E, 0x0055, Z(AnalogValue), 1, Z_Nop }, + { Zunk, Cx000E, 0x0057, Z(AnalogPriorityArray), 1, Z_Nop }, + { Zenum8, Cx000E, 0x0067, Z(AnalogReliability), 1, Z_Nop }, + { Zsingle, Cx000E, 0x0068, Z(AnalogRelinquishDefault),1, Z_Nop }, + { Zmap8, Cx000E, 0x006F, Z(AnalogStatusFlags), 1, Z_Nop }, + { Zenum16, Cx000E, 0x0075, Z(AnalogEngineeringUnits),1, Z_Nop }, + { Zuint32, Cx000E, 0x0100, Z(AnalogApplicationType),1, Z_Nop }, + + // Binary Input cluster + { Zstring, Cx000F, 0x0004, Z(BinaryInActiveText), 1, Z_Nop }, + { Zstring, Cx000F, 0x001C, Z(BinaryInDescription), 1, Z_Nop }, + { Zstring, Cx000F, 0x002E, Z(BinaryInInactiveText),1, Z_Nop }, + { Zbool, Cx000F, 0x0051, Z(BinaryInOutOfService),1, Z_Nop }, + { Zenum8, Cx000F, 0x0054, Z(BinaryInPolarity), 1, Z_Nop }, + { Zstring, Cx000F, 0x0055, Z(BinaryInValue), 1, Z_Nop }, + // { 0xFF, Cx000F, 0x0057, Z(BinaryInPriorityArray),1, Z_Nop }, + { Zenum8, Cx000F, 0x0067, Z(BinaryInReliability), 1, Z_Nop }, + { Zmap8, Cx000F, 0x006F, Z(BinaryInStatusFlags), 1, Z_Nop }, + { Zuint32, Cx000F, 0x0100, Z(BinaryInApplicationType),1, Z_Nop }, + + // Binary Output cluster + { Zstring, Cx0010, 0x0004, Z(BinaryOutActiveText), 1, Z_Nop }, + { Zstring, Cx0010, 0x001C, Z(BinaryOutDescription), 1, Z_Nop }, + { Zstring, Cx0010, 0x002E, Z(BinaryOutInactiveText),1, Z_Nop }, + { Zuint32, Cx0010, 0x0042, Z(BinaryOutMinimumOffTime),1, Z_Nop }, + { Zuint32, Cx0010, 0x0043, Z(BinaryOutMinimumOnTime),1, Z_Nop }, + { Zbool, Cx0010, 0x0051, Z(BinaryOutOutOfService),1, Z_Nop }, + { Zenum8, Cx0010, 0x0054, Z(BinaryOutPolarity), 1, Z_Nop }, + { Zbool, Cx0010, 0x0055, Z(BinaryOutValue), 1, Z_Nop }, + // { Zunk, Cx0010, 0x0057, Z(BinaryOutPriorityArray),1, Z_Nop }, + { Zenum8, Cx0010, 0x0067, Z(BinaryOutReliability), 1, Z_Nop }, + { Zbool, Cx0010, 0x0068, Z(BinaryOutRelinquishDefault),1, Z_Nop }, + { Zmap8, Cx0010, 0x006F, Z(BinaryOutStatusFlags), 1, Z_Nop }, + { Zuint32, Cx0010, 0x0100, Z(BinaryOutApplicationType),1, Z_Nop }, + + // Binary Value cluster + { Zstring, Cx0011, 0x0004, Z(BinaryActiveText), 1, Z_Nop }, + { Zstring, Cx0011, 0x001C, Z(BinaryDescription), 1, Z_Nop }, + { Zstring, Cx0011, 0x002E, Z(BinaryInactiveText), 1, Z_Nop }, + { Zuint32, Cx0011, 0x0042, Z(BinaryMinimumOffTime), 1, Z_Nop }, + { Zuint32, Cx0011, 0x0043, Z(BinaryMinimumOnTime), 1, Z_Nop }, + { Zbool, Cx0011, 0x0051, Z(BinaryOutOfService), 1, Z_Nop }, + { Zbool, Cx0011, 0x0055, Z(BinaryValue), 1, Z_Nop }, + // { Zunk, Cx0011, 0x0057, Z(BinaryPriorityArray), 1, Z_Nop }, + { Zenum8, Cx0011, 0x0067, Z(BinaryReliability), 1, Z_Nop }, + { Zbool, Cx0011, 0x0068, Z(BinaryRelinquishDefault),1, Z_Nop }, + { Zmap8, Cx0011, 0x006F, Z(BinaryStatusFlags), 1, Z_Nop }, + { Zuint32, Cx0011, 0x0100, Z(BinaryApplicationType),1, Z_Nop }, + + // Multistate Input cluster + // { Zunk, Cx0012, 0x000E, Z(MultiInStateText), 1, Z_Nop }, + { Zstring, Cx0012, 0x001C, Z(MultiInDescription), 1, Z_Nop }, + { Zuint16, Cx0012, 0x004A, Z(MultiInNumberOfStates),1, Z_Nop }, + { Zbool, Cx0012, 0x0051, Z(MultiInOutOfService), 1, Z_Nop }, + { Zuint16, Cx0012, 0x0055, Z(MultiInValue), 0, Z_AqaraCube }, + { Zenum8, Cx0012, 0x0067, Z(MultiInReliability), 1, Z_Nop }, + { Zmap8, Cx0012, 0x006F, Z(MultiInStatusFlags), 1, Z_Nop }, + { Zuint32, Cx0012, 0x0100, Z(MultiInApplicationType),1, Z_Nop }, + + // Multistate output + // { Zunk, Cx0013, 0x000E, Z(MultiOutStateText), 1, Z_Nop }, + { Zstring, Cx0013, 0x001C, Z(MultiOutDescription), 1, Z_Nop }, + { Zuint16, Cx0013, 0x004A, Z(MultiOutNumberOfStates),1, Z_Nop }, + { Zbool, Cx0013, 0x0051, Z(MultiOutOutOfService), 1, Z_Nop }, + { Zuint16, Cx0013, 0x0055, Z(MultiOutValue), 1, Z_Nop }, + // { Zunk, Cx0013, 0x0057, Z(MultiOutPriorityArray),1, Z_Nop }, + { Zenum8, Cx0013, 0x0067, Z(MultiOutReliability), 1, Z_Nop }, + { Zuint16, Cx0013, 0x0068, Z(MultiOutRelinquishDefault),1, Z_Nop }, + { Zmap8, Cx0013, 0x006F, Z(MultiOutStatusFlags), 1, Z_Nop }, + { Zuint32, Cx0013, 0x0100, Z(MultiOutApplicationType),1, Z_Nop }, + + // Multistate Value cluster + // { Zunk, Cx0014, 0x000E, Z(MultiStateText), 1, Z_Nop }, + { Zstring, Cx0014, 0x001C, Z(MultiDescription), 1, Z_Nop }, + { Zuint16, Cx0014, 0x004A, Z(MultiNumberOfStates), 1, Z_Nop }, + { Zbool, Cx0014, 0x0051, Z(MultiOutOfService), 1, Z_Nop }, + { Zuint16, Cx0014, 0x0055, Z(MultiValue), 1, Z_Nop }, + { Zenum8, Cx0014, 0x0067, Z(MultiReliability), 1, Z_Nop }, + { Zuint16, Cx0014, 0x0068, Z(MultiRelinquishDefault),1, Z_Nop }, + { Zmap8, Cx0014, 0x006F, Z(MultiStatusFlags), 1, Z_Nop }, + { Zuint32, Cx0014, 0x0100, Z(MultiApplicationType), 1, Z_Nop }, + + // Power Profile cluster + { Zuint8, Cx001A, 0x0000, Z(TotalProfileNum), 1, Z_Nop }, + { Zbool, Cx001A, 0x0001, Z(MultipleScheduling), 1, Z_Nop }, + { Zmap8, Cx001A, 0x0002, Z(EnergyFormatting), 1, Z_Nop }, + { Zbool, Cx001A, 0x0003, Z(EnergyRemote), 1, Z_Nop }, + { Zmap8, Cx001A, 0x0004, Z(ScheduleMode), 1, Z_Nop }, + + // Poll Control cluster + { Zuint32, Cx0020, 0x0000, Z(CheckinInterval), 1, Z_Nop }, + { Zuint32, Cx0020, 0x0001, Z(LongPollInterval), 1, Z_Nop }, + { Zuint16, Cx0020, 0x0002, Z(ShortPollInterval), 1, Z_Nop }, + { Zuint16, Cx0020, 0x0003, Z(FastPollTimeout), 1, Z_Nop }, + { Zuint32, Cx0020, 0x0004, Z(CheckinIntervalMin), 1, Z_Nop }, + { Zuint32, Cx0020, 0x0005, Z(LongPollIntervalMin), 1, Z_Nop }, + { Zuint16, Cx0020, 0x0006, Z(FastPollTimeoutMax), 1, Z_Nop }, + + // Shade Configuration cluster + { Zuint16, Cx0100, 0x0000, Z(PhysicalClosedLimit), 1, Z_Nop }, + { Zuint8, Cx0100, 0x0001, Z(MotorStepSize), 1, Z_Nop }, + { Zmap8, Cx0100, 0x0002, Z(Status), 1, Z_Nop }, + { Zuint16, Cx0100, 0x0010, Z(ClosedLimit), 1, Z_Nop }, + { Zenum8, Cx0100, 0x0011, Z(Mode), 1, Z_Nop }, + + // Door Lock cluster + { Zenum8, Cx0101, 0x0000, Z(LockState), 1, Z_Nop }, + { Zenum8, Cx0101, 0x0001, Z(LockType), 1, Z_Nop }, + { Zbool, Cx0101, 0x0002, Z(ActuatorEnabled), 1, Z_Nop }, + { Zenum8, Cx0101, 0x0003, Z(DoorState), 1, Z_Nop }, + { Zuint32, Cx0101, 0x0004, Z(DoorOpenEvents), 1, Z_Nop }, + { Zuint32, Cx0101, 0x0005, Z(DoorClosedEvents), 1, Z_Nop }, + { Zuint16, Cx0101, 0x0006, Z(OpenPeriod), 1, Z_Nop }, + + // Aqara Lumi Vibration Sensor + { Zuint16, Cx0101, 0x0055, Z(AqaraVibrationMode), 0, Z_AqaraVibration }, + { Zuint16, Cx0101, 0x0503, Z(AqaraVibrationsOrAngle), 1, Z_Nop }, + { Zuint32, Cx0101, 0x0505, Z(AqaraVibration505), 1, Z_Nop }, + { Zuint48, Cx0101, 0x0508, Z(AqaraAccelerometer), 0, Z_AqaraVibration }, + + // Window Covering cluster + { Zenum8, Cx0102, 0x0000, Z(WindowCoveringType), 1, Z_Nop }, + { Zuint16, Cx0102, 0x0001, Z(PhysicalClosedLimitLift),1, Z_Nop }, + { Zuint16, Cx0102, 0x0002, Z(PhysicalClosedLimitTilt),1, Z_Nop }, + { Zuint16, Cx0102, 0x0003, Z(CurrentPositionLift), 1, Z_Nop }, + { Zuint16, Cx0102, 0x0004, Z(CurrentPositionTilt), 1, Z_Nop }, + { Zuint16, Cx0102, 0x0005, Z(NumberofActuationsLift),1, Z_Nop }, + { Zuint16, Cx0102, 0x0006, Z(NumberofActuationsTilt),1, Z_Nop }, + { Zmap8, Cx0102, 0x0007, Z(ConfigStatus), 1, Z_Nop }, + { Zuint8, Cx0102, 0x0008, Z(CurrentPositionLiftPercentage),1, Z_Nop }, + { Zuint8, Cx0102, 0x0009, Z(CurrentPositionTiltPercentage),1, Z_Nop }, + { Zuint16, Cx0102, 0x0010, Z(InstalledOpenLimitLift),1, Z_Nop }, + { Zuint16, Cx0102, 0x0011, Z(InstalledClosedLimitLift),1, Z_Nop }, + { Zuint16, Cx0102, 0x0012, Z(InstalledOpenLimitTilt),1, Z_Nop }, + { Zuint16, Cx0102, 0x0013, Z(InstalledClosedLimitTilt),1, Z_Nop }, + { Zuint16, Cx0102, 0x0014, Z(VelocityLift), 1, Z_Nop }, + { Zuint16, Cx0102, 0x0015, Z(AccelerationTimeLift),1, Z_Nop }, + { Zuint16, Cx0102, 0x0016, Z(DecelerationTimeLift), 1, Z_Nop }, + { Zmap8, Cx0102, 0x0017, Z(Mode), 1, Z_Nop }, + { Zoctstr, Cx0102, 0x0018, Z(IntermediateSetpointsLift),1, Z_Nop }, + { Zoctstr, Cx0102, 0x0019, Z(IntermediateSetpointsTilt),1, Z_Nop }, + + // Color Control cluster + { Zuint8, Cx0300, 0x0000, Z(Hue), 1, Z_Nop }, + { Zuint8, Cx0300, 0x0001, Z(Sat), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0002, Z(RemainingTime), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0003, Z(X), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0004, Z(Y), 1, Z_Nop }, + { Zenum8, Cx0300, 0x0005, Z(DriftCompensation), 1, Z_Nop }, + { Zstring, Cx0300, 0x0006, Z(CompensationText), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0007, Z(CT), 1, Z_Nop }, + { Zenum8, Cx0300, 0x0008, Z(ColorMode), 1, Z_Nop }, + { Zuint8, Cx0300, 0x0010, Z(NumberOfPrimaries), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0011, Z(Primary1X), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0012, Z(Primary1Y), 1, Z_Nop }, + { Zuint8, Cx0300, 0x0013, Z(Primary1Intensity), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0015, Z(Primary2X), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0016, Z(Primary2Y), 1, Z_Nop }, + { Zuint8, Cx0300, 0x0017, Z(Primary2Intensity), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0019, Z(Primary3X), 1, Z_Nop }, + { Zuint16, Cx0300, 0x001A, Z(Primary3Y), 1, Z_Nop }, + { Zuint8, Cx0300, 0x001B, Z(Primary3Intensity), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0030, Z(WhitePointX), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0031, Z(WhitePointY), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0032, Z(ColorPointRX), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0033, Z(ColorPointRY), 1, Z_Nop }, + { Zuint8, Cx0300, 0x0034, Z(ColorPointRIntensity), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0036, Z(ColorPointGX), 1, Z_Nop }, + { Zuint16, Cx0300, 0x0037, Z(ColorPointGY), 1, Z_Nop }, + { Zuint8, Cx0300, 0x0038, Z(ColorPointGIntensity), 1, Z_Nop }, + { Zuint16, Cx0300, 0x003A, Z(ColorPointBX), 1, Z_Nop }, + { Zuint16, Cx0300, 0x003B, Z(ColorPointBY), 1, Z_Nop }, + { Zuint8, Cx0300, 0x003C, Z(ColorPointBIntensity), 1, Z_Nop }, + + // Illuminance Measurement cluster + { Zuint16, Cx0400, 0x0000, Z(Illuminance), 1, Z_Nop }, // Illuminance (in Lux) + { Zuint16, Cx0400, 0x0001, Z(IlluminanceMinMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0400, 0x0002, Z(IlluminanceMaxMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0400, 0x0003, Z(IlluminanceTolerance), 1, Z_Nop }, // + { Zenum8, Cx0400, 0x0004, Z(IlluminanceLightSensorType), 1, Z_Nop }, // + { Zunk, Cx0400, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + + // Illuminance Level Sensing cluster + { Zenum8, Cx0401, 0x0000, Z(IlluminanceLevelStatus), 1, Z_Nop }, // Illuminance (in Lux) + { Zenum8, Cx0401, 0x0001, Z(IlluminanceLightSensorType), 1, Z_Nop }, // LightSensorType + { Zuint16, Cx0401, 0x0010, Z(IlluminanceTargetLevel), 1, Z_Nop }, // + { Zunk, Cx0401, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + + // Temperature Measurement cluster + { Zint16, Cx0402, 0x0000, Z(Temperature), -100, Z_Nop }, // divide by 100 + { Zint16, Cx0402, 0x0001, Z(TemperatureMinMeasuredValue), -100, Z_Nop }, // + { Zint16, Cx0402, 0x0002, Z(TemperatureMaxMeasuredValue), -100, Z_Nop }, // + { Zuint16, Cx0402, 0x0003, Z(TemperatureTolerance), -100, Z_Nop }, // + { Zunk, Cx0402, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + + // Pressure Measurement cluster + { Zunk, Cx0403, 0x0000, Z(PressureUnit), 0, Z_AddPressureUnit }, // Pressure Unit + { Zint16, Cx0403, 0x0000, Z(Pressure), 1, Z_Nop }, // Pressure + { Zint16, Cx0403, 0x0001, Z(PressureMinMeasuredValue), 1, Z_Nop }, // + { Zint16, Cx0403, 0x0002, Z(PressureMaxMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0403, 0x0003, Z(PressureTolerance), 1, Z_Nop }, // + { Zint16, Cx0403, 0x0010, Z(PressureScaledValue), 1, Z_Nop }, // + { Zint16, Cx0403, 0x0011, Z(PressureMinScaledValue), 1, Z_Nop }, // + { Zint16, Cx0403, 0x0012, Z(PressureMaxScaledValue), 1, Z_Nop }, // + { Zuint16, Cx0403, 0x0013, Z(PressureScaledTolerance), 1, Z_Nop }, // + { Zint8, Cx0403, 0x0014, Z(PressureScale), 1, Z_Nop }, // + { Zunk, Cx0403, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other Pressure values + + // Flow Measurement cluster + { Zuint16, Cx0404, 0x0000, Z(FlowRate), -10, Z_Nop }, // Flow (in m3/h) + { Zuint16, Cx0404, 0x0001, Z(FlowMinMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0404, 0x0002, Z(FlowMaxMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0404, 0x0003, Z(FlowTolerance), 1, Z_Nop }, // + { Zunk, Cx0404, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + + // Relative Humidity Measurement cluster + { Zuint16, Cx0405, 0x0000, Z(Humidity), -100, Z_Nop }, // Humidity + { Zuint16, Cx0405, 0x0001, Z(HumidityMinMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0405, 0x0002, Z(HumidityMaxMeasuredValue), 1, Z_Nop }, // + { Zuint16, Cx0405, 0x0003, Z(HumidityTolerance), 1, Z_Nop }, // + { Zunk, Cx0405, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + + // Occupancy Sensing cluster + { Zmap8, Cx0406, 0x0000, Z(Occupancy), 1, Z_Nop }, // Occupancy (map8) + { Zenum8, Cx0406, 0x0001, Z(OccupancySensorType), 1, Z_Nop }, // OccupancySensorType + { Zunk, Cx0406, 0xFFFF, nullptr, 0, Z_Nop }, // Remove all other values + + // Meter Identification cluster + { Zstring, Cx0B01, 0x0000, Z(CompanyName), 1, Z_Nop }, + { Zuint16, Cx0B01, 0x0001, Z(MeterTypeID), 1, Z_Nop }, + { Zuint16, Cx0B01, 0x0004, Z(DataQualityID), 1, Z_Nop }, + { Zstring, Cx0B01, 0x0005, Z(CustomerName), 1, Z_Nop }, + { Zoctstr, Cx0B01, 0x0006, Z(Model), 1, Z_Nop }, + { Zoctstr, Cx0B01, 0x0007, Z(PartNumber), 1, Z_Nop }, + { Zoctstr, Cx0B01, 0x0008, Z(ProductRevision), 1, Z_Nop }, + { Zoctstr, Cx0B01, 0x000A, Z(SoftwareRevision), 1, Z_Nop }, + { Zstring, Cx0B01, 0x000B, Z(UtilityName), 1, Z_Nop }, + { Zstring, Cx0B01, 0x000C, Z(POD), 1, Z_Nop }, + { Zint24, Cx0B01, 0x000D, Z(AvailablePower), 1, Z_Nop }, + { Zint24, Cx0B01, 0x000E, Z(PowerThreshold), 1, Z_Nop }, + + // Diagnostics cluster + { Zuint16, Cx0B05, 0x0000, Z(NumberOfResets), 1, Z_Nop }, + { Zuint16, Cx0B05, 0x0001, Z(PersistentMemoryWrites),1, Z_Nop }, + { Zuint8, Cx0B05, 0x011C, Z(LastMessageLQI), 1, Z_Nop }, + { Zuint8, Cx0B05, 0x011D, Z(LastMessageRSSI), 1, Z_Nop }, + +}; + + typedef union ZCLHeaderFrameControl_t { struct { uint8_t frame_type : 2; // 00 = across entire profile, 01 = cluster specific @@ -166,8 +662,9 @@ public: } static void generateAttributeName(const JsonObject& json, uint16_t cluster, uint16_t attr, char *key, size_t key_len); - void parseRawAttributes(JsonObject& json, uint8_t offset = 0); + void parseReportAttributes(JsonObject& json, uint8_t offset = 0); void parseReadAttributes(JsonObject& json, uint8_t offset = 0); + void parseReadAttributesResponse(JsonObject& json, uint8_t offset = 0); void parseResponse(void); void parseClusterSpecificCommand(JsonObject& json, uint8_t offset = 0); void postProcessAttributes(uint16_t shortaddr, JsonObject& json); @@ -242,6 +739,102 @@ uint8_t toPercentageCR2032(uint32_t voltage) { return percentage; } +// +// Appends the attribute value to Write or to Report +// Adds to buf: +// - 2 bytes: attribute identigier +// - 1 byte: attribute type +// - n bytes: value (typically between 1 and 4 bytes, or bigger for strings) +// returns number of bytes of attribute, or <0 if error +// status: shall we insert a status OK (0x00) as required by ReadResponse +int32_t encodeSingleAttribute(class SBuffer &buf, const JsonVariant &val, float val_f, uint16_t attr, uint8_t attrtype, bool status = false) { + uint32_t len = Z_getDatatypeLen(attrtype); // pre-compute lenght, overloaded for variable length attributes + uint32_t u32; + int32_t i32; + float f32; + + if (&val) { + u32 = val.as(); + i32 = val.as(); + f32 = val.as(); + } else { + u32 = val_f; + i32 = val_f; + f32 = val_f; + } + + buf.add16(attr); // prepend with attribute identifier + if (status) { + buf.add8(Z_SUCCESS); // status OK = 0x00 + } + buf.add8(attrtype); // prepend with attribute type + + switch (attrtype) { + // unsigned 8 + case Zbool: // bool + case Zuint8: // uint8 + case Zenum8: // enum8 + case Zdata8: // data8 + case Zmap8: // map8 + buf.add8(u32); + break; + // unsigned 16 + case Zuint16: // uint16 + case Zenum16: // enum16 + case Zdata16: // data16 + case Zmap16: // map16 + buf.add16(u32); + break; + // unisgned 32 + case Zuint32: // uint32 + case Zdata32: // data32 + case Zmap32: // map32 + case ZUTC: // UTC - epoch 32 bits, seconds since 1-Jan-2000 + buf.add32(u32); + break; + + // signed 8 + case Zint8: // int8 + buf.add8(i32); + break; + case Zint16: // int16 + buf.add16(i32); + break; + case Zint32: // int32 + buf.add32(i32); + break; + + case Zsingle: // float + uint32_t *f_ptr; + buf.add32( *((uint32_t*)&f32) ); // cast float as uint32_t + break; + + case Zstring: + case Zstring16: + { + const char * val_str = (&val) ? val.as() : ""; // avoid crash if &val is null + if (nullptr == val_str) { return -2; } + size_t val_len = strlen(val_str); + if (val_len > 32) { val_len = 32; } + len = val_len + 1; + buf.add8(val_len); + if (Zstring16 == attrtype) { + buf.add8(0); // len is on 2 bytes + len++; + } + for (uint32_t i = 0; i < val_len; i++) { + buf.add8(val_str[i]); + } + } + break; + + default: + // remove the attribute type we just added + buf.setLen(buf.len() - (status ? 4 : 3)); + return -1; + } + return len + (status ? 4 : 3); +} uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer &buf, uint32_t offset, uint32_t buflen) { @@ -281,6 +874,7 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer } break; case Zuint32: // uint32 + case ZUTC: // UTC { uint32_t uint32_val = buf.get32(i); // i += 4; @@ -407,7 +1001,6 @@ uint32_t parseSingleAttribute(JsonObject& json, char *attrid_str, class SBuffer // TODO case ZToD: // ToD case Zdate: // date - case ZUTC: // UTC case ZclusterId: // clusterId case ZattribId: // attribId case ZbacOID: // bacOID @@ -461,7 +1054,7 @@ void ZCLFrame::generateAttributeName(const JsonObject& json, uint16_t cluster, u } // First pass, parse all attributes in their native format -void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { +void ZCLFrame::parseReportAttributes(JsonObject& json, uint8_t offset) { uint32_t i = offset; uint32_t len = _payload.len(); @@ -482,12 +1075,41 @@ void ZCLFrame::parseRawAttributes(JsonObject& json, uint8_t offset) { } } -// ZCL_READ_ATTRIBUTES_RESPONSE +// ZCL_READ_ATTRIBUTES +// TODO void ZCLFrame::parseReadAttributes(JsonObject& json, uint8_t offset) { uint32_t i = offset; uint32_t len = _payload.len(); - while (len - i >= 4) { + json[F(D_CMND_ZIGBEE_CLUSTER)] = _cluster_id; + + JsonArray &attr_list = json.createNestedArray(F("Read")); + JsonObject &attr_names = json.createNestedObject(F("ReadNames")); + while (len - i >= 2) { + uint16_t attrid = _payload.get16(i); + attr_list.add(attrid); + + // find the attribute name + for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { + const Z_AttributeConverter *converter = &Z_PostProcess[i]; + uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); + uint16_t conv_attribute = pgm_read_word(&converter->attribute); + + if ((conv_cluster == _cluster_id) && (conv_attribute == attrid)) { + attr_names[(const __FlashStringHelper*) converter->name] = true; + break; + } + } + i += 2; + } +} + +// ZCL_READ_ATTRIBUTES_RESPONSE +void ZCLFrame::parseReadAttributesResponse(JsonObject& json, uint8_t offset) { + uint32_t i = offset; + uint32_t len = _payload.len(); + + while (len >= i + 4) { uint16_t attrid = _payload.get16(i); i += 2; uint8_t status = _payload.get8(i++); @@ -550,532 +1172,38 @@ void ZCLFrame::parseClusterSpecificCommand(JsonObject& json, uint8_t offset) { sendHueUpdate(_srcaddr, _groupaddr, _cluster_id, _cmd_id, _frame_control.b.direction); } -// return value: -// 0 = keep initial value -// 1 = remove initial value -typedef int32_t (*Z_AttrConverter)(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr); -typedef struct Z_AttributeConverter { - uint8_t type; - uint8_t cluster_short; - uint16_t attribute; - const char * name; - Z_AttrConverter func; -} Z_AttributeConverter; - -// Cluster numbers are store in 8 bits format to save space, -// the following tables allows the conversion from 8 bits index Cx... -// to the 16 bits actual cluster number -enum Cx_cluster_short { - Cx0000, Cx0001, Cx0002, Cx0003, Cx0004, Cx0005, Cx0006, Cx0007, - Cx0008, Cx0009, Cx000A, Cx000B, Cx000C, Cx000D, Cx000E, Cx000F, - Cx0010, Cx0011, Cx0012, Cx0013, Cx0014, Cx001A, Cx0020, Cx0100, - Cx0101, Cx0102, Cx0300, Cx0400, Cx0401, Cx0402, Cx0403, Cx0404, - Cx0405, Cx0406, Cx0B01, Cx0B05, -}; - -const uint16_t Cx_cluster[] PROGMEM = { - 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, - 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, - 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x001A, 0x0020, 0x0100, - 0x0101, 0x0102, 0x0300, 0x0400, 0x0401, 0x0402, 0x0403, 0x0404, - 0x0405, 0x0406, 0x0B01, 0x0B05, -}; - -uint16_t CxToCluster(uint8_t cx) { - if (cx < ARRAY_SIZE(Cx_cluster)) { - return pgm_read_word(&Cx_cluster[cx]); - } - return 0xFFFF; -} - -ZF(ZCLVersion) ZF(AppVersion) ZF(StackVersion) ZF(HWVersion) ZF(Manufacturer) ZF(ModelId) -ZF(DateCode) ZF(PowerSource) ZF(SWBuildID) ZF(Power) ZF(SwitchType) ZF(Dimmer) -ZF(MainsVoltage) ZF(MainsFrequency) ZF(BatteryVoltage) ZF(BatteryPercentage) -ZF(CurrentTemperature) ZF(MinTempExperienced) ZF(MaxTempExperienced) ZF(OverTempTotalDwell) -ZF(SceneCount) ZF(CurrentScene) ZF(CurrentGroup) ZF(SceneValid) -ZF(AlarmCount) ZF(Time) ZF(TimeStatus) ZF(TimeZone) ZF(DstStart) ZF(DstEnd) -ZF(DstShift) ZF(StandardTime) ZF(LocalTime) ZF(LastSetTime) ZF(ValidUntilTime) - -ZF(LocationType) ZF(LocationMethod) ZF(LocationAge) ZF(QualityMeasure) ZF(NumberOfDevices) - -ZF(AnalogInActiveText) ZF(AnalogInDescription) ZF(AnalogInInactiveText) ZF(AnalogInMaxValue) -ZF(AnalogInMinValue) ZF(AnalogInOutOfService) ZF(AqaraRotate) ZF(AnalogInPriorityArray) -ZF(AnalogInReliability) ZF(AnalogInRelinquishDefault) ZF(AnalogInResolution) ZF(AnalogInStatusFlags) -ZF(AnalogInEngineeringUnits) ZF(AnalogInApplicationType) ZF(Aqara_FF05) - -ZF(AnalogOutDescription) ZF(AnalogOutMaxValue) ZF(AnalogOutMinValue) ZF(AnalogOutOutOfService) -ZF(AnalogOutValue) ZF(AnalogOutPriorityArray) ZF(AnalogOutReliability) ZF(AnalogOutRelinquishDefault) -ZF(AnalogOutResolution) ZF(AnalogOutStatusFlags) ZF(AnalogOutEngineeringUnits) ZF(AnalogOutApplicationType) - -ZF(AnalogDescription) ZF(AnalogOutOfService) ZF(AnalogValue) ZF(AnalogPriorityArray) ZF(AnalogReliability) -ZF(AnalogRelinquishDefault) ZF(AnalogStatusFlags) ZF(AnalogEngineeringUnits) ZF(AnalogApplicationType) - -ZF(BinaryInActiveText) ZF(BinaryInDescription) ZF(BinaryInInactiveText) ZF(BinaryInOutOfService) -ZF(BinaryInPolarity) ZF(BinaryInValue) ZF(BinaryInPriorityArray) ZF(BinaryInReliability) -ZF(BinaryInStatusFlags) ZF(BinaryInApplicationType) - -ZF(BinaryOutActiveText) ZF(BinaryOutDescription) ZF(BinaryOutInactiveText) ZF(BinaryOutMinimumOffTime) -ZF(BinaryOutMinimumOnTime) ZF(BinaryOutOutOfService) ZF(BinaryOutPolarity) ZF(BinaryOutValue) -ZF(BinaryOutPriorityArray) ZF(BinaryOutReliability) ZF(BinaryOutRelinquishDefault) ZF(BinaryOutStatusFlags) -ZF(BinaryOutApplicationType) - -ZF(BinaryActiveText) ZF(BinaryDescription) ZF(BinaryInactiveText) ZF(BinaryMinimumOffTime) -ZF(BinaryMinimumOnTime) ZF(BinaryOutOfService) ZF(BinaryValue) ZF(BinaryPriorityArray) ZF(BinaryReliability) -ZF(BinaryRelinquishDefault) ZF(BinaryStatusFlags) ZF(BinaryApplicationType) - -ZF(MultiInStateText) ZF(MultiInDescription) ZF(MultiInNumberOfStates) ZF(MultiInOutOfService) -ZF(MultiInValue) ZF(MultiInReliability) ZF(MultiInStatusFlags) ZF(MultiInApplicationType) - -ZF(MultiOutStateText) ZF(MultiOutDescription) ZF(MultiOutNumberOfStates) ZF(MultiOutOutOfService) -ZF(MultiOutValue) ZF(MultiOutPriorityArray) ZF(MultiOutReliability) ZF(MultiOutRelinquishDefault) -ZF(MultiOutStatusFlags) ZF(MultiOutApplicationType) - -ZF(MultiStateText) ZF(MultiDescription) ZF(MultiNumberOfStates) ZF(MultiOutOfService) ZF(MultiValue) -ZF(MultiReliability) ZF(MultiRelinquishDefault) ZF(MultiStatusFlags) ZF(MultiApplicationType) - -ZF(TotalProfileNum) ZF(MultipleScheduling) ZF(EnergyFormatting) ZF(EnergyRemote) ZF(ScheduleMode) - -ZF(CheckinInterval) ZF(LongPollInterval) ZF(ShortPollInterval) ZF(FastPollTimeout) ZF(CheckinIntervalMin) -ZF(LongPollIntervalMin) ZF(FastPollTimeoutMax) - -ZF(PhysicalClosedLimit) ZF(MotorStepSize) ZF(Status) ZF(ClosedLimit) ZF(Mode) - -ZF(LockState) ZF(LockType) ZF(ActuatorEnabled) ZF(DoorState) ZF(DoorOpenEvents) -ZF(DoorClosedEvents) ZF(OpenPeriod) - -ZF(AqaraVibrationMode) ZF(AqaraVibrationsOrAngle) ZF(AqaraVibration505) ZF(AqaraAccelerometer) - -ZF(WindowCoveringType) ZF(PhysicalClosedLimitLift) ZF(PhysicalClosedLimitTilt) ZF(CurrentPositionLift) -ZF(CurrentPositionTilt) ZF(NumberofActuationsLift) ZF(NumberofActuationsTilt) ZF(ConfigStatus) -ZF(CurrentPositionLiftPercentage) ZF(CurrentPositionTiltPercentage) ZF(InstalledOpenLimitLift) -ZF(InstalledClosedLimitLift) ZF(InstalledOpenLimitTilt) ZF(InstalledClosedLimitTilt) ZF(VelocityLift) -ZF(AccelerationTimeLift) ZF(DecelerationTimeLift) ZF(IntermediateSetpointsLift) -ZF(IntermediateSetpointsTilt) - -ZF(Hue) ZF(Sat) ZF(RemainingTime) ZF(X) ZF(Y) ZF(DriftCompensation) ZF(CompensationText) ZF(CT) -ZF(ColorMode) ZF(NumberOfPrimaries) ZF(Primary1X) ZF(Primary1Y) ZF(Primary1Intensity) ZF(Primary2X) -ZF(Primary2Y) ZF(Primary2Intensity) ZF(Primary3X) ZF(Primary3Y) ZF(Primary3Intensity) ZF(WhitePointX) -ZF(WhitePointY) ZF(ColorPointRX) ZF(ColorPointRY) ZF(ColorPointRIntensity) ZF(ColorPointGX) ZF(ColorPointGY) -ZF(ColorPointGIntensity) ZF(ColorPointBX) ZF(ColorPointBY) ZF(ColorPointBIntensity) - -ZF(Illuminance) ZF(IlluminanceMinMeasuredValue) ZF(IlluminanceMaxMeasuredValue) ZF(IlluminanceTolerance) -ZF(IlluminanceLightSensorType) ZF(IlluminanceLevelStatus) ZF(IlluminanceTargetLevel) - -ZF(Temperature) ZF(TemperatureMinMeasuredValue) ZF(TemperatureMaxMeasuredValue) ZF(TemperatureTolerance) - -ZF(PressureUnit) ZF(Pressure) ZF(PressureMinMeasuredValue) ZF(PressureMaxMeasuredValue) ZF(PressureTolerance) -ZF(PressureScaledValue) ZF(PressureMinScaledValue) ZF(PressureMaxScaledValue) ZF(PressureScaledTolerance) -ZF(PressureScale) - -ZF(FlowRate) ZF(FlowMinMeasuredValue) ZF(FlowMaxMeasuredValue) ZF(FlowTolerance) - -ZF(Humidity) ZF(HumidityMinMeasuredValue) ZF(HumidityMaxMeasuredValue) ZF(HumidityTolerance) - -ZF(Occupancy) ZF(OccupancySensorType) - -ZF(CompanyName) ZF(MeterTypeID) ZF(DataQualityID) ZF(CustomerName) ZF(Model) ZF(PartNumber) -ZF(SoftwareRevision) ZF(POD) ZF(AvailablePower) ZF(PowerThreshold) ZF(ProductRevision) ZF(UtilityName) - -ZF(NumberOfResets) ZF(PersistentMemoryWrites) ZF(LastMessageLQI) ZF(LastMessageRSSI) -// list of post-processing directives -const Z_AttributeConverter Z_PostProcess[] PROGMEM = { - { Zuint8, Cx0000, 0x0000, Z(ZCLVersion), &Z_Copy }, - { Zuint8, Cx0000, 0x0001, Z(AppVersion), &Z_Copy }, - { Zuint8, Cx0000, 0x0002, Z(StackVersion), &Z_Copy }, - { Zuint8, Cx0000, 0x0003, Z(HWVersion), &Z_Copy }, - { Zstring, Cx0000, 0x0004, Z(Manufacturer), &Z_ManufKeep }, // record Manufacturer - { Zstring, Cx0000, 0x0005, Z(ModelId), &Z_ModelKeep }, // record Model - { Zstring, Cx0000, 0x0006, Z(DateCode), &Z_Copy }, - { Zenum8, Cx0000, 0x0007, Z(PowerSource), &Z_Copy }, - { Zstring, Cx0000, 0x4000, Z(SWBuildID), &Z_Copy }, - { Zunk, Cx0000, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - // Cmd 0x0A - Cluster 0x0000, attribute 0xFF01 - proprietary - { Zmap8, Cx0000, 0xFF01, nullptr, &Z_AqaraSensor }, // Occupancy (map8) - - // Power Configuration cluster - { Zuint16, Cx0001, 0x0000, Z(MainsVoltage), &Z_Copy }, - { Zuint8, Cx0001, 0x0001, Z(MainsFrequency), &Z_Copy }, - { Zuint8, Cx0001, 0x0020, Z(BatteryVoltage), &Z_FloatDiv10 }, - { Zuint8, Cx0001, 0x0021, Z(BatteryPercentage), &Z_FloatDiv2 }, - - // Device Temperature Configuration cluster - { Zint16, Cx0002, 0x0000, Z(CurrentTemperature), &Z_Copy }, - { Zint16, Cx0002, 0x0001, Z(MinTempExperienced), &Z_Copy }, - { Zint16, Cx0002, 0x0002, Z(MaxTempExperienced), &Z_Copy }, - { Zuint16, Cx0002, 0x0003, Z(OverTempTotalDwell), &Z_Copy }, - - // Scenes cluster - { Zuint8, Cx0005, 0x0000, Z(SceneCount), &Z_Copy }, - { Zuint8, Cx0005, 0x0001, Z(CurrentScene), &Z_Copy }, - { Zuint16, Cx0005, 0x0002, Z(CurrentGroup), &Z_Copy }, - { Zbool, Cx0005, 0x0003, Z(SceneValid), &Z_Copy }, - //{ Zmap8, Cx0005, 0x0004, Z(NameSupport), &Z_Copy }, - - // On/off cluster - { Zbool, Cx0006, 0x0000, Z(Power), &Z_Copy }, - { Zbool, Cx0006, 0x8000, Z(Power), &Z_Copy }, // See 7280 - - // On/Off Switch Configuration cluster - { Zenum8, Cx0007, 0x0000, Z(SwitchType), &Z_Copy }, - - // Level Control cluster - { Zuint8, Cx0008, 0x0000, Z(Dimmer), &Z_Copy }, - // { Zuint16, Cx0008, 0x0001, Z(RemainingTime", &Z_Copy }, - // { Zuint16, Cx0008, 0x0010, Z(OnOffTransitionTime", &Z_Copy }, - // { Zuint8, Cx0008, 0x0011, Z(OnLevel", &Z_Copy }, - // { Zuint16, Cx0008, 0x0012, Z(OnTransitionTime", &Z_Copy }, - // { Zuint16, Cx0008, 0x0013, Z(OffTransitionTime", &Z_Copy }, - // { Zuint16, Cx0008, 0x0014, Z(DefaultMoveRate", &Z_Copy }, - - // Alarms cluster - { Zuint16, Cx0009, 0x0000, Z(AlarmCount), &Z_Copy }, - - // Time cluster - { ZUTC, Cx000A, 0x0000, Z(Time), &Z_Copy }, - { Zmap8, Cx000A, 0x0001, Z(TimeStatus), &Z_Copy }, - { Zint32, Cx000A, 0x0002, Z(TimeZone), &Z_Copy }, - { Zuint32, Cx000A, 0x0003, Z(DstStart), &Z_Copy }, - { Zuint32, Cx000A, 0x0004, Z(DstEnd), &Z_Copy }, - { Zint32, Cx000A, 0x0005, Z(DstShift), &Z_Copy }, - { Zuint32, Cx000A, 0x0006, Z(StandardTime), &Z_Copy }, - { Zuint32, Cx000A, 0x0007, Z(LocalTime), &Z_Copy }, - { ZUTC, Cx000A, 0x0008, Z(LastSetTime), &Z_Copy }, - { ZUTC, Cx000A, 0x0009, Z(ValidUntilTime), &Z_Copy }, - - // RSSI Location cluster - { Zdata8, Cx000B, 0x0000, Z(LocationType), &Z_Copy }, - { Zenum8, Cx000B, 0x0001, Z(LocationMethod), &Z_Copy }, - { Zuint16, Cx000B, 0x0002, Z(LocationAge), &Z_Copy }, - { Zuint8, Cx000B, 0x0003, Z(QualityMeasure), &Z_Copy }, - { Zuint8, Cx000B, 0x0004, Z(NumberOfDevices), &Z_Copy }, - - // Analog Input cluster - // { 0xFF, Cx000C, 0x0004, Z(AnalogInActiveText), &Z_Copy }, - { Zstring, Cx000C, 0x001C, Z(AnalogInDescription), &Z_Copy }, - // { 0xFF, Cx000C, 0x002E, Z(AnalogInInactiveText), &Z_Copy }, - { Zsingle, Cx000C, 0x0041, Z(AnalogInMaxValue), &Z_Copy }, - { Zsingle, Cx000C, 0x0045, Z(AnalogInMinValue), &Z_Copy }, - { Zbool, Cx000C, 0x0051, Z(AnalogInOutOfService), &Z_Copy }, - { Zsingle, Cx000C, 0x0055, Z(AqaraRotate), &Z_Copy }, - // { 0xFF, Cx000C, 0x0057, Z(AnalogInPriorityArray),&Z_Copy }, - { Zenum8, Cx000C, 0x0067, Z(AnalogInReliability), &Z_Copy }, - // { 0xFF, Cx000C, 0x0068, Z(AnalogInRelinquishDefault),&Z_Copy }, - { Zsingle, Cx000C, 0x006A, Z(AnalogInResolution), &Z_Copy }, - { Zmap8, Cx000C, 0x006F, Z(AnalogInStatusFlags), &Z_Copy }, - { Zenum16, Cx000C, 0x0075, Z(AnalogInEngineeringUnits),&Z_Copy }, - { Zuint32, Cx000C, 0x0100, Z(AnalogInApplicationType),&Z_Copy }, - { Zuint16, Cx000C, 0xFF05, Z(Aqara_FF05), &Z_Copy }, - - // Analog Output cluster - { Zstring, Cx000D, 0x001C, Z(AnalogOutDescription), &Z_Copy }, - { Zsingle, Cx000D, 0x0041, Z(AnalogOutMaxValue), &Z_Copy }, - { Zsingle, Cx000D, 0x0045, Z(AnalogOutMinValue), &Z_Copy }, - { Zbool, Cx000D, 0x0051, Z(AnalogOutOutOfService),&Z_Copy }, - { Zsingle, Cx000D, 0x0055, Z(AnalogOutValue), &Z_Copy }, - // { Zunk, Cx000D, 0x0057, Z(AnalogOutPriorityArray),&Z_Copy }, - { Zenum8, Cx000D, 0x0067, Z(AnalogOutReliability), &Z_Copy }, - { Zsingle, Cx000D, 0x0068, Z(AnalogOutRelinquishDefault),&Z_Copy }, - { Zsingle, Cx000D, 0x006A, Z(AnalogOutResolution), &Z_Copy }, - { Zmap8, Cx000D, 0x006F, Z(AnalogOutStatusFlags), &Z_Copy }, - { Zenum16, Cx000D, 0x0075, Z(AnalogOutEngineeringUnits),&Z_Copy }, - { Zuint32, Cx000D, 0x0100, Z(AnalogOutApplicationType),&Z_Copy }, - - // Analog Value cluster - { Zstring, Cx000E, 0x001C, Z(AnalogDescription), &Z_Copy }, - { Zbool, Cx000E, 0x0051, Z(AnalogOutOfService), &Z_Copy }, - { Zsingle, Cx000E, 0x0055, Z(AnalogValue), &Z_Copy }, - { Zunk, Cx000E, 0x0057, Z(AnalogPriorityArray), &Z_Copy }, - { Zenum8, Cx000E, 0x0067, Z(AnalogReliability), &Z_Copy }, - { Zsingle, Cx000E, 0x0068, Z(AnalogRelinquishDefault),&Z_Copy }, - { Zmap8, Cx000E, 0x006F, Z(AnalogStatusFlags), &Z_Copy }, - { Zenum16, Cx000E, 0x0075, Z(AnalogEngineeringUnits),&Z_Copy }, - { Zuint32, Cx000E, 0x0100, Z(AnalogApplicationType),&Z_Copy }, - - // Binary Input cluster - { Zstring, Cx000F, 0x0004, Z(BinaryInActiveText), &Z_Copy }, - { Zstring, Cx000F, 0x001C, Z(BinaryInDescription), &Z_Copy }, - { Zstring, Cx000F, 0x002E, Z(BinaryInInactiveText),&Z_Copy }, - { Zbool, Cx000F, 0x0051, Z(BinaryInOutOfService),&Z_Copy }, - { Zenum8, Cx000F, 0x0054, Z(BinaryInPolarity), &Z_Copy }, - { Zstring, Cx000F, 0x0055, Z(BinaryInValue), &Z_Copy }, - // { 0xFF, Cx000F, 0x0057, Z(BinaryInPriorityArray),&Z_Copy }, - { Zenum8, Cx000F, 0x0067, Z(BinaryInReliability), &Z_Copy }, - { Zmap8, Cx000F, 0x006F, Z(BinaryInStatusFlags), &Z_Copy }, - { Zuint32, Cx000F, 0x0100, Z(BinaryInApplicationType),&Z_Copy }, - - // Binary Output cluster - { Zstring, Cx0010, 0x0004, Z(BinaryOutActiveText), &Z_Copy }, - { Zstring, Cx0010, 0x001C, Z(BinaryOutDescription), &Z_Copy }, - { Zstring, Cx0010, 0x002E, Z(BinaryOutInactiveText),&Z_Copy }, - { Zuint32, Cx0010, 0x0042, Z(BinaryOutMinimumOffTime),&Z_Copy }, - { Zuint32, Cx0010, 0x0043, Z(BinaryOutMinimumOnTime),&Z_Copy }, - { Zbool, Cx0010, 0x0051, Z(BinaryOutOutOfService),&Z_Copy }, - { Zenum8, Cx0010, 0x0054, Z(BinaryOutPolarity), &Z_Copy }, - { Zbool, Cx0010, 0x0055, Z(BinaryOutValue), &Z_Copy }, - // { Zunk, Cx0010, 0x0057, Z(BinaryOutPriorityArray),&Z_Copy }, - { Zenum8, Cx0010, 0x0067, Z(BinaryOutReliability), &Z_Copy }, - { Zbool, Cx0010, 0x0068, Z(BinaryOutRelinquishDefault),&Z_Copy }, - { Zmap8, Cx0010, 0x006F, Z(BinaryOutStatusFlags), &Z_Copy }, - { Zuint32, Cx0010, 0x0100, Z(BinaryOutApplicationType),&Z_Copy }, - - // Binary Value cluster - { Zstring, Cx0011, 0x0004, Z(BinaryActiveText), &Z_Copy }, - { Zstring, Cx0011, 0x001C, Z(BinaryDescription), &Z_Copy }, - { Zstring, Cx0011, 0x002E, Z(BinaryInactiveText), &Z_Copy }, - { Zuint32, Cx0011, 0x0042, Z(BinaryMinimumOffTime), &Z_Copy }, - { Zuint32, Cx0011, 0x0043, Z(BinaryMinimumOnTime), &Z_Copy }, - { Zbool, Cx0011, 0x0051, Z(BinaryOutOfService), &Z_Copy }, - { Zbool, Cx0011, 0x0055, Z(BinaryValue), &Z_Copy }, - // { Zunk, Cx0011, 0x0057, Z(BinaryPriorityArray), &Z_Copy }, - { Zenum8, Cx0011, 0x0067, Z(BinaryReliability), &Z_Copy }, - { Zbool, Cx0011, 0x0068, Z(BinaryRelinquishDefault),&Z_Copy }, - { Zmap8, Cx0011, 0x006F, Z(BinaryStatusFlags), &Z_Copy }, - { Zuint32, Cx0011, 0x0100, Z(BinaryApplicationType),&Z_Copy }, - - // Multistate Input cluster - // { Zunk, Cx0012, 0x000E, Z(MultiInStateText), &Z_Copy }, - { Zstring, Cx0012, 0x001C, Z(MultiInDescription), &Z_Copy }, - { Zuint16, Cx0012, 0x004A, Z(MultiInNumberOfStates),&Z_Copy }, - { Zbool, Cx0012, 0x0051, Z(MultiInOutOfService), &Z_Copy }, - { Zuint16, Cx0012, 0x0055, Z(MultiInValue), &Z_AqaraCube }, - { Zenum8, Cx0012, 0x0067, Z(MultiInReliability), &Z_Copy }, - { Zmap8, Cx0012, 0x006F, Z(MultiInStatusFlags), &Z_Copy }, - { Zuint32, Cx0012, 0x0100, Z(MultiInApplicationType),&Z_Copy }, - - // Multistate output - // { Zunk, Cx0013, 0x000E, Z(MultiOutStateText), &Z_Copy }, - { Zstring, Cx0013, 0x001C, Z(MultiOutDescription), &Z_Copy }, - { Zuint16, Cx0013, 0x004A, Z(MultiOutNumberOfStates),&Z_Copy }, - { Zbool, Cx0013, 0x0051, Z(MultiOutOutOfService), &Z_Copy }, - { Zuint16, Cx0013, 0x0055, Z(MultiOutValue), &Z_Copy }, - // { Zunk, Cx0013, 0x0057, Z(MultiOutPriorityArray),&Z_Copy }, - { Zenum8, Cx0013, 0x0067, Z(MultiOutReliability), &Z_Copy }, - { Zuint16, Cx0013, 0x0068, Z(MultiOutRelinquishDefault),&Z_Copy }, - { Zmap8, Cx0013, 0x006F, Z(MultiOutStatusFlags), &Z_Copy }, - { Zuint32, Cx0013, 0x0100, Z(MultiOutApplicationType),&Z_Copy }, - - // Multistate Value cluster - // { Zunk, Cx0014, 0x000E, Z(MultiStateText), &Z_Copy }, - { Zstring, Cx0014, 0x001C, Z(MultiDescription), &Z_Copy }, - { Zuint16, Cx0014, 0x004A, Z(MultiNumberOfStates), &Z_Copy }, - { Zbool, Cx0014, 0x0051, Z(MultiOutOfService), &Z_Copy }, - { Zuint16, Cx0014, 0x0055, Z(MultiValue), &Z_Copy }, - { Zenum8, Cx0014, 0x0067, Z(MultiReliability), &Z_Copy }, - { Zuint16, Cx0014, 0x0068, Z(MultiRelinquishDefault),&Z_Copy }, - { Zmap8, Cx0014, 0x006F, Z(MultiStatusFlags), &Z_Copy }, - { Zuint32, Cx0014, 0x0100, Z(MultiApplicationType), &Z_Copy }, - - // Power Profile cluster - { Zuint8, Cx001A, 0x0000, Z(TotalProfileNum), &Z_Copy }, - { Zbool, Cx001A, 0x0001, Z(MultipleScheduling), &Z_Copy }, - { Zmap8, Cx001A, 0x0002, Z(EnergyFormatting), &Z_Copy }, - { Zbool, Cx001A, 0x0003, Z(EnergyRemote), &Z_Copy }, - { Zmap8, Cx001A, 0x0004, Z(ScheduleMode), &Z_Copy }, - - // Poll Control cluster - { Zuint32, Cx0020, 0x0000, Z(CheckinInterval), &Z_Copy }, - { Zuint32, Cx0020, 0x0001, Z(LongPollInterval), &Z_Copy }, - { Zuint16, Cx0020, 0x0002, Z(ShortPollInterval), &Z_Copy }, - { Zuint16, Cx0020, 0x0003, Z(FastPollTimeout), &Z_Copy }, - { Zuint32, Cx0020, 0x0004, Z(CheckinIntervalMin), &Z_Copy }, - { Zuint32, Cx0020, 0x0005, Z(LongPollIntervalMin), &Z_Copy }, - { Zuint16, Cx0020, 0x0006, Z(FastPollTimeoutMax), &Z_Copy }, - - // Shade Configuration cluster - { Zuint16, Cx0100, 0x0000, Z(PhysicalClosedLimit), &Z_Copy }, - { Zuint8, Cx0100, 0x0001, Z(MotorStepSize), &Z_Copy }, - { Zmap8, Cx0100, 0x0002, Z(Status), &Z_Copy }, - { Zuint16, Cx0100, 0x0010, Z(ClosedLimit), &Z_Copy }, - { Zenum8, Cx0100, 0x0011, Z(Mode), &Z_Copy }, - - // Door Lock cluster - { Zenum8, Cx0101, 0x0000, Z(LockState), &Z_Copy }, - { Zenum8, Cx0101, 0x0001, Z(LockType), &Z_Copy }, - { Zbool, Cx0101, 0x0002, Z(ActuatorEnabled), &Z_Copy }, - { Zenum8, Cx0101, 0x0003, Z(DoorState), &Z_Copy }, - { Zuint32, Cx0101, 0x0004, Z(DoorOpenEvents), &Z_Copy }, - { Zuint32, Cx0101, 0x0005, Z(DoorClosedEvents), &Z_Copy }, - { Zuint16, Cx0101, 0x0006, Z(OpenPeriod), &Z_Copy }, - - // Aqara Lumi Vibration Sensor - { Zuint16, Cx0101, 0x0055, Z(AqaraVibrationMode), &Z_AqaraVibration }, - { Zuint16, Cx0101, 0x0503, Z(AqaraVibrationsOrAngle), &Z_Copy }, - { Zuint32, Cx0101, 0x0505, Z(AqaraVibration505), &Z_Copy }, - { Zuint48, Cx0101, 0x0508, Z(AqaraAccelerometer), &Z_AqaraVibration }, - - // Window Covering cluster - { Zenum8, Cx0102, 0x0000, Z(WindowCoveringType), &Z_Copy }, - { Zuint16, Cx0102, 0x0001, Z(PhysicalClosedLimitLift),&Z_Copy }, - { Zuint16, Cx0102, 0x0002, Z(PhysicalClosedLimitTilt),&Z_Copy }, - { Zuint16, Cx0102, 0x0003, Z(CurrentPositionLift), &Z_Copy }, - { Zuint16, Cx0102, 0x0004, Z(CurrentPositionTilt), &Z_Copy }, - { Zuint16, Cx0102, 0x0005, Z(NumberofActuationsLift),&Z_Copy }, - { Zuint16, Cx0102, 0x0006, Z(NumberofActuationsTilt),&Z_Copy }, - { Zmap8, Cx0102, 0x0007, Z(ConfigStatus), &Z_Copy }, - { Zuint8, Cx0102, 0x0008, Z(CurrentPositionLiftPercentage),&Z_Copy }, - { Zuint8, Cx0102, 0x0009, Z(CurrentPositionTiltPercentage),&Z_Copy }, - { Zuint16, Cx0102, 0x0010, Z(InstalledOpenLimitLift),&Z_Copy }, - { Zuint16, Cx0102, 0x0011, Z(InstalledClosedLimitLift),&Z_Copy }, - { Zuint16, Cx0102, 0x0012, Z(InstalledOpenLimitTilt),&Z_Copy }, - { Zuint16, Cx0102, 0x0013, Z(InstalledClosedLimitTilt),&Z_Copy }, - { Zuint16, Cx0102, 0x0014, Z(VelocityLift), &Z_Copy }, - { Zuint16, Cx0102, 0x0015, Z(AccelerationTimeLift),&Z_Copy }, - { Zuint16, Cx0102, 0x0016, Z(DecelerationTimeLift), &Z_Copy }, - { Zmap8, Cx0102, 0x0017, Z(Mode), &Z_Copy }, - { Zoctstr, Cx0102, 0x0018, Z(IntermediateSetpointsLift),&Z_Copy }, - { Zoctstr, Cx0102, 0x0019, Z(IntermediateSetpointsTilt),&Z_Copy }, - - // Color Control cluster - { Zuint8, Cx0300, 0x0000, Z(Hue), &Z_Copy }, - { Zuint8, Cx0300, 0x0001, Z(Sat), &Z_Copy }, - { Zuint16, Cx0300, 0x0002, Z(RemainingTime), &Z_Copy }, - { Zuint16, Cx0300, 0x0003, Z(X), &Z_Copy }, - { Zuint16, Cx0300, 0x0004, Z(Y), &Z_Copy }, - { Zenum8, Cx0300, 0x0005, Z(DriftCompensation), &Z_Copy }, - { Zstring, Cx0300, 0x0006, Z(CompensationText), &Z_Copy }, - { Zuint16, Cx0300, 0x0007, Z(CT), &Z_Copy }, - { Zenum8, Cx0300, 0x0008, Z(ColorMode), &Z_Copy }, - { Zuint8, Cx0300, 0x0010, Z(NumberOfPrimaries), &Z_Copy }, - { Zuint16, Cx0300, 0x0011, Z(Primary1X), &Z_Copy }, - { Zuint16, Cx0300, 0x0012, Z(Primary1Y), &Z_Copy }, - { Zuint8, Cx0300, 0x0013, Z(Primary1Intensity), &Z_Copy }, - { Zuint16, Cx0300, 0x0015, Z(Primary2X), &Z_Copy }, - { Zuint16, Cx0300, 0x0016, Z(Primary2Y), &Z_Copy }, - { Zuint8, Cx0300, 0x0017, Z(Primary2Intensity), &Z_Copy }, - { Zuint16, Cx0300, 0x0019, Z(Primary3X), &Z_Copy }, - { Zuint16, Cx0300, 0x001A, Z(Primary3Y), &Z_Copy }, - { Zuint8, Cx0300, 0x001B, Z(Primary3Intensity), &Z_Copy }, - { Zuint16, Cx0300, 0x0030, Z(WhitePointX), &Z_Copy }, - { Zuint16, Cx0300, 0x0031, Z(WhitePointY), &Z_Copy }, - { Zuint16, Cx0300, 0x0032, Z(ColorPointRX), &Z_Copy }, - { Zuint16, Cx0300, 0x0033, Z(ColorPointRY), &Z_Copy }, - { Zuint8, Cx0300, 0x0034, Z(ColorPointRIntensity), &Z_Copy }, - { Zuint16, Cx0300, 0x0036, Z(ColorPointGX), &Z_Copy }, - { Zuint16, Cx0300, 0x0037, Z(ColorPointGY), &Z_Copy }, - { Zuint8, Cx0300, 0x0038, Z(ColorPointGIntensity), &Z_Copy }, - { Zuint16, Cx0300, 0x003A, Z(ColorPointBX), &Z_Copy }, - { Zuint16, Cx0300, 0x003B, Z(ColorPointBY), &Z_Copy }, - { Zuint8, Cx0300, 0x003C, Z(ColorPointBIntensity), &Z_Copy }, - - // Illuminance Measurement cluster - { Zuint16, Cx0400, 0x0000, Z(Illuminance), &Z_Copy }, // Illuminance (in Lux) - { Zuint16, Cx0400, 0x0001, Z(IlluminanceMinMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0400, 0x0002, Z(IlluminanceMaxMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0400, 0x0003, Z(IlluminanceTolerance), &Z_Copy }, // - { Zenum8, Cx0400, 0x0004, Z(IlluminanceLightSensorType), &Z_Copy }, // - { Zunk, Cx0400, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - - // Illuminance Level Sensing cluster - { Zenum8, Cx0401, 0x0000, Z(IlluminanceLevelStatus), &Z_Copy }, // Illuminance (in Lux) - { Zenum8, Cx0401, 0x0001, Z(IlluminanceLightSensorType), &Z_Copy }, // LightSensorType - { Zuint16, Cx0401, 0x0010, Z(IlluminanceTargetLevel), &Z_Copy }, // - { Zunk, Cx0401, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - - // Temperature Measurement cluster - { Zint16, Cx0402, 0x0000, Z(Temperature), &Z_FloatDiv100 }, // Temperature - { Zint16, Cx0402, 0x0001, Z(TemperatureMinMeasuredValue), &Z_FloatDiv100 }, // - { Zint16, Cx0402, 0x0002, Z(TemperatureMaxMeasuredValue), &Z_FloatDiv100 }, // - { Zuint16, Cx0402, 0x0003, Z(TemperatureTolerance), &Z_FloatDiv100 }, // - { Zunk, Cx0402, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - - // Pressure Measurement cluster - { Zunk, Cx0403, 0x0000, Z(PressureUnit), &Z_AddPressureUnit }, // Pressure Unit - { Zint16, Cx0403, 0x0000, Z(Pressure), &Z_Copy }, // Pressure - { Zint16, Cx0403, 0x0001, Z(PressureMinMeasuredValue), &Z_Copy }, // - { Zint16, Cx0403, 0x0002, Z(PressureMaxMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0403, 0x0003, Z(PressureTolerance), &Z_Copy }, // - { Zint16, Cx0403, 0x0010, Z(PressureScaledValue), &Z_Copy }, // - { Zint16, Cx0403, 0x0011, Z(PressureMinScaledValue), &Z_Copy }, // - { Zint16, Cx0403, 0x0012, Z(PressureMaxScaledValue), &Z_Copy }, // - { Zuint16, Cx0403, 0x0013, Z(PressureScaledTolerance), &Z_Copy }, // - { Zint8, Cx0403, 0x0014, Z(PressureScale), &Z_Copy }, // - { Zunk, Cx0403, 0xFFFF, nullptr, &Z_Remove }, // Remove all other Pressure values - - // Flow Measurement cluster - { Zuint16, Cx0404, 0x0000, Z(FlowRate), &Z_FloatDiv10 }, // Flow (in m3/h) - { Zuint16, Cx0404, 0x0001, Z(FlowMinMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0404, 0x0002, Z(FlowMaxMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0404, 0x0003, Z(FlowTolerance), &Z_Copy }, // - { Zunk, Cx0404, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - - // Relative Humidity Measurement cluster - { Zuint16, Cx0405, 0x0000, Z(Humidity), &Z_FloatDiv100 }, // Humidity - { Zuint16, Cx0405, 0x0001, Z(HumidityMinMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0405, 0x0002, Z(HumidityMaxMeasuredValue), &Z_Copy }, // - { Zuint16, Cx0405, 0x0003, Z(HumidityTolerance), &Z_Copy }, // - { Zunk, Cx0405, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - - // Occupancy Sensing cluster - { Zmap8, Cx0406, 0x0000, Z(Occupancy), &Z_Copy }, // Occupancy (map8) - { Zenum8, Cx0406, 0x0001, Z(OccupancySensorType), &Z_Copy }, // OccupancySensorType - { Zunk, Cx0406, 0xFFFF, nullptr, &Z_Remove }, // Remove all other values - - // Meter Identification cluster - { Zstring, Cx0B01, 0x0000, Z(CompanyName), &Z_Copy }, - { Zuint16, Cx0B01, 0x0001, Z(MeterTypeID), &Z_Copy }, - { Zuint16, Cx0B01, 0x0004, Z(DataQualityID), &Z_Copy }, - { Zstring, Cx0B01, 0x0005, Z(CustomerName), &Z_Copy }, - { Zoctstr, Cx0B01, 0x0006, Z(Model), &Z_Copy }, - { Zoctstr, Cx0B01, 0x0007, Z(PartNumber), &Z_Copy }, - { Zoctstr, Cx0B01, 0x0008, Z(ProductRevision), &Z_Copy }, - { Zoctstr, Cx0B01, 0x000A, Z(SoftwareRevision), &Z_Copy }, - { Zstring, Cx0B01, 0x000B, Z(UtilityName), &Z_Copy }, - { Zstring, Cx0B01, 0x000C, Z(POD), &Z_Copy }, - { Zint24, Cx0B01, 0x000D, Z(AvailablePower), &Z_Copy }, - { Zint24, Cx0B01, 0x000E, Z(PowerThreshold), &Z_Copy }, - - // Diagnostics cluster - { Zuint16, Cx0B05, 0x0000, Z(NumberOfResets), &Z_Copy }, - { Zuint16, Cx0B05, 0x0001, Z(PersistentMemoryWrites),&Z_Copy }, - { Zuint8, Cx0B05, 0x011C, Z(LastMessageLQI), &Z_Copy }, - { Zuint8, Cx0B05, 0x011D, Z(LastMessageRSSI), &Z_Copy }, - -}; - // ====================================================================== // Record Manuf -int32_t Z_ManufKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_ManufKeepFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; zigbee_devices.setManufId(shortaddr, value.as()); return 1; } // -int32_t Z_ModelKeep(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_ModelKeepFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; zigbee_devices.setModelId(shortaddr, value.as()); return 1; } -// ====================================================================== -// Remove attribute -int32_t Z_Remove(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { - return 1; // remove original key -} - -// Copy value as-is -int32_t Z_Copy(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { - json[new_name] = value; - return 1; // remove original key -} - // Add pressure unit -int32_t Z_AddPressureUnit(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AddPressureUnitFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = F(D_UNIT_PRESSURE); return 0; // keep original key } // Convert int to float and divide by 100 -int32_t Z_FloatDiv100(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv100Func(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 100.0f; return 1; // remove original key } // Convert int to float and divide by 10 -int32_t Z_FloatDiv10(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv10Func(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 10.0f; return 1; // remove original key } // Convert int to float and divide by 10 -int32_t Z_FloatDiv2(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_FloatDiv2Func(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = ((float)value) / 2.0f; return 1; // remove original key } @@ -1089,7 +1217,7 @@ int32_t Z_OccupancyCallback(uint16_t shortaddr, uint16_t groupaddr, uint16_t clu } // Aqara Cube -int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraCubeFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { json[new_name] = value; // copy the original value int32_t val = value; const __FlashStringHelper *aqara_cube = F("AqaraCube"); @@ -1148,7 +1276,7 @@ int32_t Z_AqaraCube(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& j } // Aqara Vibration Sensor - special proprietary attributes -int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraVibrationFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { //json[new_name] = value; switch (attr) { case 0x0055: @@ -1199,7 +1327,7 @@ int32_t Z_AqaraVibration(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObje return 1; // remove original key } -int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { +int32_t Z_AqaraSensorFunc(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, uint16_t cluster, uint16_t attr) { String hex = value; SBuffer buf2 = SBuffer::SBufferFromHex(hex.c_str(), hex.length()); uint32_t i = 0; @@ -1259,6 +1387,50 @@ int32_t Z_AqaraSensor(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& } // ====================================================================== +// apply the transformation from the converter +int32_t Z_ApplyConverter(const class ZCLFrame *zcl, uint16_t shortaddr, JsonObject& json, const char *name, JsonVariant& value, const String &new_name, + uint16_t cluster, uint16_t attr, int16_t multiplier, uint16_t cb) { + // apply multiplier if needed + if (1 == multiplier) { // copy unchanged + json[new_name] = value; + } else if (0 != multiplier) { + if (multiplier > 0) { + json[new_name] = ((float)value) * multiplier; + } else { + json[new_name] = ((float)value) / multiplier; + } + } + + // apply callback if needed + Z_AttrConverter func = nullptr; + switch (cb) { + case Z_Nop: + return 1; // drop original key + case Z_AddPressureUnit: + func = &Z_AddPressureUnitFunc; + break; + case Z_ManufKeep: + func = &Z_ManufKeepFunc; + break; + case Z_ModelKeep: + func = &Z_ModelKeepFunc; + break; + case Z_AqaraSensor: + func = &Z_AqaraSensorFunc; + break; + case Z_AqaraVibration: + func = &Z_AqaraVibrationFunc; + break; + case Z_AqaraCube: + func = &Z_AqaraCubeFunc; + break; + }; + + if (func) { + return (*func)(zcl, shortaddr, json, name, value, new_name, cluster, attr); + } +} + void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { // iterate on json elements for (auto kv : json) { @@ -1320,12 +1492,15 @@ void ZCLFrame::postProcessAttributes(uint16_t shortaddr, JsonObject& json) { const Z_AttributeConverter *converter = &Z_PostProcess[i]; uint16_t conv_cluster = CxToCluster(pgm_read_byte(&converter->cluster_short)); uint16_t conv_attribute = pgm_read_word(&converter->attribute); + int16_t conv_multiplier = pgm_read_word(&converter->multiplier); + uint16_t conv_cb = pgm_read_word(&converter->cb); // callback id if ((conv_cluster == cluster) && ((conv_attribute == attribute) || (conv_attribute == 0xFFFF)) ) { String new_name_str = (const __FlashStringHelper*) converter->name; if (suffix > 1) { new_name_str += suffix; } // append suffix number - int32_t drop = (*converter->func)(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute); + // apply the transformation + int32_t drop = Z_ApplyConverter(this, shortaddr, json, key, value, new_name_str, conv_cluster, conv_attribute, conv_multiplier, conv_cb); if (drop) { json.remove(key); } diff --git a/tasmota/xdrv_23_zigbee_7_statemachine.ino b/tasmota/xdrv_23_zigbee_7_statemachine.ino index 590c795ac..4297a4375 100644 --- a/tasmota/xdrv_23_zigbee_7_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_statemachine.ino @@ -106,10 +106,11 @@ enum Zigbee_StateMachine_Instruction_Set { // Labels used in the State Machine -- internal only const uint8_t ZIGBEE_LABEL_INIT_COORD = 10; // Start ZNP as coordinator const uint8_t ZIGBEE_LABEL_START_COORD = 11; // Start ZNP as coordinator -const uint8_t ZIGBEE_LABEL_INIT_ROUTER = 12; // Start ZNP as router +const uint8_t ZIGBEE_LABEL_INIT_ROUTER = 12; // Init ZNP as router const uint8_t ZIGBEE_LABEL_START_ROUTER = 13; // Start ZNP as router -const uint8_t ZIGBEE_LABEL_INIT_DEVICE = 14; // Start ZNP as end-device -// const uint8_t ZIGBEE_LABEL_START_DEVICE = 15; // Start ZNP as end-device - same as ZIGBEE_LABEL_START_ROUTER +const uint8_t ZIGBEE_LABEL_INIT_DEVICE = 14; // Init ZNP as end-device +const uint8_t ZIGBEE_LABEL_START_DEVICE = 15; // Start ZNP as end-device +const uint8_t ZIGBEE_LABEL_START_ROUTER_DEVICE = 16; // Start common to router and device const uint8_t ZIGBEE_LABEL_FACT_RESET_ROUTER_DEVICE_POST = 19; // common post configuration for router and device const uint8_t ZIGBEE_LABEL_READY = 20; // goto label 20 for main loop const uint8_t ZIGBEE_LABEL_MAIN_LOOP = 21; // main loop @@ -400,7 +401,7 @@ void Z_UpdateConfig(uint8_t zb_channel, uint16_t zb_pan_id, uint64_t zb_ext_pani const char kCheckingDeviceConfiguration[] PROGMEM = D_LOG_ZIGBEE "checking device configuration"; const char kConfiguredCoord[] PROGMEM = "Configured, starting coordinator"; const char kConfiguredRouter[] PROGMEM = "Configured, starting router"; -const char kConfiguredDevice[] PROGMEM = "Configured, starting end-device"; +const char kConfiguredDevice[] PROGMEM = "Configured, starting device"; const char kStarted[] PROGMEM = "Started"; const char kZigbeeStarted[] PROGMEM = D_LOG_ZIGBEE "Zigbee started"; const char kResetting[] PROGMEM = "Resetting configuration"; @@ -426,7 +427,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_WAIT_RECV_FUNC(2000, ZBR_VERSION, &Z_ReceiveCheckVersion) // Check if version is valid // Dispatching whether coordinator, router or end-device - ZI_CALL(&Z_SwitchDeviceType, 0) // goto ZIGBEE_LABEL_START_ROUTER, ZIGBEE_LABEL_START_DEVICE or continue if coordinator + ZI_CALL(&Z_SwitchDeviceType, 0) // goto ZIGBEE_LABEL_INIT_ROUTER, ZIGBEE_LABEL_INIT_DEVICE or continue if coordinator // ====================================================================== // Start as Zigbee Coordinator @@ -537,8 +538,9 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_SEND(ZBS_LOGTYPE) // check the logical type ZI_WAIT_RECV(1000, ZBS_LOGTYPE_ROUTER) // it should be coordinator - ZI_LABEL(ZIGBEE_LABEL_START_ROUTER) // Init as a router + // ZI_LABEL(ZIGBEE_LABEL_START_ROUTER) // Init as a router ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredRouter) + ZI_LABEL(ZIGBEE_LABEL_START_ROUTER_DEVICE) ZI_ON_ERROR_GOTO(ZIGBEE_LABEL_ABORT) ZI_SEND(ZBS_AF_REGISTER_ALL) // Z_AF register for endpoint 01, profile 0x0104 Home Automation ZI_WAIT_RECV(1000, ZBR_AF_REGISTER) @@ -570,7 +572,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_SEND(ZBS_WNV_ZNPHC) // Write NV ZNP Has Configured ZI_WAIT_RECV(1000, ZBR_WNV_OK) - ZI_GOTO(ZIGBEE_LABEL_START_ROUTER) + ZI_GOTO(ZIGBEE_LABEL_START_ROUTER_DEVICE) // ====================================================================== // Start as Zigbee Device @@ -583,7 +585,8 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_SEND(ZBS_LOGTYPE) // check the logical type ZI_WAIT_RECV(1000, ZBS_LOGTYPE_DEVICE) // it should be coordinator - ZI_GOTO(ZIGBEE_LABEL_START_ROUTER) + ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredDevice) + ZI_GOTO(ZIGBEE_LABEL_START_ROUTER_DEVICE) ZI_LABEL(ZIGBEE_LABEL_FACT_RESET_DEVICE) // Factory reset for router ZI_MQTT_STATE(ZIGBEE_STATUS_RESET_CONF, kResetting) diff --git a/tasmota/xdrv_23_zigbee_8_parsers.ino b/tasmota/xdrv_23_zigbee_8_parsers.ino index ffe108c5a..996103fe5 100644 --- a/tasmota/xdrv_23_zigbee_8_parsers.ino +++ b/tasmota/xdrv_23_zigbee_8_parsers.ino @@ -653,22 +653,28 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { JsonObject& json = jsonBuffer.createObject(); if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_DEFAULT_RESPONSE == zcl_received.getCmdId())) { - zcl_received.parseResponse(); + zcl_received.parseResponse(); // Zigbee general "Degault Response", publish ZbResponse message } else { // Build the ZbReceive json if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_REPORT_ATTRIBUTES == zcl_received.getCmdId())) { - zcl_received.parseRawAttributes(json); + zcl_received.parseReportAttributes(json); // Zigbee report attributes from sensors if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES_RESPONSE == zcl_received.getCmdId())) { - zcl_received.parseReadAttributes(json); + zcl_received.parseReadAttributesResponse(json); if (clusterid) { defer_attributes = true; } // don't defer system Cluster=0 messages + } else if ( (!zcl_received.isClusterSpecificCommand()) && (ZCL_READ_ATTRIBUTES == zcl_received.getCmdId())) { + zcl_received.parseReadAttributes(json); + // never defer read_attributes, so the auto-responder can send response back on a per cluster basis } else if (zcl_received.isClusterSpecificCommand()) { zcl_received.parseClusterSpecificCommand(json); } - String msg(""); - msg.reserve(100); - json.printTo(msg); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str()); + + { // fence to force early de-allocation of msg + String msg(""); + msg.reserve(100); + json.printTo(msg); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_ZIGBEE D_JSON_ZIGBEEZCL_RAW_RECEIVED ": {\"0x%04X\":%s}"), srcaddr, msg.c_str()); + } zcl_received.postProcessAttributes(srcaddr, json); // Add Endpoint @@ -698,6 +704,9 @@ int32_t Z_ReceiveAfIncomingMessage(int32_t res, const class SBuffer &buf) { } else { // Publish immediately zigbee_devices.jsonPublishNow(srcaddr, json); + + // Add auto-responder here + Z_AutoResponder(srcaddr, clusterid, srcendpoint, json[F("ReadNames")]); } } return -1; @@ -814,4 +823,74 @@ int32_t Z_State_Ready(uint8_t value) { return 0; // continue } +// +// Auto-responder for Read request from extenal devices. +// +// Mostly used for routers/end-devices +// json: holds the attributes in JSON format +void Z_AutoResponder(uint16_t srcaddr, uint16_t cluster, uint8_t endpoint, const JsonObject &json) { + DynamicJsonBuffer jsonBuffer; + JsonObject& json_out = jsonBuffer.createObject(); + + // responder + switch (cluster) { + case 0x0000: + if (HasKeyCaseInsensitive(json, PSTR("ModelId"))) { json_out[F("ModelId")] = F("Tasmota Z2T"); } + if (HasKeyCaseInsensitive(json, PSTR("Manufacturer"))) { json_out[F("Manufacturer")] = F("Tasmota"); } + break; +#ifdef USE_LIGHT + case 0x0006: + if (HasKeyCaseInsensitive(json, PSTR("Power"))) { json_out[F("Power")] = Light.power ? 1 : 0; } + break; + case 0x0008: + if (HasKeyCaseInsensitive(json, PSTR("Dimmer"))) { json_out[F("Dimmer")] = LightGetDimmer(0); } + break; + case 0x0300: + { + uint16_t hue; + uint8_t sat; + float XY[2]; + LightGetHSB(&hue, &sat, nullptr); + LightGetXY(&XY[0], &XY[1]); + uint16_t uxy[2]; + for (uint32_t i = 0; i < ARRAY_SIZE(XY); i++) { + uxy[i] = XY[i] * 65536.0f; + uxy[i] = (uxy[i] > 0xFEFF) ? uxy[i] : 0xFEFF; + } + if (HasKeyCaseInsensitive(json, PSTR("Hue"))) { json_out[F("Hue")] = changeUIntScale(hue, 0, 360, 0, 254); } + if (HasKeyCaseInsensitive(json, PSTR("Sat"))) { json_out[F("Sat")] = changeUIntScale(sat, 0, 255, 0, 254); } + if (HasKeyCaseInsensitive(json, PSTR("CT"))) { json_out[F("CT")] = LightGetColorTemp(); } + if (HasKeyCaseInsensitive(json, PSTR("X"))) { json_out[F("X")] = uxy[0]; } + if (HasKeyCaseInsensitive(json, PSTR("Y"))) { json_out[F("Y")] = uxy[1]; } + } + break; +#endif + case 0x000A: // Time + if (HasKeyCaseInsensitive(json, PSTR("Time"))) { json_out[F("Time")] = Rtc.utc_time; } + if (HasKeyCaseInsensitive(json, PSTR("TimeStatus"))) { json_out[F("TimeStatus")] = (Rtc.utc_time > (60 * 60 * 24 * 365 * 10)) ? 0x02 : 0x00; } // if time is beyond 2010 then we are synchronized + if (HasKeyCaseInsensitive(json, PSTR("TimeZone"))) { json_out[F("TimeZone")] = Settings.toffset[0] * 60; } // seconds + break; + } + + if (json_out.size() > 0) { + // we have a non-empty output + + // log first + String msg(""); + msg.reserve(100); + json_out.printTo(msg); + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Auto-responder: ZbSend {\"Device\":\"0x%04X\"" + ",\"Cluster\":\"0x%04X\"" + ",\"Endpoint\":%d" + ",\"Response\":%s}" + ), + srcaddr, cluster, endpoint, + msg.c_str()); + + // send + const JsonVariant &json_out_v = json_out; + ZbSendReportWrite(json_out_v, srcaddr, 0 /* group */,cluster, endpoint, 0 /* manuf */, ZCL_READ_ATTRIBUTES_RESPONSE); + } +} + #endif // USE_ZIGBEE diff --git a/tasmota/xdrv_23_zigbee_9_impl.ino b/tasmota/xdrv_23_zigbee_9_impl.ino index 5c5181b6d..0ea39c194 100644 --- a/tasmota/xdrv_23_zigbee_9_impl.ino +++ b/tasmota/xdrv_23_zigbee_9_impl.ino @@ -32,7 +32,7 @@ TasmotaSerial *ZigbeeSerial = nullptr; const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix D_CMND_ZIGBEEZNPSEND "|" D_CMND_ZIGBEE_PERMITJOIN "|" D_CMND_ZIGBEE_STATUS "|" D_CMND_ZIGBEE_RESET "|" D_CMND_ZIGBEE_SEND "|" - D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEE_READ "|" D_CMND_ZIGBEEZNPRECEIVE "|" + D_CMND_ZIGBEE_PROBE "|" D_CMND_ZIGBEEZNPRECEIVE "|" D_CMND_ZIGBEE_FORGET "|" D_CMND_ZIGBEE_SAVE "|" D_CMND_ZIGBEE_NAME "|" D_CMND_ZIGBEE_BIND "|" D_CMND_ZIGBEE_UNBIND "|" D_CMND_ZIGBEE_PING "|" D_CMND_ZIGBEE_MODELID "|" D_CMND_ZIGBEE_LIGHT "|" D_CMND_ZIGBEE_RESTORE "|" D_CMND_ZIGBEE_BIND_STATE "|" @@ -42,7 +42,7 @@ const char kZbCommands[] PROGMEM = D_PRFX_ZB "|" // prefix void (* const ZigbeeCommand[])(void) PROGMEM = { &CmndZbZNPSend, &CmndZbPermitJoin, &CmndZbStatus, &CmndZbReset, &CmndZbSend, - &CmndZbProbe, &CmndZbRead, &CmndZbZNPReceive, + &CmndZbProbe, &CmndZbZNPReceive, &CmndZbForget, &CmndZbSave, &CmndZbName, &CmndZbBind, &CmndZbUnbind, &CmndZbPing, &CmndZbModelId, &CmndZbLight, &CmndZbRestore, &CmndZbBindState, @@ -393,9 +393,345 @@ void zigbeeZCLSendStr(uint16_t shortaddr, uint16_t groupaddr, uint8_t endpoint, } } +// Parse "Report", "Write" or "Response" attribute +// Operation is one of: ZCL_REPORT_ATTRIBUTES (0x0A), ZCL_WRITE_ATTRIBUTES (0x02) or ZCL_READ_ATTRIBUTES_RESPONSE (0x01) +void ZbSendReportWrite(const JsonObject &val_pubwrite, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf, uint32_t operation) { + SBuffer buf(200); // buffer to store the binary output of attibutes + + if (nullptr == XdrvMailbox.command) { + XdrvMailbox.command = (char*) ""; // prevent a crash when calling ReponseCmndChar and there was no previous command + } + + // iterate on keys + for (JsonObject::const_iterator it=val_pubwrite.begin(); it!=val_pubwrite.end(); ++it) { + const char *key = it->key; + const JsonVariant &value = it->value; + + uint16_t attr_id = 0xFFFF; + uint16_t cluster_id = 0xFFFF; + uint8_t type_id = Znodata; + int16_t multiplier = 1; // multiplier to adjust the key value + float val_f = 0.0f; // alternative value if multiplier is used + + // check if the name has the format "XXXX/YYYY" where XXXX is the cluster, YYYY the attribute id + // alternative "XXXX/YYYY%ZZ" where ZZ is the type (for unregistered attributes) + char * delimiter = strchr(key, '/'); + char * delimiter2 = strchr(key, '%'); + if (delimiter) { + cluster_id = strtoul(key, &delimiter, 16); + if (!delimiter2) { + attr_id = strtoul(delimiter+1, nullptr, 16); + } else { + attr_id = strtoul(delimiter+1, &delimiter2, 16); + type_id = strtoul(delimiter2+1, nullptr, 16); + } + } + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X"), cluster_id, attr_id); + + // do we already know the type, i.e. attribute and cluster are also known + if (Znodata == type_id) { + // scan attributes to find by name, and retrieve type + for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { + const Z_AttributeConverter *converter = &Z_PostProcess[i]; + bool match = false; + uint16_t local_attr_id = pgm_read_word(&converter->attribute); + uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short)); + uint8_t local_type_id = pgm_read_byte(&converter->type); + int16_t local_multiplier = pgm_read_word(&converter->multiplier); + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Try cluster = 0x%04X, attr = 0x%04X, type_id = 0x%02X"), local_cluster_id, local_attr_id, local_type_id); + + if (delimiter) { + if ((cluster_id == local_cluster_id) && (attr_id == local_attr_id)) { + type_id = local_type_id; + break; + } + } else if (converter->name) { + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("Comparing '%s' with '%s'"), attr_name, converter->name); + if (0 == strcasecmp_P(key, converter->name)) { + // match + cluster_id = local_cluster_id; + attr_id = local_attr_id; + type_id = local_type_id; + multiplier = local_multiplier; + break; + } + } + } + } + + // Buffer ready, do some sanity checks + // AddLog_P2(LOG_LEVEL_DEBUG, PSTR("cluster_id = 0x%04X, attr_id = 0x%04X, type_id = 0x%02X"), cluster_id, attr_id, type_id); + if ((0xFFFF == attr_id) || (0xFFFF == cluster_id)) { + Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute "), key); + return; + } + if (Znodata == type_id) { + Response_P(PSTR("{\"%s\":\"%s'%s'\"}"), XdrvMailbox.command, PSTR("Unknown attribute type for attribute "), key); + return; + } + + if (0xFFFF == cluster) { + cluster = cluster_id; // set the cluster for this packet + } else if (cluster != cluster_id) { + ResponseCmndChar_P(PSTR("No more than one cluster id per command")); + return; + } + // apply multiplier if needed + bool use_val = true; + if ((0 != multiplier) && (1 != multiplier)) { + val_f = value; + if (multiplier > 0) { // inverse of decoding + val_f = val_f / multiplier; + } else { + val_f = val_f * multiplier; + } + use_val = false; + } + // push the value in the buffer + int32_t res = encodeSingleAttribute(buf, use_val ? value : *(const JsonVariant*)nullptr, val_f, attr_id, type_id, operation == ZCL_READ_ATTRIBUTES_RESPONSE); // force status if Reponse + if (res < 0) { + Response_P(PSTR("{\"%s\":\"%s'%s' 0x%02X\"}"), XdrvMailbox.command, PSTR("Unsupported attribute type "), key, type_id); + return; + } + } + + // did we have any attribute? + if (0 == buf.len()) { + ResponseCmndChar_P(PSTR("No attribute in list")); + return; + } + + // all good, send the packet + ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, operation, false /* not cluster specific */, manuf, buf.getBuffer(), buf.len(), false /* noresponse */, zigbee_devices.getNextSeqNumber(device)); + ResponseCmndDone(); +} + +// Parse the "Send" attribute and send the command +void ZbSendSend(const JsonVariant &val_cmd, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) { + uint8_t cmd = 0; + String cmd_str = ""; // the actual low-level command, either specified or computed + const char *cmd_s; // pointer to payload string + bool clusterSpecific = true; + + static 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 + if (val_cmd.is()) { + // we have a high-level command + const JsonObject &cmd_obj = val_cmd.as(); + int32_t cmd_size = cmd_obj.size(); + if (cmd_size > 1) { + Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size); + return; + } else if (1 == cmd_size) { + // We have exactly 1 command, parse it + JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value + String key = it->key; + const JsonVariant& value = it->value; + uint32_t x = 0, y = 0, z = 0; + uint16_t cmd_var; + uint16_t local_cluster_id; + + const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &local_cluster_id, &cmd_var); + if (tasmota_cmd) { + cmd_str = tasmota_cmd; + } else { + Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str()); + return; + } + // check cluster + if (0xFFFF == cluster) { + cluster = local_cluster_id; + } else if (cluster != local_cluster_id) { + ResponseCmndChar_P(PSTR("No more than one cluster id per command")); + return; + } + + // parse the JSON value, depending on its type fill in x,y,z + if (value.is()) { + x = value.as() ? 1 : 0; + } else if (value.is()) { + x = value.as(); + } else { + // if non-bool or non-int, trying char* + const char *s_const = value.as(); + if (s_const != nullptr) { + char s[strlen(s_const)+1]; + strcpy(s, s_const); + if ((nullptr != s) && (0x00 != *s)) { // ignore any null or empty string, could represent 'null' json value + char *sval = strtok(s, delim); + if (sval) { + x = ZigbeeAliasOrNumber(sval); + sval = strtok(nullptr, delim); + if (sval) { + y = ZigbeeAliasOrNumber(sval); + sval = strtok(nullptr, delim); + if (sval) { + z = ZigbeeAliasOrNumber(sval); + } + } + } + } + } + } + + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str()); + if (0xFF == cmd_var) { // if command number is a variable, replace it with x + cmd = x; + x = y; // and shift other variables + y = z; + } else { + cmd = cmd_var; // or simply copy the cmd number + } + cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters + //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str()); + cmd_s = cmd_str.c_str(); + } else { + // we have zero command, pass through until last error for missing command + } + } else if (val_cmd.is()) { + // low-level command + cmd_str = val_cmd.as(); + // Now parse the string to extract cluster, command, and payload + // Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC" + // where AA is the cluster number, BBBB the command number, CCCC... the payload + // First delimiter is '_' for a global command, or '!' for a cluster specific command + const char * data = cmd_str.c_str(); + uint16_t local_cluster_id = parseHex(&data, 4); + + // check cluster + if (0xFFFF == cluster) { + cluster = local_cluster_id; + } else if (cluster != local_cluster_id) { + ResponseCmndChar_P(PSTR("No more than one cluster id per command")); + return; + } + + // delimiter + if (('_' == *data) || ('!' == *data)) { + if ('_' == *data) { clusterSpecific = false; } + data++; + } else { + ResponseCmndChar_P(PSTR("Wrong delimiter for payload")); + return; + } + // parse cmd number + cmd = parseHex(&data, 2); + + // move to end of payload + // delimiter is optional + if ('/' == *data) { data++; } // skip delimiter + + cmd_s = data; + } else { + // we have an unsupported command type, just ignore it and fallback to missing command + } + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%s\""), + device, groupaddr, endpoint, cluster, cmd, cmd_s); + zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s); + ResponseCmndDone(); +} + + +// Parse the "Send" attribute and send the command +void ZbSendRead(const JsonVariant &val_attr, uint16_t device, uint16_t groupaddr, uint16_t cluster, uint8_t endpoint, uint16_t manuf) { + // ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":5} + // ZbSend {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Read":"0x0005"} + // ZbSend {"Device":"0xF289","Cluster":0,"Endpoint":3,"Read":[5,6,7,4]} + // ZbSend {"Device":"0xF289","Endpoint":3,"Read":{"ModelId":true}} + // ZbSend {"Device":"0xF289","Read":{"ModelId":true}} + + // params + size_t attrs_len = 0; + uint8_t* attrs = nullptr; // empty string is valid + + uint16_t val = strToUInt(val_attr); + if (val_attr.is()) { + const JsonArray& attr_arr = val_attr.as(); + attrs_len = attr_arr.size() * 2; + attrs = new uint8_t[attrs_len]; + + uint32_t i = 0; + for (auto value : attr_arr) { + uint16_t val = strToUInt(value); + attrs[i++] = val & 0xFF; + attrs[i++] = val >> 8; + } + } else if (val_attr.is()) { + const JsonObject& attr_obj = val_attr.as(); + attrs_len = attr_obj.size() * 2; + attrs = new uint8_t[attrs_len]; + uint32_t actual_attr_len = 0; + + // iterate on keys + for (JsonObject::const_iterator it=attr_obj.begin(); it!=attr_obj.end(); ++it) { + const char *key = it->key; + // const JsonVariant &value = it->value; // we don't need the value here, only keys are relevant + + bool found = false; + // scan attributes to find by name, and retrieve type + for (uint32_t i = 0; i < ARRAY_SIZE(Z_PostProcess); i++) { + const Z_AttributeConverter *converter = &Z_PostProcess[i]; + bool match = false; + uint16_t local_attr_id = pgm_read_word(&converter->attribute); + uint16_t local_cluster_id = CxToCluster(pgm_read_byte(&converter->cluster_short)); + // uint8_t local_type_id = pgm_read_byte(&converter->type); + + if ((converter->name) && (0 == strcasecmp_P(key, converter->name))) { + // match name + // check if there is a conflict with cluster + // TODO + attrs[actual_attr_len++] = local_attr_id & 0xFF; + attrs[actual_attr_len++] = local_attr_id >> 8; + found = true; + // check cluster + if (0xFFFF == cluster) { + cluster = local_cluster_id; + } else if (cluster != local_cluster_id) { + ResponseCmndChar_P(PSTR("No more than one cluster id per command")); + if (attrs) { delete[] attrs; } + return; + } + break; // found, exit loop + } + } + if (!found) { + AddLog_P2(LOG_LEVEL_INFO, PSTR("ZIG: Unknown attribute name (ignored): %s"), key); + } + } + + attrs_len = actual_attr_len; + } else { + attrs_len = 2; + attrs = new uint8_t[attrs_len]; + attrs[0] = val & 0xFF; // little endian + attrs[1] = val >> 8; + } + + if (attrs_len > 0) { + ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device)); + ResponseCmndDone(); + } else { + ResponseCmndChar_P(PSTR("Missing parameters")); + } + + if (attrs) { delete[] attrs; } +} + // // Command `ZbSend` // +// Examples: +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"0006/0000":0}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"Power":0}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AqaraRotate":0}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AqaraRotate":12.5}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"006/0000%39":12.5}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"AnalogInApplicationType":1000000}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"TimeZone":-1000000}} +// ZbSend {"Device":"0x0000","Endpoint":1,"Write":{"Manufacturer":"Tasmota","ModelId":"Tasmota Z2T Router"}} void CmndZbSend(void) { // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"3"} } @@ -405,7 +741,7 @@ void CmndZbSend(void) { // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":true} } // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":"true"} } // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"ShutterClose":null} } - // ZbSend { "devicse":"0x1234", "endpoint":"0x03", "send":{"Power":1} } + // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Power":1} } // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"1,2"} } // ZbSend { "device":"0x1234", "endpoint":"0x03", "send":{"Color":"0x1122,0xFFEE"} } if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } @@ -414,26 +750,21 @@ void CmndZbSend(void) { if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params - static char delim[] = ", "; // delimiters for parameters - uint16_t device = BAD_SHORTADDR; // 0x0000 is local, so considered invalid - uint16_t groupaddr = 0x0000; // group address - uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint - uint16_t manuf = 0x0000; // Manuf Id in ZCL frame - // Command elements - uint16_t cluster = 0; - uint8_t cmd = 0; - String cmd_str = ""; // the actual low-level command, either specified or computed - const char *cmd_s; // pointer to payload string - bool clusterSpecific = true; + uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid + uint16_t groupaddr = 0x0000; // group address valid only if device == BAD_SHORTADDR + uint16_t cluster = 0xFFFF; // no default + uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint + uint16_t manuf = 0x0000; // Manuf Id in ZCL frame - // parse JSON - const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device")); + + // parse "Device" and "Group" + const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE)); if (nullptr != &val_device) { device = zigbee_devices.parseDeviceParam(val_device.as()); if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } } if (BAD_SHORTADDR == device) { // if not found, check if we have a group - const JsonVariant &val_group = GetCaseInsensitive(json, PSTR("Group")); + const JsonVariant &val_group = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_GROUP)); if (nullptr != &val_group) { groupaddr = strToUInt(val_group); } else { // no device nor group @@ -441,117 +772,77 @@ void CmndZbSend(void) { return; } } + // from here, either device has a device shortaddr, or if BAD_SHORTADDR then use group address + // Note: groupaddr == 0 is valid - const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint")); + // read other parameters + const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER)); + if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } + const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT)); if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } - const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR("Manuf")); + const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_MANUF)); if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); } - const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR("Send")); + + // infer endpoint + if (BAD_SHORTADDR == device) { + endpoint = 0xFF; // endpoint not used for group addresses, so use a dummy broadcast endpoint + } else if (0 == endpoint) { // if it was not already specified, try to guess it + endpoint = zigbee_devices.findFirstEndpoint(device); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZIG: guessing endpoint %d"), endpoint); + } + if (0 == endpoint) { // after this, if it is still zero, then it's an error + ResponseCmndChar_P(PSTR("Missing endpoint")); + return; + } + // from here endpoint is valid and non-zero + // cluster may be already specified or 0xFFFF + + const JsonVariant &val_cmd = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_SEND)); + const JsonVariant &val_read = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_READ)); + const JsonVariant &val_write = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_WRITE)); + const JsonVariant &val_publish = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_REPORT)); + const JsonVariant &val_response = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_RESPONSE)); + uint32_t multi_cmd = (nullptr != &val_cmd) + (nullptr != &val_read) + (nullptr != &val_write) + (nullptr != &val_publish)+ (nullptr != &val_response); + if (multi_cmd > 1) { + ResponseCmndChar_P(PSTR("Can only have one of: 'Send', 'Read', 'Write', 'Report' or 'Reponse'")); + return; + } + // from here we have one and only one command + if (nullptr != &val_cmd) { - // probe the type of the argument - // If JSON object, it's high level commands - // If String, it's a low level command - if (val_cmd.is()) { - // we have a high-level command - const JsonObject &cmd_obj = val_cmd.as(); - int32_t cmd_size = cmd_obj.size(); - if (cmd_size > 1) { - Response_P(PSTR("Only 1 command allowed (%d)"), cmd_size); - return; - } else if (1 == cmd_size) { - // We have exactly 1 command, parse it - JsonObject::const_iterator it = cmd_obj.begin(); // just get the first key/value - String key = it->key; - const JsonVariant& value = it->value; - uint32_t x = 0, y = 0, z = 0; - uint16_t cmd_var; - - const __FlashStringHelper* tasmota_cmd = zigbeeFindCommand(key.c_str(), &cluster, &cmd_var); - if (tasmota_cmd) { - cmd_str = tasmota_cmd; - } else { - Response_P(PSTR("Unrecognized zigbee command: %s"), key.c_str()); - return; - } - - // parse the JSON value, depending on its type fill in x,y,z - if (value.is()) { - x = value.as() ? 1 : 0; - } else if (value.is()) { - x = value.as(); - } else { - // if non-bool or non-int, trying char* - const char *s_const = value.as(); - if (s_const != nullptr) { - char s[strlen(s_const)+1]; - strcpy(s, s_const); - if ((nullptr != s) && (0x00 != *s)) { // ignore any null or empty string, could represent 'null' json value - char *sval = strtok(s, delim); - if (sval) { - x = ZigbeeAliasOrNumber(sval); - sval = strtok(nullptr, delim); - if (sval) { - y = ZigbeeAliasOrNumber(sval); - sval = strtok(nullptr, delim); - if (sval) { - z = ZigbeeAliasOrNumber(sval); - } - } - } - } - } - } - - //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_template = %s"), cmd_str.c_str()); - if (0xFF == cmd_var) { // if command number is a variable, replace it with x - cmd = x; - x = y; // and shift other variables - y = z; - } else { - cmd = cmd_var; // or simply copy the cmd number - } - cmd_str = zigbeeCmdAddParams(cmd_str.c_str(), x, y, z); // fill in parameters - //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbSend: command_final = %s"), cmd_str.c_str()); - cmd_s = cmd_str.c_str(); - } else { - // we have zero command, pass through until last error for missing command - } - } else if (val_cmd.is()) { - // low-level command - cmd_str = val_cmd.as(); - // Now parse the string to extract cluster, command, and payload - // Parse 'cmd' in the form "AAAA_BB/CCCCCCCC" or "AAAA!BB/CCCCCCCC" - // where AA is the cluster number, BBBB the command number, CCCC... the payload - // First delimiter is '_' for a global command, or '!' for a cluster specific command - const char * data = cmd_str.c_str(); - cluster = parseHex(&data, 4); - - // delimiter - if (('_' == *data) || ('!' == *data)) { - if ('_' == *data) { clusterSpecific = false; } - data++; - } else { - ResponseCmndChar_P(PSTR("Wrong delimiter for payload")); - return; - } - // parse cmd number - cmd = parseHex(&data, 2); - - // move to end of payload - // delimiter is optional - if ('/' == *data) { data++; } // skip delimiter - - cmd_s = data; - } else { - // we have an unsupported command type, just ignore it and fallback to missing command + // "Send":{...commands...} + // we accept either a string or a JSON object + ZbSendSend(val_cmd, device, groupaddr, cluster, endpoint, manuf); + } else if (nullptr != &val_read) { + // "Read":{...attributes...}, "Read":attribute or "Read":[...attributes...] + // we accept eitehr a number, a string, an array of numbers/strings, or a JSON object + ZbSendRead(val_read, device, groupaddr, cluster, endpoint, manuf); + } else if (nullptr != &val_write) { + // only KSON object + if (!val_write.is()) { + ResponseCmndChar_P(PSTR("Missing parameters")); + return; } - - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZigbeeZCLSend device: 0x%04X, group: 0x%04X, endpoint:%d, cluster:0x%04X, cmd:0x%02X, send:\"%s\""), - device, groupaddr, endpoint, cluster, cmd, cmd_s); - zigbeeZCLSendStr(device, groupaddr, endpoint, clusterSpecific, manuf, cluster, cmd, cmd_s); - ResponseCmndDone(); + // "Write":{...attributes...} + ZbSendReportWrite(val_write, device, groupaddr, cluster, endpoint, manuf, ZCL_WRITE_ATTRIBUTES); + } else if (nullptr != &val_publish) { + // "Report":{...attributes...} + // only KSON object + if (!val_publish.is()) { + ResponseCmndChar_P(PSTR("Missing parameters")); + return; + } + ZbSendReportWrite(val_publish, device, groupaddr, cluster, endpoint, manuf, ZCL_REPORT_ATTRIBUTES); + } else if (nullptr != &val_response) { + // "Report":{...attributes...} + // only KSON object + if (!val_response.is()) { + ResponseCmndChar_P(PSTR("Missing parameters")); + return; + } + ZbSendReportWrite(val_response, device, groupaddr, cluster, endpoint, manuf, ZCL_READ_ATTRIBUTES_RESPONSE); } else { - Response_P(PSTR("Missing zigbee 'Send'")); + Response_P(PSTR("Missing zigbee 'Send', 'Write', 'Report' or 'Response'")); return; } } @@ -570,7 +861,6 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } // params - // static char delim[] = ", "; // delimiters for parameters uint16_t srcDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid uint16_t dstDevice = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid uint64_t dstLongAddr = 0; @@ -582,7 +872,7 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind // Information about source device: "Device", "Endpoint", "Cluster" // - the source endpoint must have a known IEEE address - const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device")); + const JsonVariant &val_device = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_DEVICE)); if (nullptr != &val_device) { srcDevice = zigbee_devices.parseDeviceParam(val_device.as()); } @@ -591,10 +881,10 @@ void ZbBindUnbind(bool unbind) { // false = bind, true = unbind uint64_t srcLongAddr = zigbee_devices.getDeviceLongAddr(srcDevice); if (0 == srcLongAddr) { ResponseCmndChar_P(PSTR("Unknown source IEEE address")); return; } // look for source endpoint - const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint")); + const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_ENDPOINT)); if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } // look for source cluster - const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR("Cluster")); + const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR(D_CMND_ZIGBEE_CLUSTER)); if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } // Either Device address @@ -885,90 +1175,6 @@ void CmndZbRestore(void) { ResponseCmndDone(); } -// -// Command `ZbRead` -// Send an attribute read command to a device, specifying cluster and list of attributes -// -void CmndZbRead(void) { - // ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":5} - // ZbRead {"Device":"0xF289","Cluster":"0x0000","Endpoint":"0x0003","Attr":"0x0005"} - // ZbRead {"Device":"0xF289","Cluster":0,"Endpoint":3,"Attr":[5,6,7,4]} - if (zigbee.init_phase) { ResponseCmndChar_P(PSTR(D_ZIGBEE_NOT_STARTED)); return; } - DynamicJsonBuffer jsonBuf; - JsonObject &json = jsonBuf.parseObject((const char*) XdrvMailbox.data); - if (!json.success()) { ResponseCmndChar_P(PSTR(D_JSON_INVALID_JSON)); return; } - - // params - uint16_t device = BAD_SHORTADDR; // BAD_SHORTADDR is broadcast, so considered invalid - uint16_t groupaddr = 0x0000; // if 0x0000 ignore group adress - uint16_t cluster = 0x0000; // default to general cluster - uint8_t endpoint = 0x00; // 0x00 is invalid for the dst endpoint - uint16_t manuf = 0x0000; // Manuf Id in ZCL frame - size_t attrs_len = 0; - uint8_t* attrs = nullptr; // empty string is valid - - const JsonVariant &val_device = GetCaseInsensitive(json, PSTR("Device")); - if (nullptr != &val_device) { - device = zigbee_devices.parseDeviceParam(val_device.as()); - if (BAD_SHORTADDR == device) { ResponseCmndChar_P(PSTR("Invalid parameter")); return; } - } - if (BAD_SHORTADDR == device) { // if not found, check if we have a group - const JsonVariant &val_group = GetCaseInsensitive(json, PSTR("Group")); - if (nullptr != &val_group) { - groupaddr = strToUInt(val_group); - } else { // no device nor group - ResponseCmndChar_P(PSTR("Unknown device")); - return; - } - } - - const JsonVariant &val_cluster = GetCaseInsensitive(json, PSTR("Cluster")); - if (nullptr != &val_cluster) { cluster = strToUInt(val_cluster); } - const JsonVariant &val_endpoint = GetCaseInsensitive(json, PSTR("Endpoint")); - if (nullptr != &val_endpoint) { endpoint = strToUInt(val_endpoint); } - const JsonVariant &val_manuf = GetCaseInsensitive(json, PSTR("Manuf")); - if (nullptr != &val_manuf) { manuf = strToUInt(val_manuf); } - - const JsonVariant &val_attr = GetCaseInsensitive(json, PSTR("Read")); - if (nullptr != &val_attr) { - uint16_t val = strToUInt(val_attr); - if (val_attr.is()) { - const JsonArray& attr_arr = val_attr.as(); - attrs_len = attr_arr.size() * 2; - attrs = new uint8_t[attrs_len]; - - uint32_t i = 0; - for (auto value : attr_arr) { - uint16_t val = strToUInt(value); - attrs[i++] = val & 0xFF; - attrs[i++] = val >> 8; - } - } else { - attrs_len = 2; - attrs = new uint8_t[attrs_len]; - attrs[0] = val & 0xFF; // little endian - attrs[1] = val >> 8; - } - } - - if ((0 == endpoint) && (device)) { // try to compute the endpoint - endpoint = zigbee_devices.findFirstEndpoint(device); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ZbRead: guessing endpoint 0x%02X"), endpoint); - } - if (BAD_SHORTADDR == device) { - endpoint = 0xFF; // endpoint not used for group addresses - } - - if ((0 != endpoint) && (attrs_len > 0)) { - ZigbeeZCLSend_Raw(device, groupaddr, cluster, endpoint, ZCL_READ_ATTRIBUTES, false, manuf, attrs, attrs_len, true /* we do want a response */, zigbee_devices.getNextSeqNumber(device)); - ResponseCmndDone(); - } else { - ResponseCmndChar_P(PSTR("Missing parameters")); - } - - if (attrs) { delete[] attrs; } -} - // // Command `ZbPermitJoin` // Allow or Deny pairing of new Zigbee devices diff --git a/tasmota/xdsp_08_ILI9488.ino b/tasmota/xdsp_08_ILI9488.ino index 1dc877e18..e49e7bffe 100644 --- a/tasmota/xdsp_08_ILI9488.ino +++ b/tasmota/xdsp_08_ILI9488.ino @@ -84,18 +84,33 @@ void ILI9488_InitDriver() bppin=Pin(GPIO_BACKLIGHT); } - // init renderer - if (PinUsed(GPIO_SSPI_CS) && PinUsed(GPIO_SSPI_MOSI) && PinUsed(GPIO_SSPI_SCLK)) { - ili9488 = new ILI9488(Pin(GPIO_SSPI_CS),Pin(GPIO_SSPI_MOSI),Pin(GPIO_SSPI_SCLK),bppin); +#ifdef ESP32 +#undef HW_SPI_MOSI +#define HW_SPI_MOSI 23 +#undef HW_SPI_MISO +#define HW_SPI_MISO 19 +#undef HW_SPI_CLK +#define HW_SPI_CLK 18 +#else +#undef HW_SPI_MOSI +#define HW_SPI_MOSI 13 +#undef HW_SPI_MISO +#define HW_SPI_MISO 12 +#undef HW_SPI_CLK +#define HW_SPI_CLK 14 +#endif + + // init renderer, must use hardware spi + if (PinUsed(GPIO_SSPI_CS) && (Pin(GPIO_SSPI_MOSI)==HW_SPI_MOSI) && (Pin(GPIO_SSPI_SCLK)==HW_SPI_CLK)) { + ili9488 = new ILI9488(Pin(GPIO_SSPI_CS),Pin(GPIO_SSPI_MOSI),Pin(GPIO_SSPI_SCLK),bppin); } else { - if (PinUsed(GPIO_SPI_CS) && PinUsed(GPIO_SPI_MOSI) && PinUsed(GPIO_SPI_CLK)) { + if (PinUsed(GPIO_SPI_CS) && (Pin(GPIO_SPI_MOSI)==HW_SPI_MOSI) && (Pin(GPIO_SPI_CLK)==HW_SPI_CLK)) { ili9488 = new ILI9488(Pin(GPIO_SPI_CS),Pin(GPIO_SPI_MOSI),Pin(GPIO_SPI_CLK),bppin); } else { return; } } - SPI.begin(); ili9488->begin(); renderer = ili9488; renderer->DisplayInit(DISPLAY_INIT_MODE,Settings.display_size,Settings.display_rotate,Settings.display_font); diff --git a/tasmota/xdsp_10_RA8876.ino b/tasmota/xdsp_10_RA8876.ino index 6a44708cb..aa8e82f4d 100644 --- a/tasmota/xdsp_10_RA8876.ino +++ b/tasmota/xdsp_10_RA8876.ino @@ -72,8 +72,11 @@ void RA8876_InitDriver() bg_color = RA8876_BLACK; #ifdef ESP32 +#undef HW_SPI_MOSI #define HW_SPI_MOSI 23 +#undef HW_SPI_MISO #define HW_SPI_MISO 19 +#undef HW_SPI_CLK #define HW_SPI_CLK 18 #else #undef HW_SPI_MOSI diff --git a/tasmota/xsns_10_bh1750.ino b/tasmota/xsns_10_bh1750.ino index a098166df..2c3c801a7 100644 --- a/tasmota/xsns_10_bh1750.ino +++ b/tasmota/xsns_10_bh1750.ino @@ -22,6 +22,11 @@ /*********************************************************************************************\ * BH1750 - Ambient Light Intensity * + * Bh1750Resolution1 0..2 - Set BH1750 1 resolution mode + * Bh1750Resolution2 0..2 - Set BH1750 2 resolution mode + * Bh1750MTime1 30..255 - Set BH1750 1 MT register + * Bh1750MTime2 30..255 - Set BH1750 2 MT register + * * I2C Address: 0x23 or 0x5C \*********************************************************************************************/ @@ -38,119 +43,148 @@ #define BH1750_MEASUREMENT_TIME_HIGH 0x40 // Measurement Time register high 3 bits #define BH1750_MEASUREMENT_TIME_LOW 0x60 // Measurement Time register low 5 bits -struct BH1750DATA { - uint8_t address; +#define D_PRFX_BH1750 "Bh1750" +#define D_CMND_RESOLUTION "Resolution" +#define D_CMND_MTREG "MTime" + +const char kBh1750Commands[] PROGMEM = D_PRFX_BH1750 "|" // Prefix + D_CMND_RESOLUTION "|" D_CMND_MTREG ; + +void (* const Bh1750Command[])(void) PROGMEM = { + &CmndBh1750Resolution, &CmndBh1750MTime }; + +struct { uint8_t addresses[2] = { BH1750_ADDR1, BH1750_ADDR2 }; uint8_t resolution[3] = { BH1750_CONTINUOUS_HIGH_RES_MODE, BH1750_CONTINUOUS_HIGH_RES_MODE2, BH1750_CONTINUOUS_LOW_RES_MODE }; - uint8_t type = 0; - uint8_t valid = 0; - uint8_t mtreg = 69; // Default Measurement Time - uint16_t illuminance = 0; + uint8_t count = 0; char types[7] = "BH1750"; } Bh1750; +struct { + uint8_t address; + uint8_t valid = 0; + uint8_t mtreg = 69; // Default Measurement Time + uint16_t illuminance = 0; +} Bh1750_sensors[2]; + /*********************************************************************************************/ -bool Bh1750SetResolution(void) -{ - Wire.beginTransmission(Bh1750.address); - Wire.write(Bh1750.resolution[Settings.SensorBits1.bh1750_resolution]); +uint8_t Bh1750Resolution(uint32_t sensor_index) { + uint8_t settings_resolution = Settings.SensorBits1.bh1750_1_resolution; + if (1 == sensor_index) { + settings_resolution = Settings.SensorBits1.bh1750_2_resolution; + } + return settings_resolution; +} + +bool Bh1750SetResolution(uint32_t sensor_index) { + Wire.beginTransmission(Bh1750_sensors[sensor_index].address); + Wire.write(Bh1750.resolution[Bh1750Resolution(sensor_index)]); return (!Wire.endTransmission()); } -bool Bh1750SetMTreg(void) -{ - Wire.beginTransmission(Bh1750.address); - uint8_t data = BH1750_MEASUREMENT_TIME_HIGH | ((Bh1750.mtreg >> 5) & 0x07); +bool Bh1750SetMTreg(uint32_t sensor_index) { + Wire.beginTransmission(Bh1750_sensors[sensor_index].address); + uint8_t data = BH1750_MEASUREMENT_TIME_HIGH | ((Bh1750_sensors[sensor_index].mtreg >> 5) & 0x07); Wire.write(data); if (Wire.endTransmission()) { return false; } - Wire.beginTransmission(Bh1750.address); - data = BH1750_MEASUREMENT_TIME_LOW | (Bh1750.mtreg & 0x1F); + Wire.beginTransmission(Bh1750_sensors[sensor_index].address); + data = BH1750_MEASUREMENT_TIME_LOW | (Bh1750_sensors[sensor_index].mtreg & 0x1F); Wire.write(data); if (Wire.endTransmission()) { return false; } - return Bh1750SetResolution(); + return Bh1750SetResolution(sensor_index); } -bool Bh1750Read(void) -{ - if (Bh1750.valid) { Bh1750.valid--; } +bool Bh1750Read(uint32_t sensor_index) { + if (Bh1750_sensors[sensor_index].valid) { Bh1750_sensors[sensor_index].valid--; } + + if (2 != Wire.requestFrom(Bh1750_sensors[sensor_index].address, (uint8_t)2)) { return false; } - if (2 != Wire.requestFrom(Bh1750.address, (uint8_t)2)) { return false; } float illuminance = (Wire.read() << 8) | Wire.read(); - illuminance /= (1.2 * (69 / (float)Bh1750.mtreg)); - if (1 == Settings.SensorBits1.bh1750_resolution) { + illuminance /= (1.2 * (69 / (float)Bh1750_sensors[sensor_index].mtreg)); + if (1 == Bh1750Resolution(sensor_index)) { illuminance /= 2; } - Bh1750.illuminance = illuminance; + Bh1750_sensors[sensor_index].illuminance = illuminance; - Bh1750.valid = SENSOR_MAX_MISS; + Bh1750_sensors[sensor_index].valid = SENSOR_MAX_MISS; return true; } /********************************************************************************************/ -void Bh1750Detect(void) -{ +void Bh1750Detect(void) { for (uint32_t i = 0; i < sizeof(Bh1750.addresses); i++) { - Bh1750.address = Bh1750.addresses[i]; - if (I2cActive(Bh1750.address)) { continue; } + if (I2cActive(Bh1750.addresses[i])) { continue; } - if (Bh1750SetMTreg()) { - I2cSetActiveFound(Bh1750.address, Bh1750.types); - Bh1750.type = 1; - break; + Bh1750_sensors[Bh1750.count].address = Bh1750.addresses[i]; + if (Bh1750SetMTreg(Bh1750.count)) { + I2cSetActiveFound(Bh1750_sensors[Bh1750.count].address, Bh1750.types); + Bh1750.count++; } } } -void Bh1750EverySecond(void) -{ - // 1mS - if (!Bh1750Read()) { - AddLogMissed(Bh1750.types, Bh1750.valid); +void Bh1750EverySecond(void) { + for (uint32_t i = 0; i < Bh1750.count; i++) { + // 1mS + if (!Bh1750Read(i)) { +// AddLogMissed(Bh1750.types, Bh1750.valid); + } } } /*********************************************************************************************\ - * Command Sensor10 - * - * 0 - High resolution mode (default) - * 1 - High resolution mode 2 - * 2 - Low resolution mode - * 31..254 - Measurement Time value (not persistent, default is 69) + * Commands \*********************************************************************************************/ -bool Bh1750CommandSensor(void) -{ - if (XdrvMailbox.data_len) { +void CmndBh1750Resolution(void) { + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Bh1750.count)) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) { - Settings.SensorBits1.bh1750_resolution = XdrvMailbox.payload; - Bh1750SetResolution(); - } - else if ((XdrvMailbox.payload > 30) && (XdrvMailbox.payload < 255)) { - Bh1750.mtreg = XdrvMailbox.payload; - Bh1750SetMTreg(); + if (1 == XdrvMailbox.index) { + Settings.SensorBits1.bh1750_1_resolution = XdrvMailbox.payload; + } else { + Settings.SensorBits1.bh1750_2_resolution = XdrvMailbox.payload; + } + Bh1750SetResolution(XdrvMailbox.index -1); } + ResponseCmndIdxNumber(Bh1750Resolution(XdrvMailbox.index -1)); } - Response_P(PSTR("{\"" D_CMND_SENSOR "10\":{\"Resolution\":%d,\"MTime\":%d}}"), Settings.SensorBits1.bh1750_resolution, Bh1750.mtreg); - - return true; } -void Bh1750Show(bool json) -{ - if (Bh1750.valid) { - if (json) { - ResponseAppend_P(JSON_SNS_ILLUMINANCE, Bh1750.types, Bh1750.illuminance); -#ifdef USE_DOMOTICZ - if (0 == tele_period) { - DomoticzSensor(DZ_ILLUMINANCE, Bh1750.illuminance); +void CmndBh1750MTime(void) { + if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= Bh1750.count)) { + if ((XdrvMailbox.payload > 30) && (XdrvMailbox.payload < 255)) { + Bh1750_sensors[XdrvMailbox.index -1].mtreg = XdrvMailbox.payload; + Bh1750SetMTreg(XdrvMailbox.index -1); + } + ResponseCmndIdxNumber(Bh1750_sensors[XdrvMailbox.index -1].mtreg); + } +} + +/********************************************************************************************/ + +void Bh1750Show(bool json) { + for (uint32_t sensor_index = 0; sensor_index < Bh1750.count; sensor_index++) { + if (Bh1750_sensors[sensor_index].valid) { + char sensor_name[10]; + strlcpy(sensor_name, Bh1750.types, sizeof(sensor_name)); + if (Bh1750.count > 1) { + snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c%02X"), sensor_name, IndexSeparator(), Bh1750_sensors[sensor_index].address); // BH1750-23 } + + if (json) { + ResponseAppend_P(JSON_SNS_ILLUMINANCE, sensor_name, Bh1750_sensors[sensor_index].illuminance); +#ifdef USE_DOMOTICZ + if ((0 == tele_period) && (0 == sensor_index)) { + DomoticzSensor(DZ_ILLUMINANCE, Bh1750_sensors[sensor_index].illuminance); + } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER - } else { - WSContentSend_PD(HTTP_SNS_ILLUMINANCE, Bh1750.types, Bh1750.illuminance); + } else { + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, sensor_name, Bh1750_sensors[sensor_index].illuminance); #endif // USE_WEBSERVER + } } } } @@ -159,8 +193,7 @@ void Bh1750Show(bool json) * Interface \*********************************************************************************************/ -bool Xsns10(uint8_t function) -{ +bool Xsns10(uint8_t function) { if (!I2cEnabled(XI2C_11)) { return false; } bool result = false; @@ -168,15 +201,13 @@ bool Xsns10(uint8_t function) if (FUNC_INIT == function) { Bh1750Detect(); } - else if (Bh1750.type) { + else if (Bh1750.count) { switch (function) { case FUNC_EVERY_SECOND: Bh1750EverySecond(); break; - case FUNC_COMMAND_SENSOR: - if (XSNS_10 == XdrvMailbox.index) { - result = Bh1750CommandSensor(); - } + case FUNC_COMMAND: + result = DecodeCommand(kBh1750Commands, Bh1750Command); break; case FUNC_JSON_APPEND: Bh1750Show(1); diff --git a/tasmota/xsns_26_lm75ad.ino b/tasmota/xsns_26_lm75ad.ino index 5b735dcb7..d5073f759 100644 --- a/tasmota/xsns_26_lm75ad.ino +++ b/tasmota/xsns_26_lm75ad.ino @@ -89,7 +89,7 @@ void LM75ADShow(bool json) dtostrfd(t, Settings.flag2.temperature_resolution, temperature); if (json) { - ResponseAppend_P(PSTR(",\"LM75AD\":{\"" D_JSON_TEMPERATURE "\":%s}"), temperature); + ResponseAppend_P(JSON_SNS_TEMP, "LM75AD", temperature); #ifdef USE_DOMOTICZ if (0 == tele_period) DomoticzSensor(DZ_TEMP, temperature); #endif // USE_DOMOTICZ diff --git a/tasmota/xsns_39_max31855.ino b/tasmota/xsns_39_max31855.ino index c8cdfc7aa..fcdb69094 100644 --- a/tasmota/xsns_39_max31855.ino +++ b/tasmota/xsns_39_max31855.ino @@ -18,20 +18,27 @@ */ #ifdef USE_MAX31855 +/*********************************************************************************************\ + * MAX31855 and MAX6675 - Thermocouple + * + * SetOption94 0 - MAX31855 + * SetOption94 1 - MAX6675 +\*********************************************************************************************/ #define XSNS_39 39 -bool initialized = false; +const char kMax31855Types[] PROGMEM = "MAX31855|MAX6675"; -struct MAX31855_ResultStruct{ - uint8_t ErrorCode; // Error Codes: 0 = No Error / 1 = TC open circuit / 2 = TC short to GND / 4 = TC short to VCC - float ProbeTemperature; // Measured temperature of the 'hot' TC junction (probe temp) - float ReferenceTemperature; // Measured temperature of the 'cold' TC junction (reference temp) +bool max31855_initialized = false; + +struct MAX31855_ResultStruct { + uint8_t ErrorCode; // Error Codes: 0 = No Error / 1 = TC open circuit / 2 = TC short to GND / 4 = TC short to VCC + float ProbeTemperature; // Measured temperature of the 'hot' TC junction (probe temp) + float ReferenceTemperature; // Measured temperature of the 'cold' TC junction (reference temp) } MAX31855_Result; -void MAX31855_Init(void){ - if(initialized) - return; +void MAX31855_Init(void) { + if (PinUsed(GPIO_MAX31855CS) && PinUsed(GPIO_MAX31855CLK) && PinUsed(GPIO_MAX31855DO)) { // Set GPIO modes for SW-SPI pinMode(Pin(GPIO_MAX31855CS), OUTPUT); @@ -42,106 +49,122 @@ void MAX31855_Init(void){ digitalWrite(Pin(GPIO_MAX31855CS), HIGH); digitalWrite(Pin(GPIO_MAX31855CLK), LOW); - initialized = true; -} - -/* -* MAX31855_GetResult(void) -* Acquires the raw data via SPI, checks for MAX31855 errors and fills result structure -*/ -void MAX31855_GetResult(void){ - int32_t RawData = MAX31855_ShiftIn(32); - uint8_t probeerror = RawData & 0x7; - - MAX31855_Result.ErrorCode = probeerror; - MAX31855_Result.ReferenceTemperature = MAX31855_GetReferenceTemperature(RawData); - if(probeerror) - MAX31855_Result.ProbeTemperature = NAN; // Return NaN if MAX31855 reports an error - else - MAX31855_Result.ProbeTemperature = MAX31855_GetProbeTemperature(RawData); -} - - -/* -* MAX31855_GetProbeTemperature(int32_t RawData) -* Decodes and returns the temperature of TCs 'hot' junction from RawData -*/ -float MAX31855_GetProbeTemperature(int32_t RawData){ - if(RawData & 0x80000000) - RawData = (RawData >> 18) | 0xFFFFC000; // Negative value - Drop lower 18 bits and extend to negative number - else - RawData >>= 18; // Positiv value - Drop lower 18 bits - - float result = (RawData * 0.25); // MAX31855 LSB resolution is 0.25°C for probe temperature - - return ConvertTemp(result); // Check if we have to convert to Fahrenheit -} - -/* -* MAX31855_GetReferenceTemperature(int32_t RawData) -* Decodes and returns the temperature of TCs 'cold' junction from RawData -*/ -float MAX31855_GetReferenceTemperature(int32_t RawData){ - if(RawData & 0x8000) - RawData = (RawData >> 4) | 0xFFFFF000; // Negative value - Drop lower 4 bits and extend to negative number - else - RawData = (RawData >> 4) & 0x00000FFF; // Positiv value - Drop lower 4 bits and mask out remaining bits (probe temp, error bit, etc.) - - float result = (RawData * 0.0625); // MAX31855 LSB resolution is 0.0625°C for reference temperature - - return ConvertTemp(result); // Check if we have to convert to Fahrenheit + max31855_initialized = true; + } } /* * MAX31855_ShiftIn(uint8_t Length) * Communicates with MAX31855 via SW-SPI and returns the raw data read from the chip */ -int32_t MAX31855_ShiftIn(uint8_t Length){ - int32_t dataIn = 0; +int32_t MAX31855_ShiftIn(uint8_t Length) { + int32_t dataIn = 0; - digitalWrite(Pin(GPIO_MAX31855CS), LOW); // CS = LOW -> Start SPI communication - delayMicroseconds(1); // CS fall to output enable = max. 100ns + digitalWrite(Pin(GPIO_MAX31855CS), LOW); // CS = LOW -> Start SPI communication + delayMicroseconds(1); // CS fall to output enable = max. 100ns - for (uint32_t i = 0; i < Length; i++) - { - digitalWrite(Pin(GPIO_MAX31855CLK), LOW); - delayMicroseconds(1); // CLK pulse width low = min. 100ns / CLK fall to output valid = max. 40ns - dataIn <<= 1; - if(digitalRead(Pin(GPIO_MAX31855DO))) - dataIn |= 1; - digitalWrite(Pin(GPIO_MAX31855CLK), HIGH); - delayMicroseconds(1); // CLK pulse width high = min. 100ns - } - - digitalWrite(Pin(GPIO_MAX31855CS), HIGH); // CS = HIGH -> End SPI communication + for (uint32_t i = 0; i < Length; i++) { digitalWrite(Pin(GPIO_MAX31855CLK), LOW); - return dataIn; + delayMicroseconds(1); // CLK pulse width low = min. 100ns / CLK fall to output valid = max. 40ns + dataIn <<= 1; + if (digitalRead(Pin(GPIO_MAX31855DO))) { + dataIn |= 1; + } + digitalWrite(Pin(GPIO_MAX31855CLK), HIGH); + delayMicroseconds(1); // CLK pulse width high = min. 100ns + } + + digitalWrite(Pin(GPIO_MAX31855CS), HIGH); // CS = HIGH -> End SPI communication + digitalWrite(Pin(GPIO_MAX31855CLK), LOW); + return dataIn; } -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); +/* +* MAX31855_GetProbeTemperature(int32_t RawData) +* Decodes and returns the temperature of TCs 'hot' junction from RawData +*/ +float MAX31855_GetProbeTemperature(int32_t RawData) { + if (RawData & 0x80000000) { + RawData = (RawData >> 18) | 0xFFFFC000; // Negative value - Drop lower 18 bits and extend to negative number + } else { + RawData >>= 18; // Positiv value - Drop lower 18 bits + } + float result = (RawData * 0.25); // MAX31855 LSB resolution is 0.25°C for probe temperature - if(Json){ - ResponseAppend_P(PSTR(",\"MAX31855\":{\"" D_JSON_PROBETEMPERATURE "\":%s,\"" D_JSON_REFERENCETEMPERATURE "\":%s,\"" D_JSON_ERROR "\":%d}"), \ - probetemp, referencetemp, MAX31855_Result.ErrorCode); + return ConvertTemp(result); // Check if we have to convert to Fahrenheit +} + +/* +* MAX31855_GetReferenceTemperature(int32_t RawData) +* Decodes and returns the temperature of TCs 'cold' junction from RawData +*/ +float MAX31855_GetReferenceTemperature(int32_t RawData) { + if (RawData & 0x8000) { + RawData = (RawData >> 4) | 0xFFFFF000; // Negative value - Drop lower 4 bits and extend to negative number + } else { + RawData = (RawData >> 4) & 0x00000FFF; // Positiv value - Drop lower 4 bits and mask out remaining bits (probe temp, error bit, etc.) + } + float result = (RawData * 0.0625); // MAX31855 LSB resolution is 0.0625°C for reference temperature + + return ConvertTemp(result); // Check if we have to convert to Fahrenheit +} + +/* +* MAX31855_GetResult(void) +* Acquires the raw data via SPI, checks for MAX31855 errors and fills result structure +*/ +void MAX31855_GetResult(void) { + if (Settings.flag4.max6675) { // SetOption94 - Implement simpler MAX6675 protocol instead of MAX31855 + int32_t RawData = MAX31855_ShiftIn(16); + int32_t temp = (RawData >> 3) & ((1 << 12) - 1); + + /* Occasionally the sensor returns 0xfff, consider it an error */ + if (temp == ((1 << 12) - 1)) { return; } + + MAX31855_Result.ErrorCode = 0; + MAX31855_Result.ReferenceTemperature = NAN; + MAX31855_Result.ProbeTemperature = ConvertTemp(0.25 * temp); + } else { + int32_t RawData = MAX31855_ShiftIn(32); + uint8_t probeerror = RawData & 0x7; + + MAX31855_Result.ErrorCode = probeerror; + MAX31855_Result.ReferenceTemperature = MAX31855_GetReferenceTemperature(RawData); + if (probeerror) { + MAX31855_Result.ProbeTemperature = NAN; // Return NaN if MAX31855 reports an error + } else { + MAX31855_Result.ProbeTemperature = MAX31855_GetProbeTemperature(RawData); + } + } +} + +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); #ifdef USE_DOMOTICZ - if (0 == tele_period) { - DomoticzSensor(DZ_TEMP, probetemp); - } + if (0 == tele_period) { + DomoticzSensor(DZ_TEMP, probetemp); + } #endif // USE_DOMOTICZ #ifdef USE_KNX - if (0 == tele_period) { - KnxSensor(KNX_TEMPERATURE, MAX31855_Result.ProbeTemperature); - } -#endif // USE_KNX - } else { -#ifdef USE_WEBSERVER - WSContentSend_PD(HTTP_SNS_TEMP, "MAX31855", probetemp, TempUnit()); -#endif // USE_WEBSERVER + if (0 == tele_period) { + KnxSensor(KNX_TEMPERATURE, MAX31855_Result.ProbeTemperature); } +#endif // USE_KNX +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, probetemp, TempUnit()); +#endif // USE_WEBSERVER + } } /*********************************************************************************************\ @@ -151,12 +174,12 @@ void MAX31855_Show(bool Json){ bool Xsns39(uint8_t function) { bool result = false; - if(PinUsed(GPIO_MAX31855CS) && PinUsed(GPIO_MAX31855CLK) && PinUsed(GPIO_MAX31855DO)){ + if (FUNC_INIT == function) { + MAX31855_Init(); + } + else if (max31855_initialized) { switch (function) { - case FUNC_INIT: - MAX31855_Init(); - break; case FUNC_EVERY_SECOND: MAX31855_GetResult(); break; diff --git a/tasmota/xsns_52_ibeacon.ino b/tasmota/xsns_52_ibeacon.ino old mode 100644 new mode 100755 index 88b040e76..5f9c0f3ba --- a/tasmota/xsns_52_ibeacon.ino +++ b/tasmota/xsns_52_ibeacon.ino @@ -25,6 +25,8 @@ #include +#define TMSBSIZ 256 + #define HM17_BAUDRATE 9600 #define IBEACON_DEBUG @@ -96,7 +98,7 @@ void IBEACON_Init() { // actually doesnt work reliably with software serial if (PinUsed(GPIO_IBEACON_RX) && PinUsed(GPIO_IBEACON_TX)) { - IBEACON_Serial = new TasmotaSerial(Pin(GPIO_IBEACON_RX), Pin(GPIO_IBEACON_TX),1); + IBEACON_Serial = new TasmotaSerial(Pin(GPIO_IBEACON_RX), Pin(GPIO_IBEACON_TX),1,0,TMSBSIZ); if (IBEACON_Serial->begin(HM17_BAUDRATE)) { if (IBEACON_Serial->hardwareSerial()) { ClaimSerial(); @@ -144,7 +146,7 @@ void hm17_every_second(void) { void hm17_sbclr(void) { memset(hm17_sbuffer,0,HM17_BSIZ); hm17_sindex=0; - IBEACON_Serial->flush(); + //IBEACON_Serial->flush(); } void hm17_sendcmd(uint8_t cmd) { @@ -405,7 +407,7 @@ hm17_v110: } } else { #ifdef IBEACON_DEBUG - if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR(">>%s"),&hm17_sbuffer[8]); + if (hm17_debug) AddLog_P2(LOG_LEVEL_INFO, PSTR(">->%s"),&hm17_sbuffer[8]); #endif } break; @@ -517,7 +519,7 @@ bool xsns52_cmd(void) { #ifdef IBEACON_DEBUG else if (*cp=='d') { cp++; - if (*cp) hm17_debug=atoi(cp); + hm17_debug=atoi(cp); Response_P(S_JSON_IBEACON, XSNS_52,"debug",hm17_debug); } #endif diff --git a/tasmota/xsns_53_sml.ino b/tasmota/xsns_53_sml.ino index 3b4357b26..cb51bcc82 100755 --- a/tasmota/xsns_53_sml.ino +++ b/tasmota/xsns_53_sml.ino @@ -49,6 +49,8 @@ #define SPECIAL_SS #endif +#define TMSBSIZ 256 + // addresses a bug in meter DWS74 //#define DWS74_BUG @@ -2144,9 +2146,9 @@ init10: // serial input, init #ifdef SPECIAL_SS if (meter_desc_p[meters].type=='m' || meter_desc_p[meters].type=='M' || meter_desc_p[meters].type=='p') { - meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1); + meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,0,TMSBSIZ); } else { - meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,1); + meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,1,TMSBSIZ); } #else #ifdef ESP32 @@ -2154,8 +2156,9 @@ init10: if (uart_index==0) { ClaimSerial(); } uart_index--; if (uart_index<0) uart_index=0; + meter_ss[meters]->setRxBufferSize(TMSBSIZ); #else - meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1); + meter_ss[meters] = new TasmotaSerial(meter_desc_p[meters].srcpin,meter_desc_p[meters].trxpin,1,0,TMSBSIZ); #endif #endif @@ -2188,6 +2191,15 @@ uint32_t SML_SetBaud(uint32_t meter, uint32_t br) { if (meter<1 || meter>meters_used) return 0; meter--; if (!meter_ss[meter]) return 0; + +#ifdef ESP32 + meter_ss[meter]->flush(); + if (meter_desc_p[meter].type=='M') { + meter_ss[meter]->begin(br,SERIAL_8E1,meter_desc_p[meter].srcpin,meter_desc_p[meter].trxpin); + } else { + meter_ss[meter]->begin(br,SERIAL_8N1,meter_desc_p[meter].srcpin,meter_desc_p[meter].trxpin); + } +#else if (meter_ss[meter]->begin(br)) { meter_ss[meter]->flush(); } @@ -2196,6 +2208,7 @@ uint32_t SML_SetBaud(uint32_t meter, uint32_t br) { Serial.begin(br, SERIAL_8E1); } } +#endif return 1; } diff --git a/tasmota/xsns_59_ds1624.ino b/tasmota/xsns_59_ds1624.ino index f3ffcd1f1..60b176c6e 100644 --- a/tasmota/xsns_59_ds1624.ino +++ b/tasmota/xsns_59_ds1624.ino @@ -184,7 +184,7 @@ void DS1624Show(bool json) dtostrfd(ds1624_sns[i].value, Settings.flag2.temperature_resolution, temperature); if (json) { - ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s}"), ds1624_sns[i].name, temperature); + ResponseAppend_P(JSON_SNS_TEMP, ds1624_sns[i].name, temperature); if ((0 == tele_period) && once) { #ifdef USE_DOMOTICZ DomoticzSensor(DZ_TEMP, temperature); diff --git a/tasmota/xsns_62_MI_ESP32.ino b/tasmota/xsns_62_MI_ESP32.ino index 28c927db9..264ffbb52 100644 --- a/tasmota/xsns_62_MI_ESP32.ino +++ b/tasmota/xsns_62_MI_ESP32.ino @@ -53,6 +53,8 @@ struct { uint32_t willSetTime:1; uint32_t shallReadBatt:1; uint32_t willReadBatt:1; + uint32_t shallSetUnit:1; + uint32_t willSetUnit:1; } mode; struct { uint8_t sensor; // points to to the number 0...255 @@ -152,7 +154,7 @@ BLEScanResults MI32foundDevices; const char S_JSON_MI32_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_MI32 "%s\":%d}"; const char S_JSON_MI32_COMMAND[] PROGMEM = "{\"" D_CMND_MI32 "%s%s\"}"; -const char kMI32_Commands[] PROGMEM = "Period|Time|Page|Battery"; +const char kMI32_Commands[] PROGMEM = "Period|Time|Page|Battery|Unit"; #define FLORA 1 #define MJ_HT_V1 2 @@ -185,7 +187,8 @@ enum MI32_Commands { // commands useable in console or rules CMND_MI32_PERIOD, // set period like TELE-period in seconds between read-cycles CMND_MI32_TIME, // set LYWSD02-Time from ESP8266-time CMND_MI32_PAGE, // sensor entries per web page, which will be shown alternated - CMND_MI32_BATTERY // read all battery levels + CMND_MI32_BATTERY, // read all battery levels + CMND_MI32_UNIT // toggles the displayed unit between C/F (LYWSD02) }; enum MI32_TASK { @@ -193,6 +196,7 @@ enum MI32_TASK { MI32_TASK_CONN = 1, MI32_TASK_TIME = 2, MI32_TASK_BATT = 3, + MI32_TASK_UNIT = 4, }; /*********************************************************************************************\ @@ -394,6 +398,10 @@ void MI32StartTask(uint32_t task){ if (MI32.mode.willReadBatt == 1) return; MI32StartBatteryTask(); break; + case MI32_TASK_UNIT: + if (MI32.mode.shallSetUnit == 0) return; + MI32StartUnitTask(); + break; default: break; } @@ -616,6 +624,72 @@ void MI32TimeTask(void *pvParameters){ vTaskDelete( NULL ); } +void MI32StartUnitTask(){ + MI32.mode.willConnect = 1; + xTaskCreatePinnedToCore( + MI32UnitTask, /* Function to implement the task */ + "MI32UnitTask", /* Name of the task */ + 8912, /* Stack size in words */ + NULL, /* Task input parameter */ + 15, /* Priority of the task */ + NULL, /* Task handle. */ + 0); /* Core where the task should run */ + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: Start unit set"),D_CMND_MI32); + // AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: with sensor: %u"),D_CMND_MI32, MI32.state.sensor); +} + +void MI32UnitTask(void *pvParameters){ + if (MIBLEsensors[MI32.state.sensor].type != LYWSD02) { + MI32.mode.shallSetUnit = 0; + vTaskDelete( NULL ); + } + + if(MI32ConnectActiveSensor()){ + uint32_t timer = 0; + while (MI32.mode.connected == 0){ + if (timer>1000){ + break; + } + timer++; + vTaskDelay(10/ portTICK_PERIOD_MS); + } + + NimBLERemoteService* pSvc = nullptr; + NimBLERemoteCharacteristic* pChr = nullptr; + static BLEUUID serviceUUID("EBE0CCB0-7A0A-4B0C-8A1A-6FF2997DA3A6"); + static BLEUUID charUUID("EBE0CCBE-7A0A-4B0C-8A1A-6FF2997DA3A6"); + pSvc = MI32Client->getService(serviceUUID); + if(pSvc) { + pChr = pSvc->getCharacteristic(charUUID); + } + + if(pChr->canRead()){ + uint8_t curUnit; + const char *buf = pChr->readValue().c_str(); + if( buf[0] != 0 && buf[0]<101 ){ + curUnit = buf[0]; + } + + if(pChr->canWrite()) { + curUnit = curUnit == 0x01?0xFF:0x01; // C/F + + if(!pChr->writeValue(&curUnit,sizeof(curUnit),true)) { // true is important ! + MI32.mode.willConnect = 0; + MI32Client->disconnect(); + } + else { + MI32.mode.shallSetUnit = 0; + MI32.mode.willSetUnit = 0; + } + } + } + MI32Client->disconnect(); + } + vTaskDelay(500/ portTICK_PERIOD_MS); + MI32.mode.connected = 0; + vTaskDelete( NULL ); +} + void MI32StartBatteryTask(){ if (MI32.mode.connected) return; MI32.mode.willReadBatt = 1; @@ -964,6 +1038,15 @@ void MI32EverySecond(bool restart){ } } + if (MI32.mode.shallSetUnit) { + MI32.mode.canScan = 0; + MI32.mode.canConnect = 0; + if (MI32.mode.willSetUnit == 0){ + MI32.mode.willSetUnit = 1; + MI32StartTask(MI32_TASK_UNIT); + } + } + if (MI32.mode.willReadBatt) return; if (_counter>MI32.period) { @@ -1057,6 +1140,21 @@ bool MI32Cmd(void) { } Response_P(S_JSON_MI32_COMMAND_NVALUE, command, XdrvMailbox.payload); break; + case CMND_MI32_UNIT: + if (XdrvMailbox.data_len > 0) { + if(MIBLEsensors.size()>XdrvMailbox.payload){ + if(MIBLEsensors[XdrvMailbox.payload].type == LYWSD02){ + AddLog_P2(LOG_LEVEL_DEBUG,PSTR("%s: will set Unit"),D_CMND_MI32); + MI32.state.sensor = XdrvMailbox.payload; + MI32.mode.canScan = 0; + MI32.mode.canConnect = 0; + MI32.mode.shallSetUnit = 1; + MI32.mode.willSetUnit = 0; + } + } + } + Response_P(S_JSON_MI32_COMMAND_NVALUE, command, XdrvMailbox.payload); + break; case CMND_MI32_PAGE: if (XdrvMailbox.data_len > 0) { if (XdrvMailbox.payload == 0) XdrvMailbox.payload = MI32.perPage; // ignore 0 diff --git a/tasmota/xsns_67_as3935.ino b/tasmota/xsns_67_as3935.ino index 59618c32a..3e795b91a 100644 --- a/tasmota/xsns_67_as3935.ino +++ b/tasmota/xsns_67_as3935.ino @@ -51,8 +51,6 @@ #define INDOORS 0x24 #define OUTDOORS 0x1C - - // Global const char HTTP_SNS_UNIT_KILOMETER[] PROGMEM = D_UNIT_KILOMETER; // Http @@ -78,7 +76,7 @@ const char HTTP_SNS_AS3935_INTNOEV[] PROGMEM = "{s}%s: " D_AS3935_INTNOEV "{e}"; const char HTTP_SNS_AS3935_MSG[] PROGMEM = "{s}%s: " D_AS3935_LIGHT " " D_AS3935_APRX " %d " D_UNIT_KILOMETER " " D_AS3935_AWAY "{e}"; const char* const HTTP_SNS_AS3935_TABLE_1[] PROGMEM = { HTTP_SNS_AS3935_EMPTY, HTTP_SNS_AS3935_MSG, HTTP_SNS_AS3935_OUT, HTTP_SNS_AS3935_NOT, HTTP_SNS_AS3935_ABOVE, HTTP_SNS_AS3935_NOISE, HTTP_SNS_AS3935_DISTURB, HTTP_SNS_AS3935_INTNOEV }; // Json -const char JSON_SNS_AS3935_EVENTS[] PROGMEM = ",\"%s\":{\"" D_JSON_EVENT "\":%d,\"" D_JSON_DISTANCE "\":%d,\"" D_JSON_ENERGY "\":%u}"; +const char JSON_SNS_AS3935_EVENTS[] PROGMEM = ",\"%s\":{\"" D_JSON_EVENT "\":%d,\"" D_JSON_DISTANCE "\":%d,\"" D_JSON_ENERGY "\":%u,\"" D_JSON_STAGE "\":%d}"; // Json Command const char* const S_JSON_AS3935_COMMAND_ONOFF[] PROGMEM = {"\"" D_AS3935_OFF "\"","\"" D_AS3935_ON"\""}; const char* const S_JSON_AS3935_COMMAND_GAIN[] PROGMEM = {"\"" D_AS3935_INDOORS "\"", "\"" D_AS3935_OUTDOORS "\""}; @@ -465,8 +463,15 @@ bool AS3935SetDefault() { void AS3935InitSettings() { if(Settings.as3935_functions.nf_autotune){ - AS3935SetGain(INDOORS); - AS3935SetNoiseFloor(0); + if(Settings.as3935_parameter.nf_autotune_min) { + if (Settings.as3935_parameter.nf_autotune_min > 7) { + AS3935SetGain(OUTDOORS); + AS3935SetNoiseFloor(Settings.as3935_parameter.nf_autotune_min - 8); + } else { + AS3935SetGain(INDOORS); + AS3935SetNoiseFloor(Settings.as3935_parameter.nf_autotune_min); + } + } } I2cWrite8(AS3935_ADDR, 0x00, Settings.as3935_sensor_cfg[0]); I2cWrite8(AS3935_ADDR, 0x01, Settings.as3935_sensor_cfg[1]); @@ -746,8 +751,10 @@ bool AS3935Cmd(void) { void AH3935Show(bool json) { if (json) { - ResponseAppend_P(JSON_SNS_AS3935_EVENTS, D_SENSOR_AS3935, as3935_sensor.mqtt_irq, as3935_sensor.distance, as3935_sensor.intensity ); - + uint16_t vrms; + uint8_t stage; + AS3935CalcVrmsLevel(vrms, stage); + ResponseAppend_P(JSON_SNS_AS3935_EVENTS, D_SENSOR_AS3935, as3935_sensor.mqtt_irq, as3935_sensor.distance, as3935_sensor.intensity, stage); #ifdef USE_WEBSERVER } else { uint8_t gain = AS3935GetGainInt(); diff --git a/tasmota/xsns_68_windmeter.ino b/tasmota/xsns_68_windmeter.ino index c0e5de2aa..3faa7ecb7 100644 --- a/tasmota/xsns_68_windmeter.ino +++ b/tasmota/xsns_68_windmeter.ino @@ -83,7 +83,7 @@ void WindMeterUpdateSpeed(void) if (time_diff > Settings.windmeter_pulse_debounce * 1000) { WindMeter.counter_time = time; WindMeter.counter++; - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("WMET: Counter %d"), WindMeter.counter); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("WMET: Counter %d"), WindMeter.counter); } } diff --git a/tasmota/xsns_71_veml7700.ino b/tasmota/xsns_71_veml7700.ino index de28661cc..3869e72d6 100644 --- a/tasmota/xsns_71_veml7700.ino +++ b/tasmota/xsns_71_veml7700.ino @@ -37,12 +37,27 @@ Adafruit_VEML7700 veml7700 = Adafruit_VEML7700(); //create object copy const char HTTP_SNS_WHITE[] PROGMEM = "{s}%s " D_WHITE_CONTENT "{m}%d {e}"; const char JSON_SNS_VEML7700[] PROGMEM = ",\"%s\":{\"" D_JSON_ILLUMINANCE "\":%d,\"" D_JSON_WHITE_CONTENT "\":%d}"; +#define D_CMND_VEML7700_PWR "power" +#define D_CMND_VEML7700_GAIN "gain" +#define D_CMND_VEML7700_INTTIME "inttime" + +const char S_JSON_VEML7700_COMMAND_NVALUE[] PROGMEM = "{\"" D_NAME_VEML7700 "\":{\"%s\":%d}}"; +const char kVEML7700_Commands[] PROGMEM = D_CMND_VEML7700_PWR "|" D_CMND_VEML7700_GAIN "|" D_CMND_VEML7700_INTTIME; + +enum VEML7700_Commands { // commands for Console + CMND_VEML7700_PWR, + CMND_VEML7700_GAIN, + CMND_VEML7700_SET_IT, + }; + struct VEML7700STRUCT { char types[9] = D_NAME_VEML7700; uint8_t address = VEML7700_I2CADDR_DEFAULT; - uint16_t lux = 0; - uint16_t white = 0; + //uint16_t lux = 0; + //uint16_t white = 0; + uint16_t lux_normalized = 0; + uint16_t white_normalized = 0; } veml7700_sensor; uint8_t veml7700_active = 0; @@ -50,34 +65,102 @@ uint8_t veml7700_active = 0; /********************************************************************************************/ void VEML7700Detect(void) { - if (I2cActive(veml7700_sensor.address)) return; + if (!I2cSetDevice(veml7700_sensor.address)) return; if (veml7700.begin()) { I2cSetActiveFound(veml7700_sensor.address, veml7700_sensor.types); veml7700_active = 1; } } +uint16_t VEML7700TranslateItMs (uint8_t ittime){ + switch (ittime) { + case 0: return 100; + case 1: return 200; + case 2: return 400; + case 3: return 800; + case 8: return 50; + case 12: return 25; + default: return 0xFFFF; + } +} + +uint8_t VEML7700TranslateItInt (uint16_t ittimems){ + switch (ittimems) { + case 100: return 0; + case 200: return 1; + case 400: return 2; + case 800: return 3; + case 50: return 8; + case 25: return 12; + default: return 0xFF; + } +} + void VEML7700EverySecond(void) { - veml7700_sensor.lux = (uint16_t) veml7700.readLux(); - veml7700_sensor.white = (uint16_t) veml7700.readWhite(); + veml7700_sensor.lux_normalized = (uint16_t) veml7700.readLuxNormalized(); + veml7700_sensor.white_normalized = (uint16_t) veml7700.readWhiteNormalized(); + //veml7700_sensor.lux = (uint16_t) veml7700.readLux(); + //veml7700_sensor.white = (uint16_t) veml7700.readWhite(); } void VEML7700Show(bool json) { if (json) { - ResponseAppend_P(JSON_SNS_VEML7700, D_NAME_VEML7700, veml7700_sensor.lux, veml7700_sensor.white); + ResponseAppend_P(JSON_SNS_VEML7700, D_NAME_VEML7700, veml7700_sensor.lux_normalized, veml7700_sensor.white_normalized); #ifdef USE_DOMOTICZ - if (0 == tele_period) DomoticzSensor(DZ_ILLUMINANCE, veml7700_sensor.lux); + if (0 == tele_period) DomoticzSensor(DZ_ILLUMINANCE, veml7700_sensor.lux_normalized); #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { - WSContentSend_PD(HTTP_SNS_ILLUMINANCE, D_NAME_VEML7700, veml7700_sensor.lux); - WSContentSend_PD(HTTP_SNS_WHITE, D_NAME_VEML7700, veml7700_sensor.white); + WSContentSend_PD(HTTP_SNS_ILLUMINANCE, D_NAME_VEML7700, veml7700_sensor.lux_normalized); + WSContentSend_PD(HTTP_SNS_WHITE, D_NAME_VEML7700, veml7700_sensor.white_normalized); #endif // USE_WEBSERVER } } +bool VEML7700Cmd(void) { + char command[CMDSZ]; + uint8_t name_len = strlen(D_NAME_VEML7700); + if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_NAME_VEML7700), name_len)) { + uint32_t command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + name_len, kVEML7700_Commands); + switch (command_code) { + case CMND_VEML7700_PWR: + if (XdrvMailbox.data_len) { + if (2 >= XdrvMailbox.payload) { + veml7700.enable(XdrvMailbox.payload); + } + } + Response_P(S_JSON_VEML7700_COMMAND_NVALUE, command, veml7700.enabled()); + break; + case CMND_VEML7700_GAIN: + if (XdrvMailbox.data_len) { + if (4 >= XdrvMailbox.payload) { + veml7700.setGain(XdrvMailbox.payload); + } + } + Response_P(S_JSON_VEML7700_COMMAND_NVALUE, command, veml7700.getGain()); + break; + case CMND_VEML7700_SET_IT: { + if (XdrvMailbox.data_len) { + uint8_t data = VEML7700TranslateItInt(XdrvMailbox.payload); + if (0xFF != data) { + veml7700.setIntegrationTime(data); + } + } + uint16_t dataret = VEML7700TranslateItMs(veml7700.getIntegrationTime()); + Response_P(S_JSON_VEML7700_COMMAND_NVALUE, command, dataret); + } + break; + default: + return false; + } + return true; + } + else { + return false; + } +} /*********************************************************************************************\ * Interface \*********************************************************************************************/ @@ -97,7 +180,7 @@ bool Xsns71(uint8_t function) VEML7700EverySecond(); break; case FUNC_COMMAND: - //result = VEML7700Cmd(); + result = VEML7700Cmd(); break; case FUNC_JSON_APPEND: VEML7700Show(1); diff --git a/tasmota/xsns_72_mcp9808.ino b/tasmota/xsns_72_mcp9808.ino new file mode 100644 index 000000000..8af129f97 --- /dev/null +++ b/tasmota/xsns_72_mcp9808.ino @@ -0,0 +1,136 @@ +/* + xsns_72_mcp9808 - MCP9808 I2C temperature sensor support for Tasmota + + Copyright (C) 2020 Martin Wagner and Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +*/ + +#ifdef USE_I2C +#ifdef USE_MCP9808 +/*********************************************************************************************\ + * MCP9808 - Temperature Sensor + * + * I2C Address: 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F + * +\*********************************************************************************************/ + +#define XSNS_72 72 +#define XI2C_51 51 // See I2CDEVICES.md + +#include "Adafruit_MCP9808.h" +Adafruit_MCP9808 mcp9808 = Adafruit_MCP9808(); // create object copy + +#define MCP9808_MAX_SENSORS 8 +#define MCP9808_START_ADDRESS 0x18 + +struct { +char types[9] = "MCP9808"; +uint8_t count = 0; +} mcp9808_cfg; + +struct { + float temperature = NAN; + uint8_t address; +} mcp9808_sensors[MCP9808_MAX_SENSORS]; + +/********************************************************************************************/ + +float MCP9808Read(uint8_t addr) { + float t = mcp9808.readTempC(addr); + return t; +} + +void MCP9808Detect(void) { + for (uint8_t i = 0; i < MCP9808_MAX_SENSORS; i++) { + if (!I2cSetDevice(MCP9808_START_ADDRESS + i)) { continue; } + + if (mcp9808.begin(MCP9808_START_ADDRESS + i)) { + mcp9808_sensors[mcp9808_cfg.count].address = MCP9808_START_ADDRESS + i; + I2cSetActiveFound(mcp9808_sensors[mcp9808_cfg.count].address, mcp9808_cfg.types); + mcp9808.setResolution (mcp9808_sensors[mcp9808_cfg.count].address, 2); // Set Resolution to 0.125°C + mcp9808_cfg.count++; + } + } +} + +void MCP9808EverySecond(void) { + for (uint32_t i = 0; i < mcp9808_cfg.count; i++) { + float t = MCP9808Read(mcp9808_sensors[i].address); + mcp9808_sensors[i].temperature = ConvertTemp(t); + } +} + +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) { + snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c%02X"), sensor_name, IndexSeparator(), mcp9808_sensors[i].address); // MCP9808-18, MCP9808-1A etc. + } + + if (json) { + ResponseAppend_P(JSON_SNS_TEMP, sensor_name, temperature); + if ((0 == tele_period) && (0 == i)) { +#ifdef USE_DOMOTICZ + DomoticzSensor(DZ_TEMP, temperature); +#endif // USE_DOMOTICZ +#ifdef USE_KNX + KnxSensor(KNX_TEMPERATURE, mcp9808_sensors[i].temperature); +#endif // USE_KNX + } +#ifdef USE_WEBSERVER + } else { + WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, temperature, TempUnit()); +#endif // USE_WEBSERVER + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns72(uint8_t function) +{ + if (!I2cEnabled(XI2C_51)) { return false; } + bool result = false; + + if (FUNC_INIT == function) { + MCP9808Detect(); + } + else if (mcp9808_cfg.count){ + switch (function) { + case FUNC_EVERY_SECOND: + MCP9808EverySecond(); + break; + case FUNC_JSON_APPEND: + MCP9808Show(1); + break; + #ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + MCP9808Show(0); + break; + #endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_MCP9808 +#endif // USE_I2C diff --git a/tools/Esptool/ESP32/readme.txt b/tools/Esptool/ESP32/readme.txt index b0730aaff..bdedc87de 100644 --- a/tools/Esptool/ESP32/readme.txt +++ b/tools/Esptool/ESP32/readme.txt @@ -1 +1,5 @@ -This files are needed for flashing Tasmota with esptool.py to a ESP32 +These files are needed for flashing Tasmota32 with esptool.py to an ESP32. + +Command syntax for flashing Tasmota32 firmware on ESP32 via Esptool (replace COM Port Number!): + +esptool.py --chip esp32 --port COM5 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dout --flash_freq 40m --flash_size detect 0x1000 bootloader_dout_40m.bin 0x8000 partitions.bin 0xe000 boot_app0.bin 0x10000 tasmota32.bin diff --git a/tools/decode-status.py b/tools/decode-status.py index a992a621c..67898d784 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -204,7 +204,7 @@ a_features = [[ "USE_KEELOQ","USE_HRXL","USE_SONOFF_D1","USE_HDC1080", "USE_IAQ","USE_DISPLAY_SEVENSEG","USE_AS3935","USE_PING", "USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075", - "USE_VEML7700","","","", + "USE_VEML7700","USE_MCP9808","","", "","","","", "","","","", "","","","", diff --git a/tools/serial-plotter.py b/tools/serial-plotter.py new file mode 100644 index 000000000..83ec6dba6 --- /dev/null +++ b/tools/serial-plotter.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 + +""" + serial-plotter.py - for Tasmota + + Copyright (C) 2020 Christian Baars + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Requirements: + - Python + - pip3 install matplotlib pyserial + - for Windows: Full python install including tkinter + - a Tasmotadriver that plots + +Instructions: + expects serial data in the format: + 'PLOT: graphnumber value' + graph (1-4) + integer value + Code snippet example: (last value will be ignored) + AddLog_P2(LOG_LEVEL_INFO, PSTR("PLOT: %u, %u, %u,"),button_index+1, _value, Button.touch_hits[button_index]); + +Usage: + ./serial-plotter.py --port /dev/PORT --baud BAUD (or change defaults in the script) + set output in tasmota, e.g.; TouchCal 1..4 (via Textbox) + +""" +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.animation as animation +from matplotlib.widgets import TextBox +import time +import serial +import argparse +import sys + +print("Python version") +print (sys.version) + +#default values +port = '/dev/cu.SLAB_USBtoUART' +baud = 115200 + +#command line input +parser = argparse.ArgumentParser() +parser.add_argument("--port", "-p", help="change serial port, default: " + port) +parser.add_argument("--baud", "-b", help="change baud rate, default: " + str(baud)) +args = parser.parse_args() +if args.port: + print("change serial port to %s" % args.port) + port = args.port +if args.baud: + print("change baud rate to %s" % args.baud) + baud = args.baud + + +#time range +dt = 0.01 +t = np.arange(0.0, 100, dt) + +#lists for the data +xs = [0] #counting up x +ys = [[0],[0],[0],[0]] #4 fixed graphs for now +max_y = 1 +# min_y = 0 + +fig = plt.figure('Tasmota Serial Plotter') +ax = fig.add_subplot(111, autoscale_on=True, xlim=(0, 200), ylim=(0, 20)) #fixed x scale for now, y will adapt +ax.grid() + +line1, = ax.plot([], [], color = "r", label='G 1') +line2, = ax.plot([], [], color = "g", label='G 2') +line3, = ax.plot([], [], color = "b", label='G 3') +line4, = ax.plot([], [], color = "y", label='G 4') + +time_template = 'time = %.1fs' +time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes) + +ser = serial.Serial() +ser.port = port +ser.baudrate = baud +ser.timeout = 0 #return immediately +try: + ser.open() +except: + print("Could not connect to serial with settings: " + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') + print("port available?") + exit() + +if ser.is_open==True: + print("Serial Plotter started ...:") + plt.title('connected to ' + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') +else: + print("Could not connect to serial: " + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') + plt.title('NOT connected to ' + str(ser.port) + ' at ' + str(ser.baudrate) + 'baud') + +def init(): + line1.set_data([], []) + line2.set_data([], []) + line3.set_data([], []) + line4.set_data([], []) + time_text.set_text('') + return [line1,line2,line3,line4,time_text ] #was line + + +def parse_line(data_line): + pos = data_line.find("PLOT:", 10) + if pos<0: + # print("wrong format") + return 0,0 + + raw_data = data_line[pos+6:] + val_list = raw_data.split(',') + try: + g = int(val_list[0]) + v = int(val_list[1]) + return g, v + except: + return 0,0 + +def update(num, line1, line2): + global xs, ys, max_y + + time_text.set_text(time_template % (num*dt) ) + + receive_data = str(ser.readline()) #string + + g, v = parse_line(receive_data) + if (g in range(1,5)): + # print(v,g) + if v>max_y: + max_y = v + print(max_y) + ax.set_ylim([0, max_y * 1.2]) + + idx = 0 + for y in ys: + y.append(y[-1]) + if idx == g-1: + y[-1] = v + idx = idx +1 + xs.append(xs[-1]+1) + + if len(ys[0])>200: + xs.pop() + for y in ys: + y.pop(0) + line1.set_data(xs, ys[0]) + line2.set_data(xs, ys[1]) + line3.set_data(xs, ys[2]) + line4.set_data(xs, ys[3]) + return [line1,line2,line3,line4, time_text] + +def handle_close(evt): + print('Closing serial connection') + ser.close() + print('Closed serial plotter') + +def submit(text): + print (text) + ser.write(text.encode() + "\n".encode()) + + +ani = animation.FuncAnimation(fig, update, None, fargs=[line1, line2], + interval=10, blit=True, init_func=init) + +ax.set_xlabel('Last 200 Samples') +ax.set_ylabel('Values') +plt.subplots_adjust(bottom=0.25) +ax.legend(loc='lower right', ncol=2) + +fig.canvas.mpl_connect('close_event', handle_close) + +axbox = plt.axes([0.15, 0.05, 0.7, 0.075]) +text_box = TextBox(axbox, 'Send:', initial='') +text_box.on_submit(submit) + +if ser.is_open==True: + plt.show() + \ No newline at end of file