5.10.0c
 * Consolidate device serial (MH-Z19, SenseAir and Pzem004T)
into TasmotaSerial library
 * Consolidate PWM device recognition
 * Fix
Wemo Emulation (#1357)
 * Add support for Arilux LC06 (#1414)
This commit is contained in:
arendst 2017-12-25 17:41:12 +01:00
parent 7723d52453
commit 67d8fa0d64
39 changed files with 760 additions and 821 deletions

View File

@ -1,7 +1,7 @@
## Sonoff-Tasmota
Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE.
Current version is **5.10.0b** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information.
Current version is **5.10.0c** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information.
### ATTENTION All versions

View File

@ -0,0 +1,8 @@
# TasmotaSerial
Implementation of software serial library for the ESP8266 at 9600 baud
Allows for several instances to be active at the same time.
Please note that due to the fact that the ESP always have other activities ongoing, there will be some inexactness in interrupt
timings. This may lead to bit errors when having heavy data traffic.

View File

@ -0,0 +1,27 @@
#include <TasmotaSerial.h>
TasmotaSerial swSer(14, 12);
void setup() {
Serial.begin(115200);
swSer.begin();
Serial.println("\nTasmota serial test started");
for (char ch = ' '; ch <= 'z'; ch++) {
swSer.write(ch);
}
swSer.println("");
}
void loop() {
while (swSer.available() > 0) {
Serial.write(swSer.read());
}
while (Serial.available() > 0) {
swSer.write(Serial.read());
}
}

View File

@ -0,0 +1,26 @@
#######################################
# Syntax Coloring Map for TasmotaSerial
# (esp8266)
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
TasmotaSerial KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
read KEYWORD2
write KEYWORD2
available KEYWORD2
flush KEYWORD2
peek KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,15 @@
{
"name": "TasmotaSerial",
"version": "1.0.0",
"keywords": [
"serial", "io", "TasmotaSerial"
],
"description": "Implementation of software serial for ESP8266 at 9600 baud.",
"repository":
{
"type": "git",
"url": "https://github.com/arendst/Sonoff-Tasmota/lib/TasmotaSerial"
},
"frameworks": "arduino",
"platforms": "espressif8266"
}

View File

@ -0,0 +1,9 @@
name=TasmotaSerial
version=1.0
author=Theo Arends
maintainer=Theo Arends <theo@arends.com>
sentence=Implementation of software serial for ESP8266 at 9600 baud.
paragraph=
category=Signal Input/Output
url=
architectures=esp8266

View File

@ -0,0 +1,193 @@
/*
TasmotaSerial.cpp - Minimal implementation of software serial for Tasmota
Copyright (C) 2018 Theo Arends
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <Arduino.h>
// The Arduino standard GPIO routines are not enough,
// must use some from the Espressif SDK as well
extern "C" {
#include "gpio.h"
}
#include <TasmotaSerial.h>
// As the Arduino attachInterrupt has no parameter, lists of objects
// and callbacks corresponding to each possible GPIO pins have to be defined
TasmotaSerial *ObjList[16];
#ifdef TM_SERIAL_USE_IRAM
void ICACHE_RAM_ATTR sws_isr_0() { ObjList[0]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_1() { ObjList[1]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_2() { ObjList[2]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_3() { ObjList[3]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_4() { ObjList[4]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void ICACHE_RAM_ATTR sws_isr_12() { ObjList[12]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_13() { ObjList[13]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_14() { ObjList[14]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_15() { ObjList[15]->rxRead(); };
#else
void sws_isr_0() { ObjList[0]->rxRead(); };
void sws_isr_1() { ObjList[1]->rxRead(); };
void sws_isr_2() { ObjList[2]->rxRead(); };
void sws_isr_3() { ObjList[3]->rxRead(); };
void sws_isr_4() { ObjList[4]->rxRead(); };
void sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void sws_isr_12() { ObjList[12]->rxRead(); };
void sws_isr_13() { ObjList[13]->rxRead(); };
void sws_isr_14() { ObjList[14]->rxRead(); };
void sws_isr_15() { ObjList[15]->rxRead(); };
#endif // TM_SERIAL_USE_IRAM
static void (*ISRList[16])() = {
sws_isr_0,
sws_isr_1,
sws_isr_2,
sws_isr_3,
sws_isr_4,
sws_isr_5,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
sws_isr_12,
sws_isr_13,
sws_isr_14,
sws_isr_15
};
TasmotaSerial::TasmotaSerial(int receive_pin, int transmit_pin)
{
m_valid = false;
if (!((isValidGPIOpin(receive_pin)) && (isValidGPIOpin(transmit_pin) || transmit_pin == 16))) {
return;
}
m_buffer = (uint8_t*)malloc(TM_SERIAL_BUFFER_SIZE);
if (m_buffer == NULL) {
return;
}
m_valid = true;
m_rx_pin = receive_pin;
m_tx_pin = transmit_pin;
m_in_pos = m_out_pos = 0;
// Use getCycleCount() loop to get as exact timing as possible
m_bit_time = ESP.getCpuFreqMHz() *1000000 /TM_SERIAL_BAUDRATE;
pinMode(m_rx_pin, INPUT);
ObjList[m_rx_pin] = this;
attachInterrupt(m_rx_pin, ISRList[m_rx_pin], FALLING);
pinMode(m_tx_pin, OUTPUT);
digitalWrite(m_tx_pin, HIGH);
}
bool TasmotaSerial::isValidGPIOpin(int pin)
{
return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= 15);
}
bool TasmotaSerial::begin() {
return m_valid;
}
int TasmotaSerial::read()
{
if (m_in_pos == m_out_pos) {
return -1;
}
uint8_t ch = m_buffer[m_out_pos];
m_out_pos = (m_out_pos +1) % TM_SERIAL_BUFFER_SIZE;
return ch;
}
int TasmotaSerial::available()
{
int avail = m_in_pos - m_out_pos;
if (avail < 0) {
avail += TM_SERIAL_BUFFER_SIZE;
}
return avail;
}
//#define TM_SERIAL_WAIT { while (ESP.getCycleCount()-start < wait) optimistic_yield(1); wait += m_bit_time; } // Watchdog timeouts
#define TM_SERIAL_WAIT { while (ESP.getCycleCount()-start < wait); wait += m_bit_time; }
size_t TasmotaSerial::txWrite(uint8_t b)
{
unsigned long wait = m_bit_time;
digitalWrite(m_tx_pin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(m_tx_pin, LOW);
TM_SERIAL_WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(m_tx_pin, (b & 1) ? HIGH : LOW);
TM_SERIAL_WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(m_tx_pin, HIGH);
TM_SERIAL_WAIT;
return 1;
}
size_t TasmotaSerial::write(const uint8_t *buffer, size_t size)
{
size_t n = 0;
// Flush input buffer on every write
m_in_pos = m_out_pos = 0;
while(size--) {
n += txWrite(*buffer++);
}
return n;
}
#ifdef TM_SERIAL_USE_IRAM
void ICACHE_RAM_ATTR TasmotaSerial::rxRead()
{
#else
void TasmotaSerial::rxRead()
{
#endif
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = m_bit_time + m_bit_time/3 - 500;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
TM_SERIAL_WAIT;
rec >>= 1;
if (digitalRead(m_rx_pin)) {
rec |= 0x80;
}
}
// Stop bit
TM_SERIAL_WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (m_in_pos+1) % TM_SERIAL_BUFFER_SIZE;
if (next != m_out_pos) {
m_buffer[m_in_pos] = rec;
m_in_pos = next;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << m_rx_pin);
}

View File

@ -0,0 +1,56 @@
/*
TasmotaSerial.h - Minimal implementation of software serial for Tasmota
Copyright (C) 2018 Theo Arends
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TasmotaSerial_h
#define TasmotaSerial_h
/*********************************************************************************************\
* TasmotaSerial supports 9600 baud with fixed buffer size of 20 bytes using optional no iram
*
* Based on EspSoftwareSerial v3.3.1 by Peter Lerup (https://github.com/plerup/espsoftwareserial)
\*********************************************************************************************/
#define TM_SERIAL_BAUDRATE 9600
#define TM_SERIAL_BUFFER_SIZE 20
//#define TM_SERIAL_USE_IRAM // Enable to use iram (+368 bytes)
class TasmotaSerial {
public:
TasmotaSerial(int receive_pin, int transmit_pin);
bool begin();
size_t write(const uint8_t *buffer, size_t size = 1);
int read();
int available();
void rxRead();
private:
bool isValidGPIOpin(int pin);
size_t txWrite(uint8_t byte);
// Member variables
bool m_valid;
int m_rx_pin;
int m_tx_pin;
unsigned long m_bit_time;
unsigned int m_in_pos;
unsigned int m_out_pos;
uint8_t *m_buffer;
};
#endif // TasmotaSerial_h

View File

@ -1,10 +1,16 @@
/* 5.10.0b
/* 5.10.0c
* Consolidate device serial (MH-Z19, SenseAir and Pzem004T) into TasmotaSerial library
* Consolidate PWM device recognition
* Fix Wemo Emulation (#1357)
* Add support for Arilux LC06 (#1414)
*
* 5.10.0b
* Add support for PZEM004T energy sensor to be enabled with define USE_PZEM004T in user_config.h
* Change Sonoff Pow Energy MQTT data message and consolidate Status 8 into Status 10
* Change Wemo SetBinaryState to distinguish from GetBinaryState (#1357)
* Change output of HTTP command to valid JSON and Array only (#1363)
* Add support for MH-Z19(B) CO2 sensor to be enabled with define USE_MHZ19 in user_config.h (#561, #1248)
* Add support for senseair S8 CO2 sensor to be enabled with define USE_SENSEAIR in user_config.h
* Add support for SenseAir S8 CO2 sensor to be enabled with define USE_SENSEAIR in user_config.h
* Add support for Domoticz Air Quality sensor to be used by MH-Z19(B) and SenseAir sensors
*
* 5.10.0a

View File

@ -182,7 +182,7 @@ void SettingsSaveAll()
} else {
Settings.power = 0;
}
XsnsCall(FUNC_XSNS_SAVE_BEFORE_RESTART);
XsnsCall(FUNC_SAVE_BEFORE_RESTART);
SettingsSave(0);
}

View File

@ -133,7 +133,7 @@ enum LightTypes {LT_BASIC, LT_PWM1, LT_PWM2, LT_PWM3, LT_PWM4, LT_PWM5, LT_PWM6,
enum LichtSubtypes {LST_NONE, LST_SINGLE, LST_COLDWARM, LST_RGB, LST_RGBW, LST_RGBWC};
enum LichtSchemes {LS_POWER, LS_WAKEUP, LS_CYCLEUP, LS_CYCLEDN, LS_RANDOM, LS_MAX};
enum XsnsFunctions {FUNC_XSNS_INIT, FUNC_XSNS_EVERY_SECOND, FUNC_XSNS_PREP_BEFORE_TELEPERIOD, FUNC_XSNS_JSON_APPEND, FUNC_XSNS_WEB_APPEND, FUNC_XSNS_SAVE_BEFORE_RESTART};
enum XsnsFunctions {FUNC_INIT, FUNC_EVERY_50_MSECOND, FUNC_EVERY_SECOND, FUNC_PREP_BEFORE_TELEPERIOD, FUNC_JSON_APPEND, FUNC_WEB_APPEND, FUNC_SAVE_BEFORE_RESTART};
const uint8_t kDefaultRfCode[9] PROGMEM = { 0x21, 0x16, 0x01, 0x0E, 0x03, 0x48, 0x2E, 0x1A, 0x00 };

View File

@ -25,8 +25,8 @@
- Select IDE Tools - Flash Size: "1M (no SPIFFS)"
====================================================*/
#define VERSION 0x050A0002
#define VERSION_STRING "5.10.0b" // Would be great to have a macro that fills this from VERSION ...
#define VERSION 0x050A0003
#define VERSION_STRING "5.10.0c" // Would be great to have a macro that fills this from VERSION ...
// Location specific includes
#include "sonoff.h" // Enumaration used in user_config.h
@ -1812,7 +1812,7 @@ boolean MqttShowSensor()
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_SWITCH "%d\":\"%s\""), mqtt_data, i +1, GetStateText(swm ^ lastwallswitch[i]));
}
}
XsnsCall(FUNC_XSNS_JSON_APPEND);
XsnsCall(FUNC_JSON_APPEND);
boolean json_data_available = (strlen(mqtt_data) - json_data_start);
if (strstr_P(mqtt_data, PSTR(D_TEMPERATURE))) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_TEMPERATURE_UNIT "\":\"%c\""), mqtt_data, TempUnit());
@ -1871,7 +1871,7 @@ void PerformEverySecond()
if (Settings.tele_period) {
tele_period++;
if (tele_period == Settings.tele_period -1) {
XsnsCall(FUNC_XSNS_PREP_BEFORE_TELEPERIOD);
XsnsCall(FUNC_PREP_BEFORE_TELEPERIOD);
}
if (tele_period >= Settings.tele_period) {
tele_period = 0;
@ -1887,7 +1887,7 @@ void PerformEverySecond()
}
}
XsnsCall(FUNC_XSNS_EVERY_SECOND);
XsnsCall(FUNC_EVERY_SECOND);
if ((2 == RtcTime.minute) && latest_uptime_flag) {
latest_uptime_flag = false;
@ -2220,6 +2220,8 @@ void StateLoop()
LightAnimate();
}
XsnsCall(FUNC_EVERY_50_MSECOND);
/*-------------------------------------------------------------------------------------------*\
* Every 0.2 second
\*-------------------------------------------------------------------------------------------*/
@ -2521,17 +2523,20 @@ void GpioInit()
#endif // USE_I2C
devices_present = 1;
light_type = LT_BASIC; // Use basic PWM control if SetOption15 = 0
if (Settings.flag.pwm_control) {
light_type = LT_BASIC;
for (byte i = 0; i < MAX_PWMS; i++) {
if (pin[GPIO_PWM1 +i] < 99) {
light_type++; // Use Dimmer/Color control for all PWM as SetOption15 = 1
}
}
}
if (SONOFF_BRIDGE == Settings.module) {
baudrate = 19200;
}
if (SONOFF_DUAL == Settings.module) {
devices_present = 2;
baudrate = 19200;
@ -2544,11 +2549,6 @@ void GpioInit()
devices_present = 0;
baudrate = 19200;
}
else if ((H801 == Settings.module) || (MAGICHOME == Settings.module) || (ARILUX_LC01 == Settings.module) || (ARILUX_LC11 == Settings.module)) { // PWM RGBCW led
if (!Settings.flag.pwm_control) {
light_type = LT_BASIC; // Use basic PWM control if SetOption15 = 0
}
}
else if (SONOFF_BN == Settings.module) { // PWM Single color led (White)
light_type = LT_PWM1;
}
@ -2572,6 +2572,7 @@ void GpioInit()
}
}
}
for (byte i = 0; i < MAX_KEYS; i++) {
if (pin[GPIO_KEY1 +i] < 99) {
pinMode(pin[GPIO_KEY1 +i], (16 == pin[GPIO_KEY1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP);
@ -2599,7 +2600,7 @@ void GpioInit()
if (light_type) { // Any Led light under Dimmer/Color control
LightInit();
} else {
for (byte i = 0; i < MAX_PWMS; i++) {
for (byte i = 0; i < MAX_PWMS; i++) { // Basic PWM control only
if (pin[GPIO_PWM1 +i] < 99) {
pinMode(pin[GPIO_PWM1 +i], OUTPUT);
analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]);
@ -2623,8 +2624,6 @@ void GpioInit()
}
#endif // USE_IR_RECEIVE
#endif // USE_IR_REMOTE
// energy_flg = (((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)) || ((pin[GPIO_PZEM_RX] < 99) && (pin[GPIO_PZEM_TX])));
}
extern "C" {

View File

@ -209,6 +209,7 @@ enum SupportedModules {
ARILUX_LC01,
ARILUX_LC11,
SONOFF_DUAL_R2,
ARILUX_LC06,
MAXMODULE };
/********************************************************************************************/
@ -260,6 +261,7 @@ const uint8_t kNiceList[MAXMODULE] PROGMEM = {
H801,
MAGICHOME,
ARILUX_LC01,
ARILUX_LC06,
ARILUX_LC11,
HUAFAN_SS,
KMC_70011,
@ -747,7 +749,7 @@ const mytmplt kModules[MAXMODULE] PROGMEM = {
GPIO_REL1, // GPIO14 Relay
0, 0, 0
},
{ "Arilux", // Arilux AL-LC01 (ESP8285) - https://www.banggood.com/nl/ARILUX-AL-LC01-Super-Mini-LED-WIFI-Smart-RGB-Controller-For-RGB-LED-Strip-Light-DC-9-12V-p-1058603.html
{ "Arilux LC01", // Arilux AL-LC01 (ESP8285) - https://www.banggood.com/nl/ARILUX-AL-LC01-Super-Mini-LED-WIFI-Smart-RGB-Controller-For-RGB-LED-Strip-Light-DC-9-12V-p-1058603.html
// (PwmFrequency 1111Hz)
GPIO_KEY1, // GPIO00 Optional Button
0,
@ -790,7 +792,38 @@ const mytmplt kModules[MAXMODULE] PROGMEM = {
GPIO_REL1, // GPIO12 Relay 1 (0 = Off, 1 = On)
GPIO_LED1_INV, // GPIO13 Blue Led (0 = On, 1 = Off)
0, 0, 0, 0
},
{ "Arilux LC06", // Arilux AL-LC06 (ESP8285) - https://www.banggood.com/ARILUX-AL-LC06-LED-WIFI-Smartphone-Controller-Romote-5-Channels-DC12-24V-For-RGBWW-Strip-light-p-1061476.html
GPIO_KEY1, // GPIO00 Optional Button
0,
GPIO_USER, // GPIO02 Empty pad
0,
GPIO_USER, // GPIO04 W2 - PWM5
0,
0, 0, 0, 0, 0, 0, // Flash connection
GPIO_PWM2, // GPIO12 RGB LED Green
GPIO_PWM3, // GPIO13 RGB LED Blue
GPIO_PWM1, // GPIO14 RGB LED Red
GPIO_USER, // GPIO15 RGBW LED White
0, 0
}
};
/*
Optionals
{ "Xenon 3CH", // Xenon 3CH (ESP8266) - (#1128)
0, 0, 0,
GPIO_KEY2, // GPIO03 Serial TXD and Optional sensor
GPIO_REL2, // GPIO04 Relay 2
GPIO_KEY3, // GPIO05 Input 2
0, 0, 0, 0, 0, 0, // Flash connection
GPIO_KEY1, // GPIO12 Key input
GPIO_REL1, // GPIO13 Relay 1
0,
GPIO_REL3, // GPIO15 Relay 3
0, 0
}
*/
#endif // _SONOFF_TEMPLATE_H_

View File

@ -1337,15 +1337,15 @@ boolean Xsns02(byte function)
if (pin[GPIO_ADC0] < 99) {
switch (function) {
// case FUNC_XSNS_INIT:
// case FUNC_INIT:
// break;
// case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
// case FUNC_PREP_BEFORE_TELEPERIOD:
// break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
AdcShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
AdcShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -163,7 +163,7 @@
// -- Sensor code selection -----------------------
#define USE_ADC_VCC // Display Vcc in Power status. Disable for use as Analog input on selected devices
//#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k3 code)
//#define USE_PZEM004T // Add support for PZEM004T Energy monitor (+2k code)
// WARNING: Select none for default one DS18B20 sensor or enable one of the following two options for multiple sensors
//#define USE_DS18x20 // Optional for more than one DS18x20 sensors with id sort, single scan and read retry (+1k3 code)
@ -192,8 +192,8 @@
#define USE_WS2812_CTYPE 1 // WS2812 Color type (0 - RGB, 1 - GRB, 2 - RGBW, 3 - GRBW)
// #define USE_WS2812_DMA // DMA supports only GPIO03 (= Serial RXD) (+1k mem). When USE_WS2812_DMA is enabled expect Exceptions on Pow
//#define USE_MHZ19 // Add support for MH-Z19 CO2 sensor (+1k8 code)
//#define USE_SENSEAIR // Add support for SenseAir K30, K70 and S8 CO2 sensor (+1k8 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_ARILUX_RF // Add support for Arilux RF remote controller (+0k8 code)

View File

@ -511,7 +511,7 @@ void HandleAjaxStatusRefresh()
String page = "";
mqtt_data[0] = '\0';
XsnsCall(FUNC_XSNS_WEB_APPEND);
XsnsCall(FUNC_WEB_APPEND);
if (strlen(mqtt_data)) {
page += FPSTR(HTTP_TABLE100);
page += mqtt_data;

View File

@ -268,6 +268,17 @@ const char WEMO_EVENTSERVICE_XML[] PROGMEM =
"</action>"
"</scpd>\r\n"
"\r\n";
const char WEMO_RESPONSE_STATE_SOAP[] PROGMEM =
"<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">"
"<s:Body>"
"<u:SetBinaryStateResponse xmlns:u=\"urn:Belkin:service:basicevent:1\">"
"<BinaryState>{x1</BinaryState>"
"</u:SetBinaryStateResponse>"
"</s:Body>"
"</s:Envelope>\r\n"
"\r\n";
const char WEMO_SETUP_XML[] PROGMEM =
"<?xml version=\"1.0\"?>"
"<root>"
@ -299,17 +310,21 @@ void HandleUpnpEvent()
{
AddLog_P(LOG_LEVEL_DEBUG, S_LOG_HTTP, PSTR(D_WEMO_BASIC_EVENT));
String request = WebServer->arg(0);
String state_xml = FPSTR(WEMO_RESPONSE_STATE_SOAP);
//differentiate get and set state
if (request.indexOf(F("SetBinaryState")) > 0) {
if (request.indexOf(F("State>1</Binary")) > 0) {
// ExecuteCommandPower(1, 1);
ExecuteCommandPower(devices_present, 1);
}
if (request.indexOf(F("State>0</Binary")) > 0) {
// ExecuteCommandPower(1, 0);
else if (request.indexOf(F("State>0</Binary")) > 0) {
ExecuteCommandPower(devices_present, 0);
}
}
WebServer->send(200, FPSTR(HDR_CTYPE_PLAIN), "");
else if(request.indexOf(F("GetBinaryState")) > 0){
state_xml.replace("Set", "Get");
}
state_xml.replace("{x1", String(bitRead(power, devices_present -1)));
WebServer->send(200, FPSTR(HDR_CTYPE_XML), state_xml);
}
void HandleUpnpService()

View File

@ -130,20 +130,18 @@ boolean Xsns01(byte function)
boolean result = false;
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
CounterInit();
break;
// case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
// break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
CounterShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
CounterShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_XSNS_SAVE_BEFORE_RESTART:
case FUNC_SAVE_BEFORE_RESTART:
CounterSaveState();
break;
}

View File

@ -41,7 +41,6 @@ const char kEnergyCommands[] PROGMEM =
D_CMND_MAXPOWER "|" D_CMND_MAXPOWERHOLD "|" D_CMND_MAXPOWERWINDOW "|"
D_CMND_SAFEPOWER "|" D_CMND_SAFEPOWERHOLD "|" D_CMND_SAFEPOWERWINDOW ;
bool energy_power_factor_ready = false;
float energy_voltage = 0; // 123.1 V
float energy_current = 0; // 123.123 A
float energy_power = 0; // 123.1 W
@ -208,25 +207,6 @@ void HlwEvery200ms()
hlw_cf1_summed_pulse_length = 0;
hlw_cf1_pulse_counter = 0;
}
/*
energy_power = 0;
if (hlw_cf_pulse_length && (power &1) && !hlw_load_off) {
hlw_w = (HLW_PREF * Settings.hlw_power_calibration) / hlw_cf_pulse_length;
energy_power = (float)hlw_w / 10;
}
energy_voltage = 0;
if (hlw_cf1_voltage_pulse_length && (power &1)) { // If powered on always provide voltage
hlw_u = (HLW_UREF * Settings.hlw_voltage_calibration) / hlw_cf1_voltage_pulse_length;
energy_voltage = (float)hlw_u / 10;
}
energy_current = 0;
if (hlw_cf1_current_pulse_length && energy_power) { // No current if no power being consumed
hlw_i = (HLW_IREF * Settings.hlw_current_calibration) / hlw_cf1_current_pulse_length;
energy_current = (float)hlw_i / 1000;
}
*/
energy_power_factor_ready = true;
}
void HlwInit()
@ -269,119 +249,9 @@ void HlwInit()
* Based on: PZEM004T library https://github.com/olehs/PZEM004T
\*********************************************************************************************/
#define PZEM_BAUD_RATE 9600
#include <TasmotaSerial.h>
/*********************************************************************************************\
* Subset SoftwareSerial
\*********************************************************************************************/
#define PZEM_SERIAL_BUFFER_SIZE 20
#define PZEM_SERIAL_WAIT { while (ESP.getCycleCount() -start < wait) optimistic_yield(1); wait += pzem_serial_bit_time; }
uint8_t pzem_serial_rx_pin;
uint8_t pzem_serial_tx_pin;
uint8_t pzem_serial_in_pos = 0;
uint8_t pzem_serial_out_pos = 0;
uint8_t pzem_serial_buffer[PZEM_SERIAL_BUFFER_SIZE];
unsigned long pzem_serial_bit_time;
unsigned long pzem_serial_bit_time_start;
bool PzemSerialValidGpioPin(uint8_t pin) {
return (pin >= 0 && pin <= 5) || (pin >= 9 && pin <= 10) || (pin >= 12 && pin <= 15);
}
bool PzemSerial(uint8_t receive_pin, uint8_t transmit_pin)
{
if (!((PzemSerialValidGpioPin(receive_pin)) && (PzemSerialValidGpioPin(transmit_pin) || transmit_pin == 16))) {
return false;
}
pzem_serial_rx_pin = receive_pin;
pinMode(pzem_serial_rx_pin, INPUT);
attachInterrupt(pzem_serial_rx_pin, PzemSerialRxRead, FALLING);
pzem_serial_tx_pin = transmit_pin;
pinMode(pzem_serial_tx_pin, OUTPUT);
digitalWrite(pzem_serial_tx_pin, 1);
pzem_serial_bit_time = ESP.getCpuFreqMHz() *1000000 /PZEM_BAUD_RATE; // 8333
pzem_serial_bit_time_start = pzem_serial_bit_time + pzem_serial_bit_time /3 -500; // 10610 ICACHE_RAM_ATTR start delay
// pzem_serial_bit_time_start = pzem_serial_bit_time; // Non ICACHE_RAM_ATTR start delay (experimental)
return true;
}
int PzemSerialRead() {
if (pzem_serial_in_pos == pzem_serial_out_pos) {
return -1;
}
int ch = pzem_serial_buffer[pzem_serial_out_pos];
pzem_serial_out_pos = (pzem_serial_out_pos +1) % PZEM_SERIAL_BUFFER_SIZE;
return ch;
}
int PzemSerialAvailable() {
int avail = pzem_serial_in_pos - pzem_serial_out_pos;
if (avail < 0) {
avail += PZEM_SERIAL_BUFFER_SIZE;
}
return avail;
}
size_t PzemSerialTxWrite(uint8_t b)
{
unsigned long wait = pzem_serial_bit_time;
digitalWrite(pzem_serial_tx_pin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(pzem_serial_tx_pin, LOW);
PZEM_SERIAL_WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(pzem_serial_tx_pin, (b & 1) ? HIGH : LOW);
PZEM_SERIAL_WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(pzem_serial_tx_pin, HIGH);
PZEM_SERIAL_WAIT;
return 1;
}
size_t PzemSerialWrite(const uint8_t *buffer, size_t size = 1) {
size_t n = 0;
while(size--) {
n += PzemSerialTxWrite(*buffer++);
}
return n;
}
//void PzemSerialRxRead() ICACHE_RAM_ATTR; // Add 215 bytes to iram usage
void PzemSerialRxRead() {
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = pzem_serial_bit_time_start;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
PZEM_SERIAL_WAIT;
rec >>= 1;
if (digitalRead(pzem_serial_rx_pin)) {
rec |= 0x80;
}
}
// Stop bit
PZEM_SERIAL_WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (pzem_serial_in_pos +1) % PZEM_SERIAL_BUFFER_SIZE;
if (next != pzem_serial_out_pos) {
pzem_serial_buffer[pzem_serial_in_pos] = rec;
pzem_serial_in_pos = next;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << pzem_serial_rx_pin);
}
/*********************************************************************************************/
TasmotaSerial *PzemSerial;
#define PZEM_VOLTAGE (uint8_t)0xB0
#define RESP_VOLTAGE (uint8_t)0xA0
@ -402,7 +272,6 @@ void PzemSerialRxRead() {
#define RESP_POWER_ALARM (uint8_t)0xA5
#define PZEM_DEFAULT_READ_TIMEOUT 500
#define PZEM_ERROR_VALUE -1.0
struct PZEMCommand {
uint8_t command;
@ -413,52 +282,16 @@ struct PZEMCommand {
IPAddress pzem_ip(192, 168, 1, 1);
float PZEM004T_voltage_rcv()
uint8_t PzemCrc(uint8_t *data)
{
uint8_t data[sizeof(PZEMCommand) -2];
if (!PZEM004T_recieve(RESP_VOLTAGE, data)) {
return PZEM_ERROR_VALUE;
uint16_t crc = 0;
for (uint8_t i = 0; i < sizeof(PZEMCommand) -1; i++) {
crc += *data++;
}
return (data[0] << 8) + data[1] + (data[2] / 10.0); // 65535.x V
return (uint8_t)(crc & 0xFF);
}
float PZEM004T_current_rcv()
{
uint8_t data[sizeof(PZEMCommand) -2];
if (!PZEM004T_recieve(RESP_CURRENT, data)) {
return PZEM_ERROR_VALUE;
}
return (data[0] << 8) + data[1] + (data[2] / 100.0); // 65535.xx A
}
float PZEM004T_power_rcv()
{
uint8_t data[sizeof(PZEMCommand) -2];
if (!PZEM004T_recieve(RESP_POWER, data)) {
return PZEM_ERROR_VALUE;
}
return (data[0] << 8) + data[1]; // 65535 W
}
float PZEM004T_energy_rcv()
{
uint8_t data[sizeof(PZEMCommand) -2];
if (!PZEM004T_recieve(RESP_ENERGY, data)) {
return PZEM_ERROR_VALUE;
}
return ((uint32_t)data[0] << 16) + ((uint16_t)data[1] << 8) + data[2]; // 16777215 Wh
}
bool PZEM004T_setAddress_rcv()
{
return PZEM004T_recieve(RESP_SET_ADDRESS, 0);
}
void PZEM004T_send(uint8_t cmd)
void PzemSend(uint8_t cmd)
{
PZEMCommand pzem;
@ -469,41 +302,37 @@ void PZEM004T_send(uint8_t cmd)
pzem.data = 0;
uint8_t *bytes = (uint8_t*)&pzem;
pzem.crc = PZEM004T_crc(bytes, sizeof(pzem) - 1);
pzem.crc = PzemCrc(bytes);
while (PzemSerialAvailable()) {
PzemSerialRead();
}
PzemSerialWrite(bytes, sizeof(pzem));
PzemSerial->write(bytes, sizeof(pzem));
}
bool PZEM004T_isReady()
bool PzemReceiveReady()
{
return PzemSerialAvailable() >= sizeof(PZEMCommand);
return PzemSerial->available() >= sizeof(PZEMCommand);
}
bool PZEM004T_recieve(uint8_t resp, uint8_t *data)
bool PzemRecieve(uint8_t resp, float *data)
{
uint8_t buffer[sizeof(PZEMCommand)];
unsigned long startTime = millis();
unsigned long start = millis();
uint8_t len = 0;
while ((len < sizeof(PZEMCommand)) && (millis() - startTime < PZEM_DEFAULT_READ_TIMEOUT)) {
if (PzemSerialAvailable() > 0) {
uint8_t c = (uint8_t)PzemSerialRead();
while ((len < sizeof(PZEMCommand)) && (millis() - start < PZEM_DEFAULT_READ_TIMEOUT)) {
if (PzemSerial->available() > 0) {
uint8_t c = (uint8_t)PzemSerial->read();
if (!c && !len) {
continue; // skip 0 at startup
}
buffer[len++] = c;
}
// yield(); // do background netw tasks while blocked for IO (prevents ESP watchdog trigger) - This triggers Watchdog!!!
}
if (len != sizeof(PZEMCommand)) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem comms timeout"));
return false;
}
if (buffer[6] != PZEM004T_crc(buffer, len - 1)) {
if (buffer[6] != PzemCrc(buffer)) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem crc error"));
return false;
}
@ -511,77 +340,51 @@ bool PZEM004T_recieve(uint8_t resp, uint8_t *data)
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "Pzem bad response"));
return false;
}
if (data) {
for (int i = 0; i < sizeof(PZEMCommand) -2; i++) {
data[i] = buffer[1 + i];
}
}
switch (resp) {
case RESP_VOLTAGE:
*data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 10.0); // 65535.x V
break;
case RESP_CURRENT:
*data = (float)(buffer[1] << 8) + buffer[2] + (buffer[3] / 100.0); // 65535.xx A
break;
case RESP_POWER:
*data = (float)(buffer[1] << 8) + buffer[2]; // 65535 W
break;
case RESP_ENERGY:
*data = (float)((uint32_t)buffer[1] << 16) + ((uint16_t)buffer[2] << 8) + buffer[3]; // 16777215 Wh
break;
}
return true;
}
uint8_t PZEM004T_crc(uint8_t *data, uint8_t sz)
{
uint16_t crc = 0;
for (uint8_t i = 0; i < sz; i++) {
crc += *data++;
}
return (uint8_t)(crc & 0xFF);
}
/*********************************************************************************************/
typedef enum
{
SET_ADDRESS,
READ_VOLTAGE,
READ_CURRENT,
READ_POWER,
READ_ENERGY,
} PZEMReadStates;
const uint8_t pzem_commands[] { PZEM_SET_ADDRESS, PZEM_VOLTAGE, PZEM_CURRENT, PZEM_POWER, PZEM_ENERGY };
const uint8_t pzem_responses[] { RESP_SET_ADDRESS, RESP_VOLTAGE, RESP_CURRENT, RESP_POWER, RESP_ENERGY };
PZEMReadStates pzem_read_state = SET_ADDRESS;
byte pzem_sendRetry = 0;
uint8_t pzem_read_state = 0;
uint8_t pzem_sendRetry = 0;
void PzemEvery200ms()
{
bool dataReady = PZEM004T_isReady();
bool data_ready = PzemReceiveReady();
if (dataReady) {
float pzem_value;
switch (pzem_read_state) {
case SET_ADDRESS:
if (PZEM004T_setAddress_rcv()) {
pzem_read_state = READ_VOLTAGE;
}
break;
case READ_VOLTAGE:
pzem_value = PZEM004T_voltage_rcv();
if (pzem_value != PZEM_ERROR_VALUE) {
energy_voltage = pzem_value; // 230.2V
pzem_read_state = READ_CURRENT;
}
break;
case READ_CURRENT:
pzem_value = PZEM004T_current_rcv();
if (pzem_value != PZEM_ERROR_VALUE) {
energy_current = pzem_value; // 17.32A
pzem_read_state = READ_POWER;
}
break;
case READ_POWER:
pzem_value = PZEM004T_power_rcv();
if (pzem_value != PZEM_ERROR_VALUE) {
energy_power = pzem_value; // 20W
energy_power_factor_ready = true;
pzem_read_state = READ_ENERGY;
}
break;
case READ_ENERGY:
pzem_value = PZEM004T_energy_rcv();
if (pzem_value != PZEM_ERROR_VALUE) {
energy_total = pzem_value / 1000; // 99999Wh
if (data_ready) {
float value = 0;
if (PzemRecieve(pzem_responses[pzem_read_state], &value)) {
switch (pzem_read_state) {
case 1:
energy_voltage = value; // 230.2V
break;
case 2:
energy_current = value; // 17.32A
break;
case 3:
energy_power = value; // 20W
break;
case 4:
energy_total = value / 1000; // 99999Wh
if (!energy_startup) {
if (energy_total < energy_start) {
energy_start = energy_total;
@ -590,32 +393,18 @@ void PzemEvery200ms()
energy_kWhtoday = (energy_total - energy_start) * 100000000;
energy_daily = (float)energy_kWhtoday / 100000000;
}
pzem_read_state = READ_VOLTAGE;
}
break;
break;
}
pzem_read_state++;
if (5 == pzem_read_state) {
pzem_read_state = 1;
}
}
}
if (0 == pzem_sendRetry || dataReady) {
if (0 == pzem_sendRetry || data_ready) {
pzem_sendRetry = 5;
switch (pzem_read_state) {
case SET_ADDRESS:
PZEM004T_send(PZEM_SET_ADDRESS);
break;
case READ_VOLTAGE:
PZEM004T_send(PZEM_VOLTAGE);
break;
case READ_CURRENT:
PZEM004T_send(PZEM_CURRENT);
break;
case READ_POWER:
PZEM004T_send(PZEM_POWER);
break;
case READ_ENERGY:
PZEM004T_send(PZEM_ENERGY);
break;
}
PzemSend(pzem_commands[pzem_read_state]);
}
else {
pzem_sendRetry--;
@ -624,7 +413,8 @@ void PzemEvery200ms()
bool PzemInit()
{
return PzemSerial(pin[GPIO_PZEM_RX], pin[GPIO_PZEM_TX]);
PzemSerial = new TasmotaSerial(pin[GPIO_PZEM_RX], pin[GPIO_PZEM_TX]);
return PzemSerial->begin();
}
/********************************************************************************************/
@ -676,14 +466,14 @@ void Energy200ms()
#endif // USE_PZEM004T
}
if (energy_power_factor_ready && energy_voltage && energy_current && energy_power) {
energy_power_factor_ready = false;
float power_factor = energy_power / (energy_voltage * energy_current);
float power_factor = 0;
if (energy_voltage && energy_current && energy_power) {
power_factor = energy_power / (energy_voltage * energy_current);
if (power_factor > 1) {
power_factor = 1;
}
energy_power_factor = power_factor;
}
energy_power_factor = power_factor;
}
void EnergySaveState()
@ -1143,23 +933,21 @@ boolean Xsns03(byte function)
if (energy_flg) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
EnergyInit();
break;
case FUNC_XSNS_EVERY_SECOND:
case FUNC_EVERY_SECOND:
EnergyMarginCheck();
break;
// case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
// break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
EnergyShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
EnergyShow(0);
break;
#endif // USE_WEBSERVER
case FUNC_XSNS_SAVE_BEFORE_RESTART:
case FUNC_SAVE_BEFORE_RESTART:
EnergySaveState();
break;
}

View File

@ -148,16 +148,14 @@ boolean Xsns04(byte function)
if (SONOFF_SC == Settings.module) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
SonoffScInit();
break;
// case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
// break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
SonoffScShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
SonoffScShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -219,17 +219,17 @@ boolean Xsns05(byte function)
if (pin[GPIO_DSB] < 99) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
Ds18x20Init();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Ds18x20Convert(); // Start conversion, takes up to one second
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Ds18b20Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Ds18b20Show(0);
Ds18x20Convert(); // Start conversion, takes up to one second
break;

View File

@ -401,17 +401,17 @@ boolean Xsns05(byte function)
if (pin[GPIO_DSB] < 99) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
Ds18x20Init();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Ds18x20Convert(); // Start conversion, takes up to one second
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Ds18x20Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Ds18x20Show(0);
Ds18x20Convert(); // Start conversion, takes up to one second
break;

View File

@ -219,18 +219,18 @@ boolean Xsns05(byte function)
if (pin[GPIO_DSB] < 99) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
Ds18x20Init();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Ds18x20Search(); // Check for changes in sensors number
Ds18x20Convert(); // Start Conversion, takes up to one second
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Ds18x20Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Ds18x20Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -253,17 +253,17 @@ boolean Xsns06(byte function)
if (dht_flg) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
DhtInit();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
DhtReadPrep();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
DhtShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
DhtShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -223,16 +223,14 @@ boolean Xsns07(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
ShtDetect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
ShtShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
ShtShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -284,16 +284,14 @@ boolean Xsns08(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
HtuDetect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
HtuShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
HtuShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -502,19 +502,17 @@ boolean Xsns09(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
BmpDetect();
#ifdef USE_BME680
Bme680PerformReading();
#endif // USE_BME680
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
BmpShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
BmpShow(0);
#ifdef USE_BME680
Bme680PerformReading();

View File

@ -99,16 +99,14 @@ boolean Xsns10(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Bh1750Detect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Bh1750Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Bh1750Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -104,16 +104,14 @@ boolean Xsns11(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Veml6070Detect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Veml6070Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Veml6070Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -218,16 +218,14 @@ boolean Xsns12(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Ads1115Detect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Ads1115Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Ads1115Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -138,16 +138,14 @@ boolean Xsns12(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Ads1115Detect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Ads1115Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Ads1115Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -219,16 +219,14 @@ boolean Xsns13(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Ina219Detect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Ina219Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Ina219Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -125,17 +125,17 @@ boolean Xsns14(byte function)
if (i2c_flg) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
Sht3xDetect();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Sht3xConvert();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Sht3xShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Sht3xShow(0);
Sht3xConvert();
break;

View File

@ -122,16 +122,14 @@ boolean Xsns14(byte function)
if (i2c_flg) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
Sht3xDetect();
break;
// case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
// break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Sht3xShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Sht3xShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -132,17 +132,17 @@ boolean Xsns14(byte function)
if (i2c_flg) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
Sht3xDetect();
break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Sht3xConvert();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Sht3xShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Sht3xShow(0);
Sht3xConvert();
break;

View File

@ -28,7 +28,7 @@
* Select filter usage on low stability readings
\*********************************************************************************************/
enum Mhz19FilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FILTER_FAST, MHZ19_FILTER_MEDIUM, MHZ19_FILTER_SLOW};
enum MhzFilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FILTER_FAST, MHZ19_FILTER_MEDIUM, MHZ19_FILTER_SLOW};
#define MHZ19_FILTER_OPTION MHZ19_FILTER_FAST
@ -52,159 +52,47 @@ enum Mhz19FilterOptions {MHZ19_FILTER_OFF, MHZ19_FILTER_OFF_ALLSAMPLES, MHZ19_FI
/*********************************************************************************************/
#define MHZ19_BAUDRATE 9600
#include <TasmotaSerial.h>
TasmotaSerial *MhzSerial;
#define MHZ19_READ_TIMEOUT 500 // Must be way less than 1000
#define MHZ19_RETRY_COUNT 8
const char kMhz19Types[] PROGMEM = "MHZ19|MHZ19B";
const char kMhzTypes[] PROGMEM = "MHZ19|MHZ19B";
const uint8_t mhz19_cmnd_read_ppm[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
const uint8_t mhz19_cmnd_abc_enable[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};
const uint8_t mhz19_cmnd_abc_disable[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};
const uint8_t mhz_cmnd_read_ppm[9] = {0xFF, 0x01, 0x86, 0x00, 0x00, 0x00, 0x00, 0x00, 0x79};
const uint8_t mhz_cmnd_abc_enable[9] = {0xFF, 0x01, 0x79, 0xA0, 0x00, 0x00, 0x00, 0x00, 0xE6};
const uint8_t mhz_cmnd_abc_disable[9] = {0xFF, 0x01, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x86};
uint8_t mhz19_type = 1;
uint16_t mhz19_last_ppm = 0;
uint8_t mhz19_filter = MHZ19_FILTER_OPTION;
bool mhz19_abc_enable = MHZ19_ABC_ENABLE;
bool mhz19_abc_must_apply = false;
char mhz19_types[7];
uint8_t mhz_type = 1;
uint16_t mhz_last_ppm = 0;
uint8_t mhz_filter = MHZ19_FILTER_OPTION;
bool mhz_abc_enable = MHZ19_ABC_ENABLE;
bool mhz_abc_must_apply = false;
char mhz_types[7];
float mhz19_temperature = 0;
uint8_t mhz19_timer = 0;
uint8_t mhz19_retry = MHZ19_RETRY_COUNT;
Ticker mhz19_ticker;
float mhz_temperature = 0;
uint8_t mhz_timer = 0;
uint8_t mhz_retry = MHZ19_RETRY_COUNT;
/*********************************************************************************************\
* Subset SoftwareSerial
\*********************************************************************************************/
#define MHZ19_SERIAL_BUFFER_SIZE 20
#define MHZ19_SERIAL_WAIT { while (ESP.getCycleCount() -start < wait) optimistic_yield(1); wait += mhz19_serial_bit_time; }
uint8_t mhz19_serial_rx_pin;
uint8_t mhz19_serial_tx_pin;
uint8_t mhz19_serial_in_pos = 0;
uint8_t mhz19_serial_out_pos = 0;
uint8_t mhz19_serial_buffer[MHZ19_SERIAL_BUFFER_SIZE];
unsigned long mhz19_serial_bit_time;
unsigned long mhz19_serial_bit_time_start;
bool Mhz19SerialValidGpioPin(uint8_t pin) {
return (pin >= 0 && pin <= 5) || (pin >= 9 && pin <= 10) || (pin >= 12 && pin <= 15);
}
bool Mhz19Serial(uint8_t receive_pin, uint8_t transmit_pin)
{
if (!((Mhz19SerialValidGpioPin(receive_pin)) && (Mhz19SerialValidGpioPin(transmit_pin) || transmit_pin == 16))) {
return false;
}
mhz19_serial_rx_pin = receive_pin;
pinMode(mhz19_serial_rx_pin, INPUT);
attachInterrupt(mhz19_serial_rx_pin, Mhz19SerialRxRead, FALLING);
mhz19_serial_tx_pin = transmit_pin;
pinMode(mhz19_serial_tx_pin, OUTPUT);
digitalWrite(mhz19_serial_tx_pin, 1);
mhz19_serial_bit_time = ESP.getCpuFreqMHz() *1000000 /MHZ19_BAUDRATE; // 8333
mhz19_serial_bit_time_start = mhz19_serial_bit_time + mhz19_serial_bit_time /3 -500; // 10610 ICACHE_RAM_ATTR start delay
// mhz19_serial_bit_time_start = mhz19_serial_bit_time; // Non ICACHE_RAM_ATTR start delay (experimental)
return true;
}
int Mhz19SerialRead() {
if (mhz19_serial_in_pos == mhz19_serial_out_pos) {
return -1;
}
int ch = mhz19_serial_buffer[mhz19_serial_out_pos];
mhz19_serial_out_pos = (mhz19_serial_out_pos +1) % MHZ19_SERIAL_BUFFER_SIZE;
return ch;
}
int Mhz19SerialAvailable() {
int avail = mhz19_serial_in_pos - mhz19_serial_out_pos;
if (avail < 0) {
avail += MHZ19_SERIAL_BUFFER_SIZE;
}
return avail;
}
void Mhz19SerialFlush()
{
mhz19_serial_in_pos = 0;
mhz19_serial_out_pos = 0;
}
size_t Mhz19SerialTxWrite(uint8_t b)
{
unsigned long wait = mhz19_serial_bit_time;
digitalWrite(mhz19_serial_tx_pin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(mhz19_serial_tx_pin, LOW);
MHZ19_SERIAL_WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(mhz19_serial_tx_pin, (b & 1) ? HIGH : LOW);
MHZ19_SERIAL_WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(mhz19_serial_tx_pin, HIGH);
MHZ19_SERIAL_WAIT;
return 1;
}
size_t Mhz19SerialWrite(const uint8_t *buffer, size_t size = 1) {
size_t n = 0;
while(size--) {
n += Mhz19SerialTxWrite(*buffer++);
}
return n;
}
//void Mhz19SerialRxRead() ICACHE_RAM_ATTR; // Add 215 bytes to iram usage
void Mhz19SerialRxRead() {
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = mhz19_serial_bit_time_start;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
MHZ19_SERIAL_WAIT;
rec >>= 1;
if (digitalRead(mhz19_serial_rx_pin)) {
rec |= 0x80;
}
}
// Stop bit
MHZ19_SERIAL_WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (mhz19_serial_in_pos +1) % MHZ19_SERIAL_BUFFER_SIZE;
if (next != mhz19_serial_out_pos) {
mhz19_serial_buffer[mhz19_serial_in_pos] = rec;
mhz19_serial_in_pos = next;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << mhz19_serial_rx_pin);
}
uint8_t mhz_state = 0;
/*********************************************************************************************/
bool Mhz19CheckAndApplyFilter(uint16_t ppm, uint8_t s)
bool MhzCheckAndApplyFilter(uint16_t ppm, uint8_t s)
{
if (1 == s) {
return false; // S==1 => "A" version sensor bootup, do not use values.
}
if (mhz19_last_ppm < 400 || mhz19_last_ppm > 5000) {
if (mhz_last_ppm < 400 || mhz_last_ppm > 5000) {
// Prevent unrealistic values during start-up with filtering enabled.
// Just assume the entered value is correct.
mhz19_last_ppm = ppm;
mhz_last_ppm = ppm;
return true;
}
int32_t difference = ppm - mhz19_last_ppm;
if (s > 0 && s < 64 && mhz19_filter != MHZ19_FILTER_OFF) {
int32_t difference = ppm - mhz_last_ppm;
if (s > 0 && s < 64 && mhz_filter != MHZ19_FILTER_OFF) {
// Not the "B" version of the sensor, S value is used.
// S==0 => "B" version, else "A" version
// The S value is an indication of the stability of the reading.
@ -216,127 +104,129 @@ bool Mhz19CheckAndApplyFilter(uint16_t ppm, uint8_t s)
difference *= s;
difference /= 64;
}
if (MHZ19_FILTER_OFF == mhz19_filter) {
if (MHZ19_FILTER_OFF == mhz_filter) {
if (s != 0 && s != 64) {
return false;
}
} else {
difference >>= (mhz19_filter -1);
difference >>= (mhz_filter -1);
}
mhz19_last_ppm = static_cast<uint16_t>(mhz19_last_ppm + difference);
mhz_last_ppm = static_cast<uint16_t>(mhz_last_ppm + difference);
return true;
}
void Mhz19Ticker()
void Mhz50ms()
{
uint8_t mhz19_response[9];
mhz_state++;
if (4 == mhz_state) { // Every 200 mSec
mhz_state = 0;
mhz19_timer++;
if (6 == mhz19_timer) { // MH-Z19 measuring cycle takes 1005 +5% ms
mhz19_timer = 0;
uint8_t mhz_response[9];
Mhz19SerialFlush();
Mhz19SerialWrite(mhz19_cmnd_read_ppm, 9);
}
mhz_timer++;
if (6 == mhz_timer) { // MH-Z19 measuring cycle takes 1005 +5% ms
mhz_timer = 0;
if (1 == mhz19_timer) {
if (mhz19_retry) {
mhz19_retry--;
if (!mhz19_retry) {
mhz19_last_ppm = 0;
mhz19_temperature = 0;
MhzSerial->write(mhz_cmnd_read_ppm, 9);
}
if (1 == mhz_timer) {
if (mhz_retry) {
mhz_retry--;
if (!mhz_retry) {
mhz_last_ppm = 0;
mhz_temperature = 0;
}
}
}
unsigned long start = millis();
uint8_t counter = 0;
while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
if (Mhz19SerialAvailable() > 0) {
mhz19_response[counter++] = Mhz19SerialRead();
unsigned long start = millis();
uint8_t counter = 0;
while (((millis() - start) < MHZ19_READ_TIMEOUT) && (counter < 9)) {
if (MhzSerial->available() > 0) {
mhz_response[counter++] = MhzSerial->read();
}
}
}
if (counter < 9) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 comms timeout"));
return;
}
byte crc = 0;
for (uint8_t i = 1; i < 8; i++) {
crc += mhz19_response[i];
}
crc = 255 - crc;
crc++;
if (mhz19_response[8] != crc) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 crc error"));
return;
}
if (0xFF != mhz19_response[0] || 0x86 != mhz19_response[1]) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 bad response"));
return;
}
uint16_t u = (mhz19_response[6] << 8) | mhz19_response[7];
if (15000 == u) { // During (and only ever at) sensor boot, 'u' is reported as 15000
if (!mhz19_abc_enable) {
// After bootup of the sensor the ABC will be enabled.
// Thus only actively disable after bootup.
mhz19_abc_must_apply = true;
if (counter < 9) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 comms timeout"));
return;
}
} else {
uint16_t ppm = (mhz19_response[2] << 8) | mhz19_response[3];
mhz19_temperature = ConvertTemp((float)mhz19_response[4] - 40);
uint8_t s = mhz19_response[5];
mhz19_type = (s) ? 1 : 2;
if (Mhz19CheckAndApplyFilter(ppm, s)) {
mhz19_retry = MHZ19_RETRY_COUNT;
if (0 == s || 64 == s) { // Reading is stable.
if (mhz19_abc_must_apply) {
mhz19_abc_must_apply = false;
if (mhz19_abc_enable) {
Mhz19SerialWrite(mhz19_cmnd_abc_enable, 9); // Sent sensor ABC Enable
} else {
Mhz19SerialWrite(mhz19_cmnd_abc_disable, 9); // Sent sensor ABC Disable
byte crc = 0;
for (uint8_t i = 1; i < 8; i++) {
crc += mhz_response[i];
}
crc = 255 - crc;
crc++;
if (mhz_response[8] != crc) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 crc error"));
return;
}
if (0xFF != mhz_response[0] || 0x86 != mhz_response[1]) {
// AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_DEBUG "MH-Z19 bad response"));
return;
}
uint16_t u = (mhz_response[6] << 8) | mhz_response[7];
if (15000 == u) { // During (and only ever at) sensor boot, 'u' is reported as 15000
if (!mhz_abc_enable) {
// After bootup of the sensor the ABC will be enabled.
// Thus only actively disable after bootup.
mhz_abc_must_apply = true;
}
} else {
uint16_t ppm = (mhz_response[2] << 8) | mhz_response[3];
mhz_temperature = ConvertTemp((float)mhz_response[4] - 40);
uint8_t s = mhz_response[5];
mhz_type = (s) ? 1 : 2;
if (MhzCheckAndApplyFilter(ppm, s)) {
mhz_retry = MHZ19_RETRY_COUNT;
if (0 == s || 64 == s) { // Reading is stable.
if (mhz_abc_must_apply) {
mhz_abc_must_apply = false;
if (mhz_abc_enable) {
MhzSerial->write(mhz_cmnd_abc_enable, 9); // Sent sensor ABC Enable
} else {
MhzSerial->write(mhz_cmnd_abc_disable, 9); // Sent sensor ABC Disable
}
}
}
}
}
}
}
}
}
/*********************************************************************************************/
void Mhz19Init()
void MhzInit()
{
mhz19_type = 0;
mhz_type = 0;
if ((pin[GPIO_MHZ_RXD] < 99) && (pin[GPIO_MHZ_TXD] < 99)) {
if (Mhz19Serial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD])) {
mhz19_type = 1;
mhz19_ticker.attach_ms(222, Mhz19Ticker);
MhzSerial = new TasmotaSerial(pin[GPIO_MHZ_RXD], pin[GPIO_MHZ_TXD]);
if (MhzSerial->begin()) {
mhz_type = 1;
}
}
}
void Mhz19Show(boolean json)
void MhzShow(boolean json)
{
char temperature[10];
dtostrfd(mhz19_temperature, Settings.flag2.temperature_resolution, temperature);
GetTextIndexed(mhz19_types, sizeof(mhz19_types), mhz19_type -1, kMhz19Types);
// uint8_t co2_limit = (mhz19_last_ppm > 1200) ? 3 : (mhz19_last_ppm > 800) ? 2 : 1;
// uint16_t co2_limit = mhz19_last_ppm / 400; // <800 = 1(Green), <1200 = 2(Orange), >1200 = 3(Red)
dtostrfd(mhz_temperature, Settings.flag2.temperature_resolution, temperature);
GetTextIndexed(mhz_types, sizeof(mhz_types), mhz_type -1, kMhzTypes);
if (json) {
// snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d,\"" D_LIMIT "\":%d,\"" D_TEMPERATURE "\":%s}"), mqtt_data, mhz19_types, mhz19_last_ppm, co2_limit, temperature);
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d,\"" D_TEMPERATURE "\":%s}"), mqtt_data, mhz19_types, mhz19_last_ppm, temperature);
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d,\"" D_TEMPERATURE "\":%s}"), mqtt_data, mhz_types, mhz_last_ppm, temperature);
#ifdef USE_DOMOTICZ
DomoticzSensor(DZ_AIRQUALITY, mhz19_last_ppm);
DomoticzSensor(DZ_AIRQUALITY, mhz_last_ppm);
#endif // USE_DOMOTICZ
#ifdef USE_WEBSERVER
} else {
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_CO2, mqtt_data, mhz19_types, mhz19_last_ppm);
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, mhz19_types, temperature, TempUnit());
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_CO2, mqtt_data, mhz_types, mhz_last_ppm);
snprintf_P(mqtt_data, sizeof(mqtt_data), HTTP_SNS_TEMP, mqtt_data, mhz_types, temperature, TempUnit());
#endif // USE_WEBSERVER
}
}
@ -351,17 +241,20 @@ boolean Xsns15(byte function)
{
boolean result = false;
if (mhz19_type) {
if (mhz_type) {
switch (function) {
case FUNC_XSNS_INIT:
Mhz19Init();
case FUNC_INIT:
MhzInit();
break;
case FUNC_XSNS_JSON_APPEND:
Mhz19Show(1);
case FUNC_EVERY_50_MSECOND:
Mhz50ms();
break;
case FUNC_JSON_APPEND:
MhzShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
Mhz19Show(0);
case FUNC_WEB_APPEND:
MhzShow(0);
break;
#endif // USE_WEBSERVER
}

View File

@ -91,16 +91,14 @@ boolean Xsns16(byte function)
if (i2c_flg) {
switch (function) {
// case FUNC_XSNS_INIT:
// break;
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_PREP_BEFORE_TELEPERIOD:
Tsl2561Detect();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_JSON_APPEND:
Tsl2561Show(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
Tsl2561Show(0);
break;
#endif // USE_WEBSERVER

View File

@ -1,5 +1,5 @@
/*
xsns_17_senseair_s8.ino - SenseAir S8 CO2 sensor support for Sonoff-Tasmota
xsns_17_senseair.ino - SenseAir CO2 sensor support for Sonoff-Tasmota
Copyright (C) 2018 Theo Arends
@ -19,16 +19,14 @@
#ifdef USE_SENSEAIR
/*********************************************************************************************\
* SenseAir S8 - CO2 sensor
* SenseAir K30, K70 and S8 - CO2 sensor
*
* Adapted from EspEasy plugin P052 by Mikael Trieb (mikael__AT__triebconsulting.se)
**********************************************************************************************
* Filter usage
*
* Select filter usage on low stability readings
\*********************************************************************************************/
#define SENSEAIR_BAUDRATE 9600
#include <TasmotaSerial.h>
TasmotaSerial *SensairSerial;
const char kSenseairTypes[] PROGMEM = "Kx0|S8";
@ -39,123 +37,7 @@ uint16_t senseair_co2 = 0;
float senseair_temperature = 0;
float senseair_humidity = 0;
Ticker senseair_ticker;
/*********************************************************************************************\
* Subset SoftwareSerial
\*********************************************************************************************/
#define SENSEAIR_SERIAL_BUFFER_SIZE 20
#define SENSEAIR_SERIAL_WAIT { while (ESP.getCycleCount() -start < wait) optimistic_yield(1); wait += senseair_serial_bit_time; }
uint8_t senseair_serial_rx_pin;
uint8_t senseair_serial_tx_pin;
uint8_t senseair_serial_in_pos = 0;
uint8_t senseair_serial_out_pos = 0;
uint8_t senseair_serial_buffer[SENSEAIR_SERIAL_BUFFER_SIZE];
unsigned long senseair_serial_bit_time;
unsigned long senseair_serial_bit_time_start;
bool SenseairSerialValidGpioPin(uint8_t pin) {
return (pin >= 0 && pin <= 5) || (pin >= 9 && pin <= 10) || (pin >= 12 && pin <= 15);
}
bool SenseairSerial(uint8_t receive_pin, uint8_t transmit_pin)
{
if (!((SenseairSerialValidGpioPin(receive_pin)) && (SenseairSerialValidGpioPin(transmit_pin) || transmit_pin == 16))) {
return false;
}
senseair_serial_rx_pin = receive_pin;
pinMode(senseair_serial_rx_pin, INPUT);
attachInterrupt(senseair_serial_rx_pin, SenseairSerialRxRead, FALLING);
senseair_serial_tx_pin = transmit_pin;
pinMode(senseair_serial_tx_pin, OUTPUT);
digitalWrite(senseair_serial_tx_pin, 1);
senseair_serial_bit_time = ESP.getCpuFreqMHz() *1000000 /SENSEAIR_BAUDRATE; // 8333
senseair_serial_bit_time_start = senseair_serial_bit_time + senseair_serial_bit_time /3 -500; // 10610 ICACHE_RAM_ATTR start delay
// senseair_serial_bit_time_start = senseair_serial_bit_time; // Non ICACHE_RAM_ATTR start delay (experimental)
return true;
}
int SenseairSerialRead() {
if (senseair_serial_in_pos == senseair_serial_out_pos) {
return -1;
}
int ch = senseair_serial_buffer[senseair_serial_out_pos];
senseair_serial_out_pos = (senseair_serial_out_pos +1) % SENSEAIR_SERIAL_BUFFER_SIZE;
return ch;
}
int SenseairSerialAvailable() {
int avail = senseair_serial_in_pos - senseair_serial_out_pos;
if (avail < 0) {
avail += SENSEAIR_SERIAL_BUFFER_SIZE;
}
return avail;
}
void SenseairSerialFlush()
{
senseair_serial_in_pos = 0;
senseair_serial_out_pos = 0;
}
size_t SenseairSerialTxWrite(uint8_t b)
{
unsigned long wait = senseair_serial_bit_time;
digitalWrite(senseair_serial_tx_pin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(senseair_serial_tx_pin, LOW);
SENSEAIR_SERIAL_WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(senseair_serial_tx_pin, (b & 1) ? HIGH : LOW);
SENSEAIR_SERIAL_WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(senseair_serial_tx_pin, HIGH);
SENSEAIR_SERIAL_WAIT;
return 1;
}
size_t SenseairSerialWrite(const uint8_t *buffer, size_t size = 1) {
size_t n = 0;
while(size--) {
n += SenseairSerialTxWrite(*buffer++);
}
return n;
}
//void SenseairSerialRxRead() ICACHE_RAM_ATTR; // Add 215 bytes to iram usage
void SenseairSerialRxRead() {
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = senseair_serial_bit_time_start;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
SENSEAIR_SERIAL_WAIT;
rec >>= 1;
if (digitalRead(senseair_serial_rx_pin)) {
rec |= 0x80;
}
}
// Stop bit
SENSEAIR_SERIAL_WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (senseair_serial_in_pos +1) % SENSEAIR_SERIAL_BUFFER_SIZE;
if (next != senseair_serial_out_pos) {
senseair_serial_buffer[senseair_serial_in_pos] = rec;
senseair_serial_in_pos = next;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << senseair_serial_rx_pin);
}
uint8_t senseair_state = 0;
/*********************************************************************************************/
@ -185,13 +67,12 @@ void ModbusSend(uint8_t function_code, uint16_t start_address, uint16_t register
frame[7] = (uint8_t)((crc >> 8) & 0xFF);
frame[6] = (uint8_t)(crc & 0xFF);
SenseairSerialFlush();
SenseairSerialWrite(frame, sizeof(frame));
SensairSerial->write(frame, sizeof(frame));
}
bool ModbusReceiveReady()
{
return (SenseairSerialAvailable() >= 5); // 5 - Error frame, 7 - Ok frame
return (SensairSerial->available() >= 5); // 5 - Error frame, 7 - Ok frame
}
uint8_t ModbusReceive(uint16_t *value)
@ -199,8 +80,8 @@ uint8_t ModbusReceive(uint16_t *value)
uint8_t buffer[7];
uint8_t len = 0;
while (SenseairSerialAvailable() > 0) {
buffer[len++] = (uint8_t)SenseairSerialRead();
while (SensairSerial->available() > 0) {
buffer[len++] = (uint8_t)SensairSerial->read();
if (3 == len) {
if (buffer[1] & 0x80) { // fe 84 02 f2 f1
return buffer[2]; // 1 = Illegal Function, 2 = Illegal Data Address, 3 = Illegal Data Value
@ -221,68 +102,74 @@ const uint8_t start_addresses[] { 0x1A, 0x00, 0x03, 0x04, 0x05, 0x1C, 0x0A };
uint8_t senseair_read_state = 0;
uint8_t senseair_send_retry = 0;
void SenseairTicker()
void Senseair50ms() // Every 50 mSec
{
uint16_t value = 0;
bool data_ready = ModbusReceiveReady();
senseair_state++;
if (6 == senseair_state) { // Every 300 mSec
senseair_state = 0;
if (data_ready) {
uint8_t error = ModbusReceive(&value);
if (error) {
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir response error %d"), error);
AddLog(LOG_LEVEL_DEBUG);
} else {
switch(senseair_read_state) {
case 0: // 0x1A (26) READ_TYPE_LOW - S8: fe 04 02 01 77 ec 92
senseair_type = 2;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir type id low %04X"), value);
AddLog(LOG_LEVEL_DEBUG);
break;
case 1: // 0x00 (0) READ_ERRORLOG - fe 04 02 00 00 ad 24
if (value) {
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir error %04X"), value);
uint16_t value = 0;
bool data_ready = ModbusReceiveReady();
if (data_ready) {
uint8_t error = ModbusReceive(&value);
if (error) {
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir response error %d"), error);
AddLog(LOG_LEVEL_DEBUG);
} else {
switch(senseair_read_state) {
case 0: // 0x1A (26) READ_TYPE_LOW - S8: fe 04 02 01 77 ec 92
senseair_type = 2;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir type id low %04X"), value);
AddLog(LOG_LEVEL_DEBUG);
break;
case 1: // 0x00 (0) READ_ERRORLOG - fe 04 02 00 00 ad 24
if (value) {
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir error %04X"), value);
AddLog(LOG_LEVEL_DEBUG);
}
break;
case 2: // 0x03 (3) READ_CO2 - fe 04 02 06 2c af 59
senseair_co2 = value;
break;
case 3: // 0x04 (4) READ_TEMPERATURE - S8: fe 84 02 f2 f1 - Illegal Data Address
senseair_temperature = ConvertTemp((float)value / 100);
break;
case 4: // 0x05 (5) READ_HUMIDITY - S8: fe 84 02 f2 f1 - Illegal Data Address
senseair_humidity = (float)value / 100;
break;
case 5: // 0x1C (28) READ_RELAY_STATE - S8: fe 04 02 01 54 ad 4b - firmware version
{
bool relay_state = value >> 8 & 1;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir relay state %d"), relay_state);
AddLog(LOG_LEVEL_DEBUG);
break;
}
break;
case 2: // 0x03 (3) READ_CO2 - fe 04 02 06 2c af 59
senseair_co2 = value;
break;
case 3: // 0x04 (4) READ_TEMPERATURE - S8: fe 84 02 f2 f1 - Illegal Data Address
senseair_temperature = ConvertTemp((float)value / 100);
break;
case 4: // 0x05 (5) READ_HUMIDITY - S8: fe 84 02 f2 f1 - Illegal Data Address
senseair_humidity = (float)value / 100;
break;
case 5: // 0x1C (28) READ_RELAY_STATE - S8: fe 04 02 01 54 ad 4b - firmware version
{
bool relay_state = value >> 8 & 1;
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir relay state %d"), relay_state);
AddLog(LOG_LEVEL_DEBUG);
break;
case 6: // 0x0A (10) READ_TEMP_ADJUSTMENT - S8: fe 84 02 f2 f1 - Illegal Data Address
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir temp adjustment %d"), value);
AddLog(LOG_LEVEL_DEBUG);
break;
}
}
senseair_read_state++;
if (2 == senseair_type) { // S8
if (3 == senseair_read_state) {
senseair_read_state = 1;
}
} else { // K30, K70
if (sizeof(start_addresses) == senseair_read_state) {
senseair_read_state = 1;
}
case 6: // 0x0A (10) READ_TEMP_ADJUSTMENT - S8: fe 84 02 f2 f1 - Illegal Data Address
snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "SenseAir temp adjustment %d"), value);
AddLog(LOG_LEVEL_DEBUG);
break;
}
}
senseair_read_state++;
if (2 == senseair_type) { // S8
if (3 == senseair_read_state) {
senseair_read_state = 1;
}
} else { // K30, K70
if (sizeof(start_addresses) == senseair_read_state) {
senseair_read_state = 1;
}
}
}
if (0 == senseair_send_retry || data_ready) {
senseair_send_retry = 5;
ModbusSend(0x04, (uint16_t)start_addresses[senseair_read_state], 1);
} else {
senseair_send_retry--;
if (0 == senseair_send_retry || data_ready) {
senseair_send_retry = 5;
ModbusSend(0x04, (uint16_t)start_addresses[senseair_read_state], 1);
} else {
senseair_send_retry--;
}
}
}
@ -292,9 +179,9 @@ void SenseairInit()
{
senseair_type = 0;
if ((pin[GPIO_SAIR_RX] < 99) && (pin[GPIO_SAIR_TX] < 99)) {
if (SenseairSerial(pin[GPIO_SAIR_RX], pin[GPIO_SAIR_TX])) {
SensairSerial = new TasmotaSerial(pin[GPIO_SAIR_RX], pin[GPIO_SAIR_TX]);
if (SensairSerial->begin()) {
senseair_type = 1;
senseair_ticker.attach_ms(510, SenseairTicker);
}
}
}
@ -306,11 +193,8 @@ void SenseairShow(boolean json)
dtostrfd(senseair_temperature, Settings.flag2.temperature_resolution, temperature);
dtostrfd(senseair_humidity, Settings.flag2.temperature_resolution, humidity);
GetTextIndexed(senseair_types, sizeof(senseair_types), senseair_type -1, kSenseairTypes);
// uint8_t co2_limit = (senseair_co2 > 1200) ? 3 : (senseair_co2 > 800) ? 2 : 1;
// uint16_t co2_limit = senseair_co2 / 400; // <800 = 1(Green), <1200 = 2(Orange), >1200 = 3(Red)
if (json) {
// snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d,\"" D_LIMIT "\":%d"), mqtt_data, senseair_types, senseair_co2, co2_limit);
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":{\"" D_CO2 "\":%d"), mqtt_data, senseair_types, senseair_co2);
if (senseair_type != 2) {
snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_TEMPERATURE "\":%s,\"" D_HUMIDITY "\":%s"), mqtt_data, temperature, humidity);
@ -342,14 +226,17 @@ boolean Xsns17(byte function)
if (senseair_type) {
switch (function) {
case FUNC_XSNS_INIT:
case FUNC_INIT:
SenseairInit();
break;
case FUNC_XSNS_JSON_APPEND:
case FUNC_EVERY_50_MSECOND:
Senseair50ms();
break;
case FUNC_JSON_APPEND:
SenseairShow(1);
break;
#ifdef USE_WEBSERVER
case FUNC_XSNS_WEB_APPEND:
case FUNC_WEB_APPEND:
SenseairShow(0);
break;
#endif // USE_WEBSERVER

View File

@ -104,7 +104,10 @@ void XSnsInit()
xsns_func_ptr[xsns_present++] = &Xsns20;
#endif
XsnsCall(FUNC_XSNS_INIT);
// snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_DEBUG "Sensors found %d"), xsns_present);
// AddLog(LOG_LEVEL_DEBUG);
XsnsCall(FUNC_INIT);
}
/*********************************************************************************************\
@ -115,18 +118,23 @@ boolean XsnsCall(byte Function)
{
boolean result = false;
/*
switch (Function) {
case FUNC_XSNS_INIT:
case FUNC_XSNS_EVERY_SECOND:
case FUNC_XSNS_PREP_BEFORE_TELEPERIOD:
case FUNC_XSNS_JSON_APPEND:
case FUNC_XSNS_WEB_APPEND:
case FUNC_XSNS_SAVE_BEFORE_RESTART:
case FUNC_INIT:
case FUNC_EVERY_50_MSECOND:
case FUNC_EVERY_SECOND:
case FUNC_PREP_BEFORE_TELEPERIOD:
case FUNC_JSON_APPEND:
case FUNC_WEB_APPEND:
case FUNC_SAVE_BEFORE_RESTART:
*/
for (byte x = 0; x < xsns_present; x++) {
xsns_func_ptr[x](Function);
}
/*
break;
}
*/
return result;
}