From bbc63c207767e3e8e5c7fd0a1553fd632e555bac Mon Sep 17 00:00:00 2001
From: Neel Malik
Date: Sat, 9 Mar 2019 17:44:42 -0800
Subject: [PATCH 01/22] preliminary SCD30 support
---
lib/FrogmoreScd30/FrogmoreScd30.cpp | 653 ++++++++++++++++++++++++++++
lib/FrogmoreScd30/FrogmoreScd30.h | 105 +++++
sonoff/i18n.h | 8 +-
sonoff/sonoff_post.h | 1 +
sonoff/xdrv_92_scd30.ino | 505 +++++++++++++++++++++
5 files changed, 1270 insertions(+), 2 deletions(-)
create mode 100644 lib/FrogmoreScd30/FrogmoreScd30.cpp
create mode 100644 lib/FrogmoreScd30/FrogmoreScd30.h
create mode 100644 sonoff/xdrv_92_scd30.ino
diff --git a/lib/FrogmoreScd30/FrogmoreScd30.cpp b/lib/FrogmoreScd30/FrogmoreScd30.cpp
new file mode 100644
index 000000000..32bbee5ba
--- /dev/null
+++ b/lib/FrogmoreScd30/FrogmoreScd30.cpp
@@ -0,0 +1,653 @@
+/*
+# Copyright (c) 2019 Frogmore42
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#include
+#include
+#include
+#include
+#include
+
+#define COMMAND_SCD30_CONTINUOUS_MEASUREMENT 0x0010
+#define COMMAND_SCD30_MEASUREMENT_INTERVAL 0x4600
+#define COMMAND_SCD30_GET_DATA_READY 0x0202
+#define COMMAND_SCD30_READ_MEASUREMENT 0x0300
+#define COMMAND_SCD30_CALIBRATION_TYPE 0x5306
+#define COMMAND_SCD30_FORCED_RECALIBRATION_FACTOR 0x5204
+#define COMMAND_SCD30_TEMPERATURE_OFFSET 0x5403
+#define COMMAND_SCD30_ALTITUDE_COMPENSATION 0x5102
+#define COMMAND_SCD30_SOFT_RESET 0xD304
+#define COMMAND_SCD30_GET_FW_VERSION 0xD100
+#define COMMAND_SCD30_STOP_MEASUREMENT 0x0104
+
+#define SCD30_DATA_REGISTER_BYTES 2
+#define SCD30_DATA_REGISTER_WITH_CRC 3
+#define SCD30_MEAS_BYTES 18
+
+#ifdef SCD30_DEBUG
+enum LoggingLevels {LOG_LEVEL_NONE, LOG_LEVEL_ERROR, LOG_LEVEL_INFO, LOG_LEVEL_DEBUG, LOG_LEVEL_DEBUG_MORE, LOG_LEVEL_ALL};
+char scd30log_data[180];
+#endif
+
+void FrogmoreScd30::begin(TwoWire *pWire, uint8_t i2cAddress)
+{
+ this->i2cAddress = i2cAddress;
+ if (pWire == NULL)
+ {
+ this->pWire = &Wire;
+ }
+ else
+ {
+ this->pWire = pWire;
+ }
+
+ co2NewDataLocation = -1; // indicates there is no data, so the 1st data point needs to fill up the median filter
+ this->pWire->setClockStretchLimit(200000);
+ this->ambientPressure = 0;
+}
+
+void FrogmoreScd30::begin(uint8_t i2cAddress)
+{
+ begin(NULL, i2cAddress);
+}
+
+void FrogmoreScd30::begin(TwoWire *pWire)
+{
+ begin(pWire, SCD30_ADDRESS);
+}
+
+void FrogmoreScd30::begin(void)
+{
+ begin(NULL, SCD30_ADDRESS);
+}
+
+/*---------------------------------------------------------------------------
+ Function : opt_med5() In : pointer to array of 5 values
+ Out : a uint16_t which is the middle value of the sorted array
+ Job : optimized search of the median of 5 values
+ Notice : found on sci.image.processing cannot go faster unless assumptions are made on the nature of the input signal.
+ ---------------------------------------------------------------------------*/
+#define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); }
+#define PIX_SWAP(a,b) { uint16_t temp=(a);(a)=(b);(b)=temp; }
+
+uint16_t opt_med5(uint16_t * p)
+{
+ PIX_SORT(p[0], p[1]);
+ PIX_SORT(p[3], p[4]);
+ PIX_SORT(p[0], p[3]);
+ PIX_SORT(p[1], p[4]);
+ PIX_SORT(p[1], p[2]);
+ PIX_SORT(p[2], p[3]);
+ PIX_SORT(p[1], p[2]);
+ return(p[2]);
+}
+
+// twi_status() attempts to read out any data left that is holding SDA low, so a new transaction can take place
+// something like (http://www.forward.com.au/pfod/ArduinoProgramming/I2C_ClearBus/index.html)
+int FrogmoreScd30::clearI2CBus(void)
+{
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "clearI2CBus");
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+ return (twi_status());
+}
+
+#ifdef SCD30_DEBUG
+void FrogmoreScd30::AddLog(uint8_t loglevel)
+{
+ if (loglevel <= LOG_LEVEL_INFO)
+ {
+ Serial.printf("%s\r\n", scd30log_data);
+ }
+}
+#endif
+
+uint8_t FrogmoreScd30::computeCRC8(uint8_t data[], uint8_t len)
+// Computes the CRC that the SCD30 uses
+{
+ uint8_t crc = 0xFF; //Init with 0xFF
+
+ for (uint8_t x = 0 ; x < len ; x++)
+ {
+ crc ^= data[x]; // XOR-in the next input byte
+ for (uint8_t i = 0 ; i < 8 ; i++)
+ {
+ if ((crc & 0x80) != 0)
+ crc = (uint8_t)((crc << 1) ^ 0x31);
+ else
+ crc <<= 1;
+ }
+ }
+
+ return crc; //No output reflection
+}
+
+// Sends stream of bytes to device
+int FrogmoreScd30::sendBytes(void *pInput, uint8_t len)
+{
+ uint8_t *pBytes = (uint8_t *) pInput;
+ int result;
+ uint8_t errorBytes = 0; // number of bytes that had an error in transmission
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendBytes: data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X", pBytes[0], pBytes[1], pBytes[2], pBytes[3], pBytes[4], pBytes[5], pBytes[6], pBytes[7], pBytes[8]);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+ pWire->beginTransmission(this->i2cAddress);
+ errorBytes = len - (pWire->write(pBytes, len));
+ result = pWire->endTransmission();
+ if (errorBytes || result)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendBytes: errorBytes: %d | Wire.end: %d", errorBytes, result);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ }
+
+ result <<= 8; // leave room for error bytes number
+ result |= errorBytes; // low byte has number of bytes that were not written correctly
+ return (result);
+}
+
+// Gets a number of bytes from device
+int FrogmoreScd30::getBytes(void *pOutput, uint8_t len)
+{
+ uint8_t *pBytes = (uint8_t *) pOutput;
+ uint8_t result;
+
+ result = pWire->requestFrom(this->i2cAddress, len);
+ if (len != result)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30GetBytes: wire request expected %d got: %d", len, result);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (ERROR_SCD30_NOT_ENOUGH_BYTES_ERROR);
+ }
+
+ if (pWire->available())
+ {
+ for (int x = 0; x < len; x++)
+ {
+ pBytes[x] = pWire->read();
+ }
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30GetBytes: data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X", pBytes[0], pBytes[1], pBytes[2], pBytes[3], pBytes[4], pBytes[5], pBytes[6], pBytes[7], pBytes[8]);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+ return (ERROR_SCD30_NO_ERROR);
+ }
+
+ return (ERROR_SCD30_UNKNOWN_ERROR);
+}
+
+//Sends just a command, no arguments, no CRC
+int FrogmoreScd30::sendCommand(uint16_t command)
+{
+ uint8_t data[2];
+ data[0] = command >> 8;
+ data[1] = command & 0xFF;
+ int error = sendBytes(data, sizeof(data));
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendCommand: Scd30SendBytes failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ }
+ return (error);
+}
+
+//Sends a command along with arguments and CRC
+int FrogmoreScd30::sendCommandArguments(uint16_t command, uint16_t arguments)
+{
+ uint8_t data[5];
+ data[0] = command >> 8;
+ data[1] = command & 0xFF;
+ data[2] = arguments >> 8;
+ data[3] = arguments & 0xFF;
+ data[4] = computeCRC8(&data[2], 2); //Calc CRC on the arguments only, not the command
+ int error = sendBytes(data, sizeof(data));
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30SendCommandArguments: Scd30SendBytes failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ }
+ return (error);
+}
+
+int FrogmoreScd30::get16BitRegCheckCRC(void* pInput, uint16_t *pData)
+{
+ uint8_t *pBytes = (uint8_t *) pInput;
+ uint8_t expectedCRC = computeCRC8(pBytes, SCD30_DATA_REGISTER_BYTES);
+ if (expectedCRC != pBytes[SCD30_DATA_REGISTER_BYTES])
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30get16BitRegCheckCRC: expected: 0x%02X, but got: 0x%02X", expectedCRC, pBytes[SCD30_DATA_REGISTER_BYTES]);
+ AddLog(LOG_LEVEL_INFO);
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30get16BitRegCheckCRC: data: 0x%02X, 0x%02X, 0x%02X", pBytes[0], pBytes[1], pBytes[2]);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (ERROR_SCD30_CRC_ERROR);
+ }
+ *pData = (uint16_t) pBytes[0] << 8 | pBytes[1]; // data from SCD30 is Big-Endian
+ return (ERROR_SCD30_NO_ERROR);
+}
+
+// gets 32 bits, (2) 16-bit chunks, and validates the CRCs
+//
+int FrogmoreScd30::get32BitRegCheckCRC(void *pInput, float *pData)
+{
+ uint16_t tempU16High;
+ uint16_t tempU16Low;
+ uint8_t *pBytes = (uint8_t *) pInput;
+ uint32_t rawInt = 0;
+
+ int error = get16BitRegCheckCRC(pBytes, &tempU16High);
+ if (error) {
+ return (error);
+ }
+
+ error = get16BitRegCheckCRC(pBytes + SCD30_DATA_REGISTER_WITH_CRC, &tempU16Low);
+ if (error) {
+ return (error);
+ }
+
+ // data from SCD is Big-Endian
+ rawInt |= tempU16High;
+ rawInt <<= 16;
+ rawInt |= tempU16Low;
+
+ *pData = * (float *) &rawInt;
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "get32BitRegCheckCRC: got: tempUs 0x%lX, %lX", tempU16High, tempU16Low);
+ AddLog(LOG_LEVEL_DEBUG);
+#endif
+
+ if (isnan(*pData) || isinf(*pData))
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "get32BitRegCheckCRC: not a floating point number: rawInt 0x%lX", rawInt);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (ERROR_SCD30_NOT_A_NUMBER_ERROR);
+ }
+
+ return (ERROR_SCD30_NO_ERROR);
+}
+
+//Gets two bytes (and check CRC) from SCD30
+int FrogmoreScd30::readRegister(uint16_t registerAddress, uint16_t* pData)
+{
+ int error = sendCommand(registerAddress);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadRegister: SendCommand error: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+ delay(1); // the SCD30 uses clock streching to give it time to prepare data, waiting here makes it work
+ uint8_t data[SCD30_DATA_REGISTER_WITH_CRC];
+ error = getBytes(data, sizeof(data));
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadRegister: Scd30GetBytes error: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+ uint16 regValue;
+ error = get16BitRegCheckCRC(data, ®Value);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadRegister: Scd30get16BitRegCheckCRC error: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+
+ *pData = regValue;
+ return (ERROR_SCD30_NO_ERROR);
+}
+
+int FrogmoreScd30::softReset(void)
+{
+ return (sendCommand(COMMAND_SCD30_SOFT_RESET));
+}
+
+int FrogmoreScd30::getAltitudeCompensation(uint16_t *pHeight_meter)
+{
+ return (readRegister(COMMAND_SCD30_ALTITUDE_COMPENSATION, pHeight_meter));
+}
+
+int FrogmoreScd30::getAmbientPressure(uint16_t *pAirPressure_mbar)
+{
+ *pAirPressure_mbar = ambientPressure;
+ return (ERROR_SCD30_NO_ERROR);
+}
+
+int FrogmoreScd30::getCalibrationType(uint16_t *pIsAuto)
+{
+ uint16_t value = 0;
+ int error = readRegister(COMMAND_SCD30_CALIBRATION_TYPE, &value);
+ if (!error)
+ {
+ *pIsAuto = value != 0;
+ }
+ return (error);
+}
+
+int FrogmoreScd30::getFirmwareVersion(uint8_t *pMajor, uint8_t *pMinor)
+{
+ uint16_t value;
+ int error = readRegister(COMMAND_SCD30_GET_FW_VERSION, &value);
+ if (!error)
+ {
+ *pMajor = value >> 8;
+ *pMinor = value & 0xFF;
+ }
+ return (error);
+}
+
+int FrogmoreScd30::getForcedRecalibrationFactor(uint16_t *pCo2_ppm)
+{
+ return (readRegister(COMMAND_SCD30_FORCED_RECALIBRATION_FACTOR, pCo2_ppm));
+}
+
+int FrogmoreScd30::getMeasurementInterval(uint16_t *pTime_sec)
+{
+ return (readRegister(COMMAND_SCD30_MEASUREMENT_INTERVAL, pTime_sec));
+}
+
+int FrogmoreScd30::getTemperatureOffset(float *pOffset_degC)
+{
+ uint16_t value;
+ int error = readRegister(COMMAND_SCD30_TEMPERATURE_OFFSET, &value);
+ if (!error)
+ {
+ // result is in centi-degrees, need to convert to degrees
+ *pOffset_degC = (float) value / 100.0;
+ }
+ return (error);
+}
+
+int FrogmoreScd30::getTemperatureOffset(uint16_t *pOffset_centiDegC)
+{
+ uint16_t value;
+ int error = readRegister(COMMAND_SCD30_TEMPERATURE_OFFSET, &value);
+ if (!error)
+ {
+ // result is in centi-degrees, need to convert to degrees
+ *pOffset_centiDegC = value;
+ }
+ return (error);
+}
+
+int FrogmoreScd30::setAltitudeCompensation(uint16_t height_meter)
+{
+ return (sendCommandArguments(COMMAND_SCD30_ALTITUDE_COMPENSATION, height_meter));
+}
+
+int FrogmoreScd30::setAmbientPressure(uint16_t airPressure_mbar)
+{
+ ambientPressure = airPressure_mbar;
+ return (beginMeasuring(ambientPressure));
+}
+
+int FrogmoreScd30::setAutoSelfCalibration(void)
+{
+ bool isAuto = true;
+ return (setCalibrationType(isAuto));
+}
+
+int FrogmoreScd30::setCalibrationType(bool isAuto)
+{
+ bool value = !!isAuto; // using NOT operator twice makes sure value is 0 or 1
+ return (sendCommandArguments(COMMAND_SCD30_CALIBRATION_TYPE, value));
+}
+
+int FrogmoreScd30::setForcedRecalibrationFactor(uint16_t co2_ppm)
+{
+ return (sendCommandArguments(COMMAND_SCD30_FORCED_RECALIBRATION_FACTOR, co2_ppm));
+}
+
+int FrogmoreScd30::setManualCalibration(void)
+{
+ bool isAuto = false;
+ return (setCalibrationType(isAuto));
+}
+
+int FrogmoreScd30::setMeasurementInterval(uint16_t time_sec)
+{
+ if (time_sec < 2) time_sec = 2;
+ if (time_sec > 1800) time_sec = 1800;
+ return (sendCommandArguments(COMMAND_SCD30_MEASUREMENT_INTERVAL, time_sec));
+}
+
+int FrogmoreScd30::setTemperatureOffset(float offset_degC)
+{
+ uint16_t offset_centiDegC;
+ if (offset_degC >= 0)
+ {
+ offset_centiDegC = (uint16_t) offset_degC * 100;
+ return (sendCommandArguments(COMMAND_SCD30_TEMPERATURE_OFFSET, offset_centiDegC));
+ }
+ else
+ {
+ return (ERROR_SCD30_INVALID_VALUE);
+ }
+
+}
+
+int FrogmoreScd30::setTemperatureOffset(uint16_t offset_centiDegC)
+{
+ return (sendCommandArguments(COMMAND_SCD30_TEMPERATURE_OFFSET, offset_centiDegC));
+}
+
+int FrogmoreScd30::beginMeasuring(void)
+{
+ return (beginMeasuring(ambientPressure));
+}
+
+int FrogmoreScd30::beginMeasuring(uint16_t airPressure_mbar)
+{
+ ambientPressure = airPressure_mbar;
+ return(sendCommandArguments(COMMAND_SCD30_CONTINUOUS_MEASUREMENT, ambientPressure));
+}
+
+int FrogmoreScd30::isDataAvailable(bool *pIsAvailable)
+{
+ uint16_t isDataAvailable = false;
+ int error = readRegister(COMMAND_SCD30_GET_DATA_READY, &isDataAvailable);
+ if (!error)
+ {
+ *pIsAvailable = isDataAvailable != 0;
+ }
+ return (error);
+}
+
+int FrogmoreScd30::readMeasurement(
+ uint16 *pCO2_ppm,
+ uint16 *pCO2EAvg_ppm,
+ float *pTemperature,
+ float *pHumidity
+)
+{
+ bool isAvailable = false;
+ int error = 0;
+ float tempCO2;
+ float tempHumidity;
+ float tempTemperature;
+
+ error = isDataAvailable(&isAvailable);
+ if (error)
+ {
+ return (error);
+ }
+
+ if (!isAvailable)
+ {
+ return (ERROR_SCD30_NO_DATA);
+ }
+
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: have data");
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+
+ error = sendCommand(COMMAND_SCD30_READ_MEASUREMENT);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: send command failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+ delay(1); // the SCD30 uses clock streching to give it time to prepare data, waiting here makes it work
+
+ uint8_t bytes[SCD30_MEAS_BYTES];
+ // there are (6) 16-bit values, each with a CRC in the measurement data
+ // the chip does not seem to like sending this data, except all at once
+ error = getBytes(bytes, SCD30_MEAS_BYTES);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30GetBytes command failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30GetBytes data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X", bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8]);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30GetBytes data: 0x %02X %02X %02X | 0x %02X %02X %02X | 0x %02X %02X %02X", bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15], bytes[16], bytes[17]);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+
+ error = get32BitRegCheckCRC(&bytes[0], &tempCO2);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30Get32BitsCheckCRC 1st command failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+
+ error = get32BitRegCheckCRC(&bytes[6], &tempTemperature);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30Get32BitsCheckCRC 2nd command failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+
+ error = get32BitRegCheckCRC(&bytes[12], &tempHumidity);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: Scd30Get32BitsCheckCRC 3rd command failed: 0x%lX", error);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (error);
+ }
+
+ if (tempCO2 == 0)
+ {
+ return (ERROR_SCD30_CO2_ZERO);
+ }
+
+ if (co2NewDataLocation < 0)
+ {
+ co2EAverage = tempCO2;
+ for (int x = 0; x < SCD30_MEDIAN_FILTER_SIZE; x++)
+ {
+ co2History[x] = tempCO2;
+ co2NewDataLocation = 1;
+ }
+ }
+ else
+ {
+ co2History[co2NewDataLocation++] = tempCO2;
+ if (co2NewDataLocation >= SCD30_MEDIAN_FILTER_SIZE)
+ {
+ co2NewDataLocation = 0;
+ }
+ }
+
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: co2History: %ld, %ld, %ld, %ld, %ld", co2History[0], co2History[1], co2History[2], co2History[3], co2History[4]);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+ // copy array since the median filter function will re-arrange it
+ uint16_t temp[SCD30_MEDIAN_FILTER_SIZE];
+ for (int x = 0; x < SCD30_MEDIAN_FILTER_SIZE; x++)
+ {
+ temp[x] = co2History[x];
+ }
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: temp: %ld, %ld, %ld, %ld, %ld", temp[0], temp[1], temp[2], temp[3], temp[4]);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+
+ *pCO2_ppm = opt_med5(temp);
+#ifdef SCD30_DEBUG
+ snprintf_P(scd30log_data, sizeof(scd30log_data), "Scd30ReadMeasurement: CO2_ppm: %ld", *pCO2_ppm);
+ AddLog(LOG_LEVEL_DEBUG_MORE);
+#endif
+ if (pCO2EAvg_ppm)
+ {
+ int16_t delta = (int16_t) *pCO2_ppm - (int16_t) co2EAverage;
+ int16_t change = delta / 32;
+ co2EAverage += change;
+#if 0
+ uint16_t remain = co2EAverage % 5;
+ uint16_t dividend = co2EAverage / 5;
+ uint16_t co2EAReported = dividend * 5;
+ if (remain > 2)
+ {
+ co2EAReported += 5;
+ }
+ *pCO2EAvg_ppm = co2EAReported;
+#else
+ *pCO2EAvg_ppm = co2EAverage;
+#endif
+
+ }
+
+ *pTemperature = tempTemperature;
+ *pHumidity = tempHumidity;
+ return (ERROR_SCD30_NO_ERROR);
+}
+
+int FrogmoreScd30::stopMeasuring(void)
+{
+ return (sendCommand(COMMAND_SCD30_STOP_MEASUREMENT));
+}
+
diff --git a/lib/FrogmoreScd30/FrogmoreScd30.h b/lib/FrogmoreScd30/FrogmoreScd30.h
new file mode 100644
index 000000000..d1f2d1309
--- /dev/null
+++ b/lib/FrogmoreScd30/FrogmoreScd30.h
@@ -0,0 +1,105 @@
+/*
+# Copyright (c) 2019 Frogmore42
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+#pragma once
+
+#include "Arduino.h"
+
+//#define SCD30_DEBUG
+
+#define SCD30_ADDRESS 0x61
+#define ERROR_SCD30_NO_ERROR 0
+#define ERROR_SCD30_NO_DATA 0x80000000
+#define ERROR_SCD30_CO2_ZERO 0x90000000
+#define ERROR_SCD30_UNKNOWN_ERROR 0x1000000
+#define ERROR_SCD30_CRC_ERROR 0x2000000
+#define ERROR_SCD30_NOT_ENOUGH_BYTES_ERROR 0x3000000
+#define ERROR_SCD30_NOT_FOUND_ERROR 0x4000000
+#define ERROR_SCD30_NOT_A_NUMBER_ERROR 0x5000000
+#define ERROR_SCD30_INVALID_VALUE 0x6000000
+
+#define SCD30_MEDIAN_FILTER_SIZE 5
+
+class FrogmoreScd30
+{
+ public:
+ FrogmoreScd30() {};
+ // Constructors
+ // the SCD30 only lists a single i2c address, so not necesary to specify
+ //
+ void begin(void);
+ void begin(uint8_t _i2cAddress);
+ void begin(TwoWire *pWire);
+ void begin(TwoWire *pWire, uint8_t _i2cAddress);
+
+ int softReset(void);
+ int clearI2CBus(void); // this is a HARD reset of the IC2 bus to restore communication, it will disrupt the bus
+
+ int getAltitudeCompensation(uint16_t *pHeight_meter);
+ int getAmbientPressure(uint16_t *pAirPressure_mbar);
+ int getCalibrationType(uint16_t *pIsAuto);
+ int getFirmwareVersion(uint8_t *pMajor, uint8_t *pMinor);
+ int getForcedRecalibrationFactor(uint16_t *pCo2_ppm);
+ int getMeasurementInterval(uint16_t *pTime_sec);
+ int getTemperatureOffset(float *pOffset_degC);
+ int getTemperatureOffset(uint16_t *pOffset_centiDegC);
+
+ int setAltitudeCompensation(uint16_t height_meter);
+ int setAmbientPressure(uint16_t airPressure_mbar);
+ int setAutoSelfCalibration(void);
+ int setCalibrationType(bool isAuto);
+ int setForcedRecalibrationFactor(uint16_t co2_ppm);
+ int setManualCalibration(void);
+ int setMeasurementInterval(uint16_t time_sec);
+ int setTemperatureOffset(float offset_degC);
+ int setTemperatureOffset(uint16_t offset_centiDegC);
+
+ int beginMeasuring(void);
+ int beginMeasuring(uint16_t airPressure_mbar); // also sets ambient pressure offset in mbar/hPascal
+ int isDataAvailable(bool *pIsAvailable);
+ int readMeasurement(
+ uint16 *pCO2_ppm,
+ uint16 *pCO2EAvg_ppm,
+ float *pTemperature,
+ float *pHumidity
+ );
+ int stopMeasuring(void);
+
+ private:
+ uint8_t i2cAddress;
+ TwoWire *pWire;
+ uint16_t ambientPressure;
+ uint16_t co2AvgExtra;
+ uint16_t co2History[SCD30_MEDIAN_FILTER_SIZE];
+ uint16_t co2EAverage;
+ int8_t co2NewDataLocation; // location to put new CO2 data for median filter
+
+ uint8_t computeCRC8(uint8_t data[], uint8_t len);
+ int sendBytes(void *pInput, uint8_t len);
+ int getBytes(void *pOutput, uint8_t len);
+ int sendCommand(uint16_t command);
+ int sendCommandArguments(uint16_t command, uint16_t arguments);
+ int get16BitRegCheckCRC(void* pInput, uint16_t* pData);
+ int get32BitRegCheckCRC(void* pInput, float* pData);
+ int readRegister(uint16_t registerAddress, uint16_t* pData);
+#ifdef SCD30_DEBUG
+ void AddLog(uint8_t loglevel);
+#endif
+};
\ No newline at end of file
diff --git a/sonoff/i18n.h b/sonoff/i18n.h
index e0b3af57b..73e228124 100644
--- a/sonoff/i18n.h
+++ b/sonoff/i18n.h
@@ -568,9 +568,13 @@ const char HTTP_SNS_SEAPRESSURE[] PROGMEM = "%s{s}%s " D_PRESSUREATSEALEVEL "{m}
const char HTTP_SNS_ANALOG[] PROGMEM = "%s{s}%s " D_ANALOG_INPUT "%d{m}%d{e}"; // {s} = , {m} = | , {e} = |
const char HTTP_SNS_ILLUMINANCE[] PROGMEM = "%s{s}%s " D_ILLUMINANCE "{m}%d " D_UNIT_LUX "{e}"; // {s} = , {m} = | , {e} = |
-#if defined(USE_MHZ19) || defined(USE_SENSEAIR) || defined(USE_AZ7798)
+#if defined(USE_MHZ19) || defined(USE_SENSEAIR) || defined(USE_AZ7798) || defined(USE_SCD30)
const char HTTP_SNS_CO2[] PROGMEM = "%s{s}%s " D_CO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; // {s} = , {m} = | , {e} = |
-#endif // USE_WEBSERVER
+#endif // USE_MHZ19
+
+#if defined(USE_SCD30)
+const char HTTP_SNS_CO2EAVG[] PROGMEM = "%s{s}%s " D_ECO2 "{m}%d " D_UNIT_PARTS_PER_MILLION "{e}"; // {s} = , {m} = | , {e} = |
+#endif // USE_SCD30
const char S_MAIN_MENU[] PROGMEM = D_MAIN_MENU;
const char S_CONFIGURATION[] PROGMEM = D_CONFIGURATION;
diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h
index 06bd3688b..b114c9c3d 100644
--- a/sonoff/sonoff_post.h
+++ b/sonoff/sonoff_post.h
@@ -95,6 +95,7 @@ void KNX_CB_Action(message_t const &msg, void *arg);
//#define USE_MAX44009 // Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code)
#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
+#define USE_SCD30 // Add support for Sensiron SCd30 CO2 sensor (+3k6 code)
#ifndef CO2_LOW
#define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#endif
diff --git a/sonoff/xdrv_92_scd30.ino b/sonoff/xdrv_92_scd30.ino
new file mode 100644
index 000000000..80743ebb0
--- /dev/null
+++ b/sonoff/xdrv_92_scd30.ino
@@ -0,0 +1,505 @@
+/*
+ xdrv_92_scd30.ino - SC30 CO2 sensor support for Sonoff-Tasmota
+
+ Copyright (C) 2019 Frogmore42
+
+ 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_SCD30
+
+#define XDRV_92 92
+#define XSNS_92 92
+#define SCD30_MAX_MISSED_READS 3
+#define SONOFF_SCD30_STATE_NO_ERROR 0
+#define SONOFF_SCD30_STATE_ERROR_DATA_CRC 1
+#define SONOFF_SCD30_STATE_ERROR_READ_MEAS 2
+#define SONOFF_SCD30_STATE_ERROR_SOFT_RESET 3
+#define SONOFF_SCD30_STATE_ERROR_I2C_RESET 4
+#define SONOFF_SCD30_STATE_ERROR_UNKNOWN 5
+
+#include "Arduino.h"
+#include
+
+#define D_CMND_SCD30 "SCD30"
+
+const char S_JSON_SCD30_COMMAND_NVALUE[] PROGMEM = "{\"" D_CMND_SCD30 "%s\":%d}";
+const char S_JSON_SCD30_COMMAND_NFW_VALUE[] PROGMEM = "{\"" D_CMND_SCD30 "%s\":%d.%d}";
+const char S_JSON_SCD30_COMMAND[] PROGMEM = "{\"" D_CMND_SCD30 "%s\"}";
+const char kSCD30_Commands[] PROGMEM = "Alt|Auto|Cal|FW|Int|Pres|TOff";
+
+/*********************************************************************************************\
+ * enumerationsines
+\*********************************************************************************************/
+
+enum SCD30_Commands { // commands useable in console or rules
+ CMND_SCD30_ALTITUDE,
+ CMND_SCD30_AUTOMODE,
+ CMND_SCD30_CALIBRATE,
+ CMND_SCD30_FW,
+ CMND_SCD30_INTERVAL,
+ CMND_SCD30_PRESSURE,
+ CMND_SCD30_TEMPOFFSET
+};
+
+
+
+FrogmoreScd30 scd30;
+
+bool scd30Found = false;
+bool scd30IsDataValid = false;
+int scd30ErrorState = SONOFF_SCD30_STATE_NO_ERROR;
+uint16_t scd30Interval_sec;
+int scd30Loop_count = 0;
+int scd30DataNotAvailable_count = 0;
+int scd30GoodMeas_count = 0;
+int scd30Reset_count = 0;
+int scd30CrcError_count = 0;
+int scd30Co2Zero_count = 0;
+int i2cReset_count = 0;
+uint16_t scd30_CO2 = 0;
+uint16_t scd30_CO2EAvg = 0;
+float scd30_Humid = 0.0;
+float scd30_Temp = 0.0;
+
+bool Scd30Init()
+{
+ int error;
+ bool i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99));
+ if (i2c_flg)
+ {
+ uint8_t major = 0;
+ uint8_t minor = 0;
+ uint16_t interval_sec;
+ scd30.begin();
+ error = scd30.getFirmwareVersion(&major, &minor);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: did not find an SCD30: 0x%lX", error);
+ AddLog(LOG_LEVEL_DEBUG);
+#endif
+ return false;
+ }
+ else
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: found an SCD30: FW v%d.%d", major, minor);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ }
+
+ error = scd30.getMeasurementInterval(&scd30Interval_sec);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: error getMeasurementInterval: 0x%lX", error);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ return false;
+ }
+
+ error = scd30.beginMeasuring();
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "Error: Scd30BeginMeasuring: 0x%lX", error);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ return false;
+ }
+
+ return true;
+ }
+}
+
+// gets data from the sensor every 3 seconds or so to give the sensor time to gather new data
+int Scd30Update()
+{
+ int error = 0;
+ int16_t delta = 0;
+ scd30Loop_count++;
+
+ if (!scd30Found)
+ {
+ scd30Found = Scd30Init();
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "Scd30Update: found: %d ", scd30Found);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ if (!scd30Found)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "Scd30Update: found: %d ", scd30Found);
+ AddLog(LOG_LEVEL_INFO);
+#endif
+ return (ERROR_SCD30_NOT_FOUND_ERROR);
+ }
+ }
+ else
+ {
+ if (scd30Loop_count > (scd30Interval_sec - 1))
+ {
+ switch (scd30ErrorState)
+ {
+ case SONOFF_SCD30_STATE_NO_ERROR:
+ {
+ error = scd30.readMeasurement(&scd30_CO2, &scd30_CO2EAvg, &scd30_Temp, &scd30_Humid);
+ switch (error)
+ {
+ case ERROR_SCD30_NO_ERROR:
+ scd30Loop_count = 0;
+ scd30IsDataValid = true;
+ scd30GoodMeas_count++;
+ break;
+
+ case ERROR_SCD30_NO_DATA:
+ scd30DataNotAvailable_count++;
+ break;
+
+ case ERROR_SCD30_CRC_ERROR:
+ scd30ErrorState = SONOFF_SCD30_STATE_ERROR_DATA_CRC;
+ scd30CrcError_count++;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: CRC error, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld", scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ break;
+
+ case ERROR_SCD30_CO2_ZERO:
+ scd30Co2Zero_count++;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: CO2 zero, CRC error: %ld, CO2 zero: %ld, good: %ld, no data: %ld, sc30_reset: %ld, i2c_reset: %ld", scd30CrcError_count, scd30Co2Zero_count, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ break;
+
+ default:
+ {
+ scd30ErrorState = SONOFF_SCD30_STATE_ERROR_READ_MEAS;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: Update: ReadMeasurement error: 0x%lX, counter: %ld", error, scd30Loop_count);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ return (error);
+ }
+ break;
+ }
+ }
+ break;
+
+ case SONOFF_SCD30_STATE_ERROR_DATA_CRC:
+ {
+ //scd30IsDataValid = false;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog(LOG_LEVEL_ERROR);
+ snprintf_P(log_data, sizeof(log_data), "SCD30: got CRC error, try again, counter: %ld", scd30Loop_count);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ scd30ErrorState = ERROR_SCD30_NO_ERROR;
+ }
+ break;
+
+ case SONOFF_SCD30_STATE_ERROR_READ_MEAS:
+ {
+ //scd30IsDataValid = false;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog(LOG_LEVEL_ERROR);
+ snprintf_P(log_data, sizeof(log_data), "SCD30: not answering, sending soft reset, counter: %ld", scd30Loop_count);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ scd30Reset_count++;
+ error = scd30.softReset();
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: resetting got error: 0x%lX", error);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ error >>= 8;
+ if (error == 4)
+ {
+ scd30ErrorState = SONOFF_SCD30_STATE_ERROR_SOFT_RESET;
+ }
+ else
+ {
+ scd30ErrorState = SONOFF_SCD30_STATE_ERROR_UNKNOWN;
+ }
+ }
+ else
+ {
+ scd30ErrorState = ERROR_SCD30_NO_ERROR;
+ }
+ }
+ break;
+
+ case SONOFF_SCD30_STATE_ERROR_SOFT_RESET:
+ {
+ //scd30IsDataValid = false;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: in error state: %d, good: %ld, no data: %ld, sc30 reset: %ld, i2c reset: %ld", scd30ErrorState, scd30GoodMeas_count, scd30DataNotAvailable_count, scd30Reset_count, i2cReset_count);
+ AddLog(LOG_LEVEL_ERROR);
+ snprintf_P(log_data, sizeof(log_data), "SCD30: clearing i2c bus");
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ i2cReset_count++;
+ error = scd30.clearI2CBus();
+ if (error)
+ {
+ scd30ErrorState = SONOFF_SCD30_STATE_ERROR_I2C_RESET;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: error clearing i2c bus: 0x%lX", error);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ }
+ else
+ {
+ scd30ErrorState = ERROR_SCD30_NO_ERROR;
+ }
+ }
+ break;
+
+ default:
+ {
+ //scd30IsDataValid = false;
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: unknown error state: 0x%lX", scd30ErrorState);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ scd30ErrorState = SONOFF_SCD30_STATE_ERROR_SOFT_RESET; // try again
+ }
+ }
+
+ if (scd30Loop_count > (SCD30_MAX_MISSED_READS * scd30Interval_sec))
+ {
+ scd30IsDataValid = false;
+ }
+ }
+ }
+ return (ERROR_SCD30_NO_ERROR);
+}
+
+
+int Scd30GetCommand(int command_code, uint16_t *pvalue)
+{
+ switch (command_code)
+ {
+ case CMND_SCD30_ALTITUDE:
+ return scd30.getAltitudeCompensation(pvalue);
+ break;
+
+ case CMND_SCD30_AUTOMODE:
+ return scd30.getCalibrationType(pvalue);
+ break;
+
+ case CMND_SCD30_CALIBRATE:
+ return scd30.getForcedRecalibrationFactor(pvalue);
+ break;
+
+ case CMND_SCD30_INTERVAL:
+ return scd30.getMeasurementInterval(pvalue);
+ break;
+
+ case CMND_SCD30_PRESSURE:
+ return scd30.getAmbientPressure(pvalue);
+ break;
+
+ case CMND_SCD30_TEMPOFFSET:
+ return scd30.getTemperatureOffset(pvalue);
+ break;
+
+ default:
+ // else for Unknown command
+ break;
+ }
+}
+
+int Scd30SetCommand(int command_code, uint16_t value)
+{
+ switch (command_code)
+ {
+ case CMND_SCD30_ALTITUDE:
+ return scd30.setAltitudeCompensation(value);
+ break;
+
+ case CMND_SCD30_AUTOMODE:
+ return scd30.setCalibrationType(value);
+ break;
+
+ case CMND_SCD30_CALIBRATE:
+ return scd30.setForcedRecalibrationFactor(value);
+ break;
+
+ case CMND_SCD30_INTERVAL:
+ {
+ int error = scd30.setMeasurementInterval(value);
+ if (!error)
+ {
+ scd30Interval_sec = value;
+ }
+
+ return error;
+ }
+ break;
+
+ case CMND_SCD30_PRESSURE:
+ return scd30.setAmbientPressure(value);
+ break;
+
+ case CMND_SCD30_TEMPOFFSET:
+ return scd30.setTemperatureOffset(value);
+ break;
+
+ default:
+ // else for Unknown command
+ break;
+ }
+}
+/*********************************************************************************************\
+ * Command Sensor92
+\*********************************************************************************************/
+
+bool Scd30CommandSensor()
+{
+ char command[CMDSZ];
+ bool serviced = true;
+ uint8_t prefix_len = strlen(D_CMND_SCD30);
+
+ if (!strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_SCD30), prefix_len)) { // prefix
+ int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + prefix_len, kSCD30_Commands);
+
+ switch (command_code) {
+ case CMND_SCD30_ALTITUDE:
+ case CMND_SCD30_AUTOMODE:
+ case CMND_SCD30_CALIBRATE:
+ case CMND_SCD30_INTERVAL:
+ case CMND_SCD30_PRESSURE:
+ case CMND_SCD30_TEMPOFFSET:
+ {
+ uint16_t value = 0;
+ if (XdrvMailbox.data_len > 0)
+ {
+ value = XdrvMailbox.payload16;
+ Scd30SetCommand(command_code, value);
+ }
+ else
+ {
+ Scd30GetCommand(command_code, &value);
+ }
+
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_SCD30_COMMAND_NVALUE, command, value);
+ }
+ break;
+
+ case CMND_SCD30_FW:
+ {
+ uint8_t major = 0;
+ uint8_t minor = 0;
+ int error;
+ error = scd30.getFirmwareVersion(&major, &minor);
+ if (error)
+ {
+#ifdef SCD30_DEBUG
+ snprintf_P(log_data, sizeof(log_data), "SCD30: error getting FW version: 0x%lX", error);
+ AddLog(LOG_LEVEL_ERROR);
+#endif
+ serviced = false;
+ }
+ else
+ {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_SCD30_COMMAND_NFW_VALUE, command, major, minor);
+ }
+ }
+ break;
+
+ default:
+ // else for Unknown command
+ serviced = false;
+ break;
+ }
+ }
+ return serviced;
+}
+
+void Scd30Show(bool json)
+{
+ char humidity[10];
+ char temperature[10];
+
+ if (scd30Found && scd30IsDataValid)
+ {
+ dtostrfd(scd30_Humid, Settings.flag2.humidity_resolution, humidity);
+ dtostrfd(ConvertTemp(scd30_Temp), Settings.flag2.temperature_resolution, temperature);
+ if (json) {
+ //snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"SCD30\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"), mqtt_data, scd30_CO2, temperature, humidity);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"SCD30\":{\"" D_JSON_CO2 "\":%d,\"" D_JSON_ECO2 "\":%d,\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_HUMIDITY "\":%s}"), mqtt_data, scd30_CO2, scd30_CO2EAvg, temperature, humidity);
+#ifdef USE_DOMOTICZ
+ if (0 == tele_period) DomoticzSensor(DZ_AIRQUALITY, scd30_CO2);
+#endif // USE_DOMOTICZ
+#ifdef USE_WEBSERVER
+ } else {
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_CO2EAVG, mqtt_data, "SCD30", scd30_CO2EAvg);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_CO2, mqtt_data, "SCD30", scd30_CO2);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, "SCD30", temperature, TempUnit());
+ snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_HUM, mqtt_data, "SCD30", humidity);
+#endif // USE_WEBSERVER
+ }
+ }
+}
+
+/*********************************************************************************************\
+ * Interface
+\*********************************************************************************************/
+
+bool Xdrv92(byte function)
+{
+ bool result = false;
+
+ if (i2c_flg) {
+ switch (function) {
+ case FUNC_COMMAND:
+ result = Scd30CommandSensor();
+ break;
+ }
+ }
+ return result;
+}
+
+bool Xsns92(byte function)
+{
+ bool result = false;
+
+ if (i2c_flg) {
+ switch (function) {
+ case FUNC_EVERY_SECOND:
+ Scd30Update();
+ break;
+ case FUNC_JSON_APPEND:
+ Scd30Show(1);
+ break;
+#ifdef USE_WEBSERVER
+ case FUNC_WEB_APPEND:
+ Scd30Show(0);
+ break;
+#endif // USE_WEBSERVER
+ }
+ }
+ return result;
+}
+
+#endif // USE_SCD30
+#endif // USE_I2C
From 37e1b31937cc8dbb3971453e2bfb2a29a22a2b27 Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 10 Mar 2019 13:33:32 +0100
Subject: [PATCH 02/22] Add support for sensor SCD30
* Add support for sensor SCD30 (#5434)
* Add support for commands in sensor drivers
---
API.md | 2 ++
sonoff/_changelog.ino | 2 ++
sonoff/my_user_config.h | 1 +
sonoff/sonoff.h | 2 +-
sonoff/sonoff.ino | 18 ++++++++++---
sonoff/sonoff_post.h | 4 +--
sonoff/xdrv_interface.ino | 15 +----------
sonoff/xsns_13_ina219.ino | 2 +-
sonoff/xsns_15_mhz19.ino | 2 +-
sonoff/xsns_27_apds9960.ino | 2 +-
sonoff/xsns_29_mcp230xx.ino | 2 +-
sonoff/xsns_34_hx711.ino | 2 +-
sonoff/xsns_36_mgc3130.ino | 2 +-
sonoff/xsns_40_pn532.ino | 2 +-
.../{xdrv_92_scd30.ino => xsns_42_scd30.ino} | 27 ++++++-------------
sonoff/xsns_interface.ino | 4 ++-
16 files changed, 41 insertions(+), 48 deletions(-)
rename sonoff/{xdrv_92_scd30.ino => xsns_42_scd30.ino} (98%)
diff --git a/API.md b/API.md
index 23e45369d..caec908c6 100644
--- a/API.md
+++ b/API.md
@@ -23,6 +23,8 @@ FUNC_JSON_APPEND | | | | x | | Extend tele
FUNC_WEB_APPEND | | | | x | | Extend webgui ajax info
FUNC_SAVE_BEFORE_RESTART | | | | x | | Just before a planned restart
FUNC_COMMAND | x | | x | x | | When a command is not recognized
+FUNC_COMMAND_DRIVER | x | 6.4.1.21 | x | | | When command Driver\ is executed
+FUNC_COMMAND_SENSOR | x | 6.4.1.21 | | x | | When command Sensor\ is executed
FUNC_MQTT_SUBSCRIBE | | 5.12.0k | x | | | At end of MQTT subscriptions
FUNC_MQTT_INIT | | 5.12.0k | x | | | Once at end of MQTT connection
FUNC_MQTT_DATA | x | 5.12.0k | x | | | Before decoding command
diff --git a/sonoff/_changelog.ino b/sonoff/_changelog.ino
index 7fcc80b8d..f0112b908 100644
--- a/sonoff/_changelog.ino
+++ b/sonoff/_changelog.ino
@@ -1,5 +1,7 @@
/* 6.4.1.21 20190309
* Fix exception on GUI Configure Logging and Configure Other (#5424)
+ * Add support for sensor SCD30 (#5434)
+ * Add support for commands in sensor drivers
*
* 6.4.1.20 20190304
* Changed webserver content handling from single String to small Chunks increasing RAM
diff --git a/sonoff/my_user_config.h b/sonoff/my_user_config.h
index b5c52ff81..3030654ce 100644
--- a/sonoff/my_user_config.h
+++ b/sonoff/my_user_config.h
@@ -331,6 +331,7 @@
// #define USE_RTC_ADDR 0x68 // Default I2C address 0x68
// #define USE_MGC3130 // Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem)
// #define USE_MAX44009 // Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code)
+// #define USE_SCD30 // Enable Sensiron SCd30 CO2 sensor (I2C address 0x61) (+3k3 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/sonoff/sonoff.h b/sonoff/sonoff.h
index 951c7f6d2..6c44f54fd 100644
--- a/sonoff/sonoff.h
+++ b/sonoff/sonoff.h
@@ -252,7 +252,7 @@ enum LightSchemes {LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MA
enum XsnsFunctions {FUNC_SETTINGS_OVERRIDE, FUNC_MODULE_INIT, FUNC_PRE_INIT, FUNC_INIT,
FUNC_LOOP, FUNC_EVERY_50_MSECOND, FUNC_EVERY_100_MSECOND, FUNC_EVERY_200_MSECOND, FUNC_EVERY_250_MSECOND, FUNC_EVERY_SECOND,
- FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART, FUNC_COMMAND,
+ FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART, FUNC_COMMAND, FUNC_COMMAND_SENSOR, FUNC_COMMAND_DRIVER,
FUNC_MQTT_SUBSCRIBE, FUNC_MQTT_INIT, FUNC_MQTT_DATA,
FUNC_SET_POWER, FUNC_SET_DEVICE_POWER, FUNC_SHOW_SENSOR,
FUNC_RULES_PROCESS, FUNC_SERIAL, FUNC_FREE_MEM, FUNC_BUTTON_PRESSED,
diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino
index 0f403312e..57e4f62df 100755
--- a/sonoff/sonoff.ino
+++ b/sonoff/sonoff.ino
@@ -527,8 +527,18 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len)
int command_code = GetCommandCode(command, sizeof(command), type, kTasmotaCommands);
if (-1 == command_code) {
- if (!XdrvCommand(grpflg, type, index, dataBuf, data_len, payload, payload16)) {
- type = NULL; // Unknown command
+// XdrvMailbox.valid = 1;
+ XdrvMailbox.index = index;
+ XdrvMailbox.data_len = data_len;
+ XdrvMailbox.payload16 = payload16;
+ XdrvMailbox.payload = payload;
+ XdrvMailbox.grpflg = grpflg;
+ XdrvMailbox.topic = type;
+ XdrvMailbox.data = dataBuf;
+ if (!XdrvCall(FUNC_COMMAND)) {
+ if (!XsnsCall(FUNC_COMMAND)) {
+ type = NULL; // Unknown command
+ }
}
}
else if (CMND_BACKLOG == command_code) {
@@ -711,9 +721,9 @@ void MqttDataHandler(char* topic, uint8_t* data, unsigned int data_len)
XdrvMailbox.topic = command;
XdrvMailbox.data = dataBuf;
if (CMND_SENSOR == command_code) {
- XsnsCall(FUNC_COMMAND);
+ XsnsCall(FUNC_COMMAND_SENSOR);
} else {
- XdrvCall(FUNC_COMMAND);
+ XdrvCall(FUNC_COMMAND_DRIVER);
}
}
else if ((CMND_SETOPTION == command_code) && (index < 82)) {
diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h
index b114c9c3d..fe5c5c08d 100644
--- a/sonoff/sonoff_post.h
+++ b/sonoff/sonoff_post.h
@@ -92,10 +92,10 @@ void KNX_CB_Action(message_t const &msg, void *arg);
//#define USE_MPU6050 // Enable MPU6050 sensor (I2C address 0x68 AD0 low or 0x69 AD0 high) (+2k6 code)
//#define USE_DS3231 // Enable DS3231 external RTC in case no Wifi is avaliable. See docs in the source file (+1k2 code)
//#define USE_MGC3130 // Enable MGC3130 Electric Field Effect Sensor (I2C address 0x42) (+2k7 code, 0k3 mem)
-//#define USE_MAX44009 // Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code)
+//#define USE_MAX44009 // Enable MAX44009 Ambient Light sensor (I2C addresses 0x4A and 0x4B) (+0k8 code)
+#define USE_SCD30 // Enable Sensiron SCd30 CO2 sensor (I2C address 0x61) (+3k3 code)
#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+2k code)
#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+2k3 code)
-#define USE_SCD30 // Add support for Sensiron SCd30 CO2 sensor (+3k6 code)
#ifndef CO2_LOW
#define CO2_LOW 800 // Below this CO2 value show green light (needs PWM or WS2812 RG(B) led and enable with SetOption18 1)
#endif
diff --git a/sonoff/xdrv_interface.ino b/sonoff/xdrv_interface.ino
index 7c3a31c25..7afea8531 100644
--- a/sonoff/xdrv_interface.ino
+++ b/sonoff/xdrv_interface.ino
@@ -192,20 +192,6 @@ bool (* const xdrv_func_ptr[])(uint8_t) = { // Driver Function Pointers
const uint8_t xdrv_present = sizeof(xdrv_func_ptr) / sizeof(xdrv_func_ptr[0]); // Number of drivers found
-bool XdrvCommand(bool grpflg, char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload, uint16_t payload16)
-{
-// XdrvMailbox.valid = 1;
- XdrvMailbox.index = index;
- XdrvMailbox.data_len = data_len;
- XdrvMailbox.payload16 = payload16;
- XdrvMailbox.payload = payload;
- XdrvMailbox.grpflg = grpflg;
- XdrvMailbox.topic = type;
- XdrvMailbox.data = dataBuf;
-
- return XdrvCall(FUNC_COMMAND);
-}
-
bool XdrvMqttData(char *topicBuf, uint16_t stopicBuf, char *dataBuf, uint16_t sdataBuf)
{
XdrvMailbox.index = stopicBuf;
@@ -242,6 +228,7 @@ bool XdrvCall(uint8_t Function)
result = xdrv_func_ptr[x](Function);
if (result && ((FUNC_COMMAND == Function) ||
+ (FUNC_COMMAND_DRIVER == Function) ||
(FUNC_MQTT_DATA == Function) ||
(FUNC_RULES_PROCESS == Function) ||
(FUNC_BUTTON_PRESSED == Function) ||
diff --git a/sonoff/xsns_13_ina219.ino b/sonoff/xsns_13_ina219.ino
index dcf93867b..88f6ce240 100644
--- a/sonoff/xsns_13_ina219.ino
+++ b/sonoff/xsns_13_ina219.ino
@@ -266,7 +266,7 @@ bool Xsns13(uint8_t function)
if (i2c_flg) {
switch (function) {
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if ((XSNS_13 == XdrvMailbox.index) && (ina219_type)) {
result = Ina219CommandSensor();
}
diff --git a/sonoff/xsns_15_mhz19.ino b/sonoff/xsns_15_mhz19.ino
index 019a4dc6f..1e21aa6fb 100644
--- a/sonoff/xsns_15_mhz19.ino
+++ b/sonoff/xsns_15_mhz19.ino
@@ -373,7 +373,7 @@ bool Xsns15(uint8_t function)
case FUNC_EVERY_SECOND:
MhzEverySecond();
break;
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if (XSNS_15 == XdrvMailbox.index) {
result = MhzCommandSensor();
}
diff --git a/sonoff/xsns_27_apds9960.ino b/sonoff/xsns_27_apds9960.ino
index ac1b7a947..765c89263 100644
--- a/sonoff/xsns_27_apds9960.ino
+++ b/sonoff/xsns_27_apds9960.ino
@@ -2046,7 +2046,7 @@ bool Xsns27(uint8_t function)
case FUNC_EVERY_50_MSECOND:
APDS9960_loop();
break;
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if (XSNS_27 == XdrvMailbox.index) {
result = APDS9960CommandSensor();
}
diff --git a/sonoff/xsns_29_mcp230xx.ino b/sonoff/xsns_29_mcp230xx.ino
index b72afb66a..d7aaaf2ef 100644
--- a/sonoff/xsns_29_mcp230xx.ino
+++ b/sonoff/xsns_29_mcp230xx.ino
@@ -814,7 +814,7 @@ bool Xsns29(uint8_t function)
case FUNC_JSON_APPEND:
MCP230xx_Show(1);
break;
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if (XSNS_29 == XdrvMailbox.index) {
result = MCP230xx_Command();
}
diff --git a/sonoff/xsns_34_hx711.ino b/sonoff/xsns_34_hx711.ino
index 15f9f37db..349dcee63 100644
--- a/sonoff/xsns_34_hx711.ino
+++ b/sonoff/xsns_34_hx711.ino
@@ -476,7 +476,7 @@ bool Xsns34(uint8_t function)
case FUNC_EVERY_100_MSECOND:
HxEvery100mSecond();
break;
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if (XSNS_34 == XdrvMailbox.index) {
result = HxCommand();
}
diff --git a/sonoff/xsns_36_mgc3130.ino b/sonoff/xsns_36_mgc3130.ino
index 20df0a223..5c1d2df48 100644
--- a/sonoff/xsns_36_mgc3130.ino
+++ b/sonoff/xsns_36_mgc3130.ino
@@ -613,7 +613,7 @@ bool Xsns36(uint8_t function)
case FUNC_EVERY_50_MSECOND:
MGC3130_loop();
break;
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if (XSNS_36 == XdrvMailbox.index) {
result = MGC3130CommandSensor();
}
diff --git a/sonoff/xsns_40_pn532.ino b/sonoff/xsns_40_pn532.ino
index ea88143e3..0bfa8bda1 100644
--- a/sonoff/xsns_40_pn532.ino
+++ b/sonoff/xsns_40_pn532.ino
@@ -593,7 +593,7 @@ bool Xsns40(uint8_t function)
case FUNC_EVERY_SECOND:
break;
#ifdef USE_PN532_DATA_FUNCTION
- case FUNC_COMMAND:
+ case FUNC_COMMAND_SENSOR:
if (XSNS_40 == XdrvMailbox.index) {
result = PN532_Command();
}
diff --git a/sonoff/xdrv_92_scd30.ino b/sonoff/xsns_42_scd30.ino
similarity index 98%
rename from sonoff/xdrv_92_scd30.ino
rename to sonoff/xsns_42_scd30.ino
index 80743ebb0..c62df4d25 100644
--- a/sonoff/xdrv_92_scd30.ino
+++ b/sonoff/xsns_42_scd30.ino
@@ -1,5 +1,5 @@
/*
- xdrv_92_scd30.ino - SC30 CO2 sensor support for Sonoff-Tasmota
+ xsns_42_scd30.ino - SC30 CO2 sensor support for Sonoff-Tasmota
Copyright (C) 2019 Frogmore42
@@ -20,8 +20,8 @@
#ifdef USE_I2C
#ifdef USE_SCD30
-#define XDRV_92 92
-#define XSNS_92 92
+#define XSNS_42 42
+
#define SCD30_MAX_MISSED_READS 3
#define SONOFF_SCD30_STATE_NO_ERROR 0
#define SONOFF_SCD30_STATE_ERROR_DATA_CRC 1
@@ -224,7 +224,7 @@ int Scd30Update()
#endif
scd30Reset_count++;
error = scd30.softReset();
- if (error)
+ if (error)
{
#ifdef SCD30_DEBUG
snprintf_P(log_data, sizeof(log_data), "SCD30: resetting got error: 0x%lX", error);
@@ -465,21 +465,7 @@ void Scd30Show(bool json)
* Interface
\*********************************************************************************************/
-bool Xdrv92(byte function)
-{
- bool result = false;
-
- if (i2c_flg) {
- switch (function) {
- case FUNC_COMMAND:
- result = Scd30CommandSensor();
- break;
- }
- }
- return result;
-}
-
-bool Xsns92(byte function)
+bool Xsns42(byte function)
{
bool result = false;
@@ -488,6 +474,9 @@ bool Xsns92(byte function)
case FUNC_EVERY_SECOND:
Scd30Update();
break;
+ case FUNC_COMMAND:
+ result = Scd30CommandSensor();
+ break;
case FUNC_JSON_APPEND:
Scd30Show(1);
break;
diff --git a/sonoff/xsns_interface.ino b/sonoff/xsns_interface.ino
index 5e77b0d46..fdb947bfb 100644
--- a/sonoff/xsns_interface.ino
+++ b/sonoff/xsns_interface.ino
@@ -310,7 +310,9 @@ bool XsnsCall(uint8_t Function)
}
#endif // PROFILE_XSNS_SENSOR_EVERY_SECOND
- if (result && (FUNC_COMMAND == Function)) {
+ if (result && ((FUNC_COMMAND == Function) ||
+ (FUNC_COMMAND_SENSOR == Function)
+ )) {
break;
}
#ifdef USE_DEBUG_DRIVER
From d0ac200a787444f53ad65ea2c14338ab4fdb8bbb Mon Sep 17 00:00:00 2001
From: Theo Arends <11044339+arendst@users.noreply.github.com>
Date: Sun, 10 Mar 2019 15:36:34 +0100
Subject: [PATCH 03/22] Replace webserver flash string to char
Replace webserver flash string to char
---
sonoff/xdrv_01_webserver.ino | 311 ++++++++++++++++++-----------------
sonoff/xdrv_02_mqtt.ino | 8 +-
sonoff/xdrv_07_domoticz.ino | 12 +-
sonoff/xdrv_09_timers.ino | 28 ++--
sonoff/xdrv_11_knx.ino | 66 ++++----
sonoff/xsns_34_hx711.ino | 10 +-
6 files changed, 219 insertions(+), 216 deletions(-)
diff --git a/sonoff/xdrv_01_webserver.ino b/sonoff/xdrv_01_webserver.ino
index 2dbeeaa0d..7c69405ad 100644
--- a/sonoff/xdrv_01_webserver.ino
+++ b/sonoff/xdrv_01_webserver.ino
@@ -241,14 +241,14 @@ const char HTTP_HEAD_STYLE1[] PROGMEM =
"div,fieldset,input,select{padding:5px;font-size:1em;}"
"fieldset{background-color:#f2f2f2;}" // Also update HTTP_TIMER_STYLE
"p{margin:0.5em 0;}"
- "input{width:100%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;}"
+ "input{width:100%%;box-sizing:border-box;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;}"
"input[type=checkbox],input[type=radio]{width:1em;margin-right:6px;vertical-align:-1px;}"
- "select{width:100%;}"
- "textarea{resize:none;width:98%;height:318px;padding:5px;overflow:auto;}"
+ "select{width:100%%;}"
+ "textarea{resize:none;width:98%%;height:318px;padding:5px;overflow:auto;}"
"body{text-align:center;font-family:verdana;}"
"td{padding:0px;}";
const char HTTP_HEAD_STYLE2[] PROGMEM =
- "button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}"
+ "button{border:0;border-radius:0.3rem;background-color:#1fa3ec;color:#fff;line-height:2.4rem;font-size:1.2rem;width:100%%;-webkit-transition-duration:0.4s;transition-duration:0.4s;cursor:pointer;}"
"button:hover{background-color:#0e70a4;}"
".bred{background-color:#d43535;}"
".bred:hover{background-color:#931f1f;}"
@@ -403,7 +403,7 @@ const char HTTP_FORM_CMND[] PROGMEM =
"";
const char HTTP_TABLE100[] PROGMEM =
- "";
+ "";
const char HTTP_COUNTER[] PROGMEM =
"
";
@@ -614,13 +614,21 @@ void WSHeaderSend(void)
#endif
}
+/**********************************************************************************************
+* HTTP Content Page handler
+**********************************************************************************************/
+
void WSSend(int code, int ctype, const String& content)
{
char ct[25]; // strlen("application/octet-stream") +1 = Longest Content type string
WebServer->send(code, GetTextIndexed(ct, sizeof(ct), ctype, kContentTypes), content);
}
-void WSContentSendLl(const String& content) // Low level sendContent for all core versions
+/**********************************************************************************************
+* HTTP Content Chunk handler
+**********************************************************************************************/
+
+void _WSContentSend(const String& content) // Low level sendContent for all core versions
{
size_t len = content.length();
@@ -633,40 +641,19 @@ void WSContentSendLl(const String& content) // Low level sendContent for a
WebServer->sendContent(content);
#endif
- ShowFreeMem(PSTR("WSContentSend"));
-
-// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("WEB: Chunk size %d"), len);
+// ShowFreeMem(PSTR("WSContentSend"));
+// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("HTP: Chunk size %d"), len);
}
void WSContentFlush()
{
if (chunk_buffer.length() > 0) {
- WSContentSendLl(chunk_buffer); // Flush chunk buffer
+ _WSContentSend(chunk_buffer); // Flush chunk buffer
chunk_buffer = "";
}
}
-void WSContentSend(const String& content) // Content send string data
-{
- size_t len = content.length();
-
- if (0 == len) { // No content
- return;
- }
- else if (len < CHUNKED_BUFFER_SIZE) { // Append chunk buffer with small content
- chunk_buffer += content;
- len = chunk_buffer.length();
- }
-
- if (len >= CHUNKED_BUFFER_SIZE) { // Either content or chunk buffer is oversize
- WSContentFlush(); // Send chunk buffer before possible content oversize
- }
- if (content.length() >= CHUNKED_BUFFER_SIZE) { // Content is oversize
- WSContentSendLl(content); // Send content
- }
-}
-
-void WSContentSend_P(PGM_P formatP, ...) // Content send snprintf_P char data
+void WSContentSend_P(const char* formatP, ...) // Content send snprintf_P char data
{
// This uses char strings. Be aware of sending %% if % is needed
va_list arg;
@@ -674,10 +661,26 @@ void WSContentSend_P(PGM_P formatP, ...) // Content send snprintf_P cha
int len = vsnprintf_P(mqtt_data, sizeof(mqtt_data), formatP, arg);
va_end(arg);
- WSContentSend(mqtt_data);
+ if (0 == len) { // No content
+ return;
+ }
+ else if (len == sizeof(mqtt_data)) {
+ AddLog_P(LOG_LEVEL_INFO, PSTR("HTP: Content too large"));
+ }
+ else if (len < CHUNKED_BUFFER_SIZE) { // Append chunk buffer with small content
+ chunk_buffer += mqtt_data;
+ len = chunk_buffer.length();
+ }
+
+ if (len >= CHUNKED_BUFFER_SIZE) { // Either content or chunk buffer is oversize
+ WSContentFlush(); // Send chunk buffer before possible content oversize
+ }
+ if (strlen(mqtt_data) >= CHUNKED_BUFFER_SIZE) { // Content is oversize
+ _WSContentSend(mqtt_data); // Send content
+ }
}
-void WSContentStart(const String& title, bool auth)
+void WSContentStart_P(const char* title, bool auth)
{
if (auth && (Settings.web_password[0] != 0) && !WebServer->authenticate(WEB_USERNAME, Settings.web_password)) {
return WebServer->requestAuthentication();
@@ -690,45 +693,47 @@ void WSContentStart(const String& title, bool auth)
WebServer->sendHeader(F("Accept-Ranges"),F("none"));
WebServer->sendHeader(F("Transfer-Encoding"),F("chunked"));
#endif
- WSSend(200, CT_HTML, ""); // Signal start of chunked content
+ WSSend(200, CT_HTML, ""); // Signal start of chunked content
chunk_buffer = "";
- WSContentSend_P(HTTP_HEAD, Settings.friendlyname[0], title.c_str());
+ char ctitle[strlen_P(title) +1];
+ strcpy_P(ctitle, title); // Get title from flash to RAM
+ WSContentSend_P(HTTP_HEAD, Settings.friendlyname[0], ctitle);
}
-void WSContentStart(const String& title)
+void WSContentStart_P(const char* title)
{
- WSContentStart(title, true);
+ WSContentStart_P(title, true);
}
-void WSContentSendStyle(const String& style)
+void WSContentSendStyle_P(const char* style)
{
if (WifiIsInManagerMode()) {
if (WifiConfigCounter()) {
- WSContentSend(FPSTR(HTTP_SCRIPT_COUNTER));
+ WSContentSend_P(HTTP_SCRIPT_COUNTER);
}
}
- WSContentSend(FPSTR(HTTP_HEAD_STYLE1));
- WSContentSend(FPSTR(HTTP_HEAD_STYLE2));
- WSContentSend(style);
+ WSContentSend_P(HTTP_HEAD_STYLE1);
+ WSContentSend_P(HTTP_HEAD_STYLE2);
+ WSContentSend_P(style);
WSContentSend_P(HTTP_HEAD_STYLE3, ModuleName().c_str(), Settings.friendlyname[0], WSNetworkInfo().c_str());
}
-void WSContentSendStyle()
+void WSContentSendStyle(void)
{
- WSContentSendStyle(F(""));
+ WSContentSendStyle_P(PSTR(""));
}
void WSContentEnd(void)
{
if (WifiIsInManagerMode()) {
if (WifiConfigCounter()) {
- WSContentSend(FPSTR(HTTP_COUNTER));
+ WSContentSend_P(HTTP_COUNTER);
}
}
WSContentSend_P(HTTP_END, my_version);
- WSContentFlush(); // Flush chunk buffer
- WSContentSendLl(""); // Signal end of chunked content
+ WSContentFlush(); // Flush chunk buffer
+ _WSContentSend(""); // Signal end of chunked content
WebServer->client().stop();
}
@@ -743,21 +748,21 @@ void WebRestart(uint8_t type)
bool reset_only = (HTTP_MANAGER_RESET_ONLY == webserver_state);
- WSContentStart((type) ? FPSTR(S_SAVE_CONFIGURATION) : FPSTR(S_RESTART), !reset_only);
- WSContentSend(FPSTR(HTTP_SCRIPT_RELOAD));
+ WSContentStart_P((type) ? S_SAVE_CONFIGURATION : S_RESTART, !reset_only);
+ WSContentSend_P(HTTP_SCRIPT_RELOAD);
WSContentSendStyle();
if (type) {
- WSContentSend(F("" D_CONFIGURATION_SAVED ""));
+ WSContentSend_P(PSTR("
" D_CONFIGURATION_SAVED "
"));
if (2 == type) {
- WSContentSend(F("
" D_TRYING_TO_CONNECT "
"));
+ WSContentSend_P(PSTR("
" D_TRYING_TO_CONNECT "
"));
}
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
}
- WSContentSend(FPSTR(HTTP_MSG_RSTRT));
+ WSContentSend_P(HTTP_MSG_RSTRT);
if (HTTP_MANAGER == webserver_state || reset_only) {
webserver_state = HTTP_ADMIN;
} else {
- WSContentSend(FPSTR(HTTP_BTN_MAIN));
+ WSContentSend_P(HTTP_BTN_MAIN);
}
WSContentEnd();
@@ -769,15 +774,15 @@ void WebRestart(uint8_t type)
void HandleWifiLogin(void)
{
- WSContentStart(FPSTR(D_CONFIGURE_WIFI), false); // false means show page no matter if the client has or has not credentials
+ WSContentStart_P(S_CONFIGURE_WIFI, false); // false means show page no matter if the client has or has not credentials
WSContentSendStyle();
- WSContentSend(FPSTR(HTTP_FORM_LOGIN));
+ WSContentSend_P(HTTP_FORM_LOGIN);
if (HTTP_MANAGER_RESET_ONLY == webserver_state) {
- WSContentSend(F("
"));
- WSContentSend(FPSTR(HTTP_BTN_RSTRT));
+ WSContentSend_P(PSTR("
"));
+ WSContentSend_P(HTTP_BTN_RSTRT);
#ifndef FIRMWARE_MINIMAL
- WSContentSend(FPSTR(HTTP_BTN_RESET));
+ WSContentSend_P(HTTP_BTN_RESET);
#endif // FIRMWARE_MINIMAL
}
@@ -817,11 +822,11 @@ void HandleRoot(void)
char stemp[5];
- WSContentStart(FPSTR(S_MAIN_MENU));
+ WSContentStart_P(S_MAIN_MENU);
WSContentSend_P(HTTP_SCRIPT_ROOT, Settings.web_refresh);
WSContentSendStyle();
- WSContentSend(F(""));
+ WSContentSend_P(PSTR(""));
if (devices_present) {
if (light_type) {
if ((LST_COLDWARM == (light_type &7)) || (LST_RGBWC == (light_type &7))) {
@@ -829,8 +834,8 @@ void HandleRoot(void)
}
WSContentSend_P(HTTP_MSG_SLIDER2, Settings.light_dimmer);
}
- WSContentSend(FPSTR(HTTP_TABLE100));
- WSContentSend(F(""));
+ WSContentSend_P(HTTP_TABLE100);
+ WSContentSend_P(PSTR("
"));
if (SONOFF_IFAN02 == my_module_type) {
WSContentSend_P(HTTP_DEVICE_CONTROL, 36, 1, D_BUTTON_TOGGLE, "");
for (uint8_t i = 0; i < MAX_FAN_SPEED; i++) {
@@ -843,20 +848,20 @@ void HandleRoot(void)
WSContentSend_P(HTTP_DEVICE_CONTROL, 100 / devices_present, idx, (devices_present < 5) ? D_BUTTON_TOGGLE : "", (devices_present > 1) ? stemp : "");
}
}
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
}
if (SONOFF_BRIDGE == my_module_type) {
- WSContentSend(FPSTR(HTTP_TABLE100));
- WSContentSend(F(""));
+ WSContentSend_P(HTTP_TABLE100);
+ WSContentSend_P(PSTR("
"));
uint8_t idx = 0;
for (uint8_t i = 0; i < 4; i++) {
- if (idx > 0) { WSContentSend(F("
")); }
+ if (idx > 0) { WSContentSend_P(PSTR("
")); }
for (uint8_t j = 0; j < 4; j++) {
idx++;
WSContentSend_P(PSTR(" | "), idx, idx); // &k is related to WebGetArg("k", tmp, sizeof(tmp));
}
}
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR(""));
}
#ifndef FIRMWARE_MINIMAL
@@ -866,12 +871,12 @@ void HandleRoot(void)
if (HTTP_ADMIN == webserver_state) {
#ifndef FIRMWARE_MINIMAL
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(HTTP_BTN_CONF);
#else
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
#endif // Not FIRMWARE_MINIMAL
- WSContentSend(FPSTR(HTTP_BTN_MENU1));
- WSContentSend(FPSTR(HTTP_BTN_RSTRT));
+ WSContentSend_P(HTTP_BTN_MENU1);
+ WSContentSend_P(HTTP_BTN_RSTRT);
}
WSContentEnd();
}
@@ -965,17 +970,17 @@ void HandleConfiguration(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURATION);
- WSContentStart(FPSTR(S_CONFIGURATION));
+ WSContentStart_P(S_CONFIGURATION);
WSContentSendStyle();
- WSContentSend(FPSTR(HTTP_BTN_MENU_MODULE));
+ WSContentSend_P(HTTP_BTN_MENU_MODULE);
XdrvCall(FUNC_WEB_ADD_BUTTON);
XsnsCall(FUNC_WEB_ADD_BUTTON);
- WSContentSend(FPSTR(HTTP_BTN_MENU4));
- WSContentSend(FPSTR(HTTP_BTN_RESET));
- WSContentSend(FPSTR(HTTP_BTN_MENU5));
- WSContentSend(FPSTR(HTTP_BTN_MAIN));
+ WSContentSend_P(HTTP_BTN_MENU4);
+ WSContentSend_P(HTTP_BTN_RESET);
+ WSContentSend_P(HTTP_BTN_MENU5);
+ WSContentSend_P(HTTP_BTN_MAIN);
WSContentEnd();
}
@@ -1045,24 +1050,24 @@ void HandleTemplateConfiguration(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_TEMPLATE);
- WSContentStart(FPSTR(S_CONFIGURE_TEMPLATE));
- WSContentSend(FPSTR(HTTP_SCRIPT_MODULE_TEMPLATE));
- WSContentSend(FPSTR(HTTP_SCRIPT_TEMPLATE));
+ WSContentStart_P(S_CONFIGURE_TEMPLATE);
+ WSContentSend_P(HTTP_SCRIPT_MODULE_TEMPLATE);
+ WSContentSend_P(HTTP_SCRIPT_TEMPLATE);
WSContentSendStyle();
- WSContentSend(FPSTR(HTTP_FORM_TEMPLATE));
+ WSContentSend_P(HTTP_FORM_TEMPLATE);
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
for (uint8_t i = 0; i < 17; i++) {
if ((i < 6) || ((i > 8) && (i != 11))) { // Ignore flash pins GPIO06, 7, 8 and 11
WSContentSend_P(PSTR("" D_GPIO "%d | %s | |
"),
(0==i)?" style='width:74px'":"", i, ((9==i)||(10==i))? "ESP8285" :"", (0==i)?" style='width:176px'":"", i, i);
}
}
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
- WSContentSend(FPSTR(HTTP_FORM_TEMPLATE_FLAG));
- WSContentSend(FPSTR(HTTP_FORM_END));
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(HTTP_FORM_TEMPLATE_FLAG);
+ WSContentSend_P(HTTP_FORM_END);
+ WSContentSend_P(HTTP_BTN_CONF);
WSContentEnd();
}
@@ -1149,15 +1154,15 @@ void HandleModuleConfiguration(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONFIGURE_MODULE);
- WSContentStart(FPSTR(S_CONFIGURE_MODULE));
- WSContentSend(FPSTR(HTTP_SCRIPT_MODULE_TEMPLATE));
+ WSContentStart_P(S_CONFIGURE_MODULE);
+ WSContentSend_P(HTTP_SCRIPT_MODULE_TEMPLATE);
WSContentSend_P(HTTP_SCRIPT_MODULE1, Settings.module);
for (uint8_t i = 0; i < sizeof(cmodule); i++) {
if (ValidGPIO(i, cmodule.io[i])) {
WSContentSend_P(PSTR("sk(%d,%d);"), my_module.io[i], i); // g0 - g16
}
}
- WSContentSend(FPSTR(HTTP_SCRIPT_MODULE2));
+ WSContentSend_P(HTTP_SCRIPT_MODULE2);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_MODULE, AnyModuleName(MODULE).c_str());
for (uint8_t i = 0; i < sizeof(cmodule); i++) {
@@ -1167,9 +1172,9 @@ void HandleModuleConfiguration(void)
(WEMOS==my_module_type)?stemp:"", i, (0==i)? D_SENSOR_BUTTON "1":(1==i)? D_SERIAL_OUT :(3==i)? D_SERIAL_IN :((9==i)||(10==i))? "ESP8285" :(12==i)? D_SENSOR_RELAY "1":(13==i)? D_SENSOR_LED "1i":(14==i)? D_SENSOR :"", i, i);
}
}
- WSContentSend(F(""));
- WSContentSend(FPSTR(HTTP_FORM_END));
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(PSTR(""));
+ WSContentSend_P(HTTP_FORM_END);
+ WSContentSend_P(HTTP_BTN_CONF);
WSContentEnd();
}
@@ -1226,8 +1231,8 @@ void HandleWifiConfiguration(void)
return;
}
- WSContentStart(FPSTR(S_CONFIGURE_WIFI), !WifiIsInManagerMode());
- WSContentSend(FPSTR(HTTP_SCRIPT_WIFI));
+ WSContentStart_P(S_CONFIGURE_WIFI, !WifiIsInManagerMode());
+ WSContentSend_P(HTTP_SCRIPT_WIFI);
WSContentSendStyle();
if (HTTP_MANAGER_RESET_ONLY != webserver_state) {
@@ -1240,8 +1245,8 @@ void HandleWifiConfiguration(void)
if (0 == n) {
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_WIFI, S_NO_NETWORKS_FOUND);
- WSContentSend(FPSTR(S_NO_NETWORKS_FOUND));
- WSContentSend(F(". " D_REFRESH_TO_SCAN_AGAIN "."));
+ WSContentSend_P(S_NO_NETWORKS_FOUND);
+ WSContentSend_P(PSTR(". " D_REFRESH_TO_SCAN_AGAIN "."));
} else {
//sort networks
int indices[n];
@@ -1280,40 +1285,38 @@ void HandleWifiConfiguration(void)
int quality = WifiGetRssiAsQuality(WiFi.RSSI(indices[i]));
if (minimum_signal_quality == -1 || minimum_signal_quality < quality) {
- String item = F("");
- String rssiQ;
- rssiQ += quality;
- item.replace(F("{v}"), htmlEscape(WiFi.SSID(indices[i])));
- item.replace(F("{w}"), String(WiFi.channel(indices[i])));
- item.replace(F("{r}"), rssiQ);
uint8_t auth = WiFi.encryptionType(indices[i]);
- item.replace(F("{i}"), (ENC_TYPE_WEP == auth) ? F(D_WEP) : (ENC_TYPE_TKIP == auth) ? F(D_WPA_PSK) : (ENC_TYPE_CCMP == auth) ? F(D_WPA2_PSK) : (ENC_TYPE_AUTO == auth) ? F(D_AUTO) : F(""));
- WSContentSend(item);
+ WSContentSend_P(PSTR(""),
+ htmlEscape(WiFi.SSID(indices[i])).c_str(),
+ WiFi.channel(indices[i]),
+ (ENC_TYPE_WEP == auth) ? D_WEP : (ENC_TYPE_TKIP == auth) ? D_WPA_PSK : (ENC_TYPE_CCMP == auth) ? D_WPA2_PSK : (ENC_TYPE_AUTO == auth) ? D_AUTO : "",
+ quality
+ );
delay(0);
} else {
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_WIFI D_SKIPPING_LOW_QUALITY));
}
}
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
}
} else {
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR("
"));
}
// As WIFI_HOSTNAME may contain %s-%04d it cannot be part of HTTP_FORM_WIFI where it will exception
WSContentSend_P(HTTP_FORM_WIFI, Settings.sta_ssid[0], Settings.sta_ssid[1], WIFI_HOSTNAME, WIFI_HOSTNAME, Settings.hostname);
- WSContentSend(FPSTR(HTTP_FORM_END));
+ WSContentSend_P(HTTP_FORM_END);
}
if (WifiIsInManagerMode()) {
- WSContentSend(F("
"));
- WSContentSend(FPSTR(HTTP_BTN_RSTRT));
+ WSContentSend_P(PSTR("
"));
+ WSContentSend_P(HTTP_BTN_RSTRT);
#ifndef FIRMWARE_MINIMAL
- WSContentSend(FPSTR(HTTP_BTN_RESET));
+ WSContentSend_P(HTTP_BTN_RESET);
#endif // FIRMWARE_MINIMAL
} else {
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(HTTP_BTN_CONF);
}
WSContentEnd();
}
@@ -1352,9 +1355,9 @@ void HandleLoggingConfiguration(void)
return;
}
- WSContentStart(FPSTR(S_CONFIGURE_LOGGING));
+ WSContentStart_P(S_CONFIGURE_LOGGING);
WSContentSendStyle();
- WSContentSend(FPSTR(HTTP_FORM_LOG1));
+ WSContentSend_P(HTTP_FORM_LOG1);
char stemp1[32];
char stemp2[32];
uint8_t dlevel[3] = { LOG_LEVEL_INFO, LOG_LEVEL_INFO, LOG_LEVEL_NONE };
@@ -1369,11 +1372,11 @@ void HandleLoggingConfiguration(void)
(i == llevel) ? " selected" : "", i, i,
GetTextIndexed(stemp1, sizeof(stemp1), i, kLoggingLevels));
}
- WSContentSend(F("
"));
+ WSContentSend_P(PSTR(""));
}
WSContentSend_P(HTTP_FORM_LOG2, Settings.syslog_host, Settings.syslog_port, Settings.tele_period);
- WSContentSend(FPSTR(HTTP_FORM_END));
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(HTTP_FORM_END);
+ WSContentSend_P(HTTP_BTN_CONF);
WSContentEnd();
}
@@ -1416,7 +1419,7 @@ void HandleOtherConfiguration(void)
return;
}
- WSContentStart(FPSTR(S_CONFIGURE_OTHER));
+ WSContentStart_P(S_CONFIGURE_OTHER);
WSContentSendStyle();
TemplateJson();
@@ -1437,7 +1440,7 @@ void HandleOtherConfiguration(void)
}
#ifdef USE_EMULATION
- WSContentSend(F("
// }2 = |
- WSContentSend(FPSTR(HTTP_SCRIPT_INFO_BEGIN));
+ WSContentSend_P(HTTP_SCRIPT_INFO_BEGIN);
WSContentSend_P(PSTR(""));
WSContentSend_P(PSTR(D_PROGRAM_VERSION "}2%s%s"), my_version, my_image);
WSContentSend_P(PSTR("}1" D_BUILD_DATE_AND_TIME "}2%s"), GetBuildDateAndTime().c_str());
@@ -1659,13 +1662,13 @@ void HandleInformation(void)
WSContentSend_P(PSTR("}1" D_FREE_MEMORY "}2%dkB"), freeMem / 1024);
WSContentSend_P(PSTR(" |
---|
"));
- WSContentSend(FPSTR(HTTP_SCRIPT_INFO_END));
+ WSContentSend_P(HTTP_SCRIPT_INFO_END);
WSContentSendStyle();
- // WSContentSend(F(""));
+ WSContentSend_P(HTTP_BTN_MAIN);
WSContentEnd();
}
#endif // Not FIRMWARE_MINIMAL
@@ -1678,11 +1681,11 @@ void HandleUpgradeFirmware(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_FIRMWARE_UPGRADE);
- WSContentStart(FPSTR(S_FIRMWARE_UPGRADE));
+ WSContentStart_P(S_FIRMWARE_UPGRADE);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_UPG, Settings.ota_url);
WSContentSend_P(HTTP_FORM_RST_UPG, D_UPGRADE);
- WSContentSend(FPSTR(HTTP_BTN_MAIN));
+ WSContentSend_P(HTTP_BTN_MAIN);
WSContentEnd();
upload_error = 0;
@@ -1705,12 +1708,12 @@ void HandleUpgradeFirmwareStart(void)
ExecuteWebCommand(command, SRC_WEBGUI);
}
- WSContentStart(FPSTR(S_INFORMATION));
- WSContentSend(FPSTR(HTTP_SCRIPT_RELOAD_OTA));
+ WSContentStart_P(S_INFORMATION);
+ WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA);
WSContentSendStyle();
- WSContentSend(F("" D_UPGRADE_STARTED " ... "));
- WSContentSend(FPSTR(HTTP_MSG_RSTRT));
- WSContentSend(FPSTR(HTTP_BTN_MAIN));
+ WSContentSend_P(PSTR("" D_UPGRADE_STARTED " ... "));
+ WSContentSend_P(HTTP_MSG_RSTRT);
+ WSContentSend_P(HTTP_BTN_MAIN);
WSContentEnd();
snprintf_P(command, sizeof(command), PSTR(D_CMND_UPGRADE " 1"));
@@ -1729,14 +1732,14 @@ void HandleUploadDone(void)
restart_flag = 0;
MqttRetryCounter(0);
- WSContentStart(FPSTR(S_INFORMATION));
+ WSContentStart_P(S_INFORMATION);
if (!upload_error) {
- WSContentSend(FPSTR(HTTP_SCRIPT_RELOAD_OTA)); // Refesh main web ui after OTA upgrade
+ WSContentSend_P(HTTP_SCRIPT_RELOAD_OTA); // Refesh main web ui after OTA upgrade
}
WSContentSendStyle();
- WSContentSend(F("" D_UPLOAD " " D_UPLOAD " " D_FAILED "
"));
+ WSContentSend_P(PSTR("red'>" D_FAILED "
"));
#ifdef USE_RF_FLASH
if (upload_error < 14) {
#else
@@ -1746,18 +1749,18 @@ void HandleUploadDone(void)
} else {
snprintf_P(error, sizeof(error), PSTR(D_UPLOAD_ERROR_CODE " %d"), upload_error);
}
- WSContentSend(error);
+ WSContentSend_P(error);
AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_UPLOAD ": %s"), error);
stop_flash_rotate = Settings.flag.stop_flash_rotate;
} else {
- WSContentSend(F("green'>" D_SUCCESSFUL " "));
- WSContentSend(FPSTR(HTTP_MSG_RSTRT));
+ WSContentSend_P(PSTR("green'>" D_SUCCESSFUL " "));
+ WSContentSend_P(HTTP_MSG_RSTRT);
ShowWebSource(SRC_WEBGUI);
restart_flag = 2; // Always restart to re-enable disabled features during update
}
SettingsBufferFree();
- WSContentSend(F(" "));
- WSContentSend(FPSTR(HTTP_BTN_MAIN));
+ WSContentSend_P(PSTR(" "));
+ WSContentSend_P(HTTP_BTN_MAIN);
WSContentEnd();
}
@@ -2032,11 +2035,11 @@ void HandleConsole(void)
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, S_CONSOLE);
- WSContentStart(FPSTR(S_CONSOLE));
+ WSContentStart_P(S_CONSOLE);
WSContentSend_P(HTTP_SCRIPT_CONSOL, Settings.web_refresh);
WSContentSendStyle();
- WSContentSend(FPSTR(HTTP_FORM_CMND));
- WSContentSend(FPSTR(HTTP_BTN_MAIN));
+ WSContentSend_P(HTTP_FORM_CMND);
+ WSContentSend_P(HTTP_BTN_MAIN);
WSContentEnd();
}
diff --git a/sonoff/xdrv_02_mqtt.ino b/sonoff/xdrv_02_mqtt.ino
index 450ff252b..b79f6a866 100644
--- a/sonoff/xdrv_02_mqtt.ino
+++ b/sonoff/xdrv_02_mqtt.ino
@@ -917,7 +917,7 @@ void HandleMqttConfiguration(void)
char str[sizeof(Settings.mqtt_client)];
- WSContentStart(FPSTR(S_CONFIGURE_MQTT));
+ WSContentStart_P(S_CONFIGURE_MQTT);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_MQTT1,
Settings.mqtt_host,
@@ -930,8 +930,8 @@ void HandleMqttConfiguration(void)
Settings.mqtt_topic,
MQTT_FULLTOPIC, MQTT_FULLTOPIC,
Settings.mqtt_fulltopic);
- WSContentSend(FPSTR(HTTP_FORM_END));
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(HTTP_FORM_END);
+ WSContentSend_P(HTTP_BTN_CONF);
WSContentEnd();
}
@@ -980,7 +980,7 @@ bool Xdrv02(uint8_t function)
switch (function) {
#ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON:
- WSContentSend(FPSTR(HTTP_BTN_MENU_MQTT));
+ WSContentSend_P(HTTP_BTN_MENU_MQTT);
break;
case FUNC_WEB_ADD_HANDLER:
WebServer->on("/" WEB_HANDLE_MQTT, HandleMqttConfiguration);
diff --git a/sonoff/xdrv_07_domoticz.ino b/sonoff/xdrv_07_domoticz.ino
index 4c660998e..490c2e00d 100644
--- a/sonoff/xdrv_07_domoticz.ino
+++ b/sonoff/xdrv_07_domoticz.ino
@@ -460,9 +460,9 @@ void HandleDomoticzConfiguration(void)
char stemp[32];
- WSContentStart(FPSTR(S_CONFIGURE_DOMOTICZ));
+ WSContentStart_P(S_CONFIGURE_DOMOTICZ);
WSContentSendStyle();
- WSContentSend(FPSTR(HTTP_FORM_DOMOTICZ));
+ WSContentSend_P(HTTP_FORM_DOMOTICZ);
for (int i = 0; i < MAX_DOMOTICZ_IDX; i++) {
if (i < devices_present) {
WSContentSend_P(HTTP_FORM_DOMOTICZ_RELAY,
@@ -480,9 +480,9 @@ void HandleDomoticzConfiguration(void)
i +1, GetTextIndexed(stemp, sizeof(stemp), i, kDomoticzSensors), i, i, Settings.domoticz_sensor_idx[i]);
}
WSContentSend_P(HTTP_FORM_DOMOTICZ_TIMER, Settings.domoticz_update_timer);
- WSContentSend(F(""));
- WSContentSend(FPSTR(HTTP_FORM_END));
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(PSTR(""));
+ WSContentSend_P(HTTP_FORM_END);
+ WSContentSend_P(HTTP_BTN_CONF);
WSContentEnd();
}
@@ -533,7 +533,7 @@ bool Xdrv07(uint8_t function)
switch (function) {
#ifdef USE_WEBSERVER
case FUNC_WEB_ADD_BUTTON:
- WSContentSend(FPSTR(HTTP_BTN_MENU_DOMOTICZ));
+ WSContentSend_P(HTTP_BTN_MENU_DOMOTICZ);
break;
case FUNC_WEB_ADD_HANDLER:
WebServer->on("/" WEB_HANDLE_DOMOTICZ, HandleDomoticzConfiguration);
diff --git a/sonoff/xdrv_09_timers.ino b/sonoff/xdrv_09_timers.ino
index e80f5bc01..e8de5effa 100644
--- a/sonoff/xdrv_09_timers.ino
+++ b/sonoff/xdrv_09_timers.ino
@@ -538,7 +538,7 @@ const char HTTP_TIMER_SCRIPT2[] PROGMEM =
"if(m==0){" // Time is set
"so(0);" // Hide offset span and allow Hour 00..23
"q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours
- "q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes
+ "q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes
"}"
"if((m==1)||(m==2)){" // Sunrise or sunset is set
"so(1);" // Show offset span and allow Hour 00..11
@@ -546,7 +546,7 @@ const char HTTP_TIMER_SCRIPT2[] PROGMEM =
"if(q>=12){q-=12;qs('#dr').selectedIndex=1;}" // Negative offset
"else{qs('#dr').selectedIndex=0;}"
"if(q<10){q='0'+q;}qs('#ho').value=q;" // Set offset hours
- "q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set offset minutes
+ "q=p%%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set offset minutes
"}"
"}"
"function so(b){" // Hide or show offset items
@@ -648,7 +648,7 @@ const char HTTP_TIMER_SCRIPT6[] PROGMEM =
"}"
"window.onload=it;";
const char HTTP_TIMER_STYLE[] PROGMEM =
- ".tl{float:left;border-radius:0;border:1px solid #f2f2f2;padding:1px;width:6.25%;}"; // Border color needs to be the same as Fieldset background color from HTTP_HEAD_STYLE1 (transparent won't work)
+ ".tl{float:left;border-radius:0;border:1px solid #f2f2f2;padding:1px;width:6.25%%;}"; // Border color needs to be the same as Fieldset background color from HTTP_HEAD_STYLE1 (transparent won't work)
const char HTTP_FORM_TIMER1[] PROGMEM =
""
""
@@ -698,29 +698,29 @@ void HandleTimerConfiguration(void)
return;
}
- WSContentStart(FPSTR(S_CONFIGURE_TIMER));
- WSContentSend(FPSTR(HTTP_TIMER_SCRIPT1));
+ WSContentStart_P(S_CONFIGURE_TIMER);
+ WSContentSend_P(HTTP_TIMER_SCRIPT1);
#ifdef USE_SUNRISE
- WSContentSend(FPSTR(HTTP_TIMER_SCRIPT2));
+ WSContentSend_P(HTTP_TIMER_SCRIPT2);
#endif // USE_SUNRISE
WSContentSend_P(HTTP_TIMER_SCRIPT3, devices_present);
WSContentSend_P(HTTP_TIMER_SCRIPT4, devices_present);
WSContentSend_P(HTTP_TIMER_SCRIPT5, devices_present);
WSContentSend_P(HTTP_TIMER_SCRIPT6, devices_present);
- WSContentSendStyle(FPSTR(HTTP_TIMER_STYLE));
+ WSContentSendStyle_P(HTTP_TIMER_STYLE);
WSContentSend_P(HTTP_FORM_TIMER1, (Settings.flag3.timers_enable) ? " checked" : "");
for (uint8_t i = 0; i < MAX_TIMERS; i++) {
WSContentSend_P(PSTR("%s%u"), (i > 0) ? "," : "", Settings.timer[i].data);
}
- WSContentSend(FPSTR(HTTP_FORM_TIMER2));
+ WSContentSend_P(HTTP_FORM_TIMER2);
#ifdef USE_SUNRISE
WSContentSend_P(HTTP_FORM_TIMER3, 100 + (strlen(D_SUNSET) *12), GetSun(0).c_str(), GetSun(1).c_str());
#else
- WSContentSend(FPSTR(HTTP_FORM_TIMER3));
+ WSContentSend_P(HTTP_FORM_TIMER3);
#endif // USE_SUNRISE
- WSContentSend(FPSTR(HTTP_FORM_TIMER4));
- WSContentSend(FPSTR(HTTP_FORM_END));
- WSContentSend(FPSTR(HTTP_BTN_CONF));
+ WSContentSend_P(HTTP_FORM_TIMER4);
+ WSContentSend_P(HTTP_FORM_END);
+ WSContentSend_P(HTTP_BTN_CONF);
WSContentEnd();
}
@@ -764,9 +764,9 @@ bool Xdrv09(uint8_t function)
#ifdef USE_TIMERS_WEB
case FUNC_WEB_ADD_BUTTON:
#ifdef USE_RULES
- WSContentSend(FPSTR(HTTP_BTN_MENU_TIMER));
+ WSContentSend_P(HTTP_BTN_MENU_TIMER);
#else
- if (devices_present) { WSContentSend(FPSTR(HTTP_BTN_MENU_TIMER)); }
+ if (devices_present) { WSContentSend_P(HTTP_BTN_MENU_TIMER); }
#endif // USE_RULES
break;
case FUNC_WEB_ADD_HANDLER:
diff --git a/sonoff/xdrv_11_knx.ino b/sonoff/xdrv_11_knx.ino
index af23ab220..6f4a5b96d 100644
--- a/sonoff/xdrv_11_knx.ino
+++ b/sonoff/xdrv_11_knx.ino
@@ -762,7 +762,7 @@ const char HTTP_FORM_KNX2[] PROGMEM =
""
"" D_KNX_GROUP_ADDRESS_TO_WRITE " "
- " |
---|