Merge remote-tracking branch 'origin/v2_web' into v2

This commit is contained in:
proddy
2020-07-17 20:10:27 +02:00
260 changed files with 26444 additions and 2553 deletions

View File

@@ -0,0 +1,35 @@
#include "EMSESPDevicesService.h"
#include "emsesp.h"
#include "mqtt.h"
namespace emsesp {
EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(EMSESP_DEVICES_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::emsespDevicesService, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
}
void EMSESPDevicesService::emsespDevicesService(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE);
JsonObject root = response->getRoot();
JsonArray devices = root.createNestedArray("devices");
for (const auto & emsdevice : EMSESP::emsdevices) {
if (emsdevice) {
JsonObject deviceRoot = devices.createNestedObject();
deviceRoot["type"] = emsdevice->device_type_name();
deviceRoot["brand"] = emsdevice->brand_to_string();
deviceRoot["name"] = emsdevice->name();
deviceRoot["deviceid"] = emsdevice->device_id();
deviceRoot["productid"] = emsdevice->product_id();
deviceRoot["version"] = emsdevice->version();
}
}
response->setLength();
request->send(response);
}
} // namespace emsesp

View File

@@ -0,0 +1,30 @@
#ifndef EMSESPDevicesService_h
#define EMSESPDevicesService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
// #include <HttpEndpoint.h>
// #include <MqttPubSub.h>
// #include <WebSocketTxRx.h>
#include "version.h"
#define MAX_EMSESP_STATUS_SIZE 1024
#define EMSESP_DEVICES_SERVICE_PATH "/rest/emsespDevices"
namespace emsesp {
class EMSESPDevicesService {
public:
EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
private:
void emsespDevicesService(AsyncWebServerRequest * request);
};
} // namespace emsesp
#endif

View File

@@ -0,0 +1,20 @@
#include <EMSESPScanDevicesService.h>
#include "emsesp.h"
namespace emsesp {
EMSESPScanDevicesService::EMSESPScanDevicesService(AsyncWebServer * server, SecurityManager * securityManager) {
server->on(SCAN_DEVICES_SERVICE_PATH,
HTTP_POST,
securityManager->wrapRequest(std::bind(&EMSESPScanDevicesService::scan_devices, this, std::placeholders::_1), AuthenticationPredicates::IS_ADMIN));
}
void EMSESPScanDevicesService::scan_devices(AsyncWebServerRequest * request) {
request->onDisconnect([]() {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
});
request->send(200);
}
} // namespace emsesp

View File

@@ -0,0 +1,21 @@
#ifndef EMSESPScanDevicesService_h
#define EMSESPScanDevicesService_h
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
namespace emsesp {
class EMSESPScanDevicesService {
public:
EMSESPScanDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
private:
void scan_devices(AsyncWebServerRequest * request);
};
} // namespace emsesp
#endif

View File

@@ -0,0 +1,67 @@
#include "EMSESPSettingsService.h"
#include "emsesp.h"
namespace emsesp {
EMSESPSettingsService::EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
: _httpEndpoint(EMSESPSettings::read, EMSESPSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
, _fsPersistence(EMSESPSettings::read, EMSESPSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
}
void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
root["tx_mode"] = settings.tx_mode;
root["ems_bus_id"] = settings.ems_bus_id;
root["syslog_level"] = settings.syslog_level;
root["syslog_mark_interval"] = settings.syslog_mark_interval;
root["syslog_host"] = settings.syslog_host;
root["master_thermostat"] = settings.master_thermostat;
root["shower_timer"] = settings.shower_timer;
root["shower_alert"] = settings.shower_alert;
}
StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) {
EMSESPSettings newSettings = {};
newSettings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
newSettings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
newSettings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
newSettings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
newSettings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
newSettings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
newSettings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
newSettings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
bool changed = false;
if (newSettings.tx_mode != settings.tx_mode) {
EMSESP::reset_tx(newSettings.tx_mode); // reset counters
changed = true;
}
if ((newSettings.shower_timer != settings.shower_timer) || (newSettings.shower_alert != settings.shower_alert)) {
EMSESP::shower_.start();
changed = true;
}
if ((newSettings.syslog_level != settings.syslog_level) || (newSettings.syslog_mark_interval != settings.syslog_mark_interval)
|| !newSettings.syslog_host.equals(settings.syslog_host)) {
EMSESP::system_.syslog_init();
changed = true;
}
if (changed) {
settings = newSettings;
return StateUpdateResult::CHANGED;
}
return StateUpdateResult::UNCHANGED;
}
void EMSESPSettingsService::begin() {
_fsPersistence.readFromFS();
}
} // namespace emsesp

View File

@@ -0,0 +1,55 @@
#ifndef EMSESPSettingsConfig_h
#define EMSESPSettingsConfig_h
#include <HttpEndpoint.h>
#include <FSPersistence.h>
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
#define EMSESP_DEFAULT_TX_MODE 1
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
#define EMSESP_DEFAULT_SYSLOG_LEVEL -1
#define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0
#define EMSESP_DEFAULT_SYSLOG_HOST ""
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
#define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false
namespace emsesp {
enum MQTT_format : uint8_t { SINGLE = 1, NESTED, HA, CUSTOM };
class EMSESPSettings {
public:
uint8_t tx_mode;
uint8_t ems_bus_id;
uint8_t master_thermostat;
bool shower_timer;
bool shower_alert;
// syslog
int8_t syslog_level; // uuid::log::Level
uint32_t syslog_mark_interval;
String syslog_host;
static void read(EMSESPSettings & settings, JsonObject & root);
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);
};
class EMSESPSettingsService : public StatefulService<EMSESPSettings> {
public:
EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
void begin();
private:
HttpEndpoint<EMSESPSettings> _httpEndpoint;
FSPersistence<EMSESPSettings> _fsPersistence;
};
} // namespace emsesp
#endif

View File

@@ -0,0 +1,67 @@
#include "EMSESPStatusService.h"
#include "emsesp.h"
#include "mqtt.h"
#include "version.h"
namespace emsesp {
EMSESPStatusService::EMSESPStatusService(AsyncWebServer * server, SecurityManager * securityManager) {
// rest endpoint for web page
server->on(EMSESP_STATUS_SERVICE_PATH,
HTTP_GET,
securityManager->wrapRequest(std::bind(&EMSESPStatusService::emsespStatusService, this, std::placeholders::_1),
AuthenticationPredicates::IS_AUTHENTICATED));
// trigger on wifi connects
#ifdef ESP32
WiFi.onEvent(onStationModeConnected, WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
WiFi.onEvent(onStationModeDisconnected, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent(onStationModeGotIP, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
#elif defined(ESP8266)
_onStationModeConnectedHandler = WiFi.onStationModeConnected(onStationModeConnected);
_onStationModeDisconnectedHandler = WiFi.onStationModeDisconnected(onStationModeDisconnected);
_onStationModeGotIPHandler = WiFi.onStationModeGotIP(onStationModeGotIP);
#endif
}
#ifdef ESP32
void EMSESPStatusService::onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info) {
EMSESP::logger().debug(F("Wifi Connected"));
}
void EMSESPStatusService::onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
EMSESP::logger().debug(F("WiFi Disconnected. Reason code=%d"), info.disconnected.reason);
}
void EMSESPStatusService::onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info) {
EMSESP::logger().debug(F("WiFi connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
}
#elif defined(ESP8266)
void EMSESPStatusService::onStationModeConnected(const WiFiEventStationModeConnected & event) {
EMSESP::logger().debug(F("Wifi connected with SSID %s"), event.ssid.c_str());
}
void EMSESPStatusService::onStationModeDisconnected(const WiFiEventStationModeDisconnected & event) {
EMSESP::logger().debug(F("WiFi Disconnected. Reason code=%d"), event.reason);
}
void EMSESPStatusService::onStationModeGotIP(const WiFiEventStationModeGotIP & event) {
EMSESP::logger().debug(F("WiFi connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str());
}
#endif
void EMSESPStatusService::emsespStatusService(AsyncWebServerRequest * request) {
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE);
JsonObject root = response->getRoot();
root["status"] = EMSESP::bus_status(); // 0, 1 or 2
root["rx_received"] = EMSESP::rxservice_.telegram_count();
root["tx_sent"] = EMSESP::txservice_.telegram_read_count() + EMSESP::txservice_.telegram_write_count();
root["crc_errors"] = EMSESP::rxservice_.telegram_error_count();
root["tx_errors"] = EMSESP::txservice_.telegram_fail_count();
response->setLength();
request->send(response);
}
} // namespace emsesp

38
src/EMSESPStatusService.h Normal file
View File

@@ -0,0 +1,38 @@
#ifndef EMSESPStatusService_h
#define EMSESPStatusService_h
#include <ArduinoJson.h>
#include <AsyncJson.h>
#include <ESPAsyncWebServer.h>
#include <SecurityManager.h>
#include <AsyncMqttClient.h>
#define MAX_EMSESP_STATUS_SIZE 1024
#define EMSESP_STATUS_SERVICE_PATH "/rest/emsespStatus"
namespace emsesp {
class EMSESPStatusService {
public:
EMSESPStatusService(AsyncWebServer * server, SecurityManager * securityManager);
private:
void emsespStatusService(AsyncWebServerRequest * request);
#ifdef ESP32
static void onStationModeConnected(WiFiEvent_t event, WiFiEventInfo_t info);
static void onStationModeDisconnected(WiFiEvent_t event, WiFiEventInfo_t info);
static void onStationModeGotIP(WiFiEvent_t event, WiFiEventInfo_t info);
#elif defined(ESP8266)
WiFiEventHandler _onStationModeConnectedHandler;
WiFiEventHandler _onStationModeDisconnectedHandler;
WiFiEventHandler _onStationModeGotIPHandler;
static void onStationModeConnected(const WiFiEventStationModeConnected & event);
static void onStationModeDisconnected(const WiFiEventStationModeDisconnected & event);
static void onStationModeGotIP(const WiFiEventStationModeGotIP & event);
#endif
};
} // namespace emsesp
#endif

View File

@@ -18,6 +18,7 @@
#include "console.h"
#include "emsesp.h"
#include "version.h"
#ifdef EMSESP_DEBUG
#include "test/test.h"
@@ -57,35 +58,33 @@ void EMSESPShell::stopped() {
// remove all custom contexts
commands->remove_all_commands();
_console_commands_loaded = false; // make sure they get reloaded next time a console is opened
console_commands_loaded_ = false; // make sure they get reloaded next time a console is opened
}
// show welcome banner
// this is one of the first functions called when the shell is started
void EMSESPShell::display_banner() {
Settings settings;
println();
printfln(F("┌──────────────────────────────────────────┐"));
#if defined(ESP32)
printfln(F("│ %sEMS-ESP version %-10s ESP32%s │"), COLOR_BOLD_ON, settings.app_version().c_str(), COLOR_BOLD_OFF);
#else
printfln(F("│ %sEMS-ESP version %-10s%s │"), COLOR_BOLD_ON, settings.app_version().c_str(), COLOR_BOLD_OFF);
#endif
printfln(F("│ %sEMS-ESP version %-10s%s │"), COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
printfln(F("│ %s%shttps://github.com/proddy/EMS-ESP%s │"), COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET);
printfln(F("│ │"));
if (System::safe_mode()) {
printfln(F("│ %sIN SAFE MODE. EMS BUS IS DISABLED%s │"), COLOR_BRIGHT_RED_BACKGROUND, COLOR_RESET);
#ifdef EMSESP_SAFE_MODE
printfln(F("│ %s!FORCED AT COMPILE TIME!%s │"), COLOR_BRIGHT_RED, COLOR_RESET);
#endif
printfln(F("│ │"));
}
printfln(F("│ type %shelp%s to show available commands │"), COLOR_UNDERLINE, COLOR_RESET);
printfln(F("└──────────────────────────────────────────┘"));
println();
// set console name
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { console_hostname_ = wifiSettings.hostname.c_str(); });
if (console_hostname_.empty()) {
console_hostname_.resize(16, '\0');
#if defined(ESP8266)
snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp8266"));
#else
snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp32"));
#endif
}
// load the list of commands
add_console_commands();
@@ -98,7 +97,7 @@ void EMSESPShell::display_banner() {
void EMSESPShell::add_console_commands() {
// if we already have these commands loaded, stop adding duplicates
// for example when opening multiple serial/telnet sessions
if (_console_commands_loaded) {
if (console_commands_loaded_) {
return;
}
@@ -111,50 +110,136 @@ void EMSESPShell::add_console_commands() {
flash_string_vector{F_(refresh)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("Requesting data from EMS devices"));
_console_commands_loaded = false;
console_commands_loaded_ = false;
add_console_commands();
EMSESP::fetch_device_values();
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(version)},
flash_string_vector{F_(show)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, Settings().app_version().c_str(), COLOR_RESET);
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
shell.println();
EMSESP::show_device_values(shell);
EMSESP::show_sensor_values(shell);
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show)},
flash_string_vector{F_(show), F_(devices)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_devices(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(ems)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_ems(shell); });
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(show), F_(values)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_device_values(shell); });
commands->add_command(
ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(bus_id)},
flash_string_vector{F_(deviceid_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) {
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.ems_bus_id = device_id;
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
return StateUpdateResult::CHANGED;
},
"local");
} else {
shell.println(F("Must be 0B, 0D, 0A, 0F or 12"));
}
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{
read_flash_string(F("0B")),
read_flash_string(F("0D")),
read_flash_string(F("0A")),
read_flash_string(F("0F")),
read_flash_string(F("12")),
};
});
commands->add_command(ShellContext::MAIN,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(tx_mode)},
flash_string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.tx_mode = tx_mode;
shell.printfln(F_(tx_mode_fmt), tx_mode);
return StateUpdateResult::CHANGED;
},
"local");
EMSESP::reset_tx(tx_mode); // reset counters and set tx_mode
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(scan), F_(devices)},
flash_string_vector{F_(deep_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
} else {
shell.printfln(F("Performing a deep scan..."));
std::vector<uint8_t> Device_Ids;
Device_Ids.push_back(0x08); // Boilers - 0x08
Device_Ids.push_back(0x38); // HeatPump - 0x38
Device_Ids.push_back(0x30); // Solar Module - 0x30
Device_Ids.push_back(0x09); // Controllers - 0x09
Device_Ids.push_back(0x02); // Connect - 0x02
Device_Ids.push_back(0x48); // Gateway - 0x48
Device_Ids.push_back(0x20); // Mixing Devices - 0x20
Device_Ids.push_back(0x21); // Mixing Devices - 0x21
Device_Ids.push_back(0x22); // Mixing Devices - 0x22
Device_Ids.push_back(0x23); // Mixing Devices - 0x23
Device_Ids.push_back(0x28); // Mixing Devices WW- 0x28
Device_Ids.push_back(0x29); // Mixing Devices WW- 0x29
Device_Ids.push_back(0x10); // Thermostats - 0x10
Device_Ids.push_back(0x17); // Thermostats - 0x17
Device_Ids.push_back(0x18); // Thermostat remote - 0x18
Device_Ids.push_back(0x19); // Thermostat remote - 0x19
Device_Ids.push_back(0x1A); // Thermostat remote - 0x1A
Device_Ids.push_back(0x1B); // Thermostat remote - 0x1B
Device_Ids.push_back(0x11); // Switches - 0x11
// send the read command with Version command
for (const uint8_t device_id : Device_Ids) {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
}
}
});
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
// shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, Settings().app_version().c_str(), COLOR_RESET);
// shell.println();
// EMSESP::show_emsbus(shell);
// EMSESP::show_devices(shell);
EMSESP::show_values(shell);
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
});
});
/*
* add all the submenu contexts...
*/
// MQTT
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Mqtt::console_commands(shell, ShellContext::MQTT);
});
// EMS
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
flash_string_vector{F_(ems)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::console_commands(shell, ShellContext::EMS);
});
// System
commands->add_command(ShellContext::MAIN,
CommandFlags::USER,
@@ -169,30 +254,11 @@ void EMSESPShell::add_console_commands() {
Console::load_standard_commands(ShellContext::MAIN);
_console_commands_loaded = true;
console_commands_loaded_ = true;
}
std::string EMSESPShell::hostname_text() {
Settings settings;
std::string hostname = settings.hostname();
if (hostname.empty()) {
hostname.resize(16, '\0');
#if defined(ESP8266)
snprintf_P(&hostname[0], hostname.capacity() + 1, PSTR("esp8266"));
#else
snprintf_P(&hostname[0], hostname.capacity() + 1, PSTR("esp32"));
#endif
}
/*
if (System::safe_mode()) {
return std::string{"safemode@"} + hostname;
}
*/
return hostname;
return console_hostname_;
}
// remove commands from the current context to save memory before exiting
@@ -211,7 +277,7 @@ bool EMSESPShell::exit_context() {
void Console::enter_custom_context(Shell & shell, unsigned int context) {
load_standard_commands(context);
// don't enter context if we're already at the root
// don't go into the new context if it's already the root (to prevent double loading)
if (context != ShellContext::MAIN) {
shell.enter_context(context);
}
@@ -237,7 +303,6 @@ void Console::load_standard_commands(unsigned int context) {
[](Shell & shell, const std::vector<std::string> & arguments) {
if (!arguments.empty()) {
uuid::log::Level level;
if (uuid::log::parse_level_lowercase(arguments[0], level)) {
shell.log_level(level);
} else {
@@ -245,8 +310,6 @@ void Console::load_standard_commands(unsigned int context) {
return;
}
}
// print out logging settings
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level()));
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
@@ -285,29 +348,78 @@ void Console::load_standard_commands(unsigned int context) {
if (completed) {
uint64_t now = uuid::get_uptime_ms();
if (!password.empty() && password == Settings().admin_password()) {
become_admin(shell);
} else {
shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid admin password on console"));
shell.println(F("su: incorrect password"));
});
}
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
become_admin(shell);
} else {
shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) {
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid admin password on console"));
shell.println(F("su: incorrect password"));
});
}
});
}
});
}
});
#ifdef EMSESP_DEBUG
EMSESPShell::commands->add_command(
context, CommandFlags::ADMIN, flash_string_vector{F_(debug)}, [&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, Settings().app_version().c_str(), COLOR_RESET);
Settings settings;
settings.commit();
settings.show_settings(shell);
shell.println();
});
#endif
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(send), F_(telegram)},
flash_string_vector{F_(data_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
EMSESP::send_raw_telegram(arguments.front().c_str());
});
EMSESPShell::commands->add_command(context,
CommandFlags::USER,
flash_string_vector{F_(watch)},
flash_string_vector{F_(watch_format_mandatory), F_(watchid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
// get raw/pretty
if (arguments[0] == read_flash_string(F_(raw))) {
emsesp::EMSESP::watch(EMSESP::WATCH_RAW); // raw
} else if (arguments[0] == read_flash_string(F_(on))) {
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
} else if (arguments[0] == read_flash_string(F_(off))) {
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
} else {
shell.printfln(F_(invalid_watch));
return;
}
uint16_t watch_id;
if (arguments.size() == 2) {
// get the watch_id if its set
watch_id = Helpers::hextoint(arguments[1].c_str());
} else {
watch_id = WATCH_ID_NONE;
}
emsesp::EMSESP::watch_id(watch_id);
uint8_t watch = emsesp::EMSESP::watch();
if (watch == EMSESP::WATCH_OFF) {
shell.printfln(F("Watching telegrams is off"));
return;
}
// if logging is off, the watch won't show anything, show force it back to NOTICE
if (!shell.logger().enabled(Level::NOTICE)) {
shell.log_level(Level::NOTICE);
}
if (watch == EMSESP::WATCH_ON) {
shell.printfln(F("Watching incoming telegrams, displayed in decoded format"));
} else {
shell.printfln(F("Watching incoming telegrams, displayed as raw bytes")); // WATCH_RAW
}
watch_id = emsesp::EMSESP::watch_id();
if (watch_id != WATCH_ID_NONE) {
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
}
});
}
// prompt, change per context
@@ -316,12 +428,6 @@ std::string EMSESPShell::context_text() {
case ShellContext::MAIN:
return std::string{'/'};
case ShellContext::EMS:
return std::string{"/ems"};
case ShellContext::MQTT:
return std::string{"/mqtt"};
case ShellContext::BOILER:
return std::string{"/boiler"};
@@ -403,23 +509,19 @@ std::string EMSESPStreamConsole::console_name() {
void Console::start() {
// if we've detected a boot into safe mode on ESP8266, start the Serial console too
// Serial is always on with the ESP32 as it has 2 UARTs
#if defined(ESP32) || defined(EMSESP_STANDALONE)
if (true) {
#elif defined(ESP8266)
if (System::safe_mode()) {
#endif
serial_console_.begin(SERIAL_CONSOLE_BAUD_RATE);
serial_console_.println();
#if defined(ESP32) || defined(EMSESP_STANDALONE) || defined(EMSESP_FORCE_SERIAL)
serial_console_.begin(SERIAL_CONSOLE_BAUD_RATE);
serial_console_.println();
shell = std::make_shared<EMSESPStreamConsole>(serial_console_, true);
shell->maximum_log_messages(100); // default is 50
shell->start();
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
shell = std::make_shared<EMSESPStreamConsole>(serial_console_, true);
shell->maximum_log_messages(100); // default is 50
shell->start();
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
#endif
#if defined(EMSESP_STANDALONE)
shell->add_flags(CommandFlags::ADMIN);
shell->add_flags(CommandFlags::ADMIN);
#endif
}
// always start the telnet service, except on an ESP8266
// default idle is 10 minutes, default write timeout is 0 (automatic)

View File

@@ -28,9 +28,7 @@
#include <uuid/log.h>
#include "helpers.h"
#include "settings.h"
#include "system.h"
#include "network.h"
#include "mqtt.h"
using uuid::flash_string_vector;
@@ -39,8 +37,6 @@ using uuid::console::Commands;
using uuid::console::Shell;
using uuid::log::Level;
// clang-format off
#define LOG_DEBUG(...) logger_.debug(__VA_ARGS__)
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
#define LOG_TRACE(...) logger_.trace(__VA_ARGS__)
@@ -48,10 +44,10 @@ using uuid::log::Level;
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
// clang-format off
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = string_literal;
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
#define F_(string_name) FPSTR(__pstr__##string_name)
// clang-format on
// common words
@@ -82,19 +78,25 @@ MAKE_PSTR_WORD(version)
MAKE_PSTR_WORD(values)
MAKE_PSTR_WORD(system)
MAKE_PSTR_WORD(refresh)
MAKE_PSTR_WORD(change)
MAKE_PSTR_WORD(disconnect)
MAKE_PSTR_WORD(debug)
MAKE_PSTR_WORD(restart)
MAKE_PSTR_WORD(reconnect)
MAKE_PSTR_WORD(format)
MAKE_PSTR_WORD(raw)
MAKE_PSTR_WORD(watch)
// context menus
MAKE_PSTR_WORD(mqtt)
MAKE_PSTR_WORD(send)
MAKE_PSTR_WORD(telegram)
MAKE_PSTR_WORD(bus_id)
MAKE_PSTR_WORD(tx_mode)
MAKE_PSTR_WORD(ems)
MAKE_PSTR_WORD(devices)
MAKE_PSTR(deep_optional, "[deep]")
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_mandatory, "<off | on | raw>")
MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">")
MAKE_PSTR(percent, "%")
MAKE_PSTR(degrees, "°C")
MAKE_PSTR(degrees_mandatory, "<degrees>")
@@ -106,8 +108,6 @@ MAKE_PSTR(typeid_mandatory, "<type ID>")
MAKE_PSTR(deviceid_mandatory, "<device ID>")
MAKE_PSTR(deviceid_optional, "[device ID]")
MAKE_PSTR(invalid_log_level, "Invalid log level")
MAKE_PSTR(ip_address_optional, "[IP address]")
MAKE_PSTR(ip_address_mandatory, "<IP address>")
MAKE_PSTR(port_mandatory, "<port>")
MAKE_PSTR(log_level_fmt, "Log level = %s")
MAKE_PSTR(log_level_optional, "[level]")
@@ -116,8 +116,6 @@ MAKE_PSTR(name_optional, "[name]")
MAKE_PSTR(new_password_prompt1, "Enter new password: ")
MAKE_PSTR(new_password_prompt2, "Retype new password: ")
MAKE_PSTR(password_prompt, "Password: ")
MAKE_PSTR(seconds_optional, "[seconds]")
MAKE_PSTR(seconds_mandatory, "<seconds>")
MAKE_PSTR(unset, "<unset>")
#ifdef LOCAL
@@ -143,10 +141,10 @@ enum ShellContext : uint8_t {
MAIN = 0,
SYSTEM,
EMS,
MQTT,
BOILER,
THERMOSTAT
THERMOSTAT,
SOLAR,
MIXING
};
@@ -173,8 +171,9 @@ class EMSESPShell : virtual public uuid::console::Shell {
bool exit_context() override;
private:
void add_console_commands();
bool _console_commands_loaded = false; // set to true when the initial commands are loaded
void add_console_commands();
bool console_commands_loaded_ = false; // set to true when the initial commands are loaded
std::string console_hostname_;
};
class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPShell {

View File

@@ -793,7 +793,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(wwtemp)},
flash_string_vector{F_(wwtemp)},
flash_string_vector{F_(degrees_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
set_warmwater_temp(Helpers::atoint(arguments.front().c_str()));
@@ -801,7 +801,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(flowtemp)},
flash_string_vector{F_(flowtemp)},
flash_string_vector{F_(degrees_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
set_flow_temp(Helpers::atoint(arguments.front().c_str()));
@@ -810,7 +810,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(wwactive)},
flash_string_vector{F_(wwactive)},
flash_string_vector{F_(bool_mandatory)},
[=](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments[0] == read_flash_string(F_(on))) {
@@ -829,7 +829,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(wwonetime)},
flash_string_vector{F_(wwonetime)},
flash_string_vector{F_(bool_mandatory)},
[=](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments[0] == read_flash_string(F_(on))) {
@@ -848,7 +848,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(wwcirculation)},
flash_string_vector{F_(wwcirculation)},
flash_string_vector{F_(bool_mandatory)},
[=](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments[0] == read_flash_string(F_(on))) {
@@ -867,7 +867,7 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(comfort)},
flash_string_vector{F_(comfort)},
flash_string_vector{F_(comfort_mandatory)},
[=](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments[0] == read_flash_string(F_(hot))) {
@@ -889,65 +889,6 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
flash_string_vector{F_(show)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
EMSESPShell::commands->add_command(
ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(shower), F_(timer)},
flash_string_vector{F_(bool_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
bool value;
if (arguments[0] == read_flash_string(F_(on))) {
value = true;
} else if (arguments[0] == read_flash_string(F_(off))) {
value = false;
} else {
shell.println(F("Must be on or off"));
return;
}
Settings settings;
settings.shower_timer(value);
settings.commit();
shell.printfln(F_(shower_timer_fmt), settings.shower_timer() ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str());
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
});
EMSESPShell::commands->add_command(
ShellContext::BOILER,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(shower), F_(alert)},
flash_string_vector{F_(bool_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
bool value;
if (arguments[0] == read_flash_string(F_(on))) {
value = true;
} else if (arguments[0] == read_flash_string(F_(off))) {
value = false;
} else {
shell.println(F("Must be on or off"));
return;
}
Settings settings;
settings.shower_alert(value);
settings.commit();
shell.printfln(F_(shower_timer_fmt), settings.shower_alert() ? uuid::read_flash_string(F_(on)).c_str() : uuid::read_flash_string(F_(off)).c_str());
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
});
EMSESPShell::commands->add_command(ShellContext::BOILER,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Settings settings;
shell.printfln(F_(shower_timer_fmt), settings.shower_timer() ? F_(enabled) : F_(disabled));
shell.printfln(F_(shower_alert_fmt), settings.shower_alert() ? F_(enabled) : F_(disabled));
shell.println();
});
// enter the context
Console::enter_custom_context(shell, context);
}

View File

@@ -18,8 +18,6 @@
#include "connect.h"
// MAKE_PSTR_WORD(connect)
namespace emsesp {
REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT);
@@ -33,7 +31,7 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
// MQTT callbacks
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
// register_mqtt_topic("cmd", std::bind(&Connect::cmd, this, _1));
}
void Connect::add_context_menu() {

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"

View File

@@ -33,7 +33,7 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
// MQTT callbacks
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
// register_mqtt_topic("cmd", std::bind(&Gateway::cmd, this, _1));
}
void Gateway::add_context_menu() {

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"

View File

@@ -73,6 +73,9 @@ void Solar::show_values(uuid::console::Shell & shell) {
shell.printfln(F(" Pump working time: %d days %d hours %d minutes"), pumpWorkMin_ / 1440, (pumpWorkMin_ % 1440) / 60, pumpWorkMin_ % 60);
}
print_value(shell, 2, F("Tank Heated"), tankHeated_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Collector"), collectorOnOff_, nullptr, EMS_VALUE_BOOL);
print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10);
print_value(shell, 2, F("Energy today"), energyToday_, F_(wh));
print_value(shell, 2, F("Energy total"), energyTotal_, F_(kwh), 10);
@@ -87,30 +90,47 @@ void Solar::publish_values() {
if (Helpers::hasValue(collectorTemp_)) {
doc["collectortemp"] = (float)collectorTemp_ / 10;
}
if (Helpers::hasValue(bottomTemp_)) {
doc["bottomtemp"] = (float)bottomTemp_ / 10;
}
if (Helpers::hasValue(bottomTemp2_)) {
doc["bottomtemp2"] = (float)bottomTemp2_ / 10;
}
if (Helpers::hasValue(pumpModulation_)) {
doc["pumpmodulation"] = pumpModulation_;
}
if (Helpers::hasValue(pump_, true)) {
doc["pump"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(valveStatus_, true)) {
doc["valvestatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(pumpWorkMin_)) {
doc["pumpWorkMin"] = (float)pumpWorkMin_;
}
if (Helpers::hasValue(tankHeated_, true)) {
doc["tankHeated"] = Helpers::render_value(s, tankHeated_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(collectorOnOff_, true)) {
doc["collectorOnOff"] = Helpers::render_value(s, collectorOnOff_, EMS_VALUE_BOOL);
}
if (Helpers::hasValue(energyLastHour_)) {
doc["energylasthour"] = (float)energyLastHour_ / 10;
}
if (Helpers::hasValue(energyToday_)) {
doc["energytoday"] = energyToday_;
}
if (Helpers::hasValue(energyTotal_)) {
doc["energytotal"] = (float)energyTotal_ / 10;
}
@@ -186,6 +206,9 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
if (pumpmod == 0 && pumpModulation_ == 100) { // mask out boosts
pumpModulation_ = 15; // set to minimum
}
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
telegram->read_bitvalue(collectorOnOff_, 3, 0);
}
/*
@@ -221,12 +244,11 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
if (Wh != 0xFFFF) {
energyLastHour_ = Wh * 10; // set to *10
}
telegram->read_bitvalue(pump_, 8, 0); // Solar pump on (1) or off (0)
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
telegram->read_bitvalue(collectorOnOff_, 9, 0); // collector on/off
telegram->read_bitvalue(tankHeated_, 9, 2); // tank full
}
/*
* Junkers ISM1 Solar Module - type 0x0101 EMS+ for setting values
* e.g. 90 30 FF 06 00 01 50
*/
void Solar::process_ISM1Set(std::shared_ptr<const Telegram> telegram) {

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"
@@ -57,6 +56,8 @@ class Solar : public EMSdevice {
uint32_t energyToday_ = EMS_VALUE_ULONG_NOTSET;
uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET;
uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time
uint8_t tankHeated_ = EMS_VALUE_BOOL_NOTSET;
uint8_t collectorOnOff_ = EMS_VALUE_BOOL_NOTSET;
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;

View File

@@ -33,7 +33,7 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
// MQTT callbacks
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
// register_mqtt_topic("cmd", std::bind(&Switch::cmd, this, _1));
}
void Switch::add_context_menu() {

View File

@@ -26,7 +26,6 @@
#include "emsdevice.h"
#include "telegram.h"
#include "emsesp.h"
#include "helpers.h"
#include "mqtt.h"

View File

@@ -126,11 +126,17 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
}
}
Settings settings;
uint8_t master_thermostat = settings.master_thermostat(); // what the user has defined
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat
mqtt_format_ = settings.mqtt_format(); // single, nested or ha
uint8_t master_thermostat = 0;
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
master_thermostat = settings.master_thermostat; // what the user has defined
});
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
});
uint8_t actual_master_thermostat = EMSESP::actual_master_thermostat(); // what we're actually using
uint8_t num_devices = EMSESP::count_devices(EMSdevice::DeviceType::THERMOSTAT) + 1; // including this thermostat
// if we're on auto mode, register this thermostat if it has a device id of 0x10 or 0x17
// or if its the master thermostat we defined
@@ -161,7 +167,7 @@ void Thermostat::init_mqtt() {
// if the MQTT format type is ha then send the config to HA (via the mqtt discovery service)
// for each of the heating circuits
if (mqtt_format_ == Settings::MQTT_format::HA) {
if (mqtt_format_ == MQTT_format::HA) {
for (uint8_t hc = 0; hc < monitor_typeids.size(); hc++) {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
@@ -304,7 +310,8 @@ void Thermostat::thermostat_cmd(const char * message) {
std::string holiday = doc[hc_name]["holiday"];
set_holiday(holiday.c_str(), hc_num);
}
}
}
if (nullptr != doc["wwmode"]) {
std::string mode = doc["wwmode"];
set_ww_mode(mode);
@@ -502,7 +509,7 @@ void Thermostat::thermostat_cmd(const char * message) {
}
return;
}
}
} // namespace emsesp
void Thermostat::thermostat_cmd_temp(const char * message) {
float f = strtof((char *)message, 0);
@@ -559,8 +566,7 @@ void Thermostat::publish_values() {
JsonObject dataThermostat;
// add external temp
if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1)
&& (mqtt_format_ == Settings::MQTT_format::SINGLE || mqtt_format_ == Settings::MQTT_format::CUSTOM)) {
if ((flags == EMS_DEVICE_FLAG_RC35 || flags == EMS_DEVICE_FLAG_RC30_1) && (mqtt_format_ == MQTT_format::SINGLE || mqtt_format_ == MQTT_format::CUSTOM)) {
if (datetime_.size()) {
rootThermostat["time"] = datetime_.c_str();
}
@@ -597,7 +603,7 @@ void Thermostat::publish_values() {
rootThermostat["wwmode"] = "auto";
}
}
if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
if (mqtt_format_ == MQTT_format::SINGLE) {
Mqtt::publish("thermostat_data", doc);
rootThermostat = doc.to<JsonObject>(); // clear object
}
@@ -611,7 +617,7 @@ void Thermostat::publish_values() {
has_data = true;
// if the MQTT format is 'nested' or 'ha' then create the parent object hc<n>
if (mqtt_format_ != Settings::MQTT_format::SINGLE) {
if (mqtt_format_ != MQTT_format::SINGLE) {
// create nested json for each HC
char hc_name[10]; // hc{1-4}
strlcpy(hc_name, "hc", 10);
@@ -674,10 +680,10 @@ void Thermostat::publish_values() {
}
// when using HA always send the mode otherwise it'll break the component/widget and report an error
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == Settings::MQTT_format::HA)) {
if ((Helpers::hasValue(hc->mode)) || (mqtt_format_ == MQTT_format::HA)) {
uint8_t hc_mode = hc->get_mode(flags);
// if we're sending to HA the only valid mode types are heat, auto and off
if (mqtt_format_ == Settings::MQTT_format::HA) {
if (mqtt_format_ == MQTT_format::HA) {
if ((hc_mode == HeatingCircuit::Mode::MANUAL) || (hc_mode == HeatingCircuit::Mode::DAY)) {
hc_mode = HeatingCircuit::Mode::HEAT;
} else if ((hc_mode == HeatingCircuit::Mode::NIGHT) || (hc_mode == HeatingCircuit::Mode::OFF)) {
@@ -700,7 +706,7 @@ void Thermostat::publish_values() {
}
// if format is single, send immediately and clear object for next hc
if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
if (mqtt_format_ == MQTT_format::SINGLE) {
char topic[30];
char s[3]; // for formatting strings
strlcpy(topic, "thermostat_data", 30);
@@ -715,11 +721,11 @@ void Thermostat::publish_values() {
}
// if we're using nested json, send all in one go
if (mqtt_format_ == Settings::MQTT_format::NESTED) {
if (mqtt_format_ == MQTT_format::NESTED) {
Mqtt::publish("thermostat_data", doc);
} else if (mqtt_format_ == Settings::MQTT_format::HA) {
} else if (mqtt_format_ == MQTT_format::HA) {
Mqtt::publish("homeassistant/climate/ems-esp/state", doc);
} else if (mqtt_format_ == Settings::MQTT_format::CUSTOM) {
} else if (mqtt_format_ == MQTT_format::CUSTOM) {
Mqtt::publish("thermostat_data", doc);
}
}
@@ -727,7 +733,6 @@ void Thermostat::publish_values() {
// returns the heating circuit object based on the hc number
// of nullptr if it doesn't exist yet
std::shared_ptr<Thermostat::HeatingCircuit> Thermostat::heating_circuit(const uint8_t hc_num) {
// uint8_t hc = (hc_num) ? hc_num : DEFAULT_HEATING_CIRCUIT;
if (hc_num == 0) {
// return first existing hc
for (const auto & heating_circuit : heating_circuits_) {
@@ -1454,16 +1459,16 @@ void Thermostat::set_party(const uint8_t hrs, const uint8_t hc_num) {
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst
// dw - day of week (0..6), dst- summertime (0/1)
void Thermostat::set_datetime(const char * dt) {
void Thermostat::set_datetime(const char * dt) {
uint8_t data[9];
data[0] = (dt[16] - '0') * 100 + (dt[17] - '0') * 10 + (dt[18] - '0'); // year
data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month
data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour
data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day
data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week
data[7] = (dt[22] - '0'); // summerime
data[1] = (dt[12] - '0') * 10 + (dt[13] - '0'); // month
data[2] = (dt[0] - '0') * 10 + (dt[1] - '0'); // hour
data[3] = (dt[9] - '0') * 10 + (dt[10] - '0'); // day
data[4] = (dt[3] - '0') * 10 + (dt[4] - '0'); // min
data[5] = (dt[6] - '0') * 10 + (dt[7] - '0'); // sec
data[6] = (dt[20] - '0'); // day of week
data[7] = (dt[22] - '0'); // summerime
if ((flags() & 0x0F) == EMS_DEVICE_FLAG_RC35 || (flags() & 0x0F) == EMS_DEVICE_FLAG_RC30_1) {
LOG_INFO(F("Setting date and time"));
write_command(6, 0, data, 8, 0);
@@ -1490,8 +1495,8 @@ void Thermostat::set_mode(const std::string & mode, const uint8_t hc_num) {
set_mode(HeatingCircuit::Mode::NOFROST, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::ECO) == mode) {
set_mode(HeatingCircuit::Mode::ECO, hc_num);
// } else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
// set_mode(HeatingCircuit::Mode::HOLIDAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::HOLIDAY) == mode) {
set_mode(HeatingCircuit::Mode::HOLIDAY, hc_num);
} else if (mode_tostring(HeatingCircuit::Mode::COMFORT) == mode) {
set_mode(HeatingCircuit::Mode::COMFORT, hc_num);
} else {
@@ -1509,7 +1514,7 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
// get hc based on number
std::shared_ptr<Thermostat::HeatingCircuit> hc = heating_circuit(hc_num);
if (hc == nullptr) {
LOG_WARNING(F("Set mode: Heating Circuit %d not found or activated"), hc_num);
LOG_WARNING(F("set mode: Heating Circuit %d not found or activated"), hc_num);
return;
}
@@ -1593,7 +1598,7 @@ void Thermostat::set_mode(const uint8_t mode, const uint8_t hc_num) {
// add the write command to the Tx queue
// post validate is the corresponding monitor or set type IDs as they can differ per model
write_command(set_typeids[hc_p], offset, set_mode_value, validate_typeid);
write_command(set_typeids[hc->hc_num() - 1], offset, set_mode_value, validate_typeid);
}
// sets the thermostat temp, where mode is a string
@@ -1641,7 +1646,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
uint8_t model = flags() & 0x0F;
int8_t offset = -1; // we use -1 to check if there is a value
uint8_t factor = 2; // some temperatures only use 1
uint8_t factor = 2; // some temperatures only use 1
uint16_t validate_typeid = monitor_typeids[hc->hc_num() - 1];
if (model == EMS_DEVICE_FLAG_RC10) {
@@ -1771,6 +1776,7 @@ void Thermostat::set_temperature(const float temperature, const uint8_t mode, co
mode_tostring(mode).c_str());
// add the write command to the Tx queue
// value is *2
// post validate is the corresponding monitor or set type IDs as they can differ per model
write_command(set_typeids[hc->hc_num() - 1], offset, (uint8_t)((float)temperature * (float)factor), validate_typeid);
}
@@ -1789,14 +1795,17 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
} else {
value = Helpers::hextoint(arguments.front().c_str());
}
Settings settings;
settings.master_thermostat(value);
settings.commit();
EMSESP::actual_master_thermostat(value); // set the internal value too
char buffer[5];
shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat() == 0 ? uuid::read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat()));
EMSESP::emsespSettingsService.update(
[&](EMSESPSettings & settings) {
settings.master_thermostat = value;
EMSESP::actual_master_thermostat(value); // set the internal value too
char buffer[5];
shell.printfln(F_(master_thermostat_fmt),
!value ? uuid::read_flash_string(F_(auto)).c_str() : Helpers::hextoa(buffer, value));
return StateUpdateResult::CHANGED;
},
"local");
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
@@ -1810,7 +1819,7 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(temp)},
flash_string_vector{F_(temp)},
flash_string_vector{F_(degrees_mandatory), F_(hc_optional), F_(mode_optional)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t hc = (arguments.size() >= 2) ? arguments[1].at(0) - '0' : AUTO_HEATING_CIRCUIT;
@@ -1826,7 +1835,7 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(mode)},
flash_string_vector{F_(mode)},
flash_string_vector{F_(mode_mandatory), F_(hc_optional)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
uint8_t hc = (arguments.size() == 2) ? arguments[1].at(0) - '0' : AUTO_HEATING_CIRCUIT;
@@ -1850,12 +1859,11 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::THERMOSTAT,
CommandFlags::ADMIN,
flash_string_vector{F_(change), F_(wwmode)},
flash_string_vector{F_(wwmode)},
flash_string_vector{F_(mode_mandatory)},
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) { set_ww_mode(arguments.front()); },
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F("off")), read_flash_string(F("on")), read_flash_string(F("auto"))
};
return std::vector<std::string>{read_flash_string(F("off")), read_flash_string(F("on")), read_flash_string(F("auto"))};
});
EMSESPShell::commands->add_command(ShellContext::THERMOSTAT,
@@ -1867,12 +1875,13 @@ void Thermostat::console_commands(Shell & shell, unsigned int context) {
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Settings settings;
char buffer[4];
shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat() == 0 ? uuid::read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat()));
shell.println();
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
char buffer[4];
shell.printfln(F_(master_thermostat_fmt),
settings.master_thermostat == 0 ? uuid::read_flash_string(F_(auto)).c_str()
: Helpers::hextoa(buffer, settings.master_thermostat));
shell.println();
});
});
// enter the context

View File

@@ -63,7 +63,7 @@ class Thermostat : public EMSdevice {
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heatingcurve offest temp at roomtemp signed!
uint8_t hc_num() const {
return hc_num_;
return hc_num_; // 1..10
}
uint8_t get_mode(uint8_t flags) const;
@@ -85,7 +85,7 @@ class Thermostat : public EMSdevice {
}
private:
uint8_t hc_num_;
uint8_t hc_num_; // 1..10
uint16_t monitor_typeid_;
uint16_t set_typeid_;
};

View File

@@ -48,7 +48,7 @@ std::string EMSdevice::brand_to_string() const {
break;
case EMSdevice::Brand::NO_BRAND:
default:
return read_flash_string(F("?"));
return read_flash_string(F(""));
break;
}
@@ -249,7 +249,7 @@ bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
return false;
}
LOG_DEBUG(F("Decoding %s"), uuid::read_flash_string(tf.telegram_type_name_).c_str());
LOG_DEBUG(F("Received %s"), uuid::read_flash_string(tf.telegram_type_name_).c_str());
tf.process_function_(telegram);
return true;
}

View File

@@ -82,6 +82,10 @@ class EMSdevice {
version_ = version;
}
inline std::string version() const {
return version_;
}
inline void brand(uint8_t brand) {
brand_ = brand;
}
@@ -94,6 +98,10 @@ class EMSdevice {
name_ = name;
}
inline std::string name() const {
return name_;
}
std::string brand_to_string() const;
static uint8_t decode_brand(uint8_t value);

View File

@@ -18,24 +18,6 @@
#include "emsesp.h"
MAKE_PSTR_WORD(bus_id)
MAKE_PSTR_WORD(tx_mode)
MAKE_PSTR_WORD(read_only)
MAKE_PSTR_WORD(emsbus)
MAKE_PSTR_WORD(devices)
MAKE_PSTR_WORD(send)
MAKE_PSTR_WORD(telegram)
MAKE_PSTR(deep_optional, "[deep]")
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">")
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
MAKE_PSTR(read_only_fmt, "Read-only mode is %s")
MAKE_PSTR(watchid_optional, "[ID]")
MAKE_PSTR(watch_format_mandatory, "<off | on | raw>")
MAKE_PSTR(invalid_watch, "Invalid watch type")
MAKE_PSTR(logger_name, "emsesp")
namespace emsesp {
@@ -43,6 +25,22 @@ namespace emsesp {
using DeviceFlags = emsesp::EMSdevice;
using DeviceType = emsesp::EMSdevice::DeviceType;
AsyncWebServer webServer(80);
#if defined(ESP32)
ESP8266React EMSESP::esp8266React(&webServer, &SPIFFS);
EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &SPIFFS, EMSESP::esp8266React.getSecurityManager());
#else
ESP8266React EMSESP::esp8266React(&webServer, &LittleFS);
EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
#endif
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
EMSESPScanDevicesService EMSESP::emsespScanDevicesService = EMSESPScanDevicesService(&webServer, EMSESP::esp8266React.getSecurityManager());
std::vector<std::unique_ptr<EMSdevice>> EMSESP::emsdevices; // array of all the detected EMS devices
std::vector<emsesp::EMSESP::Device_record> EMSESP::device_library_; // libary of all our known EMS devices so far
@@ -55,7 +53,6 @@ Mqtt EMSESP::mqtt_; // mqtt handler
System EMSESP::system_; // core system services
Console EMSESP::console_; // telnet and serial console
Sensors EMSESP::sensors_; // Dallas sensors
Network EMSESP::network_; // WiFi
Shower EMSESP::shower_; // Shower logic
// static/common variables
@@ -63,11 +60,10 @@ uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; /
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
uint8_t EMSESP::watch_ = 0; // trace off
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
bool EMSESP::ems_read_only_;
uint32_t EMSESP::last_fetch_ = 0;
uint32_t EMSESP::last_fetch_ = 0;
// for a specific EMS device go and request data values
// or if device_id is 0 it will fetch from all known devices
// or if device_id is 0 it will fetch from all our known and active devices
void EMSESP::fetch_device_values(const uint8_t device_id) {
for (const auto & emsdevice : emsdevices) {
if (emsdevice) {
@@ -110,8 +106,48 @@ void EMSESP::watch_id(uint16_t watch_id) {
}
}
// change the tx_mode
// resets all counters and bumps the UART
void EMSESP::reset_tx(uint8_t const tx_mode) {
txservice_.telegram_read_count(0);
txservice_.telegram_write_count(0);
txservice_.telegram_fail_count(0);
if (tx_mode) {
EMSuart::stop();
EMSuart::start(tx_mode);
EMSESP::fetch_device_values();
}
}
// return status of bus: connected, connected but Tx is broken, disconnected
uint8_t EMSESP::bus_status() {
if (!rxservice_.bus_connected()) {
return BUS_STATUS_OFFLINE;
}
// check if we have Tx issues.
uint32_t total_sent = txservice_.telegram_read_count() + txservice_.telegram_write_count();
// nothing sent successfully, also no errors - must be ok
if ((total_sent == 0) && (txservice_.telegram_fail_count() == 0)) {
return BUS_STATUS_CONNECTED;
}
// nothing sent successfully, but have Tx errors
if ((total_sent == 0) && (txservice_.telegram_fail_count() != 0)) {
return BUS_STATUS_TX_ERRORS;
}
// Tx Failure rate > 5%
if (((txservice_.telegram_fail_count() * 100) / total_sent) > EMSbus::EMS_TX_ERROR_LIMIT) {
return BUS_STATUS_TX_ERRORS;
}
return BUS_STATUS_CONNECTED;
}
// show the EMS bus status plus both Rx and Tx queues
void EMSESP::show_emsbus(uuid::console::Shell & shell) {
void EMSESP::show_ems(uuid::console::Shell & shell) {
// EMS bus information
if (rxservice_.bus_connected()) {
uint8_t success_rate = 0;
@@ -120,7 +156,7 @@ void EMSESP::show_emsbus(uuid::console::Shell & shell) {
}
shell.printfln(F("EMS Bus info:"));
shell.printfln(F(" Tx mode: %d"), Settings().ems_tx_mode());
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { shell.printfln(F(" Tx mode: %d"), settings.tx_mode); });
shell.printfln(F(" Bus protocol: %s"), EMSbus::is_ht3() ? F("HT3") : F("Buderus"));
shell.printfln(F(" #telegrams received: %d"), rxservice_.telegram_count());
shell.printfln(F(" #read requests sent: %d"), txservice_.telegram_read_count());
@@ -174,12 +210,6 @@ void EMSESP::show_emsbus(uuid::console::Shell & shell) {
shell.println();
}
// display in the console all the data we have from ems devices and external sensors
void EMSESP::show_values(uuid::console::Shell & shell) {
show_device_values(shell);
show_sensor_values(shell);
}
// show EMS device values
void EMSESP::show_device_values(uuid::console::Shell & shell) {
if (emsdevices.empty()) {
@@ -330,8 +360,6 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
return;
}
uint8_t ems_bus_id = Settings().ems_bus_id();
// for each byte, check the bits and determine the device_id
for (uint8_t data_byte = 0; data_byte < telegram->message_length; data_byte++) {
uint8_t next_byte = telegram->message_data[data_byte];
@@ -342,7 +370,7 @@ void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
uint8_t device_id = ((data_byte + 1) * 8) + bit;
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
// when the version info is received, it will automagically add the device
if ((device_id != ems_bus_id) && !(EMSESP::device_exists(device_id))) {
if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) {
LOG_DEBUG(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id);
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
}
@@ -593,7 +621,8 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
// if we ask ourself at roomcontrol for version e.g. 0B 98 02 00 20
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
#ifdef EMSESP_DEBUG
LOG_TRACE(F("[DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
// get_uptime is only updated once per loop, does not give the right time
LOG_DEBUG(F("[DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
#endif
return; // it's an echo
}
@@ -643,7 +672,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
if (length == 1) {
#ifdef EMSESP_DEBUG
char s[4];
if(first_value & 0x80) {
if (first_value & 0x80) {
LOG_TRACE(F("[DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_);
// time measurement starts here, use millis because get_uptime is only updated once per loop
rx_time_ = ::millis();
@@ -677,231 +706,6 @@ void EMSESP::send_raw_telegram(const char * data) {
txservice_.send_raw(data);
}
// sets the ems read only flag preventing any Tx from going out
void EMSESP::set_ems_read_only() {
ems_read_only_ = Settings().ems_read_only();
LOG_DEBUG(F("Setting EMS read-only mode to %s"), ems_read_only_ ? F("on") : F("off"));
}
// console commands to add
void EMSESP::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(show), F_(devices)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_devices(shell); });
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(show), F_(emsbus)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_emsbus(shell); });
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(show)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
show_devices(shell);
show_emsbus(shell);
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(show), F_(values)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::show_device_values(shell);
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(refresh)},
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("Requesting data from EMS devices"));
EMSESP::fetch_device_values();
});
EMSESPShell::commands->add_command(
ShellContext::EMS,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(bus_id)},
flash_string_vector{F_(deviceid_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) {
Settings settings;
settings.ems_bus_id(device_id);
settings.commit();
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id());
} else {
shell.println(F("Must be 0B, 0D, 0A, 0F or 12"));
}
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{
read_flash_string(F("0B")),
read_flash_string(F("0D")),
read_flash_string(F("0A")),
read_flash_string(F("0F")),
read_flash_string(F("12")),
};
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(tx_mode)},
flash_string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
Settings settings;
settings.ems_tx_mode(tx_mode);
settings.commit();
shell.printfln(F_(tx_mode_fmt), settings.ems_tx_mode());
// reset counters
txservice_.telegram_read_count(0);
txservice_.telegram_write_count(0);
txservice_.telegram_fail_count(0);
// reset the UART
EMSuart::stop();
EMSuart::start(tx_mode);
});
EMSESPShell::commands->add_command(
ShellContext::EMS,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(read_only)},
flash_string_vector{F_(bool_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (arguments[0] == read_flash_string(F_(on))) {
settings.ems_read_only(true);
settings.commit();
EMSESP::ems_read_only();
} else if (arguments[0] == read_flash_string(F_(off))) {
settings.ems_read_only(false);
settings.commit();
EMSESP::ems_read_only();
} else {
shell.println(F("Must be on or off"));
return;
}
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(send), F_(telegram)},
flash_string_vector{F_(data_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
EMSESP::send_raw_telegram(arguments.front().c_str());
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(scan), F_(devices)},
flash_string_vector{F_(deep_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
if (arguments.size() == 0) {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
} else {
shell.printfln(F("Performing a deep scan by pinging our device library..."));
std::vector<uint8_t> Device_Ids;
Device_Ids.push_back(0x08); // Boilers - 0x08
Device_Ids.push_back(0x38); // HeatPump - 0x38
Device_Ids.push_back(0x30); // Solar Module - 0x30
Device_Ids.push_back(0x09); // Controllers - 0x09
Device_Ids.push_back(0x02); // Connect - 0x02
Device_Ids.push_back(0x48); // Gateway - 0x48
Device_Ids.push_back(0x20); // Mixing Devices - 0x20
Device_Ids.push_back(0x21); // Mixing Devices - 0x21
Device_Ids.push_back(0x22); // Mixing Devices - 0x22
Device_Ids.push_back(0x23); // Mixing Devices - 0x23
Device_Ids.push_back(0x28); // Mixing Devices WW- 0x28
Device_Ids.push_back(0x29); // Mixing Devices WW- 0x29
Device_Ids.push_back(0x10); // Thermostats - 0x10
Device_Ids.push_back(0x17); // Thermostats - 0x17
Device_Ids.push_back(0x18); // Thermostat remote - 0x18
Device_Ids.push_back(0x19); // Thermostat remote - 0x19
Device_Ids.push_back(0x1A); // Thermostat remote - 0x1A
Device_Ids.push_back(0x1B); // Thermostat remote - 0x1B
Device_Ids.push_back(0x11); // Switches - 0x11
// send the read command with Version command
for (const uint8_t device_id : Device_Ids) {
EMSESP::send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
}
}
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Settings settings;
shell.printfln(F_(tx_mode_fmt), settings.ems_tx_mode());
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id());
shell.printfln(F_(read_only_fmt), settings.ems_read_only() ? F_(on) : F_(off));
});
EMSESPShell::commands->add_command(ShellContext::EMS,
CommandFlags::USER,
flash_string_vector{F_(watch)},
flash_string_vector{F_(watch_format_mandatory), F_(watchid_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
// get raw/pretty
if (arguments[0] == read_flash_string(F_(raw))) {
emsesp::EMSESP::watch(WATCH_RAW); // raw
} else if (arguments[0] == read_flash_string(F_(on))) {
emsesp::EMSESP::watch(WATCH_ON); // on
} else if (arguments[0] == read_flash_string(F_(off))) {
emsesp::EMSESP::watch(WATCH_OFF); // off
} else {
shell.printfln(F_(invalid_watch));
return;
}
uint16_t watch_id;
if (arguments.size() == 2) {
// get the watch_id if its set
watch_id = Helpers::hextoint(arguments[1].c_str());
} else {
watch_id = WATCH_ID_NONE;
}
emsesp::EMSESP::watch_id(watch_id);
uint8_t watch = emsesp::EMSESP::watch();
if (watch == WATCH_OFF) {
shell.printfln(F("Watching telegrams is off"));
return;
}
// if logging is off, the watch won't show anything, show force it back to INFO
if (!logger_.enabled(Level::NOTICE)) {
shell.log_level(Level::NOTICE);
}
if (watch == WATCH_ON) {
shell.printfln(F("Watching incoming telegrams, displayed in decoded format"));
} else {
shell.printfln(F("Watching incoming telegrams, displayed as raw bytes")); // WATCH_RAW
}
watch_id = emsesp::EMSESP::watch_id();
if (watch_id != WATCH_ID_NONE) {
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
}
});
// enter the context
Console::enter_custom_context(shell, context);
}
// kick off the party, start all the services
void EMSESP::start() {
// Load our library of known devices
@@ -909,40 +713,47 @@ void EMSESP::start() {
#include "device_library.h"
};
system_.start();
network_.start();
console_.start();
sensors_.start();
txservice_.start();
shower_.start();
mqtt_.start();
#ifdef ESP32
SPIFFS.begin(true);
#elif defined(ESP8266)
LittleFS.begin();
#endif
set_ems_read_only(); // see if we have Tx disabled and set the flag
esp8266React.begin(); // starts wifi, ap, ota, security, mqtt services
emsespSettingsService.begin(); // load settings
console_.start(); // telnet and serial console
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
mqtt_.start(EMSESP::esp8266React.getMqttClient()); // mqtt init
sensors_.start(); // dallas external sensors
shower_.start(); // initialize shower timer and shower alert
txservice_.start(); // sets bus ID, sends out request for EMS devices
webServer.begin(); // start web server
}
// loop de loop
void EMSESP::loop() {
// network returns false if an OTA is being carried out
// so we disable all services when an OTA is happening
if (network_.loop()) {
system_.loop(); // does LED and checks system health, and syslog service
mqtt_.loop(); // starts mqtt, and sends out anything in the queue
rxservice_.loop(); // process what ever is in the rx queue
txservice_.loop(); // check that the Tx is all ok
shower_.loop(); // check for shower on/off
sensors_.loop(); // this will also send out via MQTT
console_.loop(); // telnet/serial console
#ifndef EMSESP_STANDALONE
esp8266React.loop();
#endif
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
last_fetch_ = uuid::get_uptime();
fetch_device_values();
}
system_.loop(); // does LED and checks system health, and syslog service
mqtt_.loop(); // starts mqtt, and sends out anything in the queue
rxservice_.loop(); // process what ever is in the rx queue
txservice_.loop(); // check that the Tx is all ok
shower_.loop(); // check for shower on/off
sensors_.loop(); // this will also send out via MQTT
console_.loop(); // telnet/serial console
// helps ease wifi dropouts effecting MQTT and Telnet services
// https://github.com/esp8266/Arduino/blob/e721089e601985e633641ab7323f81a84ea0cd1b/cores/esp8266/core_esp8266_wiring.cpp#L41-L57
delay(1);
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
last_fetch_ = uuid::get_uptime();
fetch_device_values();
}
#if defined(ESP32)
delay(1);
#endif
}
} // namespace emsesp

View File

@@ -33,11 +33,16 @@
#include <uuid/telnet.h>
#endif
#include <ESP8266React.h>
#include "EMSESPStatusService.h"
#include "EMSESPDevicesService.h"
#include "EMSESPSettingsService.h"
#include "EMSESPScanDevicesService.h"
#include "emsdevice.h"
#include "emsfactory.h"
#include "telegram.h"
#include "mqtt.h"
#include "settings.h"
#include "system.h"
#include "sensors.h"
#include "console.h"
@@ -84,15 +89,16 @@ class EMSESP {
static uint8_t actual_master_thermostat();
static void actual_master_thermostat(const uint8_t device_id);
static void show_values(uuid::console::Shell & shell);
static void show_device_values(uuid::console::Shell & shell);
static void show_sensor_values(uuid::console::Shell & shell);
static void show_devices(uuid::console::Shell & shell);
static void show_emsbus(uuid::console::Shell & shell);
static void show_ems(uuid::console::Shell & shell);
static void add_context_menus();
static void reset_tx(uint8_t const tx_mode);
static void incoming_telegram(uint8_t * data, const uint8_t length);
static const std::vector<Sensors::Device> sensor_devices() {
@@ -114,6 +120,9 @@ class EMSESP {
return watch_;
}
enum Bus_status : uint8_t { BUS_STATUS_CONNECTED = 0, BUS_STATUS_TX_ERRORS, BUS_STATUS_OFFLINE };
static uint8_t bus_status();
static bool tap_water_active() {
return tap_water_active_;
}
@@ -122,27 +131,32 @@ class EMSESP {
tap_water_active_ = tap_water_active;
}
static void set_ems_read_only();
static bool ems_read_only() {
return ems_read_only_;
}
static void console_commands(Shell & shell, unsigned int context);
static void fetch_device_values(const uint8_t device_id = 0);
static bool add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand);
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
// services
static Mqtt mqtt_;
static System system_;
static Network network_;
static Sensors sensors_;
static Console console_;
static Shower shower_;
static RxService rxservice_;
static TxService txservice_;
// web controllers
static ESP8266React esp8266React;
static EMSESPSettingsService emsespSettingsService;
static EMSESPStatusService emsespStatusService;
static EMSESPDevicesService emsespDevicesService;
static EMSESPScanDevicesService emsespScanDevicesService;
static uuid::log::Logger logger() {
return logger_;
}
private:
EMSESP() = delete;
@@ -163,14 +177,12 @@ class EMSESP {
uint8_t flags;
};
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
static std::vector<Device_record> device_library_;
static std::vector<Device_record> device_library_;
static uint8_t actual_master_thermostat_;
static uint16_t watch_id_;
static uint8_t watch_;
static bool tap_water_active_;
static bool ems_read_only_;
};
} // namespace emsesp

View File

@@ -18,52 +18,27 @@
#include "mqtt.h"
#include "emsesp.h"
#include "version.h"
MAKE_PSTR_WORD(username)
MAKE_PSTR_WORD(qos)
MAKE_PSTR_WORD(base)
MAKE_PSTR_WORD(heartbeat)
MAKE_PSTR_WORD(ip)
MAKE_PSTR_WORD(port)
MAKE_PSTR_WORD(nested)
MAKE_PSTR_WORD(single)
MAKE_PSTR_WORD(ha)
MAKE_PSTR_WORD(custom)
MAKE_PSTR_WORD(publish_time)
MAKE_PSTR_WORD(publish)
MAKE_PSTR_WORD(connected)
MAKE_PSTR_WORD(disconnected)
MAKE_PSTR(mqtt_ip_fmt, "IP = %s")
MAKE_PSTR(mqtt_user_fmt, "Username = %s")
MAKE_PSTR(mqtt_password_fmt, "Password = %S")
MAKE_PSTR(mqtt_port_fmt, "Port = %d")
MAKE_PSTR(mqtt_enabled_fmt, "MQTT is %s")
MAKE_PSTR(mqtt_base_fmt, "Base = %s")
MAKE_PSTR(mqtt_qos_fmt, "QOS = %ld")
MAKE_PSTR(mqtt_retain_fmt, "Retain Flag = %s")
MAKE_PSTR(mqtt_format_fmt, "Format for JSON = %s")
MAKE_PSTR(mqtt_heartbeat_fmt, "Heartbeat = %s")
MAKE_PSTR(mqtt_publish_time_fmt, "Publish time = %d seconds")
MAKE_PSTR(logger_name, "mqtt")
namespace emsesp {
// exposing static stuff to compiler/linker
#ifndef EMSESP_STANDALONE
AsyncMqttClient Mqtt::mqttClient_;
AsyncMqttClient * Mqtt::mqttClient_;
#endif
// static parameters we make global
std::string Mqtt::hostname_;
uint8_t Mqtt::mqtt_qos_;
uint16_t Mqtt::publish_time_;
std::vector<Mqtt::MQTTFunction> Mqtt::mqtt_functions_;
bool Mqtt::mqtt_retain_;
uint8_t Mqtt::mqtt_qos_;
std::string Mqtt::mqtt_hostname_; // copy of hostname
uint8_t Mqtt::mqtt_format_;
std::string Mqtt::mqtt_base_;
uint16_t Mqtt::mqtt_publish_fails_ = 0;
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
bool Mqtt::force_publish_ = false;
uint16_t Mqtt::mqtt_message_id_ = 0;
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
@@ -84,116 +59,6 @@ MqttMessage::MqttMessage(uint8_t operation, const std::string & topic, const std
, retain(retain) {
}
// empty queue
void Mqtt::flush_message_queue() {
mqtt_messages_.clear();
mqtt_message_id_ = 0;
}
// restart MQTT services
void Mqtt::reconnect() {
#ifndef EMSESP_STANDALONE
mqttClient_.disconnect();
#endif
LOG_DEBUG(F("Reconnecting..."));
}
// MQTT setup
void Mqtt::setup() {
// exit if already initialized
if (mqtt_start_) {
return;
}
mqtt_start_ = true;
// get some settings and store them locally. This is also because the asyncmqtt library uses references for char *
Settings settings;
mqtt_enabled_ = settings.mqtt_enabled();
mqtt_hostname_ = settings.hostname();
mqtt_base_ = settings.mqtt_base();
mqtt_qos_ = settings.mqtt_qos();
mqtt_format_ = settings.mqtt_format();
mqtt_retain_ = settings.mqtt_retain();
mqtt_heartbeat_ = settings.mqtt_heartbeat();
mqtt_publish_time_ = settings.mqtt_publish_time() * 1000; // convert to seconds
mqtt_ip_ = settings.mqtt_ip();
mqtt_user_ = settings.mqtt_user();
mqtt_password_ = settings.mqtt_password();
mqtt_port_ = settings.mqtt_port();
// if IP is empty, disable MQTT
if (settings.mqtt_ip().empty()) {
mqtt_enabled_ = false;
}
init(); // set up call backs. only done once.
#ifdef EMSESP_STANDALONE
mqtt_enabled_ = true; // force it on for debugging standalone
#else
mqttClient_.setServer(mqtt_ip_.c_str(), mqtt_port_);
mqttClient_.setClientId(mqtt_hostname_.c_str());
mqttClient_.setWill(make_topic(will_topic_, "status"), 1, true, "offline"); // qos 1, retain true
mqttClient_.setKeepAlive(MQTT_KEEP_ALIVE);
mqttClient_.setCleanSession(false);
// set credentials if we have them
if (!mqtt_user_.empty()) {
mqttClient_.setCredentials(mqtt_user_.c_str(), mqtt_password_.c_str());
}
#endif
mqtt_connecting_ = false;
mqtt_last_connection_ = uuid::get_uptime();
mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN;
LOG_DEBUG(F("Configuring MQTT service..."));
}
// MQTT init callbacks
// This should only be executed once
void Mqtt::init() {
if (mqtt_init_) {
return;
}
mqtt_init_ = true;
#ifndef EMSESP_STANDALONE
mqttClient_.onConnect([this](bool sessionPresent) { on_connect(); });
mqttClient_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
LOG_DEBUG(F("Disconnected from server"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
LOG_ERROR(F("Server identifier Rejected"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
LOG_ERROR(F("Server unavailable"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
LOG_ERROR(F("Malformed credentials"));
}
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
LOG_ERROR(F("Not authorized"));
}
// Reset reconnection delay
mqtt_last_connection_ = uuid::get_uptime();
mqtt_connecting_ = false;
mqtt_start_ = false; // will force a new start()
});
// mqttClient.onSubscribe([this](uint16_t packetId, uint8_t qos) { LOG_DEBUG(F("Subscribe ACK for PID %d"), packetId); });
mqttClient_.onPublish([this](uint16_t packetId) { on_publish(packetId); });
mqttClient_.onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
on_message(topic, payload, len);
});
#endif
}
Mqtt::MQTTFunction::MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function)
: device_id_(device_id)
, topic_(topic)
@@ -219,64 +84,27 @@ void Mqtt::subscribe(const std::string & topic, mqtt_function_p cb) {
// Checks for connection, establishes a connection if not
// sends out top item on publish queue
void Mqtt::loop() {
// exit if MQTT is not enabled, there is no WIFI or we're still in the MQTT connection process
// exit if MQTT is not enabled or ig there is no WIFI
#ifndef EMSESP_STANDALONE
if (!mqtt_enabled_ || mqtt_connecting_ || (WiFi.status() != WL_CONNECTED)) {
if (!connected()) {
#else
if (false) {
#endif
return;
}
// if we're already connected....
if (connected()) {
if (force_publish_) {
force_publish_ = false;
send_heartbeat(); // create a heartbeat payload
EMSESP::publish_all_values(); // add sensors and mqtt to queue
// process_all_queue(); // publish everything on queue
}
// send out heartbeat
uint32_t currentMillis = uuid::get_uptime();
if ((currentMillis - last_heartbeat_ > MQTT_HEARTBEAT_INTERVAL)) {
last_heartbeat_ = currentMillis;
send_heartbeat();
}
// create publish messages for each of the EMS device values, adding to queue
if (currentMillis - last_publish_ >= mqtt_publish_time_) {
last_publish_ = currentMillis;
EMSESP::publish_all_values();
}
// publish top item from MQTT queue to stop flooding
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
last_mqtt_poll_ = currentMillis;
process_queue();
}
return;
uint32_t currentMillis = uuid::get_uptime();
// create publish messages for each of the EMS device values, adding to queue
if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) {
last_publish_ = currentMillis;
EMSESP::publish_all_values();
}
// We need to reconnect. Check when was the last time we tried this
if (mqtt_last_connection_ && (uuid::get_uptime() - mqtt_last_connection_ < mqtt_reconnect_delay_)) {
return;
// publish top item from MQTT queue to stop flooding
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
last_mqtt_poll_ = currentMillis;
process_queue();
}
mqtt_connecting_ = true; // we're doing a connection now
// Increase the reconnect delay for next time
mqtt_reconnect_delay_ += MQTT_RECONNECT_DELAY_STEP;
if (mqtt_reconnect_delay_ > MQTT_RECONNECT_DELAY_MAX) {
mqtt_reconnect_delay_ = MQTT_RECONNECT_DELAY_MAX;
}
#ifndef EMSESP_STANDALONE
setup();
LOG_INFO(F("Connecting to the MQTT server..."));
mqttClient_.connect(); // Connect to the MQTT broker
#endif
}
// print MQTT log and other stuff to console
@@ -379,7 +207,7 @@ void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t devic
}
// called when an MQTT Publish ACK is received
// its a poor-mans QOS we assume the ACK represents the last Publish sent
// its a poor man's QOS we assume the ACK represents the last Publish sent
// check if ACK matches the last Publish we sent, if not report an error. Only if qos is 1 or 2
// and always remove from queue
void Mqtt::on_publish(uint16_t packetId) {
@@ -403,64 +231,58 @@ void Mqtt::on_publish(uint16_t packetId) {
mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK
}
// builds up a topic by prefixing the base and hostname
// builds up a topic by prefixing the hostname
char * Mqtt::make_topic(char * result, const std::string & topic) {
if (!mqtt_base_.empty()) {
strlcpy(result, mqtt_base_.c_str(), MQTT_TOPIC_MAX_SIZE);
strlcat(result, "/", MQTT_TOPIC_MAX_SIZE);
strlcat(result, mqtt_hostname_.c_str(), MQTT_TOPIC_MAX_SIZE);
} else {
strlcpy(result, mqtt_hostname_.c_str(), MQTT_TOPIC_MAX_SIZE);
}
strlcpy(result, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE);
strlcat(result, "/", MQTT_TOPIC_MAX_SIZE);
strlcat(result, topic.c_str(), MQTT_TOPIC_MAX_SIZE);
return result;
}
void Mqtt::start() {
send_start_topic();
void Mqtt::start(AsyncMqttClient * mqttClient) {
mqttClient_ = mqttClient; // copy over from esp8266-react's MQTT service
// get the hostname, which we'll use to prefix to all topics
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { hostname_ = wifiSettings.hostname.c_str(); });
// fetch MQTT settings
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) {
publish_time_ = mqttSettings.publish_time * 1000; // convert to milliseconds
mqtt_qos_ = mqttSettings.mqtt_qos;
});
#ifndef EMSESP_STANDALONE
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
mqttClient_->setWill(make_topic(will_topic_, "status"), 1, true, "offline"); // with qos 1, retain true
mqttClient_->onMessage([this](char * topic, char * payload, AsyncMqttClientMessageProperties properties, size_t len, size_t index, size_t total) {
on_message(topic, payload, len);
mqttClient_->onPublish([this](uint16_t packetId) { on_publish(packetId); });
});
#endif
}
// send online appended with the version information as JSON
void Mqtt::send_start_topic() {
StaticJsonDocument<90> doc;
doc["event"] = "start";
doc["version"] = Settings().app_version();
publish("info", doc, false); // send with retain off
void Mqtt::set_publish_time(uint16_t publish_time) {
publish_time_ = publish_time * 1000; // convert to milliseconds
}
void Mqtt::set_qos(uint8_t mqtt_qos) {
mqtt_qos_ = mqtt_qos;
}
// MQTT onConnect - when a connect is established
void Mqtt::on_connect() {
mqtt_reconnect_delay_ = Mqtt::MQTT_RECONNECT_DELAY_MIN;
mqtt_last_connection_ = uuid::get_uptime();
mqtt_connecting_ = false;
// send info topic appended with the version information as JSON
StaticJsonDocument<90> doc;
doc["event"] = "start";
doc["version"] = EMSESP_APP_VERSION;
doc["ip"] = WiFi.localIP().toString();
publish("info", doc, false); // send with retain off
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
send_heartbeat(); // send heartbeat if enabled
LOG_INFO(F("MQTT connected"));
}
// send periodic MQTT message with system information
void Mqtt::send_heartbeat() {
if (!mqtt_heartbeat_) {
return;
}
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
doc["rssid"] = Network::wifi_quality();
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
doc["uptime_sec"] = uuid::get_uptime_sec();
doc["freemem"] = System::free_mem();
doc["mqttpublishfails"] = mqtt_publish_fails_;
publish("heartbeat", doc, false); // send to MQTT with retain off
}
// add MQTT message to queue, payload is a string
void Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
// can't have bogus topics, but empty payloads are ok
@@ -485,7 +307,7 @@ void Mqtt::queue_subscribe_message(const std::string & topic) {
}
auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false);
LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
// LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
// if the queue is full, make room but removing the last one
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
@@ -495,14 +317,6 @@ void Mqtt::queue_subscribe_message(const std::string & topic) {
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
}
// Publish using the user's custom retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload) {
publish(topic, payload, mqtt_retain_);
}
void Mqtt::publish(const std::string & topic, const JsonDocument & payload) {
publish(topic, payload, mqtt_retain_);
}
// MQTT Publish, using a specific retain flag
void Mqtt::publish(const std::string & topic, const std::string & payload, bool retain) {
queue_publish_message(topic, payload, retain);
@@ -517,12 +331,12 @@ void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool
// for booleans, which get converted to string values 1 and 0
void Mqtt::publish(const std::string & topic, const bool value) {
queue_publish_message(topic, value ? "1" : "0", mqtt_retain_);
queue_publish_message(topic, value ? "1" : "0", false);
}
// no payload
void Mqtt::publish(const std::string & topic) {
queue_publish_message(topic, "", mqtt_retain_);
queue_publish_message(topic, "", false);
}
// publish all queued messages to MQTT
@@ -543,10 +357,9 @@ void Mqtt::process_queue() {
auto mqtt_message = mqtt_messages_.front();
auto message = mqtt_message.content_;
// append the hostname and base to the topic, unless we're doing native HA which has a different format
// append the hostname to the topic, unless we're doing native HA which has a different format
// if the topic starts with "homeassistant" we leave it untouched, otherwise append host
char full_topic[MQTT_TOPIC_MAX_SIZE];
// if the topic starts with "homeassistant" we leave it untouched, otherwise append host and base
if (strncmp(message->topic.c_str(), "homeassistant/", 13) == 0) {
strcpy(full_topic, message->topic.c_str());
} else {
@@ -557,7 +370,7 @@ void Mqtt::process_queue() {
if (message->operation == Operation::SUBSCRIBE) {
LOG_DEBUG(F("Subscribing to topic: %s"), full_topic);
#ifndef EMSESP_STANDALONE
uint16_t packet_id = mqttClient_.subscribe(full_topic, mqtt_qos_);
uint16_t packet_id = mqttClient_->subscribe(full_topic, mqtt_qos_);
#else
uint16_t packet_id = 1;
#endif
@@ -579,8 +392,7 @@ void Mqtt::process_queue() {
// else try and publish it
#ifndef EMSESP_STANDALONE
// uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str());
uint16_t packet_id = mqttClient_.publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
uint16_t packet_id = mqttClient_->publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
#else
uint16_t packet_id = 1;
#endif
@@ -611,243 +423,4 @@ void Mqtt::process_queue() {
mqtt_messages_.pop_front(); // remove the message from the queue
}
// add console commands
void Mqtt::console_commands(Shell & shell, unsigned int context) {
EMSESPShell::commands->add_command(
ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(heartbeat)},
flash_string_vector{F_(bool_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (arguments[0] == read_flash_string(F_(on))) {
settings.mqtt_heartbeat(true);
settings.commit();
} else if (arguments[0] == read_flash_string(F_(off))) {
settings.mqtt_heartbeat(false);
settings.commit();
} else {
shell.println(F("Must be on or off"));
return;
}
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
});
EMSESPShell::commands->add_command(
ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(format)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
uint8_t value;
if (arguments[0] == read_flash_string(F_(single))) {
value = Settings::MQTT_format::SINGLE;
} else if (arguments[0] == read_flash_string(F_(nested))) {
value = Settings::MQTT_format::NESTED;
} else if (arguments[0] == read_flash_string(F_(ha))) {
value = Settings::MQTT_format::HA;
} else if (arguments[0] == read_flash_string(F_(custom))) {
value = Settings::MQTT_format::CUSTOM;
} else {
shell.println(F("Must be single, nested or ha"));
return;
}
settings.mqtt_format(value);
settings.commit();
shell.println(F("Please restart EMS-ESP"));
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F_(single)), read_flash_string(F_(nested)), read_flash_string(F_(ha)), read_flash_string(F_(custom))};
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(publish_time)},
flash_string_vector{F_(seconds_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint16_t publish_time = Helpers::atoint(arguments.front().c_str());
Settings settings;
settings.mqtt_publish_time(publish_time);
settings.commit();
shell.printfln(F_(mqtt_publish_time_fmt), settings.mqtt_publish_time());
});
EMSESPShell::commands->add_command(
ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(enabled)},
flash_string_vector{F_(bool_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (arguments[0] == read_flash_string(F_(on))) {
settings.mqtt_enabled(true);
settings.commit();
reconnect();
} else if (arguments[0] == read_flash_string(F_(off))) {
settings.mqtt_enabled(false);
settings.commit();
reconnect();
} else {
shell.println(F("Value must be on or off"));
return;
}
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(ip)},
flash_string_vector{F_(ip_address_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (!arguments.empty()) {
settings.mqtt_ip(arguments.front());
settings.commit();
}
auto ip = settings.mqtt_ip();
shell.printfln(F_(mqtt_ip_fmt), ip.empty() ? uuid::read_flash_string(F_(unset)).c_str() : ip.c_str());
reconnect();
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(base)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
Settings settings;
if (!arguments.empty()) {
settings.mqtt_base(arguments.front());
settings.commit();
}
reconnect();
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(port)},
flash_string_vector{F_(port_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
Settings settings;
if (!arguments.empty()) {
settings.mqtt_port(atoi(arguments.front().c_str()));
settings.commit();
}
reconnect();
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(qos)},
flash_string_vector{F_(n_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
uint8_t value = (arguments[0]).at(0) - '0';
if (value <= 2) {
Settings settings;
settings.mqtt_qos(value);
settings.commit();
reconnect();
} else {
shell.printfln(F("Value must be 0, 1 or 2"));
}
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::USER,
flash_string_vector{F_(publish)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.printfln(F("Publishing all values to MQTT"));
force_publish_ = true;
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
reconnect();
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(password)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(new_password_prompt1), [=](Shell & shell, bool completed, const std::string & password1) {
if (completed) {
shell.enter_password(F_(new_password_prompt2),
[password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
Settings settings;
settings.mqtt_password(password2);
settings.commit();
shell.println(F("MQTT password updated"));
reconnect();
} else {
shell.println(F("Passwords do not match"));
}
}
});
}
});
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(username)},
flash_string_vector{F_(name_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (arguments.empty()) {
settings.mqtt_user("");
} else {
settings.mqtt_user(arguments.front());
}
settings.commit();
shell.printfln(F_(mqtt_user_fmt),
settings.mqtt_user().empty() ? uuid::read_flash_string(F_(unset)).c_str()
: settings.mqtt_user().c_str());
reconnect();
});
EMSESPShell::commands->add_command(ShellContext::MQTT,
CommandFlags::USER,
flash_string_vector{F_(show)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_mqtt(shell); });
EMSESPShell::commands->add_command(
ShellContext::MQTT, CommandFlags::USER, flash_string_vector{F_(set)}, [](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Settings settings;
shell.printfln(F_(mqtt_enabled_fmt), settings.mqtt_enabled() ? F_(enabled) : F_(disabled));
shell.printfln(F_(mqtt_ip_fmt), settings.mqtt_ip().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.mqtt_ip().c_str());
shell.printfln(F_(mqtt_user_fmt), settings.mqtt_user().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.mqtt_user().c_str());
shell.printfln(F_(mqtt_password_fmt), settings.mqtt_password().empty() ? F_(unset) : F_(asterisks));
shell.printfln(F_(mqtt_port_fmt), settings.mqtt_port());
shell.printfln(F_(mqtt_base_fmt), settings.mqtt_base().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.mqtt_base().c_str());
shell.printfln(F_(mqtt_qos_fmt), settings.mqtt_qos());
shell.printfln(F_(mqtt_retain_fmt), settings.mqtt_retain() ? F_(enabled) : F_(disabled));
if (settings.mqtt_format() == Settings::MQTT_format::SINGLE) {
shell.printfln(F_(mqtt_format_fmt), F_(single));
} else if (settings.mqtt_format() == Settings::MQTT_format::NESTED) {
shell.printfln(F_(mqtt_format_fmt), F_(nested));
} else if (settings.mqtt_format() == Settings::MQTT_format::HA) {
shell.printfln(F_(mqtt_format_fmt), F_(ha));
} else if (settings.mqtt_format() == Settings::MQTT_format::CUSTOM) {
shell.printfln(F_(mqtt_format_fmt), F_(custom));
}
shell.printfln(F_(mqtt_heartbeat_fmt), settings.mqtt_heartbeat() ? F_(enabled) : F_(disabled));
shell.printfln(F_(mqtt_publish_time_fmt), settings.mqtt_publish_time());
shell.println();
});
// enter the context
Console::enter_custom_context(shell, context);
}
} // namespace emsesp

View File

@@ -32,9 +32,7 @@
#endif
#include "helpers.h"
#include "settings.h"
#include "system.h"
#include "network.h"
#include "console.h"
#include <uuid/log.h>
@@ -63,38 +61,53 @@ struct MqttMessage {
class Mqtt {
public:
void loop();
void start();
void send_heartbeat();
void start(AsyncMqttClient * mqttClient);
void set_publish_time(uint16_t publish_time);
void set_qos(uint8_t mqtt_qos);
enum Operation { PUBLISH, SUBSCRIBE };
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 60; // include host and base etc
// are static to be accessed from EMS devices
static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb);
static void subscribe(const std::string & topic, mqtt_function_p cb);
static void publish(const std::string & topic, const std::string & payload);
static void publish(const std::string & topic, const std::string & payload, bool retain);
static void publish(const std::string & topic, const JsonDocument & payload);
static void publish(const std::string & topic, const JsonDocument & payload, bool retain);
static void publish(const std::string & topic, const std::string & payload, bool retain = false);
static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false);
static void publish(const std::string & topic, const bool value);
static void publish(const std::string & topic);
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id);
static void console_commands(Shell & shell, unsigned int context);
static void show_mqtt(uuid::console::Shell & shell);
static void on_connect();
void disconnect() {
#ifdef EMSESP_STANDALONE
return;
#else
mqttClient_->disconnect();
#endif
}
void incoming(char * topic, char * payload); // for testing
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 60; // include host and base etc
static bool connected() {
#ifdef EMSESP_STANDALONE
return true;
#else
return mqttClient_.connected();
return mqttClient_->connected();
#endif
}
static uint32_t publish_fails() {
return mqtt_publish_fails_;
}
private:
static uuid::log::Logger logger_;
@@ -111,40 +124,28 @@ class Mqtt {
static std::deque<QueuedMqttMessage> mqtt_messages_;
#ifndef EMSESP_STANDALONE
static AsyncMqttClient mqttClient_;
static AsyncMqttClient * mqttClient_;
#endif
void flush_message_queue();
void setup();
static constexpr size_t MAX_MQTT_MESSAGES = 50;
static size_t maximum_mqtt_messages_;
static uint16_t mqtt_message_id_;
static bool mqtt_retain_;
static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50;
static constexpr uint32_t MQTT_PUBLISH_WAIT = 250; // delay between sending publishes, to account for large payloads
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
static constexpr uint8_t MQTT_KEEP_ALIVE = 60; // 60 seconds. This could also be less, like 30 seconds
static constexpr uint32_t MQTT_RECONNECT_DELAY_MIN = 2000; // Try to reconnect in 2 seconds upon disconnection
static constexpr uint32_t MQTT_RECONNECT_DELAY_STEP = 3000; // Increase the reconnect delay in 3 seconds after each failed attempt
static constexpr uint32_t MQTT_RECONNECT_DELAY_MAX = 120000; // Set reconnect time to 2 minutes at most
static constexpr uint32_t MQTT_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50;
static constexpr uint32_t MQTT_PUBLISH_WAIT = 250; // delay between sending publishes, to account for large payloads
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
static void queue_subscribe_message(const std::string & topic);
void on_publish(uint16_t packetId);
void on_message(char * topic, char * payload, size_t len);
void on_connect();
static char * make_topic(char * result, const std::string & topic);
void process_queue();
void process_all_queue();
void send_start_topic();
static void reconnect();
void init();
static void show_mqtt(uuid::console::Shell & shell);
static uint16_t mqtt_publish_fails_;
class MQTTFunction {
public:
@@ -157,33 +158,14 @@ class Mqtt {
};
static std::vector<MQTTFunction> mqtt_functions_; // list of mqtt callbacks for all devices
static uint16_t mqtt_publish_fails_;
uint32_t mqtt_last_connection_ = 0;
uint32_t mqtt_reconnect_delay_ = MQTT_RECONNECT_DELAY_MIN;
bool mqtt_init_ = false;
bool mqtt_start_ = false;
bool mqtt_connecting_ = false;
uint16_t mqtt_publish_time_;
uint32_t last_heartbeat_ = 0;
uint32_t last_mqtt_poll_ = 0;
uint32_t last_publish_ = 0;
static bool force_publish_;
// settings
static std::string mqtt_hostname_;
static std::string mqtt_base_;
// settings, copied over
static std::string hostname_;
static uint8_t mqtt_qos_;
static uint8_t mqtt_format_;
std::string mqtt_ip_;
std::string mqtt_user_;
std::string mqtt_password_;
bool mqtt_enabled_ = true; // start off assuming we want to connect
bool mqtt_heartbeat_;
uint16_t mqtt_port_;
};
static uint16_t publish_time_;
}; // namespace emsesp
} // namespace emsesp

View File

@@ -1,392 +0,0 @@
/*
* EMS-ESP - https://github.com/proddy/EMS-ESP
* Copyright 2019 Paul Derbyshire
*
* 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/>.
*/
// code by nomis - https://github.com/nomis
#include "network.h"
MAKE_PSTR(logger_name, "network")
namespace emsesp {
uuid::log::Logger Network::logger_{F_(logger_name), uuid::log::Facility::KERN};
void Network::start() {
#ifndef EMSESP_STANDALONE
// We want the device to come up in opmode=0 (WIFI_OFF), when erasing the flash this is not the default.
// If needed, we save opmode=0 before disabling persistence so the device boots with WiFi disabled in the future.
if (WiFi.getMode() != WIFI_OFF) {
WiFi.mode(WIFI_OFF);
}
WiFi.persistent(false);
WiFi.disconnect(true);
WiFi.setAutoReconnect(false);
WiFi.mode(WIFI_STA);
#endif
#if defined(ESP8266)
sta_mode_connected_ = WiFi.onStationModeConnected(std::bind(&Network::sta_mode_connected, this, _1));
sta_mode_disconnected_ = WiFi.onStationModeDisconnected(std::bind(&Network::sta_mode_disconnected, this, _1));
sta_mode_got_ip_ = WiFi.onStationModeGotIP(std::bind(&Network::sta_mode_got_ip, this, _1));
WiFi.setSleepMode(WIFI_NONE_SLEEP); // added to possibly fix wifi dropouts in arduino core 2.5.0
// ref: https://github.com/esp8266/Arduino/issues/6471
// ref: https://github.com/esp8266/Arduino/issues/6366
// high tx power causing weird behavior, slightly lowering from 20.5 to 20.0 may help stability
// WiFi.setOutputPower(20.0); // in DBM
connect(); // connect to WiFi
ota_setup(); // initialize OTA
#elif defined(ESP32)
WiFi.mode(WIFI_MODE_MAX);
WiFi.mode(WIFI_MODE_NULL);
WiFi.onEvent(std::bind(&Network::sta_mode_connected, this, _1, _2), WiFiEvent_t::SYSTEM_EVENT_STA_CONNECTED);
WiFi.onEvent(std::bind(&Network::sta_mode_disconnected, this, _1, _2), WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED);
WiFi.onEvent(std::bind(&Network::sta_mode_got_ip, this, _1, _2), WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP);
WiFi.onEvent(std::bind(&Network::sta_mode_start, this, _1, _2), WiFiEvent_t::SYSTEM_EVENT_STA_START);
connect(); // connect to WiFi
ota_setup(); // initialize OTA
#endif
}
#if defined(ESP32)
// set the ESP32 Wifi hostname. Didn't work during setup and I didn't want to use a delay().
// see https://github.com/espressif/arduino-esp32/blob/master/libraries/WiFi/examples/WiFiIPv6/WiFiIPv6.ino#L79
void Network::sta_mode_start(WiFiEvent_t event, WiFiEventInfo_t info) {
WiFi.setHostname(Settings().hostname().c_str());
}
#endif
#if defined(ESP8266)
void Network::sta_mode_connected(const WiFiEventStationModeConnected & event) {
LOG_INFO(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
event.ssid.c_str(),
event.bssid[0],
event.bssid[1],
event.bssid[2],
event.bssid[3],
event.bssid[4],
event.bssid[5],
event.channel);
// turn off safe mode
System::save_safe_mode(false);
}
#elif defined(ESP32)
void Network::sta_mode_connected(WiFiEvent_t event, WiFiEventInfo_t info) {
LOG_INFO(F("Connected to %s (%02X:%02X:%02X:%02X:%02X:%02X) on channel %u"),
info.connected.ssid,
info.sta_connected.mac[0],
info.sta_connected.mac[1],
info.sta_connected.mac[2],
info.sta_connected.mac[3],
info.sta_connected.mac[4],
info.sta_connected.mac[5],
info.connected.channel);
// turn off safe mode
System::save_safe_mode(false);
}
#endif
#if defined(ESP8266)
void Network::sta_mode_disconnected(const WiFiEventStationModeDisconnected & event) {
// disconnect if failed to connect a few times, unless already in safe mode!
if (event.reason == 201) {
if (++disconnect_count_ == 3) {
if (System::safe_mode()) {
LOG_ERROR(F("Failed to connect to WiFi %s after %d attempts"), event.ssid.c_str(), disconnect_count_ - 1);
disconnect_count_ = 0;
} else {
LOG_ERROR(F("Failed to connect to WiFi. Rebooting into Safe mode"));
System::restart(true); // set safe mode and restart
}
}
}
}
#elif defined(ESP32)
void Network::sta_mode_disconnected(WiFiEvent_t event, WiFiEventInfo_t info) {
// LOG_ERROR(F("Failed to connect to WiFi %s, reason code %d"), info.disconnected.ssid, info.disconnected.reason);
}
#endif
#if defined(ESP8266)
void Network::sta_mode_got_ip(const WiFiEventStationModeGotIP & event) {
LOG_INFO(F("Obtained IPv4 address %s/%s and gateway %s"),
uuid::printable_to_string(event.ip).c_str(),
uuid::printable_to_string(event.mask).c_str(),
uuid::printable_to_string(event.gw).c_str());
}
#elif defined(ESP32)
void Network::sta_mode_got_ip(WiFiEvent_t event, WiFiEventInfo_t info) {
LOG_INFO(F("Obtained IPv4 address %s/%s and gateway %s"),
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.ip.addr)).c_str(),
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.netmask.addr)).c_str(),
uuid::printable_to_string(IPAddress(info.got_ip.ip_info.gw.addr)).c_str());
}
#endif
void Network::connect() {
Settings settings;
if (settings.wifi_ssid().empty()) {
return;
}
#ifndef EMSESP_STANDALONE
if (!settings.hostname().empty()) {
#if defined(ESP8266)
// experiment with fixed IP
// IPAddress ip(10, 10, 10, 140);
// IPAddress gateway(10, 10, 10, 1);
// IPAddress subnet(255, 255, 255, 0);
// WiFi.config(ip, gateway, subnet);
WiFi.config(INADDR_ANY, INADDR_ANY, INADDR_ANY);
WiFi.hostname(settings.hostname().c_str());
#elif defined(ESP32)
WiFi.config(INADDR_NONE, INADDR_NONE, INADDR_NONE);
#endif
}
LOG_INFO(F("Connecting to wifi ssid %s..."), settings.wifi_ssid().c_str());
WiFi.begin(settings.wifi_ssid().c_str(), settings.wifi_password().c_str());
#endif
}
void Network::reconnect() {
disconnect();
connect();
}
void Network::disconnect() {
#if defined(ESP8266)
WiFi.disconnect();
#elif defined(ESP32)
WiFi.disconnect(true);
#endif
}
// OTA Setup
void Network::ota_setup() {
#ifndef EMSESP_STANDALONE
if (ota_) {
delete ota_;
ota_ = nullptr;
}
ota_ = new ArduinoOTAClass;
Settings settings;
ota_->setPort(OTA_PORT);
if (settings.hostname().empty()) {
ota_->setHostname(EMSESP_DEFAULT_HOSTNAME);
} else {
ota_->setHostname(settings.hostname().c_str());
}
ota_->setPassword(settings.admin_password().c_str());
ota_->onStart([this]() {
LOG_DEBUG(F("OTA starting (send type %d)..."), ota_->getCommand());
// turn off UART stuff to stop interference on an ESP8266 only
#if defined(ESP8266)
EMSuart::stop(); // UART stop
#endif
in_ota_ = true; // set flag so all other services stop
});
ota_->onEnd([this]() { LOG_DEBUG(F("OTA done, automatically restarting")); });
ota_->onProgress([this](unsigned int progress, unsigned int total) {
/*
static unsigned int _progOld;
unsigned int _prog = (progress / (total / 100));
if (_prog != _progOld) {
LOG_DEBUG(F("[OTA] Progress: %u%%"), _prog);
_progOld = _prog;
}
*/
});
ota_->onError([this](ota_error_t error) {
if (error == OTA_AUTH_ERROR) {
LOG_ERROR(F("[OTA] Auth Failed"));
} else if (error == OTA_BEGIN_ERROR) {
LOG_ERROR(F("[OTA] Begin Failed"));
} else if (error == OTA_CONNECT_ERROR) {
LOG_ERROR(F("[OTA] Connect Failed"));
} else if (error == OTA_RECEIVE_ERROR) {
LOG_ERROR(F("[OTA] Receive Failed"));
} else if (error == OTA_END_ERROR) {
LOG_ERROR(F("[OTA] End Failed"));
} else {
LOG_ERROR(F("[OTA] Error %d"), error);
};
});
// start ota service
ota_->begin();
LOG_INFO(F("Listening to firmware updates on %s.local:%u"), ota_->getHostname().c_str(), OTA_PORT);
#endif
}
// loop - mainly calls OTA
bool Network::loop() {
#ifndef EMSESP_STANDALONE
if (ota_) {
ota_->handle();
}
#endif
return !in_ota_;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
void Network::scan(uuid::console::Shell & shell) {
#ifndef EMSESP_STANDALONE
int8_t ret = WiFi.scanNetworks(true);
if (ret == WIFI_SCAN_RUNNING) {
shell.println(F("Scanning for WiFi networks..."));
shell.block_with([](uuid::console::Shell & shell, bool stop) -> bool {
int8_t ret = WiFi.scanComplete();
if (ret == WIFI_SCAN_RUNNING) {
return stop;
} else if (ret == WIFI_SCAN_FAILED || ret < 0) {
shell.println(F("WiFi scan failed"));
return true;
} else {
shell.printfln(F("Found %u networks:"), ret);
shell.println();
for (uint8_t i = 0; i < (uint8_t)ret; i++) {
shell.printfln(F(" %s (channel %u at %d dBm) %s"), WiFi.SSID(i).c_str(), WiFi.channel(i), WiFi.RSSI(i), WiFi.BSSIDstr(i).c_str());
}
shell.println();
WiFi.scanDelete();
return true;
}
});
} else {
shell.println(F("WiFi scan failed"));
}
#endif
}
void Network::show_network(uuid::console::Shell & shell) {
#ifndef EMSESP_STANDALONE
switch (WiFi.status()) {
case WL_IDLE_STATUS:
shell.printfln(F("WiFi: idle"));
break;
case WL_NO_SSID_AVAIL:
shell.printfln(F("WiFi: network not found"));
break;
case WL_SCAN_COMPLETED:
shell.printfln(F("WiFi: network scan complete"));
break;
case WL_CONNECTED: {
shell.printfln(F("WiFi: connected"));
shell.println();
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality());
shell.println();
shell.printfln(F("MAC address: %s"), WiFi.macAddress().c_str());
#if defined(ESP8266)
shell.printfln(F("Hostname: %s"), WiFi.hostname().c_str());
#elif defined(ESP32)
shell.printfln(F("Hostname: %s"), WiFi.getHostname());
#endif
shell.println();
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str());
} break;
case WL_CONNECT_FAILED:
shell.printfln(F("WiFi: connection failed"));
break;
case WL_CONNECTION_LOST:
shell.printfln(F("WiFi: connection lost"));
break;
case WL_DISCONNECTED:
shell.printfln(F("WiFi: disconnected"));
break;
case WL_NO_SHIELD:
default:
shell.printfln(F("WiFi: unknown"));
break;
}
shell.println();
#endif
}
#pragma GCC diagnostic pop
// Return the quality (Received Signal Strength Indicator) of the WiFi network as a %. Or -1 if disconnected.
// High quality: 90% ~= -55dBm
// Medium quality: 50% ~= -75dBm
// Low quality: 30% ~= -85dBm
// Unusable quality: 8% ~= -96dBm
int8_t Network::wifi_quality() {
#ifndef EMSESP_STANDALONE
if (WiFi.status() != WL_CONNECTED) {
return -1;
}
int dBm = WiFi.RSSI();
#else
int8_t dBm = -70;
#endif
if (dBm <= -100) {
return 0;
}
if (dBm >= -50) {
return 100;
}
return 2 * (dBm + 100);
}
} // namespace emsesp

View File

@@ -1,102 +0,0 @@
/*
* EMS-ESP - https://github.com/proddy/EMS-ESP
* Copyright 2019 Paul Derbyshire
*
* 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/>.
*/
// code by nomis - https://github.com/nomis
#ifndef EMSESP_NETWORK_H_
#define EMSESP_NETWORK_H_
#ifndef EMSESP_STANDALONE
#if defined(ESP8266)
#include <ESP8266WiFi.h>
#elif defined(ESP32)
#include <WiFi.h>
#endif
#include <ArduinoOTA.h>
#endif
#include <string>
#include <vector>
#include <functional>
#include "settings.h"
#include "system.h"
#include "helpers.h"
namespace emsesp {
using namespace std::placeholders; // for `_1`
class Network {
public:
static void connect();
static void reconnect();
static void disconnect();
static void scan(uuid::console::Shell & shell);
static void show_network(uuid::console::Shell & shell);
static int8_t wifi_quality();
void start();
bool loop();
bool connected() {
#ifndef EMSESP_STANDALONE
return (WiFi.status() == WL_CONNECTED);
#else
return true;
#endif
}
private:
static constexpr uint16_t OTA_PORT = 8266;
#if defined(ESP8266)
void sta_mode_connected(const WiFiEventStationModeConnected & event);
void sta_mode_disconnected(const WiFiEventStationModeDisconnected & event);
void sta_mode_got_ip(const WiFiEventStationModeGotIP & event);
WiFiEventHandler sta_mode_connected_;
WiFiEventHandler sta_mode_disconnected_;
WiFiEventHandler sta_mode_got_ip_;
ArduinoOTAClass * ota_;
#elif defined(ESP32)
void sta_mode_connected(WiFiEvent_t event, WiFiEventInfo_t info);
void sta_mode_disconnected(WiFiEvent_t event, WiFiEventInfo_t info);
void sta_mode_got_ip(WiFiEvent_t event, WiFiEventInfo_t info);
void sta_mode_start(WiFiEvent_t event, WiFiEventInfo_t info);
ArduinoOTAClass * ota_;
#endif
static uuid::log::Logger logger_;
void ota_setup();
uint8_t disconnect_count_ = 0;
bool in_ota_ = false;
};
} // namespace emsesp
#endif

View File

@@ -65,18 +65,17 @@ void Roomctrl::check(const uint8_t addr, const uint8_t * data) {
if (hc_ > 3) {
return;
}
// no reply if the temperature is not set
if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) {
return;
}
// reply to writes with write nack byte
if (addr & 0x80) { // it's a write to us
nack_write(); // we don't accept writes.
return;
}
// for now we only reply to version and remote temperature
// reads: for now we only reply to version and remote temperature
// empty message back if temperature not set or unknown message type
if (data[2] == 0x02) {
version(addr, data[0]);
} else if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) {
unknown(addr, data[0], data[2], data[3]);
} else if (data[2] == 0xAF && data[3] == 0) {
temperature(addr, data[0]);
} else {

View File

@@ -19,6 +19,7 @@
// code written by nomis - https://github.com/nomis
#include "sensors.h"
#include "emsesp.h"
MAKE_PSTR(logger_name, "sensors")
@@ -28,11 +29,13 @@ uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON}
void Sensors::start() {
// copy over values from MQTT so we don't keep on quering the filesystem
mqtt_format_ = Settings().mqtt_format();
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) {
mqtt_format_ = settings.mqtt_format; // single, nested or ha
});
// if we're using HA MQTT Discovery, send out the config
// currently we just do this for a single sensor (sensor1)
if (mqtt_format_ == Settings::MQTT_format::HA) {
if (mqtt_format_ == MQTT_format::HA) {
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
@@ -246,7 +249,7 @@ void Sensors::publish_values() {
// if we're not using nested JSON, send each sensor out seperately
// sensor1, sensor2 etc...
// e.g. sensor_1 = {"temp":20.2}
if (mqtt_format_ == Settings::MQTT_format::SINGLE) {
if (mqtt_format_ == MQTT_format::SINGLE) {
StaticJsonDocument<100> doc;
for (const auto & device : devices_) {
char s[5];
@@ -274,7 +277,7 @@ void Sensors::publish_values() {
uint8_t i = 1;
for (const auto & device : devices_) {
if (mqtt_format_ == Settings::MQTT_format::CUSTOM) {
if (mqtt_format_ == MQTT_format::CUSTOM) {
char s[5];
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c_, 2);
} else {
@@ -288,7 +291,7 @@ void Sensors::publish_values() {
}
}
if (mqtt_format_ == Settings::MQTT_format::HA) {
if (mqtt_format_ == MQTT_format::HA) {
Mqtt::publish("homeassistant/sensor/ems-esp/external/state", doc);
} else {
Mqtt::publish("sensors", doc);

View File

@@ -25,7 +25,6 @@
#include <vector>
#include "helpers.h"
#include "settings.h"
#include "mqtt.h"
#include "console.h"

View File

@@ -1,300 +0,0 @@
/*
* EMS-ESP - https://github.com/proddy/EMS-ESP
* Copyright 2019 Paul Derbyshire
*
* 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 "settings.h"
MAKE_PSTR(settings_filename, "/settings.msgpack")
MAKE_PSTR(settings_backup_filename, "/settings.msgpack~")
MAKE_PSTR(logger_name, "settings")
namespace emsesp {
// clang-format off
#define EMSESP_SETTINGS_DATA \
EMSESP_SETTINGS_SIMPLE(std::string, "", app_version, "", (), "2.0") \
EMSESP_SETTINGS_SIMPLE(std::string, "", admin_password, "", (), EMSESP_DEFAULT_ADMIN_PASSWORD) \
EMSESP_SETTINGS_SIMPLE(std::string, "", hostname, "", (), EMSESP_DEFAULT_HOSTNAME) \
EMSESP_SETTINGS_SIMPLE(std::string, "", wifi_ssid, "", (), "") \
EMSESP_SETTINGS_SIMPLE(std::string, "", wifi_password, "", (), "") \
EMSESP_SETTINGS_CUSTOM(std::string, "", syslog_host, "", (), "") \
EMSESP_SETTINGS_ENUM(uuid::log::Level, "", syslog_level, "", (), uuid::log::Level::OFF) \
EMSESP_SETTINGS_SIMPLE(unsigned long, "", syslog_mark_interval, "", (), EMSESP_DEFAULT_SYSLOG_INTERVAL) \
EMSESP_SETTINGS_SIMPLE(uint8_t, "", ems_bus_id, "", (), EMSESP_DEFAULT_BUS_ID) \
EMSESP_SETTINGS_SIMPLE(uint8_t, "", ems_tx_mode, "", (), EMSESP_DEFAULT_TX_MODE) \
EMSESP_SETTINGS_SIMPLE(bool, "", ems_read_only, "", (), EMSESP_DEFAULT_EMS_READ_ONLY) \
EMSESP_SETTINGS_SIMPLE(bool, "", shower_timer, "", (), EMSESP_DEFAULT_SHOWER_TIMER) \
EMSESP_SETTINGS_SIMPLE(bool, "", shower_alert, "", (), EMSESP_DEFAULT_SHOWER_ALERT) \
EMSESP_SETTINGS_SIMPLE(uint8_t, "", master_thermostat, "", (), EMSESP_DEFAULT_MASTER_THERMOSTAT) \
EMSESP_SETTINGS_SIMPLE(uint16_t, "", mqtt_publish_time, "", (), EMSESP_DEFAULT_MQTT_PUBLISH_TIME) \
EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_ip, "", (), "") \
EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_user, "", (), "") \
EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_password, "", (), "") \
EMSESP_SETTINGS_SIMPLE(uint16_t, "", mqtt_port, "", (), EMSESP_DEFAULT_MQTT_PORT) \
EMSESP_SETTINGS_SIMPLE(bool, "", mqtt_enabled, "", (), EMSESP_DEFAULT_MQTT_ENABLED) \
EMSESP_SETTINGS_SIMPLE(std::string, "", mqtt_base, "", (), EMSESP_DEFAULT_MQTT_BASE) \
EMSESP_SETTINGS_SIMPLE(uint8_t, "", mqtt_qos, "", (), EMSESP_DEFAULT_MQTT_QOS) \
EMSESP_SETTINGS_SIMPLE(bool, "", mqtt_retain, "", (), EMSESP_DEFAULT_MQTT_RETAIN) \
EMSESP_SETTINGS_SIMPLE(uint8_t, "", mqtt_format, "", (), EMSESP_DEFAULT_MQTT_FORMAT) \
EMSESP_SETTINGS_SIMPLE(bool, "", mqtt_heartbeat, "", (), EMSESP_DEFAULT_MQTT_HEARTBEAT)
#define EMSESP_SETTINGS_SIMPLE EMSESP_SETTINGS_GENERIC
#define EMSESP_SETTINGS_CUSTOM EMSESP_SETTINGS_GENERIC
#define EMSESP_SETTINGS_ENUM EMSESP_SETTINGS_GENERIC
/* Create member data and flash strings */
#define EMSESP_SETTINGS_GENERIC(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
__type Settings::__name##_; \
MAKE_PSTR(__name, __key_prefix #__name __key_suffix)
EMSESP_SETTINGS_DATA
#undef EMSESP_SETTINGS_GENERIC
#undef EMSESP_SETTINGS_ENUM
void Settings::read_settings(const ArduinoJson::JsonDocument &doc) {
#define EMSESP_SETTINGS_GENERIC(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
__name(doc[FPSTR(__pstr__##__name)] | __read_default, ##__VA_ARGS__);
#define EMSESP_SETTINGS_ENUM(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
__name(static_cast<__type>(doc[FPSTR(__pstr__##__name)] | static_cast<int>(__read_default)), ##__VA_ARGS__);
EMSESP_SETTINGS_DATA
#undef EMSESP_SETTINGS_GENERIC
#undef EMSESP_SETTINGS_ENUM
}
void Settings::write_settings(ArduinoJson::JsonDocument &doc) {
#define EMSESP_SETTINGS_GENERIC(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
doc[FPSTR(__pstr__##__name)] = __name __get_function;
#define EMSESP_SETTINGS_ENUM(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
doc[FPSTR(__pstr__##__name)] = static_cast<int>(__name __get_function);
EMSESP_SETTINGS_DATA
#undef EMSESP_SETTINGS_GENERIC
}
#undef EMSESP_SETTINGS_GENERIC
#undef EMSESP_SETTINGS_SIMPLE
#undef EMSESP_SETTINGS_CUSTOM
#undef EMSESP_SETTINGS_ENUM
/* Create getters/setters for simple settings items only */
#define EMSESP_SETTINGS_SIMPLE(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
__type Settings::__name() const { \
return __name##_; \
} \
void Settings::__name(const __type &__name) { \
__name##_ = __name; \
}
#define EMSESP_SETTINGS_ENUM(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
__type Settings::__name() const { \
return __name##_; \
} \
void Settings::__name(__type __name) { \
__name##_ = __name; \
}
/* Create getters for settings items with custom setters */
#define EMSESP_SETTINGS_CUSTOM(__type, __key_prefix, __name, __key_suffix, __get_function, __read_default, ...) \
__type Settings::__name() const { \
return __name##_; \
}
EMSESP_SETTINGS_DATA
#undef EMSESP_SETTINGS_SIMPLE
#undef EMSESP_SETTINGS_CUSTOM
#undef EMSESP_SETTINGS_ENUM
// clang-format on
uuid::log::Logger Settings::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
// Settings which we don't persist
bool Settings::mounted_ = false;
bool Settings::unavailable_ = false;
bool Settings::loaded_ = false;
Settings::Settings() {
if (!unavailable_ && !mounted_) {
EMSuart::stop(); // temporary suspend UART because is can cause interference on the UART
#ifdef EMSESP_STANDALONE
if (true) {
#else
#if defined(ESP8266)
if (EMSESP_FS.begin()) {
#elif defined(ESP32)
if (EMSESP_FS.begin(true)) {
#endif
#endif
LOG_INFO(F("Mounted filesystem"));
mounted_ = true;
} else {
LOG_ERROR(F("Unable to mount filesystem"));
unavailable_ = true;
}
}
if (mounted_ && !loaded_) {
if (read_settings(uuid::read_flash_string(F_(settings_filename))) || read_settings(uuid::read_flash_string(F_(settings_backup_filename)))) {
loaded_ = true;
}
}
if (!loaded_) {
LOG_ERROR(F("Failed to load settings. Using defaults"));
read_settings(ArduinoJson::StaticJsonDocument<0>());
loaded_ = true;
}
EMSuart::restart();
}
// save settings to FS
void Settings::commit() {
if (mounted_) {
std::string filename = uuid::read_flash_string(F_(settings_filename));
std::string backup_filename = uuid::read_flash_string(F_(settings_backup_filename));
EMSuart::stop(); // temporary suspend UART because is can cause interference on the UART
LOG_DEBUG(F("Saving settings"));
if (write_settings(filename)) {
if (read_settings(filename, false)) {
write_settings(backup_filename);
}
}
EMSuart::restart(); // re-enable EMS bus
}
}
// read the settings from FS
bool Settings::read_settings(const std::string & filename, bool load) {
#ifndef EMSESP_STANDALONE
File file = EMSESP_FS.open(filename.c_str(), "r");
if (file) {
ArduinoJson::DynamicJsonDocument doc(BUFFER_SIZE);
auto error = ArduinoJson::deserializeMsgPack(doc, file);
if (error) {
LOG_ERROR(F("Failed to parse settings file %s: %s"), filename.c_str(), error.c_str());
return false;
} else {
if (load) {
LOG_INFO(F("Loading settings from file %s"), filename.c_str());
read_settings(doc);
}
return true;
}
} else {
LOG_ERROR(F("Settings file %s does not exist"), filename.c_str());
return false;
}
#else
LOG_INFO(F("Loading settings from file %s (%d)"), filename.c_str(), load);
return false;
#endif
}
// write settings from FS
bool Settings::write_settings(const std::string & filename) {
#ifndef EMSESP_STANDALONE
File file = EMSESP_FS.open(filename.c_str(), "w");
if (file) {
ArduinoJson::DynamicJsonDocument doc(BUFFER_SIZE);
write_settings(doc);
ArduinoJson::serializeMsgPack(doc, file);
if (file.getWriteError()) {
LOG_ERROR(F("Failed to write settings file %s: %u"), filename.c_str(), file.getWriteError());
return false;
} else {
return true;
}
} else {
LOG_ERROR(F("Unable to open settings file %s for writing"), filename.c_str());
return false;
}
#else
LOG_DEBUG(F("Write settings file %s"), filename.c_str());
return false;
#endif
}
// print the settings JSON to console (only in admin mode)
void Settings::show_settings(uuid::console::Shell & shell) {
#ifdef EMSESP_DEBUG
if (shell.has_flags(CommandFlags::ADMIN)) {
shell.printf(F("[DEBUG] Settings JSON: "));
ArduinoJson::DynamicJsonDocument doc(BUFFER_SIZE);
write_settings(doc);
ArduinoJson::serializeJson(doc, shell);
shell.println();
}
#endif
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
// format the FS. Wipes everything.
void Settings::format(uuid::console::Shell & shell) {
#ifndef EMSESP_STANDALONE
#if defined(ESP8266)
EMSuart::stop();
if (EMSESP_FS.begin()) {
#elif defined(ESP32)
if (EMSESP_FS.begin(true)) {
#endif
if (EMSESP_FS.remove(uuid::read_flash_string(F_(settings_filename)).c_str())) {
(void)EMSESP_FS.remove(uuid::read_flash_string(F_(settings_backup_filename)).c_str());
auto msg = F("Reset settings file to defaults");
shell.logger().warning(msg);
shell.flush();
System::restart(false);
} else {
auto msg = F("Error removing settings file");
shell.logger().emerg(msg);
}
} else {
auto msg = F("Unable to mount filesystem");
shell.logger().alert(msg);
}
#if defined(ESP8266)
EMSuart::restart();
#endif
#endif
}
void Settings::syslog_host(const std::string & syslog_host) {
#ifndef EMSESP_STANDALONE
IPAddress addr;
if (addr.fromString(syslog_host.c_str())) {
syslog_host_ = syslog_host;
} else {
syslog_host_.clear();
}
#else
syslog_host_ = std::string("99.99.99.99");
#endif
}
} // namespace emsesp
#pragma GCC diagnostic pop

View File

@@ -1,204 +0,0 @@
/*
* EMS-ESP - https://github.com/proddy/EMS-ESP
* Copyright 2019 Paul Derbyshire
*
* 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 EMSESP_SETTINGS_H
#define EMSESP_SETTINGS_H
#include <Arduino.h>
#include <ArduinoJson.h>
#include <string>
#include <vector>
// we use LittleFS on the ESP8266 and the older SPIFFS on the ESP32
#if defined(ESP8266)
#include <FS.h>
#include <LittleFS.h>
#define EMSESP_FS LittleFS
#elif defined(ESP32)
#include <SPIFFS.h>
#define EMSESP_FS SPIFFS
#endif
#include <uuid/log.h>
#include <uuid/console.h>
#include "helpers.h"
#include "console.h"
// default settings - these can be customized from within the application
#define EMSESP_DEFAULT_HOSTNAME "ems-esp"
#define EMSESP_DEFAULT_ADMIN_PASSWORD "neo"
#define EMSESP_DEFAULT_BUS_ID 0x0B
#define EMSESP_DEFAULT_TX_MODE 1
#define EMSESP_DEFAULT_MQTT_ENABLED true
#define EMSESP_DEFAULT_MQTT_BASE "home"
#define EMSESP_DEFAULT_MQTT_PORT 1883
#define EMSESP_DEFAULT_MQTT_QOS 1
#define EMSESP_DEFAULT_MQTT_RETAIN false
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // 2=nested
#define EMSESP_DEFAULT_MQTT_HEARTBEAT true
#define EMSESP_DEFAULT_EMS_READ_ONLY false
#define EMSESP_DEFAULT_SHOWER_TIMER false
#define EMSESP_DEFAULT_SHOWER_ALERT false
#define EMSESP_DEFAULT_SYSLOG_INTERVAL 0
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // 0=not set
#ifndef EMSESP_STANDALONE
#define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 10
#else
#define EMSESP_DEFAULT_MQTT_PUBLISH_TIME 0
#endif
namespace emsesp {
class Settings {
public:
Settings();
~Settings() = default;
void commit();
void show_settings(uuid::console::Shell & shell);
void format(uuid::console::Shell & shell);
std::string app_version() const;
void app_version(const std::string & app_version);
std::string admin_password() const;
void admin_password(const std::string & admin_password);
std::string hostname() const;
void hostname(const std::string & hostname);
std::string wifi_ssid() const;
void wifi_ssid(const std::string & wifi_ssid);
std::string wifi_password() const;
void wifi_password(const std::string & wifi_password);
std::string syslog_host() const;
void syslog_host(const std::string & syslog_host);
uuid::log::Level syslog_level() const;
void syslog_level(uuid::log::Level syslog_level);
unsigned long syslog_mark_interval() const;
void syslog_mark_interval(const unsigned long & syslog_mark_interval);
uint8_t ems_bus_id() const;
void ems_bus_id(const uint8_t & ems_bus_id);
uint8_t ems_tx_mode() const;
void ems_tx_mode(const uint8_t & ems_tx_mode);
bool ems_read_only() const;
void ems_read_only(const bool & ems_read_only);
uint16_t mqtt_publish_time() const;
void mqtt_publish_time(const uint16_t & mqtt_publish_time);
std::string mqtt_ip() const;
void mqtt_ip(const std::string & mqtt_ip);
std::string mqtt_user() const;
void mqtt_user(const std::string & mqtt_user);
std::string mqtt_password() const;
void mqtt_password(const std::string & mqtt_password);
uint16_t mqtt_port() const;
void mqtt_port(const uint16_t & mqtt_port);
bool mqtt_enabled() const;
void mqtt_enabled(const bool & mqtt_enabled);
std::string mqtt_base() const;
void mqtt_base(const std::string & mqtt_base);
uint8_t mqtt_qos() const;
void mqtt_qos(const uint8_t & mqtt_qos);
bool mqtt_retain() const;
void mqtt_retain(const bool & mqtt_retain);
bool mqtt_heartbeat() const;
void mqtt_heartbeat(const bool & mqtt_heartbeat);
bool shower_timer() const;
void shower_timer(const bool & shower_timer);
bool shower_alert() const;
void shower_alert(const bool & shower_alert);
uint8_t master_thermostat() const;
void master_thermostat(const uint8_t & master_thermostat);
enum MQTT_format : uint8_t { SINGLE = 1, NESTED, HA, CUSTOM };
uint8_t mqtt_format() const;
void mqtt_format(const uint8_t & mqtt_format);
private:
static constexpr size_t BUFFER_SIZE = 2048; // max size for the settings file
static uuid::log::Logger logger_;
// global, not saved
static bool mounted_;
static bool unavailable_;
static bool loaded_;
bool read_settings(const std::string & filename, bool load = true);
bool write_settings(const std::string & filename);
void read_settings(const ArduinoJson::JsonDocument & doc);
void write_settings(ArduinoJson::JsonDocument & doc);
static std::string app_version_;
static std::string admin_password_;
static std::string hostname_;
static std::string wifi_password_;
static std::string wifi_ssid_;
static std::string syslog_host_;
static uuid::log::Level syslog_level_;
static unsigned long syslog_mark_interval_;
static bool shower_timer_; // true if we want to report back on shower times
static bool shower_alert_; // true if we want the alert of cold water
static uint8_t master_thermostat_; // which thermostat device ID is leading
static uint8_t ems_tx_mode_; // Tx mode 1, 2 or 3
static uint8_t ems_bus_id_; // EMS bus ID, default 0x0B for Service Key
static bool ems_read_only_; // switch of Tx
static std::string mqtt_ip_;
static std::string mqtt_user_;
static std::string mqtt_password_;
static uint16_t mqtt_port_;
static bool mqtt_enabled_;
static std::string mqtt_base_;
static uint8_t mqtt_qos_;
static bool mqtt_retain_;
static bool mqtt_heartbeat_;
static uint16_t mqtt_publish_time_; // seconds
static uint8_t mqtt_format_;
};
} // namespace emsesp
#endif

View File

@@ -25,10 +25,10 @@ namespace emsesp {
uuid::log::Logger Shower::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
void Shower::start() {
Settings settings;
shower_timer_ = settings.shower_timer();
shower_alert_ = settings.shower_alert();
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
shower_timer_ = settings.shower_timer;
shower_alert_ = settings.shower_alert;
});
}
void Shower::loop() {

View File

@@ -23,7 +23,6 @@
#include <ArduinoJson.h>
#include "helpers.h"
#include "settings.h"
#include "console.h"
#include "mqtt.h"
#include "telegram.h"
@@ -31,7 +30,6 @@
#include <uuid/log.h>
namespace emsesp {
class Shower {

View File

@@ -19,22 +19,20 @@
#include "system.h"
#include "emsesp.h" // for send_raw_telegram() command
#include "version.h" // version of EMS-ESP
#include "version.h" // firmware version of EMS-ESP
MAKE_PSTR_WORD(syslog)
MAKE_PSTR_WORD(mark)
MAKE_PSTR_WORD(level)
MAKE_PSTR_WORD(host)
MAKE_PSTR_WORD(passwd)
MAKE_PSTR_WORD(hostname)
MAKE_PSTR_WORD(wifi)
MAKE_PSTR_WORD(ssid)
MAKE_PSTR_WORD(heartbeat)
MAKE_PSTR(host_fmt, "Host = %s")
MAKE_PSTR(hostname_fmt, "Hostname = %s")
MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus");
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s");
MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S")
MAKE_PSTR(system_heartbeat_fmt, "Heartbeat = %s")
MAKE_PSTR(logger_name, "system")
@@ -46,12 +44,7 @@ uuid::log::Logger System::logger_{F_(logger_name), uuid::log::Facility::KERN};
uuid::syslog::SyslogService System::syslog_;
#endif
#if defined(ESP8266)
RTCVars System::state_;
#endif
uint32_t System::heap_start_ = 0;
bool System::safe_mode_ = false;
int System::reset_counter_;
// handle generic system related MQTT commands
@@ -63,12 +56,6 @@ void System::mqtt_commands(const char * message) {
return;
}
// restart EMS-ESP
if (strcmp(message, "restart") == 0) {
LOG_INFO(F("Restart command received"));
restart();
}
if (doc["send"] != nullptr) {
const char * data = doc["send"];
EMSESP::send_raw_telegram(data);
@@ -153,17 +140,10 @@ void System::mqtt_commands(const char * message) {
}
// restart EMS-ESP
// mode = safe mode. true to enable on next boot
void System::restart(bool mode) {
// check for safe mode
if (mode) {
LOG_NOTICE("Restarting system in safe mode...");
} else {
LOG_NOTICE("Restarting system...");
}
void System::restart() {
LOG_NOTICE("Restarting system...");
Shell::loop_all();
save_safe_mode(mode);
delay(1000); // wait a second
#if defined(ESP8266)
ESP.reset();
@@ -172,6 +152,27 @@ void System::restart(bool mode) {
#endif
}
// format fs
// format the FS. Wipes everything.
void System::format(uuid::console::Shell & shell) {
auto msg = F("Resetting all settings to defaults");
shell.logger().warning(msg);
shell.flush();
#ifndef EMSESP_STANDALONE
EMSuart::stop();
#if defined(ESP8266)
LittleFS.format();
#elif defined(ESP32)
SPIFFS.format();
#endif
System::restart();
#endif
}
// return free heap mem as a percentage
uint8_t System::free_mem() {
#ifndef EMSESP_STANDALONE
@@ -183,6 +184,35 @@ uint8_t System::free_mem() {
return (100 * free_memory / heap_start_);
}
void System::syslog_init() {
// fetch settings
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
syslog_level_ = settings.syslog_level;
syslog_mark_interval_ = settings.syslog_mark_interval;
syslog_host_ = settings.syslog_host;
});
#ifndef EMSESP_STANDALONE
syslog_.start(); // syslog service
// configure syslog
IPAddress addr;
if (!addr.fromString(syslog_host_.c_str())) {
addr = (uint32_t)0;
}
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); });
syslog_.log_level((uuid::log::Level)syslog_level_);
syslog_.mark_interval(syslog_mark_interval_);
syslog_.destination(addr);
#endif
}
void System::set_heartbeat(bool system_heartbeat) {
system_heartbeat_ = system_heartbeat;
}
// first call. Sets memory and starts up the UART Serial bridge
void System::start() {
// set the inital free mem
@@ -194,93 +224,30 @@ void System::start() {
#endif
}
#ifndef EMSESP_STANDALONE
syslog_.start(); // syslog service
#endif
// fetch settings
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; });
config_syslog();
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat_ = settings.system_heartbeat; });
Settings settings;
// update the version to the latest build
settings.app_version(EMSESP_APP_VERSION);
settings.commit();
syslog_init();
#if defined(ESP32)
LOG_INFO(F("System booted (EMS-ESP version %s ESP32)"), settings.app_version().c_str());
LOG_INFO(F("System booted (EMS-ESP version %s ESP32)"), EMSESP_APP_VERSION);
#else
LOG_INFO(F("System booted (EMS-ESP version %s)"), settings.app_version().c_str());
LOG_INFO(F("System booted (EMS-ESP version %s)"), EMSESP_APP_VERSION);
#endif
if (LED_GPIO) {
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 is disabled
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 means disabled
}
// register MQTT system commands
Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, _1));
// RTC state variables - only for ESP8266
#if defined(ESP8266)
state_.registerVar(&reset_counter_); // we send a pointer to each of our variables
state_.registerVar(&safe_mode_);
state_.loadFromRTC();
#endif
// safe mode is forced at compile time
#ifdef EMSESP_SAFE_MODE
safe_mode_ = true;
#endif
// if there is no wifi ssid set (like an inital setup) go into safe mode
if (settings.wifi_ssid().empty()) {
safe_mode_ = true;
#ifndef EMSESP_FORCE_SERIAL
if (tx_mode_) {
EMSuart::start(tx_mode_); // start UART, if tx_mode is not 0
}
#ifndef EMSESP_STANDALONE
// if we're in safe switch is back so next boot it'll be normal. Safe mode is a one-off
if (safe_mode()) {
} else {
save_safe_mode(false); // next time boot up in normal mode
EMSuart::start(settings.ems_tx_mode());
}
#endif
}
// gets/sets the safe mode from RTC
bool System::safe_mode() {
return safe_mode_;
}
// set safe mode and save it to RTC
void System::safe_mode(const bool safe_mode) {
safe_mode_ = safe_mode;
}
// set safe mode and save it to RTC
void System::save_safe_mode(const bool safe_mode) {
bool curr_save_mode = safe_mode_; // remember current setting
safe_mode_ = safe_mode;
#if defined(ESP8266)
state_.saveToRTC();
#endif
safe_mode_ = curr_save_mode; // restore
}
// sets up syslog
void System::config_syslog() {
#ifndef EMSESP_STANDALONE
Settings settings;
IPAddress addr;
if (!addr.fromString(settings.syslog_host().c_str())) {
addr = (uint32_t)0;
}
syslog_.hostname(settings.hostname());
syslog_.log_level(settings.syslog_level());
syslog_.mark_interval(settings.syslog_mark_interval());
syslog_.destination(addr);
#endif
}
@@ -289,21 +256,33 @@ void System::loop() {
#ifndef EMSESP_STANDALONE
syslog_.loop();
#endif
if (LED_GPIO) {
led_monitor(); // check status and report back using the LED
}
led_monitor(); // check status and report back using the LED
system_check(); // check system health
// send out heartbeat
uint32_t currentMillis = uuid::get_uptime();
if (!last_heartbeat_ || (currentMillis - last_heartbeat_ > SYSTEM_HEARTBEAT_INTERVAL)) {
last_heartbeat_ = currentMillis;
if (system_heartbeat_) {
send_heartbeat();
}
}
}
void System::show_mem(const char * text) {
#ifndef EMSESP_STANDALONE
uint32_t mem = ESP.getFreeHeap();
#else
uint32_t mem = 1000;
#endif
LOG_NOTICE(F("{%s} Free mem: %ld (%d%%)"), text, mem, (100 * mem / heap_start_));
// send periodic MQTT message with system information
void System::send_heartbeat() {
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
int rssid = wifi_quality();
if (rssid != -1) {
doc["rssid"] = rssid;
}
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
doc["uptime_sec"] = uuid::get_uptime_sec();
doc["freemem"] = free_mem();
doc["mqttpublishfails"] = Mqtt::publish_fails();
Mqtt::publish("heartbeat", doc, false); // send to MQTT with retain off
}
// sets rate of led flash
@@ -320,7 +299,7 @@ void System::system_check() {
last_system_check_ = uuid::get_uptime();
#ifndef EMSESP_STANDALONE
if ((WiFi.status() != WL_CONNECTED) || safe_mode()) {
if (WiFi.status() != WL_CONNECTED) {
set_led_speed(LED_WARNING_BLINK_FAST);
system_healthy_ = false;
return;
@@ -331,7 +310,7 @@ void System::system_check() {
if (!EMSbus::bus_connected()) {
system_healthy_ = false;
set_led_speed(LED_WARNING_BLINK); // flash every 1/2 second from now on
LOG_ERROR(F("No connection to the EMS bus!"));
// LOG_ERROR(F("Error: No connection to the EMS bus"));
} else {
// if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed
if (!system_healthy_) {
@@ -346,6 +325,10 @@ void System::system_check() {
// flashes the LED
void System::led_monitor() {
if (!LED_GPIO) {
return;
}
static uint32_t led_last_blink_ = 0;
if (!led_last_blink_ || (uint32_t)(uuid::get_uptime() - led_last_blink_) >= led_flash_speed_) {
@@ -358,6 +341,29 @@ void System::led_monitor() {
}
}
// Return the quality (Received Signal Strength Indicator) of the WiFi network as a %. Or -1 if disconnected.
// High quality: 90% ~= -55dBm
// Medium quality: 50% ~= -75dBm
// Low quality: 30% ~= -85dBm
// Unusable quality: 8% ~= -96dBm
int8_t System::wifi_quality() {
#ifndef EMSESP_STANDALONE
if (WiFi.status() != WL_CONNECTED) {
return -1;
}
int dBm = WiFi.RSSI();
#else
int8_t dBm = -70;
#endif
if (dBm <= -100) {
return 0;
}
if (dBm >= -50) {
return 100;
}
return 2 * (dBm + 100);
}
void System::show_system(uuid::console::Shell & shell) {
shell.print(F("Uptime: "));
shell.print(uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3));
@@ -381,19 +387,70 @@ void System::show_system(uuid::console::Shell & shell) {
shell.printfln(F("Maximum free block size: %lu bytes"), (unsigned long)ESP.getMaxFreeBlockSize());
shell.printfln(F("Heap fragmentation: %u%"), ESP.getHeapFragmentation());
shell.printfln(F("Free continuations stack: %lu bytes"), (unsigned long)ESP.getFreeContStack());
FSInfo info;
if (EMSESP_FS.info(info)) {
shell.printfln(F("FS size: %zu bytes (block size %zu bytes, page size %zu bytes)"), info.totalBytes, info.blockSize, info.pageSize);
shell.printfln(F("FS used: %zu bytes"), info.usedBytes);
}
#elif defined(ESP32)
shell.printfln(F("SDK version: %s"), ESP.getSdkVersion());
shell.printfln(F("CPU frequency: %u MHz"), ESP.getCpuFreqMHz());
shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace());
shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap());
shell.printfln(F("Free mem: %d %%"), free_mem());
#endif
shell.println();
#ifndef EMSESP_STANDALONE
switch (WiFi.status()) {
case WL_IDLE_STATUS:
shell.printfln(F("WiFi: idle"));
break;
case WL_NO_SSID_AVAIL:
shell.printfln(F("WiFi: network not found"));
break;
case WL_SCAN_COMPLETED:
shell.printfln(F("WiFi: network scan complete"));
break;
case WL_CONNECTED: {
shell.printfln(F("WiFi: connected"));
shell.println();
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality());
shell.println();
shell.printfln(F("MAC address: %s"), WiFi.macAddress().c_str());
#if defined(ESP8266)
shell.printfln(F("Hostname: %s"), WiFi.hostname().c_str());
#elif defined(ESP32)
shell.printfln(F("Hostname: %s"), WiFi.getHostname());
#endif
shell.println();
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str());
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str());
} break;
case WL_CONNECT_FAILED:
shell.printfln(F("WiFi: connection failed"));
break;
case WL_CONNECTION_LOST:
shell.printfln(F("WiFi: connection lost"));
break;
case WL_DISCONNECTED:
shell.printfln(F("WiFi: disconnected"));
break;
case WL_NO_SHIELD:
default:
shell.printfln(F("WiFi: unknown"));
break;
}
#endif
}
@@ -413,12 +470,13 @@ void System::console_commands(Shell & shell, unsigned int context) {
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
if (completed) {
Settings settings;
if ((settings.admin_password().empty()) || (!password.empty() && password == settings.admin_password())) {
settings.format(shell);
} else {
shell.println(F("incorrect password"));
}
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
if (securitySettings.jwtSecret.equals(password.c_str())) {
format(shell);
} else {
shell.println(F("incorrect password"));
}
});
}
});
});
@@ -433,9 +491,12 @@ void System::console_commands(Shell & shell, unsigned int context) {
[password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
Settings settings;
settings.admin_password(password2);
settings.commit();
EMSESP::esp8266React.getSecuritySettingsService()->update(
[&](SecuritySettings & securitySettings) {
securitySettings.jwtSecret = password2.c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("Admin password updated"));
} else {
shell.println(F("Passwords do not match"));
@@ -446,116 +507,40 @@ void System::console_commands(Shell & shell, unsigned int context) {
});
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(disconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
Network::disconnect();
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(reconnect)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
Network::reconnect();
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(wifi), F_(scan)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Network::scan(shell); });
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::USER,
flash_string_vector{F_(show)},
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Network::show_network(shell);
show_system(shell);
show_system(shell); // has to be static
shell.println();
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(syslog), F_(host)},
flash_string_vector{F_(ip_address_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (!arguments.empty()) {
settings.syslog_host(arguments[0]);
settings.commit();
shell.println(F("Please restart EMS-ESP"));
}
auto host = settings.syslog_host();
shell.printfln(F_(host_fmt), !host.empty() ? host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
});
EMSESPShell::commands->add_command(
ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(syslog), F_(level)},
flash_string_vector{F_(log_level_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (!arguments.empty()) {
uuid::log::Level level;
if (uuid::log::parse_level_lowercase(arguments[0], level)) {
settings.syslog_level(level);
settings.commit();
shell.println(F("Please restart EMS-ESP"));
} else {
shell.printfln(F_(invalid_log_level));
return;
}
}
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(settings.syslog_level()));
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
return uuid::log::levels_lowercase();
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(syslog), F_(mark)},
flash_string_vector{F_(seconds_optional)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
if (!arguments.empty()) {
settings.syslog_mark_interval(String(arguments[0].c_str()).toInt());
settings.commit();
shell.println(F("Please restart EMS-ESP"));
}
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval());
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(hostname)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
Settings settings;
settings.hostname(arguments.front());
settings.commit();
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.hostname = arguments.front().c_str();
return StateUpdateResult::CHANGED;
},
"local");
});
EMSESPShell::commands->add_command(
ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
Settings settings;
settings.wifi_ssid(arguments.front());
settings.commit();
shell.printfln(F_(wifi_ssid_fmt), settings.wifi_ssid().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.wifi_ssid().c_str());
shell.println(F("WiFi SSID updated. Please restart"));
Network::reconnect();
},
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
Settings settings;
return std::vector<std::string>{settings.wifi_ssid()};
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
flash_string_vector{F_(name_mandatory)},
[](Shell & shell, const std::vector<std::string> & arguments) {
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.ssid = arguments.front().c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("WiFi SSID updated"));
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::ADMIN,
@@ -567,11 +552,13 @@ void System::console_commands(Shell & shell, unsigned int context) {
[password1](Shell & shell, bool completed, const std::string & password2) {
if (completed) {
if (password1 == password2) {
Settings settings;
settings.wifi_password(password2);
settings.commit();
shell.println(F("WiFi password updated. Please restart"));
Network::reconnect();
EMSESP::esp8266React.getWiFiSettingsService()->update(
[&](WiFiSettings & wifiSettings) {
wifiSettings.password = password2.c_str();
return StateUpdateResult::CHANGED;
},
"local");
shell.println(F("WiFi password updated"));
} else {
shell.println(F("Passwords do not match"));
}
@@ -581,32 +568,45 @@ void System::console_commands(Shell & shell, unsigned int context) {
});
});
EMSESPShell::commands->add_command(
ShellContext::SYSTEM,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) {
shell.printfln(F_(hostname_fmt), wifiSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : wifiSettings.hostname.c_str());
});
if (shell.has_flags(CommandFlags::ADMIN)) {
shell.printfln("Wifi:");
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) {
shell.print(" ");
shell.printfln(F_(wifi_ssid_fmt), wifiSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : wifiSettings.ssid.c_str());
shell.print(" ");
shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
});
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
shell.printfln(F("Syslog:"));
shell.print(" ");
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.print(" ");
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(static_cast<uuid::log::Level>(settings.syslog_level)));
shell.print(" ");
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
shell.println();
bool system_heartbeat;
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat = settings.system_heartbeat; });
shell.printfln(F_(system_heartbeat_fmt), system_heartbeat ? F_(enabled) : F_(disabled));
});
}
});
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
CommandFlags::USER,
flash_string_vector{F_(set)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
Settings settings;
shell.printfln(F_(hostname_fmt),
settings.hostname().empty() ? uuid::read_flash_string(F_(unset)).c_str() : settings.hostname().c_str());
flash_string_vector{F_(show), F_(mqtt)},
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
if (shell.has_flags(CommandFlags::ADMIN)) {
shell.printfln("Wifi:");
shell.print(" ");
shell.printfln(F_(wifi_ssid_fmt),
settings.wifi_ssid().empty() ? uuid::read_flash_string(F_(unset)).c_str()
: settings.wifi_ssid().c_str());
shell.print(" ");
shell.printfln(F_(wifi_password_fmt), settings.wifi_password().empty() ? F_(unset) : F_(asterisks));
shell.printfln(F("Syslog:"));
auto host = settings.syslog_host();
shell.print(" ");
shell.printfln(F_(host_fmt), !host.empty() ? host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
shell.print(" ");
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(settings.syslog_level()));
shell.print(" ");
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval());
}
});
// enter the context
Console::enter_custom_context(shell, context);

View File

@@ -23,15 +23,10 @@
#include <ArduinoJson.h>
#include "helpers.h"
#include "settings.h"
#include "console.h"
#include "mqtt.h"
#include "telegram.h"
#if defined(ESP8266)
#include <RTCVars.h>
#endif
#ifndef EMSESP_STANDALONE
#include <uuid/syslog.h>
#endif
@@ -44,23 +39,15 @@ namespace emsesp {
class System {
public:
static uint8_t free_mem();
void start();
void loop();
static bool safe_mode();
static void save_safe_mode(const bool safe_mode);
static void safe_mode(const bool safe_mode);
static void restart(bool safe_mode);
static void restart() {
restart(false); // default, don't boot into safe mode
}
static void show_mem(const char * text);
static void console_commands(Shell & shell, unsigned int context);
static void restart();
static void format(uuid::console::Shell & shell);
static uint8_t free_mem();
void syslog_init();
void set_heartbeat(bool system_heartbeat);
static void console_commands(Shell & shell, unsigned int context);
private:
static uuid::log::Logger logger_;
@@ -69,11 +56,13 @@ class System {
static uuid::syslog::SyslogService syslog_;
#endif
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 10000; // check every 10 seconds
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 or safe-mode
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 10000; // check every 10 seconds
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 SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
// internal LED
#ifndef EMSESP_NO_LED
#if defined(ESP8266)
static constexpr uint8_t LED_GPIO = 2;
static constexpr uint8_t LED_ON = LOW;
@@ -85,29 +74,33 @@ class System {
static constexpr uint8_t LED_GPIO = 5;
static constexpr uint8_t LED_ON = LOW;
#endif
#endif
#else
static constexpr uint8_t LED_GPIO = 0;
static constexpr uint8_t LED_GPIO = 0; // no LED
static constexpr uint8_t LED_ON = 0;
#endif
void led_monitor();
void set_led_speed(uint32_t speed);
void mqtt_commands(const char * message);
void config_syslog();
void send_heartbeat();
void system_check();
static void show_system(uuid::console::Shell & shell);
static void show_system(uuid::console::Shell & shell);
static int8_t wifi_quality();
void system_check();
bool system_healthy_ = false;
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly
static bool safe_mode_;
bool system_healthy_ = false;
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly
static uint32_t heap_start_;
static int reset_counter_;
uint32_t last_heartbeat_ = 0;
#if defined(ESP8266)
static RTCVars state_;
#endif
// settings
uint8_t tx_mode_;
bool system_heartbeat_;
uint8_t syslog_level_;
uint32_t syslog_mark_interval_;
String syslog_host_;
};
} // namespace emsesp

View File

@@ -41,7 +41,7 @@ const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E,
uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time a valid Rx came in
bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected
uint8_t EMSbus::ems_mask_ = EMS_MASK_UNSET; // unset so its triggered when booting, the its 0x00=buderus, 0x80=junker/ht3
uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_BUS_ID;
uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID;
uint8_t EMSbus::tx_waiting_ = Telegram::Operation::NONE;
bool EMSbus::tx_active_ = false;
@@ -87,7 +87,7 @@ std::string Telegram::to_string() const {
}
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
uint8_t length = 0;
data[0] = this->src ^ RxService::ems_mask();
data[0] = this->src ^ RxService::ems_mask();
if (this->operation == Telegram::Operation::TX_READ) {
data[1] = this->dest | 0x80;
data[4] = this->message_data[0];
@@ -98,7 +98,7 @@ std::string Telegram::to_string() const {
length = 7;
} else {
data[2] = this->type_id;
length = 5;
length = 5;
}
}
if (this->operation == Telegram::Operation::TX_WRITE) {
@@ -205,11 +205,6 @@ void RxService::add(uint8_t * data, uint8_t length) {
message_length = length - 6 - shift;
}
// if we don't have a type_id or empty data block, exit
if ((type_id == 0) || (message_length == 0)) {
return;
}
// if we're watching and "raw" print out actual telegram as bytes to the console
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
uint16_t trace_watch_id = EMSESP::watch_id();
@@ -222,6 +217,11 @@ void RxService::add(uint8_t * data, uint8_t length) {
LOG_DEBUG(F("[DEBUG] New Rx [#%d] telegram, message length %d"), rx_telegram_id_, message_length);
#endif
// if we don't have a type_id or empty data block, exit
if ((type_id == 0) || (message_length == 0)) {
return;
}
// create the telegram
auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length);
@@ -233,7 +233,6 @@ void RxService::add(uint8_t * data, uint8_t length) {
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue
}
//
// Tx CODE starts here...
//
@@ -253,8 +252,7 @@ void TxService::flush_tx_queue() {
// start and initialize Tx
void TxService::start() {
// grab the bus ID
Settings settings;
ems_bus_id(settings.ems_bus_id());
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { ems_bus_id(settings.ems_bus_id); });
// send first Tx request to bus master (boiler) for its registered devices
// this will be added to the queue and sent during the first tx loop()
@@ -285,7 +283,7 @@ void TxService::send_poll() {
void TxService::send() {
// don't process if we don't have a connection to the EMS bus
// or we're in read-only mode
if (!bus_connected() || EMSESP::ems_read_only()) {
if (!bus_connected()) {
return;
}
@@ -421,7 +419,13 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
// builds a Tx telegram and adds to queue
// given some details like the destination, type, offset and message block
void TxService::add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const bool front) {
void TxService::add(const uint8_t operation,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const bool front) {
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
#ifdef EMSESP_DEBUG
LOG_DEBUG(F("[DEBUG] New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);

View File

@@ -129,6 +129,8 @@ class EMSbus {
static constexpr uint8_t EMS_MASK_HT3 = 0x80; // EMS bus type Junkers/HT3
static constexpr uint8_t EMS_MASK_BUDERUS = 0xFF; // EMS bus type Buderus
static constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
static bool bus_connected() {
#ifndef EMSESP_STANDALONE
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) {
@@ -270,7 +272,13 @@ class TxService : public EMSbus {
void loop();
void send();
void add(const uint8_t operation, const uint8_t dest, const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const bool front = false);
void add(const uint8_t operation,
const uint8_t dest,
const uint16_t type_id,
const uint8_t offset,
uint8_t * message_data,
const uint8_t message_length,
const bool front = false);
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const bool front = false);
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0);

View File

@@ -152,7 +152,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x09, 0x0a});
shell.loop_all();
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
}
if (command == "unknown2") {
@@ -206,14 +206,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
@@ -221,12 +221,12 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
EMSESP::rxservice_.loop();
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
EMSESP::rxservice_.loop();
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
}
if (command == "km") {
@@ -293,9 +293,9 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
uart_telegram_withCRC("90 0B FF 00 01 A5 80 00 01 26 15 00 26 2A 05 A0 03 03 03 05 A0 05 A0 00 00 11 01 03 FF FF 00 FE");
uart_telegram_withCRC("90 00 FF 19 01 A5 01 04 00 00 00 00 FF 64 2A 00 3C 01 FF 92");
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
EMSESP::rxservice_.loop();
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
}
if (command == "cr100") {
@@ -321,7 +321,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
EMSESP::rxservice_.loop();
EMSESP::txservice_.flush_tx_queue();
shell.loop_all();
EMSESP::show_values(shell);
EMSESP::show_device_values(shell);
shell.invoke_command("thermostat");
shell.loop_all();
@@ -330,7 +330,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
shell.invoke_command("set mode auto");
shell.loop_all();
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
shell.loop_all();
EMSESP::txservice_.send(); // send it to UART
@@ -395,7 +395,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "send") {
shell.printfln(F("Sending to Tx..."));
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
EMSESP::txservice_.send(); // send it to UART
}
@@ -430,7 +430,7 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// EMS+ Junkers read request
EMSESP::send_read_request(0x16F, 0x10);
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
// process whole Tx queue
for (uint8_t i = 0; i < 10; i++) {
@@ -464,10 +464,10 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
// Simulate adding a Poll - should send retry
EMSESP::incoming_telegram(poll, 1);
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
uint8_t t2[] = {0x21, 0x22};
EMSESP::send_write_request(0x91, 0x17, 0x00, t2, sizeof(t2), 0);
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
EMSESP::txservice_.flush_tx_queue();
}
@@ -520,14 +520,14 @@ void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
if (command == "poll2") {
shell.printfln(F("Testing Tx Sending last message on queue..."));
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
uint8_t poll[1] = {0x8B};
EMSESP::incoming_telegram(poll, 1);
EMSESP::rxservice_.loop();
EMSESP::show_emsbus(shell);
EMSESP::show_ems(shell);
EMSESP::txservice_.flush_tx_queue();
}

View File

@@ -63,7 +63,7 @@ void IRAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
if (EMS_UART.int_st.brk_det) {
EMS_UART.int_clr.brk_det = 1; // clear flag
EMS_UART.conf0.txd_brk = 0; // disable <brk>
EMS_UART.conf0.txd_brk = 0; // disable <brk>
if (emsTxBufIdx < emsTxBufLen) { // timer tx_mode is interrupted by <brk>
emsTxBufIdx = emsTxBufLen; // stop timer mode
drop_next_rx = true; // we have trash in buffer
@@ -87,10 +87,10 @@ void IRAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
void IRAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
if(emsTxBufLen == 0) {
if (emsTxBufLen == 0) {
return;
}
if(tx_mode_ > 50) {
if (tx_mode_ > 50) {
for (uint8_t i = 0; i < emsTxBufLen; i++) {
EMS_UART.fifo.rw_byte = emsTxBuf[i];
}
@@ -101,7 +101,7 @@ void IRAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
timerAlarmWrite(timer, emsTxWait, false);
timerAlarmEnable(timer);
} else if (emsTxBufIdx + 1 == emsTxBufLen) {
EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx];
EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx];
EMS_UART.conf0.txd_brk = 1; // <brk> after send
}
emsTxBufIdx++;
@@ -137,7 +137,7 @@ void EMSuart::start(const uint8_t tx_mode) {
uart_isr_register(EMSUART_UART, emsuart_rx_intr_handler, NULL, ESP_INTR_FLAG_IRAM, &uart_handle);
xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES - 1, NULL);
timer = timerBegin(1, 80, true); // timer prescale to 1 µs, countup
timer = timerBegin(1, 80, true); // timer prescale to 1 µs, countup
timerAttachInterrupt(timer, &emsuart_tx_timer_intr_handler, true); // Timer with edge interrupt
restart();
}
@@ -173,10 +173,10 @@ void EMSuart::restart() {
EMS_UART.idle_conf.tx_idle_num = 2;
} else if (tx_mode_ <= 50) {
EMS_UART.idle_conf.tx_idle_num = tx_mode_;
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
} else {
EMS_UART.idle_conf.tx_idle_num = 2;
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ - 50);
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ - 50);
}
}
@@ -213,7 +213,8 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
}
if (tx_mode_ == 5) { // wait before sending
vTaskDelay(4 / portTICK_PERIOD_MS);
//vTaskDelay(4 / portTICK_PERIOD_MS);
delayMicroseconds(4000);
for (uint8_t i = 0; i < len; i++) {
EMS_UART.fifo.rw_byte = buf[i];
}
@@ -233,7 +234,7 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
EMS_UART.fifo.rw_byte = buf[i];
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
}
EMS_UART.fifo.rw_byte = buf[len - 1];
EMS_UART.fifo.rw_byte = buf[len - 1];
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
return EMS_TX_STATUS_OK;
}
@@ -243,7 +244,7 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
EMS_UART.fifo.rw_byte = buf[i];
delayMicroseconds(EMSUART_TX_WAIT_HT3);
}
EMS_UART.fifo.rw_byte = buf[len - 1];
EMS_UART.fifo.rw_byte = buf[len - 1];
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
return EMS_TX_STATUS_OK;
}
@@ -257,11 +258,11 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
EMS_UART.fifo.rw_byte = buf[i]; // send each Tx byte
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
while ((EMS_UART.status.rxfifo_cnt == _usrxc) && (--timeoutcnt > 0)) {
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
}
}
EMS_UART.fifo.rw_byte = buf[len - 1]; // send each Tx byte
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
EMS_UART.fifo.rw_byte = buf[len - 1]; // send each Tx byte
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
return EMS_TX_STATUS_OK;
}

View File

@@ -46,11 +46,11 @@
#define EMS_TXMODE_NEW 4 // for michael's testing
// LEGACY
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
// EMS 1.0
#define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13
#define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13
#define EMSUART_TX_TIMEOUT (32 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) // 256
// HT3/Junkers - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) plus 7 bit delay. The -8 is for lag compensation.

View File

@@ -34,7 +34,7 @@ uint8_t emsTxBuf[EMS_MAXBUFFERSIZE];
uint8_t emsTxBufIdx;
uint8_t emsTxBufLen;
uint32_t emsTxWait;
bool EMSuart::sending_ = false;
bool EMSuart::sending_ = false;
//
// Main interrupt handler
@@ -52,7 +52,7 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
}
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
USIE(EMSUART_UART) &= ~(1 << UIFF); // disable fifo-full irq
length = 0;
length = 0;
while ((USS(EMSUART_UART) >> USRXC) & 0x0FF) { // read fifo into buffer
uint8_t rx = USF(EMSUART_UART);
if (length < EMS_MAXBUFFERSIZE) {
@@ -64,7 +64,7 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
if (!drop_next_rx) {
pEMSRxBuf->length = length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
}
drop_next_rx = false;
sending_ = false;
@@ -98,9 +98,9 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() {
// ISR to Fire when Timer is triggered
void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
if ( tx_mode_ > 50) {
if (tx_mode_ > 50) {
for (uint8_t i = 0; i < emsTxBufLen; i++) {
USF(EMSUART_UART) = emsTxBuf[i];
USF(EMSUART_UART) = emsTxBuf[i];
}
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
} else {
@@ -150,7 +150,7 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD);
// set 9600, 8 bits, no parity check, 1 stop bit
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
if (tx_mode_ == 5) {
USC0(EMSUART_UART) = 0x2C; // 8N1.5
} else {
@@ -249,7 +249,7 @@ void ICACHE_FLASH_ATTR EMSuart::tx_brk() {
void EMSuart::send_poll(uint8_t data) {
// reset tx-brk, just in case it is accidentally set
USC0(EMSUART_UART) &= ~(1 << UCBRK);
sending_ = true;
sending_ = true;
if (tx_mode_ > 50) { // timer controlled modes
emsTxBuf[0] = data;
@@ -260,7 +260,7 @@ void EMSuart::send_poll(uint8_t data) {
emsTxBufIdx = 0;
emsTxBufLen = 1;
timer1_write(emsTxWait);
}else if (tx_mode_ == 5) {
} else if (tx_mode_ == 5) {
delayMicroseconds(3000);
USF(EMSUART_UART) = data;
USC0(EMSUART_UART) |= (1 << UCBRK);
@@ -275,7 +275,7 @@ void EMSuart::send_poll(uint8_t data) {
USF(EMSUART_UART) = data;
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
tx_brk(); // send <BRK>
} else {
} else {
// tx_mode 1
// EMS1.0, same logic as in transmit
ETS_UART_INTR_DISABLE();
@@ -306,9 +306,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
}
// reset tx-brk, just in case it is accidentally set
USC0(EMSUART_UART) &= ~(1 << UCBRK);
sending_ = true;
sending_ = true;
// all at once after a inititial timer delay
// all at once after a initial timer delay
if (tx_mode_ > 50) {
for (uint8_t i = 0; i < len; i++) {
emsTxBuf[i] = buf[i];
@@ -327,7 +327,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
timer1_write(emsTxWait);
return EMS_TX_STATUS_OK;
}
// fixed dealy before sending
// fixed delay before sending
if (tx_mode_ == 5) {
delayMicroseconds(3000);
for (uint8_t i = 0; i < len; i++) {
@@ -428,7 +428,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK>
}
ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
return EMS_TX_STATUS_OK; // send the Tx ok status back
}

View File

@@ -71,7 +71,7 @@ class EMSuart {
static void ICACHE_FLASH_ATTR send_poll(uint8_t data);
static uint16_t ICACHE_FLASH_ATTR transmit(uint8_t * buf, uint8_t len);
static bool sending() {
return sending_;
return sending_;
}
typedef struct {

View File

@@ -1 +1 @@
#define EMSESP_APP_VERSION "2.0.0a32"
#define EMSESP_APP_VERSION "2.0.0b5"