Merge branch 'development' into HP303B

This commit is contained in:
Robert Jaakke 2020-06-05 20:27:48 +02:00
commit 344d544e43
69 changed files with 3684 additions and 1910 deletions

View File

@ -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 |

View File

@ -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
50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor
51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor

View File

@ -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)

View File

@ -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**

View File

@ -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.

View File

@ -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

View File

@ -0,0 +1,8 @@
# osx
.DS_Store
# doxygen
Doxyfile*
doxygen_sqlite3.db
html
*.tmp

View File

@ -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 <Wire.h>
#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;
}

View File

@ -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 <Wire.h>
#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

View File

@ -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)
<a href="https://www.adafruit.com/product/1782"><img src="assets/board.jpg?raw=true" width="500px"></a>
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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

View File

@ -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 dont 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 <support@adafruit.com>.
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 communitys 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
<https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>,
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.

View File

@ -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 <Wire.h>
#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);
}

View File

@ -0,0 +1,10 @@
name=Adafruit MCP9808 Library
version=1.1.2
author=Adafruit
maintainer=Adafruit <info@adafruit.com>
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

View File

@ -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.

View File

@ -19,7 +19,7 @@
#include <limits.h>
// 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)<<SPI_USR_COMMAND_BITLEN_S)|((uint32)bytetemp); //configure transmission variable,9bit transmission length and first 8 command bit
if(low_8bit&0x01) regvalue|=BIT15; //write the 9th bit
while(READ_PERI_REG(SPI_CMD(1))&SPI_USR); //waiting for spi module available
WRITE_PERI_REG(SPI_USER2(1), regvalue); //write command and command length into spi reg
SET_PERI_REG_MASK(SPI_CMD(1), SPI_USR); //transmission start
}
#endif
// dc = 0
void ILI9488::writecommand(uint8_t c) {
if (_hwspi) {
@ -227,9 +205,6 @@ void ILI9488::writecommand(uint8_t c) {
ILI9488_START
//#define SPI_USR_COMMAND_BITLEN 0x0000000F
//#define SPI_USR_COMMAND_BITLEN_S 28
regvalue= ((8&SPI_USR_COMMAND_BITLEN)<<SPI_USR_COMMAND_BITLEN_S)|((uint32)bytetemp); //configure transmission variable,9bit transmission length and first 8 command bit
if(c&0x01) regvalue|=BIT15; //write the 9th bit
while(READ_PERI_REG(SPI_CMD(1))&SPI_USR); //waiting for spi module available
@ -255,39 +230,74 @@ void ILI9488::writedata(uint8_t d) {
} else fastSPIwrite(d,1);
}
// Rather than a bazillion writecommand() and writedata() calls, screen
// initialization commands and arguments are organized in these tables
// stored in PROGMEM. The table may look bulky, but that's mostly the
// formatting -- storage-wise this is hundreds of bytes more compact
// than the equivalent code. Companion function follows.
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);
/*
// Companion code to the above tables. Reads and issues
// a series of LCD commands stored in PROGMEM byte array.
void ILI9488::commandList(uint8_t *addr) {
uint8_t numCommands, numArgs;
uint16_t ms;
numCommands = pgm_read_byte(addr++); // Number of commands to follow
while(numCommands--) { // For each command...
writecommand(pgm_read_byte(addr++)); // Read, issue command
numArgs = pgm_read_byte(addr++); // Number of args to follow
ms = numArgs & DELAY; // If hibit set, delay follows args
numArgs &= ~DELAY; // Mask out delay bit
while(numArgs--) { // For each argument...
writedata(pgm_read_byte(addr++)); // Read, issue argument
}
if(ms) {
ms = pgm_read_byte(addr++); // Read post-command delay time (ms)
if(ms == 255) ms = 500; // If 255, delay for 500 ms
delay(ms);
}
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);
}
*/
#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;
}
*/

View File

@ -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 {

View File

@ -0,0 +1,7 @@
name=UdpListener
version=1.0
author=Ivan Grokhotkov, Stephan Hadinger
maintainer=Stephan <stephan.hadinger@gmail.com>
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

View File

@ -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 <http://www.gnu.org/licenses/>.@
*/
// 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 <n> 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 <Arduino.h>
extern "C" {
#include <lwip/udp.h>
#include <lwip/igmp.h>
}
template <size_t PACKET_SIZE>
struct UdpPacket {
IPAddress srcaddr;
IPAddress dstaddr;
int16_t srcport;
netif* input_netif;
size_t len;
uint8_t buf[PACKET_SIZE];
};
template <size_t PACKET_SIZE>
class UdpListener
{
public:
typedef std::function<void(void)> 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_SIZE>[_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<PACKET_SIZE> * 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<UdpListener*>(arg)->_recv(upcb, p, srcaddr, srcport);
}
private:
udp_pcb* _pcb;
uint8_t _packet_number;
UdpPacket<PACKET_SIZE> * _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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}

View File

@ -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}

View File

@ -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

View File

@ -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 <core_version.h>
#if defined(ARDUINO_ESP8266_RELEASE_2_4_2)
#warning **** Tasmota is using v2.4.2 patched Parsing.cpp as planned ****
#include <Arduino.h>
#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

View File

@ -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"

View File

@ -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"

View File

@ -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.

View File

@ -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)
};

View File

@ -1459,6 +1459,7 @@ bool I2cValidRead(uint8_t addr, uint8_t reg, uint8_t size)
}
retry--;
}
if (!retry) Wire.endTransmission();
return status;
}

View File

@ -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));
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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 <Ticker.h>
@ -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);
}
}
}
/*********************************************************************************************\

View File

@ -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);
}

View File

@ -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 <Ticker.h>
@ -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<UDP_BUFFER_SIZE> 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<UDP_BUFFER_SIZE> *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

View File

@ -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)

View File

@ -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

View File

@ -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 <ESP8266WebServer.h>
#include <DNSServer.h>
@ -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("<div style='text-align:center;'><b>" D_CONFIGURATION_SAVED "</b><br>"));
@ -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("<div style='text-align:center;'><b>" D_UPGRADE_STARTED " ...</b></div>"));
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("<div style='text-align:center;'><b>" D_UPLOAD " <font color='#"));

View File

@ -1429,6 +1429,10 @@ void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
light_state.getHSB(hue, sat, bri);
}
void LightGetXY(float *X, float *Y) {
light_state.getXY(X, Y);
}
void LightHsToRgb(uint16_t hue, uint8_t sat, uint8_t *r_r, uint8_t *r_g, uint8_t *r_b) {
light_state.HsToRgb(hue, sat, r_r, r_g, r_b);
}

View File

@ -458,6 +458,21 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
rule_param = String(SunMinutes(1));
}
#endif // USE_TIMERS and USE_SUNRISE
// #ifdef USE_ZIGBEE
// if (rule_param.startsWith(F("%ZBDEVICE%"))) {
// snprintf_P(stemp, sizeof(stemp), PSTR("0x%04X"), Z_GetLastDevice());
// rule_param = String(stemp);
// }
// if (rule_param.startsWith(F("%ZBGROUP%"))) {
// rule_param = String(Z_GetLastGroup());
// }
// if (rule_param.startsWith(F("%ZBCLUSTER%"))) {
// rule_param = String(Z_GetLastCluster());
// }
// if (rule_param.startsWith(F("%ZBENDPOINT%"))) {
// rule_param = String(Z_GetLastEndpoint());
// }
// #endif
rule_param.toUpperCase();
strlcpy(rule_svalue, rule_param.c_str(), sizeof(rule_svalue));
@ -701,6 +716,13 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0)));
RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1)));
#endif // USE_TIMERS and USE_SUNRISE
#ifdef USE_ZIGBEE
snprintf_P(stemp, sizeof(stemp), PSTR("0x%04X"), Z_GetLastDevice());
RulesVarReplace(commands, F("%ZBDEVICE%"), String(stemp));
RulesVarReplace(commands, F("%ZBGROUP%"), String(Z_GetLastGroup()));
RulesVarReplace(commands, F("%ZBCLUSTER%"), String(Z_GetLastCluster()));
RulesVarReplace(commands, F("%ZBENDPOINT%"), String(Z_GetLastEndpoint()));
#endif
char command[commands.length() +1];
strlcpy(command, commands.c_str(), sizeof(command));
@ -1261,6 +1283,16 @@ bool findNextVariableValue(char * &pVarname, float &value)
} else if (sVarName.equals(F("SUNSET"))) {
value = SunMinutes(1);
#endif
// #ifdef USE_ZIGBEE
// // } else if (sVarName.equals(F("ZBDEVICE"))) {
// // value = Z_GetLastDevice();
// } else if (sVarName.equals(F("ZBGROUP"))) {
// value = Z_GetLastGroup();
// } else if (sVarName.equals(F("ZBCLUSTER"))) {
// value = Z_GetLastCluster();
// } else if (sVarName.equals(F("ZBENDPOINT"))) {
// value = Z_GetLastEndpoint();
// #endif
} else {
succeed = false;
}

View File

@ -64,7 +64,6 @@ keywords if then else endif, or, and are better readable for beginners (others m
#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS
uint32_t EncodeLightId(uint8_t relay_id);
uint32_t DecodeLightId(uint32_t hue_id);
@ -79,30 +78,40 @@ uint32_t DecodeLightId(uint32_t hue_id);
#endif
#endif // USE_SCRIPT_COMPRESSION
#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)) || (USE_SCRIPT_FATFS==-1)
#ifdef ESP32
#include "FS.h"
#include "SPIFFS.h"
#else
#include <LittleFS.h>
#endif
FS *fsp;
void SaveFile(const char *name,const uint8_t *buf,uint32_t len) {
File file = SPIFFS.open(name, FILE_WRITE);
File file = fsp->open(name, "w");
if (!file) return;
file.write(buf, len);
file.close();
}
#define FORMAT_SPIFFS_IF_FAILED true
uint8_t spiffs_mounted=0;
uint8_t fs_mounted=0;
void LoadFile(const char *name,uint8_t *buf,uint32_t len) {
if (!spiffs_mounted) {
if (!fs_mounted) {
#ifdef ESP32
if(!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)){
#else
if(!fsp->begin()){
#endif
//Serial.println("SPIFFS Mount Failed");
return;
}
spiffs_mounted=1;
fs_mounted=1;
}
File file = SPIFFS.open(name);
File file = fsp->open(name, "r");
if (!file) return;
file.read(buf, len);
file.close();
@ -116,29 +125,43 @@ enum {OPER_EQU=1,OPER_PLS,OPER_MIN,OPER_MUL,OPER_DIV,OPER_PLSEQU,OPER_MINEQU,OPE
enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD};
#ifdef USE_SCRIPT_FATFS
#if USE_SCRIPT_FATFS>=0
#include <SPI.h>
//#define USE_MMC
#ifdef USE_MMC
#include <SD_MMC.h>
#undef FS_USED
#define FS_USED SD_MMC
#else
#include <SD.h>
#undef FS_USED
#define FS_USED SD
#ifdef ESP32
FS *fsp;
#else
SDClass *fsp;
#endif
#endif
#ifndef ESP32
// esp8266
#if USE_SCRIPT_FATFS>=0
// old fs
#undef FILE_WRITE
#define FILE_WRITE (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT)
#define FILE_APPEND (sdfat::O_READ | sdfat::O_WRITE | sdfat::O_CREAT | sdfat::O_APPEND)
#else
// new fs
#undef FILE_WRITE
#define FILE_WRITE "w"
#undef FILE_READ
#define FILE_READ "r"
#undef FILE_APPEND
#define FILE_APPEND "a"
#endif
#endif // USE_SCRIPT_FATFS>=0
#ifndef FAT_SCRIPT_SIZE
#define FAT_SCRIPT_SIZE 4096
#endif
#ifdef ESP32
#undef FAT_SCRIPT_NAME
#define FAT_SCRIPT_NAME "/script.txt"
@ -150,7 +173,8 @@ enum {SCRIPT_LOGLEVEL=1,SCRIPT_TELEPERIOD};
#if USE_STANDARD_SPI_LIBRARY==0
#warning ("FATFS standard spi should be used");
#endif
#endif
#endif // USE_SCRIPT_FATFS
#ifdef SUPPORT_MQTT_EVENT
#include <LinkedList.h> // Import LinkedList library
@ -624,10 +648,18 @@ char *script;
#ifdef USE_SCRIPT_FATFS
if (!glob_script_mem.script_sd_found) {
#if USE_SCRIPT_FATFS>=0
fsp=&SD;
#ifdef USE_MMC
if (FS_USED.begin()) {
if (fsp->begin()) {
#else
if (FS_USED.begin(USE_SCRIPT_FATFS)) {
if (SD.begin(USE_SCRIPT_FATFS)) {
#endif
#else
if (fsp->begin()) {
#endif
glob_script_mem.script_sd_found=1;
} else {
@ -1276,6 +1308,7 @@ chknext:
#endif //USE_ENERGY_SENSOR
break;
case 'f':
//#define DEBUG_FS
#ifdef USE_SCRIPT_FATFS
if (!strncmp(vname,"fo(",3)) {
lp+=3;
@ -1304,7 +1337,10 @@ chknext:
for (uint8_t cnt=0;cnt<SFS_MAX;cnt++) {
if (!glob_script_mem.file_flags[cnt].is_open) {
if (mode==0) {
glob_script_mem.files[cnt]=FS_USED.open(str,FILE_READ);
#ifdef DEBUG_FS
AddLog_P2(LOG_LEVEL_INFO,PSTR("open file for read %d"),cnt);
#endif
glob_script_mem.files[cnt]=fsp->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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
Replace_Cmd_Vars(line,0,tmp,sizeof(tmp));
Replace_Cmd_Vars(lp,1,tmp,sizeof(tmp));
// check for hue defintions
// NAME, TYPE , vars
cp=tmp;
@ -5518,8 +5553,7 @@ nextwebline:
void script_send_email_body(void(*func)(char *)) {
uint8_t msect=Run_Scripter(">m",-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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
Replace_Cmd_Vars(line,0,tmp,sizeof(tmp));
Replace_Cmd_Vars(lp,1,tmp,sizeof(tmp));
//client->println(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; i<sizeof(line); i++) {
if (!*cp || *cp=='\n' || *cp=='\r') {
*cp=0;
break;
}
cp++;
}
Replace_Cmd_Vars(line,0,tmp,sizeof(tmp));
Replace_Cmd_Vars(lp,1,tmp,sizeof(tmp));
ResponseAppend_P(PSTR("%s"),tmp);
}
if (*lp==SCRIPT_EOL) {
@ -5607,6 +5620,67 @@ bool RulesProcessEvent(char *json_event) {
#ifdef ESP32
#ifdef USE_SCRIPT_TASK
#ifndef STASK_STACK
#define STASK_STACK 8192
#endif
#ifndef STASK_PRIO
#define STASK_PRIO 1
#endif
#if 1
struct ESP32_Task {
uint16_t task_timer;
TaskHandle_t task_t;
} esp32_tasks[2];
void script_task1(void *arg) {
//uint32_t lastms=millis();
//uint32_t time;
while (1) {
//time=millis()-lastms;
//lastms=millis();
//time=esp32_tasks[0].task_timer-time;
//if (time<esp32_tasks[1].task_timer) {delay(time); }
//if (time<=esp32_tasks[0].task_timer) {vTaskDelay( pdMS_TO_TICKS( time ) ); }
delay(esp32_tasks[0].task_timer);
Run_Scripter(">t1",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 (time<esp32_tasks[1].task_timer) {delay(time); }
//if (time<=esp32_tasks[1].task_timer) {vTaskDelay( pdMS_TO_TICKS( time ) ); }
delay(esp32_tasks[1].task_timer);
Run_Scripter(">t2",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;

View File

@ -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 <SD.h>
#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;

View File

@ -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;

View File

@ -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<T> & 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

File diff suppressed because it is too large Load Diff

View File

@ -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)

View File

@ -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

View File

@ -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<JsonObject>()) {
// we have a high-level command
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
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<bool>()) {
x = value.as<bool>() ? 1 : 0;
} else if (value.is<unsigned int>()) {
x = value.as<unsigned int>();
} else {
// if non-bool or non-int, trying char*
const char *s_const = value.as<const char*>();
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<const char*>()) {
// low-level command
cmd_str = val_cmd.as<String>();
// 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<JsonArray>()) {
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
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<JsonObject>()) {
const JsonObject& attr_obj = val_attr.as<const JsonObject&>();
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<char*>());
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<JsonObject>()) {
// we have a high-level command
const JsonObject &cmd_obj = val_cmd.as<const JsonObject&>();
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<bool>()) {
x = value.as<bool>() ? 1 : 0;
} else if (value.is<unsigned int>()) {
x = value.as<unsigned int>();
} else {
// if non-bool or non-int, trying char*
const char *s_const = value.as<const char*>();
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<const char*>()) {
// low-level command
cmd_str = val_cmd.as<String>();
// 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<JsonObject>()) {
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<JsonObject>()) {
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<JsonObject>()) {
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<char*>());
}
@ -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<char*>());
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<JsonArray>()) {
const JsonArray& attr_arr = val_attr.as<const JsonArray&>();
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

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -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;

10
tasmota/xsns_52_ibeacon.ino Normal file → Executable file
View File

@ -25,6 +25,8 @@
#include <TasmotaSerial.h>
#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

View File

@ -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;
}

View File

@ -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);

View File

@ -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

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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);

136
tasmota/xsns_72_mcp9808.ino Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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

View File

@ -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

View File

@ -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","","",
"","","","",
"","","","",
"","","","",

192
tools/serial-plotter.py Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
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()