mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-08 16:59:50 +03:00
Merge branch 'v2' of https://github.com/proddy/EMS-ESP into v2
This commit is contained in:
@@ -1,35 +1,93 @@
|
||||
/*
|
||||
* 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 "EMSESPDevicesService.h"
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager) {
|
||||
EMSESPDevicesService::EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager)
|
||||
: _device_dataHandler(DEVICE_DATA_SERVICE_PATH,
|
||||
securityManager->wrapCallback(std::bind(&EMSESPDevicesService::device_data, this, _1, _2), AuthenticationPredicates::IS_AUTHENTICATED)) {
|
||||
server->on(EMSESP_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::emsespDevicesService, this, std::placeholders::_1),
|
||||
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::all_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
server->on(SCAN_DEVICES_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&EMSESPDevicesService::scan_devices, this, _1), AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
_device_dataHandler.setMethod(HTTP_POST);
|
||||
_device_dataHandler.setMaxContentLength(256);
|
||||
server->addHandler(&_device_dataHandler);
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::emsespDevicesService(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE);
|
||||
void EMSESPDevicesService::scan_devices(AsyncWebServerRequest * request) {
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
request->send(200);
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::all_devices(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_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();
|
||||
JsonObject obj = devices.createNestedObject();
|
||||
obj["id"] = emsdevice->unique_id();
|
||||
obj["type"] = emsdevice->device_type_name();
|
||||
obj["brand"] = emsdevice->brand_to_string();
|
||||
obj["name"] = emsdevice->name();
|
||||
obj["deviceid"] = emsdevice->device_id();
|
||||
obj["productid"] = emsdevice->product_id();
|
||||
obj["version"] = emsdevice->version();
|
||||
}
|
||||
}
|
||||
|
||||
JsonArray sensors = root.createNestedArray("sensors");
|
||||
if (!EMSESP::sensor_devices().empty()) {
|
||||
for (const auto & sensor : EMSESP::sensor_devices()) {
|
||||
JsonObject obj = sensors.createNestedObject();
|
||||
obj["id"] = sensor.to_string();
|
||||
obj["temp"] = sensor.temperature_c;
|
||||
}
|
||||
}
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void EMSESPDevicesService::device_data(AsyncWebServerRequest * request, JsonVariant & json) {
|
||||
if (json.is<JsonObject>()) {
|
||||
uint8_t id = json["id"]; // get id from selected table row
|
||||
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_DEVICE_SIZE);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::device_info(id, (JsonObject &)response->getRoot());
|
||||
#endif
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
} else {
|
||||
AsyncWebServerResponse * response = request->beginResponse(200);
|
||||
request->send(response);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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 EMSESPDevicesService_h
|
||||
#define EMSESPDevicesService_h
|
||||
|
||||
@@ -6,23 +24,27 @@
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
// #include <HttpEndpoint.h>
|
||||
// #include <MqttPubSub.h>
|
||||
// #include <WebSocketTxRx.h>
|
||||
// #define MAX_EMSESP_STATUS_SIZE 1024
|
||||
#define MAX_EMSESP_DEVICE_SIZE 1280
|
||||
|
||||
#include "version.h"
|
||||
|
||||
#define MAX_EMSESP_STATUS_SIZE 1024
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/emsespDevices"
|
||||
#define EMSESP_DEVICES_SERVICE_PATH "/rest/allDevices"
|
||||
#define SCAN_DEVICES_SERVICE_PATH "/rest/scanDevices"
|
||||
#define DEVICE_DATA_SERVICE_PATH "/rest/deviceData"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
class EMSESPDevicesService {
|
||||
public:
|
||||
EMSESPDevicesService(AsyncWebServer * server, SecurityManager * securityManager);
|
||||
|
||||
private:
|
||||
void emsespDevicesService(AsyncWebServerRequest * request);
|
||||
void all_devices(AsyncWebServerRequest * request);
|
||||
void scan_devices(AsyncWebServerRequest * request);
|
||||
void device_data(AsyncWebServerRequest * request, JsonVariant & json);
|
||||
|
||||
AsyncCallbackJsonWebHandler _device_dataHandler;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#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
|
||||
@@ -1,21 +0,0 @@
|
||||
#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
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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 "EMSESPSettingsService.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
@@ -6,62 +24,46 @@ 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) {
|
||||
addUpdateHandler([&](const String & originId) { onUpdate(); }, false);
|
||||
}
|
||||
|
||||
void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
|
||||
root["tx_mode"] = settings.tx_mode;
|
||||
root["ems_bus_id"] = settings.ems_bus_id;
|
||||
|
||||
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;
|
||||
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;
|
||||
settings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
settings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
settings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
settings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
settings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
settings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
settings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
settings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
|
||||
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;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
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;
|
||||
// this is called after the settings have been persisted to the filesystem
|
||||
void EMSESPSettingsService::onUpdate() {
|
||||
EMSESP::shower_.start();
|
||||
EMSESP::system_.syslog_init();
|
||||
EMSESP::reset_tx();
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::save() {
|
||||
_fsPersistence.writeToFS();
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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 EMSESPSettingsConfig_h
|
||||
#define EMSESPSettingsConfig_h
|
||||
|
||||
@@ -7,7 +25,7 @@
|
||||
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
|
||||
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
|
||||
|
||||
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
|
||||
#define EMSESP_DEFAULT_TX_MODE 1 // EMS1.0
|
||||
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
|
||||
|
||||
#define EMSESP_DEFAULT_SYSLOG_LEVEL -1 // OFF
|
||||
@@ -44,10 +62,13 @@ class EMSESPSettingsService : public StatefulService<EMSESPSettings> {
|
||||
EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
void save();
|
||||
|
||||
private:
|
||||
HttpEndpoint<EMSESPSettings> _httpEndpoint;
|
||||
FSPersistence<EMSESPSettings> _fsPersistence;
|
||||
|
||||
void onUpdate();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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 "EMSESPStatusService.h"
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h"
|
||||
@@ -12,38 +30,30 @@ EMSESPStatusService::EMSESPStatusService(AsyncWebServer * server, SecurityManage
|
||||
securityManager->wrapRequest(std::bind(&EMSESPStatusService::emsespStatusService, this, std::placeholders::_1),
|
||||
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
|
||||
// trigger on wifi connects
|
||||
// trigger on wifi connects/disconnects
|
||||
#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());
|
||||
EMSESP::logger().debug(F("WiFi Connected with IP=%s, hostname=%s"), WiFi.localIP().toString().c_str(), WiFi.getHostname());
|
||||
EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection
|
||||
}
|
||||
#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());
|
||||
EMSESP::logger().debug(F("WiFi Connected with IP=%s, hostname=%s"), event.ip.toString().c_str(), WiFi.hostname().c_str());
|
||||
EMSESP::system_.send_heartbeat(); // send out heartbeat MQTT as soon as we have a connection
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,3 +1,21 @@
|
||||
/*
|
||||
* 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 EMSESPStatusService_h
|
||||
#define EMSESPStatusService_h
|
||||
|
||||
@@ -20,14 +38,11 @@ class EMSESPStatusService {
|
||||
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
|
||||
|
||||
@@ -90,6 +90,7 @@ void EMSESPShell::display_banner() {
|
||||
|
||||
// turn off watch
|
||||
emsesp::EMSESP::watch_id(WATCH_ID_NONE);
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
|
||||
}
|
||||
|
||||
// pre-loads all the console commands into the MAIN context
|
||||
@@ -107,11 +108,9 @@ void EMSESPShell::add_console_commands() {
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(refresh)},
|
||||
flash_string_vector{F_(fetch)},
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.printfln(F("Requesting data from EMS devices"));
|
||||
console_commands_loaded_ = false;
|
||||
add_console_commands();
|
||||
EMSESP::fetch_device_values();
|
||||
});
|
||||
|
||||
@@ -177,15 +176,14 @@ void EMSESPShell::add_console_commands() {
|
||||
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);
|
||||
|
||||
// save the tx_mode
|
||||
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
|
||||
EMSESP::reset_tx(); // reset counters and set tx_mode
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
@@ -516,6 +514,9 @@ void Console::start() {
|
||||
shell = std::make_shared<EMSESPStreamConsole>(serial_console_, true);
|
||||
shell->maximum_log_messages(100); // default is 50
|
||||
shell->start();
|
||||
#endif
|
||||
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
|
||||
#endif
|
||||
|
||||
@@ -524,8 +525,6 @@ void Console::start() {
|
||||
shell->add_flags(CommandFlags::ADMIN);
|
||||
#endif
|
||||
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // turn watch off in case it was still set in the last session
|
||||
|
||||
// start the telnet service
|
||||
// default idle is 10 minutes, default write timeout is 0 (automatic)
|
||||
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash
|
||||
@@ -533,6 +532,9 @@ void Console::start() {
|
||||
telnet_.start();
|
||||
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
|
||||
#endif
|
||||
|
||||
// turn watch off in case it was still set in the last session
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF);
|
||||
}
|
||||
|
||||
// handles telnet sync and logging to console
|
||||
|
||||
@@ -73,7 +73,7 @@ MAKE_PSTR_WORD(read)
|
||||
MAKE_PSTR_WORD(version)
|
||||
MAKE_PSTR_WORD(values)
|
||||
MAKE_PSTR_WORD(system)
|
||||
MAKE_PSTR_WORD(refresh)
|
||||
MAKE_PSTR_WORD(fetch)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(format)
|
||||
MAKE_PSTR_WORD(raw)
|
||||
@@ -191,8 +191,8 @@ class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPSh
|
||||
|
||||
class Console {
|
||||
public:
|
||||
static void loop();
|
||||
void start();
|
||||
void loop();
|
||||
void start();
|
||||
|
||||
uuid::log::Level log_level();
|
||||
|
||||
|
||||
@@ -28,6 +28,8 @@ MAKE_PSTR_WORD(comfort)
|
||||
MAKE_PSTR_WORD(eco)
|
||||
MAKE_PSTR_WORD(intelligent)
|
||||
MAKE_PSTR_WORD(hot)
|
||||
MAKE_PSTR_WORD(maxpower)
|
||||
MAKE_PSTR_WORD(minpower)
|
||||
|
||||
MAKE_PSTR(comfort_mandatory, "<hot | eco | intelligent>")
|
||||
|
||||
@@ -59,7 +61,7 @@ Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const
|
||||
register_telegram_type(0x33, F("UBAParameterWW"), true, std::bind(&Boiler::process_UBAParameterWW, this, _1));
|
||||
register_telegram_type(0x14, F("UBATotalUptime"), false, std::bind(&Boiler::process_UBATotalUptime, this, _1));
|
||||
register_telegram_type(0x35, F("UBAFlags"), false, std::bind(&Boiler::process_UBAFlags, this, _1));
|
||||
register_telegram_type(0x15, F("UBAMaintenanceSettings"), false, std::bind(&Boiler::process_UBAMaintenanceSettings, this, _1));
|
||||
register_telegram_type(0x15, F("UBAMaintenanceData"), false, std::bind(&Boiler::process_UBAMaintenanceData, this, _1));
|
||||
register_telegram_type(0x16, F("UBAParameters"), true, std::bind(&Boiler::process_UBAParameters, this, _1));
|
||||
register_telegram_type(0x1A, F("UBASetPoints"), false, std::bind(&Boiler::process_UBASetPoints, this, _1));
|
||||
register_telegram_type(0xD1, F("UBAOutdoorTemp"), false, std::bind(&Boiler::process_UBAOutdoorTemp, this, _1));
|
||||
@@ -103,18 +105,50 @@ void Boiler::boiler_cmd(const char * message) {
|
||||
uint8_t t = doc["wwtemp"];
|
||||
set_warmwater_temp(t);
|
||||
}
|
||||
if (nullptr != doc["boilhyston"]) {
|
||||
int8_t t = doc["boilhyston"];
|
||||
set_hyst_on(t);
|
||||
}
|
||||
if (nullptr != doc["boilhystoff"]) {
|
||||
uint8_t t = doc["boilhystoff"];
|
||||
set_hyst_off(t);
|
||||
}
|
||||
if (nullptr != doc["burnperiod"]) {
|
||||
uint8_t t = doc["burnperiod"];
|
||||
set_burn_period(t);
|
||||
}
|
||||
if (nullptr != doc["burnminpower"]) {
|
||||
uint8_t p = doc["burnminpower"];
|
||||
set_min_power(p);
|
||||
}
|
||||
if (nullptr != doc["burnmaxpower"]) {
|
||||
uint8_t p = doc["burnmaxpower"];
|
||||
set_max_power(p);
|
||||
}
|
||||
if (nullptr != doc["pumpdelay"]) {
|
||||
uint8_t t = doc["pumpdelay"];
|
||||
set_pump_delay(t);
|
||||
}
|
||||
|
||||
if (nullptr != doc["comfort"]) {
|
||||
const char * data = doc["comfort"];
|
||||
if (strcmp((char *)data, "hot") == 0) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (strcmp((char *)data, "eco") == 0) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (strcmp((char *)data, "intelligent") == 0) {
|
||||
set_warmwater_mode(3);
|
||||
}
|
||||
}
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
if (command == nullptr || doc["data"] == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler ww comfort setting
|
||||
if (strcmp(command, "comfort") == 0) {
|
||||
const char * data = doc["data"];
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
if (strcmp((char *)data, "hot") == 0) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (strcmp((char *)data, "eco") == 0) {
|
||||
@@ -128,9 +162,45 @@ void Boiler::boiler_cmd(const char * message) {
|
||||
// boiler flowtemp setting
|
||||
if (strcmp(command, "flowtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
if (t) {
|
||||
set_flow_temp(t);
|
||||
}
|
||||
set_flow_temp(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "wwtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_warmwater_temp(t);
|
||||
return;
|
||||
}
|
||||
// boiler max power setting
|
||||
if (strcmp(command, "burnmaxpower") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_max_power(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler min power setting
|
||||
if (strcmp(command, "burnminpower") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_min_power(p);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "boilhyston") == 0) {
|
||||
int8_t t = doc["data"];
|
||||
set_hyst_on(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "boilhystoff") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_hyst_off(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "burnperiod") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_burn_period(t);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "pumpdelay") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
set_pump_delay(t);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -166,9 +236,27 @@ void Boiler::boiler_cmd_wwtemp(const char * message) {
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::device_info(JsonArray & root) {
|
||||
JsonObject dataElement;
|
||||
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Hot tap water");
|
||||
dataElement["value"] = tap_water_active_ ? F("running") : F("off");
|
||||
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Central heating");
|
||||
dataElement["value"] = heating_active_ ? F("active") : F("off");
|
||||
|
||||
render_value_json(root, "", F("Selected flow temperature"), selFlowTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Current flow temperature"), curFlowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Warm Water selected temperature"), wWSelTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Warm Water set temperature"), wWSetTmp_, F_(degrees));
|
||||
render_value_json(root, "", F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10);
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Boiler::publish_values() {
|
||||
const size_t capacity = JSON_OBJECT_SIZE(47); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
const size_t capacity = JSON_OBJECT_SIZE(56); // must recalculate if more objects addded https://arduinojson.org/v6/assistant/
|
||||
DynamicJsonDocument doc(capacity);
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
@@ -280,7 +368,7 @@ void Boiler::publish_values() {
|
||||
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(heatPmp_, VALUE_BOOL)) {
|
||||
doc["heatPmp"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
|
||||
doc["heatPump"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(fanWork_, VALUE_BOOL)) {
|
||||
doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
|
||||
@@ -292,13 +380,37 @@ void Boiler::publish_values() {
|
||||
doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(heating_temp_)) {
|
||||
doc["heating_temp"] = heating_temp_;
|
||||
doc["heatingTemp"] = heating_temp_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_mod_max_)) {
|
||||
doc["pump_mod_max"] = pump_mod_max_;
|
||||
doc["pumpModMax"] = pump_mod_max_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_mod_min_)) {
|
||||
doc["pump_mod_min"] = pump_mod_min_;
|
||||
doc["pumpModMin"] = pump_mod_min_;
|
||||
}
|
||||
if (Helpers::hasValue(pumpDelay_)) {
|
||||
doc["pumpDelay"] = pumpDelay_;
|
||||
}
|
||||
if (Helpers::hasValue(burnPeriod_)) {
|
||||
doc["burnMinPeriod"] = burnPeriod_;
|
||||
}
|
||||
if (Helpers::hasValue(burnPowermin_)) {
|
||||
doc["burnMinPower"] = burnPowermin_;
|
||||
}
|
||||
if (Helpers::hasValue(burnPowermax_)) {
|
||||
doc["burnMaxPower"] = burnPowermax_;
|
||||
}
|
||||
if (Helpers::hasValue(boilTemp_on_)) {
|
||||
doc["boilHystOn"] = boilTemp_on_;
|
||||
}
|
||||
if (Helpers::hasValue(boilTemp_off_)) {
|
||||
doc["boilHystOff"] = boilTemp_off_;
|
||||
}
|
||||
if (Helpers::hasValue(setFlowTemp_)) {
|
||||
doc["setFlowTemp"] = setFlowTemp_;
|
||||
}
|
||||
if (Helpers::hasValue(setWWPumpPow_)) {
|
||||
doc["wWSetPumpPower"] = setWWPumpPow_;
|
||||
}
|
||||
if (Helpers::hasValue(wWStarts_)) {
|
||||
doc["wWStarts"] = wWStarts_;
|
||||
@@ -318,7 +430,6 @@ void Boiler::publish_values() {
|
||||
if (Helpers::hasValue(heatWorkMin_)) {
|
||||
doc["heatWorkMin"] = heatWorkMin_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(serviceCode_)) {
|
||||
doc["serviceCode"] = serviceCodeChar_;
|
||||
doc["serviceCodeNumber"] = serviceCode_;
|
||||
@@ -416,6 +527,17 @@ void Boiler::show_values(uuid::console::Shell & shell) {
|
||||
print_value(shell, 2, F("Heating temperature setting on the boiler"), heating_temp_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler circuit pump modulation max power"), pump_mod_max_, F_(percent));
|
||||
print_value(shell, 2, F("Boiler circuit pump modulation min power"), pump_mod_min_, F_(percent));
|
||||
print_value(shell, 2, F("Boiler circuit pump delay time"), pumpDelay_, F("min"));
|
||||
print_value(shell, 2, F("Boiler temp hysteresis on"), boilTemp_on_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler temp hysteresis off"), boilTemp_off_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler burner min period"), burnPeriod_, F("min"));
|
||||
print_value(shell, 2, F("Boiler burner min power"), burnPowermin_, F_(percent));
|
||||
print_value(shell, 2, F("Boiler burner max power"), burnPowermax_, F_(percent));
|
||||
|
||||
// UBASetPoint - these may differ from the above
|
||||
print_value(shell, 2, F("Set Flow temperature"), setFlowTemp_, F_(degrees));
|
||||
print_value(shell, 2, F("Boiler burner set power"), setBurnPow_, F_(percent));
|
||||
print_value(shell, 2, F("Warm Water pump set power"), setWWPumpPow_, F_(percent));
|
||||
|
||||
// UBAMonitorSlow
|
||||
if (Helpers::hasValue(extTemp_)) {
|
||||
@@ -528,6 +650,12 @@ void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
|
||||
*/
|
||||
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(heating_temp_, 1);
|
||||
telegram->read_value(burnPowermax_,2);
|
||||
telegram->read_value(burnPowermin_,3);
|
||||
telegram->read_value(boilTemp_off_,4);
|
||||
telegram->read_value(boilTemp_on_,5);
|
||||
telegram->read_value(burnPeriod_,6);
|
||||
telegram->read_value(pumpDelay_,8);
|
||||
telegram->read_value(pump_mod_max_, 9);
|
||||
telegram->read_value(pump_mod_min_, 10);
|
||||
}
|
||||
@@ -652,16 +780,16 @@ void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(extTemp_, 0);
|
||||
}
|
||||
|
||||
// UBASetPoint 0x1A
|
||||
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(setFlowTemp_, 0); // boiler set temp from thermostat
|
||||
telegram->read_value(setBurnPow_, 1); // max output power in %
|
||||
telegram->read_value(setWWPumpPow_, 2); // ww pump speed/power?
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
// UBASetPoint 0x1A
|
||||
// not yet implemented
|
||||
void Boiler::process_UBASetPoints(std::shared_ptr<const Telegram> telegram) {
|
||||
// uint8_t setpoint = telegram->message_data[0]; // boiler flow temp
|
||||
// uint8_t ww_power = telegram->message_data[2]; // power in %
|
||||
}
|
||||
|
||||
// 0x35
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) {
|
||||
@@ -670,11 +798,7 @@ void Boiler::process_UBAFlags(std::shared_ptr<const Telegram> telegram) {
|
||||
// 0x1C
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram) {
|
||||
}
|
||||
|
||||
// 0x15
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram) {
|
||||
// first byte: Maintenance due (0 = no, 3 = yes, due to operating hours, 8 = yes, due to date)
|
||||
}
|
||||
|
||||
// 0x10, 0x11, 0x12
|
||||
@@ -685,12 +809,20 @@ void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// 0x15
|
||||
void Boiler::process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram) {
|
||||
// first byte: Maintenance messages (0 = none, 1 = by operating hours, 2 = by date)
|
||||
// I see a value of 3 in the 1st byte when the boiler is booted, so probably a flag
|
||||
if (telegram->message_data[0] == 3) {
|
||||
LOG_WARNING(F("Boiler has booted."));
|
||||
}
|
||||
}
|
||||
|
||||
// Set the warm water temperature 0x33
|
||||
void Boiler::set_warmwater_temp(const uint8_t temperature) {
|
||||
LOG_INFO(F("Setting boiler warm water temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBAParameterWW, 2, temperature);
|
||||
// for i9000, see #397
|
||||
write_command(EMS_TYPE_UBAFlags, 3, temperature);
|
||||
write_command(EMS_TYPE_UBAFlags, 3, temperature); // for i9000, see #397
|
||||
}
|
||||
|
||||
// flow temp
|
||||
@@ -699,6 +831,42 @@ void Boiler::set_flow_temp(const uint8_t temperature) {
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, temperature);
|
||||
}
|
||||
|
||||
// set min boiler output
|
||||
void Boiler::set_min_power(const uint8_t power) {
|
||||
LOG_INFO(F("Setting boiler min power to "), power);
|
||||
write_command(EMS_TYPE_UBAParameters, 3, power);
|
||||
}
|
||||
|
||||
// set max temp
|
||||
void Boiler::set_max_power(const uint8_t power) {
|
||||
LOG_INFO(F("Setting boiler max power to %d C"), power);
|
||||
write_command(EMS_TYPE_UBAParameters, 2, power);
|
||||
}
|
||||
|
||||
// set oiler on hysteresis
|
||||
void Boiler::set_hyst_on(const uint8_t temp) {
|
||||
LOG_INFO(F("Setting boiler hysteresis on to %d C"), temp);
|
||||
write_command(EMS_TYPE_UBAParameters, 5, temp);
|
||||
}
|
||||
|
||||
// set boiler off hysteresis
|
||||
void Boiler::set_hyst_off(const uint8_t temp) {
|
||||
LOG_INFO(F("Setting boiler hysteresis off to %d C"), temp);
|
||||
write_command(EMS_TYPE_UBAParameters, 4, temp);
|
||||
}
|
||||
|
||||
// set min burner period
|
||||
void Boiler::set_burn_period(const uint8_t t) {
|
||||
LOG_INFO(F("Setting burner min. period to %d min"), t);
|
||||
write_command(EMS_TYPE_UBAParameters, 6, t);
|
||||
}
|
||||
|
||||
// set pump delay
|
||||
void Boiler::set_pump_delay(const uint8_t t) {
|
||||
LOG_INFO(F("Setting boiler pump delay to %d min"), t);
|
||||
write_command(EMS_TYPE_UBAParameters, 8, t);
|
||||
}
|
||||
|
||||
// 1=hot, 2=eco, 3=intelligent
|
||||
// note some boilers do not have this setting, than it's done by thermostat
|
||||
// on a RC35 it's by EMSESP::send_write_request(0x37, 0x10, 2, &set, 1, 0); (set is 1,2,3)
|
||||
@@ -803,6 +971,22 @@ void Boiler::console_commands(Shell & shell, unsigned int context) {
|
||||
set_flow_temp(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(maxpower)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_max_power(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(minpower)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_min_power(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
|
||||
@@ -40,6 +40,7 @@ class Boiler : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -54,6 +55,7 @@ class Boiler : public EMSdevice {
|
||||
static constexpr uint8_t EMS_TYPE_UBAFunctionTest = 0x1D;
|
||||
static constexpr uint8_t EMS_TYPE_UBAFlags = 0x35;
|
||||
static constexpr uint8_t EMS_TYPE_UBASetPoints = 0x1A;
|
||||
static constexpr uint8_t EMS_TYPE_UBAParameters = 0x16;
|
||||
|
||||
static constexpr uint8_t EMS_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
|
||||
|
||||
@@ -119,11 +121,22 @@ class Boiler : public EMSdevice {
|
||||
uint8_t heating_temp_ = EMS_VALUE_UINT_NOTSET; // Heating temperature setting on the boiler
|
||||
uint8_t pump_mod_max_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation max. power %
|
||||
uint8_t pump_mod_min_ = EMS_VALUE_UINT_NOTSET; // Boiler circuit pump modulation min. power
|
||||
uint8_t burnPowermin_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t burnPowermax_ = EMS_VALUE_UINT_NOTSET;
|
||||
int8_t boilTemp_off_ = EMS_VALUE_INT_NOTSET;
|
||||
int8_t boilTemp_on_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t burnPeriod_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t pumpDelay_ = EMS_VALUE_UINT_NOTSET;
|
||||
|
||||
// UBASetPoint
|
||||
uint8_t setFlowTemp_ = EMS_VALUE_UINT_NOTSET; // boiler setpoint temp
|
||||
uint8_t setBurnPow_ = EMS_VALUE_UINT_NOTSET; // max output power in %
|
||||
uint8_t setWWPumpPow_ = EMS_VALUE_UINT_NOTSET; // ww pump speed/power?
|
||||
|
||||
// other internal calculated params
|
||||
uint8_t tap_water_active_ = EMS_VALUE_BOOL_NOTSET; // Hot tap water is on/off
|
||||
uint8_t heating_active_ = EMS_VALUE_BOOL_NOTSET; // Central heating is on/off
|
||||
|
||||
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
||||
uint8_t pumpMod2_ = EMS_VALUE_UINT_NOTSET; // heatpump modulation from 0xE3 (heatpumps)
|
||||
|
||||
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -140,7 +153,7 @@ class Boiler : public EMSdevice {
|
||||
void process_UBAFlags(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MC10Status(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceStatus(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceSettings(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMaintenanceData(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
|
||||
@@ -154,6 +167,13 @@ class Boiler : public EMSdevice {
|
||||
void set_tapwarmwater_activated(const bool activated);
|
||||
void set_warmwater_onetime(const bool activated);
|
||||
void set_warmwater_circulation(const bool activated);
|
||||
void set_min_power(const uint8_t power);
|
||||
void set_max_power(const uint8_t power);
|
||||
void set_hyst_on(const uint8_t temp);
|
||||
void set_hyst_off(const uint8_t temp);
|
||||
void set_burn_period(const uint8_t t);
|
||||
void set_pump_delay(const uint8_t t);
|
||||
|
||||
|
||||
// mqtt callbacks
|
||||
void boiler_cmd(const char * message);
|
||||
|
||||
@@ -34,6 +34,9 @@ Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
// register_mqtt_topic("topic", std::bind(&Connect::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Connect::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
void Connect::add_context_menu() {
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ class Connect : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
void Controller::add_context_menu() {
|
||||
}
|
||||
|
||||
void Controller::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Controller::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Controller : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -39,6 +39,9 @@ Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, con
|
||||
void Gateway::add_context_menu() {
|
||||
}
|
||||
|
||||
void Gateway::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Gateway::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Gateway : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -50,6 +50,9 @@ Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, c
|
||||
void Heatpump::add_context_menu() {
|
||||
}
|
||||
|
||||
void Heatpump::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Heatpump::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Heatpump : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -57,6 +57,24 @@ Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
||||
void Mixing::add_context_menu() {
|
||||
}
|
||||
|
||||
// output json to web UI
|
||||
void Mixing::device_info(JsonArray & root) {
|
||||
if (type_ == Type::NONE) {
|
||||
return; // don't have any values yet
|
||||
}
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
render_value_json(root, "", F("Warm Water Circuit"), hc_, nullptr);
|
||||
} else {
|
||||
render_value_json(root, "", F("Heating Circuit"), hc_, nullptr);
|
||||
}
|
||||
render_value_json(root, "", F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||
render_value_json(root, "", F("Current pump modulation"), pumpMod_, F_(percent));
|
||||
render_value_json(root, "", F("Current valve status"), status_, nullptr);
|
||||
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Mixing::updated_values() {
|
||||
return false;
|
||||
|
||||
@@ -37,6 +37,7 @@ class Mixing : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -58,6 +58,32 @@ Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const s
|
||||
void Solar::add_context_menu() {
|
||||
}
|
||||
|
||||
// print to web
|
||||
void Solar::device_info(JsonArray & root) {
|
||||
render_value_json(root, "", F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Bottom temperature (TS2)"), bottomTemp_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Bottom temperature (TS5)"), bottomTemp2_, F_(degrees), 10);
|
||||
render_value_json(root, "", F("Pump modulation"), pumpModulation_, F_(percent));
|
||||
render_value_json(root, "", F("Valve (VS2) status"), valveStatus_, nullptr, EMS_VALUE_BOOL);
|
||||
render_value_json(root, "", F("Pump (PS1) active"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
if (Helpers::hasValue(pumpWorkMin_)) {
|
||||
JsonObject dataElement;
|
||||
dataElement = root.createNestedObject();
|
||||
dataElement["name"] = F("Pump working time");
|
||||
std::string time_str(60, '\0');
|
||||
snprintf_P(&time_str[0], time_str.capacity() + 1, PSTR("%d days %d hours %d minutes"), pumpWorkMin_ / 1440, (pumpWorkMin_ % 1440) / 60, pumpWorkMin_ % 60);
|
||||
dataElement["value"] = time_str;
|
||||
}
|
||||
|
||||
render_value_json(root, "", F("Tank Heated"), tankHeated_, nullptr, EMS_VALUE_BOOL);
|
||||
render_value_json(root, "", F("Collector shutdown"), collectorOnOff_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
render_value_json(root, "", F("Energy last hour"), energyLastHour_, F_(wh), 10);
|
||||
render_value_json(root, "", F("Energy today"), energyToday_, F_(wh));
|
||||
render_value_json(root, "", F("Energy total"), energyTotal_, F_(kwh), 10);
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Solar::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
@@ -74,7 +100,7 @@ void Solar::show_values(uuid::console::Shell & shell) {
|
||||
}
|
||||
|
||||
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("Collector shutdown"), 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));
|
||||
@@ -203,8 +229,8 @@ void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
|
||||
pumpModulation_ = 15; // set to minimum
|
||||
}
|
||||
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(collectorOnOff_, 3, 0);
|
||||
telegram->read_bitvalue(tankHeated_, 3, 1); // issue #422
|
||||
telegram->read_bitvalue(collectorOnOff_, 3, 0); // collector shutdown
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -240,11 +266,14 @@ void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram)
|
||||
if (Wh != 0xFFFF) {
|
||||
energyLastHour_ = Wh * 10; // set to *10
|
||||
}
|
||||
telegram->read_bitvalue(collectorOnOff_, 9, 0); // collector on/off
|
||||
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 shutdown 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) {
|
||||
|
||||
@@ -37,6 +37,7 @@ class Solar : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
@@ -57,7 +58,7 @@ class Solar : public EMSdevice {
|
||||
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 collectorOnOff_ = EMS_VALUE_BOOL_NOTSET; // Collector shutdown on/off
|
||||
|
||||
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
|
||||
@@ -39,6 +39,9 @@ Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const
|
||||
void Switch::add_context_menu() {
|
||||
}
|
||||
|
||||
void Switch::device_info(JsonArray & root) {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Switch::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
@@ -37,6 +37,7 @@ class Switch : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -141,8 +141,7 @@ Thermostat::Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_i
|
||||
// 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
|
||||
// see https://github.com/proddy/EMS-ESP/issues/362#issuecomment-629628161
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT) && ((device_id == 0x10) || (device_id == 0x17)))
|
||||
|| (master_thermostat == device_id)) {
|
||||
if (((num_devices == 1) && (actual_master_thermostat == EMSESP_DEFAULT_MASTER_THERMOSTAT)) || (master_thermostat == device_id)) {
|
||||
EMSESP::actual_master_thermostat(device_id);
|
||||
LOG_DEBUG(F("Registering new thermostat with device ID 0x%02X (as master)"), device_id);
|
||||
init_mqtt();
|
||||
@@ -169,6 +168,59 @@ void Thermostat::init_mqtt() {
|
||||
register_mqtt_topic("thermostat_cmd_mode", std::bind(&Thermostat::thermostat_cmd_mode, this, _1));
|
||||
}
|
||||
|
||||
// prepare data for Web UI
|
||||
void Thermostat::device_info(JsonArray & root) {
|
||||
uint8_t flags = (this->flags() & 0x0F); // specific thermostat characteristics, strip the option bits
|
||||
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
break; // skip this HC
|
||||
}
|
||||
|
||||
// different thermostat types store their temperature values differently
|
||||
uint8_t format_setpoint, format_curr;
|
||||
switch (flags) {
|
||||
case EMS_DEVICE_FLAG_EASY:
|
||||
format_setpoint = 100; // *100
|
||||
format_curr = 100; // *100
|
||||
break;
|
||||
case EMS_DEVICE_FLAG_JUNKERS:
|
||||
format_setpoint = 10; // *10
|
||||
format_curr = 10; // *10
|
||||
break;
|
||||
default: // RC30, RC35 etc...
|
||||
format_setpoint = 2; // *2
|
||||
format_curr = 10; // *10
|
||||
break;
|
||||
}
|
||||
|
||||
// create prefix with heating circuit number
|
||||
std::string hc_str(5, '\0');
|
||||
snprintf_P(&hc_str[0], hc_str.capacity() + 1, PSTR("hc%d: "), hc->hc_num());
|
||||
|
||||
render_value_json(root, hc_str, F("Current room temperature"), hc->curr_roomTemp, F_(degrees), format_curr);
|
||||
render_value_json(root, hc_str, F("Setpoint room temperature"), hc->setpoint_roomTemp, F_(degrees), format_setpoint);
|
||||
if (Helpers::hasValue(hc->mode)) {
|
||||
JsonObject dataElement;
|
||||
dataElement = root.createNestedObject();
|
||||
std::string mode_str(15, '\0');
|
||||
snprintf_P(&mode_str[0], mode_str.capacity() + 1, PSTR("%sMode"), hc_str.c_str());
|
||||
dataElement["name"] = mode_str;
|
||||
std::string modetype_str(20, '\0');
|
||||
if (Helpers::hasValue(hc->summer_mode) && hc->summer_mode) {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - summer"), mode_tostring(hc->get_mode(flags)).c_str());
|
||||
} else if (Helpers::hasValue(hc->holiday_mode) && hc->holiday_mode) {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - holiday"), mode_tostring(hc->get_mode(flags)).c_str());
|
||||
} else if (Helpers::hasValue(hc->mode_type)) {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, PSTR("%s - %s"), mode_tostring(hc->get_mode(flags)).c_str(), mode_tostring(hc->get_mode_type(flags)).c_str());
|
||||
} else {
|
||||
snprintf_P(&modetype_str[0], modetype_str.capacity() + 1, mode_tostring(hc->get_mode(flags)).c_str());
|
||||
}
|
||||
dataElement["value"] = modetype_str;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only add the menu for the master thermostat
|
||||
void Thermostat::add_context_menu() {
|
||||
if (device_id() != EMSESP::actual_master_thermostat()) {
|
||||
@@ -258,7 +310,7 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
set_holiday(holiday.c_str(), hc_num);
|
||||
}
|
||||
}
|
||||
|
||||
// commands without heatingcircuit
|
||||
if (nullptr != doc["wwmode"]) {
|
||||
std::string mode = doc["wwmode"];
|
||||
set_ww_mode(mode);
|
||||
@@ -363,10 +415,10 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
|
||||
// check for commands like {"hc":2,"cmd":"temp","data":21}
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
if (command == nullptr || doc["data"] == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ok, we have command and data
|
||||
if (strcmp(command, "temp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -374,8 +426,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// thermostat mode changes
|
||||
if (strcmp(command, "mode") == 0) {
|
||||
std::string mode = doc["data"];
|
||||
if (mode.empty()) {
|
||||
@@ -384,7 +434,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
set_mode(mode, hc_num);
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "nighttemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -392,7 +441,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "daytemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -400,7 +448,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "holidaytemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -408,7 +455,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "ecotemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -416,7 +462,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "heattemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -424,7 +469,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "nofrosttemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -432,7 +476,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "summertemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -440,7 +483,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "designtemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -448,7 +490,6 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (strcmp(command, "offsettemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f) {
|
||||
@@ -456,6 +497,40 @@ void Thermostat::thermostat_cmd(const char * message) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "remotetemp") == 0) {
|
||||
float f = doc["data"];
|
||||
if (f > 100 || f < 0) {
|
||||
Roomctrl::set_remotetemp(hc_num - 1, EMS_VALUE_SHORT_NOTSET);
|
||||
} else {
|
||||
Roomctrl::set_remotetemp(hc_num - 1, (int16_t)(f * 10));
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "control") == 0) {
|
||||
uint8_t ctrl = doc["data"];
|
||||
set_control(ctrl, hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "pause") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_pause(p, hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "party") == 0) {
|
||||
uint8_t p = doc["data"];
|
||||
set_party(p, hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "holiday") == 0) {
|
||||
std::string holiday = doc["data"];
|
||||
set_holiday(holiday.c_str(), hc_num);
|
||||
return;
|
||||
}
|
||||
if (strcmp(command, "date") == 0) {
|
||||
std::string date = doc["data"];
|
||||
set_datetime(date.c_str());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Thermostat::thermostat_cmd_temp(const char * message) {
|
||||
@@ -552,7 +627,8 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
// send this specific data using the thermostat_data topic
|
||||
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
// if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
if (mqtt_format_ != MQTT_format::NESTED) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
rootThermostat = doc.to<JsonObject>(); // clear object
|
||||
}
|
||||
@@ -566,7 +642,8 @@ 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_ != MQTT_format::SINGLE) {
|
||||
// if (mqtt_format_ != MQTT_format::SINGLE) {
|
||||
if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::HA)) {
|
||||
char hc_name[10]; // hc{1-4}
|
||||
strlcpy(hc_name, "hc", 10);
|
||||
char s[3];
|
||||
@@ -655,7 +732,8 @@ void Thermostat::publish_values() {
|
||||
|
||||
// if format is single, send immediately and clear object for next hc
|
||||
// the topic will have the hc number appended
|
||||
if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
// if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
if ((mqtt_format_ == MQTT_format::SINGLE) || (mqtt_format_ == MQTT_format::CUSTOM)) {
|
||||
char topic[30];
|
||||
char s[3];
|
||||
strlcpy(topic, "thermostat_data", 30);
|
||||
@@ -674,10 +752,9 @@ void Thermostat::publish_values() {
|
||||
}
|
||||
|
||||
// if we're using nested json, send all in one go under one topic called thermostat_data
|
||||
// if ((mqtt_format_ == MQTT_format::NESTED) || (mqtt_format_ == MQTT_format::CUSTOM)) {
|
||||
if (mqtt_format_ == MQTT_format::NESTED) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
} else if (mqtt_format_ == MQTT_format::CUSTOM) {
|
||||
Mqtt::publish("thermostat_data", doc);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -987,7 +1064,7 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
|
||||
shell.printfln(F(" Display: time"));
|
||||
} else if (ibaMainDisplay_ == 7) {
|
||||
shell.printfln(F(" Display: date"));
|
||||
} else if (ibaMainDisplay_ == 9) {
|
||||
} else if (ibaMainDisplay_ == 8) {
|
||||
shell.printfln(F(" Display: smoke temperature"));
|
||||
}
|
||||
}
|
||||
@@ -1024,8 +1101,6 @@ void Thermostat::show_values(uuid::console::Shell & shell) {
|
||||
}
|
||||
}
|
||||
|
||||
// std::sort(heating_circuits_.begin(), heating_circuits_.end()); // sort based on hc number. This has moved to the heating_circuit() function
|
||||
|
||||
for (const auto & hc : heating_circuits_) {
|
||||
if (!Helpers::hasValue(hc->setpoint_roomTemp)) {
|
||||
break; // skip this HC
|
||||
@@ -1168,7 +1243,7 @@ void Thermostat::process_EasyMonitor(std::shared_ptr<const Telegram> telegram) {
|
||||
void Thermostat::process_IBASettings(std::shared_ptr<const Telegram> telegram) {
|
||||
// 22 - display line on RC35
|
||||
telegram->read_value(ibaMainDisplay_,
|
||||
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 9 smoke temp
|
||||
0); // display on Thermostat: 0 int. temp, 1 int. setpoint, 2 ext. temp., 3 burner temp., 4 ww temp, 5 functioning mode, 6 time, 7 data, 8 smoke temp
|
||||
telegram->read_value(ibaLanguage_, 1); // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
|
||||
telegram->read_value(ibaCalIntTemperature_, 2); // offset int. temperature sensor, by * 0.1 Kelvin
|
||||
telegram->read_value(ibaBuildingType_, 6); // building type: 0 = light, 1 = medium, 2 = heavy
|
||||
@@ -1310,11 +1385,16 @@ void Thermostat::process_RCTime(std::shared_ptr<const Telegram> telegram) {
|
||||
if (flags() == EMS_DEVICE_FLAG_EASY) {
|
||||
return; // not supported
|
||||
}
|
||||
|
||||
if (telegram->message_length < 7) {
|
||||
return;
|
||||
}
|
||||
if (telegram->message_data[7] & 0x0C) { // date and time not valid
|
||||
set_datetime("NTP"); // set from NTP
|
||||
return;
|
||||
}
|
||||
if (datetime_.empty()) {
|
||||
datetime_.resize(25, '\0');
|
||||
}
|
||||
|
||||
// render time to HH:MM:SS DD/MM/YYYY
|
||||
// had to create separate buffers because of how printf works
|
||||
char buf1[6];
|
||||
@@ -1469,18 +1549,38 @@ 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
|
||||
// set date&time as string hh:mm:ss-dd.mm.yyyy-dw-dst or "NTP" for setting to internet-time
|
||||
// dw - day of week (0..6), dst- summertime (0/1)
|
||||
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
|
||||
if (strcmp(dt,"NTP") == 0) {
|
||||
time_t now = time(nullptr);
|
||||
tm * tm_ = localtime(&now);
|
||||
if (tm_->tm_year < 110) { // no NTP time
|
||||
LOG_WARNING(F("No NTP time. Cannot set RCtime"));
|
||||
return;
|
||||
}
|
||||
data[0] = tm_->tm_year - 100; // Bosch counts from 2000
|
||||
data[1] = tm_->tm_mon;
|
||||
data[2] = tm_->tm_hour;
|
||||
data[3] = tm_->tm_mday;
|
||||
data[4] = tm_->tm_min;
|
||||
data[5] = tm_->tm_sec;
|
||||
data[6] = (tm_->tm_wday + 6) % 7; // Bosch counts from Mo, time from Su
|
||||
data[7] = tm_->tm_isdst + 2; // set DST and flag for ext. clock
|
||||
char time_string[25];
|
||||
strftime(time_string, 25, "%FT%T%z", tm_);
|
||||
LOG_INFO(F("Date and time: %s"), time_string);
|
||||
} else {
|
||||
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') + 2; // DST and flag
|
||||
}
|
||||
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);
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#include "mqtt.h"
|
||||
|
||||
#include <vector>
|
||||
#include <time.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -94,6 +95,7 @@ class Thermostat : public EMSdevice {
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual void device_info(JsonArray & root);
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ uint8_t EMSdevice::decode_brand(uint8_t value) {
|
||||
}
|
||||
}
|
||||
|
||||
// print human friendly description of the EMS device
|
||||
// returns string of a human friendly description of the EMS device
|
||||
std::string EMSdevice::to_string() const {
|
||||
std::string str(160, '\0');
|
||||
|
||||
@@ -153,6 +153,17 @@ std::string EMSdevice::to_string() const {
|
||||
return str;
|
||||
}
|
||||
|
||||
// returns out brand + device name
|
||||
std::string EMSdevice::to_string_short() const {
|
||||
std::string str(160, '\0');
|
||||
if (brand_ == Brand::NO_BRAND) {
|
||||
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s: %s"), device_type_name().c_str(), name_.c_str());
|
||||
} else {
|
||||
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s: %s %s"), device_type_name().c_str(), brand_to_string().c_str(), name_.c_str());
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
// prints the header for the section
|
||||
void EMSdevice::show_values(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("%s: %s"), device_type_name().c_str(), to_string().c_str());
|
||||
@@ -198,7 +209,7 @@ void EMSdevice::show_mqtt_handlers(uuid::console::Shell & shell) {
|
||||
Mqtt::show_topic_handlers(shell, this->device_id_);
|
||||
}
|
||||
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_function_p f) {
|
||||
void EMSdevice::register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f) {
|
||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
Mqtt::subscribe(this->device_id_, topic, f);
|
||||
}
|
||||
|
||||
@@ -102,12 +102,22 @@ class EMSdevice {
|
||||
return name_;
|
||||
}
|
||||
|
||||
inline uint8_t unique_id() const {
|
||||
return unique_id_;
|
||||
}
|
||||
|
||||
inline void unique_id(uint8_t unique_id) {
|
||||
unique_id_ = unique_id;
|
||||
}
|
||||
|
||||
std::string brand_to_string() const;
|
||||
static uint8_t decode_brand(uint8_t value);
|
||||
|
||||
std::string to_string() const;
|
||||
void show_telegram_handlers(uuid::console::Shell & shell);
|
||||
void show_mqtt_handlers(uuid::console::Shell & shell);
|
||||
std::string to_string_short() const;
|
||||
|
||||
void show_telegram_handlers(uuid::console::Shell & shell);
|
||||
void show_mqtt_handlers(uuid::console::Shell & shell);
|
||||
|
||||
using process_function_p = std::function<void(std::shared_ptr<const Telegram>)>;
|
||||
void register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p cb);
|
||||
@@ -119,13 +129,14 @@ class EMSdevice {
|
||||
|
||||
void read_command(const uint16_t type_id);
|
||||
|
||||
void register_mqtt_topic(const std::string & topic, mqtt_function_p f);
|
||||
void register_mqtt_topic(const std::string & topic, mqtt_subfunction_p f);
|
||||
|
||||
// virtual functions overrules by derived classes
|
||||
virtual void show_values(uuid::console::Shell & shell) = 0;
|
||||
virtual void publish_values() = 0;
|
||||
virtual bool updated_values() = 0;
|
||||
virtual void add_context_menu() = 0;
|
||||
virtual void device_info(JsonArray & root) = 0;
|
||||
|
||||
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
@@ -165,6 +176,37 @@ class EMSdevice {
|
||||
}
|
||||
}
|
||||
|
||||
// takes a value from an ems device and creates a nested json (name, value)
|
||||
// which can be passed to the web UI
|
||||
template <typename Value>
|
||||
static void render_value_json(JsonArray & json,
|
||||
const std::string & prefix,
|
||||
const __FlashStringHelper * name,
|
||||
Value & value,
|
||||
const __FlashStringHelper * suffix,
|
||||
const uint8_t format = 0) {
|
||||
char buffer[15];
|
||||
if (Helpers::render_value(buffer, value, format) == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject dataElement = json.createNestedObject();
|
||||
|
||||
// copy flash into std::strings to ensure arduinojson can reference them without a copy
|
||||
|
||||
if (suffix != nullptr) {
|
||||
std::string text(20, '\0');
|
||||
snprintf_P(&text[0], text.capacity() + 1, PSTR("%s%s"), buffer, uuid::read_flash_string(suffix).c_str());
|
||||
dataElement["value"] = text;
|
||||
} else {
|
||||
dataElement["value"] = buffer;
|
||||
}
|
||||
|
||||
std::string text2(100, '\0');
|
||||
snprintf_P(&text2[0], text2.capacity() + 1, PSTR("%s%s"), prefix.c_str(), uuid::read_flash_string(name).c_str());
|
||||
dataElement["name"] = text2;
|
||||
}
|
||||
|
||||
static void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const __FlashStringHelper * value);
|
||||
static void print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value);
|
||||
|
||||
@@ -229,6 +271,7 @@ class EMSdevice {
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS_2 = (1 << 6); // 6th bit set if older models
|
||||
|
||||
private:
|
||||
uint8_t unique_id_;
|
||||
uint8_t device_type_ = DeviceType::UNKNOWN;
|
||||
uint8_t device_id_ = 0;
|
||||
uint8_t product_id_ = 0;
|
||||
|
||||
@@ -39,9 +39,8 @@ ESP8266React EMSESP::esp8266React(&webServer, &dummyFS);
|
||||
EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &dummyFS, 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());
|
||||
EMSESPStatusService EMSESP::emsespStatusService = EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager());
|
||||
EMSESPDevicesService EMSESP::emsespDevicesService = EMSESPDevicesService(&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
|
||||
@@ -63,6 +62,7 @@ uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; /
|
||||
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()
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
uint8_t EMSESP::unique_id_count_ = 0;
|
||||
|
||||
// for a specific EMS device go and request data values
|
||||
// or if device_id is 0 it will fetch from all our known and active devices
|
||||
@@ -110,13 +110,17 @@ 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);
|
||||
void EMSESP::reset_tx() {
|
||||
// get the tx_mode
|
||||
uint8_t tx_mode;
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode = settings.tx_mode; });
|
||||
|
||||
EMSuart::stop();
|
||||
EMSuart::start(tx_mode);
|
||||
txservice_.start();
|
||||
|
||||
// force a fetch for all new values, unless Tx is set to off
|
||||
if (tx_mode != 0) {
|
||||
EMSESP::fetch_device_values();
|
||||
}
|
||||
}
|
||||
@@ -151,7 +155,22 @@ uint8_t EMSESP::bus_status() {
|
||||
// show the EMS bus status plus both Rx and Tx queues
|
||||
void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
// EMS bus information
|
||||
if (rxservice_.bus_connected()) {
|
||||
switch (bus_status()) {
|
||||
case BUS_STATUS_OFFLINE:
|
||||
shell.printfln(F("EMS Bus is disconnected."));
|
||||
break;
|
||||
case BUS_STATUS_TX_ERRORS:
|
||||
shell.printfln(F("EMS Bus is connected, but Tx is not stable."));
|
||||
break;
|
||||
case BUS_STATUS_CONNECTED:
|
||||
default:
|
||||
shell.printfln(F("EMS Bus is connected."));
|
||||
break;
|
||||
}
|
||||
|
||||
shell.println();
|
||||
|
||||
if (bus_status() != BUS_STATUS_OFFLINE) {
|
||||
uint8_t success_rate = 0;
|
||||
if (rxservice_.telegram_error_count()) {
|
||||
success_rate = ((float)rxservice_.telegram_error_count() / (float)rxservice_.telegram_count()) * 100;
|
||||
@@ -165,8 +184,6 @@ void EMSESP::show_ems(uuid::console::Shell & shell) {
|
||||
shell.printfln(F(" #write requests sent: %d"), txservice_.telegram_write_count());
|
||||
shell.printfln(F(" #corrupted telegrams: %d (%d%%)"), rxservice_.telegram_error_count(), success_rate);
|
||||
shell.printfln(F(" #tx fails (after %d retries): %d"), TxService::MAXIMUM_TX_RETRIES, txservice_.telegram_fail_count());
|
||||
} else {
|
||||
shell.printfln(F("EMS Bus is disconnected."));
|
||||
}
|
||||
|
||||
shell.println();
|
||||
@@ -231,7 +248,7 @@ void EMSESP::show_device_values(uuid::console::Shell & shell) {
|
||||
}
|
||||
}
|
||||
|
||||
// show Dallas sensors
|
||||
// show Dallas temperature sensors
|
||||
void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
if (sensor_devices().empty()) {
|
||||
return;
|
||||
@@ -240,7 +257,7 @@ void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
char valuestr[8] = {0}; // for formatting temp
|
||||
shell.printfln(F("External temperature sensors:"));
|
||||
for (const auto & device : sensor_devices()) {
|
||||
shell.printfln(F(" Sensor ID %s: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c_, 2));
|
||||
shell.printfln(F(" ID: %s, Temperature: %s°C"), device.to_string().c_str(), Helpers::render_value(valuestr, device.temperature_c, 2));
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
@@ -474,6 +491,20 @@ bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
return found;
|
||||
}
|
||||
|
||||
// calls the device handler's function to populate a json doc with device info
|
||||
void EMSESP::device_info(const uint8_t unique_id, JsonObject & root) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->unique_id() == unique_id) {
|
||||
root["deviceName"] = emsdevice->to_string_short(); // can;t use c_str() because of scope
|
||||
JsonArray data = root.createNestedArray("deviceData");
|
||||
emsdevice->device_info(data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return true if we have this device already registered
|
||||
bool EMSESP::device_exists(const uint8_t device_id) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
@@ -513,6 +544,9 @@ void EMSESP::show_devices(uuid::console::Shell & shell) {
|
||||
// shell.printf(F("[factory ID: %d] "), device_class.first);
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
#if defined(EMSESP_DEBUG)
|
||||
shell.printf(F("[id=%d] "), emsdevice->unique_id());
|
||||
#endif
|
||||
shell.printf(F("%s: %s"), emsdevice->device_type_name().c_str(), emsdevice->to_string().c_str());
|
||||
if ((emsdevice->device_type() == EMSdevice::DeviceType::THERMOSTAT) && (emsdevice->device_id() == actual_master_thermostat())) {
|
||||
shell.printf(F(" ** master device **"));
|
||||
@@ -584,6 +618,7 @@ bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::
|
||||
} else {
|
||||
emsdevices.push_back(
|
||||
EMSFactory::add(device_p->device_type, device_id, device_p->product_id, version, uuid::read_flash_string(device_p->name), device_p->flags, brand));
|
||||
emsdevices.back()->unique_id(++unique_id_count_);
|
||||
LOG_DEBUG(F("Adding new device with device ID 0x%02X with product ID %d and version %s"), device_id, product_id, version.c_str());
|
||||
// go and fetch its data,
|
||||
fetch_device_values(device_id);
|
||||
@@ -629,14 +664,14 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
|
||||
// are we waiting for a response from a recent Tx Read or Write?
|
||||
uint8_t op = EMSbus::tx_waiting();
|
||||
if (op != Telegram::Operation::NONE) {
|
||||
uint8_t tx_state = EMSbus::tx_state();
|
||||
if (tx_state != Telegram::Operation::NONE) {
|
||||
bool tx_successful = false;
|
||||
EMSbus::tx_waiting(Telegram::Operation::NONE); // reset Tx wait state
|
||||
EMSbus::tx_state(Telegram::Operation::NONE); // reset Tx wait state
|
||||
// txservice_.print_last_tx();
|
||||
|
||||
// if we're waiting on a Write operation, we want a single byte 1 or 4
|
||||
if ((op == Telegram::Operation::TX_WRITE) && (length == 1)) {
|
||||
if ((tx_state == Telegram::Operation::TX_WRITE) && (length == 1)) {
|
||||
if (first_value == TxService::TX_WRITE_SUCCESS) {
|
||||
LOG_DEBUG(F("Last Tx write successful"));
|
||||
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
|
||||
@@ -649,7 +684,7 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
txservice_.send_poll(); // close the bus
|
||||
txservice_.reset_retry_count();
|
||||
}
|
||||
} else if (op == Telegram::Operation::TX_READ) {
|
||||
} else if (tx_state == Telegram::Operation::TX_READ) {
|
||||
// got a telegram with data in it. See if the src/dest matches that from the last one we sent and continue to process it
|
||||
uint8_t src = data[0];
|
||||
uint8_t dest = data[1];
|
||||
@@ -664,13 +699,15 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
|
||||
// if Tx wasn't successful, retry or just give up
|
||||
if (!tx_successful) {
|
||||
txservice_.retry_tx(op, data, length);
|
||||
txservice_.retry_tx(tx_state, data, length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check for poll
|
||||
if (length == 1) {
|
||||
EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
char s[4];
|
||||
if (first_value & 0x80) {
|
||||
@@ -684,7 +721,6 @@ void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
// check for poll to us, if so send top message from Tx queue immediately and quit
|
||||
// if ht3 poll must be ems_bus_id else if Buderus poll must be (ems_bus_id | 0x80)
|
||||
if ((first_value ^ 0x80 ^ rxservice_.ems_mask()) == txservice_.ems_bus_id()) {
|
||||
EMSbus::last_bus_activity(uuid::get_uptime()); // set the flag indication the EMS bus is active
|
||||
txservice_.send();
|
||||
}
|
||||
// send remote room temperature if active
|
||||
@@ -741,15 +777,17 @@ void EMSESP::loop() {
|
||||
|
||||
// if we're doing an OTA upload, skip MQTT and EMS
|
||||
if (system_.upload_status()) {
|
||||
#if defined(ESP32)
|
||||
delay(10); // slow down OTA update to avoid getting killed by task watchdog (task_wdt)
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
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
|
||||
mqtt_.loop(); // sends out anything in the queue via MQTT
|
||||
console_.loop(); // telnet/serial console
|
||||
|
||||
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||
|
||||
16
src/emsesp.h
16
src/emsesp.h
@@ -37,7 +37,6 @@
|
||||
#include "EMSESPStatusService.h"
|
||||
#include "EMSESPDevicesService.h"
|
||||
#include "EMSESPSettingsService.h"
|
||||
#include "EMSESPScanDevicesService.h"
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "emsfactory.h"
|
||||
@@ -82,6 +81,8 @@ class EMSESP {
|
||||
static void send_raw_telegram(const char * data);
|
||||
static bool device_exists(const uint8_t device_id);
|
||||
|
||||
static void device_info(const uint8_t unique_id, JsonObject & root);
|
||||
|
||||
static uint8_t count_devices(const uint8_t device_type);
|
||||
|
||||
static uint8_t actual_master_thermostat();
|
||||
@@ -95,7 +96,7 @@ class EMSESP {
|
||||
|
||||
static void add_context_menus();
|
||||
|
||||
static void reset_tx(uint8_t const tx_mode);
|
||||
static void reset_tx();
|
||||
|
||||
static void incoming_telegram(uint8_t * data, const uint8_t length);
|
||||
|
||||
@@ -146,11 +147,10 @@ class EMSESP {
|
||||
static TxService txservice_;
|
||||
|
||||
// web controllers
|
||||
static ESP8266React esp8266React;
|
||||
static EMSESPSettingsService emsespSettingsService;
|
||||
static EMSESPStatusService emsespStatusService;
|
||||
static EMSESPDevicesService emsespDevicesService;
|
||||
static EMSESPScanDevicesService emsespScanDevicesService;
|
||||
static ESP8266React esp8266React;
|
||||
static EMSESPSettingsService emsespSettingsService;
|
||||
static EMSESPStatusService emsespStatusService;
|
||||
static EMSESPDevicesService emsespDevicesService;
|
||||
|
||||
static uuid::log::Logger logger() {
|
||||
return logger_;
|
||||
@@ -182,6 +182,8 @@ class EMSESP {
|
||||
static uint16_t watch_id_;
|
||||
static uint8_t watch_;
|
||||
static bool tap_water_active_;
|
||||
|
||||
static uint8_t unique_id_count_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -89,6 +89,7 @@ class ConcreteEMSFactory : EMSFactory {
|
||||
ConcreteEMSFactory(const uint8_t device_type) {
|
||||
EMSFactory::registerFactory(device_type, this);
|
||||
}
|
||||
|
||||
auto construct(uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand) const
|
||||
-> EMSdevice * {
|
||||
return new DerivedClass(device_type, device_id, product_id, version, name, flags, brand);
|
||||
|
||||
195
src/mqtt.cpp
195
src/mqtt.cpp
@@ -35,7 +35,7 @@ std::string Mqtt::hostname_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
uint16_t Mqtt::publish_time_;
|
||||
|
||||
std::vector<Mqtt::MQTTFunction> Mqtt::mqtt_functions_;
|
||||
std::vector<Mqtt::MQTTSubFunction> Mqtt::mqtt_subfunctions_;
|
||||
uint16_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
|
||||
uint16_t Mqtt::mqtt_message_id_ = 0;
|
||||
@@ -51,62 +51,56 @@ Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMess
|
||||
packet_id_ = 0;
|
||||
}
|
||||
|
||||
MqttMessage::MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
MqttMessage::MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain)
|
||||
: operation(operation)
|
||||
, topic(topic)
|
||||
, payload(payload)
|
||||
, retain(retain) {
|
||||
}
|
||||
|
||||
Mqtt::MQTTFunction::MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function)
|
||||
Mqtt::MQTTSubFunction::MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction)
|
||||
: device_id_(device_id)
|
||||
, topic_(topic)
|
||||
, mqtt_function_(mqtt_function) {
|
||||
, mqtt_subfunction_(mqtt_subfunction) {
|
||||
}
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function
|
||||
void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb) {
|
||||
/*
|
||||
// We don't want to store the whole topic string in our lookup, just the last cmd, as this can take up too much memory
|
||||
// strip out everything until the last /
|
||||
size_t topic_pos = topic.find_last_of("/"); // returns npos which is -1
|
||||
topic_pos += 1; // skip the /
|
||||
*/
|
||||
void Mqtt::subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb) {
|
||||
auto message = queue_subscribe_message(topic); // add subscription to queue. The hostname will automatically be appended
|
||||
|
||||
// convert the topic to it's full path, so either prefixed with the hostname unless hardcoded like 'homeassistant'
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
make_topic(full_topic, topic);
|
||||
if (message == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// the message will contain the full topic, with the hostname prefixed
|
||||
// check if we already have the topic subscribed, if so don't add it again
|
||||
bool exists = false;
|
||||
if (!mqtt_functions_.empty()) {
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
if ((mqtt_function.device_id_ == device_id) && (strcmp(mqtt_function.topic_.c_str(), full_topic) == 0)) {
|
||||
if (!mqtt_subfunctions_.empty()) {
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if ((mqtt_subfunction.device_id_ == device_id) && (strcmp(mqtt_subfunction.topic_.c_str(), message->topic.c_str()) == 0)) {
|
||||
exists = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!exists) {
|
||||
mqtt_functions_.emplace_back(device_id, std::move(full_topic), cb); // register a call back function for a specific telegram type
|
||||
mqtt_subfunctions_.emplace_back(device_id, std::move(message->topic), cb); // register a call back function for a specific telegram type
|
||||
}
|
||||
|
||||
queue_subscribe_message(topic); // add subscription to queue
|
||||
}
|
||||
|
||||
// subscribe to an MQTT topic, and store the associated callback function. For generic functions not tied to a specific device
|
||||
void Mqtt::subscribe(const std::string & topic, mqtt_function_p cb) {
|
||||
void Mqtt::subscribe(const std::string & topic, mqtt_subfunction_p cb) {
|
||||
subscribe(0, topic, cb); // no device_id needed, if generic to EMS-ESP
|
||||
}
|
||||
|
||||
// resubscribe to all MQTT topics again
|
||||
void Mqtt::resubscribe() {
|
||||
if (mqtt_functions_.empty()) {
|
||||
if (mqtt_subfunctions_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
queue_subscribe_message(mqtt_function.topic_);
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
queue_message(Operation::SUBSCRIBE, mqtt_subfunction.topic_, "", false, true); // no payload, no topic prefixing
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +114,7 @@ void Mqtt::loop() {
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -146,8 +141,8 @@ void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
||||
|
||||
// show subscriptions
|
||||
shell.printfln(F("MQTT subscriptions:"));
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
shell.printfln(F(" %s"), mqtt_function.topic_.c_str());
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
shell.printfln(F(" %s"), mqtt_subfunction.topic_.c_str());
|
||||
}
|
||||
shell.println();
|
||||
|
||||
@@ -206,19 +201,11 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
strlcpy(message, payload, len + 1);
|
||||
LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len);
|
||||
|
||||
/*
|
||||
// strip out everything until the last /
|
||||
char * topic_magnitude = strrchr(topic, '/');
|
||||
if (topic_magnitude != nullptr) {
|
||||
topic = topic_magnitude + 1;
|
||||
}
|
||||
*/
|
||||
|
||||
// see if we have this topic in our subscription list, then call its callback handler
|
||||
// note: this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail. Not sure if this is going to be an issue?
|
||||
for (const auto & mf : mqtt_functions_) {
|
||||
for (const auto & mf : mqtt_subfunctions_) {
|
||||
if (strcmp(topic, mf.topic_.c_str()) == 0) {
|
||||
(mf.mqtt_function_)(message);
|
||||
(mf.mqtt_subfunction_)(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -229,15 +216,17 @@ void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
|
||||
// print all the topics related to a specific device_id
|
||||
void Mqtt::show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id) {
|
||||
if (std::count_if(mqtt_functions_.cbegin(), mqtt_functions_.cend(), [=](MQTTFunction const & mqtt_function) { return device_id == mqtt_function.device_id_; })
|
||||
if (std::count_if(mqtt_subfunctions_.cbegin(),
|
||||
mqtt_subfunctions_.cend(),
|
||||
[=](MQTTSubFunction const & mqtt_subfunction) { return device_id == mqtt_subfunction.device_id_; })
|
||||
== 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell.print(F(" Subscribed MQTT topics: "));
|
||||
for (const auto & mqtt_function : mqtt_functions_) {
|
||||
if (mqtt_function.device_id_ == device_id) {
|
||||
shell.printf(F("%s "), mqtt_function.topic_.c_str());
|
||||
for (const auto & mqtt_subfunction : mqtt_subfunctions_) {
|
||||
if (mqtt_subfunction.device_id_ == device_id) {
|
||||
shell.printf(F("%s "), mqtt_subfunction.topic_.c_str());
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
@@ -268,24 +257,7 @@ 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 hostname
|
||||
// unless it's hardcoded like "homeassistant"
|
||||
char * Mqtt::make_topic(char * result, const std::string & topic) {
|
||||
// check for homesassistant
|
||||
if (strncmp(topic.c_str(), "homeassistant/", 13) == 0) {
|
||||
strlcpy(result, topic.c_str(), MQTT_TOPIC_MAX_SIZE);
|
||||
return result;
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
mqttClient_ = EMSESP::esp8266React.getMqttClient();
|
||||
|
||||
// get the hostname, which we'll use to prefix to all topics
|
||||
@@ -298,15 +270,36 @@ void Mqtt::start() {
|
||||
});
|
||||
|
||||
mqttClient_->onConnect([this](bool sessionPresent) { on_connect(); });
|
||||
mqttClient_->setWill(make_topic(will_topic_, "status"), 1, true, "offline"); // with qos 1, retain true
|
||||
|
||||
mqttClient_->onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||
if (reason == AsyncMqttClientDisconnectReason::TCP_DISCONNECTED) {
|
||||
LOG_INFO(F("MQTT disconnected: TCP"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED) {
|
||||
LOG_INFO(F("MQTT disconnected: Identifier Rejected"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE) {
|
||||
LOG_INFO(F("MQTT disconnected: Server unavailable"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS) {
|
||||
LOG_INFO(F("MQTT disconnected: Malformed credentials"));
|
||||
}
|
||||
if (reason == AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED) {
|
||||
LOG_INFO(F("MQTT disconnected: Not authorized"));
|
||||
}
|
||||
});
|
||||
|
||||
// create will_topic with the hostname prefixed. It has to be static because asyncmqttclient destroys the reference
|
||||
static char will_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
strlcpy(will_topic, hostname_.c_str(), MQTT_TOPIC_MAX_SIZE);
|
||||
strlcat(will_topic, "/", MQTT_TOPIC_MAX_SIZE);
|
||||
strlcat(will_topic, "status", MQTT_TOPIC_MAX_SIZE);
|
||||
mqttClient_->setWill(will_topic, 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); });
|
||||
});
|
||||
|
||||
// add the system MQTT subscriptions
|
||||
Mqtt::subscribe("cmd", System::mqtt_commands);
|
||||
// Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, std::placeholders::_1));
|
||||
}
|
||||
|
||||
void Mqtt::set_publish_time(uint16_t publish_time) {
|
||||
@@ -334,47 +327,50 @@ void Mqtt::on_connect() {
|
||||
|
||||
resubscribe(); // in case this is a reconnect, re-subscribe again to all MQTT topics
|
||||
|
||||
// add the system MQTT subscriptions, only if its a fresh start with no previous subscriptions
|
||||
if (mqtt_subfunctions_.empty()) {
|
||||
Mqtt::subscribe("cmd", System::mqtt_commands);
|
||||
}
|
||||
|
||||
LOG_INFO(F("MQTT connected"));
|
||||
}
|
||||
|
||||
// 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
|
||||
// add sub or pub task to the queue. When the message is created, the topic will have
|
||||
// automatically the hostname prefixed.
|
||||
std::shared_ptr<const MqttMessage>
|
||||
Mqtt::queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix) {
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// prefix the hostname to the topic
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
make_topic(full_topic, topic);
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, full_topic, payload, retain);
|
||||
// take the topic and prefix the hostname, unless its for HA
|
||||
std::shared_ptr<MqttMessage> message;
|
||||
if ((strncmp(topic.c_str(), "homeassistant/", 13) == 0) || no_prefix) {
|
||||
// leave topic as it is
|
||||
message = std::make_shared<MqttMessage>(operation, topic, payload, retain);
|
||||
} else {
|
||||
// prefix the hostname
|
||||
std::string full_topic = Mqtt::hostname_ + "/" + topic;
|
||||
message = std::make_shared<MqttMessage>(operation, full_topic, payload, retain);
|
||||
}
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
mqtt_messages_.pop_front();
|
||||
}
|
||||
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
|
||||
return mqtt_messages_.back().content_; // this is because the message has been moved
|
||||
}
|
||||
|
||||
// add MQTT message to queue, payload is a string
|
||||
std::shared_ptr<const MqttMessage> Mqtt::queue_publish_message(const std::string & topic, const std::string & payload, const bool retain) {
|
||||
return queue_message(Operation::PUBLISH, topic, payload, retain);
|
||||
}
|
||||
|
||||
// add MQTT subscribe message to queue
|
||||
void Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::SUBSCRIBE, topic, "", false);
|
||||
#ifdef DEBUG
|
||||
LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
|
||||
#endif
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
mqtt_messages_.pop_front();
|
||||
}
|
||||
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
std::shared_ptr<const MqttMessage> Mqtt::queue_subscribe_message(const std::string & topic) {
|
||||
return queue_message(Operation::SUBSCRIBE, topic, "", false); // no payload
|
||||
}
|
||||
|
||||
// MQTT Publish, using a specific retain flag
|
||||
@@ -383,9 +379,8 @@ void Mqtt::publish(const std::string & topic, const std::string & payload, bool
|
||||
}
|
||||
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
|
||||
// convert json to string
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text);
|
||||
serializeJson(payload, payload_text); // convert json to string
|
||||
queue_publish_message(topic, payload_text, retain);
|
||||
}
|
||||
|
||||
@@ -413,6 +408,26 @@ void Mqtt::process_queue() {
|
||||
return;
|
||||
}
|
||||
|
||||
// show queue - Debug only
|
||||
/*
|
||||
Serial.printf("MQTT queue:\n\r");
|
||||
for (const auto & message : mqtt_messages_) {
|
||||
auto content = message.content_;
|
||||
if (content->operation == Operation::PUBLISH) {
|
||||
// Publish messages
|
||||
Serial.printf(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)\n\r",
|
||||
message.id_,
|
||||
content->topic.c_str(),
|
||||
content->payload.c_str(),
|
||||
message.packet_id_,
|
||||
message.retry_count_);
|
||||
} else {
|
||||
// Subscribe messages
|
||||
Serial.printf(" [%02d] (Sub) topic=%s\n\r", message.id_, content->topic.c_str());
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
// fetch first from queue and create the full topic name
|
||||
auto mqtt_message = mqtt_messages_.front();
|
||||
auto message = mqtt_message.content_;
|
||||
|
||||
36
src/mqtt.h
36
src/mqtt.h
@@ -43,11 +43,11 @@ using uuid::console::Shell;
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using mqtt_function_p = std::function<void(const char * message)>;
|
||||
using mqtt_subfunction_p = std::function<void(const char * message)>;
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
struct MqttMessage {
|
||||
MqttMessage(uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
MqttMessage(const uint8_t operation, const std::string & topic, const std::string & payload, bool retain);
|
||||
~MqttMessage() = default;
|
||||
|
||||
const uint8_t operation;
|
||||
@@ -68,8 +68,8 @@ class Mqtt {
|
||||
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 100;
|
||||
|
||||
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 subscribe(const uint8_t device_id, const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void subscribe(const std::string & topic, mqtt_subfunction_p cb);
|
||||
static void resubscribe();
|
||||
|
||||
static void publish(const std::string & topic, const std::string & payload, bool retain = false);
|
||||
@@ -100,6 +100,8 @@ class Mqtt {
|
||||
mqtt_publish_fails_ = 0;
|
||||
}
|
||||
|
||||
static std::string hostname_;
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
@@ -125,36 +127,36 @@ class Mqtt {
|
||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 200; // 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);
|
||||
static std::shared_ptr<const MqttMessage> queue_message(const uint8_t operation, const std::string & topic, const std::string & payload, const bool retain, bool no_prefix = false);
|
||||
static std::shared_ptr<const MqttMessage> queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
|
||||
static std::shared_ptr<const MqttMessage> queue_subscribe_message(const std::string & topic);
|
||||
|
||||
void on_publish(uint16_t packetId);
|
||||
void on_message(char * topic, char * payload, size_t len);
|
||||
static char * make_topic(char * result, const std::string & topic);
|
||||
void process_queue();
|
||||
void process_all_queue();
|
||||
|
||||
static uint16_t mqtt_publish_fails_;
|
||||
|
||||
class MQTTFunction {
|
||||
// function handlers for MQTT subscriptions
|
||||
class MQTTSubFunction {
|
||||
public:
|
||||
MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function);
|
||||
~MQTTFunction() = default;
|
||||
MQTTSubFunction(const uint8_t device_id, const std::string && topic, mqtt_subfunction_p mqtt_subfunction);
|
||||
~MQTTSubFunction() = default;
|
||||
|
||||
uint8_t device_id_; // which device ID owns this
|
||||
std::string topic_;
|
||||
mqtt_function_p mqtt_function_;
|
||||
const uint8_t device_id_; // which device ID owns this
|
||||
const std::string topic_;
|
||||
mqtt_subfunction_p mqtt_subfunction_;
|
||||
};
|
||||
|
||||
static std::vector<MQTTFunction> mqtt_functions_; // list of mqtt subscribe callbacks for all devices
|
||||
static std::vector<MQTTSubFunction> mqtt_subfunctions_; // list of mqtt subscribe callbacks for all devices
|
||||
|
||||
uint32_t last_mqtt_poll_ = 0;
|
||||
uint32_t last_publish_ = 0;
|
||||
|
||||
// settings, copied over
|
||||
static std::string hostname_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
static uint8_t mqtt_qos_;
|
||||
static uint16_t publish_time_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
@@ -65,6 +65,10 @@ 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.
|
||||
|
||||
@@ -23,6 +23,12 @@
|
||||
|
||||
MAKE_PSTR(logger_name, "sensors")
|
||||
|
||||
#ifdef ESP32
|
||||
#define YIELD
|
||||
#else
|
||||
#define YIELD yield()
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
|
||||
@@ -63,10 +69,9 @@ void Sensors::loop() {
|
||||
if (time_now - last_activity_ >= READ_INTERVAL_MS) {
|
||||
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
|
||||
if (bus_.reset()) {
|
||||
yield();
|
||||
YIELD;
|
||||
bus_.skip();
|
||||
bus_.write(CMD_CONVERT_TEMP);
|
||||
|
||||
state_ = State::READING;
|
||||
} else {
|
||||
// no sensors found
|
||||
@@ -80,20 +85,15 @@ void Sensors::loop() {
|
||||
// LOG_DEBUG(F("Scanning for sensors")); // uncomment for debug
|
||||
bus_.reset_search();
|
||||
found_.clear();
|
||||
|
||||
state_ = State::SCANNING;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::SCANNING;
|
||||
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
|
||||
LOG_ERROR(F("Sensor read timeout"));
|
||||
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::IDLE;
|
||||
}
|
||||
} else if (state_ == State::SCANNING) {
|
||||
if (time_now - last_activity_ > SCAN_TIMEOUT_MS) {
|
||||
LOG_ERROR(F("Sensor scan timeout"));
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::IDLE;
|
||||
} else {
|
||||
uint8_t addr[ADDR_LEN] = {0};
|
||||
|
||||
@@ -107,7 +107,7 @@ void Sensors::loop() {
|
||||
case TYPE_DS1822:
|
||||
case TYPE_DS1825:
|
||||
found_.emplace_back(addr);
|
||||
found_.back().temperature_c_ = get_temperature_c(addr);
|
||||
found_.back().temperature_c = get_temperature_c(addr);
|
||||
|
||||
/*
|
||||
// comment out for debugging
|
||||
@@ -127,11 +127,15 @@ void Sensors::loop() {
|
||||
}
|
||||
} else {
|
||||
bus_.depower();
|
||||
devices_ = std::move(found_);
|
||||
if ((found_.size() >= devices_.size()) || (retrycnt_ > 5)) {
|
||||
devices_ = std::move(found_);
|
||||
retrycnt_ = 0;
|
||||
} else {
|
||||
retrycnt_++;
|
||||
}
|
||||
found_.clear();
|
||||
// LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
state_ = State::IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,17 +159,17 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
YIELD;
|
||||
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
|
||||
bus_.select(addr);
|
||||
bus_.write(CMD_READ_SCRATCHPAD);
|
||||
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
|
||||
yield();
|
||||
YIELD;
|
||||
if (!bus_.reset()) {
|
||||
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
YIELD;
|
||||
if (bus_.crc8(scratchpad, SCRATCHPAD_LEN - 1) != scratchpad[SCRATCHPAD_LEN - 1]) {
|
||||
LOG_WARNING(F("Invalid scratchpad CRC: %02X%02X%02X%02X%02X%02X%02X%02X%02X from device %s"),
|
||||
scratchpad[0],
|
||||
@@ -202,7 +206,8 @@ float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)raw_value / 16;
|
||||
uint32_t raw = (raw_value *625) / 100; // round to 0.01
|
||||
return (float)raw / 100;
|
||||
#else
|
||||
return NAN;
|
||||
#endif
|
||||
@@ -253,7 +258,7 @@ void Sensors::publish_values() {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
char s[5];
|
||||
doc["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
doc["temp"] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
char topic[60]; // sensors{1-n}
|
||||
strlcpy(topic, "sensor_", 50); // create topic, e.g. home/ems-esp/sensor_28-EA41-9497-0E03-5F
|
||||
strlcat(topic, device.to_string().c_str(), 60);
|
||||
@@ -279,7 +284,7 @@ void Sensors::publish_values() {
|
||||
for (const auto & device : devices_) {
|
||||
if (mqtt_format_ == MQTT_format::CUSTOM) {
|
||||
char s[5];
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
doc[device.to_string()] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
} else {
|
||||
char sensorID[10]; // sensor{1-n}
|
||||
strlcpy(sensorID, "sensor", 10);
|
||||
@@ -287,7 +292,7 @@ void Sensors::publish_values() {
|
||||
strlcat(sensorID, Helpers::itoa(s, i++), 10);
|
||||
JsonObject dataSensor = doc.createNestedObject(sensorID);
|
||||
dataSensor["id"] = device.to_string();
|
||||
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c_, 2);
|
||||
dataSensor["temp"] = Helpers::render_value(s, device.temperature_c, 2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class Sensors {
|
||||
uint64_t id() const;
|
||||
std::string to_string() const;
|
||||
|
||||
float temperature_c_ = NAN;
|
||||
float temperature_c = NAN;
|
||||
|
||||
private:
|
||||
const uint64_t id_;
|
||||
@@ -90,7 +90,7 @@ class Sensors {
|
||||
static constexpr uint32_t READ_INTERVAL_MS = 5000; // 5 seconds
|
||||
static constexpr uint32_t CONVERSION_MS = 1000; // 1 seconds
|
||||
static constexpr uint32_t READ_TIMEOUT_MS = 2000; // 2 seconds
|
||||
static constexpr uint32_t SCAN_TIMEOUT_MS = 30000; // 30 seconds
|
||||
static constexpr uint32_t SCAN_TIMEOUT_MS = 3000; // 3 seconds
|
||||
|
||||
static constexpr uint8_t CMD_CONVERT_TEMP = 0x44;
|
||||
static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE;
|
||||
@@ -111,6 +111,8 @@ class Sensors {
|
||||
std::vector<Device> devices_;
|
||||
|
||||
uint8_t mqtt_format_;
|
||||
uint8_t retrycnt_ = 0;
|
||||
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
122
src/system.cpp
122
src/system.cpp
@@ -24,8 +24,10 @@
|
||||
MAKE_PSTR_WORD(passwd)
|
||||
MAKE_PSTR_WORD(hostname)
|
||||
MAKE_PSTR_WORD(wifi)
|
||||
MAKE_PSTR_WORD(reconnect)
|
||||
MAKE_PSTR_WORD(ssid)
|
||||
MAKE_PSTR_WORD(heartbeat)
|
||||
MAKE_PSTR_WORD(users)
|
||||
|
||||
MAKE_PSTR(host_fmt, "Host = %s")
|
||||
MAKE_PSTR(hostname_fmt, "WiFi Hostname = %s")
|
||||
@@ -141,7 +143,6 @@ void System::mqtt_commands(const char * message) {
|
||||
// restart EMS-ESP
|
||||
void System::restart() {
|
||||
LOG_NOTICE("Restarting system...");
|
||||
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
#if defined(ESP8266)
|
||||
@@ -151,10 +152,19 @@ void System::restart() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// saves all settings
|
||||
void System::wifi_reconnect() {
|
||||
LOG_NOTICE("The wifi will reconnect...");
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
EMSESP::emsespSettingsService.save(); // local settings
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->callUpdateHandlers("local"); // in case we've changed ssid or password
|
||||
}
|
||||
|
||||
// format fs
|
||||
// format the FS. Wipes everything.
|
||||
void System::format(uuid::console::Shell & shell) {
|
||||
auto msg = F("Formatting file system. This will also reset all settings to their defaults");
|
||||
auto msg = F("Formatting file system. This will reset all settings to their defaults");
|
||||
shell.logger().warning(msg);
|
||||
shell.flush();
|
||||
|
||||
@@ -189,19 +199,17 @@ void System::syslog_init() {
|
||||
});
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
syslog_.start(); // syslog service
|
||||
syslog_.start(); // syslog service re-start
|
||||
|
||||
// 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);
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); });
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -220,32 +228,32 @@ void System::start() {
|
||||
#endif
|
||||
}
|
||||
|
||||
// fetch settings
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; });
|
||||
// fetch system heartbeat
|
||||
EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & settings) { system_heartbeat_ = settings.system_heartbeat; });
|
||||
|
||||
syslog_init(); // init SysLog
|
||||
// print boot message
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read(
|
||||
[&](WiFiSettings & wifiSettings) { LOG_INFO(F("System %s booted (EMS-ESP version %s)"), wifiSettings.hostname.c_str(), EMSESP_APP_VERSION); });
|
||||
|
||||
#if defined(ESP32)
|
||||
LOG_INFO(F("System booted (EMS-ESP version %s ESP32)"), EMSESP_APP_VERSION);
|
||||
#else
|
||||
LOG_INFO(F("System booted (EMS-ESP version %s)"), EMSESP_APP_VERSION);
|
||||
#endif
|
||||
syslog_init(); // init SysLog
|
||||
|
||||
if (LED_GPIO) {
|
||||
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 means disabled
|
||||
}
|
||||
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { tx_mode_ = settings.tx_mode; });
|
||||
#ifndef EMSESP_FORCE_SERIAL
|
||||
if (tx_mode_) {
|
||||
EMSuart::start(tx_mode_); // start UART, if tx_mode is not 0
|
||||
}
|
||||
EMSuart::start(tx_mode_); // start UART
|
||||
#endif
|
||||
}
|
||||
|
||||
// returns true if OTA is uploading
|
||||
bool System::upload_status() {
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
return false;
|
||||
#else
|
||||
return upload_status_ || Update.isRunning();
|
||||
#endif
|
||||
}
|
||||
|
||||
void System::upload_status(bool in_progress) {
|
||||
@@ -372,10 +380,23 @@ int8_t System::wifi_quality() {
|
||||
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));
|
||||
// print users to console
|
||||
void System::show_users(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Users:"));
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
|
||||
for (User user : securitySettings.users) {
|
||||
shell.printfln(F(" username: %s, password: %s, is_admin: %s"), user.username.c_str(), user.password.c_str(), user.admin ? F("yes") : F("no"));
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Uptime: %s"), uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3).c_str());
|
||||
|
||||
#if defined(ESP8266)
|
||||
shell.printfln(F("Chip ID: 0x%08x"), ESP.getChipId());
|
||||
@@ -389,7 +410,7 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace());
|
||||
shell.printfln(F("Reset reason: %s"), ESP.getResetReason().c_str());
|
||||
shell.printfln(F("Reset info: %s"), ESP.getResetInfo().c_str());
|
||||
|
||||
shell.println();
|
||||
shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap());
|
||||
shell.printfln(F("Free mem: %d %%"), free_mem());
|
||||
shell.printfln(F("Maximum free block size: %lu bytes"), (unsigned long)ESP.getMaxFreeBlockSize());
|
||||
@@ -408,21 +429,19 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
switch (WiFi.status()) {
|
||||
case WL_IDLE_STATUS:
|
||||
shell.printfln(F("WiFi: idle"));
|
||||
shell.printfln(F("WiFi: Idle"));
|
||||
break;
|
||||
|
||||
case WL_NO_SSID_AVAIL:
|
||||
shell.printfln(F("WiFi: network not found"));
|
||||
shell.printfln(F("WiFi: Network not found"));
|
||||
break;
|
||||
|
||||
case WL_SCAN_COMPLETED:
|
||||
shell.printfln(F("WiFi: network scan complete"));
|
||||
shell.printfln(F("WiFi: Network scan complete"));
|
||||
break;
|
||||
|
||||
case WL_CONNECTED: {
|
||||
shell.printfln(F("WiFi: connected"));
|
||||
shell.println();
|
||||
|
||||
shell.printfln(F("WiFi: Connected"));
|
||||
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());
|
||||
@@ -432,27 +451,26 @@ void System::show_system(uuid::console::Shell & shell) {
|
||||
#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"));
|
||||
shell.printfln(F("WiFi: Connection failed"));
|
||||
break;
|
||||
|
||||
case WL_CONNECTION_LOST:
|
||||
shell.printfln(F("WiFi: connection lost"));
|
||||
shell.printfln(F("WiFi: Connection lost"));
|
||||
break;
|
||||
|
||||
case WL_DISCONNECTED:
|
||||
shell.printfln(F("WiFi: disconnected"));
|
||||
shell.printfln(F("WiFi: Disconnected"));
|
||||
break;
|
||||
|
||||
case WL_NO_SHIELD:
|
||||
default:
|
||||
shell.printfln(F("WiFi: unknown"));
|
||||
shell.printfln(F("WiFi: Unknown"));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -479,6 +497,13 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
restart();
|
||||
});
|
||||
|
||||
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))) {
|
||||
wifi_reconnect();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(format)},
|
||||
@@ -535,8 +560,9 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
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) {
|
||||
shell.println("Note, connection will be reset...");
|
||||
Console::loop();
|
||||
shell.println("The wifi connection will be reset...");
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.hostname = arguments.front().c_str();
|
||||
@@ -550,14 +576,11 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
shell.println("Note, connection will be reset...");
|
||||
Console::loop();
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.ssid = arguments.front().c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation([&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.ssid = arguments.front().c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
});
|
||||
shell.println("Use `wifi reconnect` to apply the new settings");
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
@@ -570,13 +593,12 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
[password1](Shell & shell, bool completed, const std::string & password2) {
|
||||
if (completed) {
|
||||
if (password1 == password2) {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->updateWithoutPropagation(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.password = password2.c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
shell.println(F("WiFi password updated"));
|
||||
});
|
||||
shell.println("Use `wifi reconnect` to apply the new settings");
|
||||
} else {
|
||||
shell.println(F("Passwords do not match"));
|
||||
}
|
||||
@@ -612,6 +634,11 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
flash_string_vector{F_(show), F_(mqtt)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(show), F_(users)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { System::show_users(shell); });
|
||||
|
||||
|
||||
// enter the context
|
||||
Console::enter_custom_context(shell, context);
|
||||
@@ -621,8 +648,7 @@ void System::console_commands(Shell & shell, unsigned int context) {
|
||||
void System::check_upgrade() {
|
||||
// check for v1.9. It uses SPIFFS and only on the ESP8266
|
||||
#if defined(ESP8266)
|
||||
|
||||
Serial.begin(115200); // TODO remove
|
||||
Serial.begin(115200); // TODO remove, just for debugging
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
|
||||
@@ -50,8 +50,8 @@ class System {
|
||||
static void mqtt_commands(const char * message);
|
||||
|
||||
static uint8_t free_mem();
|
||||
static void upload_status(bool in_progress);
|
||||
static bool upload_status();
|
||||
static void upload_status(bool in_progress);
|
||||
static bool upload_status();
|
||||
|
||||
void syslog_init();
|
||||
|
||||
@@ -97,6 +97,8 @@ class System {
|
||||
void system_check();
|
||||
|
||||
static void show_system(uuid::console::Shell & shell);
|
||||
static void show_users(uuid::console::Shell & shell);
|
||||
static void wifi_reconnect();
|
||||
static int8_t wifi_quality();
|
||||
|
||||
bool system_healthy_ = false;
|
||||
|
||||
@@ -42,8 +42,8 @@ uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time
|
||||
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_EMS_BUS_ID;
|
||||
uint8_t EMSbus::tx_waiting_ = Telegram::Operation::NONE;
|
||||
bool EMSbus::tx_active_ = false;
|
||||
uint8_t EMSbus::tx_mode_ = EMSESP_DEFAULT_TX_MODE;
|
||||
uint8_t EMSbus::tx_state_ = Telegram::Operation::NONE;
|
||||
|
||||
uuid::log::Logger EMSbus::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
@@ -82,9 +82,10 @@ Telegram::Telegram(const uint8_t operation,
|
||||
|
||||
// returns telegram's message data bytes in hex
|
||||
std::string Telegram::to_string() const {
|
||||
if (message_length == 0) {
|
||||
if (this->message_length == 0) {
|
||||
return read_flash_string(F("<empty>"));
|
||||
}
|
||||
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
uint8_t length = 0;
|
||||
data[0] = this->src ^ RxService::ems_mask();
|
||||
@@ -100,8 +101,7 @@ std::string Telegram::to_string() const {
|
||||
data[2] = this->type_id;
|
||||
length = 5;
|
||||
}
|
||||
}
|
||||
if (this->operation == Telegram::Operation::TX_WRITE) {
|
||||
} else if (this->operation == Telegram::Operation::TX_WRITE) {
|
||||
data[1] = this->dest;
|
||||
if (this->type_id > 0xFF) {
|
||||
data[2] = 0xFF;
|
||||
@@ -115,7 +115,12 @@ std::string Telegram::to_string() const {
|
||||
for (uint8_t i = 0; i < this->message_length; i++) {
|
||||
data[length++] = this->message_data[i];
|
||||
}
|
||||
} else {
|
||||
for (uint8_t i = 0; i < this->message_length; i++) {
|
||||
data[length++] = this->message_data[i];
|
||||
}
|
||||
}
|
||||
|
||||
return Helpers::data_to_hex(data, length);
|
||||
}
|
||||
|
||||
@@ -160,17 +165,18 @@ void RxService::add(uint8_t * data, uint8_t length) {
|
||||
|
||||
// validate the CRC
|
||||
uint8_t crc = calculate_crc(data, length - 1);
|
||||
|
||||
if ((data[length - 1] != crc) && (EMSESP::watch() != EMSESP::Watch::WATCH_OFF)) {
|
||||
LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
if (data[length - 1] != crc) {
|
||||
increment_telegram_error_count();
|
||||
if (EMSESP::watch() != EMSESP::Watch::WATCH_OFF) {
|
||||
LOG_ERROR(F("Rx: %s %s(CRC %02X != %02X)%s"), Helpers::data_to_hex(data, length).c_str(), COLOR_RED, data[length - 1], crc, COLOR_RESET);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// since it's a valid telegram, work out the ems mask
|
||||
// we check the 1st byte, which assumed is the src ID and see if the MSB (8th bit) is set
|
||||
// this is used to identify if the protocol should be Junkers/HT3 or Buderus
|
||||
// this only happens once with the first rx telegram is processed
|
||||
// this only happens once with the first valid rx telegram is processed
|
||||
if (ems_mask() == EMS_MASK_UNSET) {
|
||||
ems_mask(data[0]);
|
||||
}
|
||||
@@ -251,48 +257,51 @@ void TxService::flush_tx_queue() {
|
||||
|
||||
// start and initialize Tx
|
||||
void TxService::start() {
|
||||
// grab the bus ID
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { ems_bus_id(settings.ems_bus_id); });
|
||||
// grab the bus ID and tx_mode
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
ems_bus_id(settings.ems_bus_id);
|
||||
tx_mode(settings.tx_mode);
|
||||
});
|
||||
|
||||
// reset counters
|
||||
telegram_read_count(0);
|
||||
telegram_write_count(0);
|
||||
telegram_fail_count(0);
|
||||
|
||||
// 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()
|
||||
read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
}
|
||||
|
||||
// Tx loop
|
||||
// here we check if the Tx is not full and report an error
|
||||
void TxService::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if ((uuid::get_uptime() - last_tx_check_) > TX_LOOP_WAIT) {
|
||||
last_tx_check_ = uuid::get_uptime();
|
||||
if (!tx_active() && (EMSbus::bus_connected())) {
|
||||
LOG_ERROR(F("Tx is not active. Please check settings and the circuit connection."));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// sends a 1 byte poll which is our own device ID
|
||||
void TxService::send_poll() {
|
||||
//LOG_DEBUG(F("Ack %02X"),ems_bus_id() ^ ems_mask());
|
||||
EMSuart::send_poll(ems_bus_id() ^ ems_mask());
|
||||
if (tx_mode()) {
|
||||
EMSuart::send_poll(ems_bus_id() ^ ems_mask());
|
||||
}
|
||||
}
|
||||
|
||||
// Process the next telegram on the Tx queue
|
||||
// This is sent when we receieve a poll request
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if there's nothing in the queue to transmit, send back a poll and quit
|
||||
// unless tx_mode is 0
|
||||
if (tx_telegrams_.empty()) {
|
||||
send_poll();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're in read-only mode (tx_mode 0) forget the Tx call
|
||||
if (tx_mode() == 0) {
|
||||
tx_telegrams_.pop_front();
|
||||
return;
|
||||
}
|
||||
|
||||
// send next telegram in the queue (which is actually a list!)
|
||||
send_telegram(tx_telegrams_.front());
|
||||
|
||||
@@ -380,12 +389,12 @@ void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
|
||||
if (status == EMS_TX_STATUS_ERR) {
|
||||
LOG_ERROR(F("Failed to transmit Tx via UART."));
|
||||
increment_telegram_fail_count(); // another Tx fail
|
||||
tx_waiting(Telegram::Operation::NONE); // nothing send, tx not in wait state
|
||||
increment_telegram_fail_count(); // another Tx fail
|
||||
tx_state(Telegram::Operation::NONE); // nothing send, tx not in wait state
|
||||
return;
|
||||
}
|
||||
|
||||
tx_waiting(telegram->operation); // tx now in a wait state
|
||||
tx_state(telegram->operation); // tx now in a wait state
|
||||
}
|
||||
|
||||
// send an array of bytes as a telegram
|
||||
@@ -399,7 +408,7 @@ void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
|
||||
}
|
||||
telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC
|
||||
|
||||
tx_waiting(Telegram::Operation::NONE); // no post validation needed
|
||||
tx_state(Telegram::Operation::NONE); // no post validation needed
|
||||
|
||||
// send the telegram to the UART Tx
|
||||
uint16_t status = EMSuart::transmit(telegram_raw, length);
|
||||
|
||||
@@ -126,11 +126,38 @@ class EMSbus {
|
||||
public:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
static constexpr uint8_t EMS_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
|
||||
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_MASK_UNSET = 0xFF; // EMS bus type (budrus/junkers) hasn't been detected yet
|
||||
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 constexpr uint8_t EMS_TX_ERROR_LIMIT = 10; // % limit of failed Tx read/write attempts before showing a warning
|
||||
static bool is_ht3() {
|
||||
return (ems_mask_ == EMS_MASK_HT3);
|
||||
}
|
||||
|
||||
static uint8_t ems_mask() {
|
||||
return ems_mask_;
|
||||
}
|
||||
|
||||
static void ems_mask(uint8_t ems_mask) {
|
||||
ems_mask_ = ems_mask & 0x80; // only keep the MSB (8th bit)
|
||||
}
|
||||
|
||||
static uint8_t tx_mode() {
|
||||
return tx_mode_;
|
||||
}
|
||||
|
||||
static void tx_mode(uint8_t tx_mode) {
|
||||
tx_mode_ = tx_mode;
|
||||
}
|
||||
|
||||
static uint8_t ems_bus_id() {
|
||||
return ems_bus_id_;
|
||||
}
|
||||
|
||||
static void ems_bus_id(uint8_t ems_bus_id) {
|
||||
ems_bus_id_ = ems_bus_id;
|
||||
}
|
||||
|
||||
static bool bus_connected() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
@@ -143,53 +170,17 @@ class EMSbus {
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool is_ht3() {
|
||||
return (ems_mask_ == EMS_MASK_HT3);
|
||||
}
|
||||
|
||||
static uint8_t protocol() {
|
||||
return ems_mask_;
|
||||
}
|
||||
|
||||
static uint8_t ems_mask() {
|
||||
return ems_mask_;
|
||||
}
|
||||
|
||||
static void ems_mask(uint8_t ems_mask) {
|
||||
ems_mask_ = ems_mask & 0x80; // only keep the MSB (8th bit)
|
||||
}
|
||||
|
||||
static uint8_t ems_bus_id() {
|
||||
return ems_bus_id_;
|
||||
}
|
||||
|
||||
static void ems_bus_id(uint8_t ems_bus_id) {
|
||||
ems_bus_id_ = ems_bus_id;
|
||||
}
|
||||
|
||||
// sets the flag for EMS bus connected
|
||||
static void last_bus_activity(uint32_t timestamp) {
|
||||
last_bus_activity_ = timestamp;
|
||||
bus_connected_ = true;
|
||||
}
|
||||
|
||||
static bool tx_active() {
|
||||
return tx_active_;
|
||||
static uint8_t tx_state() {
|
||||
return tx_state_;
|
||||
}
|
||||
static void tx_active(bool tx_active) {
|
||||
tx_active_ = tx_active;
|
||||
}
|
||||
|
||||
static uint8_t tx_waiting() {
|
||||
return tx_waiting_;
|
||||
}
|
||||
static void tx_waiting(uint8_t tx_waiting) {
|
||||
tx_waiting_ = tx_waiting;
|
||||
|
||||
// if NONE, then it's been reset which means we have an active Tx
|
||||
if ((tx_waiting == Telegram::Operation::NONE) && !(tx_active_)) {
|
||||
tx_active_ = true;
|
||||
}
|
||||
static void tx_state(uint8_t tx_state) {
|
||||
tx_state_ = tx_state;
|
||||
}
|
||||
|
||||
static uint8_t calculate_crc(const uint8_t * data, const uint8_t length);
|
||||
@@ -201,8 +192,8 @@ class EMSbus {
|
||||
static bool bus_connected_; // start assuming the bus hasn't been connected
|
||||
static uint8_t ems_mask_; // unset=0xFF, buderus=0x00, junkers/ht3=0x80
|
||||
static uint8_t ems_bus_id_; // the bus id, which configurable and stored in settings
|
||||
static uint8_t tx_waiting_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
|
||||
static bool tx_active_; // is true is we have a working Tx connection
|
||||
static uint8_t tx_mode_; // local copy of the tx mode
|
||||
static uint8_t tx_state_; // state of the Tx line (NONE or waiting on a TX_READ or TX_WRITE)
|
||||
};
|
||||
|
||||
class RxService : public EMSbus {
|
||||
@@ -269,7 +260,6 @@ class TxService : public EMSbus {
|
||||
~TxService() = default;
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
void send();
|
||||
|
||||
void add(const uint8_t operation,
|
||||
@@ -364,8 +354,7 @@ class TxService : public EMSbus {
|
||||
private:
|
||||
uint8_t tx_telegram_id_ = 0; // queue counter
|
||||
|
||||
static constexpr uint32_t TX_LOOP_WAIT = 10000; // when to check if Tx is up and running (10 sec)
|
||||
uint32_t last_tx_check_ = 0;
|
||||
uint32_t last_tx_check_ = 0;
|
||||
|
||||
std::deque<QueuedTxTelegram> tx_telegrams_;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
|
||||
#include "test.h"
|
||||
|
||||
@@ -692,3 +693,5 @@ void Test::dummy_mqtt_commands(const char * message) {
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
|
||||
#ifndef EMSESP_TEST_H
|
||||
#define EMSESP_TEST_H
|
||||
|
||||
@@ -51,3 +53,5 @@ class Test {
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,8 +34,8 @@ static hw_timer_t * timer = NULL;
|
||||
bool drop_next_rx = true;
|
||||
uint8_t tx_mode_ = 0xFF;
|
||||
uint8_t emsTxBuf[EMS_MAXBUFFERSIZE];
|
||||
uint8_t emsTxBufIdx;
|
||||
uint8_t emsTxBufLen;
|
||||
uint8_t emsTxBufIdx = 0;
|
||||
uint8_t emsTxBufLen = 0;
|
||||
uint32_t emsTxWait;
|
||||
|
||||
/*
|
||||
@@ -88,12 +88,15 @@ void IRAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
portENTER_CRITICAL(&mux);
|
||||
if (emsTxBufIdx < emsTxBufLen) {
|
||||
EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx];
|
||||
if (emsTxBufIdx == 1) {
|
||||
timerAlarmWrite(timer, emsTxWait, true);
|
||||
}
|
||||
} else if (emsTxBufIdx == emsTxBufLen) {
|
||||
EMS_UART.conf0.txd_inv = 1;
|
||||
timerAlarmWrite(timer, EMSUART_TX_WAIT_BRK, true);
|
||||
timerAlarmWrite(timer, EMSUART_TX_BRK_TIMER, true);
|
||||
} else if (emsTxBufIdx == emsTxBufLen + 1) {
|
||||
// delayMicroseconds(EMSUART_TX_WAIT_BRK);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
emsTxBufLen = 0;
|
||||
timerAlarmDisable(timer);
|
||||
}
|
||||
emsTxBufIdx++;
|
||||
@@ -122,28 +125,31 @@ void EMSuart::start(const uint8_t tx_mode) {
|
||||
uart_set_pin(EMSUART_UART, EMSUART_TXPIN, EMSUART_RXPIN, UART_PIN_NO_CHANGE, UART_PIN_NO_CHANGE);
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
EMS_UART.int_clr.val = 0xFFFFFFFF; // clear all intr. flags
|
||||
EMS_UART.idle_conf.tx_brk_num = 11; // breaklength 11 bit
|
||||
EMS_UART.idle_conf.tx_brk_num = 10; // breaklength 10 bit
|
||||
EMS_UART.idle_conf.rx_idle_thrhd = 256;
|
||||
drop_next_rx = true;
|
||||
buf_handle = xRingbufferCreate(128, RINGBUF_TYPE_NOSPLIT);
|
||||
uart_isr_register(EMSUART_UART, emsuart_rx_intr_handler, NULL, ESP_INTR_FLAG_IRAM, NULL);
|
||||
xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
|
||||
timer = timerBegin(1, 80, true); // timer prescale to 1 µs, countup
|
||||
timer = timerBegin(0, 80, true); // timer prescale to 1 us, countup
|
||||
timerAttachInterrupt(timer, &emsuart_tx_timer_intr_handler, true); // Timer with edge interrupt
|
||||
restart();
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop, disables interrupt
|
||||
* Stop, disable interrupt
|
||||
*/
|
||||
void EMSuart::stop() {
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
// timerAlarmDisable(timer);
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
EMS_UART.conf0.txd_inv = 0; // stop break
|
||||
if (emsTxBufLen > 0) {
|
||||
timerAlarmDisable(timer);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Restart Interrupt
|
||||
* Restart uart and make mode dependent configs.
|
||||
*/
|
||||
void EMSuart::restart() {
|
||||
if (EMS_UART.int_raw.brk_det) { // we received a break in the meantime
|
||||
@@ -153,7 +159,11 @@ void EMSuart::restart() {
|
||||
EMS_UART.int_ena.brk_det = 1; // activate only break
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 0;
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
|
||||
if (tx_mode_ > 100) {
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ - 90);
|
||||
} else {
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
|
||||
}
|
||||
if(tx_mode_ == EMS_TXMODE_NEW) {
|
||||
EMS_UART.conf0.txd_brk = 1;
|
||||
} else {
|
||||
@@ -161,44 +171,11 @@ void EMSuart::restart() {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 11-bit break by inverting the tx-port
|
||||
*/
|
||||
void EMSuart::tx_brk() {
|
||||
EMS_UART.conf0.txd_inv = 1;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BRK);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
*/
|
||||
void EMSuart::send_poll(const uint8_t data) {
|
||||
if (tx_mode_ > 5) { // timer controlled modes
|
||||
emsTxBuf[0] = data;
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 1;
|
||||
timerAlarmWrite(timer, emsTxWait, true); // start timer with autoreload
|
||||
timerAlarmEnable(timer); // first interrupt comes immediately
|
||||
} else if (tx_mode_ == EMS_TXMODE_DEFAULT) {
|
||||
volatile uint8_t _usrxc = EMS_UART.status.rxfifo_cnt;
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
while ((EMS_UART.status.rxfifo_cnt == _usrxc) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT);
|
||||
}
|
||||
tx_brk();
|
||||
} else if (tx_mode_ == EMS_TXMODE_EMSPLUS) {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
tx_brk();
|
||||
} else if (tx_mode_ == EMS_TXMODE_HT3) {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
tx_brk();
|
||||
} else {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
}
|
||||
transmit(&data, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -217,7 +194,11 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
}
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = len;
|
||||
timerAlarmWrite(timer, emsTxWait, true); // start with autoreload
|
||||
if (tx_mode_ > 100 && len > 1) {
|
||||
timerAlarmWrite(timer, EMSUART_TX_WAIT_REPLY, true);
|
||||
} else {
|
||||
timerAlarmWrite(timer, emsTxWait, true); // start with autoreload
|
||||
}
|
||||
timerAlarmEnable(timer);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
@@ -234,7 +215,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
}
|
||||
tx_brk();
|
||||
EMS_UART.conf0.txd_inv = 1; // send <brk>
|
||||
delayMicroseconds(EMSUART_TX_BRK_PLUS);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -243,11 +226,13 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
}
|
||||
tx_brk();
|
||||
EMS_UART.conf0.txd_inv = 1; // send <brk>
|
||||
delayMicroseconds(EMSUART_TX_BRK_HT3);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// mode 1
|
||||
// mode 1: wait for echo after each byte
|
||||
// flush fifos -- not supported in ESP32 uart #2!
|
||||
// EMS_UART.conf0.rxfifo_rst = 1;
|
||||
// EMS_UART.conf0.txfifo_rst = 1;
|
||||
@@ -259,7 +244,9 @@ uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
}
|
||||
tx_brk();
|
||||
EMS_UART.conf0.txd_inv = 1;
|
||||
delayMicroseconds(EMSUART_TX_BRK_EMS);
|
||||
EMS_UART.conf0.txd_inv = 0;
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,19 +46,25 @@
|
||||
#define EMS_TXMODE_NEW 4 // for michael's testing
|
||||
|
||||
// LEGACY
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
|
||||
// Timer controlled modes
|
||||
#define EMSUART_TX_BRK_TIMER (EMSUART_TX_BIT_TIME * 10 + 28) // 10.25 bit times
|
||||
#define EMSUART_TX_WAIT_REPLY 100000 // delay 100ms after first byte
|
||||
|
||||
// EMS 1.0
|
||||
#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
|
||||
#define EMSUART_TX_TIMEOUT (20 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT)
|
||||
#define EMSUART_TX_BRK_EMS (EMSUART_TX_BIT_TIME * 10)
|
||||
|
||||
// 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.
|
||||
// since we use a faster processor the lag is negligible
|
||||
#define EMSUART_TX_WAIT_HT3 (EMSUART_TX_BIT_TIME * 17) // 1768
|
||||
#define EMSUART_TX_BRK_HT3 (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
// EMS+ - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) and delay of another Bytetime.
|
||||
#define EMSUART_TX_WAIT_PLUS (EMSUART_TX_BIT_TIME * 20) // 2080
|
||||
#define EMSUART_TX_BRK_PLUS (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
|
||||
// customize the GPIO pins for RX and TX here
|
||||
@@ -90,10 +96,9 @@ class EMSuart {
|
||||
static void emsuart_recvTask(void * para);
|
||||
static void IRAM_ATTR emsuart_rx_intr_handler(void * para);
|
||||
static void IRAM_ATTR emsuart_tx_timer_intr_handler();
|
||||
static void tx_brk();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -95,10 +95,10 @@ void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
timer1_write(emsTxWait);
|
||||
} else if (emsTxBufIdx == emsTxBufLen) {
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
|
||||
timer1_write(EMSUART_TX_WAIT_BRK * 5);
|
||||
timer1_write(EMSUART_TX_BRK_TIMER);
|
||||
} else {
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset <BRK>
|
||||
sending_ = false;
|
||||
sending_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +114,6 @@ void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() {
|
||||
* init UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode + 10); // bittimes wait between bytes
|
||||
if (tx_mode_ != 0xFF) { // it's a restart no need to configure uart
|
||||
tx_mode_ = tx_mode;
|
||||
restart();
|
||||
@@ -129,7 +128,6 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
}
|
||||
pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer
|
||||
|
||||
ETS_UART_INTR_DISABLE();
|
||||
ETS_UART_INTR_ATTACH(nullptr, nullptr);
|
||||
|
||||
// pin settings
|
||||
@@ -149,22 +147,14 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127)
|
||||
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
|
||||
//
|
||||
// change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
|
||||
// Otherwise, we're only noticed by UCTOT or RxBRK!
|
||||
// change: don't care, we do not use these interrupts
|
||||
USC1(EMSUART_UART) = 0; // reset config first
|
||||
USC1(EMSUART_UART) = 0; // reset config
|
||||
// USC1(EMSUART_UART) = (0x7F << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts
|
||||
|
||||
// set interrupts for triggers
|
||||
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
|
||||
USIE(EMSUART_UART) = 0; // disable all interrupts
|
||||
|
||||
// enable rx break, fifo full and timeout.
|
||||
// but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes
|
||||
// change: we don't care about Rx Timeout - it may lead to wrong readouts
|
||||
// change:we don't care about Fifo full and read only on break-detect
|
||||
USIE(EMSUART_UART) = (1 << UIBD) | (0 << UIFF) | (0 << UITO);
|
||||
|
||||
// set up interrupt callbacks for Rx
|
||||
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
|
||||
|
||||
@@ -175,13 +165,12 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
system_uart_swap();
|
||||
|
||||
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
|
||||
// ETS_UART_INTR_ENABLE();
|
||||
drop_next_rx = true;
|
||||
|
||||
// for sending with large delay in EMS+ mode we use a timer interrupt
|
||||
timer1_attachInterrupt(emsuart_tx_timer_intr_handler); // Add ISR Function
|
||||
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE); // 5 MHz timer
|
||||
USIE(EMSUART_UART) = (1 << UIBD);
|
||||
timer1_attachInterrupt(emsuart_tx_timer_intr_handler);
|
||||
|
||||
restart();
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -190,7 +179,9 @@ void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::stop() {
|
||||
USIE(EMSUART_UART) = 0;
|
||||
// timer1_disable();
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear BRK bit
|
||||
timer1_disable();
|
||||
sending_ = false;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -198,67 +189,25 @@ void ICACHE_FLASH_ATTR EMSuart::stop() {
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::restart() {
|
||||
if (USIR(EMSUART_UART) & ((1 << UIBD))) {
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the <brk> detect interrupt
|
||||
drop_next_rx = true;
|
||||
}
|
||||
if (tx_mode_ > 100) {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode_ - 90);
|
||||
} else {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode_ + 10); // bittimes wait to next bytes
|
||||
}
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 0;
|
||||
// timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
|
||||
USIE(EMSUART_UART) = (1 << UIBD);
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a BRK signal
|
||||
* Which is a 11-bit set of zero's (11 cycles)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::tx_brk() {
|
||||
// make sure Tx FIFO is empty
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xFF)) {
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set bit
|
||||
// also for EMS+ there is no need to wait longer, we are finished and can free the bus.
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BRK); // 1144
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear BRK bit
|
||||
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
|
||||
USIE(EMSUART_UART) = (1 << UIBD); // enable <brk> interrupt
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
* It's a bit dirty. there is no special wait logic per tx_mode type, fifo flushes or error checking
|
||||
*/
|
||||
void EMSuart::send_poll(uint8_t data) {
|
||||
// reset tx-brk, just in case it is accidentally set
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
sending_ = true;
|
||||
|
||||
if (tx_mode_ >= 5) { // timer controlled modes
|
||||
emsTxBuf[0] = data;
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 1;
|
||||
timer1_write(emsTxWait);
|
||||
} else if (tx_mode_ == EMS_TXMODE_NEW) { // hardware controlled modes
|
||||
USF(EMSUART_UART) = data;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK);
|
||||
} else if (tx_mode_ == EMS_TXMODE_HT3) {
|
||||
USF(EMSUART_UART) = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
} else if (tx_mode_ == EMS_TXMODE_EMSPLUS) {
|
||||
USF(EMSUART_UART) = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
} else {
|
||||
// tx_mode 1
|
||||
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
|
||||
USF(EMSUART_UART) = data;
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
}
|
||||
void ICACHE_FLASH_ATTR EMSuart::send_poll(uint8_t data) {
|
||||
transmit(&data, 1);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -270,19 +219,21 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
if (len == 0 || len >= EMS_MAXBUFFERSIZE) {
|
||||
return EMS_TX_STATUS_ERR; // nothing or to much to send
|
||||
}
|
||||
// reset tx-brk, just in case it is accidentally set
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
sending_ = true;
|
||||
|
||||
// timer controlled modes with extra delay
|
||||
if (tx_mode_ >= 5) {
|
||||
sending_ = true;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
emsTxBuf[i] = buf[i];
|
||||
}
|
||||
USF(EMSUART_UART) = buf[0]; // send first byte
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = len;
|
||||
timer1_write(emsTxWait);
|
||||
if (tx_mode_ > 100 && len > 1) {
|
||||
timer1_write(EMSUART_TX_WAIT_REPLY); // large delay after first byte
|
||||
} else {
|
||||
timer1_write(emsTxWait);
|
||||
}
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -291,7 +242,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end, clear by interrupt
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -301,8 +252,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS); // 2070
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set break
|
||||
delayMicroseconds(EMSUART_TX_BRK_PLUS);
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -316,8 +268,9 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
// wait until bits are sent on wire
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
sending_ = false;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set break bit
|
||||
delayMicroseconds(EMSUART_TX_BRK_HT3);
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
@@ -346,9 +299,7 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
*
|
||||
*/
|
||||
|
||||
// disable rx interrupt
|
||||
// clear Rx status register, resetting the Rx FIFO and flush it
|
||||
// ETS_UART_INTR_DISABLE();
|
||||
emsuart_flush_fifos();
|
||||
|
||||
// send the bytes along the serial line
|
||||
@@ -361,21 +312,12 @@ uint16_t ICACHE_FLASH_ATTR EMSuart::transmit(uint8_t * buf, uint8_t len) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
}
|
||||
|
||||
// we got the whole telegram in the Rx buffer
|
||||
// on Rx-BRK (bus collision), we simply enable Rx and leave it
|
||||
// otherwise we send the final Tx-BRK
|
||||
// worst case, we'll see an additional Rx-BRK...
|
||||
// neither bus collision nor timeout - send terminating BRK signal
|
||||
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
|
||||
// no bus collision - send terminating BRK signal
|
||||
tx_brk();
|
||||
}
|
||||
// ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
|
||||
sending_ = false;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // snd break
|
||||
delayMicroseconds(EMSUART_TX_BRK_EMS);
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK);
|
||||
return EMS_TX_STATUS_OK; // send the Tx ok status back
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -40,20 +40,26 @@
|
||||
#define EMS_TXMODE_NEW 4 // for michael's testing
|
||||
|
||||
// LEGACY
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
#define EMSUART_TX_WAIT_BRK (EMSUART_TX_BIT_TIME * 11) // 1144
|
||||
#define EMSUART_TX_BIT_TIME 104 // bit time @9600 baud
|
||||
|
||||
// TIMER modes
|
||||
#define EMSUART_TX_BRK_TIMER (EMSUART_TX_BIT_TIME * 52) // > 10 bittimes for timer modes
|
||||
#define EMSUART_TX_WAIT_REPLY 500000 // delay 100ms after first byte
|
||||
|
||||
// EMS 1.0
|
||||
#define EMSUART_TX_BUSY_WAIT (EMSUART_TX_BIT_TIME / 8) // 13
|
||||
// #define EMSUART_TX_TIMEOUT (22 * EMSUART_TX_BIT_TIME / EMSUART_TX_BUSY_WAIT) // 176
|
||||
#define EMSUART_TX_TIMEOUT (32 * 8) // 256 for tx_mode 1 - see https://github.com/proddy/EMS-ESP/issues/398#issuecomment-645886277
|
||||
// #define EMSUART_TX_TIMEOUT (32 * 8) // 256 for tx_mode 1 - see https://github.com/proddy/EMS-ESP/issues/398#issuecomment-645886277
|
||||
#define EMSUART_TX_TIMEOUT (220 * 8) // 1760 as in v1.9 (180 ms)
|
||||
#define EMSUART_TX_BRK_EMS (EMSUART_TX_BIT_TIME * 10)
|
||||
|
||||
// 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.
|
||||
// since we use a faster processor the lag is negligible
|
||||
#define EMSUART_TX_WAIT_HT3 (EMSUART_TX_BIT_TIME * 17) // 1768
|
||||
#define EMSUART_TX_BRK_HT3 (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
// EMS+ - Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit) and delay of another Bytetime.
|
||||
#define EMSUART_TX_WAIT_PLUS (EMSUART_TX_BIT_TIME * 20) // 2080
|
||||
#define EMSUART_TX_BRK_PLUS (EMSUART_TX_BIT_TIME * 11)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
@@ -83,7 +89,6 @@ class EMSuart {
|
||||
static void ICACHE_RAM_ATTR emsuart_rx_intr_handler(void * para);
|
||||
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events);
|
||||
static void ICACHE_FLASH_ATTR emsuart_flush_fifos();
|
||||
static void ICACHE_FLASH_ATTR tx_brk();
|
||||
static void ICACHE_RAM_ATTR emsuart_tx_timer_intr_handler();
|
||||
static bool sending_;
|
||||
};
|
||||
@@ -91,4 +96,4 @@ class EMSuart {
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -1 +1 @@
|
||||
#define EMSESP_APP_VERSION "2.0.0b7"
|
||||
#define EMSESP_APP_VERSION "2.0.0b11"
|
||||
|
||||
Reference in New Issue
Block a user