From 66679515053d907c7cd81234121a0bcb106df197 Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Sat, 6 Jun 2020 22:35:41 +0200 Subject: [PATCH 01/35] Working sample --- lib/LOLIN_HP303B/README.md | 4 + .../i2c_background/i2c_background.ino | 98 + .../examples/i2c_command/i2c_command.ino | 75 + .../examples/i2c_interrupt/i2c_interrupt.ino | 112 ++ lib/LOLIN_HP303B/keywords.txt | 26 + lib/LOLIN_HP303B/library.properties | 9 + lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp | 1668 +++++++++++++++++ lib/LOLIN_HP303B/src/LOLIN_HP303B.h | 144 ++ lib/LOLIN_HP303B/src/util/hp303b_consts.h | 258 +++ tasmota/support_features.ino | 4 +- tasmota/xsns_73_hp303b.ino | 161 ++ tools/decode-status.py | 2 +- 12 files changed, 2559 insertions(+), 2 deletions(-) create mode 100644 lib/LOLIN_HP303B/README.md create mode 100644 lib/LOLIN_HP303B/examples/i2c_background/i2c_background.ino create mode 100644 lib/LOLIN_HP303B/examples/i2c_command/i2c_command.ino create mode 100644 lib/LOLIN_HP303B/examples/i2c_interrupt/i2c_interrupt.ino create mode 100644 lib/LOLIN_HP303B/keywords.txt create mode 100644 lib/LOLIN_HP303B/library.properties create mode 100644 lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp create mode 100644 lib/LOLIN_HP303B/src/LOLIN_HP303B.h create mode 100644 lib/LOLIN_HP303B/src/util/hp303b_consts.h create mode 100644 tasmota/xsns_73_hp303b.ino diff --git a/lib/LOLIN_HP303B/README.md b/lib/LOLIN_HP303B/README.md new file mode 100644 index 000000000..24d6c2d1b --- /dev/null +++ b/lib/LOLIN_HP303B/README.md @@ -0,0 +1,4 @@ +# Arduino library for the HP303B +### Installation +- Clone this repository or download&unzip [zip file](https://github.com/wemos/LOLIN_HP303B_Library/archive/master.zip) into Arduino/libraries + diff --git a/lib/LOLIN_HP303B/examples/i2c_background/i2c_background.ino b/lib/LOLIN_HP303B/examples/i2c_background/i2c_background.ino new file mode 100644 index 000000000..1b9ae6e68 --- /dev/null +++ b/lib/LOLIN_HP303B/examples/i2c_background/i2c_background.ino @@ -0,0 +1,98 @@ +#include + +// HP303B Opject +LOLIN_HP303B HP303BPressureSensor = LOLIN_HP303B(); + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + //Call begin to initialize HP303BPressureSensor + //The parameter 0x76 is the bus address. The default address is 0x77 and does not need to be given. + //HP303BPressureSensor.begin(Wire, 0x76); + //Use the commented line below instead to use the default I2C address. + HP303BPressureSensor.begin(); + + //temperature measure rate (value from 0 to 7) + //2^temp_mr temperature measurement results per second + int16_t temp_mr = 2; + //temperature oversampling rate (value from 0 to 7) + //2^temp_osr internal temperature measurements per result + //A higher value increases precision + int16_t temp_osr = 2; + //pressure measure rate (value from 0 to 7) + //2^prs_mr pressure measurement results per second + int16_t prs_mr = 2; + //pressure oversampling rate (value from 0 to 7) + //2^prs_osr internal pressure measurements per result + //A higher value increases precision + int16_t prs_osr = 2; + //startMeasureBothCont enables background mode + //temperature and pressure ar measured automatically + //High precision and hgh measure rates at the same time are not available. + //Consult Datasheet (or trial and error) for more information + int16_t ret = HP303BPressureSensor.startMeasureBothCont(temp_mr, temp_osr, prs_mr, prs_osr); + //Use one of the commented lines below instead to measure only temperature or pressure + //int16_t ret = HP303BPressureSensor.startMeasureTempCont(temp_mr, temp_osr); + //int16_t ret = HP303BPressureSensor.startMeasurePressureCont(prs_mr, prs_osr); + + + if (ret != 0) + { + Serial.print("Init FAILED! ret = "); + Serial.println(ret); + } + else + { + Serial.println("Init complete!"); + } +} + + + +void loop() +{ + unsigned char pressureCount = 20; + int32_t pressure[pressureCount]; + unsigned char temperatureCount = 20; + int32_t temperature[temperatureCount]; + + //This function writes the results of continuous measurements to the arrays given as parameters + //The parameters temperatureCount and pressureCount should hold the sizes of the arrays temperature and pressure when the function is called + //After the end of the function, temperatureCount and pressureCount hold the numbers of values written to the arrays + //Note: The HP303B cannot save more than 32 results. When its result buffer is full, it won't save any new measurement results + int16_t ret = HP303BPressureSensor.getContResults(temperature, temperatureCount, pressure, pressureCount); + + if (ret != 0) + { + Serial.println(); + Serial.println(); + Serial.print("FAIL! ret = "); + Serial.println(ret); + } + else + { + Serial.println(); + Serial.println(); + Serial.print(temperatureCount); + Serial.println(" temperature values found: "); + for (int16_t i = 0; i < temperatureCount; i++) + { + Serial.print(temperature[i]); + Serial.println(" degrees of Celsius"); + } + + Serial.println(); + Serial.print(pressureCount); + Serial.println(" pressure values found: "); + for (int16_t i = 0; i < pressureCount; i++) + { + Serial.print(pressure[i]); + Serial.println(" Pascal"); + } + } + + //Wait some time, so that the HP303B can refill its buffer + delay(10000); +} diff --git a/lib/LOLIN_HP303B/examples/i2c_command/i2c_command.ino b/lib/LOLIN_HP303B/examples/i2c_command/i2c_command.ino new file mode 100644 index 000000000..e629f7d5f --- /dev/null +++ b/lib/LOLIN_HP303B/examples/i2c_command/i2c_command.ino @@ -0,0 +1,75 @@ +#include + +// HP303B Opject +LOLIN_HP303B HP303BPressureSensor; + + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + + //Call begin to initialize HP303BPressureSensor + //The parameter 0x76 is the bus address. The default address is 0x77 and does not need to be given. + //HP303BPressureSensor.begin(Wire, 0x76); + //Use the commented line below instead of the one above to use the default I2C address. + //if you are using the Pressure 3 click Board, you need 0x76 + HP303BPressureSensor.begin(); + + Serial.println("Init complete!"); +} + + + +void loop() +{ + int32_t temperature; + int32_t pressure; + int16_t oversampling = 7; + int16_t ret; + Serial.println(); + + //lets the HP303B perform a Single temperature measurement with the last (or standard) configuration + //The result will be written to the paramerter temperature + //ret = HP303BPressureSensor.measureTempOnce(temperature); + //the commented line below does exactly the same as the one above, but you can also config the precision + //oversampling can be a value from 0 to 7 + //the HP303B will perform 2^oversampling internal temperature measurements and combine them to one result with higher precision + //measurements with higher precision take more time, consult datasheet for more information + ret = HP303BPressureSensor.measureTempOnce(temperature, oversampling); + + if (ret != 0) + { + //Something went wrong. + //Look at the library code for more information about return codes + Serial.print("FAIL! ret = "); + Serial.println(ret); + } + else + { + Serial.print("Temperature: "); + Serial.print(temperature); + Serial.println(" degrees of Celsius"); + } + + //Pressure measurement behaves like temperature measurement + //ret = HP303BPressureSensor.measurePressureOnce(pressure); + ret = HP303BPressureSensor.measurePressureOnce(pressure, oversampling); + if (ret != 0) + { + //Something went wrong. + //Look at the library code for more information about return codes + Serial.print("FAIL! ret = "); + Serial.println(ret); + } + else + { + Serial.print("Pressure: "); + Serial.print(pressure); + Serial.println(" Pascal"); + } + + //Wait some time + delay(500); +} diff --git a/lib/LOLIN_HP303B/examples/i2c_interrupt/i2c_interrupt.ino b/lib/LOLIN_HP303B/examples/i2c_interrupt/i2c_interrupt.ino new file mode 100644 index 000000000..d1221109b --- /dev/null +++ b/lib/LOLIN_HP303B/examples/i2c_interrupt/i2c_interrupt.ino @@ -0,0 +1,112 @@ +#include + +// HP303B Opject +LOLIN_HP303B HP303BPressureSensor = LOLIN_HP303B(); + +void onFifoFull(); + +const unsigned char pressureLength = 50; +unsigned char pressureCount = 0; +int32_t pressure[pressureLength]; +unsigned char temperatureCount = 0; +const unsigned char temperatureLength = 50; +int32_t temperature[temperatureLength]; + + + +void setup() +{ + Serial.begin(115200); + while (!Serial); + + //Call begin to initialize HP303BPressureSensor + //The parameter 0x76 is the bus address. The default address is 0x77 and does not need to be given. + //HP303BPressureSensor.begin(Wire, 0x76); + //Use the commented line below instead to use the default I2C address. + HP303BPressureSensor.begin(); + + int16_t ret = HP303BPressureSensor.setInterruptPolarity(1); + ret = HP303BPressureSensor.setInterruptSources(1, 0, 0); + //clear interrupt flag by reading + HP303BPressureSensor.getIntStatusFifoFull(); + + //initialization of Interrupt for Controller unit + //SDO pin of HP303B has to be connected with interrupt pin + int16_t interruptPin = 3; + pinMode(interruptPin, INPUT); + attachInterrupt(digitalPinToInterrupt(interruptPin), onFifoFull, RISING); + + //start of a continuous measurement just like before + int16_t temp_mr = 3; + int16_t temp_osr = 2; + int16_t prs_mr = 1; + int16_t prs_osr = 3; + ret = HP303BPressureSensor.startMeasureBothCont(temp_mr, temp_osr, prs_mr, prs_osr); + if (ret != 0) + { + Serial.print("Init FAILED! ret = "); + Serial.println(ret); + } + else + { + Serial.println("Init complete!"); + } +} + + +void loop() +{ + //do other stuff + Serial.println("loop running"); + delay(500); + + + //if result arrays are full + //This could also be in the interrupt handler, but it would take too much time for a proper ISR + if (pressureCount == pressureLength && temperatureCount == temperatureLength) + { + //print results + Serial.println(); + Serial.println(); + Serial.print(temperatureCount); + Serial.println(" temperature values found: "); + for (int16_t i = 0; i < temperatureCount; i++) + { + Serial.print(temperature[i]); + Serial.println(" degrees of Celsius"); + } + Serial.println(); + Serial.print(pressureCount); + Serial.println(" pressure values found: "); + for (int16_t i = 0; i < pressureCount; i++) + { + Serial.print(pressure[i]); + Serial.println(" Pascal"); + } + Serial.println(); + Serial.println(); + //reset result counters + pressureCount = 0; + temperatureCount = 0; + } +} + + +//interrupt handler +void onFifoFull() +{ + //message for debugging + Serial.println("Interrupt handler called"); + + //clear interrupt flag by reading + HP303BPressureSensor.getIntStatusFifoFull(); + + //calculate the number of free indexes in the result arrays + unsigned char prs_freespace = pressureLength - pressureCount; + unsigned char temp_freespace = temperatureLength - temperatureCount; + //read the results from HP303B, new results will be added at the end of the arrays + HP303BPressureSensor.getContResults(&temperature[temperatureCount], temp_freespace, &pressure[pressureCount], prs_freespace); + //after reading the result counters are increased by the amount of new results + pressureCount += prs_freespace; + temperatureCount += temp_freespace; +} diff --git a/lib/LOLIN_HP303B/keywords.txt b/lib/LOLIN_HP303B/keywords.txt new file mode 100644 index 000000000..b283317f5 --- /dev/null +++ b/lib/LOLIN_HP303B/keywords.txt @@ -0,0 +1,26 @@ +####################################### +# Syntax Coloring Map For DHT12 +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### + +DHT12 KEYWORD1 + +####################################### +# Methods and Functions (KEYWORD2) +####################################### + +get KEYWORD2 + +####################################### +# Constants (LITERAL1) +####################################### + +cTemp LITERAL1 +fTemp LITERAL1 +humidity LITERAL1 + + + diff --git a/lib/LOLIN_HP303B/library.properties b/lib/LOLIN_HP303B/library.properties new file mode 100644 index 000000000..7f71ad2b2 --- /dev/null +++ b/lib/LOLIN_HP303B/library.properties @@ -0,0 +1,9 @@ +name=LOLIN_HP303B +version=1.0.0 +author=WEMOS.CC +maintainer=WEMOS.CC +sentence=Library for the HP303B.. +paragraph=LOLIN HP303B +category=Device Control +url=https://github.com/wemos/LOLIN_HP303B_Library +architectures=* diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp new file mode 100644 index 000000000..43fcd435e --- /dev/null +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp @@ -0,0 +1,1668 @@ +#include "LOLIN_HP303B.h" + + + +const int32_t LOLIN_HP303B::scaling_facts[HP303B__NUM_OF_SCAL_FACTS] + = {524288, 1572864, 3670016, 7864320, 253952, 516096, 1040384, 2088960}; + + + +//////// Constructor, Destructor, begin, end //////// + + +/** + * Standard Constructor + */ +LOLIN_HP303B::LOLIN_HP303B(void) +{ + //assume that initialization has failed before it has been done + m_initFail = 1U; +} + +/** + * Standard Destructor + */ +LOLIN_HP303B::~LOLIN_HP303B(void) +{ + end(); +} + + + +/** + * Standard I2C begin function + * + * &bus: I2CBus which connects MC to HP303B + * slaveAddress: Address of the HP303B (0x77 or 0x76) + */ +void LOLIN_HP303B::begin(TwoWire &bus, uint8_t slaveAddress) +{ + //this flag will show if the initialization was successful + m_initFail = 0U; + + //Set I2C bus connection + m_SpiI2c = 1U; + m_i2cbus = &bus; + m_slaveAddress = slaveAddress; + + // Init bus + m_i2cbus->begin(); + + delay(50); //startup time of HP303B + + init(); +} + +void LOLIN_HP303B::begin(uint8_t slaveAddress) +{ + begin(Wire,slaveAddress); +} + +/** + * SPI begin function for HP303B with 4-wire SPI + */ +void LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect) +{ + begin(bus, chipSelect, 0U); +} + +/** + * Standard SPI begin function + * + * &bus: SPI bus which connects MC to HP303B + * chipSelect: Number of the CS line for the HP303B + * threeWire: 1 if HP303B is connected with 3-wire SPI + * 0 if HP303B is connected with 4-wire SPI (standard) + */ +void LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire) +{ + //this flag will show if the initialization was successful + m_initFail = 0U; + + //Set SPI bus connection + m_SpiI2c = 0U; + m_spibus = &bus; + m_chipSelect = chipSelect; + + // Init bus + m_spibus->begin(); + m_spibus->setDataMode(SPI_MODE3); + + pinMode(m_chipSelect, OUTPUT); + digitalWrite(m_chipSelect, HIGH); + + delay(50); //startup time of HP303B + + //switch to 3-wire mode if necessary + //do not use writeByteBitfield or check option to set SPI mode! + //Reading is not possible until SPI-mode is valid + if(threeWire) + { + m_threeWire = 1U; + if(writeByte(HP303B__REG_ADR_SPI3W, HP303B__REG_CONTENT_SPI3W)) + { + m_initFail = 1U; + return; + } + } + + init(); +} + +/** + * End function for HP303B + * Sets the sensor to idle mode + */ +void LOLIN_HP303B::end(void) +{ + standby(); +} + + +//////// Declaration of other public functions starts here //////// + + +/** + * returns the Product ID of the connected HP303B sensor + */ +uint8_t LOLIN_HP303B::getProductId(void) +{ + return m_productID; +} + +/** + * returns the Revision ID of the connected HP303B sensor + */ +uint8_t LOLIN_HP303B::getRevisionId(void) +{ + return m_revisionID; +} + +/** + * Sets the HP303B to standby mode + * + * returns: 0 on success + * -2 if object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::standby(void) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //set device to idling mode + int16_t ret = setOpMode(IDLE); + if(ret != HP303B__SUCCEEDED) + { + return ret; + } + //flush the FIFO + ret = writeByteBitfield(1U, HP303B__REG_INFO_FIFO_FL); + if(ret < 0) + { + return ret; + } + //disable the FIFO + ret = writeByteBitfield(0U, HP303B__REG_INFO_FIFO_EN); + return ret; +} + + +/** + * performs one temperature measurement and writes result to the given address + * + * &result: reference to a 32-Bit signed Integer value where the result will be written + * It will not be written if result==NULL + * returns: 0 on success + * -4 if the HP303B is could not finish its measurement in time + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::measureTempOnce(int32_t &result) +{ + return measureTempOnce(result, m_tempOsr); +} + +/** + * performs one temperature measurement and writes result to the given address + * the desired precision can be set with oversamplingRate + * + * &result: reference to a 32-Bit signed Integer where the result will be written + * It will not be written if result==NULL + * oversamplingRate: a value from 0 to 7 that decides about the precision + * of the measurement + * If this value equals n, the HP303B will perform + * 2^n measurements and combine the results + * returns: 0 on success + * -4 if the HP303B is could not finish its measurement in time + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::measureTempOnce(int32_t &result, uint8_t oversamplingRate) +{ + //Start measurement + int16_t ret = startMeasureTempOnce(oversamplingRate); + if(ret!=HP303B__SUCCEEDED) + { + return ret; + } + + //wait until measurement is finished + delay(calcBusyTime(0U, m_tempOsr)/HP303B__BUSYTIME_SCALING); + delay(HP303B__BUSYTIME_FAILSAFE); + + ret = getSingleResult(result); + if(ret!=HP303B__SUCCEEDED) + { + standby(); + } + return ret; +} + +/** + * starts a single temperature measurement + * + * returns: 0 on success + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::startMeasureTempOnce(void) +{ + return startMeasureTempOnce(m_tempOsr); +} + +/** + * starts a single temperature measurement + * The desired precision can be set with oversamplingRate + * + * oversamplingRate: a value from 0 to 7 that decides about the precision + * of the measurement + * If this value equals n, the HP303B will perform + * 2^n measurements and combine the results + * returns: 0 on success + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::startMeasureTempOnce(uint8_t oversamplingRate) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //abort if device is not in idling mode + if(m_opMode!=IDLE) + { + return HP303B__FAIL_TOOBUSY; + } + + if(oversamplingRate!=m_tempOsr) + { + //configuration of oversampling rate + if(configTemp(0U, oversamplingRate) != HP303B__SUCCEEDED) + { + return HP303B__FAIL_UNKNOWN; + } + } + + //set device to temperature measuring mode + return setOpMode(0U, 1U, 0U); +} + +/** + * performs one pressure measurement and writes result to the given address + * + * &result: reference to a 32-Bit signed Integer value where the result will be written + * It will not be written if result==NULL + * returns: 0 on success + * -4 if the HP303B is could not finish its measurement in time + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::measurePressureOnce(int32_t &result) +{ + return measurePressureOnce(result, m_prsOsr); +} + +/** + * performs one pressure measurement and writes result to the given address + * the desired precision can be set with oversamplingRate + * + * &result: reference to a 32-Bit signed Integer where the result will be written + * It will not be written if result==NULL + * oversamplingRate: a value from 0 to 7 that decides about the precision + * of the measurement + * If this value equals n, the HP303B will perform + * 2^n measurements and combine the results + * returns: 0 on success + * -4 if the HP303B is could not finish its measurement in time + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::measurePressureOnce(int32_t &result, uint8_t oversamplingRate) +{ + //start the measurement + int16_t ret = startMeasurePressureOnce(oversamplingRate); + if(ret != HP303B__SUCCEEDED) + { + return ret; + } + + //wait until measurement is finished + delay(calcBusyTime(0U, m_prsOsr)/HP303B__BUSYTIME_SCALING); + delay(HP303B__BUSYTIME_FAILSAFE); + + ret = getSingleResult(result); + if(ret!=HP303B__SUCCEEDED) + { + standby(); + } + return ret; +} + +/** + * starts a single pressure measurement + * + * returns: 0 on success + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::startMeasurePressureOnce(void) +{ + return startMeasurePressureOnce(m_prsOsr); +} + +/** + * starts a single pressure measurement + * The desired precision can be set with oversamplingRate + * + * oversamplingRate: a value from 0 to 7 that decides about the precision + * of the measurement + * If this value equals n, the HP303B will perform + * 2^n measurements and combine the results + * returns: 0 on success + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::startMeasurePressureOnce(uint8_t oversamplingRate) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //abort if device is not in idling mode + if(m_opMode != IDLE) + { + return HP303B__FAIL_TOOBUSY; + } + //configuration of oversampling rate, lowest measure rate to avoid conflicts + if(oversamplingRate != m_prsOsr) + { + if(configPressure(0U, oversamplingRate)) + { + return HP303B__FAIL_UNKNOWN; + } + } + //set device to pressure measuring mode + return setOpMode(0U, 0U, 1U); +} + +/** + * gets the result a single temperature or pressure measurement in °C or Pa + * + * &result: reference to a 32-Bit signed Integer value where the result will be written + * returns: 0 on success + * -4 if the HP303B is still busy + * -3 if the HP303B is not in command mode + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::getSingleResult(int32_t &result) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + + //read finished bit for current opMode + int16_t rdy; + switch(m_opMode) + { + case CMD_TEMP: //temperature + rdy = readByteBitfield(HP303B__REG_INFO_TEMP_RDY); + break; + case CMD_PRS: //pressure + rdy = readByteBitfield(HP303B__REG_INFO_PRS_RDY); + break; + default: //HP303B not in command mode + return HP303B__FAIL_TOOBUSY; + } + + //read new measurement result + switch(rdy) + { + case HP303B__FAIL_UNKNOWN: //could not read ready flag + return HP303B__FAIL_UNKNOWN; + case 0: //ready flag not set, measurement still in progress + return HP303B__FAIL_UNFINISHED; + case 1: //measurement ready, expected case + LOLIN_HP303B::Mode oldMode = m_opMode; + m_opMode = IDLE; //opcode was automatically reseted by HP303B + switch(oldMode) + { + case CMD_TEMP: //temperature + return getTemp(&result); //get and calculate the temperature value + case CMD_PRS: //pressure + return getPressure(&result); //get and calculate the pressure value + default: + return HP303B__FAIL_UNKNOWN; //should already be filtered above + } + } + return HP303B__FAIL_UNKNOWN; +} + +/** + * starts a continuous temperature measurement + * The desired precision can be set with oversamplingRate + * The desired number of measurements per second can be set with measureRate + * + * measureRate: a value from 0 to 7 that decides about + * the number of measurements per second + * If this value equals n, the HP303B will perform + * 2^n measurements per second + * oversamplingRate: a value from 0 to 7 that decides about + * the precision of the measurements + * If this value equals m, the HP303B will perform + * 2^m internal measurements and combine the results + * to one more exact measurement + * returns: 0 on success + * -4 if measureRate or oversamplingRate is too high + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + * NOTE: If measure rate is n and oversampling rate is m, + * the HP303B performs 2^(n+m) internal measurements per second. + * The HP303B cannot operate with high precision and high speed + * at the same time. + * Consult the datasheet for more information. + */ +int16_t LOLIN_HP303B::startMeasureTempCont(uint8_t measureRate, uint8_t oversamplingRate) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //abort if device is not in idling mode + if(m_opMode != IDLE) + { + return HP303B__FAIL_TOOBUSY; + } + //abort if speed and precision are too high + if(calcBusyTime(measureRate, oversamplingRate) >= HP303B__MAX_BUSYTIME) + { + return HP303B__FAIL_UNFINISHED; + } + //update precision and measuring rate + if(configTemp(measureRate, oversamplingRate)) + { + return HP303B__FAIL_UNKNOWN; + } + //enable result FIFO + if(writeByteBitfield(1U, HP303B__REG_INFO_FIFO_EN)) + { + return HP303B__FAIL_UNKNOWN; + } + //Start measuring in background mode + if(setOpMode(1U, 1U, 0U)) + { + return HP303B__FAIL_UNKNOWN; + } + return HP303B__SUCCEEDED; +} + + +/** + * starts a continuous temperature measurement + * The desired precision can be set with oversamplingRate + * The desired number of measurements per second can be set with measureRate + * + * measureRate: a value from 0 to 7 that decides about + * the number of measurements per second + * If this value equals n, the HP303B will perform + * 2^n measurements per second + * oversamplingRate: a value from 0 to 7 that decides about the precision + * of the measurements + * If this value equals m, the HP303B will perform + * 2^m internal measurements + * and combine the results to one more exact measurement + * returns: 0 on success + * -4 if measureRate or oversamplingRate is too high + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + * NOTE: If measure rate is n and oversampling rate is m, + * the HP303B performs 2^(n+m) internal measurements per second. + * The HP303B cannot operate with high precision and high speed + * at the same time. + * Consult the datasheet for more information. + */ +int16_t LOLIN_HP303B::startMeasurePressureCont(uint8_t measureRate, uint8_t oversamplingRate) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //abort if device is not in idling mode + if(m_opMode != IDLE) + { + return HP303B__FAIL_TOOBUSY; + } + //abort if speed and precision are too high + if(calcBusyTime(measureRate, oversamplingRate) >= HP303B__MAX_BUSYTIME) + { + return HP303B__FAIL_UNFINISHED; + } + //update precision and measuring rate + if(configPressure(measureRate, oversamplingRate)) + return HP303B__FAIL_UNKNOWN; + //enable result FIFO + if(writeByteBitfield(1U, HP303B__REG_INFO_FIFO_EN)) + { + return HP303B__FAIL_UNKNOWN; + } + //Start measuring in background mode + if(setOpMode(1U, 0U, 1U)) + { + return HP303B__FAIL_UNKNOWN; + } + return HP303B__SUCCEEDED; +} + +/** + * starts a continuous temperature and pressure measurement + * The desired precision can be set with tempOsr and prsOsr + * The desired number of measurements per second can be set with tempMr and prsMr + * + * tempMr measure rate for temperature + * tempOsr oversampling rate for temperature + * prsMr measure rate for pressure + * prsOsr oversampling rate for pressure + * returns: 0 on success + * -4 if precision or speed is too high + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + * NOTE: High precision and speed for both temperature and pressure + * can not be reached at the same time. + * Estimated time for temperature and pressure measurement + * is the sum of both values. + * This sum must not be more than 1 second. + * Consult the datasheet for more information. + */ +int16_t LOLIN_HP303B::startMeasureBothCont(uint8_t tempMr, + uint8_t tempOsr, + uint8_t prsMr, + uint8_t prsOsr) +{ + //abort if initialization failed + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //abort if device is not in idling mode + if(m_opMode!=IDLE) + { + return HP303B__FAIL_TOOBUSY; + } + //abort if speed and precision are too high + if(calcBusyTime(tempMr, tempOsr) + calcBusyTime(prsMr, prsOsr)>=HP303B__MAX_BUSYTIME) + { + return HP303B__FAIL_UNFINISHED; + } + //update precision and measuring rate + if(configTemp(tempMr, tempOsr)) + { + return HP303B__FAIL_UNKNOWN; + } + //update precision and measuring rate + if(configPressure(prsMr, prsOsr)) + return HP303B__FAIL_UNKNOWN; + //enable result FIFO + if(writeByteBitfield(1U, HP303B__REG_INFO_FIFO_EN)) + { + return HP303B__FAIL_UNKNOWN; + } + //Start measuring in background mode + if(setOpMode(1U, 1U, 1U)) + { + return HP303B__FAIL_UNKNOWN; + } + return HP303B__SUCCEEDED; +} + +/** + * Gets the results from continuous measurements and writes them to given arrays + * + * *tempBuffer: The start address of the buffer where the temperature results + * are written + * If this is NULL, no temperature results will be written out + * &tempCount: This has to be a reference to a number which contains + * the size of the buffer for temperature results. + * When the function ends, it will contain + * the number of bytes written to the buffer + * *prsBuffer: The start address of the buffer where the pressure results + * are written + * If this is NULL, no pressure results will be written out + * &prsCount: This has to be a reference to a number which contains + * the size of the buffer for pressure results. + * When the function ends, it will contain + * the number of bytes written to the buffer + * returns: 0 on success + * -3 if HP303B is not in background mode + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::getContResults(int32_t *tempBuffer, + uint8_t &tempCount, + int32_t *prsBuffer, + uint8_t &prsCount) +{ + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + //abort if device is not in background mode + if(!(m_opMode & INVAL_OP_CONT_NONE)) + { + return HP303B__FAIL_TOOBUSY; + } + + //prepare parameters for buffer length and count + uint8_t tempLen = tempCount; + uint8_t prsLen = prsCount; + tempCount = 0U; + prsCount = 0U; + + //while FIFO is not empty + while(readByteBitfield(HP303B__REG_INFO_FIFO_EMPTY) == 0) + { + int32_t result; + //read next result from FIFO + int16_t type = getFIFOvalue(&result); + switch(type) + { + case 0: //temperature + //calculate compensated pressure value + result = calcTemp(result); + //if buffer exists and is not full + //write result to buffer and increase temperature result counter + if(tempBuffer != NULL) + { + if(tempCount> HP303B__REG_SHIFT_INT_EN_FIFO; + tempReady &= HP303B__REG_MASK_INT_EN_TEMP >> HP303B__REG_SHIFT_INT_EN_TEMP; + prsReady &= HP303B__REG_MASK_INT_EN_PRS >> HP303B__REG_SHIFT_INT_EN_PRS; + //read old value from register + int16_t regData = readByte(HP303B__REG_ADR_INT_EN_FIFO); + if(regData <0) + { + return HP303B__FAIL_UNKNOWN; + } + uint8_t toWrite = (uint8_t)regData; + //update FIFO enable bit + toWrite &= ~HP303B__REG_MASK_INT_EN_FIFO; //clear bit + toWrite |= fifoFull << HP303B__REG_SHIFT_INT_EN_FIFO; //set new bit + //update TempReady enable bit + toWrite &= ~HP303B__REG_MASK_INT_EN_TEMP; + toWrite |= tempReady << HP303B__REG_SHIFT_INT_EN_TEMP; + //update PrsReady enable bit + toWrite &= ~HP303B__REG_MASK_INT_EN_PRS; + toWrite |= prsReady << HP303B__REG_SHIFT_INT_EN_PRS; + //write updated value to register + return writeByte(HP303B__REG_ADR_INT_EN_FIFO, toWrite); +} + +/** + * Gets the interrupt status flag of the FIFO + * + * Returns: 1 if the FIFO is full and caused an interrupt + * 0 if the FIFO is not full or FIFO interrupt is disabled + * -1 on fail + */ +int16_t LOLIN_HP303B::getIntStatusFifoFull(void) +{ + return readByteBitfield(HP303B__REG_INFO_INT_FLAG_FIFO); +} + +/** + * Gets the interrupt status flag that indicates a finished temperature measurement + * + * Returns: 1 if a finished temperature measurement caused an interrupt + * 0 if there is no finished temperature measurement + * or interrupts are disabled + * -1 on fail + */ +int16_t LOLIN_HP303B::getIntStatusTempReady(void) +{ + return readByteBitfield(HP303B__REG_INFO_INT_FLAG_TEMP); +} + +/** + * Gets the interrupt status flag that indicates a finished pressure measurement + * + * Returns: 1 if a finished pressure measurement caused an interrupt + * 0 if there is no finished pressure measurement + * or interrupts are disabled + * -1 on fail + */ +int16_t LOLIN_HP303B::getIntStatusPrsReady(void) +{ + return readByteBitfield(HP303B__REG_INFO_INT_FLAG_PRS); +} + +/** + * Function to fix a hardware problem on some devices + * You have this problem if you measure a temperature which is too high (e.g. 60°C when temperature is around 20°C) + * Call correctTemp() directly after begin() to fix this issue + */ +int16_t LOLIN_HP303B::correctTemp(void) +{ + if(m_initFail) + { + return HP303B__FAIL_INIT_FAILED; + } + writeByte(0x0E, 0xA5); + writeByte(0x0F, 0x96); + writeByte(0x62, 0x02); + writeByte(0x0E, 0x00); + writeByte(0x0F, 0x00); + + //perform a first temperature measurement (again) + //the most recent temperature will be saved internally + //and used for compensation when calculating pressure + int32_t trash; + measureTempOnce(trash); + + return HP303B__SUCCEEDED; +} + + + +//////// Declaration of private functions starts here //////// + + +/** + * Initializes the sensor. + * This function has to be called from begin() + * and requires a valid bus initialization. + */ +void LOLIN_HP303B::init(void) +{ + int16_t prodId = readByteBitfield(HP303B__REG_INFO_PROD_ID); + if(prodId != HP303B__PROD_ID) + { + //Connected device is not a HP303B + m_initFail = 1U; + return; + } + m_productID = prodId; + + int16_t revId = readByteBitfield(HP303B__REG_INFO_REV_ID); + if(revId < 0) + { + m_initFail = 1U; + return; + } + m_revisionID = revId; + + //find out which temperature sensor is calibrated with coefficients... + int16_t sensor = readByteBitfield(HP303B__REG_INFO_TEMP_SENSORREC); + if(sensor < 0) + { + m_initFail = 1U; + return; + } + + //...and use this sensor for temperature measurement + m_tempSensor = sensor; + if(writeByteBitfield((uint8_t)sensor, HP303B__REG_INFO_TEMP_SENSOR) < 0) + { + m_initFail = 1U; + return; + } + + //read coefficients + if(readcoeffs() < 0) + { + m_initFail = 1U; + return; + } + + //set to standby for further configuration + standby(); + + //set measurement precision and rate to standard values; + configTemp(HP303B__TEMP_STD_MR, HP303B__TEMP_STD_OSR); + configPressure(HP303B__PRS_STD_MR, HP303B__PRS_STD_OSR); + + //perform a first temperature measurement + //the most recent temperature will be saved internally + //and used for compensation when calculating pressure + int32_t trash; + measureTempOnce(trash); + + //make sure the HP303B is in standby after initialization + standby(); + + // Fix IC with a fuse bit problem, which lead to a wrong temperature + // Should not affect ICs without this problem + correctTemp(); +} + + +/** + * reads the compensation coefficients from the HP303B + * this is called once from init(), which is called from begin() + * + * returns: 0 on success, -1 on fail + */ +int16_t LOLIN_HP303B::readcoeffs(void) +{ + uint8_t buffer[HP303B__REG_LEN_COEF]; + //read COEF registers to buffer + int16_t ret = readBlock(HP303B__REG_ADR_COEF, + HP303B__REG_LEN_COEF, + buffer); + //abort if less than REG_LEN_COEF bytes were read + if(ret < HP303B__REG_LEN_COEF) + { + return HP303B__FAIL_UNKNOWN; + } + + //compose coefficients from buffer content + m_c0Half = ((uint32_t)buffer[0] << 4) + | (((uint32_t)buffer[1] >> 4) & 0x0F); + //this construction recognizes non-32-bit negative numbers + //and converts them to 32-bit negative numbers with 2's complement + if(m_c0Half & ((uint32_t)1 << 11)) + { + m_c0Half -= (uint32_t)1 << 12; + } + //c0 is only used as c0*0.5, so c0_half is calculated immediately + m_c0Half = m_c0Half / 2U; + + //now do the same thing for all other coefficients + m_c1 = (((uint32_t)buffer[1] & 0x0F) << 8) | (uint32_t)buffer[2]; + if(m_c1 & ((uint32_t)1 << 11)) + { + m_c1 -= (uint32_t)1 << 12; + } + + m_c00 = ((uint32_t)buffer[3] << 12) + | ((uint32_t)buffer[4] << 4) + | (((uint32_t)buffer[5] >> 4) & 0x0F); + if(m_c00 & ((uint32_t)1 << 19)) + { + m_c00 -= (uint32_t)1 << 20; + } + + m_c10 = (((uint32_t)buffer[5] & 0x0F) << 16) + | ((uint32_t)buffer[6] << 8) + | (uint32_t)buffer[7]; + if(m_c10 & ((uint32_t)1<<19)) + { + m_c10 -= (uint32_t)1 << 20; + } + + m_c01 = ((uint32_t)buffer[8] << 8) + | (uint32_t)buffer[9]; + if(m_c01 & ((uint32_t)1 << 15)) + { + m_c01 -= (uint32_t)1 << 16; + } + + m_c11 = ((uint32_t)buffer[10] << 8) + | (uint32_t)buffer[11]; + if(m_c11 & ((uint32_t)1 << 15)) + { + m_c11 -= (uint32_t)1 << 16; + } + + m_c20 = ((uint32_t)buffer[12] << 8) + | (uint32_t)buffer[13]; + if(m_c20 & ((uint32_t)1 << 15)) + { + m_c20 -= (uint32_t)1 << 16; + } + + m_c21 = ((uint32_t)buffer[14] << 8) + | (uint32_t)buffer[15]; + if(m_c21 & ((uint32_t)1 << 15)) + { + m_c21 -= (uint32_t)1 << 16; + } + + m_c30 = ((uint32_t)buffer[16] << 8) + | (uint32_t)buffer[17]; + if(m_c30 & ((uint32_t)1 << 15)) + { + m_c30 -= (uint32_t)1 << 16; + } + + return HP303B__SUCCEEDED; +} + +/** + * Sets the Operation Mode of the HP303B + * + * background: determines the general behavior of the HP303B + * 0 enables command mode (only measure on commands) + * 1 enables background mode (continuous work in background) + * temperature: set 1 to measure temperature + * pressure: set 1 to measure pressure + * return: 0 on success, -1 on fail + * + * NOTE! + * You cannot set background to 1 without setting temperature and pressure + * You cannot set both temperature and pressure when background mode is disabled + */ +int16_t LOLIN_HP303B::setOpMode(uint8_t background, uint8_t temperature, uint8_t pressure) +{ + uint8_t opMode = (background & HP303B__LSB) << 2U + | (temperature & HP303B__LSB) << 1U + | (pressure & HP303B__LSB); + return setOpMode(opMode); +} + + +/** + * Sets the Operation Mode of the HP303B + * + * opMode: the new OpMode that has to be set + * return: 0 on success, -1 on fail + * + * NOTE! + * You cannot set background to 1 without setting temperature and pressure + * You cannot set both temperature and pressure when background mode is disabled + */ +int16_t LOLIN_HP303B::setOpMode(uint8_t opMode) +{ + //Filter irrelevant bits + opMode &= HP303B__REG_MASK_OPMODE >> HP303B__REG_SHIFT_OPMODE; + //Filter invalid OpModes + if(opMode == INVAL_OP_CMD_BOTH || opMode == INVAL_OP_CONT_NONE) + { + return HP303B__FAIL_UNKNOWN; + } + //Set OpMode + if(writeByte(HP303B__REG_ADR_OPMODE, opMode)) + { + return HP303B__FAIL_UNKNOWN; + } + m_opMode = (LOLIN_HP303B::Mode)opMode; + return HP303B__SUCCEEDED; +} + +/** + * Configures temperature measurement + * + * tempMr: the new measure rate for temperature + * This can be a value from 0U to 7U. + * Actual measure rate will be 2^tempMr, + * so this will be a value from 1 to 128. + * tempOsr: the new oversampling rate for temperature + * This can be a value from 0U to 7U. + * Actual measure rate will be 2^tempOsr, + * so this will be a value from 1 to 128. + * returns: 0 normally or -1 on fail + */ +int16_t LOLIN_HP303B::configTemp(uint8_t tempMr, uint8_t tempOsr) +{ + //mask parameters + tempMr &= HP303B__REG_MASK_TEMP_MR >> HP303B__REG_SHIFT_TEMP_MR; + tempOsr &= HP303B__REG_MASK_TEMP_OSR >> HP303B__REG_SHIFT_TEMP_OSR; + + //set config register according to parameters + uint8_t toWrite = tempMr << HP303B__REG_SHIFT_TEMP_MR; + toWrite |= tempOsr << HP303B__REG_SHIFT_TEMP_OSR; + //using recommended temperature sensor + toWrite |= HP303B__REG_MASK_TEMP_SENSOR + & (m_tempSensor << HP303B__REG_SHIFT_TEMP_SENSOR); + int16_t ret = writeByte(HP303B__REG_ADR_TEMP_MR, toWrite); + //abort immediately on fail + if(ret != HP303B__SUCCEEDED) + { + return HP303B__FAIL_UNKNOWN; + } + + //set TEMP SHIFT ENABLE if oversampling rate higher than eight(2^3) + if(tempOsr > HP303B__OSR_SE) + { + ret=writeByteBitfield(1U, HP303B__REG_INFO_TEMP_SE); + } + else + { + ret=writeByteBitfield(0U, HP303B__REG_INFO_TEMP_SE); + } + + if(ret == HP303B__SUCCEEDED) + { //save new settings + m_tempMr = tempMr; + m_tempOsr = tempOsr; + } + else + { + //try to rollback on fail avoiding endless recursion + //this is to make sure that shift enable and oversampling rate + //are always consistent + if(tempMr != m_tempMr || tempOsr != m_tempOsr) + { + configTemp(m_tempMr, m_tempOsr); + } + } + return ret; +} + +/** + * Configures pressure measurement + * + * prsMr: the new measure rate for pressure + * This can be a value from 0U to 7U. + * Actual measure rate will be 2^prs_mr, + * so this will be a value from 1 to 128. + * prsOs: the new oversampling rate for temperature + * This can be a value from 0U to 7U. + * Actual measure rate will be 2^prsOsr, + * so this will be a value from 1 to 128. + * returns: 0 normally or -1 on fail + */ +int16_t LOLIN_HP303B::configPressure(uint8_t prsMr, uint8_t prsOsr) +{ + //mask parameters + prsMr &= HP303B__REG_MASK_PRS_MR >> HP303B__REG_SHIFT_PRS_MR; + prsOsr &= HP303B__REG_MASK_PRS_OSR >> HP303B__REG_SHIFT_PRS_OSR; + + //set config register according to parameters + uint8_t toWrite = prsMr << HP303B__REG_SHIFT_PRS_MR; + toWrite |= prsOsr << HP303B__REG_SHIFT_PRS_OSR; + int16_t ret = writeByte(HP303B__REG_ADR_PRS_MR, toWrite); + //abort immediately on fail + if(ret != HP303B__SUCCEEDED) + { + return HP303B__FAIL_UNKNOWN; + } + + //set PM SHIFT ENABLE if oversampling rate higher than eight(2^3) + if(prsOsr > HP303B__OSR_SE) + { + ret = writeByteBitfield(1U, HP303B__REG_INFO_PRS_SE); + } + else + { + ret = writeByteBitfield(0U, HP303B__REG_INFO_PRS_SE); + } + + if(ret == HP303B__SUCCEEDED) + { //save new settings + m_prsMr = prsMr; + m_prsOsr = prsOsr; + } + else + { //try to rollback on fail avoiding endless recursion + //this is to make sure that shift enable and oversampling rate + //are always consistent + if(prsMr != m_prsMr || prsOsr != m_prsOsr) + { + configPressure(m_prsMr, m_prsOsr); + } + } + return ret; +} + +/** + * calculates the time that the HP303B needs for 2^mr measurements + * with an oversampling rate of 2^osr + * + * mr: Measure rate for temperature or pressure + * osr: Oversampling rate for temperature or pressure + * returns: time that the HP303B needs for this measurement + * a value of 10000 equals 1 second + * NOTE! The measurement time for temperature and pressure + * in sum must not be more than 1 second! + * Timing behavior of pressure and temperature sensors + * can be considered as equal. + */ +uint16_t LOLIN_HP303B::calcBusyTime(uint16_t mr, uint16_t osr) +{ + //mask parameters first + mr &= HP303B__REG_MASK_TEMP_MR >> HP303B__REG_SHIFT_TEMP_MR; + osr &= HP303B__REG_MASK_TEMP_OSR >> HP303B__REG_SHIFT_TEMP_OSR; + //formula from datasheet (optimized) + return ((uint32_t)20U << mr) + ((uint32_t)16U << (osr + mr)); +} + +/** + * Gets the next temperature measurement result in degrees of Celsius + * + * result: address where the result will be written + * returns: 0 on success + * -1 on fail; + */ +int16_t LOLIN_HP303B::getTemp(int32_t *result) +{ + uint8_t buffer[3] = {0}; + //read raw pressure data to buffer + + int16_t i = readBlock(HP303B__REG_ADR_TEMP, + HP303B__REG_LEN_TEMP, + buffer); + if(i != HP303B__REG_LEN_TEMP) + { + //something went wrong + return HP303B__FAIL_UNKNOWN; + } + + //compose raw temperature value from buffer + int32_t temp = (uint32_t)buffer[0] << 16 + | (uint32_t)buffer[1] << 8 + | (uint32_t)buffer[2]; + //recognize non-32-bit negative numbers + //and convert them to 32-bit negative numbers using 2's complement + if(temp & ((uint32_t)1 << 23)) + { + temp -= (uint32_t)1 << 24; + } + + //return temperature + *result = calcTemp(temp); + return HP303B__SUCCEEDED; +} + +/** + * Gets the next pressure measurement result in Pa + * + * result: address where the result will be written + * returns: 0 on success + * -1 on fail; + */ +int16_t LOLIN_HP303B::getPressure(int32_t *result) +{ + uint8_t buffer[3] = {0}; + //read raw pressure data to buffer + int16_t i = readBlock(HP303B__REG_ADR_PRS, + HP303B__REG_LEN_PRS, + buffer); + if(i != HP303B__REG_LEN_PRS) + { + //something went wrong + //negative pressure is not allowed + return HP303B__FAIL_UNKNOWN; + } + + //compose raw pressure value from buffer + int32_t prs = (uint32_t)buffer[0] << 16 + | (uint32_t)buffer[1] << 8 + | (uint32_t)buffer[2]; + //recognize non-32-bit negative numbers + //and convert them to 32-bit negative numbers using 2's complement + if(prs & ((uint32_t)1 << 23)) + { + prs -= (uint32_t)1 << 24; + } + + *result = calcPressure(prs); + return HP303B__SUCCEEDED; +} + +/** + * reads the next raw value from the HP303B FIFO + * + * value: address where the value will be written + * returns: -1 on fail + * 0 if result is a temperature raw value + * 1 if result is a pressure raw value + */ +int16_t LOLIN_HP303B::getFIFOvalue(int32_t* value) +{ + //abort on invalid argument + if(value == NULL) + { + return HP303B__FAIL_UNKNOWN; + } + + uint8_t buffer[HP303B__REG_LEN_PRS] = {0}; + //always read from pressure raw value register + int16_t i = readBlock(HP303B__REG_ADR_PRS, + HP303B__REG_LEN_PRS, + buffer); + if(i != HP303B__REG_LEN_PRS) + { + //something went wrong + //return error code + return HP303B__FAIL_UNKNOWN; + } + //compose raw pressure value from buffer + *value = (uint32_t)buffer[0] << 16 + | (uint32_t)buffer[1] << 8 + | (uint32_t)buffer[2]; + //recognize non-32-bit negative numbers + //and convert them to 32-bit negative numbers using 2's complement + if(*value & ((uint32_t)1 << 23)) + { + *value -= (uint32_t)1 << 24; + } + + //least significant bit shows measurement type + return buffer[2] & HP303B__LSB; +} + +/** + * Calculates a scaled and compensated pressure value from raw data + * raw: raw temperature value read from HP303B + * returns: temperature value in °C + */ +int32_t LOLIN_HP303B::calcTemp(int32_t raw) +{ + double temp = raw; + + //scale temperature according to scaling table and oversampling + temp /= scaling_facts[m_tempOsr]; + + //update last measured temperature + //it will be used for pressure compensation + m_lastTempScal = temp; + + //Calculate compensated temperature + temp = m_c0Half + m_c1 * temp; + + //return temperature + return (int32_t)temp; +} + +/** + * Calculates a scaled and compensated pressure value from raw data + * raw: raw pressure value read from HP303B + * returns: pressure value in Pa + */ +int32_t LOLIN_HP303B::calcPressure(int32_t raw) +{ + double prs = raw; + + //scale pressure according to scaling table and oversampling + prs /= scaling_facts[m_prsOsr]; + + //Calculate compensated pressure + prs = m_c00 + + prs * (m_c10 + prs * (m_c20 + prs * m_c30)) + + m_lastTempScal * (m_c01 + prs * (m_c11 + prs * m_c21)); + + //return pressure + return (int32_t)prs; +} + +/** + * reads a byte from HP303B + * + * regAdress: Address that has to be read + * returns: register content or -1 on fail + */ +int16_t LOLIN_HP303B::readByte(uint8_t regAddress) +{ + //delegate to specialized function if HP303B is connected via SPI + if(m_SpiI2c==0) + { + return readByteSPI(regAddress); + } + + m_i2cbus->beginTransmission(m_slaveAddress); + m_i2cbus->write(regAddress); + m_i2cbus->endTransmission(0); + //request 1 byte from slave + if(m_i2cbus->requestFrom(m_slaveAddress, 1U, 1U) > 0) + { + return m_i2cbus->read(); //return this byte on success + } + else + { + return HP303B__FAIL_UNKNOWN; //if 0 bytes were read successfully + } +} + +/** + * reads a byte from HP303B via SPI + * this function is automatically called by readByte + * if HP303B is connected via SPI + * + * regAdress: Address that has to be read + * returns: register content or -1 on fail + */ +int16_t LOLIN_HP303B::readByteSPI(uint8_t regAddress) +{ + //this function is only made for communication via SPI + if(m_SpiI2c != 0) + { + return HP303B__FAIL_UNKNOWN; + } + //mask regAddress + regAddress &= ~HP303B__SPI_RW_MASK; + //reserve and initialize bus + m_spibus->beginTransaction(SPISettings(HP303B__SPI_MAX_FREQ, + MSBFIRST, + SPI_MODE3)); + //enable ChipSelect for HP303B + digitalWrite(m_chipSelect, LOW); + //send address with read command to HP303B + m_spibus->transfer(regAddress | HP303B__SPI_READ_CMD); + //receive register content from HP303B + uint8_t ret = m_spibus->transfer(0xFF); //send a dummy byte while receiving + //disable ChipSelect for HP303B + digitalWrite(m_chipSelect, HIGH); + //close current SPI transaction + m_spibus->endTransaction(); + //return received data + return ret; +} + +/** + * reads a block from HP303B + * + * regAdress: Address that has to be read + * length: Length of data block + * buffer: Buffer where data will be stored + * returns: number of bytes that have been read successfully + * NOTE! This is not always equal to length + * due to rx-Buffer overflow etc. + */ +int16_t LOLIN_HP303B::readBlock(uint8_t regAddress, uint8_t length, uint8_t *buffer) +{ + //delegate to specialized function if HP303B is connected via SPI + if(m_SpiI2c == 0) + { + return readBlockSPI(regAddress, length, buffer); + } + //do not read if there is no buffer + if(buffer == NULL) + { + return 0; //0 bytes read successfully + } + + m_i2cbus->beginTransmission(m_slaveAddress); + m_i2cbus->write(regAddress); + m_i2cbus->endTransmission(0); + //request length bytes from slave + int16_t ret = m_i2cbus->requestFrom(m_slaveAddress, length, 1U); + //read all received bytes to buffer + for(int16_t count = 0; count < ret; count++) + { + buffer[count] = m_i2cbus->read(); + } + return ret; +} + +/** + * reads a block from HP303B via SPI + * + * regAdress: Address that has to be read + * length: Length of data block + * readbuffer: Buffer where data will be stored + * returns: number of bytes that have been read successfully + * NOTE! This is not always equal to length + * due to rx-Buffer overflow etc. + */ +int16_t LOLIN_HP303B::readBlockSPI(uint8_t regAddress, uint8_t length, uint8_t *buffer) +{ + //this function is only made for communication via SPI + if(m_SpiI2c != 0) + { + return HP303B__FAIL_UNKNOWN; + } + //do not read if there is no buffer + if(buffer == NULL) + { + return 0; //0 bytes were read successfully + } + //mask regAddress + regAddress &= ~HP303B__SPI_RW_MASK; + //reserve and initialize bus + m_spibus->beginTransaction(SPISettings(HP303B__SPI_MAX_FREQ, + MSBFIRST, + SPI_MODE3)); + //enable ChipSelect for HP303B + digitalWrite(m_chipSelect, LOW); + //send address with read command to HP303B + m_spibus->transfer(regAddress | HP303B__SPI_READ_CMD); + + //receive register contents from HP303B + for(uint8_t count = 0; count < length; count++) + { + buffer[count] = m_spibus->transfer(0xFF);//send a dummy byte while receiving + } + + //disable ChipSelect for HP303B + digitalWrite(m_chipSelect, HIGH); + //close current SPI transaction + m_spibus->endTransaction(); + //return received data + return length; +} + +/** + * writes a given byte to a given register of HP303B without checking + * + * regAdress: Address of the register that has to be updated + * data: Byte that will be written to the register + * return: 0 if byte was written successfully + * or -1 on fail + */ +int16_t LOLIN_HP303B::writeByte(uint8_t regAddress, uint8_t data) +{ + return writeByte(regAddress, data, 0U); +} + +/** + * writes a given byte to a given register of HP303B + * + * regAdress: Address of the register that has to be updated + * data: Byte that will be written to the register + * check: If this is true, register content will be read after writing + * to check if update was successful + * return: 0 if byte was written successfully + * or -1 on fail + */ +int16_t LOLIN_HP303B::writeByte(uint8_t regAddress, uint8_t data, uint8_t check) +{ + //delegate to specialized function if HP303B is connected via SPI + if(m_SpiI2c==0) + { + return writeByteSpi(regAddress, data, check); + } + m_i2cbus->beginTransmission(m_slaveAddress); + m_i2cbus->write(regAddress); //Write Register number to buffer + m_i2cbus->write(data); //Write data to buffer + if(m_i2cbus->endTransmission() != 0) //Send buffer content to slave + { + return HP303B__FAIL_UNKNOWN; + } + else + { + if(check == 0) return 0; //no checking + if(readByte(regAddress) == data) //check if desired by calling function + { + return HP303B__SUCCEEDED; + } + else + { + return HP303B__FAIL_UNKNOWN; + } + } +} + +/** + * writes a given byte to a given register of HP303B via SPI + * + * regAdress: Address of the register that has to be updated + * data: Byte that will be written to the register + * check: If this is true, register content will be read after writing + * to check if update was successful + * return: 0 if byte was written successfully + * or -1 on fail + */ +int16_t LOLIN_HP303B::writeByteSpi(uint8_t regAddress, uint8_t data, uint8_t check) +{ + //this function is only made for communication via SPI + if(m_SpiI2c != 0) + { + return HP303B__FAIL_UNKNOWN; + } + //mask regAddress + regAddress &= ~HP303B__SPI_RW_MASK; + //reserve and initialize bus + m_spibus->beginTransaction(SPISettings(HP303B__SPI_MAX_FREQ, + MSBFIRST, + SPI_MODE3)); + //enable ChipSelect for HP303B + digitalWrite(m_chipSelect, LOW); + //send address with read command to HP303B + m_spibus->transfer(regAddress | HP303B__SPI_WRITE_CMD); + + //write register content from HP303B + m_spibus->transfer(data); + + //disable ChipSelect for HP303B + digitalWrite(m_chipSelect, HIGH); + //close current SPI transaction + m_spibus->endTransaction(); + + //check if necessary + if(check == 0) + { + //no checking necessary + return HP303B__SUCCEEDED; + } + //checking necessary + if(readByte(regAddress) == data) + { + //check passed + return HP303B__SUCCEEDED; + } + else + { + //check failed + return HP303B__FAIL_UNKNOWN; + } +} + +/** + * updates some given bits of a given register of HP303B without checking + * + * regAdress: Address of the register that has to be updated + * data: BitValues that will be written to the register + * shift: Amount of bits the data byte is shifted (left) before being masked + * mask: Masks the bits of the register that have to be updated + * Bits with value 1 are updated + * Bits with value 0 are not changed + * return: 0 if byte was written successfully + * or -1 on fail + */ +int16_t LOLIN_HP303B::writeByteBitfield(uint8_t data, + uint8_t regAddress, + uint8_t mask, + uint8_t shift) +{ + return writeByteBitfield(data, regAddress, mask, shift, 0U); +} + +/** + * updates some given bits of a given register of HP303B + * + * regAdress: Address of the register that has to be updated + * data: BitValues that will be written to the register + * shift: Amount of bits the data byte is shifted (left) before being masked + * mask: Masks the bits of the register that have to be updated + * Bits with value 1 are updated + * Bits with value 0 are not changed + * check: enables/disables check after writing + * 0 disables check + * if check fails, -1 will be returned + * return: 0 if byte was written successfully + * or -1 on fail + */ +int16_t LOLIN_HP303B::writeByteBitfield(uint8_t data, + uint8_t regAddress, + uint8_t mask, + uint8_t shift, + uint8_t check) +{ + int16_t old = readByte(regAddress); + if(old < 0) + { + //fail while reading + return old; + } + return writeByte(regAddress, ((uint8_t)old & ~mask)|((data << shift) & mask), check); +} + +/** + * reads some given bits of a given register of HP303B + * + * regAdress: Address of the register that has to be updated + * mask: Masks the bits of the register that have to be updated + * Bits masked with value 1 are read + * Bits masked with value 0 are set 0 + * shift: Amount of bits the data byte is shifted (right) after being masked + * return: read and processed bits + * or -1 on fail + */ +int16_t LOLIN_HP303B::readByteBitfield(uint8_t regAddress, uint8_t mask, uint8_t shift) +{ + int16_t ret = readByte(regAddress); + if(ret<0) + { + return ret; + } + return (((uint8_t)ret) & mask) >> shift; +} diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h new file mode 100644 index 000000000..f9c54e356 --- /dev/null +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h @@ -0,0 +1,144 @@ +#ifndef __LOLIN_HP303B_H +#define __LOLIN_HP303B_H + +#if ARDUINO >= 100 +#include "Arduino.h" +#else +#include "WProgram.h" +#endif + +#include +#include +#include "util/hp303b_consts.h" + +class LOLIN_HP303B +{ +public: + //constructor + LOLIN_HP303B(void); + //destructor + ~LOLIN_HP303B(void); + //begin + void begin(TwoWire &bus, uint8_t slaveAddress); + void begin(uint8_t slaveAddress=HP303B__STD_SLAVE_ADDRESS); + void begin(SPIClass &bus, int32_t chipSelect); + void begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire); + //end + void end(void); + + //general + uint8_t getProductId(void); + uint8_t getRevisionId(void); + + //Idle Mode + int16_t standby(void); + + //Command Mode + int16_t measureTempOnce(int32_t &result); + int16_t measureTempOnce(int32_t &result, uint8_t oversamplingRate); + int16_t startMeasureTempOnce(void); + int16_t startMeasureTempOnce(uint8_t oversamplingRate); + int16_t measurePressureOnce(int32_t &result); + int16_t measurePressureOnce(int32_t &result, uint8_t oversamplingRate); + int16_t startMeasurePressureOnce(void); + int16_t startMeasurePressureOnce(uint8_t oversamplingRate); + int16_t getSingleResult(int32_t &result); + + //Background Mode + int16_t startMeasureTempCont(uint8_t measureRate, uint8_t oversamplingRate); + int16_t startMeasurePressureCont(uint8_t measureRate, uint8_t oversamplingRate); + int16_t startMeasureBothCont(uint8_t tempMr, uint8_t tempOsr, uint8_t prsMr, uint8_t prsOsr); + int16_t getContResults(int32_t *tempBuffer, uint8_t &tempCount, int32_t *prsBuffer, uint8_t &prsCount); + + //Interrupt Control + int16_t setInterruptPolarity(uint8_t polarity); + int16_t setInterruptSources(uint8_t fifoFull, uint8_t tempReady, uint8_t prsReady); + int16_t getIntStatusFifoFull(void); + int16_t getIntStatusTempReady(void); + int16_t getIntStatusPrsReady(void); + + //function to fix a hardware problem on some devices + int16_t correctTemp(void); + +private: + //scaling factor table + static const int32_t scaling_facts[HP303B__NUM_OF_SCAL_FACTS]; + + //enum for operating mode + enum Mode + { + IDLE = 0x00, + CMD_PRS = 0x01, + CMD_TEMP = 0x02, + INVAL_OP_CMD_BOTH = 0x03, //invalid + INVAL_OP_CONT_NONE = 0x04, //invalid + CONT_PRS = 0x05, + CONT_TMP = 0x06, + CONT_BOTH = 0x07 + }; + Mode m_opMode; + + //flags + uint8_t m_initFail; + uint8_t m_productID; + uint8_t m_revisionID; + + //settings + uint8_t m_tempMr; + uint8_t m_tempOsr; + uint8_t m_prsMr; + uint8_t m_prsOsr; + uint8_t m_tempSensor; + + //compensation coefficients + int32_t m_c0Half; + int32_t m_c1; + int32_t m_c00; + int32_t m_c10; + int32_t m_c01; + int32_t m_c11; + int32_t m_c20; + int32_t m_c21; + int32_t m_c30; + //last measured scaled temperature + //(necessary for pressure compensation) + double m_lastTempScal; + + //bus specific + uint8_t m_SpiI2c; //0=SPI, 1=I2C + //used for I2C + TwoWire *m_i2cbus; + uint8_t m_slaveAddress; + //used for SPI + SPIClass *m_spibus; + int32_t m_chipSelect; + uint8_t m_threeWire; + + //measurement + void init(void); + int16_t readcoeffs(void); + int16_t setOpMode(uint8_t background, uint8_t temperature, uint8_t pressure); + int16_t setOpMode(uint8_t opMode); + int16_t configTemp(uint8_t temp_mr, uint8_t temp_osr); + int16_t configPressure(uint8_t prs_mr, uint8_t prs_osr); + uint16_t calcBusyTime(uint16_t temp_rate, uint16_t temp_osr); + int16_t getTemp(int32_t *result); + int16_t getPressure(int32_t *result); + int16_t getFIFOvalue(int32_t *value); + int32_t calcTemp(int32_t raw); + int32_t calcPressure(int32_t raw); + + //bus specific + int16_t readByte(uint8_t regAddress); + int16_t readByteSPI(uint8_t regAddress); + int16_t readBlock(uint8_t regAddress, uint8_t length, uint8_t *buffer); + int16_t readBlockSPI(uint8_t regAddress, uint8_t length, uint8_t *readbuffer); + int16_t writeByte(uint8_t regAddress, uint8_t data); + int16_t writeByte(uint8_t regAddress, uint8_t data, uint8_t check); + int16_t writeByteSpi(uint8_t regAddress, uint8_t data, uint8_t check); + int16_t writeByteBitfield(uint8_t data, uint8_t regAddress, uint8_t mask, uint8_t shift); + int16_t writeByteBitfield(uint8_t data, uint8_t regAddress, uint8_t mask, uint8_t shift, uint8_t check); + int16_t readByteBitfield(uint8_t regAddress, uint8_t mask, uint8_t shift); +}; + +#endif diff --git a/lib/LOLIN_HP303B/src/util/hp303b_consts.h b/lib/LOLIN_HP303B/src/util/hp303b_consts.h new file mode 100644 index 000000000..f93629e93 --- /dev/null +++ b/lib/LOLIN_HP303B/src/util/hp303b_consts.h @@ -0,0 +1,258 @@ +/** + * + * + */ + +#ifndef __HP303B_CONSTS_H_ +#define __HP303B_CONSTS_H_ + + + //general Constants +#define HP303B__PROD_ID 0U +#define HP303B__STD_SLAVE_ADDRESS 0x77U +#define HP303B__SPI_WRITE_CMD 0x00U +#define HP303B__SPI_READ_CMD 0x80U +#define HP303B__SPI_RW_MASK 0x80U +#define HP303B__SPI_MAX_FREQ 100000U + +#define HP303B__LSB 0x01U + +#define HP303B__TEMP_STD_MR 2U +#define HP303B__TEMP_STD_OSR 3U +#define HP303B__PRS_STD_MR 2U +#define HP303B__PRS_STD_OSR 3U +#define HP303B__OSR_SE 3U +//we use 0.1 mS units for time calculations, so 10 units are one millisecond +#define HP303B__BUSYTIME_SCALING 10U +// DPS310 has 10 milliseconds of spare time for each synchronous measurement / per second for asynchronous measurements +// this is for error prevention on friday-afternoon-products :D +// you can set it to 0 if you dare, but there is no warranty that it will still work +#define HP303B__BUSYTIME_FAILSAFE 10U +#define HP303B__MAX_BUSYTIME ((1000U-HP303B__BUSYTIME_FAILSAFE)*HP303B__BUSYTIME_SCALING) +#define HP303B__NUM_OF_SCAL_FACTS 8 + +#define HP303B__SUCCEEDED 0 +#define HP303B__FAIL_UNKNOWN -1 +#define HP303B__FAIL_INIT_FAILED -2 +#define HP303B__FAIL_TOOBUSY -3 +#define HP303B__FAIL_UNFINISHED -4 + + + //Constants for register manipulation + //SPI mode (3 or 4 wire) +#define HP303B__REG_ADR_SPI3W 0x09U +#define HP303B__REG_CONTENT_SPI3W 0x01U + + + //product id +#define HP303B__REG_INFO_PROD_ID HP303B__REG_ADR_PROD_ID, \ + HP303B__REG_MASK_PROD_ID, \ + HP303B__REG_SHIFT_PROD_ID +#define HP303B__REG_ADR_PROD_ID 0x0DU +#define HP303B__REG_MASK_PROD_ID 0x0FU +#define HP303B__REG_SHIFT_PROD_ID 0U + + //revision id +#define HP303B__REG_INFO_REV_ID HP303B__REG_ADR_REV_ID, \ + HP303B__REG_MASK_REV_ID, \ + HP303B__REG_SHIFT_REV_ID +#define HP303B__REG_ADR_REV_ID 0x0DU +#define HP303B__REG_MASK_REV_ID 0xF0U +#define HP303B__REG_SHIFT_REV_ID 4U + + //operating mode +#define HP303B__REG_INFO_OPMODE HP303B__REG_ADR_OPMODE, \ + HP303B__REG_MASK_OPMODE, \ + HP303B__REG_SHIFT_OPMODE +#define HP303B__REG_ADR_OPMODE 0x08U +#define HP303B__REG_MASK_OPMODE 0x07U +#define HP303B__REG_SHIFT_OPMODE 0U + + + //temperature measure rate +#define HP303B__REG_INFO_TEMP_MR HP303B__REG_ADR_TEMP_MR, \ + HP303B__REG_MASK_TEMP_MR, \ + HP303B__REG_SHIFT_TEMP_MR +#define HP303B__REG_ADR_TEMP_MR 0x07U +#define HP303B__REG_MASK_TEMP_MR 0x70U +#define HP303B__REG_SHIFT_TEMP_MR 4U + + //temperature oversampling rate +#define HP303B__REG_INFO_TEMP_OSR HP303B__REG_ADR_TEMP_OSR, \ + HP303B__REG_MASK_TEMP_OSR, \ + HP303B__REG_SHIFT_TEMP_OSR +#define HP303B__REG_ADR_TEMP_OSR 0x07U +#define HP303B__REG_MASK_TEMP_OSR 0x07U +#define HP303B__REG_SHIFT_TEMP_OSR 0U + + //temperature sensor +#define HP303B__REG_INFO_TEMP_SENSOR HP303B__REG_ADR_TEMP_SENSOR, \ + HP303B__REG_MASK_TEMP_SENSOR, \ + HP303B__REG_SHIFT_TEMP_SENSOR +#define HP303B__REG_ADR_TEMP_SENSOR 0x07U +#define HP303B__REG_MASK_TEMP_SENSOR 0x80U +#define HP303B__REG_SHIFT_TEMP_SENSOR 7U + + //temperature sensor recommendation +#define HP303B__REG_INFO_TEMP_SENSORREC HP303B__REG_ADR_TEMP_SENSORREC, \ + HP303B__REG_MASK_TEMP_SENSORREC, \ + HP303B__REG_SHIFT_TEMP_SENSORREC +#define HP303B__REG_ADR_TEMP_SENSORREC 0x28U +#define HP303B__REG_MASK_TEMP_SENSORREC 0x80U +#define HP303B__REG_SHIFT_TEMP_SENSORREC 7U + + //temperature shift enable (if temp_osr>3) +#define HP303B__REG_INFO_TEMP_SE HP303B__REG_ADR_TEMP_SE, \ + HP303B__REG_MASK_TEMP_SE, \ + HP303B__REG_SHIFT_TEMP_SE +#define HP303B__REG_ADR_TEMP_SE 0x09U +#define HP303B__REG_MASK_TEMP_SE 0x08U +#define HP303B__REG_SHIFT_TEMP_SE 3U + + + //pressure measure rate +#define HP303B__REG_INFO_PRS_MR HP303B__REG_ADR_PRS_MR, \ + HP303B__REG_MASK_PRS_MR, \ + HP303B__REG_SHIFT_PRS_MR +#define HP303B__REG_ADR_PRS_MR 0x06U +#define HP303B__REG_MASK_PRS_MR 0x70U +#define HP303B__REG_SHIFT_PRS_MR 4U + + //pressure oversampling rate +#define HP303B__REG_INFO_PRS_OSR HP303B__REG_ADR_PRS_OSR, \ + HP303B__REG_MASK_PRS_OSR, \ + HP303B__REG_SHIFT_PRS_OSR +#define HP303B__REG_ADR_PRS_OSR 0x06U +#define HP303B__REG_MASK_PRS_OSR 0x07U +#define HP303B__REG_SHIFT_PRS_OSR 0U + + //pressure shift enable (if prs_osr>3) +#define HP303B__REG_INFO_PRS_SE HP303B__REG_ADR_PRS_SE, \ + HP303B__REG_MASK_PRS_SE, \ + HP303B__REG_SHIFT_PRS_SE +#define HP303B__REG_ADR_PRS_SE 0x09U +#define HP303B__REG_MASK_PRS_SE 0x04U +#define HP303B__REG_SHIFT_PRS_SE 2U + + + //temperature ready flag +#define HP303B__REG_INFO_TEMP_RDY HP303B__REG_ADR_TEMP_RDY, \ + HP303B__REG_MASK_TEMP_RDY, \ + HP303B__REG_SHIFT_TEMP_RDY +#define HP303B__REG_ADR_TEMP_RDY 0x08U +#define HP303B__REG_MASK_TEMP_RDY 0x20U +#define HP303B__REG_SHIFT_TEMP_RDY 5U + + //pressure ready flag +#define HP303B__REG_INFO_PRS_RDY HP303B__REG_ADR_PRS_RDY, \ + HP303B__REG_MASK_PRS_RDY, \ + HP303B__REG_SHIFT_PRS_RDY +#define HP303B__REG_ADR_PRS_RDY 0x08U +#define HP303B__REG_MASK_PRS_RDY 0x10U +#define HP303B__REG_SHIFT_PRS_RDY 4U + + //pressure value +#define HP303B__REG_ADR_PRS 0x00U +#define HP303B__REG_LEN_PRS 3U + + //temperature value +#define HP303B__REG_ADR_TEMP 0x03U +#define HP303B__REG_LEN_TEMP 3U + + //compensation coefficients +#define HP303B__REG_ADR_COEF 0x10U +#define HP303B__REG_LEN_COEF 18 + + + //FIFO enable +#define HP303B__REG_INFO_FIFO_EN HP303B__REG_ADR_FIFO_EN, \ + HP303B__REG_MASK_FIFO_EN, \ + HP303B__REG_SHIFT_FIFO_EN +#define HP303B__REG_ADR_FIFO_EN 0x09U +#define HP303B__REG_MASK_FIFO_EN 0x02U +#define HP303B__REG_SHIFT_FIFO_EN 1U + + //FIFO flush +#define HP303B__REG_INFO_FIFO_FL HP303B__REG_ADR_FIFO_EN, \ + HP303B__REG_MASK_FIFO_EN, \ + HP303B__REG_SHIFT_FIFO_EN +#define HP303B__REG_ADR_FIFO_FL 0x0CU +#define HP303B__REG_MASK_FIFO_FL 0x80U +#define HP303B__REG_SHIFT_FIFO_FL 7U + + //FIFO empty +#define HP303B__REG_INFO_FIFO_EMPTY HP303B__REG_ADR_FIFO_EMPTY, \ + HP303B__REG_MASK_FIFO_EMPTY, \ + HP303B__REG_SHIFT_FIFO_EMPTY +#define HP303B__REG_ADR_FIFO_EMPTY 0x0BU +#define HP303B__REG_MASK_FIFO_EMPTY 0x01U +#define HP303B__REG_SHIFT_FIFO_EMPTY 0U + + //FIFO full +#define HP303B__REG_INFO_FIFO_FULL HP303B__REG_ADR_FIFO_FULL, \ + HP303B__REG_MASK_FIFO_FULL, \ + HP303B__REG_SHIFT_FIFO_FULL +#define HP303B__REG_ADR_FIFO_FULL 0x0BU +#define HP303B__REG_MASK_FIFO_FULL 0x02U +#define HP303B__REG_SHIFT_FIFO_FULL 1U + + + //INT HL +#define HP303B__REG_INFO_INT_HL HP303B__REG_ADR_INT_HL, \ + HP303B__REG_MASK_INT_HL, \ + HP303B__REG_SHIFT_INT_HL +#define HP303B__REG_ADR_INT_HL 0x09U +#define HP303B__REG_MASK_INT_HL 0x80U +#define HP303B__REG_SHIFT_INT_HL 7U + + //INT FIFO enable +#define HP303B__REG_INFO_INT_EN_FIFO HP303B__REG_ADR_INT_EN_FIFO, \ + HP303B__REG_MASK_INT_EN_FIFO, \ + HP303B__REG_SHIFT_INT_EN_FIFO +#define HP303B__REG_ADR_INT_EN_FIFO 0x09U +#define HP303B__REG_MASK_INT_EN_FIFO 0x40U +#define HP303B__REG_SHIFT_INT_EN_FIFO 6U + + //INT TEMP enable +#define HP303B__REG_INFO_INT_EN_TEMP HP303B__REG_ADR_INT_EN_TEMP, \ + HP303B__REG_MASK_INT_EN_TEMP, \ + HP303B__REG_SHIFT_INT_EN_TEMP +#define HP303B__REG_ADR_INT_EN_TEMP 0x09U +#define HP303B__REG_MASK_INT_EN_TEMP 0x20U +#define HP303B__REG_SHIFT_INT_EN_TEMP 5U + + //INT PRS enable +#define HP303B__REG_INFO_INT_EN_PRS HP303B__REG_ADR_INT_EN_PRS, \ + HP303B__REG_MASK_INT_EN_PRS, \ + HP303B__REG_SHIFT_INT_EN_PRS +#define HP303B__REG_ADR_INT_EN_PRS 0x09U +#define HP303B__REG_MASK_INT_EN_PRS 0x10U +#define HP303B__REG_SHIFT_INT_EN_PRS 4U + + //INT FIFO flag +#define HP303B__REG_INFO_INT_FLAG_FIFO HP303B__REG_ADR_INT_FLAG_FIFO, \ + HP303B__REG_MASK_INT_FLAG_FIFO, \ + HP303B__REG_SHIFT_INT_FLAG_FIFO +#define HP303B__REG_ADR_INT_FLAG_FIFO 0x0AU +#define HP303B__REG_MASK_INT_FLAG_FIFO 0x04U +#define HP303B__REG_SHIFT_INT_FLAG_FIFO 2U + + //INT TMP flag +#define HP303B__REG_INFO_INT_FLAG_TEMP HP303B__REG_ADR_INT_FLAG_TEMP, \ + HP303B__REG_MASK_INT_FLAG_TEMP, \ + HP303B__REG_SHIFT_INT_FLAG_TEMP +#define HP303B__REG_ADR_INT_FLAG_TEMP 0x0AU +#define HP303B__REG_MASK_INT_FLAG_TEMP 0x02U +#define HP303B__REG_SHIFT_INT_FLAG_TEMP 1U + + //INT PRS flag +#define HP303B__REG_INFO_INT_FLAG_PRS HP303B__REG_ADR_INT_FLAG_PRS, \ + HP303B__REG_MASK_INT_FLAG_PRS, \ + HP303B__REG_SHIFT_INT_FLAG_PRS +#define HP303B__REG_ADR_INT_FLAG_PRS 0x0AU +#define HP303B__REG_MASK_INT_FLAG_PRS 0x01U +#define HP303B__REG_SHIFT_INT_FLAG_PRS 0U + + + +#endif /* DPS310_CONSTS_H_ */ diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index 5622bcde9..25a8a2203 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -572,8 +572,10 @@ void GetFeatures(void) #ifdef USE_MCP9808 feature6 |= 0x00002000; // xsns_72_mcp9808.ino #endif +#ifdef USE_HP303B + feature6 |= 0x00004000; // xsns_73_hp303b.ino +#endif -// feature6 |= 0x00004000; // feature6 |= 0x00008000; // feature6 |= 0x00010000; diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino new file mode 100644 index 000000000..e5df20508 --- /dev/null +++ b/tasmota/xsns_73_hp303b.ino @@ -0,0 +1,161 @@ +/* + xsns_72_hp303b.ino - HP303B digital barometric air pressure sensor support for Tasmota + + Copyright (C) 2020 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ +#ifdef USE_I2C +#ifdef USE_HP303B +/*********************************************************************************************\ + * HP303B - Gas (TVOC - Total Volatile Organic Compounds) and Air Quality (CO2) + * + * Source: Lolin LOLIN_HP303B_Library + * + * I2C Address: 0x77 or 0x76 +\*********************************************************************************************/ + +#define XSNS_73 73 +#define XI2C_52 52 // See I2CDEVICES.md + +#define HP303B_ADDR1 0x77 +#define HP303B_ADDR2 0x76 + +#include +// HP303B Opject +LOLIN_HP303B HP303BSensor = LOLIN_HP303B(); + +struct HP303BDATA +{ + uint8_t address; + uint8_t addresses[2] = {HP303B_ADDR1, HP303B_ADDR2}; + uint8_t type = 0; + uint8_t valid = 0; + int32_t temperature; + int32_t pressure; + int16_t oversampling = 7; + char types[7] = "HP303B"; +} HP303B; + +/*********************************************************************************************/ + +bool HP303B_Read(int32_t &t, int32_t &p, uint8_t hp303b_address) +{ + HP303BSensor.begin(hp303b_address); + + int16_t ret; + + ret = HP303BSensor.measureTempOnce(t, HP303B.oversampling); + if (ret != 0) + return false; + + ret = HP303BSensor.measurePressureOnce(p, HP303B.oversampling); + if (ret != 0) + return false; + + HP303B.temperature = ConvertTemp(t); + HP303B.pressure = ConvertPressure(p); + + return true; +} + +/********************************************************************************************/ + +void HP303B_Detect(void) +{ + for (uint32_t i = 0; i < sizeof(HP303B.addresses); i++) + { + if (!I2cSetDevice(HP303B.addresses[i])) + { + continue; + } + + int32_t t; + int32_t p; + if (HP303B_Read(t, p, HP303B.addresses[i])) + { + I2cSetActiveFound(HP303B.addresses[i], HP303B.types); + HP303B.address = HP303B.addresses[i]; + HP303B.type = 1; + } + } +} + +void HP303B_Show(bool json) +{ + + if (HP303B_Read(HP303B.temperature, HP303B.pressure, HP303B.address)) + { + if (json) + { + ResponseAppend_P(PSTR(",\"HP303B\":{\"" D_JSON_TEMPERATURE "\":%d,\"" D_JSON_PRESSURE "\":%d"), HP303B.temperature, HP303B.pressure); +#ifdef USE_DOMOTICZ + if (0 == tele_period) + { + DomoticzSensor(DZ_TEMP, HP303B.temperature); + } +#endif // USE_DOMOTICZ +#ifdef USE_WEBSERVER + } + else + { + char str_temperature[12]; + char str_pressure[12]; + + itoa(HP303B.temperature, str_temperature, 10); + itoa(HP303B.pressure, str_pressure, 10); + + WSContentSend_PD(HTTP_SNS_TEMP, "HP303B", str_temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_PRESSURE, "HP303B", str_pressure, PressureUnit().c_str()); +#endif // USE_WEBSERVER + } + } +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xsns73(uint8_t function) +{ + if (!I2cEnabled(XI2C_52)) + { + return false; + } + + bool result = false; + + if (FUNC_INIT == function) + { + HP303B_Detect(); + } + else if (HP303B.type) + { + switch (function) + { + case FUNC_JSON_APPEND: + HP303B_Show(1); + break; +#ifdef USE_WEBSERVER + case FUNC_WEB_SENSOR: + HP303B_Show(0); + break; +#endif // USE_WEBSERVER + } + } + return result; +} + +#endif // USE_HP303B +#endif // USE_I2C diff --git a/tools/decode-status.py b/tools/decode-status.py index 67898d784..b77ddf2c6 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -204,7 +204,7 @@ a_features = [[ "USE_KEELOQ","USE_HRXL","USE_SONOFF_D1","USE_HDC1080", "USE_IAQ","USE_DISPLAY_SEVENSEG","USE_AS3935","USE_PING", "USE_WINDMETER","USE_OPENTHERM","USE_THERMOSTAT","USE_VEML6075", - "USE_VEML7700","USE_MCP9808","","", + "USE_VEML7700","USE_MCP9808","USE_HP303B","", "","","","", "","","","", "","","","", From 2da09526ba41f73663b7ddda55df80c3104c2407 Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Sat, 6 Jun 2020 23:05:55 +0200 Subject: [PATCH 02/35] switched to float values and converted Pa to hPa --- tasmota/xsns_73_hp303b.ino | 64 ++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 33 deletions(-) diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index e5df20508..3f508b7d8 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -36,36 +36,35 @@ // HP303B Opject LOLIN_HP303B HP303BSensor = LOLIN_HP303B(); -struct HP303BDATA -{ - uint8_t address; - uint8_t addresses[2] = {HP303B_ADDR1, HP303B_ADDR2}; - uint8_t type = 0; - uint8_t valid = 0; - int32_t temperature; - int32_t pressure; - int16_t oversampling = 7; - char types[7] = "HP303B"; -} HP303B; +uint8_t address; +uint8_t addresses[2] = {HP303B_ADDR1, HP303B_ADDR2}; +uint8_t type = 0; +uint8_t valid = 0; +float temperature; +float pressure; +int16_t oversampling = 7; +char types[7] = "HP303B"; /*********************************************************************************************/ -bool HP303B_Read(int32_t &t, int32_t &p, uint8_t hp303b_address) +bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) { HP303BSensor.begin(hp303b_address); + int32_t t; + int32_t p; int16_t ret; - ret = HP303BSensor.measureTempOnce(t, HP303B.oversampling); + ret = HP303BSensor.measureTempOnce(t, oversampling); if (ret != 0) return false; - ret = HP303BSensor.measurePressureOnce(p, HP303B.oversampling); + ret = HP303BSensor.measurePressureOnce(p, oversampling); if (ret != 0) return false; - HP303B.temperature = ConvertTemp(t); - HP303B.pressure = ConvertPressure(p); + temperature = (float)ConvertTemp(t); + pressure = (float)ConvertPressure(p) / 100; //conversion to hPa return true; } @@ -74,20 +73,20 @@ bool HP303B_Read(int32_t &t, int32_t &p, uint8_t hp303b_address) void HP303B_Detect(void) { - for (uint32_t i = 0; i < sizeof(HP303B.addresses); i++) + for (uint32_t i = 0; i < sizeof(addresses); i++) { - if (!I2cSetDevice(HP303B.addresses[i])) + if (!I2cSetDevice(addresses[i])) { continue; } - int32_t t; - int32_t p; - if (HP303B_Read(t, p, HP303B.addresses[i])) + float t; + float p; + if (HP303B_Read(t, p, addresses[i])) { - I2cSetActiveFound(HP303B.addresses[i], HP303B.types); - HP303B.address = HP303B.addresses[i]; - HP303B.type = 1; + I2cSetActiveFound(addresses[i], types); + address = addresses[i]; + type = 1; } } } @@ -95,26 +94,25 @@ void HP303B_Detect(void) void HP303B_Show(bool json) { - if (HP303B_Read(HP303B.temperature, HP303B.pressure, HP303B.address)) + if (HP303B_Read(temperature, pressure, address)) { if (json) { - ResponseAppend_P(PSTR(",\"HP303B\":{\"" D_JSON_TEMPERATURE "\":%d,\"" D_JSON_PRESSURE "\":%d"), HP303B.temperature, HP303B.pressure); + ResponseAppend_P(PSTR(",\"HP303B\":{\"" D_JSON_TEMPERATURE "\":%d,\"" D_JSON_PRESSURE "\":%d"), temperature, pressure); #ifdef USE_DOMOTICZ if (0 == tele_period) { - DomoticzSensor(DZ_TEMP, HP303B.temperature); + DomoticzSensor(DZ_TEMP, temperature); } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER } else { - char str_temperature[12]; - char str_pressure[12]; - - itoa(HP303B.temperature, str_temperature, 10); - itoa(HP303B.pressure, str_pressure, 10); + char str_temperature[33]; + dtostrfd(temperature, Settings.flag2.temperature_resolution, str_temperature); + char str_pressure[33]; + dtostrfd(pressure, Settings.flag2.pressure_resolution, str_pressure); WSContentSend_PD(HTTP_SNS_TEMP, "HP303B", str_temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_PRESSURE, "HP303B", str_pressure, PressureUnit().c_str()); @@ -140,7 +138,7 @@ bool Xsns73(uint8_t function) { HP303B_Detect(); } - else if (HP303B.type) + else if (type) { switch (function) { From d3a59fd65c3f5834d9fb61eee1374d50a2f14db3 Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Sun, 7 Jun 2020 16:33:28 +0200 Subject: [PATCH 03/35] Changed driver to measure float --- lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp | 82 +++++++++++++-------------- lib/LOLIN_HP303B/src/LOLIN_HP303B.h | 22 +++---- tasmota/xsns_73_hp303b.ino | 17 +++--- 3 files changed, 60 insertions(+), 61 deletions(-) diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp index 43fcd435e..fdeebd6a0 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp @@ -181,7 +181,7 @@ int16_t LOLIN_HP303B::standby(void) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::measureTempOnce(int32_t &result) +int16_t LOLIN_HP303B::measureTempOnce(float &result) { return measureTempOnce(result, m_tempOsr); } @@ -202,7 +202,7 @@ int16_t LOLIN_HP303B::measureTempOnce(int32_t &result) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::measureTempOnce(int32_t &result, uint8_t oversamplingRate) +int16_t LOLIN_HP303B::measureTempOnce(float &result, uint8_t oversamplingRate) { //Start measurement int16_t ret = startMeasureTempOnce(oversamplingRate); @@ -286,7 +286,7 @@ int16_t LOLIN_HP303B::startMeasureTempOnce(uint8_t oversamplingRate) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::measurePressureOnce(int32_t &result) +int16_t LOLIN_HP303B::measurePressureOnce(float &result) { return measurePressureOnce(result, m_prsOsr); } @@ -307,7 +307,7 @@ int16_t LOLIN_HP303B::measurePressureOnce(int32_t &result) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::measurePressureOnce(int32_t &result, uint8_t oversamplingRate) +int16_t LOLIN_HP303B::measurePressureOnce(float &result, uint8_t oversamplingRate) { //start the measurement int16_t ret = startMeasurePressureOnce(oversamplingRate); @@ -388,7 +388,7 @@ int16_t LOLIN_HP303B::startMeasurePressureOnce(uint8_t oversamplingRate) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::getSingleResult(int32_t &result) +int16_t LOLIN_HP303B::getSingleResult(float &result) { //abort if initialization failed if(m_initFail) @@ -636,9 +636,9 @@ int16_t LOLIN_HP303B::startMeasureBothCont(uint8_t tempMr, * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::getContResults(int32_t *tempBuffer, +int16_t LOLIN_HP303B::getContResults(float *tempBuffer, uint8_t &tempCount, - int32_t *prsBuffer, + float *prsBuffer, uint8_t &prsCount) { if(m_initFail) @@ -660,7 +660,7 @@ int16_t LOLIN_HP303B::getContResults(int32_t *tempBuffer, //while FIFO is not empty while(readByteBitfield(HP303B__REG_INFO_FIFO_EMPTY) == 0) { - int32_t result; + float result; //read next result from FIFO int16_t type = getFIFOvalue(&result); switch(type) @@ -820,13 +820,13 @@ int16_t LOLIN_HP303B::correctTemp(void) writeByte(0x62, 0x02); writeByte(0x0E, 0x00); writeByte(0x0F, 0x00); - + //perform a first temperature measurement (again) //the most recent temperature will be saved internally //and used for compensation when calculating pressure - int32_t trash; + float trash; measureTempOnce(trash); - + return HP303B__SUCCEEDED; } @@ -892,13 +892,13 @@ void LOLIN_HP303B::init(void) //perform a first temperature measurement //the most recent temperature will be saved internally //and used for compensation when calculating pressure - int32_t trash; + float trash; measureTempOnce(trash); //make sure the HP303B is in standby after initialization - standby(); + standby(); - // Fix IC with a fuse bit problem, which lead to a wrong temperature + // Fix IC with a fuse bit problem, which lead to a wrong temperature // Should not affect ICs without this problem correctTemp(); } @@ -1192,9 +1192,9 @@ uint16_t LOLIN_HP303B::calcBusyTime(uint16_t mr, uint16_t osr) * returns: 0 on success * -1 on fail; */ -int16_t LOLIN_HP303B::getTemp(int32_t *result) +int16_t LOLIN_HP303B::getTemp(float *result) { - uint8_t buffer[3] = {0}; + unsigned char buffer[3] = {0}; //read raw pressure data to buffer int16_t i = readBlock(HP303B__REG_ADR_TEMP, @@ -1207,14 +1207,14 @@ int16_t LOLIN_HP303B::getTemp(int32_t *result) } //compose raw temperature value from buffer - int32_t temp = (uint32_t)buffer[0] << 16 - | (uint32_t)buffer[1] << 8 - | (uint32_t)buffer[2]; + float temp = buffer[0] << 16 + | buffer[1] << 8 + | buffer[2]; //recognize non-32-bit negative numbers //and convert them to 32-bit negative numbers using 2's complement - if(temp & ((uint32_t)1 << 23)) + if(temp > 0x7FFFFF) { - temp -= (uint32_t)1 << 24; + temp = temp - 0x1000000; } //return temperature @@ -1229,9 +1229,9 @@ int16_t LOLIN_HP303B::getTemp(int32_t *result) * returns: 0 on success * -1 on fail; */ -int16_t LOLIN_HP303B::getPressure(int32_t *result) +int16_t LOLIN_HP303B::getPressure(float *result) { - uint8_t buffer[3] = {0}; + unsigned char buffer[3] = {0}; //read raw pressure data to buffer int16_t i = readBlock(HP303B__REG_ADR_PRS, HP303B__REG_LEN_PRS, @@ -1244,14 +1244,12 @@ int16_t LOLIN_HP303B::getPressure(int32_t *result) } //compose raw pressure value from buffer - int32_t prs = (uint32_t)buffer[0] << 16 - | (uint32_t)buffer[1] << 8 - | (uint32_t)buffer[2]; + float prs = buffer[0] << 16 | buffer[1] << 8 | buffer[2]; //recognize non-32-bit negative numbers //and convert them to 32-bit negative numbers using 2's complement - if(prs & ((uint32_t)1 << 23)) + if(prs > 0x7FFFFF) { - prs -= (uint32_t)1 << 24; + prs = prs - 0x1000000; } *result = calcPressure(prs); @@ -1266,7 +1264,7 @@ int16_t LOLIN_HP303B::getPressure(int32_t *result) * 0 if result is a temperature raw value * 1 if result is a pressure raw value */ -int16_t LOLIN_HP303B::getFIFOvalue(int32_t* value) +int16_t LOLIN_HP303B::getFIFOvalue(float *value) { //abort on invalid argument if(value == NULL) @@ -1274,7 +1272,7 @@ int16_t LOLIN_HP303B::getFIFOvalue(int32_t* value) return HP303B__FAIL_UNKNOWN; } - uint8_t buffer[HP303B__REG_LEN_PRS] = {0}; + unsigned char buffer[HP303B__REG_LEN_PRS] = {0}; //always read from pressure raw value register int16_t i = readBlock(HP303B__REG_ADR_PRS, HP303B__REG_LEN_PRS, @@ -1286,14 +1284,14 @@ int16_t LOLIN_HP303B::getFIFOvalue(int32_t* value) return HP303B__FAIL_UNKNOWN; } //compose raw pressure value from buffer - *value = (uint32_t)buffer[0] << 16 - | (uint32_t)buffer[1] << 8 - | (uint32_t)buffer[2]; + *value = buffer[0] << 16 + | buffer[1] << 8 + | buffer[2]; //recognize non-32-bit negative numbers //and convert them to 32-bit negative numbers using 2's complement - if(*value & ((uint32_t)1 << 23)) + if(*value > 0x7FFFFF) { - *value -= (uint32_t)1 << 24; + *value = *value - 0x1000000; } //least significant bit shows measurement type @@ -1305,10 +1303,10 @@ int16_t LOLIN_HP303B::getFIFOvalue(int32_t* value) * raw: raw temperature value read from HP303B * returns: temperature value in °C */ -int32_t LOLIN_HP303B::calcTemp(int32_t raw) +float LOLIN_HP303B::calcTemp(float raw) { double temp = raw; - + //scale temperature according to scaling table and oversampling temp /= scaling_facts[m_tempOsr]; @@ -1320,7 +1318,7 @@ int32_t LOLIN_HP303B::calcTemp(int32_t raw) temp = m_c0Half + m_c1 * temp; //return temperature - return (int32_t)temp; + return (float)temp; } /** @@ -1328,7 +1326,7 @@ int32_t LOLIN_HP303B::calcTemp(int32_t raw) * raw: raw pressure value read from HP303B * returns: pressure value in Pa */ -int32_t LOLIN_HP303B::calcPressure(int32_t raw) +float LOLIN_HP303B::calcPressure(float raw) { double prs = raw; @@ -1341,7 +1339,7 @@ int32_t LOLIN_HP303B::calcPressure(int32_t raw) + m_lastTempScal * (m_c01 + prs * (m_c11 + prs * m_c21)); //return pressure - return (int32_t)prs; + return (float)prs; } /** @@ -1357,7 +1355,7 @@ int16_t LOLIN_HP303B::readByte(uint8_t regAddress) { return readByteSPI(regAddress); } - + m_i2cbus->beginTransmission(m_slaveAddress); m_i2cbus->write(regAddress); m_i2cbus->endTransmission(0); @@ -1429,7 +1427,7 @@ int16_t LOLIN_HP303B::readBlock(uint8_t regAddress, uint8_t length, uint8_t *buf { return 0; //0 bytes read successfully } - + m_i2cbus->beginTransmission(m_slaveAddress); m_i2cbus->write(regAddress); m_i2cbus->endTransmission(0); diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h index f9c54e356..ce65e17f1 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h @@ -34,21 +34,21 @@ public: int16_t standby(void); //Command Mode - int16_t measureTempOnce(int32_t &result); - int16_t measureTempOnce(int32_t &result, uint8_t oversamplingRate); + int16_t measureTempOnce(float &result); + int16_t measureTempOnce(float &result, uint8_t oversamplingRate); int16_t startMeasureTempOnce(void); int16_t startMeasureTempOnce(uint8_t oversamplingRate); - int16_t measurePressureOnce(int32_t &result); - int16_t measurePressureOnce(int32_t &result, uint8_t oversamplingRate); + int16_t measurePressureOnce(float &result); + int16_t measurePressureOnce(float &result, uint8_t oversamplingRate); int16_t startMeasurePressureOnce(void); int16_t startMeasurePressureOnce(uint8_t oversamplingRate); - int16_t getSingleResult(int32_t &result); + int16_t getSingleResult(float &result); //Background Mode int16_t startMeasureTempCont(uint8_t measureRate, uint8_t oversamplingRate); int16_t startMeasurePressureCont(uint8_t measureRate, uint8_t oversamplingRate); int16_t startMeasureBothCont(uint8_t tempMr, uint8_t tempOsr, uint8_t prsMr, uint8_t prsOsr); - int16_t getContResults(int32_t *tempBuffer, uint8_t &tempCount, int32_t *prsBuffer, uint8_t &prsCount); + int16_t getContResults(float *tempBuffer, uint8_t &tempCount, float *prsBuffer, uint8_t &prsCount); //Interrupt Control int16_t setInterruptPolarity(uint8_t polarity); @@ -122,11 +122,11 @@ private: int16_t configTemp(uint8_t temp_mr, uint8_t temp_osr); int16_t configPressure(uint8_t prs_mr, uint8_t prs_osr); uint16_t calcBusyTime(uint16_t temp_rate, uint16_t temp_osr); - int16_t getTemp(int32_t *result); - int16_t getPressure(int32_t *result); - int16_t getFIFOvalue(int32_t *value); - int32_t calcTemp(int32_t raw); - int32_t calcPressure(int32_t raw); + int16_t getTemp(float *result); + int16_t getPressure(float *result); + int16_t getFIFOvalue(float *value); + float calcTemp(float raw); + float calcPressure(float raw); //bus specific int16_t readByte(uint8_t regAddress); diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index 3f508b7d8..926a437ad 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -51,8 +51,8 @@ bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) { HP303BSensor.begin(hp303b_address); - int32_t t; - int32_t p; + float t; + float p; int16_t ret; ret = HP303BSensor.measureTempOnce(t, oversampling); @@ -96,9 +96,15 @@ void HP303B_Show(bool json) if (HP303B_Read(temperature, pressure, address)) { + char str_temperature[33]; + dtostrfd(temperature, Settings.flag2.temperature_resolution, str_temperature); + char str_pressure[33]; + dtostrfd(pressure, Settings.flag2.pressure_resolution, str_pressure); + if (json) { - ResponseAppend_P(PSTR(",\"HP303B\":{\"" D_JSON_TEMPERATURE "\":%d,\"" D_JSON_PRESSURE "\":%d"), temperature, pressure); + ResponseAppend_P(PSTR(",\"HP303B\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_PRESSURE "\":%s"), str_temperature, str_pressure); + ResponseJsonEnd(); #ifdef USE_DOMOTICZ if (0 == tele_period) { @@ -109,11 +115,6 @@ void HP303B_Show(bool json) } else { - char str_temperature[33]; - dtostrfd(temperature, Settings.flag2.temperature_resolution, str_temperature); - char str_pressure[33]; - dtostrfd(pressure, Settings.flag2.pressure_resolution, str_pressure); - WSContentSend_PD(HTTP_SNS_TEMP, "HP303B", str_temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_PRESSURE, "HP303B", str_pressure, PressureUnit().c_str()); #endif // USE_WEBSERVER From aa5587f9e19444d33aae379e8f9dcadc1d2cad30 Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Mon, 8 Jun 2020 08:58:38 +0200 Subject: [PATCH 04/35] Mover begin() to detect function --- tasmota/xsns_73_hp303b.ino | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index 926a437ad..abff91f09 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -49,8 +49,6 @@ char types[7] = "HP303B"; bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) { - HP303BSensor.begin(hp303b_address); - float t; float p; int16_t ret; @@ -80,6 +78,8 @@ void HP303B_Detect(void) continue; } + HP303BSensor.begin(addresses[i]); + float t; float p; if (HP303B_Read(t, p, addresses[i])) @@ -87,6 +87,7 @@ void HP303B_Detect(void) I2cSetActiveFound(addresses[i], types); address = addresses[i]; type = 1; + break; } } } From d6e1ecbe2624695552c101d501ea8ded9193d12e Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Mon, 8 Jun 2020 09:13:01 +0200 Subject: [PATCH 05/35] Moved global variables to struct --- tasmota/xsns_73_hp303b.ino | 47 +++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index abff91f09..5a16ca905 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -35,16 +35,17 @@ #include // HP303B Opject LOLIN_HP303B HP303BSensor = LOLIN_HP303B(); +uint8_t bhp303b_addresses[2] = {HP303B_ADDR1, HP303B_ADDR2}; -uint8_t address; -uint8_t addresses[2] = {HP303B_ADDR1, HP303B_ADDR2}; -uint8_t type = 0; -uint8_t valid = 0; -float temperature; -float pressure; -int16_t oversampling = 7; -char types[7] = "HP303B"; - +struct BHP303B { + uint8_t address; + uint8_t type = 0; + uint8_t valid = 0; + float temperature; + float pressure; + int16_t oversampling = 7; + char types[7] = "HP303B"; +} bhp303b_sensor; /*********************************************************************************************/ bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) @@ -53,11 +54,11 @@ bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) float p; int16_t ret; - ret = HP303BSensor.measureTempOnce(t, oversampling); + ret = HP303BSensor.measureTempOnce(t, bhp303b_sensor.oversampling); if (ret != 0) return false; - ret = HP303BSensor.measurePressureOnce(p, oversampling); + ret = HP303BSensor.measurePressureOnce(p, bhp303b_sensor.oversampling); if (ret != 0) return false; @@ -71,22 +72,22 @@ bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) void HP303B_Detect(void) { - for (uint32_t i = 0; i < sizeof(addresses); i++) + for (uint32_t i = 0; i < sizeof(bhp303b_addresses); i++) { - if (!I2cSetDevice(addresses[i])) + if (!I2cSetDevice(bhp303b_addresses[i])) { continue; } - HP303BSensor.begin(addresses[i]); + HP303BSensor.begin(bhp303b_addresses[i]); float t; float p; - if (HP303B_Read(t, p, addresses[i])) + if (HP303B_Read(t, p, bhp303b_addresses[i])) { - I2cSetActiveFound(addresses[i], types); - address = addresses[i]; - type = 1; + I2cSetActiveFound(bhp303b_addresses[i], bhp303b_sensor.types); + bhp303b_sensor.address = bhp303b_addresses[i]; + bhp303b_sensor.type = 1; break; } } @@ -95,12 +96,12 @@ void HP303B_Detect(void) void HP303B_Show(bool json) { - if (HP303B_Read(temperature, pressure, address)) + if (HP303B_Read(bhp303b_sensor.temperature, bhp303b_sensor.pressure, bhp303b_sensor.address)) { char str_temperature[33]; - dtostrfd(temperature, Settings.flag2.temperature_resolution, str_temperature); + dtostrfd(bhp303b_sensor.temperature, Settings.flag2.temperature_resolution, str_temperature); char str_pressure[33]; - dtostrfd(pressure, Settings.flag2.pressure_resolution, str_pressure); + dtostrfd(bhp303b_sensor.pressure, Settings.flag2.pressure_resolution, str_pressure); if (json) { @@ -109,7 +110,7 @@ void HP303B_Show(bool json) #ifdef USE_DOMOTICZ if (0 == tele_period) { - DomoticzSensor(DZ_TEMP, temperature); + DomoticzSensor(DZ_TEMP, bhp303b_sensor.temperature); } #endif // USE_DOMOTICZ #ifdef USE_WEBSERVER @@ -140,7 +141,7 @@ bool Xsns73(uint8_t function) { HP303B_Detect(); } - else if (type) + else if (bhp303b_sensor.type) { switch (function) { From abfa4f4fcd8fb24b5d47a8a599cadd5acd8e5bda Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Mon, 8 Jun 2020 10:19:51 +0200 Subject: [PATCH 06/35] refactored implementation --- tasmota/xsns_73_hp303b.ino | 50 +++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index 5a16ca905..ad439e555 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -44,12 +44,14 @@ struct BHP303B { float temperature; float pressure; int16_t oversampling = 7; - char types[7] = "HP303B"; + char name[7] = "HP303B"; } bhp303b_sensor; /*********************************************************************************************/ -bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) +bool HP303B_Read() { + if (bhp303b_sensor.valid) { bhp303b_sensor.valid--; } + float t; float p; int16_t ret; @@ -62,9 +64,10 @@ bool HP303B_Read(float &temperature, float &pressure, uint8_t hp303b_address) if (ret != 0) return false; - temperature = (float)ConvertTemp(t); - pressure = (float)ConvertPressure(p) / 100; //conversion to hPa + bhp303b_sensor.temperature = (float)ConvertTemp(t); + bhp303b_sensor.pressure = (float)ConvertPressure(p) / 100; //conversion to hPa + bhp303b_sensor.valid = SENSOR_MAX_MISS; return true; } @@ -74,29 +77,33 @@ void HP303B_Detect(void) { for (uint32_t i = 0; i < sizeof(bhp303b_addresses); i++) { - if (!I2cSetDevice(bhp303b_addresses[i])) - { - continue; - } + if (I2cActive(bhp303b_addresses[i])) { return; } - HP303BSensor.begin(bhp303b_addresses[i]); + bhp303b_sensor.address = bhp303b_addresses[i]; - float t; - float p; - if (HP303B_Read(t, p, bhp303b_addresses[i])) + HP303BSensor.begin( bhp303b_sensor.address); + + if (HP303B_Read()) { - I2cSetActiveFound(bhp303b_addresses[i], bhp303b_sensor.types); - bhp303b_sensor.address = bhp303b_addresses[i]; + I2cSetActiveFound(bhp303b_sensor.address, bhp303b_sensor.name); bhp303b_sensor.type = 1; break; } } } +void HP303B_EverySecond(void) +{ + if (uptime &1) { + if (!HP303B_Read()) { + AddLogMissed(bhp303b_sensor.name, bhp303b_sensor.valid); + } + } +} + void HP303B_Show(bool json) { - - if (HP303B_Read(bhp303b_sensor.temperature, bhp303b_sensor.pressure, bhp303b_sensor.address)) + if (bhp303b_sensor.valid) { char str_temperature[33]; dtostrfd(bhp303b_sensor.temperature, Settings.flag2.temperature_resolution, str_temperature); @@ -130,21 +137,20 @@ void HP303B_Show(bool json) bool Xsns73(uint8_t function) { - if (!I2cEnabled(XI2C_52)) - { - return false; - } + if (!I2cEnabled(XI2C_52)) { return false; } bool result = false; - if (FUNC_INIT == function) - { + if (FUNC_INIT == function) { HP303B_Detect(); } else if (bhp303b_sensor.type) { switch (function) { + case FUNC_EVERY_SECOND: + HP303B_EverySecond(); + break; case FUNC_JSON_APPEND: HP303B_Show(1); break; From 92643e89d023aafbfc4af8e09cc4de9771f5627b Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Mon, 8 Jun 2020 20:37:04 +0200 Subject: [PATCH 07/35] Added support for multiple i2c addresses --- lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp | 32 +++---- lib/LOLIN_HP303B/src/LOLIN_HP303B.h | 10 +-- tasmota/xsns_73_hp303b.ino | 115 ++++++++++++++------------ 3 files changed, 84 insertions(+), 73 deletions(-) diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp index fdeebd6a0..7f7f60a3f 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp @@ -35,7 +35,7 @@ LOLIN_HP303B::~LOLIN_HP303B(void) * &bus: I2CBus which connects MC to HP303B * slaveAddress: Address of the HP303B (0x77 or 0x76) */ -void LOLIN_HP303B::begin(TwoWire &bus, uint8_t slaveAddress) +uint8_t LOLIN_HP303B::begin(TwoWire &bus, uint8_t slaveAddress) { //this flag will show if the initialization was successful m_initFail = 0U; @@ -50,20 +50,20 @@ void LOLIN_HP303B::begin(TwoWire &bus, uint8_t slaveAddress) delay(50); //startup time of HP303B - init(); + return init(); } -void LOLIN_HP303B::begin(uint8_t slaveAddress) +uint8_t LOLIN_HP303B::begin(uint8_t slaveAddress) { - begin(Wire,slaveAddress); + return begin(Wire,slaveAddress); } /** * SPI begin function for HP303B with 4-wire SPI */ -void LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect) +uint8_t LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect) { - begin(bus, chipSelect, 0U); + return begin(bus, chipSelect, 0U); } /** @@ -74,7 +74,7 @@ void LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect) * threeWire: 1 if HP303B is connected with 3-wire SPI * 0 if HP303B is connected with 4-wire SPI (standard) */ -void LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire) +uint8_t LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire) { //this flag will show if the initialization was successful m_initFail = 0U; @@ -102,11 +102,11 @@ void LOLIN_HP303B::begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire) if(writeByte(HP303B__REG_ADR_SPI3W, HP303B__REG_CONTENT_SPI3W)) { m_initFail = 1U; - return; + return 0U; } } - init(); + return init(); } /** @@ -840,14 +840,14 @@ int16_t LOLIN_HP303B::correctTemp(void) * This function has to be called from begin() * and requires a valid bus initialization. */ -void LOLIN_HP303B::init(void) +uint8_t LOLIN_HP303B::init(void) { int16_t prodId = readByteBitfield(HP303B__REG_INFO_PROD_ID); if(prodId != HP303B__PROD_ID) { //Connected device is not a HP303B m_initFail = 1U; - return; + return 0U; } m_productID = prodId; @@ -855,7 +855,7 @@ void LOLIN_HP303B::init(void) if(revId < 0) { m_initFail = 1U; - return; + return 0U; } m_revisionID = revId; @@ -864,7 +864,7 @@ void LOLIN_HP303B::init(void) if(sensor < 0) { m_initFail = 1U; - return; + return 0U; } //...and use this sensor for temperature measurement @@ -872,14 +872,14 @@ void LOLIN_HP303B::init(void) if(writeByteBitfield((uint8_t)sensor, HP303B__REG_INFO_TEMP_SENSOR) < 0) { m_initFail = 1U; - return; + return 0U; } //read coefficients if(readcoeffs() < 0) { m_initFail = 1U; - return; + return 0U; } //set to standby for further configuration @@ -901,6 +901,8 @@ void LOLIN_HP303B::init(void) // Fix IC with a fuse bit problem, which lead to a wrong temperature // Should not affect ICs without this problem correctTemp(); + + return 1U; } diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h index ce65e17f1..651b80380 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h @@ -19,10 +19,10 @@ public: //destructor ~LOLIN_HP303B(void); //begin - void begin(TwoWire &bus, uint8_t slaveAddress); - void begin(uint8_t slaveAddress=HP303B__STD_SLAVE_ADDRESS); - void begin(SPIClass &bus, int32_t chipSelect); - void begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire); + uint8_t begin(TwoWire &bus, uint8_t slaveAddress); + uint8_t begin(uint8_t slaveAddress=HP303B__STD_SLAVE_ADDRESS); + uint8_t begin(SPIClass &bus, int32_t chipSelect); + uint8_t begin(SPIClass &bus, int32_t chipSelect, uint8_t threeWire); //end void end(void); @@ -115,7 +115,7 @@ private: uint8_t m_threeWire; //measurement - void init(void); + uint8_t init(void); int16_t readcoeffs(void); int16_t setOpMode(uint8_t background, uint8_t temperature, uint8_t pressure); int16_t setOpMode(uint8_t opMode); diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index ad439e555..b56fa6ded 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -29,45 +29,47 @@ #define XSNS_73 73 #define XI2C_52 52 // See I2CDEVICES.md -#define HP303B_ADDR1 0x77 -#define HP303B_ADDR2 0x76 - #include -// HP303B Opject +// HP303B Object LOLIN_HP303B HP303BSensor = LOLIN_HP303B(); -uint8_t bhp303b_addresses[2] = {HP303B_ADDR1, HP303B_ADDR2}; + +#define HP303B_MAX_SENSORS 2 +#define HP303B_START_ADDRESS 0x76 + +struct { +char types[7] = "HP303B"; +uint8_t count = 0; +int16_t oversampling = 7; +} hp303b_cfg; struct BHP303B { uint8_t address; - uint8_t type = 0; uint8_t valid = 0; - float temperature; - float pressure; - int16_t oversampling = 7; - char name[7] = "HP303B"; -} bhp303b_sensor; + float temperature = NAN; + float pressure = NAN; +} hp303b_sensor[HP303B_MAX_SENSORS]; /*********************************************************************************************/ -bool HP303B_Read() +bool HP303B_Read(uint8_t hp303b_idx) { - if (bhp303b_sensor.valid) { bhp303b_sensor.valid--; } + if (hp303b_sensor[hp303b_idx].valid) { hp303b_sensor[hp303b_idx].valid--; } float t; float p; int16_t ret; - ret = HP303BSensor.measureTempOnce(t, bhp303b_sensor.oversampling); + ret = HP303BSensor.measureTempOnce(t, hp303b_cfg.oversampling); if (ret != 0) return false; - ret = HP303BSensor.measurePressureOnce(p, bhp303b_sensor.oversampling); + ret = HP303BSensor.measurePressureOnce(p, hp303b_cfg.oversampling); if (ret != 0) return false; - bhp303b_sensor.temperature = (float)ConvertTemp(t); - bhp303b_sensor.pressure = (float)ConvertPressure(p) / 100; //conversion to hPa + hp303b_sensor[hp303b_idx].temperature = (float)ConvertTemp(t); + hp303b_sensor[hp303b_idx].pressure = (float)ConvertPressure(p) / 100; //conversion to hPa - bhp303b_sensor.valid = SENSOR_MAX_MISS; + hp303b_sensor[hp303b_idx].valid = SENSOR_MAX_MISS; return true; } @@ -75,18 +77,15 @@ bool HP303B_Read() void HP303B_Detect(void) { - for (uint32_t i = 0; i < sizeof(bhp303b_addresses); i++) + for (uint32_t i = 0; i < HP303B_MAX_SENSORS; i++) { - if (I2cActive(bhp303b_addresses[i])) { return; } + if (I2cActive(HP303B_START_ADDRESS + i)) { return; } - bhp303b_sensor.address = bhp303b_addresses[i]; - - HP303BSensor.begin( bhp303b_sensor.address); - - if (HP303B_Read()) + if (HP303BSensor.begin(HP303B_START_ADDRESS + i)) { - I2cSetActiveFound(bhp303b_sensor.address, bhp303b_sensor.name); - bhp303b_sensor.type = 1; + hp303b_sensor[hp303b_cfg.count].address = HP303B_START_ADDRESS + i; + I2cSetActiveFound(hp303b_sensor[hp303b_cfg.count].address, hp303b_cfg.types); + hp303b_cfg.count++; break; } } @@ -94,39 +93,49 @@ void HP303B_Detect(void) void HP303B_EverySecond(void) { - if (uptime &1) { - if (!HP303B_Read()) { - AddLogMissed(bhp303b_sensor.name, bhp303b_sensor.valid); + for (uint32_t i = 0; i < hp303b_cfg.count; i++) { + if (uptime &1) { + if (!HP303B_Read(i)) { + AddLogMissed(hp303b_cfg.types, hp303b_sensor[i].valid); } } + } } void HP303B_Show(bool json) { - if (bhp303b_sensor.valid) - { - char str_temperature[33]; - dtostrfd(bhp303b_sensor.temperature, Settings.flag2.temperature_resolution, str_temperature); - char str_pressure[33]; - dtostrfd(bhp303b_sensor.pressure, Settings.flag2.pressure_resolution, str_pressure); - - if (json) - { - ResponseAppend_P(PSTR(",\"HP303B\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_PRESSURE "\":%s"), str_temperature, str_pressure); - ResponseJsonEnd(); -#ifdef USE_DOMOTICZ - if (0 == tele_period) - { - DomoticzSensor(DZ_TEMP, bhp303b_sensor.temperature); - } -#endif // USE_DOMOTICZ -#ifdef USE_WEBSERVER + for (uint32_t i = 0; i < hp303b_cfg.count; i++) { + char sensor_name[12]; + strlcpy(sensor_name, hp303b_cfg.types, sizeof(sensor_name)); + if (hp303b_cfg.count > 1) { + snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c0x%02X"), sensor_name, IndexSeparator(), hp303b_sensor[i].address); // MCP9808-18, MCP9808-1A etc. } - else + + if (hp303b_sensor[i].valid) { - WSContentSend_PD(HTTP_SNS_TEMP, "HP303B", str_temperature, TempUnit()); - WSContentSend_PD(HTTP_SNS_PRESSURE, "HP303B", str_pressure, PressureUnit().c_str()); -#endif // USE_WEBSERVER + char str_temperature[33]; + dtostrfd(hp303b_sensor[i].temperature, Settings.flag2.temperature_resolution, str_temperature); + char str_pressure[33]; + dtostrfd(hp303b_sensor[i].pressure, Settings.flag2.pressure_resolution, str_pressure); + + if (json) + { + ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_PRESSURE "\":%s"), sensor_name, str_temperature, str_pressure); + ResponseJsonEnd(); + #ifdef USE_DOMOTICZ + if (0 == tele_period) + { + DomoticzSensor(DZ_TEMP, hp303b_sensor[i].temperature); + } + #endif // USE_DOMOTICZ + #ifdef USE_WEBSERVER + } + else + { + WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, str_temperature, TempUnit()); + WSContentSend_PD(HTTP_SNS_PRESSURE, sensor_name, str_pressure, PressureUnit().c_str()); + #endif // USE_WEBSERVER + } } } } @@ -144,7 +153,7 @@ bool Xsns73(uint8_t function) if (FUNC_INIT == function) { HP303B_Detect(); } - else if (bhp303b_sensor.type) + else if (hp303b_cfg.count) { switch (function) { From 9d2d22558c9150a23eb86a88ba4fad0a4a9a22a5 Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Tue, 9 Jun 2020 09:14:44 +0200 Subject: [PATCH 08/35] Resolved review comments --- tasmota/xsns_73_hp303b.ino | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index b56fa6ded..01145e0d1 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -79,14 +79,13 @@ void HP303B_Detect(void) { for (uint32_t i = 0; i < HP303B_MAX_SENSORS; i++) { - if (I2cActive(HP303B_START_ADDRESS + i)) { return; } + if (!I2cSetDevice(HP303B_START_ADDRESS + i )) { continue; } if (HP303BSensor.begin(HP303B_START_ADDRESS + i)) { hp303b_sensor[hp303b_cfg.count].address = HP303B_START_ADDRESS + i; I2cSetActiveFound(hp303b_sensor[hp303b_cfg.count].address, hp303b_cfg.types); hp303b_cfg.count++; - break; } } } @@ -108,7 +107,7 @@ void HP303B_Show(bool json) char sensor_name[12]; strlcpy(sensor_name, hp303b_cfg.types, sizeof(sensor_name)); if (hp303b_cfg.count > 1) { - snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c0x%02X"), sensor_name, IndexSeparator(), hp303b_sensor[i].address); // MCP9808-18, MCP9808-1A etc. + snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c0x%02X"), sensor_name, IndexSeparator(), hp303b_sensor[i].address); // HP303B-0x76, HP303B-0x77 } if (hp303b_sensor[i].valid) @@ -123,8 +122,8 @@ void HP303B_Show(bool json) ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_PRESSURE "\":%s"), sensor_name, str_temperature, str_pressure); ResponseJsonEnd(); #ifdef USE_DOMOTICZ - if (0 == tele_period) - { + // Domoticz and knx only support one temp sensor + if ((0 == tele_period) && (0 == i)) { DomoticzSensor(DZ_TEMP, hp303b_sensor[i].temperature); } #endif // USE_DOMOTICZ From 325564fbc7da74211b70b166f27bcea9d832a022 Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Wed, 10 Jun 2020 10:03:02 +0200 Subject: [PATCH 09/35] Added option to set i2c address in measure...Once in lib --- lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp | 46 ++++++++++++++++++++++++--- lib/LOLIN_HP303B/src/LOLIN_HP303B.h | 6 ++-- tasmota/xsns_73_hp303b.ino | 4 +-- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp index 7f7f60a3f..b73e12b83 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp @@ -183,7 +183,23 @@ int16_t LOLIN_HP303B::standby(void) */ int16_t LOLIN_HP303B::measureTempOnce(float &result) { - return measureTempOnce(result, m_tempOsr); + return measureTempOnce(result, m_slaveAddress, m_tempOsr); +} + +/** + * performs one temperature measurement and writes result to the given address + * + * &result: reference to a 32-Bit signed Integer value where the result will be written + * It will not be written if result==NULL + * returns: 0 on success + * -4 if the HP303B is could not finish its measurement in time + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::measureTempOnce(float &result, uint8_t slaveAddress) +{ + return measureTempOnce(result, slaveAddress, m_tempOsr); } /** @@ -202,8 +218,11 @@ int16_t LOLIN_HP303B::measureTempOnce(float &result) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::measureTempOnce(float &result, uint8_t oversamplingRate) +int16_t LOLIN_HP303B::measureTempOnce(float &result, uint8_t slaveAddress, uint8_t oversamplingRate) { + //Set I2C bus connection + m_slaveAddress = slaveAddress; + //Start measurement int16_t ret = startMeasureTempOnce(oversamplingRate); if(ret!=HP303B__SUCCEEDED) @@ -288,7 +307,23 @@ int16_t LOLIN_HP303B::startMeasureTempOnce(uint8_t oversamplingRate) */ int16_t LOLIN_HP303B::measurePressureOnce(float &result) { - return measurePressureOnce(result, m_prsOsr); + return measurePressureOnce(result, m_slaveAddress, m_prsOsr); +} + +/** + * performs one pressure measurement and writes result to the given address + * + * &result: reference to a 32-Bit signed Integer value where the result will be written + * It will not be written if result==NULL + * returns: 0 on success + * -4 if the HP303B is could not finish its measurement in time + * -3 if the HP303B is already busy + * -2 if the object initialization failed + * -1 on other fail + */ +int16_t LOLIN_HP303B::measurePressureOnce(float &result, uint8_t slaveAddress) +{ + return measurePressureOnce(result, slaveAddress, m_prsOsr); } /** @@ -307,8 +342,11 @@ int16_t LOLIN_HP303B::measurePressureOnce(float &result) * -2 if the object initialization failed * -1 on other fail */ -int16_t LOLIN_HP303B::measurePressureOnce(float &result, uint8_t oversamplingRate) +int16_t LOLIN_HP303B::measurePressureOnce(float &result, uint8_t slaveAddress, uint8_t oversamplingRate) { + //Set I2C bus connection + m_slaveAddress = slaveAddress; + //start the measurement int16_t ret = startMeasurePressureOnce(oversamplingRate); if(ret != HP303B__SUCCEEDED) diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h index 651b80380..4d04ff6da 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.h +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.h @@ -35,11 +35,13 @@ public: //Command Mode int16_t measureTempOnce(float &result); - int16_t measureTempOnce(float &result, uint8_t oversamplingRate); + int16_t measureTempOnce(float &result, uint8_t slaveAddress); + int16_t measureTempOnce(float &result, uint8_t slaveAddress, uint8_t oversamplingRate); int16_t startMeasureTempOnce(void); int16_t startMeasureTempOnce(uint8_t oversamplingRate); int16_t measurePressureOnce(float &result); - int16_t measurePressureOnce(float &result, uint8_t oversamplingRate); + int16_t measurePressureOnce(float &result, uint8_t slaveAddress); + int16_t measurePressureOnce(float &result, uint8_t slaveAddress, uint8_t oversamplingRate); int16_t startMeasurePressureOnce(void); int16_t startMeasurePressureOnce(uint8_t oversamplingRate); int16_t getSingleResult(float &result); diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index 01145e0d1..1be0a1606 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -58,11 +58,11 @@ bool HP303B_Read(uint8_t hp303b_idx) float p; int16_t ret; - ret = HP303BSensor.measureTempOnce(t, hp303b_cfg.oversampling); + ret = HP303BSensor.measureTempOnce(t, hp303b_sensor[hp303b_idx].address, hp303b_cfg.oversampling); if (ret != 0) return false; - ret = HP303BSensor.measurePressureOnce(p, hp303b_cfg.oversampling); + ret = HP303BSensor.measurePressureOnce(p, hp303b_sensor[hp303b_idx].address, hp303b_cfg.oversampling); if (ret != 0) return false; From 1d68fe9bc6e770f0f3ac590b98cfcadfe410a52e Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Wed, 10 Jun 2020 20:14:46 +0200 Subject: [PATCH 10/35] Cleaned TLS options and prepare for TELEGRAM --- tasmota/StackThunk_light.cpp | 2 +- tasmota/WiFiClientSecureLightBearSSL.cpp | 1827 +++++++++++----------- tasmota/WiFiClientSecureLightBearSSL.h | 442 +++--- tasmota/my_user_config.h | 12 + tasmota/tasmota.ino | 4 +- tasmota/tasmota_ca.ino | 93 +- tasmota/tasmota_globals.h | 2 +- 7 files changed, 1223 insertions(+), 1159 deletions(-) mode change 100644 => 100755 tasmota/WiFiClientSecureLightBearSSL.cpp mode change 100644 => 100755 tasmota/WiFiClientSecureLightBearSSL.h diff --git a/tasmota/StackThunk_light.cpp b/tasmota/StackThunk_light.cpp index 5dcc20d62..c9f9bc78e 100644 --- a/tasmota/StackThunk_light.cpp +++ b/tasmota/StackThunk_light.cpp @@ -40,7 +40,7 @@ uint32_t *stack_thunk_light_save = NULL; /* Saved A1 while in BearSSL */ uint32_t stack_thunk_light_refcnt = 0; //#define _stackSize (5600/4) -#if defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_TLS_FORCE_EC_CIPHER) +#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER #define _stackSize (5300/4) // using a light version of bearssl we can save 300 bytes #else #define _stackSize (3600/4) // using a light version of bearssl we can save 2k diff --git a/tasmota/WiFiClientSecureLightBearSSL.cpp b/tasmota/WiFiClientSecureLightBearSSL.cpp old mode 100644 new mode 100755 index 434522b14..d0907788e --- a/tasmota/WiFiClientSecureLightBearSSL.cpp +++ b/tasmota/WiFiClientSecureLightBearSSL.cpp @@ -1,912 +1,915 @@ -/* - WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries - - Mostly compatible with Arduino WiFi shield library and standard - WiFiClient/ServerSecure (except for certificate handling). - - Copyright (c) 2018 Earle F. Philhower, III - - 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 -*/ - -#include "my_user_config.h" -//#ifdef USE_MQTT_TLS -#if defined ESP8266 && (defined(USE_MQTT_TLS) || defined (USE_SENDMAIL)) - -//#define DEBUG_TLS - -#define LWIP_INTERNAL - -#include -#include -#include - -extern "C" { -#include "osapi.h" -#include "ets_sys.h" -} -#include "debug.h" -#include "WiFiClientSecureLightBearSSL.h" // needs to be before "ESP8266WiFi.h" to avoid conflict with Arduino headers -#include "ESP8266WiFi.h" -#include "WiFiClient.h" -#include "StackThunk_light.h" -#include "lwip/opt.h" -#include "lwip/ip.h" -#include "lwip/tcp.h" -#include "lwip/inet.h" -#include "lwip/netif.h" -#include -#include "c_types.h" - -#include -#ifndef ARDUINO_ESP8266_RELEASE_2_5_2 -#undef DEBUG_TLS -#endif - -#ifdef DEBUG_TLS -#include "coredecls.h" -#define LOG_HEAP_SIZE(a) _Log_heap_size(a) -void _Log_heap_size(const char *msg) { - register uint32_t *sp asm("a1"); - int freestack = 4 * (sp - g_pcont->stack); - Serial.printf("%s %d, Fragmentation=%d, Thunkstack=%d, Free stack=%d, FreeContStack=%d\n", - msg, ESP.getFreeHeap(), ESP.getHeapFragmentation(), stack_thunk_light_get_max_usage(), - freestack, ESP.getFreeContStack()); -} -#else -#define LOG_HEAP_SIZE(a) -#endif - -// Stack thunked versions of calls -// Initially in BearSSLHelpers.h -extern "C" { -extern unsigned char *thunk_light_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len); -extern void thunk_light_br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len); -extern unsigned char *thunk_light_br_ssl_engine_recvrec_buf( const br_ssl_engine_context *cc, size_t *len); -extern void thunk_light_br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len); -extern unsigned char *thunk_light_br_ssl_engine_sendapp_buf( const br_ssl_engine_context *cc, size_t *len); -extern void thunk_light_br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len); -extern unsigned char *thunk_light_br_ssl_engine_sendrec_buf( const br_ssl_engine_context *cc, size_t *len); -extern void thunk_light_br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len); -}; - -// Second stack thunked helpers -make_stack_thunk_light(br_ssl_engine_recvapp_ack); -make_stack_thunk_light(br_ssl_engine_recvapp_buf); -make_stack_thunk_light(br_ssl_engine_recvrec_ack); -make_stack_thunk_light(br_ssl_engine_recvrec_buf); -make_stack_thunk_light(br_ssl_engine_sendapp_ack); -make_stack_thunk_light(br_ssl_engine_sendapp_buf); -make_stack_thunk_light(br_ssl_engine_sendrec_ack); -make_stack_thunk_light(br_ssl_engine_sendrec_buf); - -// create new version of Thunk function to store on SYS stack -// unless the Thunk was initialized. Thanks to AES128 GCM, we can keep -// symetric processing on the stack -void min_br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_recvapp_ack(cc, len); - } else { - return br_ssl_engine_recvapp_ack(cc, len); - } -} -unsigned char *min_br_ssl_engine_recvapp_buf(const br_ssl_engine_context *cc, size_t *len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_recvapp_buf(cc, len); - } else { - return br_ssl_engine_recvapp_buf(cc, len); - } -} -void min_br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_recvrec_ack(cc, len); - } else { - return br_ssl_engine_recvrec_ack(cc, len); - } -} -unsigned char *min_br_ssl_engine_recvrec_buf(const br_ssl_engine_context *cc, size_t *len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_recvrec_buf(cc, len); - } else { - return br_ssl_engine_recvrec_buf(cc, len); - } -} -void min_br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_sendapp_ack(cc, len); - } else { - return br_ssl_engine_sendapp_ack(cc, len); - } -} -unsigned char *min_br_ssl_engine_sendapp_buf(const br_ssl_engine_context *cc, size_t *len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_sendapp_buf(cc, len); - } else { - return br_ssl_engine_sendapp_buf(cc, len); - } -} -void min_br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_sendrec_ack(cc, len); - } else { - return br_ssl_engine_sendrec_ack(cc, len); - } -} -unsigned char *min_br_ssl_engine_sendrec_buf(const br_ssl_engine_context *cc, size_t *len) { - if (stack_thunk_light_get_refcnt()) { - return thunk_light_br_ssl_engine_sendrec_buf(cc, len); - } else { - return br_ssl_engine_sendrec_buf(cc, len); - } -} - -// Use min_ instead of original thunk_ -#define br_ssl_engine_recvapp_ack min_br_ssl_engine_recvapp_ack -#define br_ssl_engine_recvapp_buf min_br_ssl_engine_recvapp_buf -#define br_ssl_engine_recvrec_ack min_br_ssl_engine_recvrec_ack -#define br_ssl_engine_recvrec_buf min_br_ssl_engine_recvrec_buf -#define br_ssl_engine_sendapp_ack min_br_ssl_engine_sendapp_ack -#define br_ssl_engine_sendapp_buf min_br_ssl_engine_sendapp_buf -#define br_ssl_engine_sendrec_ack min_br_ssl_engine_sendrec_ack -#define br_ssl_engine_sendrec_buf min_br_ssl_engine_sendrec_buf - -//#define DEBUG_ESP_SSL -#ifdef DEBUG_ESP_SSL -#define DEBUG_BSSL(fmt, ...) DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__) -//#define DEBUG_BSSL(fmt, ...) Serial.printf(fmt, ## __VA_ARGS__) -#else -#define DEBUG_BSSL(...) -#endif - -namespace BearSSL { - -void WiFiClientSecure_light::_clear() { - // TLS handshake may take more than the 5 second default timeout - _timeout = 10000; // 10 seconds max, it should never go over 6 seconds - - _sc = nullptr; - _ctx_present = false; - _eng = nullptr; - _iobuf_in = nullptr; - _iobuf_out = nullptr; - _now = 0; // You can override or ensure time() is correct w/configTime - setBufferSizes(1024, 1024); // reasonable minimum - _handshake_done = false; - _last_error = 0; - _recvapp_buf = nullptr; - _recvapp_len = 0; - _fingerprint_any = true; // by default accept all fingerprints - _fingerprint1 = nullptr; - _fingerprint2 = nullptr; - _chain_P = nullptr; - _sk_ec_P = nullptr; - _ta_P = nullptr; - _max_thunkstack_use = 0; -} - -// Constructor -WiFiClientSecure_light::WiFiClientSecure_light(int recv, int xmit) : WiFiClient() { - _clear(); -LOG_HEAP_SIZE("StackThunk before"); - //stack_thunk_light_add_ref(); -LOG_HEAP_SIZE("StackThunk after"); - // now finish the setup - setBufferSizes(recv, xmit); // reasonable minimum - allocateBuffers(); -} - -WiFiClientSecure_light::~WiFiClientSecure_light() { - if (_client) { - _client->unref(); - _client = nullptr; - } - //_cipher_list = nullptr; // std::shared will free if last reference - _freeSSL(); -} - -void WiFiClientSecure_light::allocateBuffers(void) { - // We prefer to allocate all buffers at start, rather than lazy allocation and deallocation - // in the long run it avoids heap fragmentation and improves stability - LOG_HEAP_SIZE("allocateBuffers before"); - _sc = std::make_shared(); - LOG_HEAP_SIZE("allocateBuffers ClientContext"); - _iobuf_in = std::shared_ptr(new unsigned char[_iobuf_in_size], std::default_delete()); - _iobuf_out = std::shared_ptr(new unsigned char[_iobuf_out_size], std::default_delete()); - LOG_HEAP_SIZE("allocateBuffers after"); -} - -void WiFiClientSecure_light::setClientECCert(const br_x509_certificate *cert, const br_ec_private_key *sk, - unsigned allowed_usages, unsigned cert_issuer_key_type) { - _chain_P = cert; - _sk_ec_P = sk; - _allowed_usages = allowed_usages; - _cert_issuer_key_type = cert_issuer_key_type; -} - -void WiFiClientSecure_light::setTrustAnchor(const br_x509_trust_anchor *ta) { - _ta_P = ta; -} - -void WiFiClientSecure_light::setBufferSizes(int recv, int xmit) { - // Following constants taken from bearssl/src/ssl/ssl_engine.c (not exported unfortunately) - const int MAX_OUT_OVERHEAD = 85; - const int MAX_IN_OVERHEAD = 325; - - // The data buffers must be between 512B and 16KB - recv = std::max(512, std::min(16384, recv)); - xmit = std::max(512, std::min(16384, xmit)); - - // Add in overhead for SSL protocol - recv += MAX_IN_OVERHEAD; - xmit += MAX_OUT_OVERHEAD; - _iobuf_in_size = recv; - _iobuf_out_size = xmit; -} - -bool WiFiClientSecure_light::stop(unsigned int maxWaitMs) { -#ifdef ARDUINO_ESP8266_RELEASE_2_4_2 - WiFiClient::stop(); // calls our virtual flush() - _freeSSL(); - return true; -#else - bool ret = WiFiClient::stop(maxWaitMs); // calls our virtual flush() - _freeSSL(); - return ret; -#endif -} - -bool WiFiClientSecure_light::flush(unsigned int maxWaitMs) { - (void) _run_until(BR_SSL_SENDAPP); -#ifdef ARDUINO_ESP8266_RELEASE_2_4_2 - WiFiClient::flush(); -#else - return WiFiClient::flush(maxWaitMs); -#endif -} - -int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) { - clearLastError(); - if (!WiFiClient::connect(ip, port)) { - setLastError(ERR_TCP_CONNECT); - return 0; - } - return _connectSSL(nullptr); -} - -int WiFiClientSecure_light::connect(const char* name, uint16_t port) { - IPAddress remote_addr; - clearLastError(); - if (!WiFi.hostByName(name, remote_addr)) { - DEBUG_BSSL("connect: Name loopup failure\n"); - setLastError(ERR_CANT_RESOLVE_IP); - return 0; - } - if (!WiFiClient::connect(remote_addr, port)) { - DEBUG_BSSL("connect: Unable to connect TCP socket\n"); - _last_error = ERR_TCP_CONNECT; - return 0; - } - LOG_HEAP_SIZE("Before calling _connectSSL"); - return _connectSSL(name); -} - -void WiFiClientSecure_light::_freeSSL() { - _ctx_present = false; - _recvapp_buf = nullptr; - _recvapp_len = 0; - // This connection is toast - _handshake_done = false; -} - -bool WiFiClientSecure_light::_clientConnected() { - return (_client && _client->state() == ESTABLISHED); -} - -uint8_t WiFiClientSecure_light::connected() { - if (available() || (_clientConnected() && _handshake_done)) { - return true; - } - return false; -} - -size_t WiFiClientSecure_light::_write(const uint8_t *buf, size_t size, bool pmem) { - size_t sent_bytes = 0; - - if (!connected() || !size || !_handshake_done) { - return 0; - } - - do { - // Ensure we yield if we need multiple fragments to avoid WDT - if (sent_bytes) { - optimistic_yield(1000); - } - - // Get BearSSL to a state where we can send - if (_run_until(BR_SSL_SENDAPP) < 0) { - break; - } - - if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) { - size_t sendapp_len; - unsigned char *sendapp_buf = br_ssl_engine_sendapp_buf(_eng, &sendapp_len); - int to_send = size > sendapp_len ? sendapp_len : size; - if (pmem) { - memcpy_P(sendapp_buf, buf, to_send); - } else { - memcpy(sendapp_buf, buf, to_send); - } - br_ssl_engine_sendapp_ack(_eng, to_send); - br_ssl_engine_flush(_eng, 0); - flush(); - buf += to_send; - sent_bytes += to_send; - size -= to_send; - } else { - break; - } - } while (size); - - LOG_HEAP_SIZE("_write"); - return sent_bytes; -} - -size_t WiFiClientSecure_light::write(const uint8_t *buf, size_t size) { - return _write(buf, size, false); -} - -size_t WiFiClientSecure_light::write_P(PGM_P buf, size_t size) { - return _write((const uint8_t *)buf, size, true); -} - -// We have to manually read and send individual chunks. -size_t WiFiClientSecure_light::write(Stream& stream) { - size_t totalSent = 0; - size_t countRead; - size_t countSent; - - if (!connected() || !_handshake_done) { - DEBUG_BSSL("write: Connect/handshake not completed yet\n"); - return 0; - } - - do { - uint8_t temp[256]; // Temporary chunk size same as ClientContext - countSent = 0; - countRead = stream.readBytes(temp, sizeof(temp)); - if (countRead) { - countSent = _write((const uint8_t*)temp, countRead, true); - totalSent += countSent; - } - yield(); // Feed the WDT - } while ((countSent == countRead) && (countSent > 0)); - return totalSent; -} - -int WiFiClientSecure_light::read(uint8_t *buf, size_t size) { - if (!ctx_present() || !_handshake_done) { - return -1; - } - - int avail = available(); - bool conn = connected(); - if (!avail && conn) { - return 0; // We're still connected, but nothing to read - } - if (!avail && !conn) { - DEBUG_BSSL("read: Not connected, none left available\n"); - return -1; - } - - if (avail) { - // Take data from the recvapp buffer - int to_copy = _recvapp_len < size ? _recvapp_len : size; - memcpy(buf, _recvapp_buf, to_copy); - br_ssl_engine_recvapp_ack(_eng, to_copy); - _recvapp_buf = nullptr; - _recvapp_len = 0; - return to_copy; - } - - if (!conn) { - DEBUG_BSSL("read: Not connected\n"); - return -1; - } - return 0; // If we're connected, no error but no read. -} - -int WiFiClientSecure_light::read() { - uint8_t c; - if (1 == read(&c, 1)) { - return c; - } - DEBUG_BSSL("read: failed\n"); - return -1; -} - -int WiFiClientSecure_light::available() { - if (_recvapp_buf) { - return _recvapp_len; // Anything from last call? - } - _recvapp_buf = nullptr; - _recvapp_len = 0; - if (!ctx_present() || _run_until(BR_SSL_RECVAPP, false) < 0) { - return 0; - } - int st = br_ssl_engine_current_state(_eng); - if (st == BR_SSL_CLOSED) { - return 0; // Nothing leftover, SSL is closed - } - if (st & BR_SSL_RECVAPP) { - _recvapp_buf = br_ssl_engine_recvapp_buf(_eng, &_recvapp_len); - return _recvapp_len; - } - - return 0; -} - -int WiFiClientSecure_light::peek() { - if (!ctx_present() || !available()) { - DEBUG_BSSL("peek: Not connected, none left available\n"); - return -1; - } - if (_recvapp_buf && _recvapp_len) { - return _recvapp_buf[0]; - } - DEBUG_BSSL("peek: No data left\n"); - return -1; -} - -size_t WiFiClientSecure_light::peekBytes(uint8_t *buffer, size_t length) { - size_t to_copy = 0; - if (!ctx_present()) { - DEBUG_BSSL("peekBytes: Not connected\n"); - return 0; - } - - _startMillis = millis(); - while ((available() < (int) length) && ((millis() - _startMillis) < 5000)) { - yield(); - } - - to_copy = _recvapp_len < length ? _recvapp_len : length; - memcpy(buffer, _recvapp_buf, to_copy); - return to_copy; -} - -/* --- Copied almost verbatim from BEARSSL SSL_IO.C --- - Run the engine, until the specified target state is achieved, or - an error occurs. The target state is SENDAPP, RECVAPP, or the - combination of both (the combination matches either). When a match is - achieved, this function returns 0. On error, it returns -1. -*/ -int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { -//LOG_HEAP_SIZE("_run_until 1"); - if (!ctx_present()) { - DEBUG_BSSL("_run_until: Not connected\n"); - return -1; - } - for (int no_work = 0; blocking || no_work < 2;) { - if (blocking) { - // Only for blocking operations can we afford to yield() - optimistic_yield(100); - } - - int state; - state = br_ssl_engine_current_state(_eng); - if (state & BR_SSL_CLOSED) { - return -1; - } - - if (!(_client->state() == ESTABLISHED) && !WiFiClient::available()) { - return (state & target) ? 0 : -1; - } - - /* - If there is some record data to send, do it. This takes - precedence over everything else. - */ - if (state & BR_SSL_SENDREC) { - unsigned char *buf; - size_t len; - int wlen; - - buf = br_ssl_engine_sendrec_buf(_eng, &len); - wlen = WiFiClient::write(buf, len); - if (wlen <= 0) { - /* - If we received a close_notify and we - still send something, then we have our - own response close_notify to send, and - the peer is allowed by RFC 5246 not to - wait for it. - */ - return -1; - } - if (wlen > 0) { - br_ssl_engine_sendrec_ack(_eng, wlen); - } - no_work = 0; - continue; - } - - /* - If we reached our target, then we are finished. - */ - if (state & target) { - return 0; - } - /* - If some application data must be read, and we did not - exit, then this means that we are trying to write data, - and that's not possible until the application data is - read. This may happen if using a shared in/out buffer, - and the underlying protocol is not strictly half-duplex. - This is unrecoverable here, so we report an error. - */ - if (state & BR_SSL_RECVAPP) { - DEBUG_BSSL("_run_until: Fatal protocol state\n"); - return -1; - } - /* - If we reached that point, then either we are trying - to read data and there is some, or the engine is stuck - until a new record is obtained. - */ - if (state & BR_SSL_RECVREC) { - if (WiFiClient::available()) { - unsigned char *buf; - size_t len; - int rlen; - - buf = br_ssl_engine_recvrec_buf(_eng, &len); - rlen = WiFiClient::read(buf, len); - if (rlen < 0) { - return -1; - } - if (rlen > 0) { - br_ssl_engine_recvrec_ack(_eng, rlen); - } - no_work = 0; - continue; - } - } - /* - We can reach that point if the target RECVAPP, and - the state contains SENDAPP only. This may happen with - a shared in/out buffer. In that case, we must flush - the buffered data to "make room" for a new incoming - record. - */ - br_ssl_engine_flush(_eng, 0); - - no_work++; // We didn't actually advance here - } - // We only get here if we ran through the loop without getting anything done - return -1; -} - -bool WiFiClientSecure_light::_wait_for_handshake() { - _handshake_done = false; - while (!_handshake_done && _clientConnected()) { - int ret = _run_until(BR_SSL_SENDAPP); - if (ret < 0) { - DEBUG_BSSL("_wait_for_handshake: failed\n"); - break; - } - if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) { - _handshake_done = true; - } - optimistic_yield(1000); - } - return _handshake_done; -} - -static uint8_t htoi (unsigned char c) -{ - if (c>='0' && c <='9') return c - '0'; - else if (c>='A' && c<='F') return 10 + c - 'A'; - else if (c>='a' && c<='f') return 10 + c - 'a'; - else return 255; -} - -extern "C" { - - // see https://stackoverflow.com/questions/6357031/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-in-c - void tohex(unsigned char * in, size_t insz, char * out, size_t outsz) { - unsigned char * pin = in; - static const char * hex = "0123456789ABCDEF"; - char * pout = out; - for(; pin < in+insz; pout +=3, pin++){ - pout[0] = hex[(*pin>>4) & 0xF]; - pout[1] = hex[ *pin & 0xF]; - pout[2] = ':'; - if (pout + 3 - out > outsz){ - /* Better to truncate output string than overflow buffer */ - /* it would be still better to either return a status */ - /* or ensure the target buffer is large enough and it never happen */ - break; - } - } - pout[-1] = 0; - } - - - // BearSSL doesn't define a true insecure decoder, so we make one ourselves - // from the simple parser. It generates the issuer and subject hashes and - // the SHA1 fingerprint, only one (or none!) of which will be used to - // "verify" the certificate. - - // Private x509 decoder state - struct br_x509_pubkeyfingerprint_context { - const br_x509_class *vtable; - bool done_cert; // did we parse the first cert already? - bool fingerprint_all; - uint8_t *pubkey_recv_fingerprint; - const uint8_t *fingerprint1; - const uint8_t *fingerprint2; - unsigned usages; // pubkey usage - br_x509_decoder_context ctx; // defined in BearSSL - }; - - // Callback on the first byte of any certificate - static void pubkeyfingerprint_start_chain(const br_x509_class **ctx, const char *server_name) { - br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; - // Don't process anything but the first certificate in the chain - if (!xc->done_cert) { - br_x509_decoder_init(&xc->ctx, nullptr, nullptr, nullptr, nullptr); - } - (void)server_name; // ignore server name - } - - // Callback for each certificate present in the chain (but only operates - // on the first one by design). - static void pubkeyfingerprint_start_cert(const br_x509_class **ctx, uint32_t length) { - (void) ctx; // do nothing - (void) length; - } - - // Callback for each byte stream in the chain. Only process first cert. - static void pubkeyfingerprint_append(const br_x509_class **ctx, const unsigned char *buf, size_t len) { - br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; - // Don't process anything but the first certificate in the chain - if (!xc->done_cert) { - br_x509_decoder_push(&xc->ctx, (const void*)buf, len); - } - } - - // Callback on individual cert end. - static void pubkeyfingerprint_end_cert(const br_x509_class **ctx) { - br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; - xc->done_cert = true; // first cert already processed - } - - static void pubkeyfingerprint_pubkey_fingerprint(br_sha1_context *shactx, br_rsa_public_key rsakey) { - br_sha1_init(shactx); - br_sha1_update(shactx, "ssh-rsa", 7); // tag - br_sha1_update(shactx, rsakey.e, rsakey.elen); // exponent - br_sha1_update(shactx, rsakey.n, rsakey.nlen); // modulus - } - - // Callback when complete chain has been parsed. - // Return 0 on validation success, !0 on validation error - static unsigned pubkeyfingerprint_end_chain(const br_x509_class **ctx) { - br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; - - br_sha1_context sha1_context; - pubkeyfingerprint_pubkey_fingerprint(&sha1_context, xc->ctx.pkey.key.rsa); - br_sha1_out(&sha1_context, xc->pubkey_recv_fingerprint); // copy to fingerprint - - if (!xc->fingerprint_all) { - if (0 == memcmp(xc->fingerprint1, xc->pubkey_recv_fingerprint, 20)) { - return 0; - } - if (0 == memcmp(xc->fingerprint2, xc->pubkey_recv_fingerprint, 20)) { - return 0; - } - return 1; // no match, error - } else { - // Default (no validation at all) or no errors in prior checks = success. - return 0; - } - } - - // Return the public key from the validator (set by x509_minimal) - static const br_x509_pkey *pubkeyfingerprint_get_pkey(const br_x509_class *const *ctx, unsigned *usages) { - const br_x509_pubkeyfingerprint_context *xc = (const br_x509_pubkeyfingerprint_context *)ctx; - - if (usages != NULL) { - *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; // I said we were insecure! - } - return &xc->ctx.pkey; - } - - // Set up the x509 insecure data structures for BearSSL core to use. - void br_x509_pubkeyfingerprint_init(br_x509_pubkeyfingerprint_context *ctx, - const uint8_t *fingerprint1, const uint8_t *fingerprint2, - uint8_t *recv_fingerprint, - bool fingerprint_all) { - static const br_x509_class br_x509_pubkeyfingerprint_vtable PROGMEM = { - sizeof(br_x509_pubkeyfingerprint_context), - pubkeyfingerprint_start_chain, - pubkeyfingerprint_start_cert, - pubkeyfingerprint_append, - pubkeyfingerprint_end_cert, - pubkeyfingerprint_end_chain, - pubkeyfingerprint_get_pkey - }; - - memset(ctx, 0, sizeof * ctx); - ctx->vtable = &br_x509_pubkeyfingerprint_vtable; - ctx->done_cert = false; - ctx->fingerprint1 = fingerprint1; - ctx->fingerprint2 = fingerprint2; - ctx->pubkey_recv_fingerprint = recv_fingerprint; - ctx->fingerprint_all = fingerprint_all; - } - - // We limit to a single cipher to reduce footprint - // we reference it, don't put in PROGMEM - static const uint16_t suites[] = { -#if defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_TLS_FORCE_EC_CIPHER) - BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 -#else - BR_TLS_RSA_WITH_AES_128_GCM_SHA256 -#endif - }; - - // Default initializion for our SSL clients - static void br_ssl_client_base_init(br_ssl_client_context *cc) { - br_ssl_client_zero(cc); - // forbid SSL renegociation, as we free the Private Key after handshake - br_ssl_engine_add_flags(&cc->eng, BR_OPT_NO_RENEGOTIATION); - - br_ssl_engine_set_versions(&cc->eng, BR_TLS12, BR_TLS12); - br_ssl_engine_set_suites(&cc->eng, suites, (sizeof suites) / (sizeof suites[0])); - br_ssl_client_set_default_rsapub(cc); - br_ssl_engine_set_default_rsavrfy(&cc->eng); - - // install hashes - br_ssl_engine_set_hash(&cc->eng, br_sha256_ID, &br_sha256_vtable); - br_ssl_engine_set_prf_sha256(&cc->eng, &br_tls12_sha256_prf); - - // AES CTR/GCM small version, not contstant time (we don't really care here as there is no TPM anyways) - br_ssl_engine_set_gcm(&cc->eng, &br_sslrec_in_gcm_vtable, &br_sslrec_out_gcm_vtable); - br_ssl_engine_set_aes_ctr(&cc->eng, &br_aes_small_ctr_vtable); - br_ssl_engine_set_ghash(&cc->eng, &br_ghash_ctmul32); - -#if defined(USE_MQTT_AWS_IOT) || defined(USE_MQTT_TLS_FORCE_EC_CIPHER) - // we support only P256 EC curve for AWS IoT, no EC curve for Letsencrypt unless forced - br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); -#endif - } -} - -// Called by connect() to do the actual SSL setup and handshake. -// Returns if the SSL handshake succeeded. -bool WiFiClientSecure_light::_connectSSL(const char* hostName) { -#ifdef USE_MQTT_AWS_IOT - if ((!_chain_P) || (!_sk_ec_P)) { - setLastError(ERR_MISSING_EC_KEY); - return false; - } -#endif - - // Validation context, either full CA validation or checking only fingerprints -#ifdef USE_MQTT_TLS_CA_CERT - br_x509_minimal_context *x509_minimal; -#else - br_x509_pubkeyfingerprint_context *x509_insecure; -#endif - - LOG_HEAP_SIZE("_connectSSL.start"); - - do { // used to exit on Out of Memory error and keep all cleanup code at the same place - // ============================================================ - // allocate Thunk stack, move to alternate stack and initialize - stack_thunk_light_add_ref(); - LOG_HEAP_SIZE("Thunk allocated"); - DEBUG_BSSL("_connectSSL: start connection\n"); - _freeSSL(); - clearLastError(); - if (!stack_thunk_light_get_stack_bot()) break; - - _ctx_present = true; - _eng = &_sc->eng; // Allocation/deallocation taken care of by the _sc shared_ptr - - br_ssl_client_base_init(_sc.get()); - - // ============================================================ - // Allocatte and initialize Decoder Context - LOG_HEAP_SIZE("_connectSSL before DecoderContext allocation"); - // Only failure possible in the installation is OOM - #ifdef USE_MQTT_TLS_CA_CERT - x509_minimal = (br_x509_minimal_context*) malloc(sizeof(br_x509_minimal_context)); - if (!x509_minimal) break; - br_x509_minimal_init(x509_minimal, &br_sha256_vtable, _ta_P, 1); - br_x509_minimal_set_rsa(x509_minimal, br_ssl_engine_get_rsavrfy(_eng)); - br_x509_minimal_set_hash(x509_minimal, br_sha256_ID, &br_sha256_vtable); - br_ssl_engine_set_x509(_eng, &x509_minimal->vtable); - - #else - x509_insecure = (br_x509_pubkeyfingerprint_context*) malloc(sizeof(br_x509_pubkeyfingerprint_context)); - //x509_insecure = std::unique_ptr(new br_x509_pubkeyfingerprint_context); - if (!x509_insecure) break; - br_x509_pubkeyfingerprint_init(x509_insecure, _fingerprint1, _fingerprint2, _recv_fingerprint, _fingerprint_any); - br_ssl_engine_set_x509(_eng, &x509_insecure->vtable); - #endif - LOG_HEAP_SIZE("_connectSSL after DecoderContext allocation"); - - // ============================================================ - // Set send/receive buffers - br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size); - - // ============================================================ - // allocate Private key if needed, only if USE_MQTT_AWS_IOT - LOG_HEAP_SIZE("_connectSSL before PrivKey allocation"); - #ifdef USE_MQTT_AWS_IOT - // ============================================================ - // Set the EC Private Key, only USE_MQTT_AWS_IOT - // limited to P256 curve - br_ssl_client_set_single_ec(_sc.get(), _chain_P, 1, - _sk_ec_P, _allowed_usages, - _cert_issuer_key_type, &br_ec_p256_m15, br_ecdsa_sign_asn1_get_default()); - #endif // USE_MQTT_AWS_IOT - - // ============================================================ - // Start TLS connection, ALL - if (!br_ssl_client_reset(_sc.get(), hostName, 0)) break; - - auto ret = _wait_for_handshake(); - #ifdef DEBUG_ESP_SSL - if (!ret) { - DEBUG_BSSL("Couldn't connect. Error = %d\n", getLastError()); - } else { - DEBUG_BSSL("Connected! MFLNStatus = %d\n", getMFLNStatus()); - } - #endif - LOG_HEAP_SIZE("_connectSSL.end"); - _max_thunkstack_use = stack_thunk_light_get_max_usage(); - stack_thunk_light_del_ref(); - //stack_thunk_light_repaint(); - LOG_HEAP_SIZE("_connectSSL.end, freeing StackThunk"); - - #ifdef USE_MQTT_TLS_CA_CERT - free(x509_minimal); - #else - free(x509_insecure); - #endif - LOG_HEAP_SIZE("_connectSSL after release of Priv Key"); - return ret; - } while (0); - - // ============================================================ - // if we arrived here, this means we had an OOM error, cleaning up - setLastError(ERR_OOM); - DEBUG_BSSL("_connectSSL: Out of memory\n"); - stack_thunk_light_del_ref(); -#ifdef USE_MQTT_TLS_CA_CERT - free(x509_minimal); -#else - free(x509_insecure); -#endif - LOG_HEAP_SIZE("_connectSSL clean_on_error"); - return false; -} - -}; - -#include "t_bearssl_tasmota_config.h" - -#endif // USE_MQTT_TLS +/* + WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries + - Mostly compatible with Arduino WiFi shield library and standard + WiFiClient/ServerSecure (except for certificate handling). + + Copyright (c) 2018 Earle F. Philhower, III + + 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 +*/ + +#include "my_user_config.h" +#if defined(ESP8266) && defined(USE_TLS) + +// #define DEBUG_TLS +// #define DEBUG_ESP_SSL + +#define LWIP_INTERNAL + +#include +#include +#include + +extern "C" { +#include "osapi.h" +#include "ets_sys.h" +} +#include "debug.h" +#include "WiFiClientSecureLightBearSSL.h" // needs to be before "ESP8266WiFi.h" to avoid conflict with Arduino headers +#include "ESP8266WiFi.h" +#include "WiFiClient.h" +#include "StackThunk_light.h" +#include "lwip/opt.h" +#include "lwip/ip.h" +#include "lwip/tcp.h" +#include "lwip/inet.h" +#include "lwip/netif.h" +#include +#include "c_types.h" + +#include +#ifndef ARDUINO_ESP8266_RELEASE_2_5_2 +#undef DEBUG_TLS +#endif + +#ifdef DEBUG_TLS +#include "coredecls.h" +#define LOG_HEAP_SIZE(a) _Log_heap_size(a) +void _Log_heap_size(const char *msg) { + register uint32_t *sp asm("a1"); + int freestack = 4 * (sp - g_pcont->stack); + Serial.printf("%s %d, Fragmentation=%d, Thunkstack=%d, Free stack=%d, FreeContStack=%d\n", + msg, ESP.getFreeHeap(), ESP.getHeapFragmentation(), stack_thunk_light_get_max_usage(), + freestack, ESP.getFreeContStack()); +} +#else +#define LOG_HEAP_SIZE(a) +#endif + +// Stack thunked versions of calls +// Initially in BearSSLHelpers.h +extern "C" { +extern unsigned char *thunk_light_br_ssl_engine_recvapp_buf( const br_ssl_engine_context *cc, size_t *len); +extern void thunk_light_br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len); +extern unsigned char *thunk_light_br_ssl_engine_recvrec_buf( const br_ssl_engine_context *cc, size_t *len); +extern void thunk_light_br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len); +extern unsigned char *thunk_light_br_ssl_engine_sendapp_buf( const br_ssl_engine_context *cc, size_t *len); +extern void thunk_light_br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len); +extern unsigned char *thunk_light_br_ssl_engine_sendrec_buf( const br_ssl_engine_context *cc, size_t *len); +extern void thunk_light_br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len); +}; + +// Second stack thunked helpers +make_stack_thunk_light(br_ssl_engine_recvapp_ack); +make_stack_thunk_light(br_ssl_engine_recvapp_buf); +make_stack_thunk_light(br_ssl_engine_recvrec_ack); +make_stack_thunk_light(br_ssl_engine_recvrec_buf); +make_stack_thunk_light(br_ssl_engine_sendapp_ack); +make_stack_thunk_light(br_ssl_engine_sendapp_buf); +make_stack_thunk_light(br_ssl_engine_sendrec_ack); +make_stack_thunk_light(br_ssl_engine_sendrec_buf); + +// create new version of Thunk function to store on SYS stack +// unless the Thunk was initialized. Thanks to AES128 GCM, we can keep +// symetric processing on the stack +void min_br_ssl_engine_recvapp_ack(br_ssl_engine_context *cc, size_t len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_recvapp_ack(cc, len); + } else { + return br_ssl_engine_recvapp_ack(cc, len); + } +} +unsigned char *min_br_ssl_engine_recvapp_buf(const br_ssl_engine_context *cc, size_t *len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_recvapp_buf(cc, len); + } else { + return br_ssl_engine_recvapp_buf(cc, len); + } +} +void min_br_ssl_engine_recvrec_ack(br_ssl_engine_context *cc, size_t len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_recvrec_ack(cc, len); + } else { + return br_ssl_engine_recvrec_ack(cc, len); + } +} +unsigned char *min_br_ssl_engine_recvrec_buf(const br_ssl_engine_context *cc, size_t *len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_recvrec_buf(cc, len); + } else { + return br_ssl_engine_recvrec_buf(cc, len); + } +} +void min_br_ssl_engine_sendapp_ack(br_ssl_engine_context *cc, size_t len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_sendapp_ack(cc, len); + } else { + return br_ssl_engine_sendapp_ack(cc, len); + } +} +unsigned char *min_br_ssl_engine_sendapp_buf(const br_ssl_engine_context *cc, size_t *len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_sendapp_buf(cc, len); + } else { + return br_ssl_engine_sendapp_buf(cc, len); + } +} +void min_br_ssl_engine_sendrec_ack(br_ssl_engine_context *cc, size_t len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_sendrec_ack(cc, len); + } else { + return br_ssl_engine_sendrec_ack(cc, len); + } +} +unsigned char *min_br_ssl_engine_sendrec_buf(const br_ssl_engine_context *cc, size_t *len) { + if (stack_thunk_light_get_refcnt()) { + return thunk_light_br_ssl_engine_sendrec_buf(cc, len); + } else { + return br_ssl_engine_sendrec_buf(cc, len); + } +} + +// Use min_ instead of original thunk_ +#define br_ssl_engine_recvapp_ack min_br_ssl_engine_recvapp_ack +#define br_ssl_engine_recvapp_buf min_br_ssl_engine_recvapp_buf +#define br_ssl_engine_recvrec_ack min_br_ssl_engine_recvrec_ack +#define br_ssl_engine_recvrec_buf min_br_ssl_engine_recvrec_buf +#define br_ssl_engine_sendapp_ack min_br_ssl_engine_sendapp_ack +#define br_ssl_engine_sendapp_buf min_br_ssl_engine_sendapp_buf +#define br_ssl_engine_sendrec_ack min_br_ssl_engine_sendrec_ack +#define br_ssl_engine_sendrec_buf min_br_ssl_engine_sendrec_buf + +//#define DEBUG_ESP_SSL +#ifdef DEBUG_ESP_SSL +//#define DEBUG_BSSL(fmt, ...) DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__) +#define DEBUG_BSSL(fmt, ...) Serial.printf(fmt, ## __VA_ARGS__) +#else +#define DEBUG_BSSL(...) +#endif + +namespace BearSSL { + +void WiFiClientSecure_light::_clear() { + // TLS handshake may take more than the 5 second default timeout + _timeout = 10000; // 10 seconds max, it should never go over 6 seconds + + _sc = nullptr; + _ctx_present = false; + _eng = nullptr; + _iobuf_in = nullptr; + _iobuf_out = nullptr; + _now = 0; // You can override or ensure time() is correct w/configTime + setBufferSizes(1024, 1024); // reasonable minimum + _handshake_done = false; + _last_error = 0; + _recvapp_buf = nullptr; + _recvapp_len = 0; + _fingerprint_any = true; // by default accept all fingerprints + _fingerprint1 = nullptr; + _fingerprint2 = nullptr; + _chain_P = nullptr; + _sk_ec_P = nullptr; + _ta_P = nullptr; + _max_thunkstack_use = 0; +} + +// Constructor +WiFiClientSecure_light::WiFiClientSecure_light(int recv, int xmit) : WiFiClient() { + _clear(); +LOG_HEAP_SIZE("StackThunk before"); + //stack_thunk_light_add_ref(); +LOG_HEAP_SIZE("StackThunk after"); + // now finish the setup + setBufferSizes(recv, xmit); // reasonable minimum + allocateBuffers(); +} + +WiFiClientSecure_light::~WiFiClientSecure_light() { + if (_client) { + _client->unref(); + _client = nullptr; + } + //_cipher_list = nullptr; // std::shared will free if last reference + _freeSSL(); +} + +void WiFiClientSecure_light::allocateBuffers(void) { + // We prefer to allocate all buffers at start, rather than lazy allocation and deallocation + // in the long run it avoids heap fragmentation and improves stability + LOG_HEAP_SIZE("allocateBuffers before"); + _sc = std::make_shared(); + LOG_HEAP_SIZE("allocateBuffers ClientContext"); + _iobuf_in = std::shared_ptr(new unsigned char[_iobuf_in_size], std::default_delete()); + _iobuf_out = std::shared_ptr(new unsigned char[_iobuf_out_size], std::default_delete()); + LOG_HEAP_SIZE("allocateBuffers after"); +} + +void WiFiClientSecure_light::setClientECCert(const br_x509_certificate *cert, const br_ec_private_key *sk, + unsigned allowed_usages, unsigned cert_issuer_key_type) { + _chain_P = cert; + _sk_ec_P = sk; + _allowed_usages = allowed_usages; + _cert_issuer_key_type = cert_issuer_key_type; +} + +void WiFiClientSecure_light::setTrustAnchor(const br_x509_trust_anchor *ta) { + _ta_P = ta; +} + +void WiFiClientSecure_light::setBufferSizes(int recv, int xmit) { + // Following constants taken from bearssl/src/ssl/ssl_engine.c (not exported unfortunately) + const int MAX_OUT_OVERHEAD = 85; + const int MAX_IN_OVERHEAD = 325; + + // The data buffers must be between 512B and 16KB + recv = std::max(512, std::min(16384, recv)); + xmit = std::max(512, std::min(16384, xmit)); + + // Add in overhead for SSL protocol + recv += MAX_IN_OVERHEAD; + xmit += MAX_OUT_OVERHEAD; + _iobuf_in_size = recv; + _iobuf_out_size = xmit; +} + +bool WiFiClientSecure_light::stop(unsigned int maxWaitMs) { +#ifdef ARDUINO_ESP8266_RELEASE_2_4_2 + WiFiClient::stop(); // calls our virtual flush() + _freeSSL(); + return true; +#else + bool ret = WiFiClient::stop(maxWaitMs); // calls our virtual flush() + _freeSSL(); + return ret; +#endif +} + +bool WiFiClientSecure_light::flush(unsigned int maxWaitMs) { + (void) _run_until(BR_SSL_SENDAPP); +#ifdef ARDUINO_ESP8266_RELEASE_2_4_2 + WiFiClient::flush(); +#else + return WiFiClient::flush(maxWaitMs); +#endif +} + +int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) { + DEBUG_BSSL("connect(%s,%d)", ip.toString().c_str(), port); + clearLastError(); + if (!WiFiClient::connect(ip, port)) { + setLastError(ERR_TCP_CONNECT); + return 0; + } + return _connectSSL(nullptr); +} + +int WiFiClientSecure_light::connect(const char* name, uint16_t port) { + DEBUG_BSSL("connect(%s,%d)\n", name, port); + IPAddress remote_addr; + clearLastError(); + if (!WiFi.hostByName(name, remote_addr)) { + DEBUG_BSSL("connect: Name loopup failure\n"); + setLastError(ERR_CANT_RESOLVE_IP); + return 0; + } + DEBUG_BSSL("connect(%s,%d)\n", remote_addr.toString().c_str(), port); + if (!WiFiClient::connect(remote_addr, port)) { + DEBUG_BSSL("connect: Unable to connect TCP socket\n"); + _last_error = ERR_TCP_CONNECT; + return 0; + } + LOG_HEAP_SIZE("Before calling _connectSSL"); + return _connectSSL(name); +} + +void WiFiClientSecure_light::_freeSSL() { + _ctx_present = false; + _recvapp_buf = nullptr; + _recvapp_len = 0; + // This connection is toast + _handshake_done = false; +} + +bool WiFiClientSecure_light::_clientConnected() { + return (_client && _client->state() == ESTABLISHED); +} + +uint8_t WiFiClientSecure_light::connected() { + if (available() || (_clientConnected() && _handshake_done)) { + return true; + } + return false; +} + +size_t WiFiClientSecure_light::_write(const uint8_t *buf, size_t size, bool pmem) { + size_t sent_bytes = 0; + + if (!connected() || !size || !_handshake_done) { + return 0; + } + + do { + // Ensure we yield if we need multiple fragments to avoid WDT + if (sent_bytes) { + optimistic_yield(1000); + } + + // Get BearSSL to a state where we can send + if (_run_until(BR_SSL_SENDAPP) < 0) { + break; + } + + if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) { + size_t sendapp_len; + unsigned char *sendapp_buf = br_ssl_engine_sendapp_buf(_eng, &sendapp_len); + int to_send = size > sendapp_len ? sendapp_len : size; + if (pmem) { + memcpy_P(sendapp_buf, buf, to_send); + } else { + memcpy(sendapp_buf, buf, to_send); + } + br_ssl_engine_sendapp_ack(_eng, to_send); + br_ssl_engine_flush(_eng, 0); + flush(); + buf += to_send; + sent_bytes += to_send; + size -= to_send; + } else { + break; + } + } while (size); + + LOG_HEAP_SIZE("_write"); + return sent_bytes; +} + +size_t WiFiClientSecure_light::write(const uint8_t *buf, size_t size) { + return _write(buf, size, false); +} + +size_t WiFiClientSecure_light::write_P(PGM_P buf, size_t size) { + return _write((const uint8_t *)buf, size, true); +} + +// We have to manually read and send individual chunks. +size_t WiFiClientSecure_light::write(Stream& stream) { + size_t totalSent = 0; + size_t countRead; + size_t countSent; + + if (!connected() || !_handshake_done) { + DEBUG_BSSL("write: Connect/handshake not completed yet\n"); + return 0; + } + + do { + uint8_t temp[256]; // Temporary chunk size same as ClientContext + countSent = 0; + countRead = stream.readBytes(temp, sizeof(temp)); + if (countRead) { + countSent = _write((const uint8_t*)temp, countRead, true); + totalSent += countSent; + } + yield(); // Feed the WDT + } while ((countSent == countRead) && (countSent > 0)); + return totalSent; +} + +int WiFiClientSecure_light::read(uint8_t *buf, size_t size) { + if (!ctx_present() || !_handshake_done) { + return -1; + } + + int avail = available(); + bool conn = connected(); + if (!avail && conn) { + return 0; // We're still connected, but nothing to read + } + if (!avail && !conn) { + DEBUG_BSSL("read: Not connected, none left available\n"); + return -1; + } + + if (avail) { + // Take data from the recvapp buffer + int to_copy = _recvapp_len < size ? _recvapp_len : size; + memcpy(buf, _recvapp_buf, to_copy); + br_ssl_engine_recvapp_ack(_eng, to_copy); + _recvapp_buf = nullptr; + _recvapp_len = 0; + return to_copy; + } + + if (!conn) { + DEBUG_BSSL("read: Not connected\n"); + return -1; + } + return 0; // If we're connected, no error but no read. +} + +int WiFiClientSecure_light::read() { + uint8_t c; + if (1 == read(&c, 1)) { + return c; + } + DEBUG_BSSL("read: failed\n"); + return -1; +} + +int WiFiClientSecure_light::available() { + if (_recvapp_buf) { + return _recvapp_len; // Anything from last call? + } + _recvapp_buf = nullptr; + _recvapp_len = 0; + if (!ctx_present() || _run_until(BR_SSL_RECVAPP, false) < 0) { + return 0; + } + int st = br_ssl_engine_current_state(_eng); + if (st == BR_SSL_CLOSED) { + return 0; // Nothing leftover, SSL is closed + } + if (st & BR_SSL_RECVAPP) { + _recvapp_buf = br_ssl_engine_recvapp_buf(_eng, &_recvapp_len); + return _recvapp_len; + } + + return 0; +} + +int WiFiClientSecure_light::peek() { + if (!ctx_present() || !available()) { + DEBUG_BSSL("peek: Not connected, none left available\n"); + return -1; + } + if (_recvapp_buf && _recvapp_len) { + return _recvapp_buf[0]; + } + DEBUG_BSSL("peek: No data left\n"); + return -1; +} + +size_t WiFiClientSecure_light::peekBytes(uint8_t *buffer, size_t length) { + size_t to_copy = 0; + if (!ctx_present()) { + DEBUG_BSSL("peekBytes: Not connected\n"); + return 0; + } + + _startMillis = millis(); + while ((available() < (int) length) && ((millis() - _startMillis) < 5000)) { + yield(); + } + + to_copy = _recvapp_len < length ? _recvapp_len : length; + memcpy(buffer, _recvapp_buf, to_copy); + return to_copy; +} + +/* --- Copied almost verbatim from BEARSSL SSL_IO.C --- + Run the engine, until the specified target state is achieved, or + an error occurs. The target state is SENDAPP, RECVAPP, or the + combination of both (the combination matches either). When a match is + achieved, this function returns 0. On error, it returns -1. +*/ +int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { +//LOG_HEAP_SIZE("_run_until 1"); + if (!ctx_present()) { + DEBUG_BSSL("_run_until: Not connected\n"); + return -1; + } + for (int no_work = 0; blocking || no_work < 2;) { + if (blocking) { + // Only for blocking operations can we afford to yield() + optimistic_yield(100); + } + + int state; + state = br_ssl_engine_current_state(_eng); + if (state & BR_SSL_CLOSED) { + return -1; + } + + if (!(_client->state() == ESTABLISHED) && !WiFiClient::available()) { + return (state & target) ? 0 : -1; + } + + /* + If there is some record data to send, do it. This takes + precedence over everything else. + */ + if (state & BR_SSL_SENDREC) { + unsigned char *buf; + size_t len; + int wlen; + + buf = br_ssl_engine_sendrec_buf(_eng, &len); + wlen = WiFiClient::write(buf, len); + if (wlen <= 0) { + /* + If we received a close_notify and we + still send something, then we have our + own response close_notify to send, and + the peer is allowed by RFC 5246 not to + wait for it. + */ + return -1; + } + if (wlen > 0) { + br_ssl_engine_sendrec_ack(_eng, wlen); + } + no_work = 0; + continue; + } + + /* + If we reached our target, then we are finished. + */ + if (state & target) { + return 0; + } + /* + If some application data must be read, and we did not + exit, then this means that we are trying to write data, + and that's not possible until the application data is + read. This may happen if using a shared in/out buffer, + and the underlying protocol is not strictly half-duplex. + This is unrecoverable here, so we report an error. + */ + if (state & BR_SSL_RECVAPP) { + DEBUG_BSSL("_run_until: Fatal protocol state\n"); + return -1; + } + /* + If we reached that point, then either we are trying + to read data and there is some, or the engine is stuck + until a new record is obtained. + */ + if (state & BR_SSL_RECVREC) { + if (WiFiClient::available()) { + unsigned char *buf; + size_t len; + int rlen; + + buf = br_ssl_engine_recvrec_buf(_eng, &len); + rlen = WiFiClient::read(buf, len); + if (rlen < 0) { + return -1; + } + if (rlen > 0) { + br_ssl_engine_recvrec_ack(_eng, rlen); + } + no_work = 0; + continue; + } + } + /* + We can reach that point if the target RECVAPP, and + the state contains SENDAPP only. This may happen with + a shared in/out buffer. In that case, we must flush + the buffered data to "make room" for a new incoming + record. + */ + br_ssl_engine_flush(_eng, 0); + + no_work++; // We didn't actually advance here + } + // We only get here if we ran through the loop without getting anything done + return -1; +} + +bool WiFiClientSecure_light::_wait_for_handshake() { + _handshake_done = false; + while (!_handshake_done && _clientConnected()) { + int ret = _run_until(BR_SSL_SENDAPP); + if (ret < 0) { + DEBUG_BSSL("_wait_for_handshake: failed\n"); + break; + } + if (br_ssl_engine_current_state(_eng) & BR_SSL_SENDAPP) { + _handshake_done = true; + } + optimistic_yield(1000); + } + return _handshake_done; +} + +static uint8_t htoi (unsigned char c) +{ + if (c>='0' && c <='9') return c - '0'; + else if (c>='A' && c<='F') return 10 + c - 'A'; + else if (c>='a' && c<='f') return 10 + c - 'a'; + else return 255; +} + +extern "C" { + + // see https://stackoverflow.com/questions/6357031/how-do-you-convert-a-byte-array-to-a-hexadecimal-string-in-c + void tohex(unsigned char * in, size_t insz, char * out, size_t outsz) { + unsigned char * pin = in; + static const char * hex = "0123456789ABCDEF"; + char * pout = out; + for(; pin < in+insz; pout +=3, pin++){ + pout[0] = hex[(*pin>>4) & 0xF]; + pout[1] = hex[ *pin & 0xF]; + pout[2] = ':'; + if (pout + 3 - out > outsz){ + /* Better to truncate output string than overflow buffer */ + /* it would be still better to either return a status */ + /* or ensure the target buffer is large enough and it never happen */ + break; + } + } + pout[-1] = 0; + } + + + // BearSSL doesn't define a true insecure decoder, so we make one ourselves + // from the simple parser. It generates the issuer and subject hashes and + // the SHA1 fingerprint, only one (or none!) of which will be used to + // "verify" the certificate. + + // Private x509 decoder state + struct br_x509_pubkeyfingerprint_context { + const br_x509_class *vtable; + bool done_cert; // did we parse the first cert already? + bool fingerprint_all; + uint8_t *pubkey_recv_fingerprint; + const uint8_t *fingerprint1; + const uint8_t *fingerprint2; + unsigned usages; // pubkey usage + br_x509_decoder_context ctx; // defined in BearSSL + }; + + // Callback on the first byte of any certificate + static void pubkeyfingerprint_start_chain(const br_x509_class **ctx, const char *server_name) { + br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; + // Don't process anything but the first certificate in the chain + if (!xc->done_cert) { + br_x509_decoder_init(&xc->ctx, nullptr, nullptr, nullptr, nullptr); + } + (void)server_name; // ignore server name + } + + // Callback for each certificate present in the chain (but only operates + // on the first one by design). + static void pubkeyfingerprint_start_cert(const br_x509_class **ctx, uint32_t length) { + (void) ctx; // do nothing + (void) length; + } + + // Callback for each byte stream in the chain. Only process first cert. + static void pubkeyfingerprint_append(const br_x509_class **ctx, const unsigned char *buf, size_t len) { + br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; + // Don't process anything but the first certificate in the chain + if (!xc->done_cert) { + br_x509_decoder_push(&xc->ctx, (const void*)buf, len); + } + } + + // Callback on individual cert end. + static void pubkeyfingerprint_end_cert(const br_x509_class **ctx) { + br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; + xc->done_cert = true; // first cert already processed + } + + static void pubkeyfingerprint_pubkey_fingerprint(br_sha1_context *shactx, br_rsa_public_key rsakey) { + br_sha1_init(shactx); + br_sha1_update(shactx, "ssh-rsa", 7); // tag + br_sha1_update(shactx, rsakey.e, rsakey.elen); // exponent + br_sha1_update(shactx, rsakey.n, rsakey.nlen); // modulus + } + + // Callback when complete chain has been parsed. + // Return 0 on validation success, !0 on validation error + static unsigned pubkeyfingerprint_end_chain(const br_x509_class **ctx) { + br_x509_pubkeyfingerprint_context *xc = (br_x509_pubkeyfingerprint_context *)ctx; + + br_sha1_context sha1_context; + pubkeyfingerprint_pubkey_fingerprint(&sha1_context, xc->ctx.pkey.key.rsa); + br_sha1_out(&sha1_context, xc->pubkey_recv_fingerprint); // copy to fingerprint + + if (!xc->fingerprint_all) { + if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint1, 20)) { + return 0; + } + if (0 == memcmp_P(xc->pubkey_recv_fingerprint, xc->fingerprint2, 20)) { + return 0; + } + return 1; // no match, error + } else { + // Default (no validation at all) or no errors in prior checks = success. + return 0; + } + } + + // Return the public key from the validator (set by x509_minimal) + static const br_x509_pkey *pubkeyfingerprint_get_pkey(const br_x509_class *const *ctx, unsigned *usages) { + const br_x509_pubkeyfingerprint_context *xc = (const br_x509_pubkeyfingerprint_context *)ctx; + + if (usages != NULL) { + *usages = BR_KEYTYPE_KEYX | BR_KEYTYPE_SIGN; // I said we were insecure! + } + return &xc->ctx.pkey; + } + + // Set up the x509 insecure data structures for BearSSL core to use. + void br_x509_pubkeyfingerprint_init(br_x509_pubkeyfingerprint_context *ctx, + const uint8_t *fingerprint1, const uint8_t *fingerprint2, + uint8_t *recv_fingerprint, + bool fingerprint_all) { + static const br_x509_class br_x509_pubkeyfingerprint_vtable PROGMEM = { + sizeof(br_x509_pubkeyfingerprint_context), + pubkeyfingerprint_start_chain, + pubkeyfingerprint_start_cert, + pubkeyfingerprint_append, + pubkeyfingerprint_end_cert, + pubkeyfingerprint_end_chain, + pubkeyfingerprint_get_pkey + }; + + memset(ctx, 0, sizeof * ctx); + ctx->vtable = &br_x509_pubkeyfingerprint_vtable; + ctx->done_cert = false; + ctx->fingerprint1 = fingerprint1; + ctx->fingerprint2 = fingerprint2; + ctx->pubkey_recv_fingerprint = recv_fingerprint; + ctx->fingerprint_all = fingerprint_all; + } + + // We limit to a single cipher to reduce footprint + // we reference it, don't put in PROGMEM + static const uint16_t suites[] = { +#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER + BR_TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 +#else + BR_TLS_RSA_WITH_AES_128_GCM_SHA256 +#endif + }; + + // Default initializion for our SSL clients + static void br_ssl_client_base_init(br_ssl_client_context *cc) { + br_ssl_client_zero(cc); + // forbid SSL renegociation, as we free the Private Key after handshake + br_ssl_engine_add_flags(&cc->eng, BR_OPT_NO_RENEGOTIATION); + + br_ssl_engine_set_versions(&cc->eng, BR_TLS12, BR_TLS12); + br_ssl_engine_set_suites(&cc->eng, suites, (sizeof suites) / (sizeof suites[0])); + br_ssl_client_set_default_rsapub(cc); + br_ssl_engine_set_default_rsavrfy(&cc->eng); + + // install hashes + br_ssl_engine_set_hash(&cc->eng, br_sha256_ID, &br_sha256_vtable); + br_ssl_engine_set_prf_sha256(&cc->eng, &br_tls12_sha256_prf); + + // AES CTR/GCM small version, not contstant time (we don't really care here as there is no TPM anyways) + br_ssl_engine_set_gcm(&cc->eng, &br_sslrec_in_gcm_vtable, &br_sslrec_out_gcm_vtable); + br_ssl_engine_set_aes_ctr(&cc->eng, &br_aes_small_ctr_vtable); + br_ssl_engine_set_ghash(&cc->eng, &br_ghash_ctmul32); + +#ifdef USE_MQTT_TLS_FORCE_EC_CIPHER + // we support only P256 EC curve for AWS IoT, no EC curve for Letsencrypt unless forced + br_ssl_engine_set_ec(&cc->eng, &br_ec_p256_m15); +#endif + } +} + +// Called by connect() to do the actual SSL setup and handshake. +// Returns if the SSL handshake succeeded. +bool WiFiClientSecure_light::_connectSSL(const char* hostName) { +// #ifdef USE_MQTT_AWS_IOT +// if ((!_chain_P) || (!_sk_ec_P)) { +// setLastError(ERR_MISSING_EC_KEY); +// return false; +// } +// #endif + + // Validation context, either full CA validation or checking only fingerprints +#ifdef USE_MQTT_TLS_CA_CERT + br_x509_minimal_context *x509_minimal; +#else + br_x509_pubkeyfingerprint_context *x509_insecure; +#endif + + LOG_HEAP_SIZE("_connectSSL.start"); + + do { // used to exit on Out of Memory error and keep all cleanup code at the same place + // ============================================================ + // allocate Thunk stack, move to alternate stack and initialize + stack_thunk_light_add_ref(); + LOG_HEAP_SIZE("Thunk allocated"); + DEBUG_BSSL("_connectSSL: start connection\n"); + _freeSSL(); + clearLastError(); + if (!stack_thunk_light_get_stack_bot()) break; + + _ctx_present = true; + _eng = &_sc->eng; // Allocation/deallocation taken care of by the _sc shared_ptr + + br_ssl_client_base_init(_sc.get()); + + // ============================================================ + // Allocatte and initialize Decoder Context + LOG_HEAP_SIZE("_connectSSL before DecoderContext allocation"); + // Only failure possible in the installation is OOM + #ifdef USE_MQTT_TLS_CA_CERT + x509_minimal = (br_x509_minimal_context*) malloc(sizeof(br_x509_minimal_context)); + if (!x509_minimal) break; + br_x509_minimal_init(x509_minimal, &br_sha256_vtable, _ta_P, 1); + br_x509_minimal_set_rsa(x509_minimal, br_ssl_engine_get_rsavrfy(_eng)); + br_x509_minimal_set_hash(x509_minimal, br_sha256_ID, &br_sha256_vtable); + br_ssl_engine_set_x509(_eng, &x509_minimal->vtable); + + #else + x509_insecure = (br_x509_pubkeyfingerprint_context*) malloc(sizeof(br_x509_pubkeyfingerprint_context)); + //x509_insecure = std::unique_ptr(new br_x509_pubkeyfingerprint_context); + if (!x509_insecure) break; + br_x509_pubkeyfingerprint_init(x509_insecure, _fingerprint1, _fingerprint2, _recv_fingerprint, _fingerprint_any); + br_ssl_engine_set_x509(_eng, &x509_insecure->vtable); + #endif + LOG_HEAP_SIZE("_connectSSL after DecoderContext allocation"); + + // ============================================================ + // Set send/receive buffers + br_ssl_engine_set_buffers_bidi(_eng, _iobuf_in.get(), _iobuf_in_size, _iobuf_out.get(), _iobuf_out_size); + + // ============================================================ + // allocate Private key if needed, only if USE_MQTT_AWS_IOT + LOG_HEAP_SIZE("_connectSSL before PrivKey allocation"); + #ifdef USE_MQTT_AWS_IOT + // ============================================================ + // Set the EC Private Key, only USE_MQTT_AWS_IOT + // limited to P256 curve + br_ssl_client_set_single_ec(_sc.get(), _chain_P, 1, + _sk_ec_P, _allowed_usages, + _cert_issuer_key_type, &br_ec_p256_m15, br_ecdsa_sign_asn1_get_default()); + #endif // USE_MQTT_AWS_IOT + + // ============================================================ + // Start TLS connection, ALL + if (!br_ssl_client_reset(_sc.get(), hostName, 0)) break; + + auto ret = _wait_for_handshake(); + #ifdef DEBUG_ESP_SSL + if (!ret) { + DEBUG_BSSL("Couldn't connect. Error = %d\n", getLastError()); + } else { + DEBUG_BSSL("Connected! MFLNStatus = %d\n", getMFLNStatus()); + } + #endif + LOG_HEAP_SIZE("_connectSSL.end"); + _max_thunkstack_use = stack_thunk_light_get_max_usage(); + stack_thunk_light_del_ref(); + //stack_thunk_light_repaint(); + LOG_HEAP_SIZE("_connectSSL.end, freeing StackThunk"); + + #ifdef USE_MQTT_TLS_CA_CERT + free(x509_minimal); + #else + free(x509_insecure); + #endif + LOG_HEAP_SIZE("_connectSSL after release of Priv Key"); + return ret; + } while (0); + + // ============================================================ + // if we arrived here, this means we had an OOM error, cleaning up + setLastError(ERR_OOM); + DEBUG_BSSL("_connectSSL: Out of memory\n"); + stack_thunk_light_del_ref(); +#ifdef USE_MQTT_TLS_CA_CERT + free(x509_minimal); +#else + free(x509_insecure); +#endif + LOG_HEAP_SIZE("_connectSSL clean_on_error"); + return false; +} + +}; + +#include "t_bearssl_tasmota_config.h" + +#endif // USE_TLS diff --git a/tasmota/WiFiClientSecureLightBearSSL.h b/tasmota/WiFiClientSecureLightBearSSL.h old mode 100644 new mode 100755 index 5dd0df35e..e5908275e --- a/tasmota/WiFiClientSecureLightBearSSL.h +++ b/tasmota/WiFiClientSecureLightBearSSL.h @@ -1,221 +1,221 @@ -/* - WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries - - Mostly compatible with Arduino WiFi shield library and standard - WiFiClient/ServerSecure (except for certificate handling). - - Copyright (c) 2018 Earle F. Philhower, III - - 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 -*/ - -#include - -#ifndef wificlientlightbearssl_h -#define wificlientlightbearssl_h -#if defined(USE_MQTT_TLS) || defined (USE_SENDMAIL) -#include -#include "WiFiClient.h" -#include - -namespace BearSSL { - -class WiFiClientSecure_light : public WiFiClient { - public: - WiFiClientSecure_light(int recv, int xmit); - ~WiFiClientSecure_light() override; - - void allocateBuffers(void); - - int connect(IPAddress ip, uint16_t port) override; - int connect(const char* name, uint16_t port) override; - - uint8_t connected() override; - size_t write(const uint8_t *buf, size_t size) override; - size_t write_P(PGM_P buf, size_t size) override; - size_t write(const char *buf) { - return write((const uint8_t*)buf, strlen(buf)); - } - size_t write_P(const char *buf) { - return write_P((PGM_P)buf, strlen_P(buf)); - } - size_t write(Stream& stream); // Note this is not virtual - int read(uint8_t *buf, size_t size) override; - int available() override; - int read() override; - int peek() override; - size_t peekBytes(uint8_t *buffer, size_t length) override; - bool flush(unsigned int maxWaitMs); - bool stop(unsigned int maxWaitMs); - void flush() override { (void)flush(0); } - void stop() override { (void)stop(0); } - - // Only check SHA1 fingerprint of public key - void setPubKeyFingerprint(const uint8_t *f1, const uint8_t *f2, - bool f_any = false) { - _fingerprint1 = f1; - _fingerprint2 = f2; - _fingerprint_any = f_any; - } - const uint8_t * getRecvPubKeyFingerprint(void) { - return _recv_fingerprint; - } - - void setClientECCert(const br_x509_certificate *cert, const br_ec_private_key *sk, - unsigned allowed_usages, unsigned cert_issuer_key_type); - - void setTrustAnchor(const br_x509_trust_anchor *ta); - - // Sets the requested buffer size for transmit and receive - void setBufferSizes(int recv, int xmit); - - // Returns whether MFLN negotiation for the above buffer sizes succeeded (after connection) - int getMFLNStatus() { - return connected() && br_ssl_engine_get_mfln_negotiated(_eng); - } - - int32_t getLastError(void) { - if (_last_error) { - return _last_error; - } else { - return br_ssl_engine_last_error(_eng); - } - } - inline void setLastError(int32_t err) { - _last_error = err; - } - inline void clearLastError(void) { - _last_error = 0; - } - inline size_t getMaxThunkStackUse(void) { - return _max_thunkstack_use; - } - - private: - void _clear(); - bool _ctx_present; - std::shared_ptr _sc; - inline bool ctx_present() { - return _ctx_present; - } - br_ssl_engine_context *_eng; // &_sc->eng, to allow for client or server contexts - std::shared_ptr _iobuf_in; - std::shared_ptr _iobuf_out; - time_t _now; - int _iobuf_in_size; - int _iobuf_out_size; - bool _handshake_done; - uint64_t _last_error; - - bool _fingerprint_any; // accept all fingerprints - const uint8_t *_fingerprint1; // fingerprint1 to be checked against - const uint8_t *_fingerprint2; // fingerprint2 to be checked against - uint8_t _recv_fingerprint[20]; // fingerprint received - - unsigned char *_recvapp_buf; - size_t _recvapp_len; - - bool _clientConnected(); // Is the underlying socket alive? - bool _connectSSL(const char *hostName); // Do initial SSL handshake - void _freeSSL(); - int _run_until(unsigned target, bool blocking = true); - size_t _write(const uint8_t *buf, size_t size, bool pmem); - bool _wait_for_handshake(); // Sets and return the _handshake_done after connecting - - // Optional client certificate - const br_x509_certificate *_chain_P; // PROGMEM certificate - const br_ec_private_key *_sk_ec_P; // PROGMEM private key - const br_x509_trust_anchor *_ta_P; // PROGMEM server CA - unsigned _allowed_usages; - unsigned _cert_issuer_key_type; - - // record the maximum use of ThunkStack for monitoring - size_t _max_thunkstack_use; - -}; - -#define ERR_OOM -1000 -#define ERR_CANT_RESOLVE_IP -1001 -#define ERR_TCP_CONNECT -1002 -#define ERR_MISSING_EC_KEY -1003 -#define ERR_MISSING_CA -1004 - -// For reference, BearSSL error codes: -// #define BR_ERR_OK 0 -// #define BR_ERR_BAD_PARAM 1 -// #define BR_ERR_BAD_STATE 2 -// #define BR_ERR_UNSUPPORTED_VERSION 3 -// #define BR_ERR_BAD_VERSION 4 -// #define BR_ERR_BAD_LENGTH 5 -// #define BR_ERR_TOO_LARGE 6 -// #define BR_ERR_BAD_MAC 7 -// #define BR_ERR_NO_RANDOM 8 -// #define BR_ERR_UNKNOWN_TYPE 9 -// #define BR_ERR_UNEXPECTED 10 -// #define BR_ERR_BAD_CCS 12 -// #define BR_ERR_BAD_ALERT 13 -// #define BR_ERR_BAD_HANDSHAKE 14 -// #define BR_ERR_OVERSIZED_ID 15 -// #define BR_ERR_BAD_CIPHER_SUITE 16 -// #define BR_ERR_BAD_COMPRESSION 17 -// #define BR_ERR_BAD_FRAGLEN 18 -// #define BR_ERR_BAD_SECRENEG 19 -// #define BR_ERR_EXTRA_EXTENSION 20 -// #define BR_ERR_BAD_SNI 21 -// #define BR_ERR_BAD_HELLO_DONE 22 -// #define BR_ERR_LIMIT_EXCEEDED 23 -// #define BR_ERR_BAD_FINISHED 24 -// #define BR_ERR_RESUME_MISMATCH 25 -// #define BR_ERR_INVALID_ALGORITHM 26 -// #define BR_ERR_BAD_SIGNATURE 27 -// #define BR_ERR_WRONG_KEY_USAGE 28 -// #define BR_ERR_NO_CLIENT_AUTH 29 -// #define BR_ERR_IO 31 -// #define BR_ERR_RECV_FATAL_ALERT 256 -// #define BR_ERR_SEND_FATAL_ALERT 512 -// #define BR_ERR_X509_OK 32 -// #define BR_ERR_X509_INVALID_VALUE 33 -// #define BR_ERR_X509_TRUNCATED 34 -// #define BR_ERR_X509_EMPTY_CHAIN 35 -// #define BR_ERR_X509_INNER_TRUNC 36 -// #define BR_ERR_X509_BAD_TAG_CLASS 37 -// #define BR_ERR_X509_BAD_TAG_VALUE 38 -// #define BR_ERR_X509_INDEFINITE_LENGTH 39 -// #define BR_ERR_X509_EXTRA_ELEMENT 40 -// #define BR_ERR_X509_UNEXPECTED 41 -// #define BR_ERR_X509_NOT_CONSTRUCTED 42 -// #define BR_ERR_X509_NOT_PRIMITIVE 43 -// #define BR_ERR_X509_PARTIAL_BYTE 44 -// #define BR_ERR_X509_BAD_BOOLEAN 45 -// #define BR_ERR_X509_OVERFLOW 46 -// #define BR_ERR_X509_BAD_DN 47 -// #define BR_ERR_X509_BAD_TIME 48 -// #define BR_ERR_X509_UNSUPPORTED 49 -// #define BR_ERR_X509_LIMIT_EXCEEDED 50 -// #define BR_ERR_X509_WRONG_KEY_TYPE 51 -// #define BR_ERR_X509_BAD_SIGNATURE 52 -// #define BR_ERR_X509_TIME_UNKNOWN 53 -// #define BR_ERR_X509_EXPIRED 54 -// #define BR_ERR_X509_DN_MISMATCH 55 -// #define BR_ERR_X509_BAD_SERVER_NAME 56 -// #define BR_ERR_X509_CRITICAL_EXTENSION 57 -// #define BR_ERR_X509_NOT_CA 58 -// #define BR_ERR_X509_FORBIDDEN_KEY_USAGE 59 -// #define BR_ERR_X509_WEAK_PUBLIC_KEY 60 -// #define BR_ERR_X509_NOT_TRUSTED 62 - -}; - -#endif // USE_MQTT_TLS -#endif // wificlientlightbearssl_h +/* + WiFiClientBearSSL- SSL client/server for esp8266 using BearSSL libraries + - Mostly compatible with Arduino WiFi shield library and standard + WiFiClient/ServerSecure (except for certificate handling). + + Copyright (c) 2018 Earle F. Philhower, III + + 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 +*/ + +#include + +#ifndef wificlientlightbearssl_h +#define wificlientlightbearssl_h +#ifdef USE_TLS +#include +#include "WiFiClient.h" +#include + +namespace BearSSL { + +class WiFiClientSecure_light : public WiFiClient { + public: + WiFiClientSecure_light(int recv, int xmit); + ~WiFiClientSecure_light() override; + + void allocateBuffers(void); + + int connect(IPAddress ip, uint16_t port) override; + int connect(const char* name, uint16_t port) override; + + uint8_t connected() override; + size_t write(const uint8_t *buf, size_t size) override; + size_t write_P(PGM_P buf, size_t size) override; + size_t write(const char *buf) { + return write((const uint8_t*)buf, strlen(buf)); + } + size_t write_P(const char *buf) { + return write_P((PGM_P)buf, strlen_P(buf)); + } + size_t write(Stream& stream); // Note this is not virtual + int read(uint8_t *buf, size_t size) override; + int available() override; + int read() override; + int peek() override; + size_t peekBytes(uint8_t *buffer, size_t length) override; + bool flush(unsigned int maxWaitMs); + bool stop(unsigned int maxWaitMs); + void flush() override { (void)flush(0); } + void stop() override { (void)stop(0); } + + // Only check SHA1 fingerprint of public key + void setPubKeyFingerprint(const uint8_t *f1, const uint8_t *f2, + bool f_any = false) { + _fingerprint1 = f1; + _fingerprint2 = f2; + _fingerprint_any = f_any; + } + const uint8_t * getRecvPubKeyFingerprint(void) { + return _recv_fingerprint; + } + + void setClientECCert(const br_x509_certificate *cert, const br_ec_private_key *sk, + unsigned allowed_usages, unsigned cert_issuer_key_type); + + void setTrustAnchor(const br_x509_trust_anchor *ta); + + // Sets the requested buffer size for transmit and receive + void setBufferSizes(int recv, int xmit); + + // Returns whether MFLN negotiation for the above buffer sizes succeeded (after connection) + int getMFLNStatus() { + return connected() && br_ssl_engine_get_mfln_negotiated(_eng); + } + + int32_t getLastError(void) { + if (_last_error) { + return _last_error; + } else { + return br_ssl_engine_last_error(_eng); + } + } + inline void setLastError(int32_t err) { + _last_error = err; + } + inline void clearLastError(void) { + _last_error = 0; + } + inline size_t getMaxThunkStackUse(void) { + return _max_thunkstack_use; + } + + private: + void _clear(); + bool _ctx_present; + std::shared_ptr _sc; + inline bool ctx_present() { + return _ctx_present; + } + br_ssl_engine_context *_eng; // &_sc->eng, to allow for client or server contexts + std::shared_ptr _iobuf_in; + std::shared_ptr _iobuf_out; + time_t _now; + int _iobuf_in_size; + int _iobuf_out_size; + bool _handshake_done; + uint64_t _last_error; + + bool _fingerprint_any; // accept all fingerprints + const uint8_t *_fingerprint1; // fingerprint1 to be checked against + const uint8_t *_fingerprint2; // fingerprint2 to be checked against + uint8_t _recv_fingerprint[20]; // fingerprint received + + unsigned char *_recvapp_buf; + size_t _recvapp_len; + + bool _clientConnected(); // Is the underlying socket alive? + bool _connectSSL(const char *hostName); // Do initial SSL handshake + void _freeSSL(); + int _run_until(unsigned target, bool blocking = true); + size_t _write(const uint8_t *buf, size_t size, bool pmem); + bool _wait_for_handshake(); // Sets and return the _handshake_done after connecting + + // Optional client certificate + const br_x509_certificate *_chain_P; // PROGMEM certificate + const br_ec_private_key *_sk_ec_P; // PROGMEM private key + const br_x509_trust_anchor *_ta_P; // PROGMEM server CA + unsigned _allowed_usages; + unsigned _cert_issuer_key_type; + + // record the maximum use of ThunkStack for monitoring + size_t _max_thunkstack_use; + +}; + +#define ERR_OOM -1000 +#define ERR_CANT_RESOLVE_IP -1001 +#define ERR_TCP_CONNECT -1002 +// #define ERR_MISSING_EC_KEY -1003 // deprecated, AWS IoT is not called if the private key is not present +#define ERR_MISSING_CA -1004 + +// For reference, BearSSL error codes: +// #define BR_ERR_OK 0 +// #define BR_ERR_BAD_PARAM 1 +// #define BR_ERR_BAD_STATE 2 +// #define BR_ERR_UNSUPPORTED_VERSION 3 +// #define BR_ERR_BAD_VERSION 4 +// #define BR_ERR_BAD_LENGTH 5 +// #define BR_ERR_TOO_LARGE 6 +// #define BR_ERR_BAD_MAC 7 +// #define BR_ERR_NO_RANDOM 8 +// #define BR_ERR_UNKNOWN_TYPE 9 +// #define BR_ERR_UNEXPECTED 10 +// #define BR_ERR_BAD_CCS 12 +// #define BR_ERR_BAD_ALERT 13 +// #define BR_ERR_BAD_HANDSHAKE 14 +// #define BR_ERR_OVERSIZED_ID 15 +// #define BR_ERR_BAD_CIPHER_SUITE 16 +// #define BR_ERR_BAD_COMPRESSION 17 +// #define BR_ERR_BAD_FRAGLEN 18 +// #define BR_ERR_BAD_SECRENEG 19 +// #define BR_ERR_EXTRA_EXTENSION 20 +// #define BR_ERR_BAD_SNI 21 +// #define BR_ERR_BAD_HELLO_DONE 22 +// #define BR_ERR_LIMIT_EXCEEDED 23 +// #define BR_ERR_BAD_FINISHED 24 +// #define BR_ERR_RESUME_MISMATCH 25 +// #define BR_ERR_INVALID_ALGORITHM 26 +// #define BR_ERR_BAD_SIGNATURE 27 +// #define BR_ERR_WRONG_KEY_USAGE 28 +// #define BR_ERR_NO_CLIENT_AUTH 29 +// #define BR_ERR_IO 31 +// #define BR_ERR_RECV_FATAL_ALERT 256 +// #define BR_ERR_SEND_FATAL_ALERT 512 +// #define BR_ERR_X509_OK 32 +// #define BR_ERR_X509_INVALID_VALUE 33 +// #define BR_ERR_X509_TRUNCATED 34 +// #define BR_ERR_X509_EMPTY_CHAIN 35 +// #define BR_ERR_X509_INNER_TRUNC 36 +// #define BR_ERR_X509_BAD_TAG_CLASS 37 +// #define BR_ERR_X509_BAD_TAG_VALUE 38 +// #define BR_ERR_X509_INDEFINITE_LENGTH 39 +// #define BR_ERR_X509_EXTRA_ELEMENT 40 +// #define BR_ERR_X509_UNEXPECTED 41 +// #define BR_ERR_X509_NOT_CONSTRUCTED 42 +// #define BR_ERR_X509_NOT_PRIMITIVE 43 +// #define BR_ERR_X509_PARTIAL_BYTE 44 +// #define BR_ERR_X509_BAD_BOOLEAN 45 +// #define BR_ERR_X509_OVERFLOW 46 +// #define BR_ERR_X509_BAD_DN 47 +// #define BR_ERR_X509_BAD_TIME 48 +// #define BR_ERR_X509_UNSUPPORTED 49 +// #define BR_ERR_X509_LIMIT_EXCEEDED 50 +// #define BR_ERR_X509_WRONG_KEY_TYPE 51 +// #define BR_ERR_X509_BAD_SIGNATURE 52 +// #define BR_ERR_X509_TIME_UNKNOWN 53 +// #define BR_ERR_X509_EXPIRED 54 +// #define BR_ERR_X509_DN_MISMATCH 55 +// #define BR_ERR_X509_BAD_SERVER_NAME 56 +// #define BR_ERR_X509_CRITICAL_EXTENSION 57 +// #define BR_ERR_X509_NOT_CA 58 +// #define BR_ERR_X509_FORBIDDEN_KEY_USAGE 59 +// #define BR_ERR_X509_WEAK_PUBLIC_KEY 60 +// #define BR_ERR_X509_NOT_TRUSTED 62 + +}; + +#endif // USE_TLS +#endif // wificlientlightbearssl_h diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 6e20a598e..4a75b4b98 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -753,4 +753,16 @@ #error "Select either USE_RULES or USE_SCRIPT. They can't both be used at the same time" #endif +/*********************************************************************************************\ + * Post-process compile options for TLS +\*********************************************************************************************/ + +#if defined(USE_MQTT_TLS) || defined(USE_SENDMAIL) || defined(USE_TELEGRAM) + #define USE_TLS // flag indicates we need to include TLS code + + #if defined(USE_MQTT_AWS_IOT) || defined(USE_TELEGRAM) + #define USE_MQTT_TLS_FORCE_EC_CIPHER // AWS IoT and TELEGRAM require EC Cipher + #endif +#endif + #endif // _MY_USER_CONFIG_H_ diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index ecf1127ec..78c50edc3 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -36,9 +36,9 @@ #include "tasmota_version.h" // Tasmota version information #include "tasmota.h" // Enumeration used in my_user_config.h #include "my_user_config.h" // Fixed user configurable options -#ifdef USE_MQTT_TLS +#ifdef USE_TLS #include // We need to include before "tasmota_globals.h" to take precedence over the BearSSL version in Arduino -#endif // USE_MQTT_TLS +#endif // USE_TLS #include "tasmota_globals.h" // Function prototypes and global configuration #include "i18n.h" // Language support configured by my_user_config.h #include "tasmota_template.h" // Hardware configuration diff --git a/tasmota/tasmota_ca.ino b/tasmota/tasmota_ca.ino index 8e75f891e..1db0a8c63 100644 --- a/tasmota/tasmota_ca.ino +++ b/tasmota/tasmota_ca.ino @@ -21,9 +21,8 @@ // Please use fingerprint validation instead // However, the CA are available below for future use if it appears to be useful -#ifdef USE_MQTT_TLS_CA_CERT +#if defined(USE_TLS) && defined(USE_MQTT_TLS_CA_CERT) -#ifndef USE_MQTT_AWS_IOT /*********************************************************************************************\ * LetsEncrypt IdenTrust DST Root CA X3 certificate, RSA 2048 bits SHA 256, valid until 20210417 * @@ -35,7 +34,7 @@ * remove "static" and add "PROGMEM" \*********************************************************************************************/ -static const unsigned char PROGMEM TA0_DN[] = { +static const unsigned char PROGMEM LetsEncrypt_DN[] = { 0x30, 0x4A, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x16, 0x30, 0x14, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x0D, 0x4C, 0x65, 0x74, 0x27, 0x73, 0x20, 0x45, 0x6E, 0x63, 0x72, @@ -45,7 +44,7 @@ static const unsigned char PROGMEM TA0_DN[] = { 0x79, 0x20, 0x58, 0x33 }; -static const unsigned char PROGMEM TA0_RSA_N[] = { +static const unsigned char PROGMEM LetsEncrypt_RSA_N[] = { 0x9C, 0xD3, 0x0C, 0xF0, 0x5A, 0xE5, 0x2E, 0x47, 0xB7, 0x72, 0x5D, 0x37, 0x83, 0xB3, 0x68, 0x63, 0x30, 0xEA, 0xD7, 0x35, 0x26, 0x19, 0x25, 0xE1, 0xBD, 0xBE, 0x35, 0xF1, 0x70, 0x92, 0x2F, 0xB7, 0xB8, 0x4B, 0x41, 0x05, @@ -70,27 +69,22 @@ static const unsigned char PROGMEM TA0_RSA_N[] = { 0xD8, 0x7D, 0xC3, 0x93 }; -static const unsigned char TA0_RSA_E[] = { +static const unsigned char LetsEncrypt_RSA_E[] = { 0x01, 0x00, 0x01 }; static const br_x509_trust_anchor PROGMEM LetsEncryptX3CrossSigned_TA = { - { (unsigned char *)TA0_DN, sizeof TA0_DN }, + { (unsigned char *)LetsEncrypt_DN, sizeof LetsEncrypt_DN }, BR_X509_TA_CA, { BR_KEYTYPE_RSA, { .rsa = { - (unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N, - (unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E, + (unsigned char *)LetsEncrypt_RSA_N, sizeof LetsEncrypt_RSA_N, + (unsigned char *)LetsEncrypt_RSA_E, sizeof LetsEncrypt_RSA_E, } } } }; -#define TAs_NUM 1 - -#endif // not USE_MQTT_AWS_IOT - -#ifdef USE_MQTT_AWS_IOT /*********************************************************************************************\ * Amazon Root CA, RSA 2048 bits SHA 256, valid until 20380117 * @@ -103,7 +97,7 @@ static const br_x509_trust_anchor PROGMEM LetsEncryptX3CrossSigned_TA = { \*********************************************************************************************/ -const unsigned char PROGMEM TA0_DN[] = { +const unsigned char PROGMEM AmazonRootCA1_DN[] = { 0x30, 0x39, 0x31, 0x0B, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x0F, 0x30, 0x0D, 0x06, 0x03, 0x55, 0x04, 0x0A, 0x13, 0x06, 0x41, 0x6D, 0x61, 0x7A, 0x6F, 0x6E, 0x31, 0x19, 0x30, 0x17, @@ -111,7 +105,7 @@ const unsigned char PROGMEM TA0_DN[] = { 0x6E, 0x20, 0x52, 0x6F, 0x6F, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31 }; -const unsigned char PROGMEM TA0_RSA_N[] = { +const unsigned char PROGMEM AmazonRootCA1_RSA_N[] = { 0xB2, 0x78, 0x80, 0x71, 0xCA, 0x78, 0xD5, 0xE3, 0x71, 0xAF, 0x47, 0x80, 0x50, 0x74, 0x7D, 0x6E, 0xD8, 0xD7, 0x88, 0x76, 0xF4, 0x99, 0x68, 0xF7, 0x58, 0x21, 0x60, 0xF9, 0x74, 0x84, 0x01, 0x2F, 0xAC, 0x02, 0x2D, 0x86, @@ -136,24 +130,79 @@ const unsigned char PROGMEM TA0_RSA_N[] = { 0x9A, 0xC8, 0xAA, 0x0D }; -static const unsigned char PROGMEM TA0_RSA_E[] = { +static const unsigned char PROGMEM AmazonRootCA1_RSA_E[] = { 0x01, 0x00, 0x01 }; const br_x509_trust_anchor PROGMEM AmazonRootCA1_TA = { - { (unsigned char *)TA0_DN, sizeof TA0_DN }, + { (unsigned char *)AmazonRootCA1_DN, sizeof AmazonRootCA1_DN }, BR_X509_TA_CA, { BR_KEYTYPE_RSA, { .rsa = { - (unsigned char *)TA0_RSA_N, sizeof TA0_RSA_N, - (unsigned char *)TA0_RSA_E, sizeof TA0_RSA_E, + (unsigned char *)AmazonRootCA1_RSA_N, sizeof AmazonRootCA1_RSA_N, + (unsigned char *)AmazonRootCA1_RSA_E, sizeof AmazonRootCA1_RSA_E, } } } }; -#define TAs_NUM 1 +// we add a separate CA for telegram +/*********************************************************************************************\ + * GoDaddy Daddy Secure Certificate Authority - G2, RSA 2048 bits SHA 256, valid until 20220523 + * + * to convert do: "brssl ta GoDaddyCA.pem" + * then copy and paste below, chain the generic names to the same as below + * remove "static" and add "PROGMEM" +\*********************************************************************************************/ -#endif // USE_MQTT_AWS_IOT +const unsigned char GoDaddyCAG2_DN[] PROGMEM = { + 0x30, 0x3E, 0x31, 0x21, 0x30, 0x1F, 0x06, 0x03, 0x55, 0x04, 0x0B, 0x13, + 0x18, 0x44, 0x6F, 0x6D, 0x61, 0x69, 0x6E, 0x20, 0x43, 0x6F, 0x6E, 0x74, + 0x72, 0x6F, 0x6C, 0x20, 0x56, 0x61, 0x6C, 0x69, 0x64, 0x61, 0x74, 0x65, + 0x64, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x10, + 0x61, 0x70, 0x69, 0x2E, 0x74, 0x65, 0x6C, 0x65, 0x67, 0x72, 0x61, 0x6D, + 0x2E, 0x6F, 0x72, 0x67 +}; -#endif // USE_MQTT_TLS_CA_CERT +const unsigned char GoDaddyCAG2_RSA_N[] PROGMEM = { + 0xB4, 0xA3, 0x16, 0x9E, 0x5C, 0x57, 0xC9, 0x89, 0x65, 0xED, 0xEA, 0x78, + 0x0B, 0xAE, 0x8A, 0x58, 0x2F, 0xAE, 0x5A, 0xC8, 0x6E, 0x49, 0x8D, 0xFC, + 0x57, 0xA5, 0x98, 0x88, 0x78, 0x2E, 0x0B, 0x3C, 0x40, 0x3C, 0x21, 0x2E, + 0x9A, 0x94, 0x98, 0x33, 0xA7, 0xE3, 0x42, 0xA7, 0x85, 0xFA, 0xD0, 0x73, + 0x84, 0x01, 0x1C, 0x72, 0x39, 0x37, 0x23, 0xB5, 0x56, 0x1D, 0x43, 0xA5, + 0x71, 0x14, 0x08, 0x24, 0xA5, 0x39, 0xCC, 0xDE, 0x58, 0x53, 0x94, 0x8E, + 0x2A, 0x42, 0xA7, 0x4E, 0x2D, 0x07, 0x32, 0x9E, 0xBA, 0x8B, 0xD3, 0x2A, + 0xA9, 0x9E, 0xC0, 0xE3, 0xCE, 0x9A, 0x10, 0x96, 0x45, 0x58, 0x7A, 0xC7, + 0x1E, 0x45, 0x14, 0x23, 0x92, 0xBB, 0x54, 0x82, 0x88, 0x94, 0x49, 0xB6, + 0xBE, 0x81, 0x21, 0x00, 0x29, 0x6D, 0xC9, 0xCE, 0x8B, 0x39, 0x3A, 0xDC, + 0x35, 0x15, 0xD9, 0xEB, 0x47, 0x9C, 0xEF, 0xBA, 0x09, 0x0E, 0x16, 0xE4, + 0xD9, 0xEB, 0x72, 0x30, 0xFA, 0x49, 0xAB, 0x98, 0x31, 0x7C, 0xB3, 0xAC, + 0x2B, 0x29, 0x91, 0x87, 0x08, 0x41, 0x72, 0x5E, 0x35, 0xC7, 0x87, 0x04, + 0x22, 0xF5, 0x48, 0x76, 0x30, 0x6D, 0x88, 0xDF, 0xF2, 0xA5, 0x29, 0x13, + 0x70, 0xB3, 0x87, 0x02, 0xD5, 0x6B, 0x58, 0xB1, 0xE8, 0x73, 0xC7, 0xE4, + 0xEF, 0x79, 0x86, 0xA4, 0x07, 0x5F, 0x67, 0xB4, 0x79, 0x8D, 0xA4, 0x25, + 0x01, 0x82, 0x8C, 0xE0, 0x30, 0x17, 0xCB, 0x4B, 0x5C, 0xFB, 0xEB, 0x4C, + 0x12, 0x51, 0xB9, 0xC9, 0x04, 0x1F, 0x7E, 0xD2, 0xF8, 0xBA, 0xF5, 0x35, + 0x8D, 0x8A, 0x1C, 0x37, 0x82, 0xF0, 0x15, 0x73, 0x00, 0x6E, 0x3D, 0x1C, + 0x76, 0x8B, 0x01, 0x74, 0x81, 0x3D, 0xE4, 0x2C, 0xA7, 0xCC, 0x2F, 0x66, + 0xDC, 0x44, 0xA8, 0x27, 0x3F, 0xEA, 0xD0, 0xA7, 0xA8, 0xF1, 0xCB, 0xEA, + 0xDA, 0x07, 0x38, 0xBD +}; + +const unsigned char GoDaddyCAG2_RSA_E[] PROGMEM = { + 0x01, 0x00, 0x01 +}; + +const br_x509_trust_anchor GoDaddyCAG2_TA PROGMEM = { + { (unsigned char *)GoDaddyCAG2_DN, sizeof GoDaddyCAG2_DN }, + 0, + { + BR_KEYTYPE_RSA, + { .rsa = { + (unsigned char *)GoDaddyCAG2_RSA_N, sizeof GoDaddyCAG2_RSA_N, + (unsigned char *)GoDaddyCAG2_RSA_E, sizeof GoDaddyCAG2_RSA_E, + } } + } +}; + +#endif // defined(USE_TLS) && defined(USE_MQTT_TLS_CA_CERT) diff --git a/tasmota/tasmota_globals.h b/tasmota/tasmota_globals.h index 44826fa64..f33024c45 100644 --- a/tasmota/tasmota_globals.h +++ b/tasmota/tasmota_globals.h @@ -88,7 +88,7 @@ extern "C" void resetPins(); const uint16_t WEB_LOG_SIZE = 4000; // Max number of characters in weblog #endif -#if defined(USE_MQTT_TLS) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) +#if defined(USE_TLS) && defined(ARDUINO_ESP8266_RELEASE_2_3_0) #error "TLS is no more supported on Core 2.3.0, use 2.4.2 or higher." #endif From dc8d354f161c74fefbcbf4a22b78364d4b25a720 Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Wed, 10 Jun 2020 21:14:18 +0200 Subject: [PATCH 11/35] Fix asm for gcc17 --- tasmota/StackThunk_light.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/StackThunk_light.h b/tasmota/StackThunk_light.h index 13a0cb7d3..164417000 100644 --- a/tasmota/StackThunk_light.h +++ b/tasmota/StackThunk_light.h @@ -52,7 +52,7 @@ extern uint32_t stack_thunk_light_refcnt; // Thunking macro #define make_stack_thunk_light(fcnToThunk) \ -__asm("\n\ +__asm__("\n\ .text\n\ .literal_position\n\ .literal .LC_STACK_VALUE"#fcnToThunk", 0xdeadbeef\n\ From 8cbb35ec4fbb00e29e754c0602c75af1a2c70fce Mon Sep 17 00:00:00 2001 From: Robert Jaakke Date: Wed, 10 Jun 2020 21:54:32 +0200 Subject: [PATCH 12/35] Removed m_initFail check because we moved it to begin() --- lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp index b73e12b83..c6f8aeb1d 100644 --- a/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp +++ b/lib/LOLIN_HP303B/src/LOLIN_HP303B.cpp @@ -147,11 +147,6 @@ uint8_t LOLIN_HP303B::getRevisionId(void) */ int16_t LOLIN_HP303B::standby(void) { - //abort if initialization failed - if(m_initFail) - { - return HP303B__FAIL_INIT_FAILED; - } //set device to idling mode int16_t ret = setOpMode(IDLE); if(ret != HP303B__SUCCEEDED) @@ -270,11 +265,6 @@ int16_t LOLIN_HP303B::startMeasureTempOnce(void) */ int16_t LOLIN_HP303B::startMeasureTempOnce(uint8_t oversamplingRate) { - //abort if initialization failed - if(m_initFail) - { - return HP303B__FAIL_INIT_FAILED; - } //abort if device is not in idling mode if(m_opMode!=IDLE) { @@ -394,11 +384,6 @@ int16_t LOLIN_HP303B::startMeasurePressureOnce(void) */ int16_t LOLIN_HP303B::startMeasurePressureOnce(uint8_t oversamplingRate) { - //abort if initialization failed - if(m_initFail) - { - return HP303B__FAIL_INIT_FAILED; - } //abort if device is not in idling mode if(m_opMode != IDLE) { @@ -428,12 +413,6 @@ int16_t LOLIN_HP303B::startMeasurePressureOnce(uint8_t oversamplingRate) */ int16_t LOLIN_HP303B::getSingleResult(float &result) { - //abort if initialization failed - if(m_initFail) - { - return HP303B__FAIL_INIT_FAILED; - } - //read finished bit for current opMode int16_t rdy; switch(m_opMode) From 440219fd91afef1b763a02efc80cadd9663d369d Mon Sep 17 00:00:00 2001 From: Phil Dubach Date: Wed, 10 Jun 2020 19:55:49 -0700 Subject: [PATCH 13/35] Fix thermostat when using local sensor Macros are not expanded in string constants, so the thermostat driver never managed to obtain the current temperature from the local sensor (SensorInputSet 1). --- tasmota/xdrv_39_thermostat.ino | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/xdrv_39_thermostat.ino b/tasmota/xdrv_39_thermostat.ino index 79e842dc5..768088117 100644 --- a/tasmota/xdrv_39_thermostat.ino +++ b/tasmota/xdrv_39_thermostat.ino @@ -1331,7 +1331,7 @@ void ThermostatGetLocalSensor(uint8_t ctr_output) { DynamicJsonBuffer jsonBuffer; JsonObject& root = jsonBuffer.parseObject((const char*)mqtt_data); if (root.success()) { - const char* value_c = root["THERMOSTAT_SENSOR_NAME"]["Temperature"]; + const char* value_c = root[THERMOSTAT_SENSOR_NAME]["Temperature"]; if (value_c != NULL && strlen(value_c) > 0 && (isdigit(value_c[0]) || (value_c[0] == '-' && isdigit(value_c[1])) ) ) { int16_t value = (int16_t)(CharToFloat(value_c) * 10); if ( (value >= -1000) From 2453beb30f7e6b1792b2ac073deb84fd7b16b9af Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Thu, 11 Jun 2020 06:43:24 +0200 Subject: [PATCH 14/35] scripter fix sdcard regression --- tasmota/xdrv_10_scripter.ino | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 5a79d50b5..4d0bc1588 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -3979,8 +3979,7 @@ void ListDir(char *path, uint8_t depth) { char path[48]; -void Script_FileUploadConfiguration(void) -{ +void Script_FileUploadConfiguration(void) { uint8_t depth=0; strcpy(path,"/"); @@ -3995,17 +3994,6 @@ void Script_FileUploadConfiguration(void) } } - void ScriptFileUploadSuccess(void) { - WSContentStart_P(S_INFORMATION); - WSContentSendStyle(); - WSContentSend_P(PSTR("
" D_UPLOAD " " D_SUCCESSFUL "
"), WebColor(COL_TEXT_SUCCESS)); - WSContentSend_P(PSTR("

")); - WSContentSend_P(PSTR("

"),"/upl",D_UPL_DONE); - //WSContentSpaceButton(BUTTON_MAIN); - WSContentStop(); - } - WSContentStart_P(S_SCRIPT_FILE_UPLOAD); WSContentSendStyle(); WSContentSend_P(HTTP_FORM_FILE_UPLOAD,D_SDCARD_DIR); @@ -4023,13 +4011,22 @@ void Script_FileUploadConfiguration(void) Web.upload_error = 0; } +void ScriptFileUploadSuccess(void) { + WSContentStart_P(S_INFORMATION); + WSContentSendStyle(); + WSContentSend_P(PSTR("
" D_UPLOAD " " D_SUCCESSFUL "
"), WebColor(COL_TEXT_SUCCESS)); + WSContentSend_P(PSTR("

")); + WSContentSend_P(PSTR("

"),"/upl",D_UPL_DONE); + //WSContentSpaceButton(BUTTON_MAIN); + WSContentStop(); +} + + File upload_file; - void script_upload(void) { - //AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: file upload")); - HTTPUpload& upload = Webserver->upload(); if (upload.status == UPLOAD_FILE_START) { char npath[48]; From ce4f987367461dd1cdc16627bbd039050b6f0ad4 Mon Sep 17 00:00:00 2001 From: Staars Date: Thu, 11 Jun 2020 09:55:07 +0200 Subject: [PATCH 15/35] bugfix --- tasmota/xsns_48_chirp.ino | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tasmota/xsns_48_chirp.ino b/tasmota/xsns_48_chirp.ino index 9606b419c..934dff205 100644 --- a/tasmota/xsns_48_chirp.ino +++ b/tasmota/xsns_48_chirp.ino @@ -20,6 +20,8 @@ Version Date Action Description -------------------------------------------------------------------------------------------- + 1.0.0.2 20200611 changed - bugfix: decouple restart of the work loop from FUNC_JSON_APPEND callback + --- 1.0.0.1 20190917 changed - rework of the inner loop to enable delays in the middle of I2C-reads changed - double send address change only for fw>0x25 changed - use DEBUG_SENSOR_LOG, change ILLUMINANCE to DARKNESS @@ -300,7 +302,7 @@ void ChirpServiceAllSensors(uint8_t job){ void ChirpEvery100MSecond(void) { - // DEBUG_SENSOR_LOG(PSTR("CHIRP: every second")); + // DEBUG_SENSOR_LOG(PSTR("CHIRP: every 100 mseconds, counter: %u, next job: %u"),chirp_timeout_count,chirp_next_job); if(chirp_timeout_count == 0) { //countdown complete, now do something switch(chirp_next_job) { case 0: //this should only be called after driver initialization @@ -377,10 +379,11 @@ void ChirpEvery100MSecond(void) break; case 13: DEBUG_SENSOR_LOG(PSTR("CHIRP: paused, waiting for TELE")); + chirp_next_job++; break; case 14: if (Settings.tele_period > 16){ - chirp_timeout_count = (Settings.tele_period - 17) * 10; // sync it with the TELEPERIOD, we need about up to 17 seconds to measure + chirp_timeout_count = (Settings.tele_period - 16) * 10; // sync it with the TELEPERIOD, we need about up to 16 seconds to measure DEBUG_SENSOR_LOG(PSTR("CHIRP: timeout 1/10 sec: %u, tele: %u"), chirp_timeout_count, Settings.tele_period); } else{ @@ -533,7 +536,6 @@ bool Xsns48(uint8_t function) break; case FUNC_JSON_APPEND: ChirpShow(1); - chirp_next_job = 14; // TELE done, now compute time for next measure cycle break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: From 4c5b2f37fdbcb742083165e25d92f5cb4c69a051 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 11 Jun 2020 17:30:33 +0200 Subject: [PATCH 16/35] Add initial support for Telegram Add initial support for Telegram bot (#8619) --- RELEASENOTES.md | 3 +- tasmota/CHANGELOG.md | 4 + tasmota/my_user_config.h | 5 + tasmota/support_features.ino | 4 +- tasmota/tasmota.h | 13 +- tasmota/tasmota_version.h | 2 +- tasmota/xdrv_40_telegram.ino | 479 +++++++++++++++++++++++++++++++++++ tools/decode-status.py | 4 +- 8 files changed, 503 insertions(+), 11 deletions(-) create mode 100644 tasmota/xdrv_40_telegram.ino diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fdac7edde..ab01ddaa4 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -52,7 +52,7 @@ The following binary downloads have been compiled with ESP8266/Arduino library c ## Changelog -### Version 8.3.1.2 +### Version 8.3.1.3 - Change IRremoteESP8266 library updated to v2.7.7 - Change Adafruit_SGP30 library from v1.0.3 to v1.2.0 (#8519) @@ -76,3 +76,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - 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) - Add support for BL0940 energy monitor as used in Blitzwolf BW-SHP10 (#8175) +- Add initial support for Telegram bot (#8619) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 4f7d3132b..9033a2bba 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -1,5 +1,9 @@ ## Unreleased (development) +### 8.3.1.3 20200611 + +- Add initial support for Telegram bot (#8619) + ### 8.3.1.2 20200522 - Change Energy JSON Total field from ``"Total":[33.736,11.717,16.978]`` to ``"Total":33.736,"TotalTariff":[11.717,16.978]`` diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index 4a75b4b98..ea2e6eefe 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -367,6 +367,11 @@ // Full documentation here: https://github.com/arendst/Tasmota/wiki/AWS-IoT // #define USE_4K_RSA // Support 4096 bits certificates, instead of 2048 +// -- Telegram Protocol --------------------------- +//#define USE_TELEGRAM // Support for Telegram protocol (+49k code, +7.0k mem and +4.8k additional during connection handshake) + #define USE_TELEGRAM_FINGERPRINT "\xB2\x72\x47\xA6\x69\x8C\x3C\x69\xF9\x58\x6C\xF3\x60\x02\xFB\x83\xFA\x8B\x1F\x23" // Telegram api.telegram.org TLS public key fingerpring +// #define USE_MQTT_TLS_CA_CERT // Use certificate instead of fingerprint + // -- KNX IP Protocol ----------------------------- //#define USE_KNX // Enable KNX IP Protocol Support (+9.4k code, +3k7 mem) #define USE_KNX_WEB_MENU // Enable KNX WEB MENU (+8.3k code, +144 mem) diff --git a/tasmota/support_features.ino b/tasmota/support_features.ino index 20bb9acf3..04de532d6 100644 --- a/tasmota/support_features.ino +++ b/tasmota/support_features.ino @@ -575,7 +575,9 @@ void GetFeatures(void) #ifdef USE_BL0940 feature6 |= 0x00004000; // xnrg_14_bl0940.ino #endif -// feature6 |= 0x00008000; +#ifdef USE_TELEGRAM + feature6 |= 0x00008000; // xdrv_40_telegram.ino +#endif // feature6 |= 0x00010000; // feature6 |= 0x00020000; diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index c3913e8a1..49347f1f8 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -293,7 +293,7 @@ enum SettingsTextIndex { SET_OTAURL, SET_TEMPLATE_NAME, SET_DEV_GROUP_NAME1, SET_DEV_GROUP_NAME2, SET_DEV_GROUP_NAME3, SET_DEV_GROUP_NAME4, SET_DEVICENAME, - SET_TELEGRAMTOKEN, + SET_TELEGRAM_TOKEN, SET_TELEGRAM_CHATID, SET_MAX }; enum DevGroupMessageType { DGR_MSGTYP_FULL_STATUS, DGR_MSGTYP_PARTIAL_UPDATE, DGR_MSGTYP_UPDATE, DGR_MSGTYP_UPDATE_MORE_TO_COME, DGR_MSGTYP_UPDATE_DIRECT, DGR_MSGTYPE_UPDATE_COMMAND }; @@ -321,9 +321,10 @@ enum DevGroupShareItem { DGR_SHARE_POWER = 1, DGR_SHARE_LIGHT_BRI = 2, DGR_SHARE enum CommandSource { SRC_IGNORE, SRC_MQTT, SRC_RESTART, SRC_BUTTON, SRC_SWITCH, SRC_BACKLOG, SRC_SERIAL, SRC_WEBGUI, SRC_WEBCOMMAND, SRC_WEBCONSOLE, SRC_PULSETIMER, SRC_TIMER, SRC_RULE, SRC_MAXPOWER, SRC_MAXENERGY, SRC_OVERTEMP, SRC_LIGHT, SRC_KNX, SRC_DISPLAY, SRC_WEMO, SRC_HUE, SRC_RETRY, SRC_REMOTE, SRC_SHUTTER, - SRC_THERMOSTAT, SRC_MAX }; + SRC_THERMOSTAT, SRC_CHAT, SRC_MAX }; const char kCommandSource[] PROGMEM = "I|MQTT|Restart|Button|Switch|Backlog|Serial|WebGui|WebCommand|WebConsole|PulseTimer|" - "Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote|Shutter|Thermostat"; + "Timer|Rule|MaxPower|MaxEnergy|Overtemp|Light|Knx|Display|Wemo|Hue|Retry|Remote|Shutter|" + "Thermostat|Chat"; const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 }; @@ -344,10 +345,10 @@ const SerConfu8 kTasmotaSerialConfig[] PROGMEM = { SERIAL_5O2, SERIAL_6O2, SERIAL_7O2, SERIAL_8O2 }; -enum TuyaSupportedFunctions { TUYA_MCU_FUNC_NONE, TUYA_MCU_FUNC_SWT1 = 1, TUYA_MCU_FUNC_SWT2, TUYA_MCU_FUNC_SWT3, TUYA_MCU_FUNC_SWT4, - TUYA_MCU_FUNC_REL1 = 11, TUYA_MCU_FUNC_REL2, TUYA_MCU_FUNC_REL3, TUYA_MCU_FUNC_REL4, TUYA_MCU_FUNC_REL5, +enum TuyaSupportedFunctions { TUYA_MCU_FUNC_NONE, TUYA_MCU_FUNC_SWT1 = 1, TUYA_MCU_FUNC_SWT2, TUYA_MCU_FUNC_SWT3, TUYA_MCU_FUNC_SWT4, + TUYA_MCU_FUNC_REL1 = 11, TUYA_MCU_FUNC_REL2, TUYA_MCU_FUNC_REL3, TUYA_MCU_FUNC_REL4, TUYA_MCU_FUNC_REL5, TUYA_MCU_FUNC_REL6, TUYA_MCU_FUNC_REL7, TUYA_MCU_FUNC_REL8, TUYA_MCU_FUNC_DIMMER = 21, TUYA_MCU_FUNC_POWER = 31, - TUYA_MCU_FUNC_CURRENT, TUYA_MCU_FUNC_VOLTAGE, TUYA_MCU_FUNC_BATTERY_STATE, TUYA_MCU_FUNC_BATTERY_PERCENTAGE, + TUYA_MCU_FUNC_CURRENT, TUYA_MCU_FUNC_VOLTAGE, TUYA_MCU_FUNC_BATTERY_STATE, TUYA_MCU_FUNC_BATTERY_PERCENTAGE, TUYA_MCU_FUNC_REL1_INV = 41, TUYA_MCU_FUNC_REL2_INV, TUYA_MCU_FUNC_REL3_INV, TUYA_MCU_FUNC_REL4_INV, TUYA_MCU_FUNC_REL5_INV, TUYA_MCU_FUNC_REL6_INV, TUYA_MCU_FUNC_REL7_INV, TUYA_MCU_FUNC_REL8_INV, TUYA_MCU_FUNC_LOWPOWER_MODE = 51, TUYA_MCU_FUNC_LAST = 255 }; diff --git a/tasmota/tasmota_version.h b/tasmota/tasmota_version.h index 1b21293b2..f22a0288b 100644 --- a/tasmota/tasmota_version.h +++ b/tasmota/tasmota_version.h @@ -20,7 +20,7 @@ #ifndef _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_ -const uint32_t VERSION = 0x08030102; +const uint32_t VERSION = 0x08030103; // Lowest compatible version const uint32_t VERSION_COMPATIBLE = 0x07010006; diff --git a/tasmota/xdrv_40_telegram.ino b/tasmota/xdrv_40_telegram.ino new file mode 100644 index 000000000..a2ec2e49f --- /dev/null +++ b/tasmota/xdrv_40_telegram.ino @@ -0,0 +1,479 @@ +/* + xdrv_40_telegram.ino - telegram for Tasmota + + Copyright (C) 2020 Theo Arends + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifdef USE_TELEGRAM +/*********************************************************************************************\ + * Telegram bot + * + * Supported commands: + * TGToken - Add your BotFather created bot token (default none) + * TGChatId - Add your BotFather created bot chat id (default none) + * TGPoll - Telegram receive poll time (default 10 seconds) + * TGState 0 - Disable telegram sending (default) + * TGState 1 - Enable telegram sending + * TGState 2 - Disable telegram listener (default) + * TGState 3 - Enable telegram listener + * TGState 4 - Disable telegram response echo (default) + * TGState 5 - Enable telegram response echo + * TGSend - If telegram sending is enabled AND a chat id is present then send data + * + * Tested with defines + * #define USE_TELEGRAM // Support for Telegram protocol + * #define USE_TELEGRAM_FINGERPRINT "\xB2\x72\x47\xA6\x69\x8C\x3C\x69\xF9\x58\x6C\xF3\x60\x02\xFB\x83\xFA\x8B\x1F\x23" // Telegram api.telegram.org TLS public key fingerpring +\*********************************************************************************************/ + +#define XDRV_40 40 + +#define TELEGRAM_SEND_RETRY 4 // Retries +#define TELEGRAM_LOOP_WAIT 10 // Seconds + +#ifdef USE_MQTT_TLS_CA_CERT + static const uint32_t tls_rx_size = 2048; // since Telegram CA is bigger than 1024 bytes, we need to increase rx buffer + static const uint32_t tls_tx_size = 1024; +#else + static const uint32_t tls_rx_size = 1024; + static const uint32_t tls_tx_size = 1024; +#endif + +#include "WiFiClientSecureLightBearSSL.h" +BearSSL::WiFiClientSecure_light *telegramClient = nullptr; + +static const uint8_t Telegram_Fingerprint[] PROGMEM = USE_TELEGRAM_FINGERPRINT; + +struct { + String message[3][6]; // amount of messages read per time (update_id, name_id, name, lastname, chat_id, text) + uint8_t state = 0; + uint8_t index = 0; + uint8_t retry = 0; + uint8_t poll = TELEGRAM_LOOP_WAIT; + uint8_t wait = 0; + bool send_enable = false; + bool recv_enable = false; + bool echo_enable = false; + bool recv_busy = false; +} Telegram; + +bool TelegramInit(void) { + bool init_done = false; + if (strlen(SettingsText(SET_TELEGRAM_TOKEN))) { + if (!telegramClient) { + telegramClient = new BearSSL::WiFiClientSecure_light(tls_rx_size, tls_tx_size); +#ifdef USE_MQTT_TLS_CA_CERT + telegramClient->setTrustAnchor(&GoDaddyCAG2_TA); +#else + telegramClient->setPubKeyFingerprint(Telegram_Fingerprint, Telegram_Fingerprint, false); // check server fingerprint +#endif + + Telegram.message[0][0]="0"; // Number of received messages + Telegram.message[1][0]=""; + Telegram.message[0][1]="0"; // Code of last read Message + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Started")); + } + + init_done = true; + } + return init_done; +} + +/************************************************************************************************** + * function to achieve connection to api.telegram.org and send command to telegram * + * (Argument to pass: URL to address to Telegram) * + **************************************************************************************************/ +String TelegramConnectToTelegram(String command) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Cmnd %s"), command.c_str()); + + if (!TelegramInit()) { return ""; } + + String response = ""; + uint32_t tls_connect_time = millis(); + + if (telegramClient->connect("api.telegram.org", 443)) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Connected in %d ms, max ThunkStack used %d"), + millis() - tls_connect_time, telegramClient->getMaxThunkStackUse()); + + telegramClient->println("GET /"+command); + + String a = ""; + char c; + int ch_count=0; + uint32_t now = millis(); + bool avail = false; + while (millis() -now < 1500) { + while (telegramClient->available()) { + char c = telegramClient->read(); + if (ch_count < 700) { + response = response + c; + ch_count++; + } + avail = true; + } + if (avail) { + break; + } + } + + telegramClient->stop(); + } + + return response; +} + +/*************************************************************** + * GetUpdates - function to receive all messages from telegram * + * (Argument to pass: the last+1 message to read) * + ***************************************************************/ +void TelegramGetUpdates(String offset) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: getUpdates")); + + if (!TelegramInit()) { return; } + + String _token = SettingsText(SET_TELEGRAM_TOKEN); + String command = "bot" + _token + "/getUpdates?offset=" + offset; + String response = TelegramConnectToTelegram(command); //recieve reply from telegram.org + + // {"ok":true,"result":[]} + // or + // {"ok":true,"result":[ + // {"update_id":973125394, + // "message":{"message_id":25, + // "from":{"id":139920293,"is_bot":false,"first_name":"Theo","last_name":"Arends","username":"tjatja","language_code":"nl"}, + // "chat":{"id":139920293,"first_name":"Theo","last_name":"Arends","username":"tjatja","type":"private"}, + // "date":1591877503, + // "text":"M1" + // } + // }, + // {"update_id":973125395, + // "message":{"message_id":26, + // "from":{"id":139920293,"is_bot":false,"first_name":"Theo","last_name":"Arends","username":"tjatja","language_code":"nl"}, + // "chat":{"id":139920293,"first_name":"Theo","last_name":"Arends","username":"tjatja","type":"private"}, + // "date":1591877508, + // "text":"M2" + // } + // } + // ]} + // or + // {"ok":true,"result":[ + // {"update_id":973125396, + // "message":{"message_id":29, + // "from":{"id":139920293,"is_bot":false,"first_name":"Theo","last_name":"Arends","username":"tjatja","language_code":"nl"}, + // "chat":{"id":139920293,"first_name":"Theo","last_name":"Arends","username":"tjatja","type":"private"}, + // "date":1591879753, + // "text":"/power toggle", + // "entities":[{"offset":0,"length":6,"type":"bot_command"}] + // } + // } + // ]} + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Response %s"), response.c_str()); + + // parsing of reply from Telegram into separate received messages + int i = 0; //messages received counter + if (response != "") { + + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Sent Update request messages up to %s"), offset.c_str()); + + String a = ""; + int ch_count = 0; + String c; + for (uint32_t n = 1; n < response.length() +1; n++) { //Search for each message start + ch_count++; + c = response.substring(n -1, n); + a = a + c; + if (ch_count > 8) { + if (a.substring(ch_count -9) == "update_id") { + if (i > 1) { break; } + Telegram.message[i][0] = a.substring(0, ch_count -11); + a = a.substring(ch_count-11); + i++; + ch_count = 11; + } + } + } + if (1 == i) { + Telegram.message[i][0] = a.substring(0, ch_count); //Assign of parsed message into message matrix if only 1 message) + } + if (i > 1) { i = i -1; } + } + //check result of parsing process + if (response == "") { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Failed to update")); + return; + } + if (0 == i) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: No new messages")); + Telegram.message[0][0] = "0"; + } else { + Telegram.message[0][0] = String(i); //returns how many messages are in the array + for (int b = 1; b < i+1; b++) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Msg %d %s"), b, Telegram.message[b][0].c_str()); + } + + TelegramAnalizeMessage(); + } +} + +void TelegramAnalizeMessage(void) { + for (uint32_t i = 1; i < Telegram.message[0][0].toInt() +1; i++) { + Telegram.message[i][5] = ""; + + DynamicJsonBuffer jsonBuffer; + JsonObject &root = jsonBuffer.parseObject(Telegram.message[i][0]); + if (root.success()) { + Telegram.message[i][0] = root["update_id"].as(); + Telegram.message[i][1] = root["message"]["from"]["id"].as(); + Telegram.message[i][2] = root["message"]["from"]["first_name"].as(); + Telegram.message[i][3] = root["message"]["from"]["last_name"].as(); + Telegram.message[i][4] = root["message"]["chat"]["id"].as(); + Telegram.message[i][5] = root["message"]["text"].as(); + } + + int id = Telegram.message[Telegram.message[0][0].toInt()][0].toInt() +1; + Telegram.message[0][1] = id; // Write id of last read message + + for (int j = 0; j < 6; j++) { + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Parsed%d \"%s\""), j, Telegram.message[i][j].c_str()); + } + } +} + +bool TelegramSendMessage(String chat_id, String text) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: sendMessage")); + + if (!TelegramInit()) { return false; } + + bool sent = false; + if (text != "") { + String _token = SettingsText(SET_TELEGRAM_TOKEN); + String command = "bot" + _token + "/sendMessage?chat_id=" + chat_id + "&text=" + text; + String response = TelegramConnectToTelegram(command); + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Response %s"), response.c_str()); + + if (response.startsWith("{\"ok\":true")) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Message sent")); + sent = true; + } + + } + + return sent; +} + +/* +void TelegramSendGetMe(void) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: getMe")); + + if (!TelegramInit()) { return; } + + String _token = SettingsText(SET_TELEGRAM_TOKEN); + String command = "bot" + _token + "/getMe"; + String response = TelegramConnectToTelegram(command); + + // {"ok":true,"result":{"id":1179906608,"is_bot":true,"first_name":"Tasmota","username":"tasmota_bot","can_join_groups":true,"can_read_all_group_messages":false,"supports_inline_queries":false}} + + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Response %s"), response.c_str()); +} +*/ + +String TelegramExecuteCommand(const char *svalue) { + String response = ""; + + uint32_t curridx = web_log_index; + ExecuteCommand(svalue, SRC_CHAT); + if (web_log_index != curridx) { + uint32_t counter = curridx; + response = F("{"); + bool cflg = false; + do { + char* tmp; + size_t len; + GetLog(counter, &tmp, &len); + if (len) { + // [14:49:36 MQTT: stat/wemos5/RESULT = {"POWER":"OFF"}] > [{"POWER":"OFF"}] + char* JSON = (char*)memchr(tmp, '{', len); + if (JSON) { // Is it a JSON message (and not only [15:26:08 MQT: stat/wemos5/POWER = O]) + size_t JSONlen = len - (JSON - tmp); + if (JSONlen > sizeof(mqtt_data)) { JSONlen = sizeof(mqtt_data); } + char stemp[JSONlen]; + strlcpy(stemp, JSON +1, JSONlen -2); + if (cflg) { response += F(","); } + response += stemp; + cflg = true; + } + } + counter++; + counter &= 0xFF; + if (!counter) counter++; // Skip 0 as it is not allowed + } while (counter != web_log_index); + response += F("}"); + } else { + response = F("{\"" D_RSLT_WARNING "\":\"" D_ENABLE_WEBLOG_FOR_RESPONSE "\"}"); + } + + return response; +} + +void TelegramLoop(void) { + if (!global_state.wifi_down && (Telegram.recv_enable || Telegram.echo_enable)) { + switch (Telegram.state) { + case 0: + TelegramInit(); + Telegram.state++; + break; + case 1: + TelegramGetUpdates(Telegram.message[0][1]); // launch API GetUpdates up to xxx message + Telegram.index = 1; + Telegram.retry = TELEGRAM_SEND_RETRY; + Telegram.state++; + break; + case 2: + if (Telegram.echo_enable) { + if (Telegram.retry && (Telegram.index < Telegram.message[0][0].toInt() + 1)) { + if (TelegramSendMessage(Telegram.message[Telegram.index][4], Telegram.message[Telegram.index][5])) { + Telegram.index++; + Telegram.retry = TELEGRAM_SEND_RETRY; + } else { + Telegram.retry--; + } + } else { + Telegram.message[0][0] = ""; // All messages have been replied - reset new messages + Telegram.wait = Telegram.poll; + Telegram.state++; + } + } else { + if (Telegram.message[0][0].toInt() && (Telegram.message[Telegram.index][5].length() > 0)) { + String logging = TelegramExecuteCommand(Telegram.message[Telegram.index][5].c_str()); + if (logging.length() > 0) { + TelegramSendMessage(Telegram.message[Telegram.index][4], logging); + } + } + Telegram.message[0][0] = ""; // All messages have been replied - reset new messages + Telegram.wait = Telegram.poll; + Telegram.state++; + } + break; + case 3: + if (Telegram.wait) { + Telegram.wait--; + } else { + Telegram.state = 1; + } + } + } +} + +/*********************************************************************************************\ + * Commands +\*********************************************************************************************/ + +#define D_CMND_TGSTATE "State" +#define D_CMND_TGPOLL "Poll" +#define D_CMND_TGSEND "Send" +#define D_CMND_TGTOKEN "Token" +#define D_CMND_TGCHATID "ChatId" + +const char kTelegramCommands[] PROGMEM = "TG|" // Prefix + D_CMND_TGSTATE "|" D_CMND_TGPOLL "|" D_CMND_TGTOKEN "|" D_CMND_TGCHATID "|" D_CMND_TGSEND; + +void (* const TelegramCommand[])(void) PROGMEM = { + &CmndTgState, &CmndTgPoll, &CmndTgToken, &CmndTgChatId, &CmndTgSend }; + +void CmndTgState(void) { + if (XdrvMailbox.data_len > 0) { + if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6)) { + switch (XdrvMailbox.payload) { + case 0: // Off + case 1: // On + Telegram.send_enable = XdrvMailbox.payload &1; + break; + case 2: // Off + case 3: // On + Telegram.recv_enable = XdrvMailbox.payload &1; + break; + case 4: // Off + case 5: // On + Telegram.echo_enable = XdrvMailbox.payload &1; + break; + } + } + } + snprintf_P (mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":{\"Send\":\"%s\",\"Receive\":\"%s\",\"Echo\":\"%s\"}}"), + XdrvMailbox.command, GetStateText(Telegram.send_enable), GetStateText(Telegram.recv_enable), GetStateText(Telegram.echo_enable)); +} + +void CmndTgPoll(void) { + if ((XdrvMailbox.payload >= 4) && (XdrvMailbox.payload <= 300)) { + Telegram.poll = XdrvMailbox.payload; + if (Telegram.poll < Telegram.wait) { + Telegram.wait = Telegram.poll; + } + } + ResponseCmndNumber(Telegram.poll); +} + +void CmndTgToken(void) { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_TELEGRAM_TOKEN, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); + } + ResponseCmndChar(SettingsText(SET_TELEGRAM_TOKEN)); +} + +void CmndTgChatId(void) { + if (XdrvMailbox.data_len > 0) { + SettingsUpdateText(SET_TELEGRAM_CHATID, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); + } + ResponseCmndChar(SettingsText(SET_TELEGRAM_CHATID)); +} + +void CmndTgSend(void) { + if (!Telegram.send_enable || !strlen(SettingsText(SET_TELEGRAM_CHATID))) { + ResponseCmndChar(D_JSON_FAILED); + return; + } + if (XdrvMailbox.data_len > 0) { + String message = XdrvMailbox.data; + String chat_id = SettingsText(SET_TELEGRAM_CHATID); + if (!TelegramSendMessage(chat_id, message)) { + ResponseCmndChar(D_JSON_FAILED); + return; + } + } + ResponseCmndDone(); +} + +/*********************************************************************************************\ + * Interface +\*********************************************************************************************/ + +bool Xdrv40(uint8_t function) +{ + bool result = false; + + switch (function) { + case FUNC_EVERY_SECOND: + TelegramLoop(); + break; + case FUNC_COMMAND: + result = DecodeCommand(kTelegramCommands, TelegramCommand); + break; + } + return result; +} +#endif // USE_TELEGRAM diff --git a/tools/decode-status.py b/tools/decode-status.py index 1ce04951e..4228b69df 100755 --- a/tools/decode-status.py +++ b/tools/decode-status.py @@ -205,7 +205,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_MCP9808","USE_BL0940","", + "USE_VEML7700","USE_MCP9808","USE_BL0940","USE_TELEGRAM", "","","","", "","","","", "","","","", @@ -243,7 +243,7 @@ else: obj = json.load(fp) def StartDecode(): - print ("\n*** decode-status.py v20200607 by Theo Arends and Jacek Ziolkowski ***") + print ("\n*** decode-status.py v20200611 by Theo Arends and Jacek Ziolkowski ***") # print("Decoding\n{}".format(obj)) From 18b00f9cbeca16572cf79f6506cfc57114f8fceb Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 11 Jun 2020 17:52:10 +0200 Subject: [PATCH 17/35] Change telegram command prefix to Tm --- tasmota/xdrv_40_telegram.ino | 76 ++++++++++++++++++------------------ 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/tasmota/xdrv_40_telegram.ino b/tasmota/xdrv_40_telegram.ino index a2ec2e49f..ce0d83f10 100644 --- a/tasmota/xdrv_40_telegram.ino +++ b/tasmota/xdrv_40_telegram.ino @@ -22,16 +22,16 @@ * Telegram bot * * Supported commands: - * TGToken - Add your BotFather created bot token (default none) - * TGChatId - Add your BotFather created bot chat id (default none) - * TGPoll - Telegram receive poll time (default 10 seconds) - * TGState 0 - Disable telegram sending (default) - * TGState 1 - Enable telegram sending - * TGState 2 - Disable telegram listener (default) - * TGState 3 - Enable telegram listener - * TGState 4 - Disable telegram response echo (default) - * TGState 5 - Enable telegram response echo - * TGSend - If telegram sending is enabled AND a chat id is present then send data + * TmToken - Add your BotFather created bot token (default none) + * TmChatId - Add your BotFather created bot chat id (default none) + * TmPoll - Telegram receive poll time (default 10 seconds) + * TmState 0 - Disable telegram sending (default) + * TmState 1 - Enable telegram sending + * TmState 2 - Disable telegram listener (default) + * TmState 3 - Enable telegram listener + * TmState 4 - Disable telegram response echo (default) + * TmState 5 - Enable telegram response echo + * TmSend - If telegram sending is enabled AND a chat id is present then send data * * Tested with defines * #define USE_TELEGRAM // Support for Telegram protocol @@ -84,7 +84,7 @@ bool TelegramInit(void) { Telegram.message[1][0]=""; Telegram.message[0][1]="0"; // Code of last read Message - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Started")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Started")); } init_done = true; @@ -97,7 +97,7 @@ bool TelegramInit(void) { * (Argument to pass: URL to address to Telegram) * **************************************************************************************************/ String TelegramConnectToTelegram(String command) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Cmnd %s"), command.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Cmnd %s"), command.c_str()); if (!TelegramInit()) { return ""; } @@ -105,7 +105,7 @@ String TelegramConnectToTelegram(String command) { uint32_t tls_connect_time = millis(); if (telegramClient->connect("api.telegram.org", 443)) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Connected in %d ms, max ThunkStack used %d"), + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Connected in %d ms, max ThunkStack used %d"), millis() - tls_connect_time, telegramClient->getMaxThunkStackUse()); telegramClient->println("GET /"+command); @@ -140,7 +140,7 @@ String TelegramConnectToTelegram(String command) { * (Argument to pass: the last+1 message to read) * ***************************************************************/ void TelegramGetUpdates(String offset) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: getUpdates")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: getUpdates")); if (!TelegramInit()) { return; } @@ -181,13 +181,13 @@ void TelegramGetUpdates(String offset) { // } // ]} - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Response %s"), response.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); // parsing of reply from Telegram into separate received messages int i = 0; //messages received counter if (response != "") { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Sent Update request messages up to %s"), offset.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Sent Update request messages up to %s"), offset.c_str()); String a = ""; int ch_count = 0; @@ -213,16 +213,16 @@ void TelegramGetUpdates(String offset) { } //check result of parsing process if (response == "") { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Failed to update")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Failed to update")); return; } if (0 == i) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: No new messages")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: No new messages")); Telegram.message[0][0] = "0"; } else { Telegram.message[0][0] = String(i); //returns how many messages are in the array for (int b = 1; b < i+1; b++) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Msg %d %s"), b, Telegram.message[b][0].c_str()); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Msg %d %s"), b, Telegram.message[b][0].c_str()); } TelegramAnalizeMessage(); @@ -248,13 +248,13 @@ void TelegramAnalizeMessage(void) { Telegram.message[0][1] = id; // Write id of last read message for (int j = 0; j < 6; j++) { - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Parsed%d \"%s\""), j, Telegram.message[i][j].c_str()); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Parsed%d \"%s\""), j, Telegram.message[i][j].c_str()); } } } bool TelegramSendMessage(String chat_id, String text) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: sendMessage")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: sendMessage")); if (!TelegramInit()) { return false; } @@ -264,10 +264,10 @@ bool TelegramSendMessage(String chat_id, String text) { String command = "bot" + _token + "/sendMessage?chat_id=" + chat_id + "&text=" + text; String response = TelegramConnectToTelegram(command); - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Response %s"), response.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); if (response.startsWith("{\"ok\":true")) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: Message sent")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Message sent")); sent = true; } @@ -278,7 +278,7 @@ bool TelegramSendMessage(String chat_id, String text) { /* void TelegramSendGetMe(void) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TLG: getMe")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: getMe")); if (!TelegramInit()) { return; } @@ -288,7 +288,7 @@ void TelegramSendGetMe(void) { // {"ok":true,"result":{"id":1179906608,"is_bot":true,"first_name":"Tasmota","username":"tasmota_bot","can_join_groups":true,"can_read_all_group_messages":false,"supports_inline_queries":false}} - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TLG: Response %s"), response.c_str()); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); } */ @@ -383,19 +383,19 @@ void TelegramLoop(void) { * Commands \*********************************************************************************************/ -#define D_CMND_TGSTATE "State" -#define D_CMND_TGPOLL "Poll" -#define D_CMND_TGSEND "Send" -#define D_CMND_TGTOKEN "Token" -#define D_CMND_TGCHATID "ChatId" +#define D_CMND_TMSTATE "State" +#define D_CMND_TMPOLL "Poll" +#define D_CMND_TMSEND "Send" +#define D_CMND_TMTOKEN "Token" +#define D_CMND_TMCHATID "ChatId" -const char kTelegramCommands[] PROGMEM = "TG|" // Prefix - D_CMND_TGSTATE "|" D_CMND_TGPOLL "|" D_CMND_TGTOKEN "|" D_CMND_TGCHATID "|" D_CMND_TGSEND; +const char kTelegramCommands[] PROGMEM = "Tm|" // Prefix + D_CMND_TMSTATE "|" D_CMND_TMPOLL "|" D_CMND_TMTOKEN "|" D_CMND_TMCHATID "|" D_CMND_TMSEND; void (* const TelegramCommand[])(void) PROGMEM = { - &CmndTgState, &CmndTgPoll, &CmndTgToken, &CmndTgChatId, &CmndTgSend }; + &CmndTmState, &CmndTmPoll, &CmndTmToken, &CmndTmChatId, &CmndTmSend }; -void CmndTgState(void) { +void CmndTmState(void) { if (XdrvMailbox.data_len > 0) { if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 6)) { switch (XdrvMailbox.payload) { @@ -418,7 +418,7 @@ void CmndTgState(void) { XdrvMailbox.command, GetStateText(Telegram.send_enable), GetStateText(Telegram.recv_enable), GetStateText(Telegram.echo_enable)); } -void CmndTgPoll(void) { +void CmndTmPoll(void) { if ((XdrvMailbox.payload >= 4) && (XdrvMailbox.payload <= 300)) { Telegram.poll = XdrvMailbox.payload; if (Telegram.poll < Telegram.wait) { @@ -428,21 +428,21 @@ void CmndTgPoll(void) { ResponseCmndNumber(Telegram.poll); } -void CmndTgToken(void) { +void CmndTmToken(void) { if (XdrvMailbox.data_len > 0) { SettingsUpdateText(SET_TELEGRAM_TOKEN, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); } ResponseCmndChar(SettingsText(SET_TELEGRAM_TOKEN)); } -void CmndTgChatId(void) { +void CmndTmChatId(void) { if (XdrvMailbox.data_len > 0) { SettingsUpdateText(SET_TELEGRAM_CHATID, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data); } ResponseCmndChar(SettingsText(SET_TELEGRAM_CHATID)); } -void CmndTgSend(void) { +void CmndTmSend(void) { if (!Telegram.send_enable || !strlen(SettingsText(SET_TELEGRAM_CHATID))) { ResponseCmndChar(D_JSON_FAILED); return; From 24cd4276256aeaefe9fd67ca34978bcf81ac2f73 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 11 Jun 2020 18:18:03 +0200 Subject: [PATCH 18/35] Remove debugging info from telegram --- tasmota/xdrv_40_telegram.ino | 39 ++++++++++++++---------------------- 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/tasmota/xdrv_40_telegram.ino b/tasmota/xdrv_40_telegram.ino index ce0d83f10..97c5aa453 100644 --- a/tasmota/xdrv_40_telegram.ino +++ b/tasmota/xdrv_40_telegram.ino @@ -84,7 +84,7 @@ bool TelegramInit(void) { Telegram.message[1][0]=""; Telegram.message[0][1]="0"; // Code of last read Message - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Started")); + AddLog_P2(LOG_LEVEL_INFO, PSTR("TGM: Started")); } init_done = true; @@ -92,12 +92,8 @@ bool TelegramInit(void) { return init_done; } -/************************************************************************************************** - * function to achieve connection to api.telegram.org and send command to telegram * - * (Argument to pass: URL to address to Telegram) * - **************************************************************************************************/ String TelegramConnectToTelegram(String command) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Cmnd %s"), command.c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Cmnd %s"), command.c_str()); if (!TelegramInit()) { return ""; } @@ -105,8 +101,7 @@ String TelegramConnectToTelegram(String command) { uint32_t tls_connect_time = millis(); if (telegramClient->connect("api.telegram.org", 443)) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Connected in %d ms, max ThunkStack used %d"), - millis() - tls_connect_time, telegramClient->getMaxThunkStackUse()); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Connected in %d ms, max ThunkStack used %d"), millis() - tls_connect_time, telegramClient->getMaxThunkStackUse()); telegramClient->println("GET /"+command); @@ -135,12 +130,8 @@ String TelegramConnectToTelegram(String command) { return response; } -/*************************************************************** - * GetUpdates - function to receive all messages from telegram * - * (Argument to pass: the last+1 message to read) * - ***************************************************************/ void TelegramGetUpdates(String offset) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: getUpdates")); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: getUpdates")); if (!TelegramInit()) { return; } @@ -181,13 +172,13 @@ void TelegramGetUpdates(String offset) { // } // ]} - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); // parsing of reply from Telegram into separate received messages int i = 0; //messages received counter if (response != "") { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Sent Update request messages up to %s"), offset.c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Sent Update request messages up to %s"), offset.c_str()); String a = ""; int ch_count = 0; @@ -213,16 +204,16 @@ void TelegramGetUpdates(String offset) { } //check result of parsing process if (response == "") { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Failed to update")); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Failed to update")); return; } if (0 == i) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: No new messages")); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: No new messages")); Telegram.message[0][0] = "0"; } else { Telegram.message[0][0] = String(i); //returns how many messages are in the array for (int b = 1; b < i+1; b++) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Msg %d %s"), b, Telegram.message[b][0].c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Msg %d %s"), b, Telegram.message[b][0].c_str()); } TelegramAnalizeMessage(); @@ -248,13 +239,13 @@ void TelegramAnalizeMessage(void) { Telegram.message[0][1] = id; // Write id of last read message for (int j = 0; j < 6; j++) { - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Parsed%d \"%s\""), j, Telegram.message[i][j].c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Parsed%d \"%s\""), j, Telegram.message[i][j].c_str()); } } } bool TelegramSendMessage(String chat_id, String text) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: sendMessage")); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: sendMessage")); if (!TelegramInit()) { return false; } @@ -264,10 +255,10 @@ bool TelegramSendMessage(String chat_id, String text) { String command = "bot" + _token + "/sendMessage?chat_id=" + chat_id + "&text=" + text; String response = TelegramConnectToTelegram(command); - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); if (response.startsWith("{\"ok\":true")) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Message sent")); +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: Message sent")); sent = true; } @@ -278,7 +269,7 @@ bool TelegramSendMessage(String chat_id, String text) { /* void TelegramSendGetMe(void) { - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("TGM: getMe")); + AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: getMe")); if (!TelegramInit()) { return; } @@ -288,7 +279,7 @@ void TelegramSendGetMe(void) { // {"ok":true,"result":{"id":1179906608,"is_bot":true,"first_name":"Tasmota","username":"tasmota_bot","can_join_groups":true,"can_read_all_group_messages":false,"supports_inline_queries":false}} - AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); +// AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("TGM: Response %s"), response.c_str()); } */ From 11d64865347d268a5933c7b645d86f8fa2091e27 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:13:08 +0200 Subject: [PATCH 19/35] Fix time 4 display with SO52 1 --- tasmota/support.ino | 2 +- tasmota/support_rtc.ino | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index 8d915cf05..b27cb072d 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -997,7 +997,7 @@ char* ResponseGetTime(uint32_t format, char* time_str) snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":%u"), UtcTime()); break; case 3: - snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s.%03d\""), GetDateAndTime(DT_LOCAL).c_str(), RtcMillis()); + snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL_MILLIS).c_str()); break; default: snprintf_P(time_str, TIMESZ, PSTR("{\"" D_JSON_TIME "\":\"%s\""), GetDateAndTime(DT_LOCAL).c_str()); diff --git a/tasmota/support_rtc.ino b/tasmota/support_rtc.ino index e918ae99d..445976eb3 100644 --- a/tasmota/support_rtc.ino +++ b/tasmota/support_rtc.ino @@ -206,6 +206,14 @@ String GetDateAndTime(uint8_t time_type) break; } String dt = GetDT(time); // 2017-03-07T11:08:02 + + if (DT_LOCAL_MILLIS == time_type) { + char ms[10]; + snprintf_P(ms, sizeof(ms), PSTR(".%03d"), RtcMillis()); + dt += ms; + time_type = DT_LOCAL; + } + if (Settings.flag3.time_append_timezone && (DT_LOCAL == time_type)) { // SetOption52 - Append timezone to JSON time dt += GetTimeZone(); // 2017-03-07T11:08:02-07:00 } From 536730273596bb1563c2b6cc8a23c82c4571d91f Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Thu, 11 Jun 2020 19:14:39 +0200 Subject: [PATCH 20/35] Fix compile error --- tasmota/tasmota.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/tasmota.h b/tasmota/tasmota.h index 49347f1f8..3d3811c89 100644 --- a/tasmota/tasmota.h +++ b/tasmota/tasmota.h @@ -214,7 +214,7 @@ enum WeekInMonthOptions {Last, First, Second, Third, Fourth}; enum DayOfTheWeekOptions {Sun=1, Mon, Tue, Wed, Thu, Fri, Sat}; enum MonthNamesOptions {Jan=1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec}; enum HemisphereOptions {North, South}; -enum GetDateAndTimeOptions { DT_LOCAL, DT_UTC, DT_LOCALNOTZ, DT_DST, DT_STD, DT_RESTART, DT_ENERGY, DT_BOOTCOUNT }; +enum GetDateAndTimeOptions { DT_LOCAL, DT_UTC, DT_LOCALNOTZ, DT_DST, DT_STD, DT_RESTART, DT_ENERGY, DT_BOOTCOUNT, DT_LOCAL_MILLIS }; enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE}; From 5427fc937e686f262ee96f2a3d3479cdb6006caf Mon Sep 17 00:00:00 2001 From: Stephan Hadinger Date: Thu, 11 Jun 2020 21:14:30 +0200 Subject: [PATCH 21/35] Fix Dimmer tele inconsistency when SO37 128 --- tasmota/xdrv_04_light.ino | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino index 1d6ac6a58..a8eaf9629 100644 --- a/tasmota/xdrv_04_light.ino +++ b/tasmota/xdrv_04_light.ino @@ -1555,10 +1555,10 @@ void LightState(uint8_t append) if (!Light.pwm_multi_channels) { if (unlinked) { // RGB and W are unlinked, we display the second Power/Dimmer - ResponseAppend_P(PSTR("\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d" - ",\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "%d\":%d"), - Light.device, GetStateText(Light.power & 1), Light.device, light_state.getDimmer(1), - Light.device + 1, GetStateText(Light.power & 2 ? 1 : 0), Light.device + 1, light_state.getDimmer(2)); + ResponseAppend_P(PSTR("\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "1\":%d" + ",\"" D_RSLT_POWER "%d\":\"%s\",\"" D_CMND_DIMMER "2\":%d"), + Light.device, GetStateText(Light.power & 1), light_state.getDimmer(1), + Light.device + 1, GetStateText(Light.power & 2 ? 1 : 0), light_state.getDimmer(2)); } else { GetPowerDevice(scommand, Light.device, sizeof(scommand), Settings.flag.device_index_enable); // SetOption26 - Switch between POWER or POWER1 ResponseAppend_P(PSTR("\"%s\":\"%s\",\"" D_CMND_DIMMER "\":%d"), scommand, GetStateText(Light.power & 1), From 03cd543127317f3984fea0cdc3149421128824ca Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:38:52 +0200 Subject: [PATCH 22/35] Add support for HP303B Add support for HP303B Temperature and Pressure sensor by Robert Jaakke (#8638) --- RELEASENOTES.md | 1 + tasmota/CHANGELOG.md | 1 + tasmota/my_user_config.h | 1 + tasmota/tasmota_configurations.h | 1 + tasmota/xsns_73_hp303b.ino | 114 ++++++++++++++++--------------- 5 files changed, 62 insertions(+), 56 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index ab01ddaa4..c1637501c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -77,3 +77,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for up to eight MCP9808 temperature sensors by device111 (#8594) - Add support for BL0940 energy monitor as used in Blitzwolf BW-SHP10 (#8175) - Add initial support for Telegram bot (#8619) +- Add support for HP303B Temperature and Pressure sensor by Robert Jaakke (#8638) diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 9033a2bba..0c6ef82bd 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -3,6 +3,7 @@ ### 8.3.1.3 20200611 - Add initial support for Telegram bot (#8619) +- Add support for HP303B Temperature and Pressure sensor by Robert Jaakke (#8638) ### 8.3.1.2 20200522 diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index ea2e6eefe..d6a140663 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -527,6 +527,7 @@ // #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_HP303B // [I2cDriver52] Enable HP303B temperature and pressure sensor (I2C address 0x76 or 0x77) (+6k2 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 diff --git a/tasmota/tasmota_configurations.h b/tasmota/tasmota_configurations.h index 811076d8f..ff81e486d 100644 --- a/tasmota/tasmota_configurations.h +++ b/tasmota/tasmota_configurations.h @@ -155,6 +155,7 @@ //#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_HP303B // Add support for HP303B temperature and pressure sensor (I2C address 0x76 or 0x77) (+6k2 code) #define USE_ENERGY_SENSOR // Add energy sensors (-14k code) #define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code) diff --git a/tasmota/xsns_73_hp303b.ino b/tasmota/xsns_73_hp303b.ino index 1be0a1606..0d9363409 100644 --- a/tasmota/xsns_73_hp303b.ino +++ b/tasmota/xsns_73_hp303b.ino @@ -16,58 +16,57 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ + #ifdef USE_I2C #ifdef USE_HP303B /*********************************************************************************************\ - * HP303B - Gas (TVOC - Total Volatile Organic Compounds) and Air Quality (CO2) + * HP303B - Pressure and temperature sensor * * Source: Lolin LOLIN_HP303B_Library * * I2C Address: 0x77 or 0x76 \*********************************************************************************************/ -#define XSNS_73 73 -#define XI2C_52 52 // See I2CDEVICES.md +#define XSNS_73 73 +#define XI2C_52 52 // See I2CDEVICES.md + +#define HP303B_MAX_SENSORS 2 +#define HP303B_START_ADDRESS 0x76 #include // HP303B Object LOLIN_HP303B HP303BSensor = LOLIN_HP303B(); -#define HP303B_MAX_SENSORS 2 -#define HP303B_START_ADDRESS 0x76 - struct { -char types[7] = "HP303B"; -uint8_t count = 0; -int16_t oversampling = 7; + int16_t oversampling = 7; + char types[7] = "HP303B"; + uint8_t count = 0; } hp303b_cfg; struct BHP303B { - uint8_t address; - uint8_t valid = 0; float temperature = NAN; float pressure = NAN; + uint8_t address; + uint8_t valid = 0; } hp303b_sensor[HP303B_MAX_SENSORS]; + /*********************************************************************************************/ -bool HP303B_Read(uint8_t hp303b_idx) -{ +bool HP303B_Read(uint32_t hp303b_idx) { if (hp303b_sensor[hp303b_idx].valid) { hp303b_sensor[hp303b_idx].valid--; } float t; + if (HP303BSensor.measureTempOnce(t, hp303b_sensor[hp303b_idx].address, hp303b_cfg.oversampling) != 0) { + return false; + } + float p; - int16_t ret; - - ret = HP303BSensor.measureTempOnce(t, hp303b_sensor[hp303b_idx].address, hp303b_cfg.oversampling); - if (ret != 0) - return false; - - ret = HP303BSensor.measurePressureOnce(p, hp303b_sensor[hp303b_idx].address, hp303b_cfg.oversampling); - if (ret != 0) + if (HP303BSensor.measurePressureOnce(p, hp303b_sensor[hp303b_idx].address, hp303b_cfg.oversampling) != 0) { return false; + } hp303b_sensor[hp303b_idx].temperature = (float)ConvertTemp(t); - hp303b_sensor[hp303b_idx].pressure = (float)ConvertPressure(p) / 100; //conversion to hPa + hp303b_sensor[hp303b_idx].pressure = (float)ConvertPressure(p / 100); // Conversion to hPa hp303b_sensor[hp303b_idx].valid = SENSOR_MAX_MISS; return true; @@ -75,14 +74,11 @@ bool HP303B_Read(uint8_t hp303b_idx) /********************************************************************************************/ -void HP303B_Detect(void) -{ - for (uint32_t i = 0; i < HP303B_MAX_SENSORS; i++) - { - if (!I2cSetDevice(HP303B_START_ADDRESS + i )) { continue; } +void HP303B_Detect(void) { + for (uint32_t i = 0; i < HP303B_MAX_SENSORS; i++) { + if (!I2cSetDevice(HP303B_START_ADDRESS + i)) { continue; } - if (HP303BSensor.begin(HP303B_START_ADDRESS + i)) - { + if (HP303BSensor.begin(HP303B_START_ADDRESS + i)) { hp303b_sensor[hp303b_cfg.count].address = HP303B_START_ADDRESS + i; I2cSetActiveFound(hp303b_sensor[hp303b_cfg.count].address, hp303b_cfg.types); hp303b_cfg.count++; @@ -90,50 +86,58 @@ void HP303B_Detect(void) } } -void HP303B_EverySecond(void) -{ +void HP303B_EverySecond(void) { for (uint32_t i = 0; i < hp303b_cfg.count; i++) { if (uptime &1) { - if (!HP303B_Read(i)) { - AddLogMissed(hp303b_cfg.types, hp303b_sensor[i].valid); + if (!HP303B_Read(i)) { + AddLogMissed(hp303b_cfg.types, hp303b_sensor[i].valid); + } } } - } } -void HP303B_Show(bool json) -{ +void HP303B_Show(bool json) { for (uint32_t i = 0; i < hp303b_cfg.count; i++) { - char sensor_name[12]; - strlcpy(sensor_name, hp303b_cfg.types, sizeof(sensor_name)); - if (hp303b_cfg.count > 1) { - snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c0x%02X"), sensor_name, IndexSeparator(), hp303b_sensor[i].address); // HP303B-0x76, HP303B-0x77 - } + if (hp303b_sensor[i].valid) { + char sensor_name[12]; + strlcpy(sensor_name, hp303b_cfg.types, sizeof(sensor_name)); + if (hp303b_cfg.count > 1) { + snprintf_P(sensor_name, sizeof(sensor_name), PSTR("%s%c%02X"), sensor_name, IndexSeparator(), hp303b_sensor[i].address); // HP303B-76, HP303B-77 + } + + float sealevel = 0.0; + if (hp303b_sensor[i].pressure != 0.0) { + sealevel = (hp303b_sensor[i].pressure / FastPrecisePow(1.0 - ((float)Settings.altitude / 44330.0), 5.255)) - 21.6; + sealevel = ConvertPressure(sealevel); + } - if (hp303b_sensor[i].valid) - { char str_temperature[33]; dtostrfd(hp303b_sensor[i].temperature, Settings.flag2.temperature_resolution, str_temperature); char str_pressure[33]; dtostrfd(hp303b_sensor[i].pressure, Settings.flag2.pressure_resolution, str_pressure); + char sea_pressure[33]; + dtostrfd(sealevel, Settings.flag2.pressure_resolution, sea_pressure); - if (json) - { + if (json) { ResponseAppend_P(PSTR(",\"%s\":{\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_PRESSURE "\":%s"), sensor_name, str_temperature, str_pressure); + if (Settings.altitude != 0) { + ResponseAppend_P(PSTR(",\"" D_JSON_PRESSUREATSEALEVEL "\":%s"), sea_pressure); + } ResponseJsonEnd(); - #ifdef USE_DOMOTICZ +#ifdef USE_DOMOTICZ // Domoticz and knx only support one temp sensor if ((0 == tele_period) && (0 == i)) { DomoticzSensor(DZ_TEMP, hp303b_sensor[i].temperature); } - #endif // USE_DOMOTICZ - #ifdef USE_WEBSERVER - } - else - { +#endif // USE_DOMOTICZ +#ifdef USE_WEBSERVER + } else { WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, str_temperature, TempUnit()); WSContentSend_PD(HTTP_SNS_PRESSURE, sensor_name, str_pressure, PressureUnit().c_str()); - #endif // USE_WEBSERVER + if (Settings.altitude != 0) { + WSContentSend_PD(HTTP_SNS_SEAPRESSURE, sensor_name, sea_pressure, PressureUnit().c_str()); + } +#endif // USE_WEBSERVER } } } @@ -152,10 +156,8 @@ bool Xsns73(uint8_t function) if (FUNC_INIT == function) { HP303B_Detect(); } - else if (hp303b_cfg.count) - { - switch (function) - { + else if (hp303b_cfg.count) { + switch (function) { case FUNC_EVERY_SECOND: HP303B_EverySecond(); break; From a97f391f91491dda1751311a41d3dd0c78d2c3e4 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 12 Jun 2020 12:40:42 +0200 Subject: [PATCH 23/35] Update I2CDEVICES.md --- I2CDEVICES.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 4f6718b90..b67cc9029 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -72,4 +72,5 @@ Index | Define | Driver | Device | Address(es) | Description 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 - 51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor \ No newline at end of file + 51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor + 52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor \ No newline at end of file From d64c0a8179500c6a668d4018661e19138496c2b9 Mon Sep 17 00:00:00 2001 From: bovirus <1262554+bovirus@users.noreply.github.com> Date: Fri, 12 Jun 2020 16:33:19 +0200 Subject: [PATCH 24/35] Update Italian language --- tasmota/language/it_IT.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tasmota/language/it_IT.h b/tasmota/language/it_IT.h index f1af3765f..22181a7be 100644 --- a/tasmota/language/it_IT.h +++ b/tasmota/language/it_IT.h @@ -672,7 +672,7 @@ #define D_SENSOR_HM10_TX "HM10 - TX" #define D_SENSOR_LE01MR_RX "LE-01MR - RX" #define D_SENSOR_LE01MR_TX "LE-01MR - TX" -#define D_SENSOR_BL0940_RX "BL0940 Rx" +#define D_SENSOR_BL0940_RX "BL0940 - Rx" #define D_SENSOR_CC1101_GDO0 "CC1101 - GDO0" #define D_SENSOR_CC1101_GDO2 "CC1101 - GDO2" #define D_SENSOR_HRXL_RX "HRXL - RX" From 0abfcf1954fa6d60fab5a63d04cfde8d4477a32f Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 12 Jun 2020 16:51:21 +0200 Subject: [PATCH 25/35] Fix global temperature - Fix global temperature use of float solving intermittend power off (#8175) - Fix BL0940 power monitoring when powered on but no load present --- tasmota/support.ino | 6 +++--- tasmota/support_command.ino | 2 +- tasmota/tasmota.ino | 6 +++--- tasmota/xdrv_03_energy.ino | 7 ++++++- tasmota/xnrg_14_bl0940.ino | 15 +++++++++------ tasmota/xsns_21_sgp30.ino | 8 ++++---- tasmota/xsns_31_ccs811.ino | 4 +++- tasmota/xsns_91_prometheus.ino | 2 +- 8 files changed, 30 insertions(+), 20 deletions(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index b27cb072d..879a91fb0 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -687,9 +687,9 @@ void ResetGlobalValues(void) { if ((uptime - global_update) > GLOBAL_VALUES_VALID) { // Reset after 5 minutes global_update = 0; - global_temperature = 9999; - global_humidity = 0; - global_pressure = 0; + global_temperature = NAN; + global_humidity = 0.0f; + global_pressure = 0.0f; } } diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index abda76482..fe855ee92 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -626,7 +626,7 @@ void CmndGlobalTemp(void) if (!isnan(temperature) && Settings.flag.temperature_conversion) { // SetOption8 - Switch between Celsius or Fahrenheit temperature = (temperature - 32) / 1.8; // Celsius } - if ((temperature >= -50.0) && (temperature <= 100.0)) { + if ((temperature >= -50.0f) && (temperature <= 100.0f)) { ConvertTemp(temperature); global_update = 1; // Keep global values just entered valid } diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 78c50edc3..7ee0c8316 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -111,9 +111,9 @@ uint32_t uptime = 0; // Counting every second until 42949 uint32_t loop_load_avg = 0; // Indicative loop load average uint32_t global_update = 0; // Timestamp of last global temperature and humidity update uint32_t web_log_index = 1; // Index in Web log buffer (should never be 0) -float global_temperature = 9999; // Provide a global temperature to be used by some sensors -float global_humidity = 0; // Provide a global humidity to be used by some sensors -float global_pressure = 0; // Provide a global pressure to be used by some sensors +float global_temperature = NAN; // Provide a global temperature to be used by some sensors +float global_humidity = 0.0f; // Provide a global humidity to be used by some sensors +float global_pressure = 0.0f; // Provide a global pressure to be used by some sensors uint16_t tele_period = 9999; // Tele period timer uint16_t blink_counter = 0; // Number of blink cycles uint16_t seriallog_timer = 0; // Timer to disable Seriallog diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino index f5e4a729d..bf9515b6a 100644 --- a/tasmota/xdrv_03_energy.ino +++ b/tasmota/xdrv_03_energy.ino @@ -459,7 +459,12 @@ void EnergyEverySecond(void) { // Overtemp check if (global_update) { - if (power && (global_temperature != 9999) && (global_temperature > Settings.param[P_OVER_TEMP])) { // Device overtemp, turn off relays + if (power && !isnan(global_temperature) && (global_temperature > (float)Settings.param[P_OVER_TEMP])) { // Device overtemp, turn off relays + + char temperature[33]; + dtostrfd(global_temperature, 1, temperature); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("NRG: GlobTemp %s"), temperature); + SetAllPower(POWER_ALL_OFF, SRC_OVERTEMP); } } diff --git a/tasmota/xnrg_14_bl0940.ino b/tasmota/xnrg_14_bl0940.ino index 655f3d8df..bfdbac232 100644 --- a/tasmota/xnrg_14_bl0940.ino +++ b/tasmota/xnrg_14_bl0940.ino @@ -83,17 +83,20 @@ void Bl0940Received(void) { return; } - Bl0940.voltage = Bl0940.rx_buffer[12] << 16 | Bl0940.rx_buffer[11] << 8 | Bl0940.rx_buffer[10]; - Bl0940.current = Bl0940.rx_buffer[6] << 16 | Bl0940.rx_buffer[5] << 8 | Bl0940.rx_buffer[4]; - Bl0940.power = Bl0940.rx_buffer[18] << 16 | Bl0940.rx_buffer[17] << 8 | Bl0940.rx_buffer[16]; -// Bl0940.cf_pulses = Bl0940.rx_buffer[24] << 16 | Bl0940.rx_buffer[23] << 8 | Bl0940.rx_buffer[22]; - uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; + Bl0940.voltage = Bl0940.rx_buffer[12] << 16 | Bl0940.rx_buffer[11] << 8 | Bl0940.rx_buffer[10]; // V_RMS unsigned + Bl0940.current = Bl0940.rx_buffer[6] << 16 | Bl0940.rx_buffer[5] << 8 | Bl0940.rx_buffer[4]; // I_RMS unsigned + int32_t power = Bl0940.rx_buffer[18] << 24 | Bl0940.rx_buffer[17] << 16 | Bl0940.rx_buffer[16] << 8; // WATT signed + Bl0940.power = abs(power) >> 8; // WATT unsigned +// Bl0940.cf_pulses = Bl0940.rx_buffer[24] << 16 | Bl0940.rx_buffer[23] << 8 | Bl0940.rx_buffer[22]; // CF_CNT unsigned + uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; // TPS1 unsigned float t = ((170.0f/448.0f)*(((float)tps1/2.0f)-32.0f))-45.0f; Bl0940.temperature = ConvertTemp(t); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: U %d, I %d, P %d, T %d"), Bl0940.voltage, Bl0940.current, Bl0940.power, tps1); + if (Energy.power_on) { // Powered on Energy.voltage[0] = (float)Bl0940.voltage / Settings.energy_voltage_calibration; - if (power != 0) { + if (power && (Bl0940.power > Settings.energy_power_calibration)) { // We need at least 1W Energy.active_power[0] = (float)Bl0940.power / Settings.energy_power_calibration; Energy.current[0] = (float)Bl0940.current / (Settings.energy_current_calibration * 100); } else { diff --git a/tasmota/xsns_21_sgp30.ino b/tasmota/xsns_21_sgp30.ino index ab417a8c3..ec1e85258 100644 --- a/tasmota/xsns_21_sgp30.ino +++ b/tasmota/xsns_21_sgp30.ino @@ -87,7 +87,7 @@ void Sgp30Update(void) // Perform every second to ensure proper operation of th if (!sgp.IAQmeasure()) { return; // Measurement failed } - if (global_update && (global_humidity > 0) && (global_temperature != 9999)) { + if (global_update && (global_humidity > 0) && !isnan(global_temperature)) { // abs hum in mg/m3 sgp30_abshum=sgp30_AbsoluteHumidity(global_temperature,global_humidity,TempUnit()); sgp.setHumidity(sgp30_abshum*1000); @@ -118,14 +118,14 @@ void Sgp30Show(bool json) { if (sgp30_ready) { char abs_hum[33]; - - if (global_update && global_humidity>0 && global_temperature!=9999) { + + if (global_update && (global_humidity > 0) && !isnan(global_temperature)) { // has humidity + temperature dtostrfd(sgp30_abshum,4,abs_hum); } if (json) { ResponseAppend_P(PSTR(",\"SGP30\":{\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TVOC "\":%d"), sgp.eCO2, sgp.TVOC); - if (global_update && global_humidity>0 && global_temperature!=9999) { + if (global_update && global_humidity>0 && !isnan(global_temperature)) { ResponseAppend_P(PSTR(",\"" D_JSON_AHUM "\":%s"),abs_hum); } ResponseJsonEnd(); diff --git a/tasmota/xsns_31_ccs811.ino b/tasmota/xsns_31_ccs811.ino index a968319cb..aada03d04 100644 --- a/tasmota/xsns_31_ccs811.ino +++ b/tasmota/xsns_31_ccs811.ino @@ -65,7 +65,9 @@ void CCS811Update(void) // Perform every n second TVOC = ccs.getTVOC(); eCO2 = ccs.geteCO2(); CCS811_ready = 1; - if (global_update && global_humidity>0 && global_temperature!=9999) { ccs.setEnvironmentalData((uint8_t)global_humidity, global_temperature); } + if (global_update && (global_humidity > 0) && !isnan(global_temperature)) { + ccs.setEnvironmentalData((uint8_t)global_humidity, global_temperature); + } ecnt = 0; } } else { diff --git a/tasmota/xsns_91_prometheus.ino b/tasmota/xsns_91_prometheus.ino index f5b0eba9d..2a361b550 100644 --- a/tasmota/xsns_91_prometheus.ino +++ b/tasmota/xsns_91_prometheus.ino @@ -37,7 +37,7 @@ void HandleMetrics(void) char parameter[FLOATSZ]; - if (global_temperature != 9999) { + if (!isnan(global_temperature)) { dtostrfd(global_temperature, Settings.flag2.temperature_resolution, parameter); WSContentSend_P(PSTR("# TYPE global_temperature gauge\nglobal_temperature %s\n"), parameter); } From 2b327c96c99448850690ff16fe5126506ee89361 Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Fri, 12 Jun 2020 17:42:04 +0200 Subject: [PATCH 26/35] Fix BL0940 invalid overtemp Fix BL0940 invalid overtemp (#8175) --- tasmota/xnrg_14_bl0940.ino | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/tasmota/xnrg_14_bl0940.ino b/tasmota/xnrg_14_bl0940.ino index bfdbac232..61dd8b808 100644 --- a/tasmota/xnrg_14_bl0940.ino +++ b/tasmota/xnrg_14_bl0940.ino @@ -60,6 +60,7 @@ struct BL0940 { float temperature; int byte_counter = 0; + uint16_t tps1 = 0; uint8_t *rx_buffer = nullptr; bool received = false; } Bl0940; @@ -78,21 +79,25 @@ void Bl0940Received(void) { // 55 B9 33 00 DE 45 00 94 02 00 CF E4 70 63 02 00 6C 4C 00 13 01 00 09 00 00 00 00 00 E4 01 00 FE 03 00 72 // Hd IFRms--- Current- Reserved Voltage- Reserved Power--- Reserved CF------ Reserved TPS1---- TPS2---- Ck - if (Bl0940.rx_buffer[0] != BL0940_PACKET_HEADER) { + uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; // TPS1 unsigned + if ((Bl0940.rx_buffer[0] != BL0940_PACKET_HEADER) || // Bad header + (Bl0940.tps1 && ((tps1 < (Bl0940.tps1 -10)) || (tps1 > (Bl0940.tps1 +10)))) // Invalid temperature change + ) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: Invalid data")); return; } + Bl0940.tps1 = tps1; + float t = ((170.0f/448.0f)*(((float)Bl0940.tps1/2.0f)-32.0f))-45.0f; + Bl0940.temperature = ConvertTemp(t); + Bl0940.voltage = Bl0940.rx_buffer[12] << 16 | Bl0940.rx_buffer[11] << 8 | Bl0940.rx_buffer[10]; // V_RMS unsigned Bl0940.current = Bl0940.rx_buffer[6] << 16 | Bl0940.rx_buffer[5] << 8 | Bl0940.rx_buffer[4]; // I_RMS unsigned int32_t power = Bl0940.rx_buffer[18] << 24 | Bl0940.rx_buffer[17] << 16 | Bl0940.rx_buffer[16] << 8; // WATT signed Bl0940.power = abs(power) >> 8; // WATT unsigned // Bl0940.cf_pulses = Bl0940.rx_buffer[24] << 16 | Bl0940.rx_buffer[23] << 8 | Bl0940.rx_buffer[22]; // CF_CNT unsigned - uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; // TPS1 unsigned - float t = ((170.0f/448.0f)*(((float)tps1/2.0f)-32.0f))-45.0f; - Bl0940.temperature = ConvertTemp(t); - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: U %d, I %d, P %d, T %d"), Bl0940.voltage, Bl0940.current, Bl0940.power, tps1); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: U %d, I %d, P %d, T %d"), Bl0940.voltage, Bl0940.current, Bl0940.power, Bl0940.tps1); if (Energy.power_on) { // Powered on Energy.voltage[0] = (float)Bl0940.voltage / Settings.energy_voltage_calibration; From 57ffd2715364015fd00c979206017bbe8b75d55a Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 13 Jun 2020 12:26:55 +0200 Subject: [PATCH 27/35] Alternative method of calculating energy usage Alternative method of calculating energy usage (#8175) --- tasmota/xnrg_14_bl0940.ino | 58 ++++++++++++++++++++++++++++++-------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/tasmota/xnrg_14_bl0940.ino b/tasmota/xnrg_14_bl0940.ino index 61dd8b808..d9bc51db8 100644 --- a/tasmota/xnrg_14_bl0940.ino +++ b/tasmota/xnrg_14_bl0940.ino @@ -33,6 +33,8 @@ #define BL0940_UREF 33000 #define BL0940_IREF 2750 +#define BL0940_PULSES_NOT_INITIALIZED -1 + #define BL0940_BUFFER_SIZE 36 #define BL0940_WRITE_COMMAND 0xA0 // 0xA8 according to documentation @@ -55,8 +57,11 @@ struct BL0940 { long voltage = 0; long current = 0; long power = 0; -// long power_cycle_first = 0; -// long cf_pulses = 0; + + long power_cycle_first = 0; + long cf_pulses = 0; + long cf_pulses_last_time = BL0940_PULSES_NOT_INITIALIZED; + float temperature; int byte_counter = 0; @@ -79,9 +84,9 @@ void Bl0940Received(void) { // 55 B9 33 00 DE 45 00 94 02 00 CF E4 70 63 02 00 6C 4C 00 13 01 00 09 00 00 00 00 00 E4 01 00 FE 03 00 72 // Hd IFRms--- Current- Reserved Voltage- Reserved Power--- Reserved CF------ Reserved TPS1---- TPS2---- Ck - uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; // TPS1 unsigned - if ((Bl0940.rx_buffer[0] != BL0940_PACKET_HEADER) || // Bad header - (Bl0940.tps1 && ((tps1 < (Bl0940.tps1 -10)) || (tps1 > (Bl0940.tps1 +10)))) // Invalid temperature change + uint16_t tps1 = Bl0940.rx_buffer[29] << 8 | Bl0940.rx_buffer[28]; // TPS1 unsigned + if ((Bl0940.rx_buffer[0] != BL0940_PACKET_HEADER) || // Bad header + (Bl0940.tps1 && ((tps1 < (Bl0940.tps1 -10)) || (tps1 > (Bl0940.tps1 +10)))) // Invalid temperature change ) { AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: Invalid data")); return; @@ -91,17 +96,19 @@ void Bl0940Received(void) { float t = ((170.0f/448.0f)*(((float)Bl0940.tps1/2.0f)-32.0f))-45.0f; Bl0940.temperature = ConvertTemp(t); - Bl0940.voltage = Bl0940.rx_buffer[12] << 16 | Bl0940.rx_buffer[11] << 8 | Bl0940.rx_buffer[10]; // V_RMS unsigned - Bl0940.current = Bl0940.rx_buffer[6] << 16 | Bl0940.rx_buffer[5] << 8 | Bl0940.rx_buffer[4]; // I_RMS unsigned - int32_t power = Bl0940.rx_buffer[18] << 24 | Bl0940.rx_buffer[17] << 16 | Bl0940.rx_buffer[16] << 8; // WATT signed - Bl0940.power = abs(power) >> 8; // WATT unsigned -// Bl0940.cf_pulses = Bl0940.rx_buffer[24] << 16 | Bl0940.rx_buffer[23] << 8 | Bl0940.rx_buffer[22]; // CF_CNT unsigned + Bl0940.voltage = Bl0940.rx_buffer[12] << 16 | Bl0940.rx_buffer[11] << 8 | Bl0940.rx_buffer[10]; // V_RMS unsigned + Bl0940.current = Bl0940.rx_buffer[6] << 16 | Bl0940.rx_buffer[5] << 8 | Bl0940.rx_buffer[4]; // I_RMS unsigned + int32_t power = Bl0940.rx_buffer[18] << 24 | Bl0940.rx_buffer[17] << 16 | Bl0940.rx_buffer[16] << 8; // WATT signed + Bl0940.power = abs(power) >> 8; // WATT unsigned + int32_t cf_cnt = Bl0940.rx_buffer[24] << 24 | Bl0940.rx_buffer[23] << 16 | Bl0940.rx_buffer[22] << 8; // CF_CNT signed + Bl0940.cf_pulses = abs(cf_cnt) >> 8; - AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: U %d, I %d, P %d, T %d"), Bl0940.voltage, Bl0940.current, Bl0940.power, Bl0940.tps1); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: U %d, I %d, P %d, C %d, T %d"), + Bl0940.voltage, Bl0940.current, Bl0940.power, Bl0940.cf_pulses, Bl0940.tps1); if (Energy.power_on) { // Powered on Energy.voltage[0] = (float)Bl0940.voltage / Settings.energy_voltage_calibration; - if (power && (Bl0940.power > Settings.energy_power_calibration)) { // We need at least 1W + if (power && (Bl0940.power > Settings.energy_power_calibration)) { // We need at least 1W Energy.active_power[0] = (float)Bl0940.power / Settings.energy_power_calibration; Energy.current[0] = (float)Bl0940.current / (Settings.energy_current_calibration * 100); } else { @@ -163,10 +170,37 @@ void Bl0940EverySecond(void) { Bl0940.current = 0; Bl0940.power = 0; } else { +/* + // Calculate energy by using active power if (Energy.active_power[0]) { Energy.kWhtoday_delta += (Energy.active_power[0] * 1000) / 36; EnergyUpdateToday(); } +*/ + // Calculate energy by using active energy pulse count + if (BL0940_PULSES_NOT_INITIALIZED == Bl0940.cf_pulses_last_time) { + Bl0940.cf_pulses_last_time = Bl0940.cf_pulses; // Init after restart + } else { + uint32_t cf_pulses = 0; + if (Bl0940.cf_pulses < Bl0940.cf_pulses_last_time) { // Rolled over after 0xFFFFFF (16777215) pulses + cf_pulses = (0x1000000 - Bl0940.cf_pulses_last_time) + Bl0940.cf_pulses; + } else { + cf_pulses = Bl0940.cf_pulses - Bl0940.cf_pulses_last_time; + } + if (cf_pulses && Energy.active_power[0]) { + if (cf_pulses < 16) { // max load for SHP10: 4.00kW (3.68kW) + uint32_t watt256 = (1638400 * 256) / Settings.energy_power_calibration; + uint32_t delta = (cf_pulses * watt256) / 36; + Bl0940.cf_pulses_last_time = Bl0940.cf_pulses; + Energy.kWhtoday_delta += delta; + } else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: Overload")); + Bl0940.cf_pulses_last_time = BL0940_PULSES_NOT_INITIALIZED; + } + EnergyUpdateToday(); + } + } + } Bl0940Serial->flush(); From fcc0a29909779ab492a7c1ac34ac37dba1351e3b Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Sat, 13 Jun 2020 13:05:25 +0200 Subject: [PATCH 28/35] scripter support for global vars --- tasmota/xdrv_10_scripter.ino | 247 +++++++++++++++++++++++++++++++++-- 1 file changed, 236 insertions(+), 11 deletions(-) diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 4d0bc1588..35427e67a 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -26,7 +26,6 @@ uses about 17 k of flash to do optimize code for space -g:var gloabal vars (via udp broadcast) remarks @@ -237,7 +236,11 @@ extern VButton *buttons[MAXBUTTONS]; #endif typedef union { +#ifdef USE_SCRIPT_GLOBVARS + uint16_t data; +#else uint8_t data; +#endif struct { uint8_t is_string : 1; // string or number uint8_t is_permanent : 1; @@ -247,6 +250,9 @@ typedef union { uint8_t settable : 1; uint8_t is_filter : 1; uint8_t constant : 1; +#ifdef USE_SCRIPT_GLOBVARS + uint8_t global : 1; +#endif }; } SCRIPT_TYPE; @@ -276,6 +282,32 @@ typedef union { }; } FILE_FLAGS; +typedef union { + uint8_t data; + struct { + uint8_t nutu8 : 1; + uint8_t nutu7 : 1; + uint8_t nutu6 : 1; + uint8_t nutu5 : 1; + uint8_t nutu4 : 1; + uint8_t nutu3 : 1; + uint8_t udp_connected : 1; + uint8_t udp_used : 1; + }; +} UDP_FLAGS; + + +#define NUM_RES 0xfe +#define STR_RES 0xfd +#define VAR_NV 0xff + +#define NTYPE 0 +#define STYPE 0x80 + +#ifndef FLT_MAX +#define FLT_MAX 99999999 +#endif + #define SFS_MAX 4 // global memory struct SCRIPT_MEM { @@ -308,12 +340,19 @@ struct SCRIPT_MEM { uint8_t script_sd_found; char flink[2][14]; #endif +#ifdef USE_SCRIPT_GLOBVARS + UDP_FLAGS udp_flags; +#endif } glob_script_mem; +#ifdef USE_SCRIPT_GLOBVARS +IPAddress last_udp_ip; +#endif int16_t last_findex; uint8_t tasm_cmd_activ=0; uint8_t fast_script=0; +uint8_t glob_script=0; uint32_t script_lastmillis; @@ -436,6 +475,16 @@ char *script; } else { vtypes[vars].bits.is_autoinc=0; } + +#ifdef USE_SCRIPT_GLOBVARS + if (*lp=='g' && *(lp+1)==':') { + lp+=2; + vtypes[vars].bits.global=1; + glob_script_mem.udp_flags.udp_used = 1; + } else { + vtypes[vars].bits.global=0; + } +#endif if ((*lp=='m' || *lp=='M') && *(lp+1)==':') { uint8_t flg=*lp; lp+=2; @@ -703,10 +752,112 @@ char *script; // store start of actual program here glob_script_mem.scriptptr=lp-1; glob_script_mem.scriptptr_bu=glob_script_mem.scriptptr; + +#ifdef USE_SCRIPT_GLOBVARS + if (glob_script_mem.udp_flags.udp_used) { + Script_Init_UDP(); + glob_script=Run_Scripter(">G",-2,0); + } +#endif + return 0; } +#ifdef USE_SCRIPT_GLOBVARS +#define SCRIPT_UDP_BUFFER_SIZE 128 +#define SCRIPT_UDP_PORT 1999 +IPAddress script_udp_remote_ip; + +void Script_Init_UDP() { + if (global_state.wifi_down) return; + if (glob_script_mem.udp_flags.udp_connected) return; + + if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), SCRIPT_UDP_PORT)) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP "SCRIPT UDP started")); + glob_script_mem.udp_flags.udp_connected = 1; + } else { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP "SCRIPT UDP failed")); + glob_script_mem.udp_flags.udp_connected = 0; + } +} +void Script_PollUdp(void) { + if (!glob_script_mem.udp_flags.udp_used) return; + if (glob_script_mem.udp_flags.udp_connected ) { + while (PortUdp.parsePacket()) { + char packet_buffer[SCRIPT_UDP_BUFFER_SIZE]; + int32_t len = PortUdp.read(packet_buffer, SCRIPT_UDP_BUFFER_SIZE -1); + packet_buffer[len] = 0; + script_udp_remote_ip = PortUdp.remoteIP(); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("UDP: Packet %s - %d - %s"), packet_buffer, len, script_udp_remote_ip.toString().c_str()); + char *lp=packet_buffer; + if (!strncmp(lp,"=>",2)) { + lp+=2; + char *cp=strchr(lp,'='); + if (cp) { + char vnam[32]; + for (uint32_t count=0; countG",2,0); + } + } + } + } + optimistic_yield(100); + } + } else { + Script_Init_UDP(); + } +} + +void script_udp_sendvar(char *vname,float *fp,char *sp) { + if (!glob_script_mem.udp_flags.udp_used) return; + if (!glob_script_mem.udp_flags.udp_connected) return; + + char sbuf[SCRIPT_MAXSSIZE+4]; + strcpy(sbuf,"=>"); + strcat(sbuf,vname); + strcat(sbuf,"="); + if (fp) { + char flstr[16]; + dtostrfd(*fp,8,flstr); + strcat(sbuf,flstr); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("num var updated - %s"),sbuf); + } else { + strcat(sbuf,sp); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("string var updated - %s"),sbuf); + } + PortUdp.beginPacket(IPAddress(239,255,255,250), SCRIPT_UDP_PORT); + // Udp.print(String("RET UC: ") + String(recv_Packet)); + PortUdp.write((const uint8_t*)sbuf,strlen(sbuf)); + PortUdp.endPacket(); +} + +#endif + #ifdef USE_LIGHT #ifdef USE_WS2812 void ws2812_set_array(float *array ,uint32_t len, uint32_t offset) { @@ -723,16 +874,7 @@ void ws2812_set_array(float *array ,uint32_t len, uint32_t offset) { #endif #endif -#define NUM_RES 0xfe -#define STR_RES 0xfd -#define VAR_NV 0xff -#define NTYPE 0 -#define STYPE 0x80 - -#ifndef FLT_MAX -#define FLT_MAX 99999999 -#endif float median_array(float *array,uint8_t len) { uint8_t ind[len]; @@ -1011,6 +1153,37 @@ uint32_t MeasurePulseTime(int32_t in) { } #endif // USE_ANGLE_FUNC +#ifdef USE_SCRIPT_GLOBVARS +uint32_t match_vars(char *dvnam, float **fp, char **sp, uint32_t *ind) { + uint16_t olen=strlen(dvnam); + struct T_INDEX *vtp=glob_script_mem.type; + for (uint32_t count=0; count ff=nothing found, fe=constant number,fd = constant string else bit 7 => 80 = string, 0 = number // no flash strings here for performance reasons!!! char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,JsonObject *jo) { @@ -1765,12 +1938,40 @@ chknext: len=0; goto exit; } + if (!strncmp(vname,"is(",3)) { + lp=GetNumericResult(lp+3,OPER_EQU,&fvar,0); + SCRIPT_SKIP_SPACES + if (*lp!='"') { + break; + } + lp++; + char *sstr=lp; + for (uint32_t cnt=0; cnt<256; cnt++) { + if (lp[cnt]='\n' || lp[cnt]=='"') { + lp+=cnt+1; + break; + } + } + char str[SCRIPT_MAXSSIZE]; + GetTextIndexed(str, sizeof(str), fvar, sstr); + lp++; + if (sp) strlcpy(sp,str,glob_script_mem.max_ssize); + fvar=0; + len=0; + goto exit; + } break; case 'l': if (!strncmp(vname,"lip",3)) { if (sp) strlcpy(sp,(const char*)WiFi.localIP().toString().c_str(),glob_script_mem.max_ssize); goto strexit; } +#ifdef USE_SCRIPT_GLOBVARS + if (!strncmp(vname,"luip",4)) { + if (sp) strlcpy(sp,IPAddressToString(last_udp_ip),glob_script_mem.max_ssize); + goto strexit; + } +#endif if (!strncmp(vname,"loglvl",6)) { fvar=glob_script_mem.script_loglevel; tind->index=SCRIPT_LOGLEVEL; @@ -3167,7 +3368,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { if (!if_exe[ifstck] && if_state[ifstck]!=1) goto next_line; #ifdef IFTHEN_DEBUG - sprintf(tbuff,"stack=%d,exe=%d,state=%d,cmpres=%d execute line: ",ifstck,if_exe[ifstck],if_state[ifstck],if_result[ifstck]); + sdtoff(tbuff,"stack=%d,exe=%d,state=%d,cmpres=%d execute line: ",ifstck,if_exe[ifstck],if_state[ifstck],if_result[ifstck]); toLogEOL(tbuff,lp); #endif @@ -3382,8 +3583,16 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { } goto next_line; } else { + char *vnp=lp; lp=isvar(lp,&vtype,&ind,&sysvar,0,0); if (vtype!=VAR_NV) { +#ifdef USE_SCRIPT_GLOBVARS + char varname[16]; + uint32_t vnl=(uint32_t)lp-(uint32)vnp; + strncpy(varname,vnp,vnl); + varname[vnl]=0; +#endif + // found variable as result globvindex=ind.index; // save destination var index here globaindex=last_findex; @@ -3451,6 +3660,11 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { } // var was changed glob_script_mem.type[globvindex].bits.changed=1; +#ifdef USE_SCRIPT_GLOBVARS + if (glob_script_mem.type[globvindex].bits.global) { + script_udp_sendvar(varname,dfvar,0); + } +#endif if (glob_script_mem.type[globvindex].bits.is_filter) { if (globaindex>=0) { Set_MFVal(glob_script_mem.type[globvindex].index,globaindex,*dfvar); @@ -3492,6 +3706,11 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) { if (!glob_script_mem.var_not_found) { // var was changed glob_script_mem.type[globvindex].bits.changed=1; +#ifdef USE_SCRIPT_GLOBVARS + if (glob_script_mem.type[globvindex].bits.global) { + script_udp_sendvar(varname,0,str); + } +#endif if (lastop==OPER_EQU) { strlcpy(glob_script_mem.glob_snp+(sindex*glob_script_mem.max_ssize),str,glob_script_mem.max_ssize); } else if (lastop==OPER_PLSEQU) { @@ -6136,6 +6355,12 @@ bool Xdrv10(uint8_t function) break; #endif +#ifdef USE_SCRIPT_GLOBVARS + case FUNC_LOOP: + Script_PollUdp(); + break; +#endif + } return result; } From 1e577a8213d6e7724624dcdbd41183d56e919a2b Mon Sep 17 00:00:00 2001 From: device111 <48546979+device111@users.noreply.github.com> Date: Sat, 13 Jun 2020 15:08:52 +0200 Subject: [PATCH 29/35] Fix VEML7700 lux measurement, add new command --- tasmota/xsns_71_veml7700.ino | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/tasmota/xsns_71_veml7700.ino b/tasmota/xsns_71_veml7700.ino index 3869e72d6..695e35824 100644 --- a/tasmota/xsns_71_veml7700.ino +++ b/tasmota/xsns_71_veml7700.ino @@ -40,27 +40,27 @@ const char JSON_SNS_VEML7700[] PROGMEM = ",\"%s\":{\"" D_JSON_ILLUMINANCE "\":%d #define D_CMND_VEML7700_PWR "power" #define D_CMND_VEML7700_GAIN "gain" #define D_CMND_VEML7700_INTTIME "inttime" +#define D_CMND_VEML7700_PERSIST "persist" 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; +const char kVEML7700_Commands[] PROGMEM = D_CMND_VEML7700_PWR "|" D_CMND_VEML7700_GAIN "|" D_CMND_VEML7700_INTTIME "|" D_CMND_VEML7700_PERSIST; enum VEML7700_Commands { // commands for Console CMND_VEML7700_PWR, CMND_VEML7700_GAIN, CMND_VEML7700_SET_IT, - }; + CMND_VEML7700_PERSIST, +}; struct VEML7700STRUCT { + bool active = 0; char types[9] = D_NAME_VEML7700; uint8_t address = VEML7700_I2CADDR_DEFAULT; - //uint16_t lux = 0; - //uint16_t white = 0; - uint16_t lux_normalized = 0; - uint16_t white_normalized = 0; + uint32_t lux_normalized = 0; + uint32_t white_normalized = 0; } veml7700_sensor; -uint8_t veml7700_active = 0; /********************************************************************************************/ @@ -68,7 +68,7 @@ void VEML7700Detect(void) { if (!I2cSetDevice(veml7700_sensor.address)) return; if (veml7700.begin()) { I2cSetActiveFound(veml7700_sensor.address, veml7700_sensor.types); - veml7700_active = 1; + veml7700_sensor.active = 1; } } @@ -97,10 +97,8 @@ uint8_t VEML7700TranslateItInt (uint16_t ittimems){ } void VEML7700EverySecond(void) { - 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(); + veml7700_sensor.lux_normalized = (uint32_t) veml7700.readLuxNormalized(); + veml7700_sensor.white_normalized = (uint32_t) veml7700.readWhiteNormalized(); } void VEML7700Show(bool json) @@ -152,6 +150,14 @@ bool VEML7700Cmd(void) { Response_P(S_JSON_VEML7700_COMMAND_NVALUE, command, dataret); } break; + case CMND_VEML7700_PERSIST: + if (XdrvMailbox.data_len) { + if (4 >= XdrvMailbox.payload) { + veml7700.setPersistence(XdrvMailbox.payload); + } + } + Response_P(S_JSON_VEML7700_COMMAND_NVALUE, command, veml7700.getPersistence()); + break; default: return false; } @@ -174,7 +180,7 @@ bool Xsns71(uint8_t function) if (FUNC_INIT == function) { VEML7700Detect(); } - else if (veml7700_active) { + else if (veml7700_sensor.active) { switch (function) { case FUNC_EVERY_SECOND: VEML7700EverySecond(); From f46d3751a01a112075e98ae5574a6b6a4ec3e1fc Mon Sep 17 00:00:00 2001 From: Theo Arends <11044339+arendst@users.noreply.github.com> Date: Sat, 13 Jun 2020 15:10:12 +0200 Subject: [PATCH 30/35] Refactor some energy monitoring --- tasmota/xdrv_03_energy.ino | 3 +++ tasmota/xnrg_02_cse7766.ino | 45 +++++++++++++++---------------------- tasmota/xnrg_14_bl0940.ino | 27 ++++++++++------------ 3 files changed, 33 insertions(+), 42 deletions(-) diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino index bf9515b6a..b84d69407 100644 --- a/tasmota/xdrv_03_energy.ino +++ b/tasmota/xdrv_03_energy.ino @@ -1142,6 +1142,9 @@ bool Xdrv03(uint8_t function) case FUNC_EVERY_250_MSECOND: XnrgCall(FUNC_EVERY_250_MSECOND); break; + case FUNC_EVERY_SECOND: + XnrgCall(FUNC_EVERY_SECOND); + break; case FUNC_SERIAL: result = XnrgCall(FUNC_SERIAL); break; diff --git a/tasmota/xnrg_02_cse7766.ino b/tasmota/xnrg_02_cse7766.ino index f0aa210d1..9c8417bc6 100644 --- a/tasmota/xnrg_02_cse7766.ino +++ b/tasmota/xnrg_02_cse7766.ino @@ -59,8 +59,7 @@ struct CSE { bool received = false; } Cse; -void CseReceived(void) -{ +void CseReceived(void) { // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 // F2 5A 02 F7 60 00 03 61 00 40 10 05 72 40 51 A6 58 63 10 1B E1 7F 4D 4E - F2 = Power cycle exceeds range - takes too long - No load // 55 5A 02 F7 60 00 03 5A 00 40 10 04 8B 9F 51 A6 58 18 72 75 61 AC A1 30 - 55 = Ok, 61 = Power not valid (load below 5W) @@ -69,7 +68,7 @@ void CseReceived(void) uint8_t header = Cse.rx_buffer[0]; if ((header & 0xFC) == 0xFC) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CSE: Abnormal hardware")); return; } @@ -142,8 +141,7 @@ void CseReceived(void) } } -bool CseSerialInput(void) -{ +bool CseSerialInput(void) { while (CseSerial->available()) { yield(); uint8_t serial_in_byte = CseSerial->read(); @@ -162,12 +160,12 @@ bool CseSerialInput(void) Cse.received = false; return true; } else { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); do { // Sync buffer with data (issue #1907 and #3425) memmove(Cse.rx_buffer, Cse.rx_buffer +1, 24); Cse.byte_counter--; } while ((Cse.byte_counter > 2) && (0x5A != Cse.rx_buffer[1])); if (0x5A != Cse.rx_buffer[1]) { + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CSE: " D_CHECKSUM_FAILURE)); Cse.received = false; Cse.byte_counter = 0; } @@ -186,34 +184,31 @@ bool CseSerialInput(void) /********************************************************************************************/ -void CseEverySecond(void) -{ +void CseEverySecond(void) { if (Energy.data_valid[0] > ENERGY_WATCHDOG) { Cse.voltage_cycle = 0; Cse.current_cycle = 0; Cse.power_cycle = 0; } else { - long cf_frequency = 0; - if (CSE_PULSES_NOT_INITIALIZED == Cse.cf_pulses_last_time) { Cse.cf_pulses_last_time = Cse.cf_pulses; // Init after restart } else { - if (Cse.cf_pulses < Cse.cf_pulses_last_time) { // Rolled over after 65535 pulses - cf_frequency = (65536 - Cse.cf_pulses_last_time) + Cse.cf_pulses; + uint32_t cf_pulses = 0; + if (Cse.cf_pulses < Cse.cf_pulses_last_time) { // Rolled over after 0xFFFF (65535) pulses + cf_pulses = (0x10000 - Cse.cf_pulses_last_time) + Cse.cf_pulses; } else { - cf_frequency = Cse.cf_pulses - Cse.cf_pulses_last_time; + cf_pulses = Cse.cf_pulses - Cse.cf_pulses_last_time; } - if (cf_frequency && Energy.active_power[0]) { - unsigned long delta = (cf_frequency * Settings.energy_power_calibration) / 36; + if (cf_pulses && Energy.active_power[0]) { + uint32_t delta = (cf_pulses * Settings.energy_power_calibration) / 36; // prevent invalid load delta steps even checksum is valid (issue #5789): -// if (delta <= (3680*100/36) * 10 ) { // max load for S31/Pow R2: 3.68kW // prevent invalid load delta steps even checksum is valid but allow up to 4kW (issue #7155): - if (delta <= (4000*100/36) * 10 ) { // max load for S31/Pow R2: 4.00kW + if (delta <= (4000 * 1000 / 36)) { // max load for S31/Pow R2: 4.00kW Cse.cf_pulses_last_time = Cse.cf_pulses; Energy.kWhtoday_delta += delta; } else { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("CSE: Load overflow")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("CSE: Overload")); Cse.cf_pulses_last_time = CSE_PULSES_NOT_INITIALIZED; } EnergyUpdateToday(); @@ -222,8 +217,7 @@ void CseEverySecond(void) } } -void CseSnsInit(void) -{ +void CseSnsInit(void) { // Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions // CseSerial = new TasmotaSerial(Pin(GPIO_CSE7766_RX), Pin(GPIO_CSE7766_TX), 1); CseSerial = new TasmotaSerial(Pin(GPIO_CSE7766_RX), -1, 1); @@ -241,8 +235,7 @@ void CseSnsInit(void) } } -void CseDrvInit(void) -{ +void CseDrvInit(void) { // if (PinUsed(GPIO_CSE7766_RX) && PinUsed(GPIO_CSE7766_TX)) { if (PinUsed(GPIO_CSE7766_RX)) { Cse.rx_buffer = (uint8_t*)(malloc(CSE_BUFFER_SIZE)); @@ -252,8 +245,7 @@ void CseDrvInit(void) } } -bool CseCommand(void) -{ +bool CseCommand(void) { bool serviced = true; if (CMND_POWERSET == Energy.command_code) { @@ -280,15 +272,14 @@ bool CseCommand(void) * Interface \*********************************************************************************************/ -bool Xnrg02(uint8_t function) -{ +bool Xnrg02(uint8_t function) { bool result = false; switch (function) { case FUNC_LOOP: if (CseSerial) { CseSerialInput(); } break; - case FUNC_ENERGY_EVERY_SECOND: + case FUNC_EVERY_SECOND: CseEverySecond(); break; case FUNC_COMMAND: diff --git a/tasmota/xnrg_14_bl0940.ino b/tasmota/xnrg_14_bl0940.ino index d9bc51db8..fae36ea3a 100644 --- a/tasmota/xnrg_14_bl0940.ino +++ b/tasmota/xnrg_14_bl0940.ino @@ -57,11 +57,9 @@ struct BL0940 { long voltage = 0; long current = 0; long power = 0; - long power_cycle_first = 0; long cf_pulses = 0; long cf_pulses_last_time = BL0940_PULSES_NOT_INITIALIZED; - float temperature; int byte_counter = 0; @@ -88,7 +86,7 @@ void Bl0940Received(void) { if ((Bl0940.rx_buffer[0] != BL0940_PACKET_HEADER) || // Bad header (Bl0940.tps1 && ((tps1 < (Bl0940.tps1 -10)) || (tps1 > (Bl0940.tps1 +10)))) // Invalid temperature change ) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: Invalid data")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: Invalid data")); return; } @@ -146,13 +144,12 @@ bool Bl0940SerialInput(void) { Bl0940.received = false; return true; } else { -// AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE)); do { // Sync buffer with data (issue #1907 and #3425) memmove(Bl0940.rx_buffer, Bl0940.rx_buffer +1, BL0940_BUFFER_SIZE -1); Bl0940.byte_counter--; } while ((Bl0940.byte_counter > 1) && (BL0940_PACKET_HEADER != Bl0940.rx_buffer[0])); if (BL0940_PACKET_HEADER != Bl0940.rx_buffer[0]) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE)); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: " D_CHECKSUM_FAILURE)); Bl0940.received = false; Bl0940.byte_counter = 0; } @@ -188,13 +185,13 @@ void Bl0940EverySecond(void) { cf_pulses = Bl0940.cf_pulses - Bl0940.cf_pulses_last_time; } if (cf_pulses && Energy.active_power[0]) { - if (cf_pulses < 16) { // max load for SHP10: 4.00kW (3.68kW) - uint32_t watt256 = (1638400 * 256) / Settings.energy_power_calibration; - uint32_t delta = (cf_pulses * watt256) / 36; + uint32_t watt256 = (1638400 * 256) / Settings.energy_power_calibration; + uint32_t delta = (cf_pulses * watt256) / 36; + if (delta <= (4000 * 1000 / 36)) { // max load for SHP10: 4.00kW (3.68kW) Bl0940.cf_pulses_last_time = Bl0940.cf_pulses; Energy.kWhtoday_delta += delta; } else { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("BL9: Overload")); + AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: Overload")); Bl0940.cf_pulses_last_time = BL0940_PULSES_NOT_INITIALIZED; } EnergyUpdateToday(); @@ -203,6 +200,8 @@ void Bl0940EverySecond(void) { } +// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("BL9: Poll")); + Bl0940Serial->flush(); Bl0940Serial->write(BL0940_READ_COMMAND); Bl0940Serial->write(BL0940_FULL_PACKET); @@ -211,7 +210,7 @@ void Bl0940EverySecond(void) { void Bl0940SnsInit(void) { // Software serial init needs to be done here as earlier (serial) interrupts may lead to Exceptions Bl0940Serial = new TasmotaSerial(Pin(GPIO_BL0940_RX), Pin(GPIO_TXD), 1); - if (Bl0940Serial->begin(4800, 2)) { + if (Bl0940Serial->begin(4800, 1)) { if (Bl0940Serial->hardwareSerial()) { ClaimSerial(); } @@ -268,8 +267,7 @@ bool Bl0940Command(void) { return serviced; } -void Bl0940Show(bool json) -{ +void Bl0940Show(bool json) { char temperature[33]; dtostrfd(Bl0940.temperature, Settings.flag2.temperature_resolution, temperature); @@ -294,15 +292,14 @@ void Bl0940Show(bool json) * Interface \*********************************************************************************************/ -bool Xnrg14(uint8_t function) -{ +bool Xnrg14(uint8_t function) { bool result = false; switch (function) { case FUNC_LOOP: if (Bl0940Serial) { Bl0940SerialInput(); } break; - case FUNC_ENERGY_EVERY_SECOND: + case FUNC_EVERY_SECOND: Bl0940EverySecond(); break; case FUNC_JSON_APPEND: From 4f108e05045d9a88685647cbdf2e3e56fcb58b51 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Sun, 14 Jun 2020 12:07:12 +0200 Subject: [PATCH 31/35] fix sml modbus raw mode --- tasmota/xsns_53_sml.ino | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tasmota/xsns_53_sml.ino b/tasmota/xsns_53_sml.ino index cb51bcc82..18fd53bf5 100755 --- a/tasmota/xsns_53_sml.ino +++ b/tasmota/xsns_53_sml.ino @@ -2405,12 +2405,13 @@ void SML_Send_Seq(uint32_t meter,char *seq) { if (!rflg) { *ucp++=0; *ucp++=2; + slen+=2; } // append crc - uint16_t crc = MBUS_calculateCRC(sbuff,6); + uint16_t crc = MBUS_calculateCRC(sbuff,slen); *ucp++=lowByte(crc); *ucp++=highByte(crc); - slen+=4; + slen+=2; } if (script_meter_desc[meter].type=='o') { for (uint32_t cnt=0;cnt Date: Sun, 14 Jun 2020 12:36:44 +0200 Subject: [PATCH 32/35] Add rule trigger ``System#Init`` Add rule trigger ``System#Init`` to allow early rule execution without wifi and mqtt initialized yet (#8673) --- RELEASENOTES.md | 1 + tasmota/CHANGELOG.md | 1 + tasmota/settings.h | 6 +++--- tasmota/tasmota.ino | 2 ++ tasmota/xdrv_10_rules.ino | 21 +++++++++++---------- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c1637501c..2f9d95ed2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -78,3 +78,4 @@ The following binary downloads have been compiled with ESP8266/Arduino library c - Add support for BL0940 energy monitor as used in Blitzwolf BW-SHP10 (#8175) - Add initial support for Telegram bot (#8619) - Add support for HP303B Temperature and Pressure sensor by Robert Jaakke (#8638) +- Add rule trigger ``System#Init`` to allow early rule execution without wifi and mqtt initialized yet diff --git a/tasmota/CHANGELOG.md b/tasmota/CHANGELOG.md index 0c6ef82bd..73582dfb7 100644 --- a/tasmota/CHANGELOG.md +++ b/tasmota/CHANGELOG.md @@ -4,6 +4,7 @@ - Add initial support for Telegram bot (#8619) - Add support for HP303B Temperature and Pressure sensor by Robert Jaakke (#8638) +- Add rule trigger ``System#Init`` to allow early rule execution without wifi and mqtt initialized yet ### 8.3.1.2 20200522 diff --git a/tasmota/settings.h b/tasmota/settings.h index 1b0cc0951..08175d7db 100644 --- a/tasmota/settings.h +++ b/tasmota/settings.h @@ -647,13 +647,14 @@ struct XDRVMAILBOX { } XdrvMailbox; #ifdef USE_SHUTTER -const uint8_t MAX_RULES_FLAG = 10; // Number of bits used in RulesBitfield (tricky I know...) +const uint8_t MAX_RULES_FLAG = 11; // Number of bits used in RulesBitfield (tricky I know...) #else -const uint8_t MAX_RULES_FLAG = 8; // Number of bits used in RulesBitfield (tricky I know...) +const uint8_t MAX_RULES_FLAG = 9; // Number of bits used in RulesBitfield (tricky I know...) #endif // USE_SHUTTER typedef union { // Restricted by MISRA-C Rule 18.4 but so useful... uint16_t data; // Allow bit manipulation struct { + uint16_t system_init : 1; // Changing layout here needs adjustments in xdrv_10_rules.ino too uint16_t system_boot : 1; uint16_t time_init : 1; uint16_t time_set : 1; @@ -664,7 +665,6 @@ typedef union { // Restricted by MISRA-C Rule 18.4 bu uint16_t http_init : 1; uint16_t shutter_moved : 1; uint16_t shutter_moving : 1; - uint16_t spare10 : 1; uint16_t spare11 : 1; uint16_t spare12 : 1; uint16_t spare13 : 1; diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 7ee0c8316..06bd8d9e7 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -322,6 +322,8 @@ void setup(void) { XdrvCall(FUNC_INIT); XsnsCall(FUNC_INIT); + + rules_flag.system_init = 1; } void BacklogLoop(void) { diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino index 10c4c22e8..37300d03c 100644 --- a/tasmota/xdrv_10_rules.ino +++ b/tasmota/xdrv_10_rules.ino @@ -887,17 +887,18 @@ void RulesEvery50ms(void) rules_flag.data ^= mask; json_event[0] = '\0'; switch (i) { - case 0: strncpy_P(json_event, PSTR("{\"System\":{\"Boot\":1}}"), sizeof(json_event)); break; - case 1: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Initialized\":%d}}"), MinutesPastMidnight()); break; - case 2: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Set\":%d}}"), MinutesPastMidnight()); break; - case 3: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Connected\":1}}"), sizeof(json_event)); break; - case 4: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Disconnected\":1}}"), sizeof(json_event)); break; - case 5: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Connected\":1}}"), sizeof(json_event)); break; - case 6: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Disconnected\":1}}"), sizeof(json_event)); break; - case 7: strncpy_P(json_event, PSTR("{\"HTTP\":{\"Initialized\":1}}"), sizeof(json_event)); break; + case 0: strncpy_P(json_event, PSTR("{\"System\":{\"Init\":1}}"), sizeof(json_event)); break; + case 1: strncpy_P(json_event, PSTR("{\"System\":{\"Boot\":1}}"), sizeof(json_event)); break; + case 2: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Initialized\":%d}}"), MinutesPastMidnight()); break; + case 3: snprintf_P(json_event, sizeof(json_event), PSTR("{\"Time\":{\"Set\":%d}}"), MinutesPastMidnight()); break; + case 4: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Connected\":1}}"), sizeof(json_event)); break; + case 5: strncpy_P(json_event, PSTR("{\"MQTT\":{\"Disconnected\":1}}"), sizeof(json_event)); break; + case 6: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Connected\":1}}"), sizeof(json_event)); break; + case 7: strncpy_P(json_event, PSTR("{\"WIFI\":{\"Disconnected\":1}}"), sizeof(json_event)); break; + case 8: strncpy_P(json_event, PSTR("{\"HTTP\":{\"Initialized\":1}}"), sizeof(json_event)); break; #ifdef USE_SHUTTER - case 8: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moved\":1}}"), sizeof(json_event)); break; - case 9: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moving\":1}}"), sizeof(json_event)); break; + case 9: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moved\":1}}"), sizeof(json_event)); break; + case 10: strncpy_P(json_event, PSTR("{\"SHUTTER\":{\"Moving\":1}}"), sizeof(json_event)); break; #endif // USE_SHUTTER } if (json_event[0]) { From 303b49f2fe09065c01ce691df542b10bcc58fcda Mon Sep 17 00:00:00 2001 From: Jason2866 <24528715+Jason2866@users.noreply.github.com> Date: Sun, 14 Jun 2020 15:18:38 +0200 Subject: [PATCH 33/35] use espressif8266@2.5.2 minor fixes in PlatformIO framework --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 04835d6c0..abb9d364d 100755 --- a/platformio.ini +++ b/platformio.ini @@ -128,6 +128,6 @@ build_flags = ${tasmota_core.build_flags} [tasmota_core] ; *** Esp8266 Arduino core 2.7.1 -platform = espressif8266@2.5.1 +platform = espressif8266@2.5.2 platform_packages = build_flags = ${esp82xx_defaults.build_flags} From 7d1a27560606fc0a0a83d0a0292d44d095d4b455 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Mon, 15 Jun 2020 07:25:39 +0200 Subject: [PATCH 34/35] fix chartofloat digit overflow --- tasmota/support.ino | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tasmota/support.ino b/tasmota/support.ino index 879a91fb0..0a5d26f10 100644 --- a/tasmota/support.ino +++ b/tasmota/support.ino @@ -167,6 +167,8 @@ float CharToFloat(const char *str) float right = 0; if (*pt == '.') { pt++; + // limit decimals to float max + pt[7]=0; right = atoi(pt); // Decimal part while (isdigit(*pt)) { pt++; @@ -1908,4 +1910,4 @@ String Decompress(const char * compressed, size_t uncompressed_size) { return content; } -#endif // USE_UNISHOX_COMPRESSION \ No newline at end of file +#endif // USE_UNISHOX_COMPRESSION From 5f7a32af4cfca2fbb9239a7c096190571e88df39 Mon Sep 17 00:00:00 2001 From: gemu2015 Date: Mon, 15 Jun 2020 08:20:43 +0200 Subject: [PATCH 35/35] update ibeacon to export UID --- tasmota/xsns_52_ibeacon.ino | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/tasmota/xsns_52_ibeacon.ino b/tasmota/xsns_52_ibeacon.ino index 5f9c0f3ba..6a33d94a0 100755 --- a/tasmota/xsns_52_ibeacon.ino +++ b/tasmota/xsns_52_ibeacon.ino @@ -87,6 +87,9 @@ struct IBEACON { struct IBEACON_UID { char MAC[12]; char RSSI[4]; + char UID[32]; + char MAJOR[4]; + char MINOR[4]; uint8_t FLAGS; uint8_t TIME; } ibeacons[MAX_IBEACONS]; @@ -132,7 +135,7 @@ void hm17_every_second(void) { ibeacons[cnt].TIME++; if (ibeacons[cnt].TIME>IB_TIMEOUT_TIME) { ibeacons[cnt].FLAGS=0; - ibeacon_mqtt(ibeacons[cnt].MAC,"0000"); + ibeacon_mqtt(ibeacons[cnt].MAC,"0000",ibeacons[cnt].UID,ibeacons[cnt].MAJOR,ibeacons[cnt].MINOR); } } } @@ -210,6 +213,9 @@ uint32_t ibeacon_add(struct IBEACON *ib) { if (!ibeacons[cnt].FLAGS) { memcpy(ibeacons[cnt].MAC,ib->MAC,12); memcpy(ibeacons[cnt].RSSI,ib->RSSI,4); + memcpy(ibeacons[cnt].UID,ib->UID,32); + memcpy(ibeacons[cnt].MAJOR,ib->MAJOR,4); + memcpy(ibeacons[cnt].MINOR,ib->MINOR,4); ibeacons[cnt].FLAGS=1; ibeacons[cnt].TIME=0; return 1; @@ -400,7 +406,7 @@ hm17_v110: memcpy(ib.RSSI,&hm17_sbuffer[8+8+1+32+1+4+4+2+1+12+1],4); if (ibeacon_add(&ib)) { - ibeacon_mqtt(ib.MAC,ib.RSSI); + ibeacon_mqtt(ib.MAC,ib.RSSI,ib.UID,ib.MAJOR,ib.MINOR); } hm17_sbclr(); hm17_result=1; @@ -560,15 +566,30 @@ void ib_sendbeep(void) { hm17_sendcmd(HM17_CON); } -void ibeacon_mqtt(const char *mac,const char *rssi) { +void ibeacon_mqtt(const char *mac,const char *rssi,const char *uid,const char *major,const char *minor) { char s_mac[14]; + char s_uid[34]; + char s_major[6]; + char s_minor[6]; char s_rssi[6]; memcpy(s_mac,mac,12); s_mac[12]=0; + memcpy(s_uid,uid,32); + s_uid[32]=0; + memcpy(s_major,major,4); + s_major[4]=0; + memcpy(s_minor,minor,4); + s_minor[4]=0; memcpy(s_rssi,rssi,4); s_rssi[4]=0; int16_t n_rssi=atoi(s_rssi); - ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "_%s\":{\"RSSI\":%d}}"),s_mac,n_rssi); + // if uid == all zeros, take mac + if (!strncmp_P(s_uid,PSTR("00000000000000000000000000000000"),32)) { + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "_%s\":{\"UID\":\"%s\",\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"RSSI\":%d}}"),s_mac,s_uid,s_major,s_minor,n_rssi); + } else { + ResponseTime_P(PSTR(",\"" D_CMND_IBEACON "_%s\":{\"MAJOR\":\"%s\",\"MINOR\":\"%s\",\"RSSI\":%d}}"),s_uid,s_major,s_minor,n_rssi); + } + MqttPublishTeleSensor(); }