implemented button - #708

This commit is contained in:
proddy
2021-03-01 22:19:28 +01:00
parent 7521ce6ad3
commit 3eb8ac9194
9 changed files with 560 additions and 216 deletions

View File

@@ -117,6 +117,22 @@ function EMSESPSettingsControllerForm(props: EMSESPSettingsControllerFormProps)
margin="normal" margin="normal"
/> />
<br></br> <br></br>
<Typography variant="h6" color="primary" >
External Button
</Typography>
<TextValidator
validators={['required', 'isNumber', 'minNumber:0', 'maxNumber:40']}
errorMessages={['Button GPIO is required', "Must be a number", "Must be 0 or higher", "Max value is 40"]}
name="pbutton_gpio"
label="Button GPIO pin (0=none)"
fullWidth
variant="outlined"
value={data.pbutton_gpio}
type="number"
onChange={handleValueChange('pbutton_gpio')}
margin="normal"
/>
<br></br>
<Typography variant="h6" color="primary" > <Typography variant="h6" color="primary" >
Dallas Sensor Dallas Sensor
</Typography> </Typography>

View File

@@ -17,6 +17,7 @@ export interface EMSESPSettings {
hide_led: boolean; hide_led: boolean;
api_enabled: boolean; api_enabled: boolean;
analog_enabled: boolean; analog_enabled: boolean;
pbutton_gpio: number;
trace_raw: boolean; trace_raw: boolean;
} }

187
lib/PButton/PButon.cpp Normal file
View File

@@ -0,0 +1,187 @@
/*
* Paul's Button Library
*
* Handles recognition of button presses, short, long, double-click and special sequences
* Used to send specific commands to the ESP8
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "PButton.h"
// Constructor
PButton::PButton() {
// Initialization of default properties
Debounce_ = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms)
DblClickDelay_ = 250; // Max period between clicks for a double click event (in ms)
LongPressDelay_ = 750; // Hold period for a long press event (in ms)
VLongPressDelay_ = 3000; // Hold period for a very long press event (in ms)
cb_onClick = nullptr;
cb_onDblClick = nullptr;
cb_onLongPress = nullptr;
cb_onVLongPress = nullptr;
// Initialization of variables
state_ = true; // Value read from button
lastState_ = true; // Last value of button state
dblClickWaiting_ = false; // whether we're waiting for a double click (down)
dblClickOnNextUp_ = false; // whether to register a double click on next release, or whether to wait and click
singleClickOK_ = true; // whether it's OK to do a single click (default is true)
downTime_ = -1; // time the button was pressed down
upTime_ = -1; // time the button was released
ignoreUP_ = true; // whether to ignore the button release because the click+hold was triggered, changed from false for ESP32
waitForUP_ = false; // when held, whether to wait for the up event
longPressHappened_ = false; // whether or not the hold event happened already
vLongPressHappened_ = false; // whether or not the long hold event happened already
buttonBusy_ = false; // idle
}
bool PButton::init(uint8_t pin, bool pullMode) {
pin_ = pin;
pullMode_ = pullMode; // 1=HIGH (pullup) 0=LOW (pulldown)
#if defined(ESP8266)
pinMode(pin_, pullMode ? INPUT_PULLUP : INPUT_PULLDOWN);
enabled_ = (digitalRead(pin_) != pullMode); // see if a button is connected
#else
// ESP32
pinMode(pin_, INPUT);
enabled_ = (digitalRead(pin_) == pullMode); // see if a button is connected
#endif
return enabled_;
}
void PButton::onClick(uint16_t t, buttonEventHandler handler) {
Debounce_ = t; // Debounce period to prevent flickering when pressing or releasing the button (in ms)
cb_onClick = handler;
}
void PButton::onDblClick(uint16_t t, buttonEventHandler handler) {
DblClickDelay_ = t; // Max period between clicks for a double click event (in ms)
cb_onDblClick = handler;
}
void PButton::onLongPress(uint16_t t, buttonEventHandler handler) {
LongPressDelay_ = t; // Hold period for a long press event (in ms)
cb_onLongPress = handler;
}
void PButton::onVLongPress(uint16_t t, buttonEventHandler handler) {
VLongPressDelay_ = t; // Hold period for a very long press event (in ms)
cb_onVLongPress = handler;
}
bool PButton::check(void) {
if (!enabled_) {
return false;
}
int resultEvent = 0;
long millisRes = millis();
state_ = digitalRead(pin_) == pullMode_;
// Button pressed down
if (state_ != pullMode_ && lastState_ == pullMode_ && (millisRes - upTime_) > Debounce_) {
// Serial.println("*pressed*");
downTime_ = millisRes;
ignoreUP_ = false;
waitForUP_ = false;
singleClickOK_ = true;
longPressHappened_ = false;
vLongPressHappened_ = false;
if ((millisRes - upTime_) < DblClickDelay_ && dblClickOnNextUp_ == false && dblClickWaiting_ == true) {
dblClickOnNextUp_ = true;
// Serial.println("*double up next*");
} else {
dblClickOnNextUp_ = false;
}
dblClickWaiting_ = false;
buttonBusy_ = true; // something is happening so we'll wait and see what action it is
}
// Button released
else if (state_ == pullMode_ && lastState_ != pullMode_ && (millisRes - downTime_) > Debounce_) {
if (ignoreUP_ == false) {
upTime_ = millisRes;
// Serial.println("*released*");
if (dblClickOnNextUp_ == false) {
dblClickWaiting_ = true;
} else {
resultEvent = 2;
// Serial.println("*double*");
dblClickOnNextUp_ = false;
dblClickWaiting_ = false;
singleClickOK_ = false;
}
}
}
// Test for normal click event: DblClickDelay expired
if (state_ == pullMode_ && (millisRes - upTime_) >= DblClickDelay_ && dblClickWaiting_ == true && dblClickOnNextUp_ == false && singleClickOK_ == true && resultEvent != 2) {
// Serial.println("*single click pressed*");
resultEvent = 1;
dblClickWaiting_ = false;
}
// added code: raise OnLongPress event when only when the button is released
if (state_ == pullMode_ && longPressHappened_ && !vLongPressHappened_) {
resultEvent = 3;
longPressHappened_ = false;
}
// Test for hold
if (state_ != pullMode_ && (millisRes - downTime_) >= LongPressDelay_) {
// Trigger "normal" hold
if (longPressHappened_ == false) {
// resultEvent = 3;
waitForUP_ = true;
ignoreUP_ = true;
dblClickOnNextUp_ = false;
dblClickWaiting_ = false;
// _downTime = millis();
longPressHappened_ = true;
}
// Trigger "long" hold
if ((millisRes - downTime_) >= VLongPressDelay_) {
if (vLongPressHappened_ == false) {
resultEvent = 4;
vLongPressHappened_ = true;
// _longPressHappened = false;
}
}
}
lastState_ = state_;
if (resultEvent == 1 && cb_onClick) {
cb_onClick(*this);
} else if (resultEvent == 2 && cb_onDblClick) {
cb_onDblClick(*this);
} else if (resultEvent == 3 && cb_onLongPress) {
cb_onLongPress(*this);
} else if (resultEvent == 4 && cb_onVLongPress) {
cb_onVLongPress(*this);
}
// if any action has been prefromed we can stop waiting, and become idle
if (resultEvent >= 1 && resultEvent <= 4) {
buttonBusy_ = false;
}
return buttonBusy_;
}

69
lib/PButton/PButton.h Normal file
View File

@@ -0,0 +1,69 @@
/*
* Paul's Button Library
*
* Handles recognition of button presses, short, long, double-click and special sequences
* Used to send specific commands to the ESP8
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _PButton_H_
#define _PButton_H_
#include <Arduino.h>
class PButton {
public:
PButton();
~PButton() = default;
typedef void (*buttonEventHandler)(PButton &);
void onClick(uint16_t t, buttonEventHandler handler);
void onDblClick(uint16_t, buttonEventHandler handler);
void onLongPress(uint16_t t, buttonEventHandler handler);
void onVLongPress(uint16_t, buttonEventHandler handler);
bool init(uint8_t pin, bool pullMode);
bool check(void);
private:
uint16_t Debounce_; // Debounce period to prevent flickering when pressing or releasing the button (in ms)
uint16_t DblClickDelay_; // Max period between clicks for a double click event (in ms)
uint16_t LongPressDelay_; // Hold period for a long press event (in ms)
uint16_t VLongPressDelay_; // Hold period for a very long press event (in ms)
uint8_t pin_;
bool pullMode_;
bool enabled_;
bool state_; // Value read from button
bool lastState_; // Last value of button state
bool dblClickWaiting_; // whether we're waiting for a double click (down)
bool dblClickOnNextUp_; // whether to register a double click on next release, or whether to wait and click
bool singleClickOK_; // whether it's OK to do a single click
uint32_t downTime_; // time the button was pressed down
uint32_t upTime_; // time the button was released
bool ignoreUP_; // whether to ignore the button release because the click+hold was triggered
bool waitForUP_; // when held, whether to wait for the up event
bool longPressHappened_; // whether or not the hold event happened already
bool vLongPressHappened_; // whether or not the long hold event happened already
bool buttonBusy_; // false if idle
buttonEventHandler cb_onClick, cb_onDblClick, cb_onLongPress, cb_onVLongPress;
};
#endif

View File

@@ -17,8 +17,8 @@ MAKEFLAGS+="j "
#TARGET := $(notdir $(CURDIR)) #TARGET := $(notdir $(CURDIR))
TARGET := emsesp TARGET := emsesp
BUILD := build BUILD := build
SOURCES := src lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src src/test SOURCES := src lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src src/devices lib/ArduinoJson/src lib/PButton src/test
INCLUDES := lib/ArduinoJson/src lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src src/devices lib src INCLUDES := lib/ArduinoJson/src lib_standalone lib/uuid-common/src lib/uuid-console/src lib/uuid-log/src lib/uuid-telnet/src lib/uuid-syslog/src lib/PButton src/devices lib src
LIBRARIES := LIBRARIES :=
CPPCHECK = cppcheck CPPCHECK = cppcheck

View File

@@ -48,6 +48,7 @@ void WebSettings::read(WebSettings & settings, JsonObject & root) {
root["hide_led"] = settings.hide_led; root["hide_led"] = settings.hide_led;
root["api_enabled"] = settings.api_enabled; root["api_enabled"] = settings.api_enabled;
root["analog_enabled"] = settings.analog_enabled; root["analog_enabled"] = settings.analog_enabled;
root["pbutton_gpio"] = settings.pbutton_gpio;
} }
StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) { StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings) {
@@ -67,36 +68,32 @@ StateUpdateResult WebSettings::update(JsonObject & root, WebSettings & settings)
} }
// syslog // syslog
snprintf_P(&crc_before[0], snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d%d%d%s"), settings.syslog_enabled, settings.syslog_level, settings.syslog_mark_interval, settings.syslog_host.c_str());
crc_before.capacity() + 1,
PSTR("%d%d%d%s"),
settings.syslog_enabled,
settings.syslog_level,
settings.syslog_mark_interval,
settings.syslog_host.c_str());
settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED; settings.syslog_enabled = root["syslog_enabled"] | EMSESP_DEFAULT_SYSLOG_ENABLED;
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL; settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL; settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST; settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW; settings.trace_raw = root["trace_raw"] | EMSESP_DEFAULT_TRACELOG_RAW;
EMSESP::trace_raw(settings.trace_raw); EMSESP::trace_raw(settings.trace_raw);
snprintf_P(&crc_after[0], snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d%d%d%s"), settings.syslog_enabled, settings.syslog_level, settings.syslog_mark_interval, settings.syslog_host.c_str());
crc_after.capacity() + 1,
PSTR("%d%d%d%s"),
settings.syslog_enabled,
settings.syslog_level,
settings.syslog_mark_interval,
settings.syslog_host.c_str());
if (crc_before != crc_after) { if (crc_before != crc_after) {
add_flags(ChangeFlags::SYSLOG); add_flags(ChangeFlags::SYSLOG);
} }
// other // adc
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d"), settings.analog_enabled); snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d"), settings.analog_enabled);
settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED; settings.analog_enabled = root["analog_enabled"] | EMSESP_DEFAULT_ANALOG_ENABLED;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d"), settings.analog_enabled); snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d"), settings.analog_enabled);
if (crc_before != crc_after) { if (crc_before != crc_after) {
add_flags(ChangeFlags::OTHER); add_flags(ChangeFlags::ADC);
}
// button
snprintf_P(&crc_before[0], crc_before.capacity() + 1, PSTR("%d"), settings.pbutton_gpio);
settings.pbutton_gpio = root["pbutton_gpio"] | EMSESP_DEFAULT_PBUTTON_GPIO;
snprintf_P(&crc_after[0], crc_after.capacity() + 1, PSTR("%d"), settings.pbutton_gpio);
if (crc_before != crc_after) {
add_flags(ChangeFlags::BUTTON);
} }
// dallas // dallas
@@ -152,15 +149,19 @@ void WebSettingsService::onUpdate() {
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::SYSLOG)) {
System::syslog_init(); EMSESP::system_.syslog_init(true); // reload settings
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::OTHER)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::ADC)) {
System::other_init(); EMSESP::system_.adc_init(true); // reload settings
}
if (WebSettings::has_flags(WebSettings::ChangeFlags::BUTTON)) {
EMSESP::system_.button_init(true); // reload settings
} }
if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) { if (WebSettings::has_flags(WebSettings::ChangeFlags::LED)) {
System::led_init(); EMSESP::system_.led_init(true); // reload settings
} }
} }

View File

@@ -36,13 +36,7 @@
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set #define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
#define EMSESP_DEFAULT_SHOWER_TIMER false #define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false #define EMSESP_DEFAULT_SHOWER_ALERT false
#if defined(ESP32)
#define EMSESP_DEFAULT_HIDE_LED true #define EMSESP_DEFAULT_HIDE_LED true
#else
#define EMSESP_DEFAULT_HIDE_LED false
#endif
#define EMSESP_DEFAULT_DALLAS_PARASITE false #define EMSESP_DEFAULT_DALLAS_PARASITE false
#define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure #define EMSESP_DEFAULT_API_ENABLED false // turn off, because its insecure
#define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off #define EMSESP_DEFAULT_BOOL_FORMAT 1 // on/off
@@ -54,12 +48,14 @@
#define EMSESP_DEFAULT_TX_GPIO 5 // D8 on Wemos D1-32, OR 16 for UART2 on Lolin D32 #define EMSESP_DEFAULT_TX_GPIO 5 // D8 on Wemos D1-32, OR 16 for UART2 on Lolin D32
#define EMSESP_DEFAULT_DALLAS_GPIO 18 // 18 on Wemos D1-32, 14 on LOLIN D32 #define EMSESP_DEFAULT_DALLAS_GPIO 18 // 18 on Wemos D1-32, 14 on LOLIN D32
#define EMSESP_DEFAULT_LED_GPIO 2 // 2 on Wemos D1-32, 5 on LOLIN D32 #define EMSESP_DEFAULT_LED_GPIO 2 // 2 on Wemos D1-32, 5 on LOLIN D32
#define EMSESP_DEFAULT_PBUTTON_GPIO 0 // default GPIO is 0 (off)
#else #else
// for standalone // for standalone
#define EMSESP_DEFAULT_RX_GPIO 0 #define EMSESP_DEFAULT_RX_GPIO 0
#define EMSESP_DEFAULT_TX_GPIO 0 #define EMSESP_DEFAULT_TX_GPIO 0
#define EMSESP_DEFAULT_DALLAS_GPIO 0 #define EMSESP_DEFAULT_DALLAS_GPIO 0
#define EMSESP_DEFAULT_LED_GPIO 0 #define EMSESP_DEFAULT_LED_GPIO 0
#define EMSESP_DEFAULT_PBUTTON_GPIO 0
#endif #endif
namespace emsesp { namespace emsesp {
@@ -85,6 +81,7 @@ class WebSettings {
bool hide_led; bool hide_led;
bool api_enabled; bool api_enabled;
bool analog_enabled; bool analog_enabled;
uint8_t pbutton_gpio;
static void read(WebSettings & settings, JsonObject & root); static void read(WebSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, WebSettings & settings); static StateUpdateResult update(JsonObject & root, WebSettings & settings);
@@ -94,10 +91,11 @@ class WebSettings {
NONE = 0, NONE = 0,
UART = (1 << 0), UART = (1 << 0),
SYSLOG = (1 << 1), SYSLOG = (1 << 1),
OTHER = (1 << 2), ADC = (1 << 2),
DALLAS = (1 << 3), DALLAS = (1 << 3),
SHOWER = (1 << 4), SHOWER = (1 << 4),
LED = (1 << 5) LED = (1 << 5),
BUTTON = (1 << 6)
}; };

View File

@@ -19,8 +19,6 @@
#include "system.h" #include "system.h"
#include "emsesp.h" // for send_raw_telegram() command #include "emsesp.h" // for send_raw_telegram() command
#include "version.h" // firmware version of EMS-ESP
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
#include "test/test.h" #include "test/test.h"
#endif #endif
@@ -35,14 +33,7 @@ uuid::syslog::SyslogService System::syslog_;
// init statics // init statics
uint32_t System::heap_start_ = 1; // avoid using 0 to divide-by-zero later uint32_t System::heap_start_ = 1; // avoid using 0 to divide-by-zero later
bool System::upload_status_ = false; PButton System::myPButton_;
bool System::hide_led_ = false;
uint8_t System::led_gpio_ = 0;
uint16_t System::analog_ = 0;
bool System::analog_enabled_ = false;
bool System::syslog_enabled_ = false;
std::string System::hostname_;
bool System::ethernet_connected_ = false;
// send on/off to a gpio pin // send on/off to a gpio pin
// value: true = HIGH, false = LOW // value: true = HIGH, false = LOW
@@ -106,6 +97,7 @@ void System::restart() {
void System::wifi_reconnect() { void System::wifi_reconnect() {
LOG_INFO(F("Wifi reconnecting...")); LOG_INFO(F("Wifi reconnecting..."));
Shell::loop_all(); Shell::loop_all();
EMSESP::console_.loop();
delay(1000); // wait a second delay(1000); // wait a second
EMSESP::webSettingsService.save(); // local settings EMSESP::webSettingsService.save(); // local settings
EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password EMSESP::esp8266React.getNetworkSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password
@@ -127,21 +119,12 @@ void System::format(uuid::console::Shell & shell) {
System::restart(); System::restart();
} }
void System::syslog_init() { void System::syslog_init(bool refresh) {
int8_t syslog_level_; if (refresh) {
uint32_t syslog_mark_interval_; get_settings();
String syslog_host_; }
// fetch settings
EMSESP::webSettingsService.read([&](WebSettings & settings) {
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
});
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// check for empty hostname // check for empty hostname
IPAddress addr; IPAddress addr;
if (!addr.fromString(syslog_host_.c_str())) { if (!addr.fromString(syslog_host_.c_str())) {
@@ -161,137 +144,150 @@ void System::syslog_init() {
syslog_.log_level((uuid::log::Level)syslog_level_); syslog_.log_level((uuid::log::Level)syslog_level_);
syslog_.mark_interval(syslog_mark_interval_); syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(addr); syslog_.destination(addr);
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { syslog_.hostname(networkSettings.hostname.c_str()); }); syslog_.hostname(hostname_.c_str());
EMSESP::logger().info(F("Syslog started")); EMSESP::logger().info(F("Syslog started"));
#endif #endif
} }
// read all the settings from the config files and store locally
void System::get_settings() {
EMSESP::webSettingsService.read([&](WebSettings & settings) {
// BUTTON
pbutton_gpio_ = settings.pbutton_gpio;
// ADC
analog_enabled_ = settings.analog_enabled;
// SYSLOG
syslog_enabled_ = settings.syslog_enabled;
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
// LED
hide_led_ = settings.hide_led;
led_gpio_ = settings.led_gpio;
});
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) {
hostname(networkSettings.hostname.c_str());
LOG_INFO(F("System %s booted (EMS-ESP version %s)"), networkSettings.hostname.c_str(), EMSESP_APP_VERSION); // print boot message
ethernet_profile_ = networkSettings.ethernet_profile;
});
}
// first call. Sets memory and starts up the UART Serial bridge // first call. Sets memory and starts up the UART Serial bridge
void System::start(uint32_t heap_start) { void System::start(uint32_t heap_start) {
#if defined(EMSESP_DEBUG)
show_mem("Startup");
#endif
// set the inital free mem, only on first boot // set the inital free mem, only on first boot
if (heap_start_ < 2) { if (heap_start_ < 2) {
heap_start_ = heap_start; heap_start_ = heap_start;
} }
#if defined(EMSESP_DEBUG) // load in all the settings first
show_mem("Startup"); get_settings();
#endif
uint8_t ethernet_profile; commands_init(); // console & api commands
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & networkSettings) { button_init(false); // the special button
LOG_INFO(F("System %s booted (EMS-ESP version %s)"), networkSettings.hostname.c_str(), EMSESP_APP_VERSION); // print boot message led_init(false); // init LED
ethernet_profile = networkSettings.ethernet_profile; syslog_init(false); // init SysLog
}); adc_init(false); // analog ADC
network_init(); // network
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""} EMSESP::init_tx(); // start UART
EMSESP::webSettingsService.read([&](WebSettings & settings) {
Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings);
#if defined(EMSESP_TEST)
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test);
#endif
});
// start other services first
init();
// check ethernet profile, if we're using exclusive Ethernet then disabled wifi and AP/captive portal
if (ethernet_profile == 0) {
return;
} }
uint8_t phy_addr; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110) // adc and bluetooth
int power; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source) void System::adc_init(bool refresh) {
int mdc; // Pin# of the I²C clock signal for the Ethernet PHY if (refresh) {
int mdio; // Pin# of the I²C IO signal for the Ethernet PHY get_settings();
eth_phy_type_t type; // Type of the Ethernet PHY (LAN8720 or TLK110)
eth_clock_mode_t clock_mode; // ETH_CLOCK_GPIO0_IN or ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT for 50Hz inverted clock
if (ethernet_profile == 1) {
// LAN8720
phy_addr = 0;
power = -1;
mdc = 23;
mdio = 18;
type = ETH_PHY_LAN8720;
clock_mode = ETH_CLOCK_GPIO0_IN;
} else if (ethernet_profile == 2) {
// TLK110
phy_addr = 31;
power = -1;
mdc = 23;
mdio = 18;
type = ETH_PHY_TLK110;
clock_mode = ETH_CLOCK_GPIO0_IN;
} }
#ifndef EMSESP_STANDALONE
if (ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode)) {
// disable ssid and AP
EMSESP::esp8266React.getNetworkSettingsService()->update(
[&](NetworkSettings & settings) {
settings.ssid == ""; // remove SSID
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getAPSettingsService()->update(
[&](APSettings & settings) {
settings.provisionMode = AP_MODE_NEVER;
return StateUpdateResult::CHANGED;
},
"local");
}
#endif
}
void System::other_init() {
// set the boolean format used for rendering booleans
EMSESP::webSettingsService.read([&](WebSettings & settings) { analog_enabled_ = settings.analog_enabled; });
}
// init stuff. This is called when settings are changed in the web
void System::init() {
led_init(); // init LED
other_init(); // boolean format and analog setting
syslog_init(); // init SysLog
EMSESP::esp8266React.getNetworkSettingsService()->read([&](NetworkSettings & settings) { hostname(settings.hostname.c_str()); });
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
// setCpuFrequencyMhz(160); // default is 240 // setCpuFrequencyMhz(160); // default is 240
// disable bluetooth // disable bluetooth
btStop(); btStop();
esp_bt_controller_disable(); esp_bt_controller_disable();
// turn off ADC to save power if not needed
if (!analog_enabled_) { if (!analog_enabled_) {
adc_power_off(); adc_power_off(); // turn off ADC to save power if not needed
} }
#endif #endif
}
EMSESP::init_tx(); // start UART // button single click
void System::button_OnClick(PButton & b) {
LOG_DEBUG(F("Button pressed - single click"));
}
// button double click
void System::button_OnDblClick(PButton & b) {
LOG_DEBUG(F("Button pressed - double click - reconnect"));
EMSESP::system_.wifi_reconnect();
}
// button long press
void System::button_OnLongPress(PButton & b) {
LOG_DEBUG(F("Button pressed - long press"));
}
// button indefinite press
void System::button_OnVLongPress(PButton & b) {
LOG_DEBUG(F("Button pressed - very long press"));
LOG_WARNING(F("Performing factory reset..."));
EMSESP::console_.loop();
#ifndef EMSESP_STANDALONE
// remove all files under config
File root = LITTLEFS.open(FS_CONFIG_DIRECTORY);
File file;
Serial.printf("Removing files: ", file.name());
while (file = root.openNextFile()) {
Serial.printf("%s ", file.name());
LITTLEFS.remove(file.name());
}
Serial.println();
// restart
WiFi.disconnect(true);
delay(500);
ESP.restart();
#endif
}
// push button
void System::button_init(bool refresh) {
if (refresh) {
get_settings();
}
if (pbutton_gpio_) {
if (!myPButton_.init(pbutton_gpio_, HIGH)) {
LOG_INFO(F("External multi-functional button not detected"));
} else {
LOG_INFO(F("External multi-functional button enabled"));
}
myPButton_.onClick(BUTTON_Debounce, button_OnClick);
myPButton_.onDblClick(BUTTON_DblClickDelay, button_OnDblClick);
myPButton_.onLongPress(BUTTON_LongPressDelay, button_OnLongPress);
myPButton_.onVLongPress(BUTTON_VLongPressDelay, button_OnVLongPress);
}
} }
// set the LED to on or off when in normal operating mode // set the LED to on or off when in normal operating mode
void System::led_init() { void System::led_init(bool refresh) {
EMSESP::webSettingsService.read([&](WebSettings & settings) { if (refresh) {
hide_led_ = settings.hide_led; get_settings();
led_gpio_ = settings.led_gpio; }
if (led_gpio_) { if (led_gpio_) {
pinMode(led_gpio_, OUTPUT); // 0 means disabled pinMode(led_gpio_, OUTPUT); // 0 means disabled
digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); // LED on, for ever digitalWrite(led_gpio_, hide_led_ ? !LED_ON : LED_ON); // LED on, for ever
} }
});
} }
// returns true if OTA is uploading // returns true if OTA is uploading
@@ -314,6 +310,7 @@ void System::upload_status(bool in_progress) {
// checks system health and handles LED flashing wizardry // checks system health and handles LED flashing wizardry
void System::loop() { void System::loop() {
#ifndef EMSESP_STANDALONE #ifndef EMSESP_STANDALONE
myPButton_.check(); // check button press
if (syslog_enabled_) { if (syslog_enabled_) {
syslog_.loop(); syslog_.loop();
@@ -429,7 +426,57 @@ void System::set_led_speed(uint32_t speed) {
led_monitor(); led_monitor();
} }
void System::init_network() { // initializes network
void System::network_init() {
// check ethernet profile, if we're using exclusive Ethernet then disabled wifi and AP/captive portal
if (ethernet_profile_ == 0) {
return;
}
uint8_t phy_addr; // I²C-address of Ethernet PHY (0 or 1 for LAN8720, 31 for TLK110)
int power; // Pin# of the enable signal for the external crystal oscillator (-1 to disable for internal APLL source)
int mdc; // Pin# of the I²C clock signal for the Ethernet PHY
int mdio; // Pin# of the I²C IO signal for the Ethernet PHY
eth_phy_type_t type; // Type of the Ethernet PHY (LAN8720 or TLK110)
eth_clock_mode_t clock_mode; // ETH_CLOCK_GPIO0_IN or ETH_CLOCK_GPIO0_OUT, ETH_CLOCK_GPIO16_OUT, ETH_CLOCK_GPIO17_OUT for 50Hz inverted clock
if (ethernet_profile_ == 1) {
// LAN8720
phy_addr = 0;
power = -1;
mdc = 23;
mdio = 18;
type = ETH_PHY_LAN8720;
clock_mode = ETH_CLOCK_GPIO0_IN;
} else if (ethernet_profile_ == 2) {
// TLK110
phy_addr = 31;
power = -1;
mdc = 23;
mdio = 18;
type = ETH_PHY_TLK110;
clock_mode = ETH_CLOCK_GPIO0_IN;
}
#ifndef EMSESP_STANDALONE
if (ETH.begin(phy_addr, power, mdc, mdio, type, clock_mode)) {
// disable ssid and AP
EMSESP::esp8266React.getNetworkSettingsService()->update(
[&](NetworkSettings & settings) {
settings.ssid == ""; // remove SSID
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::esp8266React.getAPSettingsService()->update(
[&](APSettings & settings) {
settings.provisionMode = AP_MODE_NEVER;
return StateUpdateResult::CHANGED;
},
"local");
}
#endif
last_system_check_ = 0; // force the LED to go from fast flash to pulse last_system_check_ = 0; // force the LED to go from fast flash to pulse
send_heartbeat(); send_heartbeat();
} }
@@ -467,6 +514,20 @@ void System::system_check() {
} }
} }
// commands - takes static function pointers
void System::commands_init() {
// these commands respond to the topic "system" and take a payload like {cmd:"", data:"", id:""}
Command::add(EMSdevice::DeviceType::SYSTEM, F_(pin), System::command_pin);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(send), System::command_send);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(publish), System::command_publish);
Command::add(EMSdevice::DeviceType::SYSTEM, F_(fetch), System::command_fetch);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(info), System::command_info);
Command::add_with_json(EMSdevice::DeviceType::SYSTEM, F_(settings), System::command_settings);
#if defined(EMSESP_TEST)
Command::add(EMSdevice::DeviceType::SYSTEM, F("test"), System::command_test);
#endif
}
// flashes the LED // flashes the LED
void System::led_monitor() { void System::led_monitor() {
if (!led_gpio_) { if (!led_gpio_) {
@@ -579,7 +640,7 @@ void System::show_system(uuid::console::Shell & shell) {
shell.println(); shell.println();
// show Ethernet // show Ethernet
if (ethernet_connected()) { if (ethernet_connected_) {
shell.printfln(F("Ethernet: Connected")); shell.printfln(F("Ethernet: Connected"));
shell.printfln(F("MAC address: %s"), ETH.macAddress().c_str()); shell.printfln(F("MAC address: %s"), ETH.macAddress().c_str());
shell.printfln(F("Hostname: %s"), ETH.getHostname()); shell.printfln(F("Hostname: %s"), ETH.getHostname());
@@ -590,21 +651,18 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("Ethernet: disconnected")); shell.printfln(F("Ethernet: disconnected"));
} }
EMSESP::webSettingsService.read([&](WebSettings & settings) {
shell.println(); shell.println();
if (!syslog_enabled_) {
if (!settings.syslog_enabled) {
shell.printfln(F("Syslog: disabled")); shell.printfln(F("Syslog: disabled"));
} else { } else {
shell.printfln(F("Syslog:")); shell.printfln(F("Syslog:"));
shell.print(F(" ")); shell.print(F(" "));
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str()); shell.printfln(F_(host_fmt), !syslog_host_.isEmpty() ? syslog_host_.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.print(F(" ")); shell.print(F(" "));
shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(settings.syslog_level))); shell.printfln(F_(log_level_fmt), uuid::log::format_level_lowercase(static_cast<uuid::log::Level>(syslog_level_)));
shell.print(F(" ")); shell.print(F(" "));
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval); shell.printfln(F_(mark_interval_fmt), syslog_mark_interval_);
} }
});
#endif #endif
} }
@@ -614,19 +672,19 @@ void System::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::SYSTEM, EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(restart)}, flash_string_vector{F_(restart)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { restart(); }); [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.restart(); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM, EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN, CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(reconnect)}, flash_string_vector{F_(wifi), F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { wifi_reconnect(); }); [](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::system_.wifi_reconnect(); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(format)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(format)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) { shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) { if (completed) {
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) { EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) { if (securitySettings.jwtSecret.equals(password.c_str())) {
format(shell); EMSESP::system_.format(shell);
} else { } else {
shell.println(F("incorrect password")); shell.println(F("incorrect password"));
} }
@@ -658,7 +716,7 @@ void System::console_commands(Shell & shell, unsigned int context) {
}); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::USER, flash_string_vector{F_(show)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::USER, flash_string_vector{F_(show)}, [=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
show_system(shell); EMSESP::system_.show_system(shell);
shell.println(); shell.println();
}); });
@@ -725,7 +783,7 @@ void System::console_commands(Shell & shell, unsigned int context) {
}); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(show), F_(users)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESPShell::commands->add_command(ShellContext::SYSTEM, CommandFlags::ADMIN, flash_string_vector{F_(show), F_(users)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
System::show_users(shell); EMSESP::system_.show_users(shell);
}); });
// enter the context // enter the context
@@ -825,6 +883,7 @@ bool System::command_settings(const char * value, const int8_t id, JsonObject &
node["hide_led"] = settings.hide_led; node["hide_led"] = settings.hide_led;
node["api_enabled"] = settings.api_enabled; node["api_enabled"] = settings.api_enabled;
node["analog_enabled"] = settings.analog_enabled; node["analog_enabled"] = settings.analog_enabled;
node["pbutton_gpio"] = settings.pbutton_gpio;
}); });
return true; return true;
@@ -894,4 +953,5 @@ bool System::command_test(const char * value, const int8_t id) {
} }
#endif #endif
} // namespace emsesp } // namespace emsesp

View File

@@ -36,6 +36,7 @@
#endif #endif
#include <uuid/log.h> #include <uuid/log.h>
#include <PButton.h>
using uuid::console::Shell; using uuid::console::Shell;
@@ -47,92 +48,103 @@ class System {
void loop(); void loop();
// commands // commands
static void restart();
static void format(uuid::console::Shell & shell);
static void console_commands(Shell & shell, unsigned int context); static void console_commands(Shell & shell, unsigned int context);
static bool command_pin(const char * value, const int8_t id); static bool command_pin(const char * value, const int8_t id);
static bool command_send(const char * value, const int8_t id); static bool command_send(const char * value, const int8_t id);
static bool command_publish(const char * value, const int8_t id); static bool command_publish(const char * value, const int8_t id);
static bool command_fetch(const char * value, const int8_t id); static bool command_fetch(const char * value, const int8_t id);
static bool command_info(const char * value, const int8_t id, JsonObject & json); static bool command_info(const char * value, const int8_t id, JsonObject & json);
static bool command_settings(const char * value, const int8_t id, JsonObject & json); static bool command_settings(const char * value, const int8_t id, JsonObject & json);
#if defined(EMSESP_TEST) #if defined(EMSESP_TEST)
static bool command_test(const char * value, const int8_t id); static bool command_test(const char * value, const int8_t id);
#endif #endif
static void upload_status(bool in_progress); void restart();
static bool upload_status(); void format(uuid::console::Shell & shell);
static void show_mem(const char * note); void upload_status(bool in_progress);
void init_network(); bool upload_status();
static void init(); void show_mem(const char * note);
static void led_init(); void get_settings();
static void syslog_init(); void led_init(bool refresh);
static void other_init(); void syslog_init(bool refresh);
void adc_init(bool refresh);
void network_init();
void commands_init();
void button_init(bool refresh);
bool check_upgrade(); bool check_upgrade();
void send_heartbeat(); void send_heartbeat();
static std::string hostname() { std::string hostname() {
return hostname_; return hostname_;
} }
static void hostname(std::string hostname) { void hostname(std::string hostname) {
hostname_ = hostname; hostname_ = hostname;
} }
static bool ethernet_connected() { bool ethernet_connected() {
return ethernet_connected_; return ethernet_connected_;
} }
static void ethernet_connected(bool b) { void ethernet_connected(bool b) {
ethernet_connected_ = b; ethernet_connected_ = b;
} }
private: private:
static uuid::log::Logger logger_; static uuid::log::Logger logger_;
static uint32_t heap_start_;
#ifndef EMSESP_STANDALONE // button
static uuid::syslog::SyslogService syslog_; static PButton myPButton_; // PButton instance
#endif static void button_OnClick(PButton & b);
static void button_OnDblClick(PButton & b);
static void button_OnLongPress(PButton & b);
static void button_OnVLongPress(PButton & b);
static constexpr uint32_t BUTTON_Debounce = 40; // Debounce period to prevent flickering when pressing or releasing the button (in ms)
static constexpr uint32_t BUTTON_DblClickDelay = 250; // Max period between clicks for a double click event (in ms)
static constexpr uint32_t BUTTON_LongPressDelay = 750; // Hold period for a long press event (in ms)
static constexpr uint32_t BUTTON_VLongPressDelay = 9000; // Hold period for a very long press event (in ms)
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 5000; // check every 5 seconds
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min) static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 500; static constexpr uint32_t SYSTEM_MEASURE_ANALOG_INTERVAL = 500;
static constexpr uint8_t LED_ON = LOW; // internal LED
// internal LED #ifndef EMSESP_STANDALONE
static constexpr uint8_t LED_ON = LOW; static uuid::syslog::SyslogService syslog_;
#endif
void led_monitor(); void led_monitor();
void set_led_speed(uint32_t speed); void set_led_speed(uint32_t speed);
void system_check(); void system_check();
void measure_analog(); void measure_analog();
static void show_system(uuid::console::Shell & shell); void show_system(uuid::console::Shell & shell);
static void show_users(uuid::console::Shell & shell); void show_users(uuid::console::Shell & shell);
static void wifi_reconnect(); void wifi_reconnect();
static int8_t wifi_quality(); int8_t wifi_quality();
bool system_healthy_ = false; bool system_healthy_ = false;
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly
uint32_t last_heartbeat_ = 0; uint32_t last_heartbeat_ = 0;
uint32_t last_system_check_ = 0; uint32_t last_system_check_ = 0;
bool upload_status_ = false; // true if we're in the middle of a OTA firmware upload
static bool upload_status_; // true if we're in the middle of a OTA firmware upload bool ethernet_connected_;
static uint32_t heap_start_; uint16_t analog_;
static uint16_t analog_;
static bool ethernet_connected_;
// settings // settings
static std::string hostname_; std::string hostname_;
static bool hide_led_; bool hide_led_;
static uint8_t led_gpio_; uint8_t led_gpio_;
static bool syslog_enabled_; bool syslog_enabled_;
static bool analog_enabled_; bool analog_enabled_;
uint8_t ethernet_profile_;
uint8_t pbutton_gpio_;
int8_t syslog_level_;
uint32_t syslog_mark_interval_;
String syslog_host_;
}; };
} // namespace emsesp } // namespace emsesp