mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-09 09:19:51 +03:00
initial commit
This commit is contained in:
93
src/EMSESPSettingsService.cpp
Normal file
93
src/EMSESPSettingsService.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "EMSESPSettingsService.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
EMSESPSettingsService::EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager)
|
||||
: _httpEndpoint(EMSESPSettings::read, EMSESPSettings::update, this, server, EMSESP_SETTINGS_SERVICE_PATH, securityManager)
|
||||
, _fsPersistence(EMSESPSettings::read, EMSESPSettings::update, this, fs, EMSESP_SETTINGS_FILE) {
|
||||
}
|
||||
|
||||
void EMSESPSettings::read(EMSESPSettings & settings, JsonObject & root) {
|
||||
root["tx_mode"] = settings.tx_mode;
|
||||
root["ems_bus_id"] = settings.ems_bus_id;
|
||||
root["system_heartbeat"] = settings.system_heartbeat;
|
||||
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["publish_time"] = settings.publish_time;
|
||||
root["mqtt_format"] = settings.mqtt_format;
|
||||
root["mqtt_qos"] = settings.mqtt_qos;
|
||||
}
|
||||
|
||||
StateUpdateResult EMSESPSettings::update(JsonObject & root, EMSESPSettings & settings) {
|
||||
EMSESPSettings newSettings = {};
|
||||
newSettings.tx_mode = root["tx_mode"] | EMSESP_DEFAULT_TX_MODE;
|
||||
newSettings.ems_bus_id = root["ems_bus_id"] | EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
newSettings.system_heartbeat = root["system_heartbeat"] | EMSESP_DEFAULT_SYSTEM_HEARTBEAT;
|
||||
newSettings.syslog_level = root["syslog_level"] | EMSESP_DEFAULT_SYSLOG_LEVEL;
|
||||
newSettings.syslog_mark_interval = root["syslog_mark_interval"] | EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL;
|
||||
newSettings.syslog_host = root["syslog_host"] | EMSESP_DEFAULT_SYSLOG_HOST;
|
||||
newSettings.master_thermostat = root["master_thermostat"] | EMSESP_DEFAULT_MASTER_THERMOSTAT;
|
||||
newSettings.shower_timer = root["shower_timer"] | EMSESP_DEFAULT_SHOWER_TIMER;
|
||||
newSettings.shower_alert = root["shower_alert"] | EMSESP_DEFAULT_SHOWER_ALERT;
|
||||
newSettings.publish_time = root["publish_time"] | EMSESP_DEFAULT_PUBLISH_TIME;
|
||||
newSettings.mqtt_format = root["mqtt_format"] | EMSESP_DEFAULT_MQTT_FORMAT;
|
||||
newSettings.mqtt_qos = root["mqtt_qos"] | EMSESP_DEFAULT_MQTT_QOS;
|
||||
|
||||
bool changed = false;
|
||||
|
||||
// changing master thermostat, bus ID, mqtt_format requires a reboot
|
||||
if ((newSettings.master_thermostat != settings.master_thermostat) || (newSettings.ems_bus_id != settings.ems_bus_id)
|
||||
|| (newSettings.mqtt_format != settings.mqtt_format)) {
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.system_heartbeat != settings.system_heartbeat) {
|
||||
EMSESP::system_.set_heartbeat(newSettings.system_heartbeat);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.tx_mode != settings.tx_mode) {
|
||||
EMSESP::reset_tx(newSettings.tx_mode); // reset counters
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.mqtt_qos != settings.mqtt_qos) {
|
||||
EMSESP::mqtt_.set_qos(newSettings.mqtt_qos);
|
||||
EMSESP::mqtt_.disconnect(); // force a disconnect & reconnect
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (newSettings.publish_time != settings.publish_time) {
|
||||
EMSESP::mqtt_.set_publish_time(newSettings.publish_time);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ((newSettings.shower_timer != settings.shower_timer) || (newSettings.shower_alert != settings.shower_alert)) {
|
||||
EMSESP::shower_.start();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if ((newSettings.syslog_level != settings.syslog_level) || (newSettings.syslog_mark_interval != settings.syslog_mark_interval)
|
||||
|| !newSettings.syslog_host.equals(settings.syslog_host)) {
|
||||
EMSESP::system_.syslog_init();
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (changed) {
|
||||
settings = newSettings;
|
||||
return StateUpdateResult::CHANGED;
|
||||
}
|
||||
|
||||
return StateUpdateResult::UNCHANGED;
|
||||
}
|
||||
|
||||
void EMSESPSettingsService::begin() {
|
||||
_fsPersistence.readFromFS();
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
64
src/EMSESPSettingsService.h
Normal file
64
src/EMSESPSettingsService.h
Normal file
@@ -0,0 +1,64 @@
|
||||
#ifndef EMSESPSettingsConfig_h
|
||||
#define EMSESPSettingsConfig_h
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <FSPersistence.h>
|
||||
|
||||
#define EMSESP_SETTINGS_FILE "/config/emsespSettings.json"
|
||||
#define EMSESP_SETTINGS_SERVICE_PATH "/rest/emsespSettings"
|
||||
|
||||
#define EMSESP_DEFAULT_TX_MODE 1
|
||||
#define EMSESP_DEFAULT_EMS_BUS_ID 0x0B // service key
|
||||
#define EMSESP_DEFAULT_SYSTEM_HEARTBEAT true
|
||||
#define EMSESP_DEFAULT_SYSLOG_LEVEL -1
|
||||
#define EMSESP_DEFAULT_SYSLOG_MARK_INTERVAL 0
|
||||
#define EMSESP_DEFAULT_SYSLOG_HOST ""
|
||||
#define EMSESP_DEFAULT_MASTER_THERMOSTAT 0 // not set
|
||||
#define EMSESP_DEFAULT_SHOWER_TIMER false
|
||||
#define EMSESP_DEFAULT_SHOWER_ALERT false
|
||||
#define EMSESP_DEFAULT_MQTT_FORMAT 2 // nested
|
||||
#define EMSESP_DEFAULT_MQTT_QOS 0
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#define EMSESP_DEFAULT_PUBLISH_TIME 10
|
||||
#else
|
||||
#define EMSESP_DEFAULT_PUBLISH_TIME 0
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
enum MQTT_format : uint8_t { SINGLE = 1, NESTED, HA, CUSTOM };
|
||||
|
||||
class EMSESPSettings {
|
||||
public:
|
||||
uint8_t tx_mode;
|
||||
uint8_t ems_bus_id;
|
||||
bool system_heartbeat;
|
||||
int8_t syslog_level; // uuid::log::Level
|
||||
uint32_t syslog_mark_interval;
|
||||
String syslog_host;
|
||||
uint8_t master_thermostat;
|
||||
bool shower_timer;
|
||||
bool shower_alert;
|
||||
uint16_t publish_time; // seconds
|
||||
uint8_t mqtt_format; // 1=single, 2=nested, 3=ha, 4=custom
|
||||
uint8_t mqtt_qos;
|
||||
|
||||
static void read(EMSESPSettings & settings, JsonObject & root);
|
||||
static StateUpdateResult update(JsonObject & root, EMSESPSettings & settings);
|
||||
};
|
||||
|
||||
class EMSESPSettingsService : public StatefulService<EMSESPSettings> {
|
||||
public:
|
||||
EMSESPSettingsService(AsyncWebServer * server, FS * fs, SecurityManager * securityManager);
|
||||
|
||||
void begin();
|
||||
|
||||
private:
|
||||
HttpEndpoint<EMSESPSettings> _httpEndpoint;
|
||||
FSPersistence<EMSESPSettings> _fsPersistence;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
44
src/EMSESPStatusService.cpp
Normal file
44
src/EMSESPStatusService.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "EMSESPStatusService.h"
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
EMSESPStatusService::EMSESPStatusService(AsyncWebServer * server, SecurityManager * securityManager, AsyncMqttClient * mqttClient)
|
||||
: _mqttClient(mqttClient) {
|
||||
_mqttClient->onConnect(std::bind(&EMSESPStatusService::init_mqtt, this)); // configure MQTT callback
|
||||
|
||||
server->on(EMSESP_STATUS_SERVICE_PATH,
|
||||
HTTP_GET,
|
||||
securityManager->wrapRequest(std::bind(&EMSESPStatusService::emsespStatusService, this, std::placeholders::_1),
|
||||
AuthenticationPredicates::IS_AUTHENTICATED));
|
||||
}
|
||||
|
||||
void EMSESPStatusService::emsespStatusService(AsyncWebServerRequest * request) {
|
||||
AsyncJsonResponse * response = new AsyncJsonResponse(false, MAX_EMSESP_STATUS_SIZE);
|
||||
JsonObject root = response->getRoot();
|
||||
|
||||
root["version"] = EMSESP_APP_VERSION;
|
||||
|
||||
root["status"] = EMSESP::bus_status(); // 0, 1 or 2
|
||||
root["rx_received"] = EMSESP::rxservice_.telegram_count();
|
||||
root["tx_sent"] = EMSESP::txservice_.telegram_read_count() + EMSESP::txservice_.telegram_write_count();
|
||||
root["crc_errors"] = EMSESP::rxservice_.telegram_error_count();
|
||||
root["tx_errors"] = EMSESP::txservice_.telegram_fail_count();
|
||||
root["mqtt_fails"] = Mqtt::publish_fails();
|
||||
root["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
||||
root["free_mem"] = System::free_mem();
|
||||
|
||||
response->setLength();
|
||||
request->send(response);
|
||||
}
|
||||
|
||||
void EMSESPStatusService::init_mqtt() {
|
||||
if (!_mqttClient->connected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Mqtt::on_connect();
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
36
src/EMSESPStatusService.h
Normal file
36
src/EMSESPStatusService.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef EMSESPStatusService_h
|
||||
#define EMSESPStatusService_h
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <AsyncJson.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <SecurityManager.h>
|
||||
|
||||
|
||||
#include <HttpEndpoint.h>
|
||||
#include <MqttPubSub.h>
|
||||
#include <WebSocketTxRx.h>
|
||||
|
||||
#include "EMSESPSettingsService.h"
|
||||
#include "version.h"
|
||||
|
||||
#define MAX_EMSESP_STATUS_SIZE 1024
|
||||
#define EMSESP_STATUS_SERVICE_PATH "/rest/emsespStatus"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class EMSESPStatusService {
|
||||
public:
|
||||
EMSESPStatusService(AsyncWebServer * server, SecurityManager * securityManager, AsyncMqttClient * mqttClient);
|
||||
|
||||
private:
|
||||
EMSESPSettingsService * _emsespSettingsService;
|
||||
void emsespStatusService(AsyncWebServerRequest * request);
|
||||
AsyncMqttClient * _mqttClient;
|
||||
|
||||
void init_mqtt();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
3051
src/MyESP.cpp
3051
src/MyESP.cpp
File diff suppressed because it is too large
Load Diff
498
src/MyESP.h
498
src/MyESP.h
@@ -1,498 +0,0 @@
|
||||
/*
|
||||
* MyESP.h - does all the basics like WiFI/MQTT/NTP/Debug logs etc
|
||||
*
|
||||
* Paul Derbyshire - first version December 2018
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef MyESP_h
|
||||
#define MyESP_h
|
||||
|
||||
#define MYESP_VERSION "1.2.37"
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <AsyncMqttClient.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <FS.h>
|
||||
#include <JustWifi.h>
|
||||
#include <deque> // for MQTT publish queue
|
||||
|
||||
// SysLog
|
||||
#include <uuid/common.h>
|
||||
#include <uuid/log.h>
|
||||
#include <uuid/syslog.h>
|
||||
static uuid::syslog::SyslogService syslog;
|
||||
enum MYESP_SYSLOG_LEVEL : uint8_t { MYESP_SYSLOG_INFO, MYESP_SYSLOG_ERROR };
|
||||
|
||||
// local libraries
|
||||
#include "Ntp.h"
|
||||
#include "TelnetSpy.h" // modified from https://github.com/yasheena/telnetspy
|
||||
|
||||
#ifdef CRASH
|
||||
#include <EEPROM_Rotate.h>
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
|
||||
#include "user_interface.h"
|
||||
extern struct rst_info resetInfo;
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <SPIFFS.h> // added for ESP32
|
||||
#define ets_vsnprintf vsnprintf // added for ESP32
|
||||
#define OTA_PORT 3232
|
||||
#else
|
||||
#include <ESPAsyncTCP.h>
|
||||
#define OTA_PORT 8266
|
||||
#endif
|
||||
|
||||
// web files
|
||||
// reference libs
|
||||
#include "webh/glyphicons-halflings-regular.woff.gz.h"
|
||||
#include "webh/required.css.gz.h"
|
||||
#include "webh/required.js.gz.h"
|
||||
|
||||
// custom stuff
|
||||
#include "webh/index.html.gz.h"
|
||||
#include "webh/myesp.html.gz.h"
|
||||
#include "webh/myesp.js.gz.h"
|
||||
|
||||
#define MYESP_CONFIG_FILE "/myesp.json"
|
||||
#define MYESP_CUSTOMCONFIG_FILE "/customconfig.json"
|
||||
#define MYESP_OLD_EVENTLOG_FILE "/eventlog.json" // depreciated
|
||||
#define MYESP_OLD_CONFIG_FILE "/config.json" // depreciated
|
||||
|
||||
#define MYESP_HTTP_USERNAME "admin" // HTTP username
|
||||
#define MYESP_HTTP_PASSWORD "admin" // default password
|
||||
|
||||
#define MYESP_NTP_SERVER "pool.ntp.org" // default ntp server
|
||||
|
||||
#define MYESP_LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) = 30 seconds
|
||||
|
||||
// WIFI
|
||||
#define MYESP_WIFI_CONNECT_TIMEOUT 20000 // Connecting timeout for WIFI in ms (20 seconds)
|
||||
#define MYESP_WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
|
||||
|
||||
// set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1.
|
||||
#define MYESP_DELAY 1
|
||||
|
||||
// MQTT
|
||||
#define MQTT_PORT 1883 // MQTT port
|
||||
#define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
|
||||
#define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt
|
||||
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
|
||||
#define MQTT_TOPIC_START "start"
|
||||
#define MQTT_TOPIC_HEARTBEAT "heartbeat"
|
||||
#define MQTT_TOPIC_RESTART "restart"
|
||||
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
|
||||
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
|
||||
#define MQTT_BASE_DEFAULT "home" // default MQTT prefix to topics
|
||||
#define MQTT_RETAIN false // default false
|
||||
#define MQTT_KEEPALIVE 60 // default keepalive 1 minute
|
||||
#define MQTT_QOS 0 // default qos 0
|
||||
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
|
||||
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
|
||||
#define MQTT_QUEUE_MAX_SIZE 50 // Size of the MQTT queue
|
||||
#define MQTT_PUBLISH_WAIT 750 // time in ms before sending MQTT messages
|
||||
#define MQTT_PUBLISH_MAX_RETRY 4 // max retries for giving up on publishing
|
||||
#define MYESP_JSON_MAXSIZE_LARGE 2000 // for large Dynamic json files - https://arduinojson.org/v6/assistant/
|
||||
#define MYESP_JSON_MAXSIZE_MEDIUM 800 // for medium Dynamic json files - https://arduinojson.org/v6/assistant/
|
||||
#define MYESP_JSON_MAXSIZE_SMALL 200 // for smaller Static json documents - https://arduinojson.org/v6/assistant/
|
||||
|
||||
// Internal MQTT events
|
||||
#define MQTT_CONNECT_EVENT 0
|
||||
#define MQTT_DISCONNECT_EVENT 1
|
||||
#define MQTT_MESSAGE_EVENT 2
|
||||
|
||||
#define MYESP_MQTT_PAYLOAD_ON '1' // for MQTT switch on
|
||||
#define MYESP_MQTT_PAYLOAD_OFF '0' // for MQTT switch off
|
||||
|
||||
// Telnet
|
||||
#define TELNET_SERIAL_BAUD 115200
|
||||
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
|
||||
#define TELNET_MAX_BUFFER_LENGTH 700 // max length of telnet string
|
||||
#define TELNET_EVENT_CONNECT 1
|
||||
#define TELNET_EVENT_DISCONNECT 0
|
||||
#define TELNET_EVENT_SHOWCMD 10
|
||||
#define TELNET_EVENT_SHOWSET 20
|
||||
|
||||
// ANSI Colors
|
||||
#define COLOR_RESET "\x1B[0m"
|
||||
#define COLOR_BLACK "\x1B[0;30m"
|
||||
#define COLOR_RED "\x1B[0;31m"
|
||||
#define COLOR_GREEN "\x1B[0;32m"
|
||||
#define COLOR_YELLOW "\x1B[0;33m"
|
||||
#define COLOR_BLUE "\x1B[0;34m"
|
||||
#define COLOR_MAGENTA "\x1B[0;35m"
|
||||
#define COLOR_CYAN "\x1B[0;36m"
|
||||
#define COLOR_WHITE "\x1B[0;37m"
|
||||
#define COLOR_BOLD_ON "\x1B[1m"
|
||||
#define COLOR_BOLD_OFF "\x1B[22m"
|
||||
#define COLOR_BRIGHT_BLACK "\x1B[0;90m"
|
||||
#define COLOR_BRIGHT_RED "\x1B[0;91m"
|
||||
#define COLOR_BRIGHT_GREEN "\x1B[0;92m"
|
||||
#define COLOR_BRIGHT_YELLOW "\x1B[0;99m"
|
||||
#define COLOR_BRIGHT_BLUE "\x1B[0;94m"
|
||||
#define COLOR_BRIGHT_MAGENTA "\x1B[0;95m"
|
||||
#define COLOR_BRIGHT_CYAN "\x1B[0;96m"
|
||||
#define COLOR_BRIGHT_WHITE "\x1B[0;97m"
|
||||
|
||||
// reset reason codes
|
||||
PROGMEM const char custom_reset_hardware[] = "Hardware button";
|
||||
PROGMEM const char custom_reset_terminal[] = "Restart from terminal";
|
||||
PROGMEM const char custom_reset_mqtt[] = "Restart from MQTT";
|
||||
PROGMEM const char custom_reset_ota[] = "Restart after successful OTA update";
|
||||
PROGMEM const char custom_reset_factory[] = "Factory reset";
|
||||
PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, custom_reset_terminal, custom_reset_mqtt, custom_reset_ota, custom_reset_factory};
|
||||
#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button
|
||||
#define CUSTOM_RESET_TERMINAL 2 // Reset from terminal
|
||||
#define CUSTOM_RESET_MQTT 3 // Reset via MQTT
|
||||
#define CUSTOM_RESET_OTA 4 // Reset after successful OTA update
|
||||
#define CUSTOM_RESET_FACTORY 5 // Factory reset
|
||||
#define CUSTOM_RESET_MAX 5
|
||||
|
||||
// SPIFFS - max allocation is 1000 KB
|
||||
// https://arduinojson.org/v6/assistant/
|
||||
#define MYESP_SPIFFS_MAXSIZE_CONFIG 999 // max size for a config file
|
||||
|
||||
// CRASH
|
||||
/**
|
||||
* Structure of the single crash data set
|
||||
*
|
||||
* 1. Crash time
|
||||
* 2. Restart reason
|
||||
* 3. Exception cause
|
||||
* 4. epc1
|
||||
* 5. epc2
|
||||
* 6. epc3
|
||||
* 7. excvaddr
|
||||
* 8. depc
|
||||
* 9. address of stack start
|
||||
* 10. address of stack end
|
||||
* 11. stack trace bytes
|
||||
* ...
|
||||
*/
|
||||
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
|
||||
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
|
||||
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
|
||||
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
|
||||
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
|
||||
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
|
||||
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
|
||||
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
|
||||
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
|
||||
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
|
||||
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
|
||||
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
|
||||
|
||||
// Base address of USER RTC memory
|
||||
// https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map#memmory-mapped-io-registers
|
||||
#define RTCMEM_ADDR_BASE (0x60001200)
|
||||
|
||||
// RTC memory is accessed using blocks of 4 bytes.
|
||||
// Blocks 0..63 are reserved by the SDK, 64..192 are available to the user.
|
||||
// Blocks 64..96 are reserved by the eboot 'struct eboot_command' (128 -> (128 / 4) -> 32):
|
||||
// https://github.com/esp8266/Arduino/blob/master/bootloaders/eboot/eboot_command.h
|
||||
#define RTCMEM_OFFSET 32u
|
||||
#define RTCMEM_ADDR (RTCMEM_ADDR_BASE + (RTCMEM_OFFSET * 4u))
|
||||
#define RTCMEM_BLOCKS 96u
|
||||
#define RTCMEM_MAGIC 0x45535076
|
||||
|
||||
struct RtcmemData {
|
||||
uint32_t magic; // RTCMEM_MAGIC
|
||||
uint32_t sys; // system details
|
||||
};
|
||||
|
||||
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
|
||||
|
||||
#define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 min)
|
||||
#define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot
|
||||
#define MYESP_HEARTBEAT_INTERVAL 60000 // in milliseconds, how often the MQTT heartbeat is sent (1 min)
|
||||
|
||||
typedef struct {
|
||||
bool set; // is it a set command?
|
||||
char key[60];
|
||||
char description[110];
|
||||
} command_t;
|
||||
|
||||
typedef enum {
|
||||
MYESP_FSACTION_SET,
|
||||
MYESP_FSACTION_LIST,
|
||||
MYESP_FSACTION_SAVE,
|
||||
MYESP_FSACTION_LOAD,
|
||||
MYESP_FSACTION_ERR,
|
||||
MYESP_FSACTION_OK,
|
||||
MYESP_FSACTION_RESTART
|
||||
} MYESP_FSACTION_t;
|
||||
|
||||
typedef enum {
|
||||
MYESP_BOOTSTATUS_POWERON = 0,
|
||||
MYESP_BOOTSTATUS_BOOTED = 1,
|
||||
MYESP_BOOTSTATUS_BOOTING = 2,
|
||||
MYESP_BOOTSTATUS_RESETNEEDED = 3
|
||||
} MYESP_BOOTSTATUS_t; // boot messages
|
||||
|
||||
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
|
||||
typedef std::function<void()> wifi_callback_f;
|
||||
typedef std::function<void()> ota_callback_f;
|
||||
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
|
||||
typedef std::function<void(uint8_t)> telnet_callback_f;
|
||||
typedef std::function<bool(MYESP_FSACTION_t, JsonObject json)> fs_loadsave_callback_f;
|
||||
typedef std::function<MYESP_FSACTION_t(MYESP_FSACTION_t, uint8_t, const char *, const char *)> fs_setlist_callback_f;
|
||||
typedef std::function<void(JsonObject root)> web_callback_f;
|
||||
|
||||
// calculates size of an 2d array at compile time
|
||||
template <typename T, size_t N>
|
||||
constexpr size_t ArraySize(T (&)[N]) {
|
||||
return N;
|
||||
}
|
||||
|
||||
#define MYESP_UPTIME_OVERFLOW 4294967295 // Uptime overflow value
|
||||
#define MYESP_MAX_STR_LEN 16 // web min and max length of wifi ssid and password
|
||||
#define MYESP_BOOTUP_FLASHDELAY 50 // flash duration for LED at bootup sequence
|
||||
#define MYESP_BOOTUP_DELAY 2000 // time before we open the window to reset. This is to stop resetting values when uploading firmware via USB
|
||||
|
||||
// class definition
|
||||
class MyESP {
|
||||
protected:
|
||||
// webserver
|
||||
AsyncWebServer * _webServer;
|
||||
AsyncWebSocket * _ws;
|
||||
|
||||
// NTP
|
||||
NtpClient NTP;
|
||||
|
||||
public:
|
||||
MyESP();
|
||||
~MyESP();
|
||||
|
||||
// wifi
|
||||
void setWIFICallback(void (*callback)());
|
||||
void setWIFI(wifi_callback_f callback);
|
||||
bool isWifiConnected();
|
||||
bool isAPmode();
|
||||
|
||||
// mqtt
|
||||
bool isMQTTConnected();
|
||||
bool mqttSubscribe(const char * topic);
|
||||
void mqttUnsubscribe(const char * topic);
|
||||
bool mqttPublish(const char * topic, const char * payload);
|
||||
bool mqttPublish(const char * topic, const char * payload, bool retain);
|
||||
bool mqttPublish(const char * topic, JsonDocument & payload);
|
||||
bool mqttPublish(const char * topic, JsonDocument & payload, bool retain);
|
||||
void setMQTT(mqtt_callback_f callback);
|
||||
bool mqttUseNestedJson();
|
||||
|
||||
// OTA
|
||||
void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post);
|
||||
|
||||
// debug & telnet
|
||||
void myDebug(const char * format, ...);
|
||||
void myDebug_P(PGM_P format_P, ...);
|
||||
void setTelnet(telnetcommand_callback_f callback_cmd, telnet_callback_f callback);
|
||||
bool getUseSerial();
|
||||
void setUseSerial(bool toggle);
|
||||
|
||||
// syslog
|
||||
void writeLogEvent(const uint8_t type, const char * msg);
|
||||
|
||||
// FS
|
||||
void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true);
|
||||
void saveSettings();
|
||||
bool fs_saveConfig(JsonObject root);
|
||||
bool fs_saveCustomConfig(JsonObject root);
|
||||
bool fs_setSettingValue(char ** setting, const char * value, const char * value_default);
|
||||
bool fs_setSettingValue(uint16_t * setting, const char * value, uint16_t value_default);
|
||||
bool fs_setSettingValue(uint8_t * setting, const char * value, uint8_t value_default);
|
||||
bool fs_setSettingValue(int8_t * setting, const char * value, int8_t value_default);
|
||||
bool fs_setSettingValue(bool * setting, const char * value, bool value_default);
|
||||
|
||||
// Web
|
||||
void setWeb(web_callback_f callback_web);
|
||||
|
||||
// Crash
|
||||
void crashClear();
|
||||
void crashDump();
|
||||
void crashInfo();
|
||||
|
||||
// general
|
||||
void end();
|
||||
void loop();
|
||||
void begin(const char * app_hostname, const char * app_name, const char * app_version, const char * app_url, const char * app_url_api);
|
||||
void resetESP();
|
||||
int getWifiQuality();
|
||||
void showSystemStats();
|
||||
bool getHeartbeat();
|
||||
uint32_t getSystemLoadAverage();
|
||||
uint32_t getSystemResetReason();
|
||||
uint8_t getSystemBootStatus();
|
||||
unsigned long getSystemTime();
|
||||
void heartbeatPrint();
|
||||
void heartbeatCheck(bool force = false);
|
||||
|
||||
private:
|
||||
// mqtt
|
||||
void _mqttOnMessage(char * topic, char * payload, size_t len);
|
||||
void _mqttOnPublish(uint16_t packetId);
|
||||
void _mqttConnect();
|
||||
void _mqtt_setup();
|
||||
void _mqttOnConnect();
|
||||
void _sendStart();
|
||||
char * _mqttTopic(const char * topic);
|
||||
bool _mqttQueue(const char * topic, const char * payload, bool retain);
|
||||
bool _mqttQueue(const char * topic, JsonDocument & payload, bool retain);
|
||||
void _printMQTTQueue();
|
||||
void _mqttPublishQueue();
|
||||
void _mqttRemoveLastPublish();
|
||||
void _sendStartTopic();
|
||||
AsyncMqttClient mqttClient; // the MQTT class
|
||||
uint32_t _mqtt_reconnect_delay;
|
||||
mqtt_callback_f _mqtt_callback_f;
|
||||
char * _mqtt_ip;
|
||||
char * _mqtt_user;
|
||||
char * _mqtt_password;
|
||||
uint16_t _mqtt_port;
|
||||
char * _mqtt_base;
|
||||
bool _mqtt_enabled;
|
||||
uint16_t _mqtt_keepalive;
|
||||
uint8_t _mqtt_qos;
|
||||
bool _mqtt_retain;
|
||||
char * _mqtt_will_topic;
|
||||
char * _mqtt_will_online_payload;
|
||||
char * _mqtt_will_offline_payload;
|
||||
uint32_t _mqtt_last_connection;
|
||||
bool _mqtt_connecting;
|
||||
bool _mqtt_heartbeat;
|
||||
bool _mqtt_nestedjson;
|
||||
uint16_t _mqtt_publish_fails;
|
||||
|
||||
// wifi
|
||||
void _wifiCallback(justwifi_messages_t code, char * parameter);
|
||||
void _wifi_setup();
|
||||
wifi_callback_f _wifi_callback_f;
|
||||
char * _network_ssid;
|
||||
char * _network_password;
|
||||
uint8_t _network_wmode;
|
||||
char * _network_staticip;
|
||||
char * _network_gatewayip;
|
||||
char * _network_nmask;
|
||||
char * _network_dnsip;
|
||||
bool _wifi_connected;
|
||||
String _getESPhostname();
|
||||
|
||||
// ota
|
||||
ota_callback_f _ota_pre_callback_f;
|
||||
ota_callback_f _ota_post_callback_f;
|
||||
void _ota_setup();
|
||||
void _OTACallback();
|
||||
|
||||
// crash
|
||||
void _eeprom_setup();
|
||||
|
||||
// telnet
|
||||
TelnetSpy SerialAndTelnet;
|
||||
void _telnetConnected();
|
||||
void _telnetDisconnected();
|
||||
void _telnetHandle();
|
||||
void _telnetCommand(char * commandLine);
|
||||
char * _telnet_readWord(bool allow_all_chars);
|
||||
void _telnet_setup();
|
||||
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
|
||||
void _consoleShowHelp();
|
||||
telnetcommand_callback_f _telnetcommand_callback_f; // Callable for projects commands
|
||||
telnet_callback_f _telnet_callback_f; // callback for connect/disconnect
|
||||
bool _changeSetting(uint8_t wc, const char * setting, const char * value);
|
||||
|
||||
// syslog
|
||||
void _syslog_setup();
|
||||
|
||||
// fs and settings
|
||||
void _fs_setup();
|
||||
bool _fs_loadConfig();
|
||||
bool _fs_loadCustomConfig();
|
||||
void _fs_eraseConfig();
|
||||
bool _fs_writeConfig();
|
||||
bool _fs_createCustomConfig();
|
||||
bool _fs_sendConfig();
|
||||
size_t _fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc);
|
||||
size_t _fs_validateLogFile(const char * filename);
|
||||
fs_loadsave_callback_f _fs_loadsave_callback_f;
|
||||
fs_setlist_callback_f _fs_setlist_callback_f;
|
||||
void _printSetCommands();
|
||||
|
||||
// general
|
||||
char * _general_hostname;
|
||||
char * _app_name;
|
||||
char * _app_version;
|
||||
char * _app_url;
|
||||
char * _app_updateurl;
|
||||
char * _app_updateurl_dev;
|
||||
bool _suspendOutput;
|
||||
bool _general_serial;
|
||||
bool _general_log_events;
|
||||
char * _general_log_ip;
|
||||
char * _buildTime;
|
||||
bool _timerequest;
|
||||
bool _formatreq;
|
||||
unsigned long _getUptime();
|
||||
char * _getBuildTime();
|
||||
bool _hasValue(const char * s);
|
||||
void _printHeap(const char * s);
|
||||
void _kick();
|
||||
|
||||
// reset reason and rtcmem
|
||||
bool _rtcmem_status;
|
||||
bool _rtcmemStatus();
|
||||
bool _getRtcmemStatus();
|
||||
void _rtcmemInit();
|
||||
void _rtcmemSetup();
|
||||
void _deferredReset(unsigned long delay, uint8_t reason);
|
||||
uint8_t _getSystemStabilityCounter();
|
||||
void _setSystemStabilityCounter(uint8_t counter);
|
||||
uint8_t _getSystemDropoutCounter();
|
||||
void _setSystemDropoutCounter(uint8_t counter);
|
||||
void _increaseSystemDropoutCounter();
|
||||
void _setSystemResetReason(uint8_t reason);
|
||||
uint8_t _getCustomResetReason();
|
||||
void _setCustomResetReason(uint8_t reason);
|
||||
uint8_t _getSystemResetReason();
|
||||
void _setSystemBootStatus(uint8_t status);
|
||||
bool _systemStable;
|
||||
void _bootupSequence();
|
||||
bool _getSystemCheck();
|
||||
void _systemCheckLoop();
|
||||
void _setSystemCheck(bool stable);
|
||||
|
||||
// load average (0..100) and heap ram
|
||||
void _calculateLoad();
|
||||
uint32_t _load_average;
|
||||
uint32_t _getInitialFreeHeap();
|
||||
uint32_t _getUsedHeap();
|
||||
|
||||
// web
|
||||
web_callback_f _web_callback_f;
|
||||
const char * _http_username;
|
||||
|
||||
// web
|
||||
void _onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len);
|
||||
void _procMsg(AsyncWebSocketClient * client, size_t sz);
|
||||
void _sendStatus();
|
||||
void _sendCustomStatus();
|
||||
void _printScanResult(int networksFound);
|
||||
void _sendTime();
|
||||
void _webserver_setup();
|
||||
|
||||
// ntp
|
||||
char * _ntp_server;
|
||||
uint16_t _ntp_interval;
|
||||
bool _ntp_enabled;
|
||||
uint8_t _ntp_timezone;
|
||||
bool _have_ntp_time;
|
||||
};
|
||||
|
||||
extern MyESP myESP;
|
||||
|
||||
#endif
|
||||
129
src/Ntp.cpp
129
src/Ntp.cpp
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Ntp.cpp
|
||||
*/
|
||||
|
||||
#include "Ntp.h"
|
||||
#include "MyESP.h"
|
||||
|
||||
char * NtpClient::TimeServerName;
|
||||
Timezone * NtpClient::tz;
|
||||
TimeChangeRule * NtpClient::tcr;
|
||||
time_t NtpClient::syncInterval;
|
||||
IPAddress NtpClient::timeServer;
|
||||
AsyncUDP NtpClient::udpListener;
|
||||
byte NtpClient::NTPpacket[NTP_PACKET_SIZE];
|
||||
|
||||
// references:
|
||||
// https://github.com/filipdanic/compact-timezone-list/blob/master/index.js
|
||||
// https://github.com/sanohin/google-timezones-json/blob/master/timezones.json
|
||||
// https://github.com/dmfilipenko/timezones.json/blob/master/timezones.json
|
||||
// https://home.kpn.nl/vanadovv/time/TZworld.html
|
||||
// https://www.timeanddate.com/time/zones/
|
||||
|
||||
// Australia Eastern Time Zone (Sydney, Melbourne)
|
||||
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; // UTC + 11 hours
|
||||
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; // UTC + 10 hours
|
||||
Timezone ausET(aEDT, aEST);
|
||||
|
||||
// Moscow Standard Time (MSK, does not observe DST)
|
||||
TimeChangeRule msk = {"MSK", Last, Sun, Mar, 1, 180};
|
||||
Timezone MSK(msk);
|
||||
|
||||
// Turkey
|
||||
TimeChangeRule trt = {"TRT", Last, Sun, Mar, 1, 180};
|
||||
Timezone TRT(trt);
|
||||
|
||||
// Central European Time (Frankfurt, Paris)
|
||||
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
|
||||
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time
|
||||
Timezone CE(CEST, CET);
|
||||
|
||||
// United Kingdom (London, Belfast)
|
||||
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time
|
||||
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; // Standard Time
|
||||
Timezone UK(BST, GMT);
|
||||
|
||||
// UTC
|
||||
TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0}; // UTC
|
||||
Timezone UTC(utcRule);
|
||||
|
||||
// US Eastern Time Zone (New York, Detroit)
|
||||
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours
|
||||
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; // Eastern Standard Time = UTC - 5 hours
|
||||
Timezone usET(usEDT, usEST);
|
||||
|
||||
// US Central Time Zone (Chicago, Houston)
|
||||
TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300};
|
||||
TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360};
|
||||
Timezone usCT(usCDT, usCST);
|
||||
|
||||
// US Mountain Time Zone (Denver, Salt Lake City)
|
||||
TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360};
|
||||
TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420};
|
||||
Timezone usMT(usMDT, usMST);
|
||||
|
||||
// Arizona is US Mountain Time Zone but does not use DST
|
||||
Timezone usAZ(usMST);
|
||||
|
||||
// US Pacific Time Zone (Las Vegas, Los Angeles)
|
||||
TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420};
|
||||
TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480};
|
||||
Timezone usPT(usPDT, usPST);
|
||||
|
||||
// build index of all timezones
|
||||
Timezone * timezones[] = {&ausET, &MSK, &CE, &UK, &UTC, &usET, &usCT, &usMT, &usAZ, &usPT, &TRT};
|
||||
|
||||
void ICACHE_FLASH_ATTR NtpClient::Ntp(const char * server, time_t syncMins, uint8_t tz_index) {
|
||||
TimeServerName = strdup(server);
|
||||
syncInterval = syncMins * 60; // convert to seconds
|
||||
|
||||
// check for out of bounds
|
||||
if (tz_index >= NTP_TIMEZONE_MAX) {
|
||||
tz_index = NTP_TIMEZONE_DEFAULT;
|
||||
}
|
||||
tz = timezones[tz_index]; // set timezone
|
||||
|
||||
WiFi.hostByName(TimeServerName, timeServer);
|
||||
setSyncProvider(getNtpTime);
|
||||
setSyncInterval(syncInterval);
|
||||
}
|
||||
|
||||
ICACHE_FLASH_ATTR NtpClient::~NtpClient() {
|
||||
udpListener.close();
|
||||
}
|
||||
|
||||
// send an NTP request to the time server at the given address
|
||||
time_t ICACHE_FLASH_ATTR NtpClient::getNtpTime() {
|
||||
memset(NTPpacket, 0, sizeof(NTPpacket));
|
||||
NTPpacket[0] = 0b11100011;
|
||||
NTPpacket[1] = 0;
|
||||
NTPpacket[2] = 6;
|
||||
NTPpacket[3] = 0xEC;
|
||||
NTPpacket[12] = 49;
|
||||
NTPpacket[13] = 0x4E;
|
||||
NTPpacket[14] = 49;
|
||||
NTPpacket[15] = 52;
|
||||
if (udpListener.connect(timeServer, 123)) {
|
||||
udpListener.onPacket([](AsyncUDPPacket packet) {
|
||||
unsigned long highWord = word(packet.data()[40], packet.data()[41]);
|
||||
unsigned long lowWord = word(packet.data()[42], packet.data()[43]);
|
||||
time_t UnixUTCtime = (highWord << 16 | lowWord) - 2208988800UL;
|
||||
time_t adjustedtime = (*tz).toLocal(UnixUTCtime, &tcr);
|
||||
|
||||
myESP.myDebug_P(PSTR("[NTP] Internet time: %02d:%02d:%02d UTC on %d/%d. Local time: %02d:%02d:%02d %s"),
|
||||
to_hour(UnixUTCtime),
|
||||
to_minute(UnixUTCtime),
|
||||
to_second(UnixUTCtime),
|
||||
to_day(UnixUTCtime),
|
||||
to_month(UnixUTCtime),
|
||||
to_hour(adjustedtime),
|
||||
to_minute(adjustedtime),
|
||||
to_second(adjustedtime),
|
||||
tcr->abbrev);
|
||||
|
||||
setTime(adjustedtime);
|
||||
});
|
||||
}
|
||||
udpListener.write(NTPpacket, sizeof(NTPpacket));
|
||||
return 0;
|
||||
}
|
||||
39
src/Ntp.h
39
src/Ntp.h
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Ntp.h
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#ifndef NTP_H_
|
||||
#define NTP_H_
|
||||
|
||||
#include <ESP8266WiFi.h>
|
||||
#include <ESPAsyncUDP.h>
|
||||
|
||||
#include "TimeLib.h" // customized version of the Time library
|
||||
#include "Timezone.h"
|
||||
|
||||
#define NTP_PACKET_SIZE 48 // NTP time is in the first 48 bytes of the message
|
||||
#define NTP_INTERVAL_DEFAULT 720 // every 12 hours
|
||||
#define NTP_TIMEZONE_DEFAULT 2 // CE
|
||||
#define NTP_TIMEZONE_MAX 11
|
||||
|
||||
class NtpClient {
|
||||
public:
|
||||
void ICACHE_FLASH_ATTR Ntp(const char * server, time_t syncMins, uint8_t tz_index);
|
||||
ICACHE_FLASH_ATTR virtual ~NtpClient();
|
||||
|
||||
static char * TimeServerName;
|
||||
static IPAddress timeServer;
|
||||
static time_t syncInterval;
|
||||
static Timezone * tz;
|
||||
static TimeChangeRule * tcr;
|
||||
|
||||
static AsyncUDP udpListener;
|
||||
|
||||
static byte NTPpacket[NTP_PACKET_SIZE];
|
||||
|
||||
static ICACHE_FLASH_ATTR time_t getNtpTime();
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,632 +0,0 @@
|
||||
/*
|
||||
* TELNET SERVER FOR ESP8266 / ESP32
|
||||
* Cloning the serial port via Telnet.
|
||||
*
|
||||
* Written by Wolfgang Mattis (arduino@yasheena.de).
|
||||
* Version 1.1 / September 7, 2018.
|
||||
* MIT license, all text above must be included in any redistribution.
|
||||
*/
|
||||
|
||||
#ifdef ESP8266
|
||||
extern "C" {
|
||||
#include "user_interface.h"
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "TelnetSpy.h"
|
||||
|
||||
#ifndef min
|
||||
#define min(a, b) ((a) < (b) ? (a) : (b))
|
||||
#endif
|
||||
#ifndef max
|
||||
#define max(a, b) ((a) > (b) ? (a) : (b))
|
||||
#endif
|
||||
|
||||
static TelnetSpy * actualObject = NULL;
|
||||
|
||||
TelnetSpy::TelnetSpy() {
|
||||
port = TELNETSPY_PORT;
|
||||
telnetServer = NULL;
|
||||
started = false;
|
||||
listening = false;
|
||||
firstMainLoop = true;
|
||||
usedSer = &Serial;
|
||||
storeOffline = true;
|
||||
connected = false;
|
||||
callbackConnect = NULL;
|
||||
callbackDisconnect = NULL;
|
||||
welcomeMsg = strdup(TELNETSPY_WELCOME_MSG);
|
||||
rejectMsg = strdup(TELNETSPY_REJECT_MSG);
|
||||
minBlockSize = TELNETSPY_MIN_BLOCK_SIZE;
|
||||
collectingTime = TELNETSPY_COLLECTING_TIME;
|
||||
maxBlockSize = TELNETSPY_MAX_BLOCK_SIZE;
|
||||
pingTime = TELNETSPY_PING_TIME;
|
||||
pingRef = 0xFFFFFFFF;
|
||||
waitRef = 0xFFFFFFFF;
|
||||
telnetBuf = NULL;
|
||||
bufLen = 0;
|
||||
uint16_t size = TELNETSPY_BUFFER_LEN;
|
||||
while (!setBufferSize(size)) {
|
||||
size = size >> 1;
|
||||
if (size < minBlockSize) {
|
||||
setBufferSize(minBlockSize);
|
||||
break;
|
||||
}
|
||||
}
|
||||
debugOutput = TELNETSPY_CAPTURE_OS_PRINT;
|
||||
if (debugOutput) {
|
||||
setDebugOutput(true);
|
||||
}
|
||||
}
|
||||
|
||||
TelnetSpy::~TelnetSpy() {
|
||||
end();
|
||||
}
|
||||
|
||||
// added by proddy
|
||||
void TelnetSpy::disconnectClient() {
|
||||
if (client.connected()) {
|
||||
client.flush();
|
||||
client.stop();
|
||||
}
|
||||
if (connected && (callbackDisconnect != NULL)) {
|
||||
callbackDisconnect();
|
||||
}
|
||||
connected = false;
|
||||
}
|
||||
|
||||
void TelnetSpy::setPort(uint16_t portToUse) {
|
||||
port = portToUse;
|
||||
if (listening) {
|
||||
if (client.connected()) {
|
||||
client.flush();
|
||||
client.stop();
|
||||
}
|
||||
if (connected && (callbackDisconnect != NULL)) {
|
||||
callbackDisconnect();
|
||||
}
|
||||
connected = false;
|
||||
telnetServer->close();
|
||||
delete telnetServer;
|
||||
telnetServer = new WiFiServer(port);
|
||||
if (started) {
|
||||
telnetServer->begin();
|
||||
telnetServer->setNoDelay(bufLen > 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetSpy::setWelcomeMsg(const char * msg) {
|
||||
if (welcomeMsg) {
|
||||
free(welcomeMsg);
|
||||
}
|
||||
welcomeMsg = strdup(msg);
|
||||
}
|
||||
|
||||
void TelnetSpy::setRejectMsg(const char * msg) {
|
||||
if (rejectMsg) {
|
||||
free(rejectMsg);
|
||||
}
|
||||
rejectMsg = strdup(msg);
|
||||
}
|
||||
|
||||
void TelnetSpy::setMinBlockSize(uint16_t minSize) {
|
||||
minBlockSize = min(max((uint16_t)1, minSize), maxBlockSize);
|
||||
}
|
||||
|
||||
void TelnetSpy::setCollectingTime(uint16_t colTime) {
|
||||
collectingTime = colTime;
|
||||
}
|
||||
|
||||
void TelnetSpy::setMaxBlockSize(uint16_t maxSize) {
|
||||
maxBlockSize = max(maxSize, minBlockSize);
|
||||
}
|
||||
|
||||
bool TelnetSpy::setBufferSize(uint16_t newSize) {
|
||||
if (telnetBuf && (bufLen == newSize)) {
|
||||
return true;
|
||||
}
|
||||
if (newSize == 0) {
|
||||
bufLen = 0;
|
||||
if (telnetBuf) {
|
||||
free(telnetBuf);
|
||||
telnetBuf = NULL;
|
||||
}
|
||||
if (telnetServer) {
|
||||
telnetServer->setNoDelay(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
newSize = max(newSize, minBlockSize);
|
||||
uint16_t oldBufLen = bufLen;
|
||||
bufLen = newSize;
|
||||
uint16_t tmp;
|
||||
if (!telnetBuf || (bufUsed == 0)) {
|
||||
bufRdIdx = 0;
|
||||
bufWrIdx = 0;
|
||||
bufUsed = 0;
|
||||
} else {
|
||||
if (bufLen < oldBufLen) {
|
||||
if (bufRdIdx < bufWrIdx) {
|
||||
if (bufWrIdx > bufLen) {
|
||||
tmp = min(bufLen, (uint16_t)(bufWrIdx - max(bufLen, bufRdIdx)));
|
||||
memcpy(telnetBuf, &telnetBuf[bufWrIdx - tmp], tmp);
|
||||
bufWrIdx = tmp;
|
||||
if (bufWrIdx > bufRdIdx) {
|
||||
bufRdIdx = bufWrIdx;
|
||||
} else {
|
||||
if (bufRdIdx > bufLen) {
|
||||
bufRdIdx = 0;
|
||||
}
|
||||
}
|
||||
if (bufRdIdx == bufWrIdx) {
|
||||
bufUsed = bufLen;
|
||||
} else {
|
||||
bufUsed = bufWrIdx - bufRdIdx;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (bufWrIdx > bufLen) {
|
||||
memcpy(telnetBuf, &telnetBuf[bufWrIdx - bufLen], bufLen);
|
||||
bufRdIdx = 0;
|
||||
bufWrIdx = 0;
|
||||
bufUsed = bufLen;
|
||||
} else {
|
||||
tmp = min(bufLen - bufWrIdx, oldBufLen - bufRdIdx);
|
||||
memcpy(&telnetBuf[bufLen - tmp], &telnetBuf[oldBufLen - tmp], tmp);
|
||||
bufRdIdx = bufLen - tmp;
|
||||
bufUsed = bufWrIdx + tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
char * temp = (char *)realloc(telnetBuf, bufLen);
|
||||
if (!temp) {
|
||||
return false;
|
||||
}
|
||||
telnetBuf = temp;
|
||||
if (telnetBuf && (bufLen > oldBufLen) && (bufRdIdx > bufWrIdx)) {
|
||||
tmp = bufLen - (oldBufLen - bufRdIdx);
|
||||
memcpy(&telnetBuf[tmp], &telnetBuf[bufRdIdx], oldBufLen - bufRdIdx);
|
||||
bufRdIdx = tmp;
|
||||
}
|
||||
if (telnetServer) {
|
||||
telnetServer->setNoDelay(true);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint16_t TelnetSpy::getBufferSize() {
|
||||
if (!telnetBuf) {
|
||||
return 0;
|
||||
}
|
||||
return bufLen;
|
||||
}
|
||||
|
||||
void TelnetSpy::setStoreOffline(bool store) {
|
||||
storeOffline = store;
|
||||
}
|
||||
|
||||
bool TelnetSpy::getStoreOffline() {
|
||||
return storeOffline;
|
||||
}
|
||||
|
||||
void TelnetSpy::setPingTime(uint16_t pngTime) {
|
||||
pingTime = pngTime;
|
||||
if (pingTime == 0) {
|
||||
pingRef = 0xFFFFFFFF;
|
||||
} else {
|
||||
pingRef = (millis() & 0x7FFFFFF) + pingTime;
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetSpy::setSerial(HardwareSerial * usedSerial) {
|
||||
usedSer = usedSerial;
|
||||
}
|
||||
|
||||
size_t TelnetSpy::write(uint8_t data) {
|
||||
if (telnetBuf) {
|
||||
if (storeOffline || client.connected()) {
|
||||
if (bufUsed == bufLen) {
|
||||
if (client.connected()) {
|
||||
sendBlock();
|
||||
}
|
||||
if (bufUsed == bufLen) {
|
||||
while (bufUsed > 0) {
|
||||
char c = pullTelnetBuf();
|
||||
if (c == '\n') {
|
||||
addTelnetBuf('\r');
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (peekTelnetBuf() == '\r') {
|
||||
pullTelnetBuf();
|
||||
}
|
||||
}
|
||||
}
|
||||
addTelnetBuf(data);
|
||||
/*
|
||||
if (data == '\n') {
|
||||
addTelnetBuf('\r'); // added by proddy, fix for Windows
|
||||
}
|
||||
*/
|
||||
}
|
||||
} else {
|
||||
if (client.connected()) {
|
||||
client.write(data);
|
||||
}
|
||||
}
|
||||
if (usedSer) {
|
||||
return usedSer->write(data);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// this still needs some work
|
||||
bool TelnetSpy::isSerialAvailable(void) {
|
||||
if (usedSer) {
|
||||
usedSer->write("test");
|
||||
//Wait for four seconds or till data is available on serial, whichever occurs first.
|
||||
while (usedSer->available() == 0 && millis() < 4000)
|
||||
;
|
||||
|
||||
if (usedSer->available() > 0) {
|
||||
(void)usedSer->read(); // We then clear the input buffer
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int TelnetSpy::available(void) {
|
||||
if (usedSer) {
|
||||
int avail = usedSer->available();
|
||||
if (avail > 0) {
|
||||
return avail;
|
||||
}
|
||||
}
|
||||
if (client.connected()) {
|
||||
return telnetAvailable();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int TelnetSpy::read(void) {
|
||||
int val = 0;
|
||||
if (usedSer) {
|
||||
val = usedSer->read();
|
||||
if (val != -1) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
if (client.connected()) {
|
||||
if (telnetAvailable()) {
|
||||
val = client.read();
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
int TelnetSpy::peek(void) {
|
||||
int val = 0;
|
||||
if (usedSer) {
|
||||
val = usedSer->peek();
|
||||
if (val != -1) {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
if (client.connected()) {
|
||||
if (telnetAvailable()) {
|
||||
val = client.peek();
|
||||
}
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void TelnetSpy::flush(void) {
|
||||
if (usedSer) {
|
||||
usedSer->flush();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
void TelnetSpy::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin) {
|
||||
if (usedSer) {
|
||||
usedSer->begin(baud, config, mode, tx_pin);
|
||||
}
|
||||
|
||||
started = true;
|
||||
}
|
||||
|
||||
#else // ESP32
|
||||
|
||||
void TelnetSpy::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert) {
|
||||
if (usedSer) {
|
||||
usedSer->begin(baud, config, rxPin, txPin, invert);
|
||||
}
|
||||
started = true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void TelnetSpy::end() {
|
||||
if (debugOutput) {
|
||||
setDebugOutput(false);
|
||||
}
|
||||
if (usedSer) {
|
||||
usedSer->end();
|
||||
}
|
||||
if (client.connected()) {
|
||||
client.flush();
|
||||
client.stop();
|
||||
}
|
||||
if (connected && (callbackDisconnect != NULL)) {
|
||||
callbackDisconnect();
|
||||
}
|
||||
connected = false;
|
||||
telnetServer->close();
|
||||
delete telnetServer;
|
||||
telnetServer = NULL;
|
||||
listening = false;
|
||||
started = false;
|
||||
}
|
||||
|
||||
#ifdef ESP8266
|
||||
|
||||
void TelnetSpy::swap(uint8_t tx_pin) {
|
||||
if (usedSer) {
|
||||
usedSer->swap(tx_pin);
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetSpy::set_tx(uint8_t tx_pin) {
|
||||
if (usedSer) {
|
||||
usedSer->set_tx(tx_pin);
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetSpy::pins(uint8_t tx, uint8_t rx) {
|
||||
if (usedSer) {
|
||||
usedSer->pins(tx, rx);
|
||||
}
|
||||
}
|
||||
|
||||
bool TelnetSpy::isTxEnabled(void) {
|
||||
if (usedSer) {
|
||||
return usedSer->isTxEnabled();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TelnetSpy::isRxEnabled(void) {
|
||||
if (usedSer) {
|
||||
return usedSer->isRxEnabled();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int TelnetSpy::availableForWrite(void) {
|
||||
if (usedSer) {
|
||||
return min(usedSer->availableForWrite(), bufLen - bufUsed);
|
||||
}
|
||||
return bufLen - bufUsed;
|
||||
}
|
||||
|
||||
TelnetSpy::operator bool() const {
|
||||
if (usedSer) {
|
||||
return (bool)*usedSer;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void TelnetSpy::setDebugOutput(bool en) {
|
||||
debugOutput = en;
|
||||
|
||||
if (debugOutput) {
|
||||
actualObject = this;
|
||||
} else {
|
||||
if (actualObject == this) {
|
||||
actualObject = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t TelnetSpy::baudRate(void) {
|
||||
if (usedSer) {
|
||||
return usedSer->baudRate();
|
||||
}
|
||||
return 115200;
|
||||
}
|
||||
|
||||
void TelnetSpy::sendBlock() {
|
||||
uint16_t len = bufUsed;
|
||||
if (len > maxBlockSize) {
|
||||
len = maxBlockSize;
|
||||
}
|
||||
len = min(len, (uint16_t)(bufLen - bufRdIdx));
|
||||
client.write(&telnetBuf[bufRdIdx], len);
|
||||
bufRdIdx += len;
|
||||
if (bufRdIdx >= bufLen) {
|
||||
bufRdIdx = 0;
|
||||
}
|
||||
bufUsed -= len;
|
||||
if (bufUsed == 0) {
|
||||
bufRdIdx = 0;
|
||||
bufWrIdx = 0;
|
||||
}
|
||||
waitRef = 0xFFFFFFFF;
|
||||
if (pingRef != 0xFFFFFFFF) {
|
||||
pingRef = (millis() & 0x7FFFFFF) + pingTime;
|
||||
if (pingRef > 0x7FFFFFFF) {
|
||||
pingRef -= 0x80000000;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetSpy::addTelnetBuf(char c) {
|
||||
telnetBuf[bufWrIdx] = c;
|
||||
if (bufUsed == bufLen) {
|
||||
bufRdIdx++;
|
||||
if (bufRdIdx >= bufLen) {
|
||||
bufRdIdx = 0;
|
||||
}
|
||||
} else {
|
||||
bufUsed++;
|
||||
}
|
||||
bufWrIdx++;
|
||||
if (bufWrIdx >= bufLen) {
|
||||
bufWrIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
char TelnetSpy::pullTelnetBuf() {
|
||||
if (bufUsed == 0) {
|
||||
return 0;
|
||||
}
|
||||
char c = telnetBuf[bufRdIdx++];
|
||||
if (bufRdIdx >= bufLen) {
|
||||
bufRdIdx = 0;
|
||||
}
|
||||
bufUsed--;
|
||||
return c;
|
||||
}
|
||||
|
||||
char TelnetSpy::peekTelnetBuf() {
|
||||
if (bufUsed == 0) {
|
||||
return 0;
|
||||
}
|
||||
return telnetBuf[bufRdIdx];
|
||||
}
|
||||
|
||||
int TelnetSpy::telnetAvailable() {
|
||||
int n = client.available();
|
||||
while (n > 0) {
|
||||
if (0xff == client.peek()) { // If esc char for telnet NVT protocol data remove that telegram:
|
||||
client.read(); // Remove esc char
|
||||
n--;
|
||||
if (0xff == client.peek()) { // If esc sequence for 0xFF data byte...
|
||||
return n; // ...return info about available data (just this 0xFF data byte)
|
||||
}
|
||||
client.read(); // Skip the rest of the telegram of the telnet NVT protocol data
|
||||
client.read();
|
||||
n--;
|
||||
n--;
|
||||
} else { // If next char is a normal data byte...
|
||||
return n; // ...return info about available data
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool TelnetSpy::isClientConnected() {
|
||||
return connected;
|
||||
}
|
||||
|
||||
void TelnetSpy::setCallbackOnConnect(telnetSpyCallback callback) {
|
||||
callbackConnect = callback;
|
||||
}
|
||||
|
||||
void TelnetSpy::setCallbackOnDisconnect(telnetSpyCallback callback) {
|
||||
callbackDisconnect = callback;
|
||||
}
|
||||
|
||||
void TelnetSpy::serialPrint(char c) {
|
||||
if (usedSer) {
|
||||
usedSer->print(c);
|
||||
}
|
||||
}
|
||||
|
||||
void TelnetSpy::handle() {
|
||||
if (firstMainLoop) {
|
||||
firstMainLoop = false;
|
||||
// Between setup() and loop() the configuration for os_print may be changed so it must be renewed
|
||||
if (debugOutput && (actualObject == this)) {
|
||||
setDebugOutput(true);
|
||||
}
|
||||
}
|
||||
if (!started) {
|
||||
return;
|
||||
}
|
||||
if (!listening) {
|
||||
if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) {
|
||||
if (usedSer) {
|
||||
usedSer->println("[TELNET] in AP mode"); // added by Proddy
|
||||
}
|
||||
} else if (WiFi.status() != WL_CONNECTED) {
|
||||
return;
|
||||
}
|
||||
|
||||
telnetServer = new WiFiServer(port);
|
||||
telnetServer->begin();
|
||||
telnetServer->setNoDelay(bufLen > 0);
|
||||
listening = true;
|
||||
if (usedSer) {
|
||||
usedSer->println("[TELNET] Telnet server started"); // added by Proddy
|
||||
}
|
||||
}
|
||||
if (telnetServer->hasClient()) {
|
||||
if (client.connected()) {
|
||||
WiFiClient rejectClient = telnetServer->available();
|
||||
if (strlen(rejectMsg) > 0) {
|
||||
rejectClient.write((const uint8_t *)rejectMsg, strlen(rejectMsg));
|
||||
}
|
||||
rejectClient.flush();
|
||||
rejectClient.stop();
|
||||
} else {
|
||||
client = telnetServer->available();
|
||||
if (strlen(welcomeMsg) > 0) {
|
||||
client.write((const uint8_t *)welcomeMsg, strlen(welcomeMsg));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (client.connected()) {
|
||||
if (!connected) {
|
||||
connected = true;
|
||||
if (pingTime != 0) {
|
||||
pingRef = (millis() & 0x7FFFFFF) + pingTime;
|
||||
}
|
||||
if (callbackConnect != NULL) {
|
||||
callbackConnect();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (connected) {
|
||||
connected = false;
|
||||
client.flush();
|
||||
client.stop();
|
||||
pingRef = 0xFFFFFFFF;
|
||||
waitRef = 0xFFFFFFFF;
|
||||
if (callbackDisconnect != NULL) {
|
||||
callbackDisconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (client.connected() && (bufUsed > 0)) {
|
||||
if (bufUsed >= minBlockSize) {
|
||||
sendBlock();
|
||||
} else {
|
||||
unsigned long m = millis() & 0x7FFFFFF;
|
||||
if (waitRef == 0xFFFFFFFF) {
|
||||
waitRef = m + collectingTime;
|
||||
if (waitRef > 0x7FFFFFFF) {
|
||||
waitRef -= 0x80000000;
|
||||
}
|
||||
} else {
|
||||
if (!((waitRef < 0x20000000) && (m > 0x60000000)) && (m >= waitRef)) {
|
||||
sendBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (client.connected() && (pingRef != 0xFFFFFFFF)) {
|
||||
unsigned long m = millis() & 0x7FFFFFF;
|
||||
if (!((pingRef < 0x20000000) && (m > 0x60000000)) && (m >= pingRef)) {
|
||||
addTelnetBuf(0);
|
||||
sendBlock();
|
||||
}
|
||||
}
|
||||
}
|
||||
281
src/TelnetSpy.h
281
src/TelnetSpy.h
@@ -1,281 +0,0 @@
|
||||
/*
|
||||
* TELNET SERVER FOR ESP8266 / ESP32
|
||||
* Cloning the serial port via Telnet.
|
||||
*
|
||||
* Written by Wolfgang Mattis (arduino@yasheena.de).
|
||||
* Version 1.1 / September 7, 2018.
|
||||
* MIT license, all text above must be included in any redistribution.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DESCRIPTION
|
||||
*
|
||||
* This module allows you "Debugging over the air". So if you already use
|
||||
* ArduinoOTA this is a helpful extension for wireless development. Use
|
||||
* "TelnetSpy" instead of "Serial" to send data to the serial port and a copy
|
||||
* to a telnet connection. There is a circular buffer which allows to store the
|
||||
* data while the telnet connection is not established. So its possible to
|
||||
* collect data even when the Wifi and Telnet connections are still not
|
||||
* established. Its also possible to create a telnet session only if it is
|
||||
* neccessary: then you will get the already collected data as far as it is
|
||||
* still stored in the circular buffer. Data send from telnet terminal to
|
||||
* ESP8266 / ESP32 will be handled as data received by serial port. It is also
|
||||
* possible to use more than one instance of TelnetSpy, for example to send
|
||||
* control information on the first instance and data dumps on the second
|
||||
* instance.
|
||||
*
|
||||
* USAGE
|
||||
*
|
||||
* Add the following line to your sketch:
|
||||
* #include <TelnetSpy.h>
|
||||
* TelnetSpy LOG;
|
||||
*
|
||||
* Add the following line to your initialisation block ( void setup() ):
|
||||
* LOG.begin();
|
||||
*
|
||||
* Add the following line at the beginning of your main loop ( void loop() ):
|
||||
* LOG.handle();
|
||||
*
|
||||
* Use the following functions of the TelnetSpy object to modify behavior
|
||||
*
|
||||
* Change the port number of this telnet server. If a client is already
|
||||
* connected it will be disconnected.
|
||||
* Default: 23
|
||||
* void setPort(uint16_t portToUse);
|
||||
*
|
||||
* Change the message which will be send to the telnet client after a session
|
||||
* is established.
|
||||
* Default: "Connection established via TelnetSpy.\n"
|
||||
* void setWelcomeMsg(char* msg);
|
||||
*
|
||||
* Change the message which will be send to the telnet client if another
|
||||
* session is already established.
|
||||
* Default: "TelnetSpy: Only one connection possible.\n"
|
||||
* void setRejectMsg(char* msg);
|
||||
*
|
||||
* Change the amount of characters to collect before sending a telnet block.
|
||||
* Default: 64
|
||||
* void setMinBlockSize(uint16_t minSize);
|
||||
*
|
||||
* Change the time (in ms) to wait before sending a telnet block if its size is
|
||||
* less than <minSize> (defined by setMinBlockSize).
|
||||
* Default: 100
|
||||
* void setCollectingTime(uint16_t colTime);
|
||||
*
|
||||
* Change the maximum size of the telnet packets to send.
|
||||
* Default: 512
|
||||
* void setMaxBlockSize(uint16_t maxSize);
|
||||
*
|
||||
* Change the size of the ring buffer. Set it to 0 to disable buffering.
|
||||
* Changing size tries to preserve the already collected data. If the new
|
||||
* buffer size is too small the youngest data will be preserved only. Returns
|
||||
* false if the requested buffer size cannot be set.
|
||||
* Default: 3000
|
||||
* bool setBufferSize(uint16_t newSize);
|
||||
*
|
||||
* This function returns the actual size of the ring buffer.
|
||||
* uint16_t getBufferSize();
|
||||
*
|
||||
* Enable / disable storing new data in the ring buffer if no telnet connection
|
||||
* is established. This function allows you to store important data only. You
|
||||
* can do this by disabling "storeOffline" for sending less important data.
|
||||
* Default: true
|
||||
* void setStoreOffline(bool store);
|
||||
*
|
||||
* Get actual state of storing data when offline.
|
||||
* bool getStoreOffline();
|
||||
*
|
||||
* If no data is sent via TelnetSpy the detection of a disconnected client has
|
||||
* a long timeout. Use setPingTime to define the time (in ms) without traffic
|
||||
* after which a ping (chr(0)) is sent to the telnet client to detect a
|
||||
* disconnect earlier. Use 0 as parameter to disable pings.
|
||||
* Default: 1500
|
||||
* void setPingTime(uint16_t pngTime);
|
||||
*
|
||||
* Set the serial port you want to use with this object (especially for ESP32)
|
||||
* or NULL if no serial port should be used (telnet only).
|
||||
* Default: Serial
|
||||
* void setSerial(HardwareSerial* usedSerial);
|
||||
*
|
||||
* This function returns true, if a telnet client is connected.
|
||||
* bool isClientConnected();
|
||||
*
|
||||
* This function installs a callback function which will be called on every
|
||||
* telnet connect of this object (except rejected connect tries). Use NULL to
|
||||
* remove the callback.
|
||||
* Default: NULL
|
||||
* void setCallbackOnConnect(void (*callback)());
|
||||
*
|
||||
* This function installs a callback function which will be called on every
|
||||
* telnet disconnect of this object (except rejected connect tries). Use NULL
|
||||
* to remove the callback.
|
||||
* Default: NULL
|
||||
* void setCallbackOnDisconnect(void (*callback)());
|
||||
*
|
||||
* HINT
|
||||
*
|
||||
* Add the following lines to your sketch:
|
||||
* #define SERIAL TelnetSpy
|
||||
* //#define SERIAL Serial
|
||||
*
|
||||
* Replace "Serial" with "SERIAL" in your sketch. Now you can switch between
|
||||
* serial only and serial with telnet by changing the comments of the defines
|
||||
* only.
|
||||
*
|
||||
* IMPORTANT
|
||||
*
|
||||
* To connect to the telnet server you have to:
|
||||
* - establish the Wifi connection
|
||||
* - execute "TelnetSpy.begin(WhatEverYouWant);"
|
||||
*
|
||||
* The order is not important.
|
||||
*
|
||||
* All you do with "Serial" you can also do with "TelnetSpy", but remember:
|
||||
* Transfering data also via telnet will need more performance than the serial
|
||||
* port only. So time critical things may be influenced.
|
||||
*
|
||||
* It is not possible to establish more than one telnet connection at the same
|
||||
* time. But its possible to use more than one instance of TelnetSpy.
|
||||
*
|
||||
* If you have problems with low memory you may reduce the value of the define
|
||||
* TELNETSPY_BUFFER_LEN for a smaller ring buffer on initialisation.
|
||||
*
|
||||
* Usage of void setDebugOutput(bool) to enable / disable of capturing of
|
||||
* os_print calls when you have more than one TelnetSpy instance: That
|
||||
* TelnetSpy object will handle this functionality where you used
|
||||
* setDebugOutput at last. On default TelnetSpy has the capturing of OS_print
|
||||
* calls enabled. So if you have more instances the last created instance will
|
||||
* handle the capturing.
|
||||
*/
|
||||
|
||||
#ifndef TelnetSpy_h
|
||||
#define TelnetSpy_h
|
||||
|
||||
#define TELNETSPY_BUFFER_LEN 1000 // was 3000
|
||||
#define TELNETSPY_MIN_BLOCK_SIZE 64
|
||||
#define TELNETSPY_COLLECTING_TIME 100
|
||||
#define TELNETSPY_MAX_BLOCK_SIZE 512
|
||||
#define TELNETSPY_PING_TIME 1500
|
||||
#define TELNETSPY_PORT 23
|
||||
#define TELNETSPY_CAPTURE_OS_PRINT false
|
||||
#define TELNETSPY_WELCOME_MSG "Connection established via Telnet.\n"
|
||||
#define TELNETSPY_REJECT_MSG "Telnet: Only one connection possible.\n"
|
||||
|
||||
#ifdef ESP8266
|
||||
#include <ESP8266WiFi.h>
|
||||
#else // ESP32
|
||||
#include <WiFi.h>
|
||||
#endif
|
||||
#include <WiFiClient.h>
|
||||
|
||||
class TelnetSpy : public Stream {
|
||||
public:
|
||||
TelnetSpy();
|
||||
~TelnetSpy();
|
||||
void handle(void);
|
||||
void setPort(uint16_t portToUse);
|
||||
void setWelcomeMsg(const char * msg);
|
||||
void setRejectMsg(const char * msg);
|
||||
void setMinBlockSize(uint16_t minSize);
|
||||
void setCollectingTime(uint16_t colTime);
|
||||
void setMaxBlockSize(uint16_t maxSize);
|
||||
bool setBufferSize(uint16_t newSize);
|
||||
uint16_t getBufferSize();
|
||||
void setStoreOffline(bool store);
|
||||
bool getStoreOffline();
|
||||
void setPingTime(uint16_t pngTime);
|
||||
void setSerial(HardwareSerial * usedSerial);
|
||||
bool isClientConnected();
|
||||
void serialPrint(char c);
|
||||
|
||||
void disconnectClient(); // added by Proddy
|
||||
typedef std::function<void()> telnetSpyCallback; // added by Proddy
|
||||
void setCallbackOnConnect(telnetSpyCallback callback); // changed by proddy
|
||||
void setCallbackOnDisconnect(telnetSpyCallback callback); // changed by proddy
|
||||
|
||||
// Functions offered by HardwareSerial class:
|
||||
#ifdef ESP8266
|
||||
void begin(unsigned long baud) {
|
||||
begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
|
||||
}
|
||||
void begin(unsigned long baud, SerialConfig config) {
|
||||
begin(baud, config, SERIAL_FULL, 1);
|
||||
}
|
||||
void begin(unsigned long baud, SerialConfig config, SerialMode mode) {
|
||||
begin(baud, config, mode, 1);
|
||||
}
|
||||
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
|
||||
#else // ESP32
|
||||
void begin(unsigned long baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false);
|
||||
#endif
|
||||
void end();
|
||||
#ifdef ESP8266
|
||||
void swap() {
|
||||
swap(1);
|
||||
}
|
||||
void swap(uint8_t tx_pin);
|
||||
void set_tx(uint8_t tx_pin);
|
||||
void pins(uint8_t tx, uint8_t rx);
|
||||
bool isTxEnabled(void);
|
||||
bool isRxEnabled(void);
|
||||
#endif
|
||||
int available(void) override;
|
||||
int peek(void) override;
|
||||
int read(void) override;
|
||||
int availableForWrite(void);
|
||||
void flush(void) override;
|
||||
size_t write(uint8_t) override;
|
||||
inline size_t write(unsigned long n) {
|
||||
return write((uint8_t)n);
|
||||
}
|
||||
inline size_t write(long n) {
|
||||
return write((uint8_t)n);
|
||||
}
|
||||
inline size_t write(unsigned int n) {
|
||||
return write((uint8_t)n);
|
||||
}
|
||||
inline size_t write(int n) {
|
||||
return write((uint8_t)n);
|
||||
}
|
||||
using Print::write;
|
||||
operator bool() const;
|
||||
void setDebugOutput(bool);
|
||||
uint32_t baudRate(void);
|
||||
|
||||
bool isSerialAvailable(void);
|
||||
|
||||
protected:
|
||||
void sendBlock(void);
|
||||
void addTelnetBuf(char c);
|
||||
char pullTelnetBuf();
|
||||
char peekTelnetBuf();
|
||||
int telnetAvailable();
|
||||
WiFiServer * telnetServer;
|
||||
WiFiClient client;
|
||||
uint16_t port;
|
||||
HardwareSerial * usedSer;
|
||||
bool storeOffline;
|
||||
bool started;
|
||||
bool listening;
|
||||
bool firstMainLoop;
|
||||
unsigned long waitRef;
|
||||
unsigned long pingRef;
|
||||
uint16_t pingTime;
|
||||
char * welcomeMsg;
|
||||
char * rejectMsg;
|
||||
uint16_t minBlockSize;
|
||||
uint16_t collectingTime;
|
||||
uint16_t maxBlockSize;
|
||||
bool debugOutput;
|
||||
char * telnetBuf;
|
||||
uint16_t bufLen;
|
||||
uint16_t bufUsed;
|
||||
uint16_t bufRdIdx;
|
||||
uint16_t bufWrIdx;
|
||||
bool connected;
|
||||
|
||||
telnetSpyCallback callbackConnect; // added by proddy
|
||||
telnetSpyCallback callbackDisconnect; // added by proddy
|
||||
};
|
||||
|
||||
#endif
|
||||
179
src/TimeLib.cpp
179
src/TimeLib.cpp
@@ -1,179 +0,0 @@
|
||||
/*
|
||||
* customized version of Time library, originally Copyright (c) Michael Margolis 2009-2014
|
||||
* modified by Paul S https://github.com/PaulStoffregen/Time
|
||||
*/
|
||||
|
||||
#include "TimeLib.h"
|
||||
|
||||
static tmElements_t tm; // a cache of time elements
|
||||
static time_t cacheTime; // the time the cache was updated
|
||||
static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds
|
||||
static uint32_t sysTime = 0;
|
||||
static uint32_t prevMillis = 0;
|
||||
static uint32_t nextSyncTime = 0;
|
||||
static timeStatus_t Status = timeNotSet;
|
||||
getExternalTime getTimePtr; // pointer to external sync function
|
||||
|
||||
#define LEAP_YEAR(Y) (((1970 + (Y)) > 0) && !((1970 + (Y)) % 4) && (((1970 + (Y)) % 100) || !((1970 + (Y)) % 400)))
|
||||
static const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // API starts months from 1, this array starts from 0
|
||||
|
||||
time_t now() {
|
||||
// calculate number of seconds passed since last call to now()
|
||||
while (millis() - prevMillis >= 1000) {
|
||||
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
|
||||
sysTime++;
|
||||
prevMillis += 1000;
|
||||
}
|
||||
if (nextSyncTime <= sysTime) {
|
||||
if (getTimePtr != 0) {
|
||||
time_t t = getTimePtr();
|
||||
if (t != 0) {
|
||||
setTime(t);
|
||||
} else {
|
||||
nextSyncTime = sysTime + syncInterval;
|
||||
Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (time_t)sysTime;
|
||||
}
|
||||
|
||||
void setSyncProvider(getExternalTime getTimeFunction) {
|
||||
getTimePtr = getTimeFunction;
|
||||
nextSyncTime = sysTime;
|
||||
now(); // this will sync the clock
|
||||
}
|
||||
|
||||
void setSyncInterval(time_t interval) { // set the number of seconds between re-sync
|
||||
syncInterval = (uint32_t)interval;
|
||||
nextSyncTime = sysTime + syncInterval;
|
||||
}
|
||||
|
||||
void breakTime(time_t timeInput, tmElements_t & tm) {
|
||||
// break the given time_t into time components
|
||||
// this is a more compact version of the C library localtime function
|
||||
// note that year is offset from 1970 !!!
|
||||
|
||||
uint8_t year;
|
||||
uint8_t month, monthLength;
|
||||
uint32_t time;
|
||||
unsigned long days;
|
||||
|
||||
time = (uint32_t)timeInput;
|
||||
tm.Second = time % 60;
|
||||
time /= 60; // now it is minutes
|
||||
tm.Minute = time % 60;
|
||||
time /= 60; // now it is hours
|
||||
tm.Hour = time % 24;
|
||||
time /= 24; // now it is days
|
||||
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
|
||||
|
||||
year = 0;
|
||||
days = 0;
|
||||
while ((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
|
||||
year++;
|
||||
}
|
||||
tm.Year = year; // year is offset from 1970
|
||||
|
||||
days -= LEAP_YEAR(year) ? 366 : 365;
|
||||
time -= days; // now it is days in this year, starting at 0
|
||||
|
||||
month = 0;
|
||||
monthLength = 0;
|
||||
for (month = 0; month < 12; month++) {
|
||||
if (month == 1) { // february
|
||||
if (LEAP_YEAR(year)) {
|
||||
monthLength = 29;
|
||||
} else {
|
||||
monthLength = 28;
|
||||
}
|
||||
} else {
|
||||
monthLength = monthDays[month];
|
||||
}
|
||||
|
||||
if (time >= monthLength) {
|
||||
time -= monthLength;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
tm.Month = month + 1; // jan is month 1
|
||||
tm.Day = time + 1; // day of month
|
||||
}
|
||||
|
||||
// assemble time elements into time_t
|
||||
time_t makeTime(const tmElements_t & tm) {
|
||||
int i;
|
||||
uint32_t seconds;
|
||||
|
||||
// seconds from 1970 till 1 jan 00:00:00 of the given year
|
||||
seconds = tm.Year * (SECS_PER_DAY * 365);
|
||||
for (i = 0; i < tm.Year; i++) {
|
||||
if (LEAP_YEAR(i)) {
|
||||
seconds += SECS_PER_DAY; // add extra days for leap years
|
||||
}
|
||||
}
|
||||
|
||||
// add days for this year, months start from 1
|
||||
for (i = 1; i < tm.Month; i++) {
|
||||
if ((i == 2) && LEAP_YEAR(tm.Year)) {
|
||||
seconds += SECS_PER_DAY * 29;
|
||||
} else {
|
||||
seconds += SECS_PER_DAY * monthDays[i - 1]; // monthDay array starts from 0
|
||||
}
|
||||
}
|
||||
seconds += (tm.Day - 1) * SECS_PER_DAY;
|
||||
seconds += tm.Hour * SECS_PER_HOUR;
|
||||
seconds += tm.Minute * SECS_PER_MIN;
|
||||
seconds += tm.Second;
|
||||
return (time_t)seconds;
|
||||
}
|
||||
|
||||
void refreshCache(time_t t) {
|
||||
if (t != cacheTime) {
|
||||
breakTime(t, tm);
|
||||
cacheTime = t;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t to_second(time_t t) { // the second for the given time
|
||||
refreshCache(t);
|
||||
return tm.Second;
|
||||
}
|
||||
|
||||
uint8_t to_minute(time_t t) { // the minute for the given time
|
||||
refreshCache(t);
|
||||
return tm.Minute;
|
||||
}
|
||||
|
||||
uint8_t to_hour(time_t t) { // the hour for the given time
|
||||
refreshCache(t);
|
||||
return tm.Hour;
|
||||
}
|
||||
|
||||
uint8_t to_day(time_t t) { // the day for the given time (0-6)
|
||||
refreshCache(t);
|
||||
return tm.Day;
|
||||
}
|
||||
|
||||
uint8_t to_month(time_t t) { // the month for the given time
|
||||
refreshCache(t);
|
||||
return tm.Month;
|
||||
}
|
||||
|
||||
uint8_t to_weekday(time_t t) {
|
||||
refreshCache(t);
|
||||
return tm.Wday;
|
||||
}
|
||||
|
||||
uint16_t to_year(time_t t) { // the year for the given time
|
||||
refreshCache(t);
|
||||
return tm.Year + 1970;
|
||||
}
|
||||
|
||||
void setTime(time_t t) {
|
||||
sysTime = (uint32_t)t;
|
||||
nextSyncTime = (uint32_t)t + syncInterval;
|
||||
Status = timeSet;
|
||||
prevMillis = millis(); // restart counting from now (thanks to Korman for this fix)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* customized version of Time library, originally Copyright (c) Michael Margolis 2009-2014
|
||||
* modified by Paul S https://github.com/PaulStoffregen/Time
|
||||
*/
|
||||
|
||||
#ifndef _TimeLib_h
|
||||
#define _TimeLib_h
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#define SECS_PER_MIN ((time_t)(60UL))
|
||||
#define SECS_PER_HOUR ((time_t)(3600UL))
|
||||
#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL))
|
||||
#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year
|
||||
|
||||
// This ugly hack allows us to define C++ overloaded functions, when included
|
||||
// from within an extern "C", as newlib's sys/stat.h does. Actually it is
|
||||
// intended to include "time.h" from the C library (on ARM, but AVR does not
|
||||
// have that file at all). On Mac and Windows, the compiler will find this
|
||||
// "Time.h" instead of the C library "time.h", so we may cause other weird
|
||||
// and unpredictable effects by conflicting with the C library header "time.h",
|
||||
// but at least this hack lets us define C++ functions as intended. Hopefully
|
||||
// nothing too terrible will result from overriding the C library header?!
|
||||
extern "C++" {
|
||||
typedef enum { timeNotSet, timeNeedsSync, timeSet } timeStatus_t;
|
||||
|
||||
typedef struct {
|
||||
uint8_t Second;
|
||||
uint8_t Minute;
|
||||
uint8_t Hour;
|
||||
uint8_t Wday; // day of week, sunday is day 1
|
||||
uint8_t Day;
|
||||
uint8_t Month;
|
||||
uint8_t Year; // offset from 1970;
|
||||
} tmElements_t;
|
||||
|
||||
typedef time_t (*getExternalTime)();
|
||||
|
||||
time_t now(); // return the current time as seconds since Jan 1 1970
|
||||
void setTime(time_t t);
|
||||
timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized
|
||||
void setSyncProvider(getExternalTime getTimeFunction); // identify the external time provider
|
||||
void setSyncInterval(time_t interval); // set the number of seconds between re-sync
|
||||
time_t makeTime(const tmElements_t & tm); // convert time elements into time_t
|
||||
|
||||
uint8_t to_hour(time_t t); // the hour for the given time
|
||||
uint8_t to_minute(time_t t); // the minute for the given time
|
||||
uint8_t to_second(time_t t); // the second for the given time
|
||||
uint8_t to_day(time_t t); // the day for the given time (0-6)
|
||||
uint8_t to_month(time_t t); // the month for the given time
|
||||
uint8_t to_weekday(time_t t); // weekday, sunday is day 1
|
||||
uint16_t to_year(time_t t); // the year for the given time
|
||||
|
||||
}
|
||||
#endif
|
||||
200
src/Timezone.cpp
200
src/Timezone.cpp
@@ -1,200 +0,0 @@
|
||||
/*----------------------------------------------------------------------*
|
||||
* Arduino Timezone Library *
|
||||
* Jack Christensen Mar 2012 *
|
||||
* *
|
||||
* Stripped down for myESP by Paul Derbyshire *
|
||||
* *
|
||||
* Arduino Timezone Library Copyright (C) 2018 by Jack Christensen and *
|
||||
* licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html *
|
||||
*----------------------------------------------------------------------*/
|
||||
|
||||
#include "Timezone.h"
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Create a Timezone object from the given time change rules. *
|
||||
*----------------------------------------------------------------------*/
|
||||
Timezone::Timezone(TimeChangeRule dstStart, TimeChangeRule stdStart)
|
||||
: m_dst(dstStart)
|
||||
, m_std(stdStart) {
|
||||
initTimeChanges();
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Create a Timezone object for a zone that does not observe *
|
||||
* daylight time. *
|
||||
*----------------------------------------------------------------------*/
|
||||
Timezone::Timezone(TimeChangeRule stdTime)
|
||||
: m_dst(stdTime)
|
||||
, m_std(stdTime) {
|
||||
initTimeChanges();
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Convert the given UTC time to local time, standard or *
|
||||
* daylight time, as appropriate. *
|
||||
*----------------------------------------------------------------------*/
|
||||
time_t Timezone::toLocal(time_t utc) {
|
||||
// recalculate the time change points if needed
|
||||
if (to_year(utc) != to_year(m_dstUTC))
|
||||
calcTimeChanges(to_year(utc));
|
||||
|
||||
if (utcIsDST(utc))
|
||||
return utc + m_dst.offset * SECS_PER_MIN;
|
||||
else
|
||||
return utc + m_std.offset * SECS_PER_MIN;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Convert the given UTC time to local time, standard or *
|
||||
* daylight time, as appropriate, and return a pointer to the time *
|
||||
* change rule used to do the conversion. The caller must take care *
|
||||
* not to alter this rule. *
|
||||
*----------------------------------------------------------------------*/
|
||||
time_t Timezone::toLocal(time_t utc, TimeChangeRule ** tcr) {
|
||||
// recalculate the time change points if needed
|
||||
if (to_year(utc) != to_year(m_dstUTC))
|
||||
calcTimeChanges(to_year(utc));
|
||||
|
||||
if (utcIsDST(utc)) {
|
||||
*tcr = &m_dst;
|
||||
return utc + m_dst.offset * SECS_PER_MIN;
|
||||
} else {
|
||||
*tcr = &m_std;
|
||||
return utc + m_std.offset * SECS_PER_MIN;
|
||||
}
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Convert the given local time to UTC time. *
|
||||
* *
|
||||
* WARNING: *
|
||||
* This function is provided for completeness, but should seldom be *
|
||||
* needed and should be used sparingly and carefully. *
|
||||
* *
|
||||
* Ambiguous situations occur after the Standard-to-DST and the *
|
||||
* DST-to-Standard time transitions. When changing to DST, there is *
|
||||
* one hour of local time that does not exist, since the clock moves *
|
||||
* forward one hour. Similarly, when changing to standard time, there *
|
||||
* is one hour of local times that occur twice since the clock moves *
|
||||
* back one hour. *
|
||||
* *
|
||||
* This function does not test whether it is passed an erroneous time *
|
||||
* value during the Local -> DST transition that does not exist. *
|
||||
* If passed such a time, an incorrect UTC time value will be returned. *
|
||||
* *
|
||||
* If passed a local time value during the DST -> Local transition *
|
||||
* that occurs twice, it will be treated as the earlier time, i.e. *
|
||||
* the time that occurs before the transistion. *
|
||||
* *
|
||||
* Calling this function with local times during a transition interval *
|
||||
* should be avoided! *
|
||||
*----------------------------------------------------------------------*/
|
||||
time_t Timezone::toUTC(time_t local) {
|
||||
// recalculate the time change points if needed
|
||||
if (to_year(local) != to_year(m_dstLoc))
|
||||
calcTimeChanges(to_year(local));
|
||||
|
||||
if (locIsDST(local))
|
||||
return local - m_dst.offset * SECS_PER_MIN;
|
||||
else
|
||||
return local - m_std.offset * SECS_PER_MIN;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Determine whether the given UTC time_t is within the DST interval *
|
||||
* or the Standard time interval. *
|
||||
*----------------------------------------------------------------------*/
|
||||
bool Timezone::utcIsDST(time_t utc) {
|
||||
// recalculate the time change points if needed
|
||||
if (to_year(utc) != to_year(m_dstUTC))
|
||||
calcTimeChanges(to_year(utc));
|
||||
|
||||
if (m_stdUTC == m_dstUTC) // daylight time not observed in this tz
|
||||
return false;
|
||||
else if (m_stdUTC > m_dstUTC) // northern hemisphere
|
||||
return (utc >= m_dstUTC && utc < m_stdUTC);
|
||||
else // southern hemisphere
|
||||
return !(utc >= m_stdUTC && utc < m_dstUTC);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Determine whether the given Local time_t is within the DST interval *
|
||||
* or the Standard time interval. *
|
||||
*----------------------------------------------------------------------*/
|
||||
bool Timezone::locIsDST(time_t local) {
|
||||
// recalculate the time change points if needed
|
||||
if (to_year(local) != to_year(m_dstLoc))
|
||||
calcTimeChanges(to_year(local));
|
||||
|
||||
if (m_stdUTC == m_dstUTC) // daylight time not observed in this tz
|
||||
return false;
|
||||
else if (m_stdLoc > m_dstLoc) // northern hemisphere
|
||||
return (local >= m_dstLoc && local < m_stdLoc);
|
||||
else // southern hemisphere
|
||||
return !(local >= m_stdLoc && local < m_dstLoc);
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Calculate the DST and standard time change points for the given *
|
||||
* given year as local and UTC time_t values. *
|
||||
*----------------------------------------------------------------------*/
|
||||
void Timezone::calcTimeChanges(int yr) {
|
||||
m_dstLoc = toTime_t(m_dst, yr);
|
||||
m_stdLoc = toTime_t(m_std, yr);
|
||||
m_dstUTC = m_dstLoc - m_std.offset * SECS_PER_MIN;
|
||||
m_stdUTC = m_stdLoc - m_dst.offset * SECS_PER_MIN;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Initialize the DST and standard time change points. *
|
||||
*----------------------------------------------------------------------*/
|
||||
void Timezone::initTimeChanges() {
|
||||
m_dstLoc = 0;
|
||||
m_stdLoc = 0;
|
||||
m_dstUTC = 0;
|
||||
m_stdUTC = 0;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Convert the given time change rule to a time_t value *
|
||||
* for the given year. *
|
||||
*----------------------------------------------------------------------*/
|
||||
time_t Timezone::toTime_t(TimeChangeRule r, int yr) {
|
||||
uint8_t m = r.month; // temp copies of r.month and r.week
|
||||
uint8_t w = r.week;
|
||||
if (w == 0) // is this a "Last week" rule?
|
||||
{
|
||||
if (++m > 12) // yes, for "Last", go to the next month
|
||||
{
|
||||
m = 1;
|
||||
++yr;
|
||||
}
|
||||
w = 1; // and treat as first week of next month, subtract 7 days later
|
||||
}
|
||||
|
||||
// calculate first day of the month, or for "Last" rules, first day of the next month
|
||||
tmElements_t tm;
|
||||
tm.Hour = r.hour;
|
||||
tm.Minute = 0;
|
||||
tm.Second = 0;
|
||||
tm.Day = 1;
|
||||
tm.Month = m;
|
||||
tm.Year = yr - 1970;
|
||||
time_t t = makeTime(tm);
|
||||
|
||||
// add offset from the first of the month to r.dow, and offset for the given week
|
||||
t += ((r.dow - to_weekday(t) + 7) % 7 + (w - 1) * 7) * SECS_PER_DAY;
|
||||
// back up a week if this is a "Last" rule
|
||||
if (r.week == 0)
|
||||
t -= 7 * SECS_PER_DAY;
|
||||
return t;
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------*
|
||||
* Read or update the daylight and standard time rules from RAM. *
|
||||
*----------------------------------------------------------------------*/
|
||||
void Timezone::setRules(TimeChangeRule dstStart, TimeChangeRule stdStart) {
|
||||
m_dst = dstStart;
|
||||
m_std = stdStart;
|
||||
initTimeChanges(); // force calcTimeChanges() at next conversion call
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*----------------------------------------------------------------------*
|
||||
* Arduino Timezone Library *
|
||||
* Jack Christensen Mar 2012 *
|
||||
* *
|
||||
* Arduino Timezone Library Copyright (C) 2018 by Jack Christensen and *
|
||||
* licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html *
|
||||
*----------------------------------------------------------------------*/
|
||||
|
||||
#ifndef TIMEZONE_H_INCLUDED
|
||||
#define TIMEZONE_H_INCLUDED
|
||||
#include <Arduino.h>
|
||||
#include <TimeLib.h>
|
||||
|
||||
// convenient constants for TimeChangeRules
|
||||
enum week_t { Last, First, Second, Third, Fourth };
|
||||
enum dow_t { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat };
|
||||
enum month_t { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec };
|
||||
|
||||
// structure to describe rules for when daylight/summer time begins,
|
||||
// or when standard time begins.
|
||||
struct TimeChangeRule {
|
||||
char abbrev[6]; // five chars max
|
||||
uint8_t week; // First, Second, Third, Fourth, or Last week of the month
|
||||
uint8_t dow; // day of week, 1=Sun, 2=Mon, ... 7=Sat
|
||||
uint8_t month; // 1=Jan, 2=Feb, ... 12=Dec
|
||||
uint8_t hour; // 0-23
|
||||
int offset; // offset from UTC in minutes
|
||||
};
|
||||
|
||||
class Timezone {
|
||||
public:
|
||||
Timezone(TimeChangeRule dstStart, TimeChangeRule stdStart);
|
||||
Timezone(TimeChangeRule stdTime);
|
||||
time_t toLocal(time_t utc);
|
||||
time_t toLocal(time_t utc, TimeChangeRule ** tcr);
|
||||
time_t toUTC(time_t local);
|
||||
bool utcIsDST(time_t utc);
|
||||
bool locIsDST(time_t local);
|
||||
void setRules(TimeChangeRule dstStart, TimeChangeRule stdStart);
|
||||
|
||||
private:
|
||||
void calcTimeChanges(int yr);
|
||||
void initTimeChanges();
|
||||
time_t toTime_t(TimeChangeRule r, int yr);
|
||||
TimeChangeRule m_dst; // rule for start of dst or summer time for any year
|
||||
TimeChangeRule m_std; // rule for start of standard time for any year
|
||||
time_t m_dstUTC; // dst start for given/current year, given in UTC
|
||||
time_t m_stdUTC; // std time start for given/current year, given in UTC
|
||||
time_t m_dstLoc; // dst start for given/current year, given in local time
|
||||
time_t m_stdLoc; // std time start for given/current year, given in local time
|
||||
};
|
||||
|
||||
#endif
|
||||
553
src/console.cpp
Normal file
553
src/console.cpp
Normal file
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* 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 "console.h"
|
||||
#include "emsesp.h"
|
||||
#include "version.h"
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
#include "test/test.h"
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
std::shared_ptr<Commands> EMSESPShell::commands = [] {
|
||||
std::shared_ptr<Commands> commands = std::make_shared<Commands>();
|
||||
return commands;
|
||||
}();
|
||||
|
||||
static std::shared_ptr<EMSESPShell> shell;
|
||||
|
||||
std::vector<bool> EMSESPStreamConsole::ptys_;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uuid::telnet::TelnetService telnet_([](Stream & stream, const IPAddress & addr, uint16_t port) -> std::shared_ptr<uuid::console::Shell> {
|
||||
return std::make_shared<EMSESPStreamConsole>(stream, addr, port);
|
||||
});
|
||||
#endif
|
||||
|
||||
EMSESPShell::EMSESPShell()
|
||||
: Shell() {
|
||||
}
|
||||
|
||||
void EMSESPShell::started() {
|
||||
logger().log(LogLevel::INFO, LogFacility::CONSOLE, F("User session opened on console %s"), console_name().c_str());
|
||||
|
||||
// set console name
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { console_hostname_ = wifiSettings.hostname.c_str(); });
|
||||
|
||||
if (console_hostname_.empty()) {
|
||||
console_hostname_.resize(16, '\0');
|
||||
|
||||
|
||||
#if defined(ESP8266)
|
||||
snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp8266"));
|
||||
#else
|
||||
snprintf_P(&console_hostname_[0], console_hostname_.capacity() + 1, PSTR("esp32"));
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void EMSESPShell::stopped() {
|
||||
if (has_flags(CommandFlags::ADMIN)) {
|
||||
logger().log(LogLevel::INFO, LogFacility::AUTH, F("Admin session closed on console %s"), console_name().c_str());
|
||||
}
|
||||
logger().log(LogLevel::INFO, LogFacility::CONSOLE, F("User session closed on console %s"), console_name().c_str());
|
||||
|
||||
// remove all custom contexts
|
||||
commands->remove_all_commands();
|
||||
|
||||
console_commands_loaded_ = false; // make sure they get reloaded next time a console is opened
|
||||
}
|
||||
|
||||
// show welcome banner
|
||||
void EMSESPShell::display_banner() {
|
||||
println();
|
||||
printfln(F("┌──────────────────────────────────────────┐"));
|
||||
printfln(F("│ %sEMS-ESP version %-10s%s │"), COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_BOLD_OFF);
|
||||
printfln(F("│ %s%shttps://github.com/proddy/EMS-ESP%s │"), COLOR_BRIGHT_GREEN, COLOR_UNDERLINE, COLOR_RESET);
|
||||
printfln(F("│ │"));
|
||||
printfln(F("│ type %shelp%s to show available commands │"), COLOR_UNDERLINE, COLOR_RESET);
|
||||
printfln(F("└──────────────────────────────────────────┘"));
|
||||
println();
|
||||
|
||||
// load the list of commands
|
||||
add_console_commands();
|
||||
|
||||
// turn off watch
|
||||
emsesp::EMSESP::watch_id(WATCH_ID_NONE);
|
||||
}
|
||||
|
||||
// pre-loads all the console commands into the MAIN context
|
||||
// This is only done after a connection is established, to save on Heap memory
|
||||
void EMSESPShell::add_console_commands() {
|
||||
// if we already have these commands loaded, stop adding duplicates
|
||||
// for example when opening multiple serial/telnet sessions
|
||||
if (console_commands_loaded_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// just in case, remove everything
|
||||
// commands->remove_context_commands(ShellContext::MAIN);
|
||||
commands->remove_all_commands();
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(refresh)},
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.printfln(F("Requesting data from EMS devices"));
|
||||
console_commands_loaded_ = false;
|
||||
add_console_commands();
|
||||
EMSESP::fetch_device_values();
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.printfln(F("%s%sEMS-ESP version %s%s"), COLOR_BRIGHT_GREEN, COLOR_BOLD_ON, EMSESP_APP_VERSION, COLOR_RESET);
|
||||
shell.println();
|
||||
EMSESP::show_device_values(shell);
|
||||
EMSESP::show_sensor_values(shell);
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show), F_(devices)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_devices(shell); });
|
||||
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show), F_(ems)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_ems(shell); });
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show), F_(values)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { EMSESP::show_device_values(shell); });
|
||||
|
||||
commands->add_command(
|
||||
ShellContext::MAIN,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(set), F_(bus_id)},
|
||||
flash_string_vector{F_(deviceid_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
uint8_t device_id = Helpers::hextoint(arguments.front().c_str());
|
||||
if ((device_id == 0x0B) || (device_id == 0x0D) || (device_id == 0x0A) || (device_id == 0x0F) || (device_id == 0x12)) {
|
||||
EMSESP::emsespSettingsService.update(
|
||||
[&](EMSESPSettings & settings) {
|
||||
settings.ems_bus_id = device_id;
|
||||
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
} else {
|
||||
shell.println(F("Must be 0B, 0D, 0A, 0F or 12"));
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{
|
||||
read_flash_string(F("0B")),
|
||||
read_flash_string(F("0D")),
|
||||
read_flash_string(F("0A")),
|
||||
read_flash_string(F("0F")),
|
||||
read_flash_string(F("12")),
|
||||
|
||||
};
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(set), F_(tx_mode)},
|
||||
flash_string_vector{F_(n_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
uint8_t tx_mode = std::strtol(arguments[0].c_str(), nullptr, 10);
|
||||
|
||||
EMSESP::emsespSettingsService.update(
|
||||
[&](EMSESPSettings & settings) {
|
||||
settings.tx_mode = tx_mode;
|
||||
shell.printfln(F_(tx_mode_fmt), tx_mode);
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
EMSESP::reset_tx(tx_mode); // reset counters and set tx_mode
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(scan), F_(devices)},
|
||||
flash_string_vector{F_(deep_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments.size() == 0) {
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_UBADevices, EMSdevice::EMS_DEVICE_ID_BOILER);
|
||||
} else {
|
||||
shell.printfln(F("Performing a deep scan by pinging our device library..."));
|
||||
std::vector<uint8_t> Device_Ids;
|
||||
|
||||
Device_Ids.push_back(0x08); // Boilers - 0x08
|
||||
Device_Ids.push_back(0x38); // HeatPump - 0x38
|
||||
Device_Ids.push_back(0x30); // Solar Module - 0x30
|
||||
Device_Ids.push_back(0x09); // Controllers - 0x09
|
||||
Device_Ids.push_back(0x02); // Connect - 0x02
|
||||
Device_Ids.push_back(0x48); // Gateway - 0x48
|
||||
Device_Ids.push_back(0x20); // Mixing Devices - 0x20
|
||||
Device_Ids.push_back(0x21); // Mixing Devices - 0x21
|
||||
Device_Ids.push_back(0x22); // Mixing Devices - 0x22
|
||||
Device_Ids.push_back(0x23); // Mixing Devices - 0x23
|
||||
Device_Ids.push_back(0x28); // Mixing Devices WW- 0x28
|
||||
Device_Ids.push_back(0x29); // Mixing Devices WW- 0x29
|
||||
Device_Ids.push_back(0x10); // Thermostats - 0x10
|
||||
Device_Ids.push_back(0x17); // Thermostats - 0x17
|
||||
Device_Ids.push_back(0x18); // Thermostat remote - 0x18
|
||||
Device_Ids.push_back(0x19); // Thermostat remote - 0x19
|
||||
Device_Ids.push_back(0x1A); // Thermostat remote - 0x1A
|
||||
Device_Ids.push_back(0x1B); // Thermostat remote - 0x1B
|
||||
Device_Ids.push_back(0x11); // Switches - 0x11
|
||||
|
||||
// send the read command with Version command
|
||||
for (const uint8_t device_id : Device_Ids) {
|
||||
EMSESP::send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(set)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
shell.printfln(F_(tx_mode_fmt), settings.tx_mode);
|
||||
shell.printfln(F_(bus_id_fmt), settings.ems_bus_id);
|
||||
});
|
||||
});
|
||||
|
||||
/*
|
||||
* add all the submenu contexts...
|
||||
*/
|
||||
|
||||
// System
|
||||
commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(system)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
System::console_commands(shell, ShellContext::SYSTEM);
|
||||
});
|
||||
|
||||
// add all the context menus for the connected devices
|
||||
// this assumes they devices have been detected and pre-registered
|
||||
EMSESP::add_context_menus();
|
||||
|
||||
Console::load_standard_commands(ShellContext::MAIN);
|
||||
|
||||
console_commands_loaded_ = true;
|
||||
}
|
||||
|
||||
std::string EMSESPShell::hostname_text() {
|
||||
return console_hostname_;
|
||||
}
|
||||
|
||||
// remove commands from the current context to save memory before exiting
|
||||
bool EMSESPShell::exit_context() {
|
||||
unsigned int current_context = context();
|
||||
commands->remove_context_commands(current_context);
|
||||
|
||||
if (current_context == ShellContext::MAIN) {
|
||||
Shell::stop();
|
||||
return true;
|
||||
}
|
||||
return Shell::exit_context();
|
||||
}
|
||||
|
||||
// enter a custom context (sub-menu)
|
||||
void Console::enter_custom_context(Shell & shell, unsigned int context) {
|
||||
load_standard_commands(context);
|
||||
|
||||
// don't go into the new context if it's already the root (to prevent double loading)
|
||||
if (context != ShellContext::MAIN) {
|
||||
shell.enter_context(context);
|
||||
}
|
||||
}
|
||||
|
||||
// each custom context has the common commands like log, help, exit, su etc
|
||||
void Console::load_standard_commands(unsigned int context) {
|
||||
#ifdef EMSESP_DEBUG
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(test)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
Test::run_test(shell, arguments.front());
|
||||
});
|
||||
#endif
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(log)},
|
||||
flash_string_vector{F_(log_level_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (!arguments.empty()) {
|
||||
uuid::log::Level level;
|
||||
if (uuid::log::parse_level_lowercase(arguments[0], level)) {
|
||||
shell.log_level(level);
|
||||
} else {
|
||||
shell.printfln(F_(invalid_log_level));
|
||||
return;
|
||||
}
|
||||
}
|
||||
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(shell.log_level()));
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> std::vector<std::string> {
|
||||
return uuid::log::levels_lowercase();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(help)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.print_all_available_commands();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(exit)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
// delete MAIN console stuff first to save memory
|
||||
EMSESPShell::commands->remove_context_commands(context);
|
||||
shell.exit_context();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(su)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
auto become_admin = [](Shell & shell) {
|
||||
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Admin session opened on console"));
|
||||
shell.add_flags(CommandFlags::ADMIN);
|
||||
};
|
||||
|
||||
if (shell.has_flags(CommandFlags::LOCAL)) {
|
||||
become_admin(shell);
|
||||
} else {
|
||||
shell.enter_password(F_(password_prompt), [=](Shell & shell, bool completed, const std::string & password) {
|
||||
if (completed) {
|
||||
uint64_t now = uuid::get_uptime_ms();
|
||||
|
||||
EMSESP::esp8266React.getSecuritySettingsService()->read([&](SecuritySettings & securitySettings) {
|
||||
if (securitySettings.jwtSecret = password.c_str()) {
|
||||
become_admin(shell);
|
||||
} else {
|
||||
shell.delay_until(now + INVALID_PASSWORD_DELAY_MS, [](Shell & shell) {
|
||||
shell.logger().log(LogLevel::NOTICE, LogFacility::AUTH, F("Invalid admin password on console"));
|
||||
shell.println(F("su: incorrect password"));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(send), F_(telegram)},
|
||||
flash_string_vector{F_(data_mandatory)},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
EMSESP::send_raw_telegram(arguments.front().c_str());
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(context,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(watch)},
|
||||
flash_string_vector{F_(watch_format_mandatory), F_(watchid_optional)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
// get raw/pretty
|
||||
if (arguments[0] == read_flash_string(F_(raw))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_RAW); // raw
|
||||
} else if (arguments[0] == read_flash_string(F_(on))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_ON); // on
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
emsesp::EMSESP::watch(EMSESP::WATCH_OFF); // off
|
||||
} else {
|
||||
shell.printfln(F_(invalid_watch));
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t watch_id;
|
||||
if (arguments.size() == 2) {
|
||||
// get the watch_id if its set
|
||||
watch_id = Helpers::hextoint(arguments[1].c_str());
|
||||
} else {
|
||||
watch_id = WATCH_ID_NONE;
|
||||
}
|
||||
|
||||
emsesp::EMSESP::watch_id(watch_id);
|
||||
|
||||
uint8_t watch = emsesp::EMSESP::watch();
|
||||
if (watch == EMSESP::WATCH_OFF) {
|
||||
shell.printfln(F("Watching telegrams is off"));
|
||||
return;
|
||||
}
|
||||
|
||||
// if logging is off, the watch won't show anything, show force it back to NOTICE
|
||||
if (!shell.logger().enabled(Level::NOTICE)) {
|
||||
shell.log_level(Level::NOTICE);
|
||||
}
|
||||
|
||||
if (watch == EMSESP::WATCH_ON) {
|
||||
shell.printfln(F("Watching incoming telegrams, displayed in decoded format"));
|
||||
} else {
|
||||
shell.printfln(F("Watching incoming telegrams, displayed as raw bytes")); // WATCH_RAW
|
||||
}
|
||||
|
||||
watch_id = emsesp::EMSESP::watch_id();
|
||||
if (watch_id != WATCH_ID_NONE) {
|
||||
shell.printfln(F("Filtering only telegrams that match a device ID or telegram type of 0x%02X"), watch_id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// prompt, change per context
|
||||
std::string EMSESPShell::context_text() {
|
||||
switch (static_cast<ShellContext>(context())) {
|
||||
case ShellContext::MAIN:
|
||||
return std::string{'/'};
|
||||
|
||||
case ShellContext::BOILER:
|
||||
return std::string{"/boiler"};
|
||||
|
||||
case ShellContext::SYSTEM:
|
||||
return std::string{"/system"};
|
||||
|
||||
case ShellContext::THERMOSTAT:
|
||||
return std::string{"/thermostat"};
|
||||
|
||||
default:
|
||||
return std::string{};
|
||||
}
|
||||
}
|
||||
|
||||
// when in su (admin) show # as the prompt suffix
|
||||
std::string EMSESPShell::prompt_suffix() {
|
||||
if (has_flags(CommandFlags::ADMIN)) {
|
||||
return std::string{'#'};
|
||||
} else {
|
||||
return std::string{'$'};
|
||||
}
|
||||
}
|
||||
|
||||
void EMSESPShell::end_of_transmission() {
|
||||
invoke_command(uuid::read_flash_string(F_(exit)));
|
||||
}
|
||||
|
||||
EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, bool local)
|
||||
: uuid::console::Shell(commands, ShellContext::MAIN, local ? (CommandFlags::USER | CommandFlags::LOCAL) : CommandFlags::USER)
|
||||
, uuid::console::StreamConsole(stream)
|
||||
, EMSESPShell()
|
||||
, name_(uuid::read_flash_string(F("Serial")))
|
||||
, pty_(std::numeric_limits<size_t>::max())
|
||||
, addr_()
|
||||
, port_(0) {
|
||||
}
|
||||
|
||||
EMSESPStreamConsole::EMSESPStreamConsole(Stream & stream, const IPAddress & addr, uint16_t port)
|
||||
: uuid::console::Shell(commands, ShellContext::MAIN, CommandFlags::USER)
|
||||
, uuid::console::StreamConsole(stream)
|
||||
, EMSESPShell()
|
||||
, addr_(addr)
|
||||
, port_(port) {
|
||||
std::vector<char> text(16);
|
||||
|
||||
pty_ = 0;
|
||||
while (pty_ < ptys_.size() && ptys_[pty_])
|
||||
pty_++;
|
||||
if (pty_ == ptys_.size()) {
|
||||
ptys_.push_back(true);
|
||||
} else {
|
||||
ptys_[pty_] = true;
|
||||
}
|
||||
|
||||
snprintf_P(text.data(), text.size(), PSTR("pty%u"), pty_);
|
||||
name_ = text.data();
|
||||
#ifndef EMSESP_STANDALONE
|
||||
logger().info(F("Allocated console %s for connection from [%s]:%u"), name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_);
|
||||
#endif
|
||||
}
|
||||
|
||||
EMSESPStreamConsole::~EMSESPStreamConsole() {
|
||||
if (pty_ != SIZE_MAX) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
logger().info(F("Shutdown console %s for connection from [%s]:%u"), name_.c_str(), uuid::printable_to_string(addr_).c_str(), port_);
|
||||
#endif
|
||||
ptys_[pty_] = false;
|
||||
ptys_.shrink_to_fit();
|
||||
|
||||
EMSESPShell::commands->remove_all_commands();
|
||||
}
|
||||
}
|
||||
|
||||
std::string EMSESPStreamConsole::console_name() {
|
||||
return name_;
|
||||
}
|
||||
|
||||
// Start up telnet and logging
|
||||
void Console::start() {
|
||||
// if we've detected a boot into safe mode on ESP8266, start the Serial console too
|
||||
// Serial is always on with the ESP32 as it has 2 UARTs
|
||||
#if defined(ESP32) || defined(EMSESP_STANDALONE) || defined(EMSESP_FORCE_SERIAL)
|
||||
serial_console_.begin(SERIAL_CONSOLE_BAUD_RATE);
|
||||
serial_console_.println();
|
||||
|
||||
shell = std::make_shared<EMSESPStreamConsole>(serial_console_, true);
|
||||
shell->maximum_log_messages(100); // default is 50
|
||||
shell->start();
|
||||
shell->log_level(uuid::log::Level::DEBUG); // order is: err, warning, notice, info, debug, trace, all
|
||||
#endif
|
||||
|
||||
#if defined(EMSESP_STANDALONE)
|
||||
shell->add_flags(CommandFlags::ADMIN);
|
||||
#endif
|
||||
|
||||
// always start the telnet service, except on an ESP8266
|
||||
// default idle is 10 minutes, default write timeout is 0 (automatic)
|
||||
// note, this must be started after the network/wifi for ESP32 otherwise it'll crash
|
||||
#ifndef EMSESP_STANDALONE
|
||||
telnet_.start();
|
||||
telnet_.default_write_timeout(1000); // in ms, socket timeout 1 second
|
||||
#endif
|
||||
}
|
||||
|
||||
// handles telnet sync and logging to console
|
||||
void Console::loop() {
|
||||
uuid::loop();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
telnet_.loop();
|
||||
#endif
|
||||
|
||||
#if defined(ESP8266)
|
||||
if (!EMSuart::sending()) {
|
||||
Shell::loop_all();
|
||||
}
|
||||
#else
|
||||
Shell::loop_all();
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
214
src/console.h
Normal file
214
src/console.h
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_CONSOLE_H
|
||||
#define EMSESP_CONSOLE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <uuid/console.h>
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "system.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
using uuid::flash_string_vector;
|
||||
using uuid::read_flash_string;
|
||||
using uuid::console::Commands;
|
||||
using uuid::console::Shell;
|
||||
using uuid::log::Level;
|
||||
|
||||
// clang-format off
|
||||
|
||||
#define LOG_DEBUG(...) logger_.debug(__VA_ARGS__)
|
||||
#define LOG_INFO(...) logger_.info(__VA_ARGS__)
|
||||
#define LOG_TRACE(...) logger_.trace(__VA_ARGS__)
|
||||
#define LOG_NOTICE(...) logger_.notice(__VA_ARGS__)
|
||||
#define LOG_WARNING(...) logger_.warning(__VA_ARGS__)
|
||||
#define LOG_ERROR(...) logger_.err(__VA_ARGS__)
|
||||
|
||||
#define MAKE_PSTR(string_name, string_literal) static const char __pstr__##string_name[] __attribute__((__aligned__(sizeof(int)))) PROGMEM = string_literal;
|
||||
#define MAKE_PSTR_WORD(string_name) MAKE_PSTR(string_name, #string_name)
|
||||
#define F_(string_name) FPSTR(__pstr__##string_name)
|
||||
|
||||
// clang-format on
|
||||
|
||||
// common words
|
||||
#ifdef EMSESP_DEBUG
|
||||
MAKE_PSTR_WORD(test)
|
||||
#endif
|
||||
|
||||
MAKE_PSTR_WORD(exit)
|
||||
MAKE_PSTR_WORD(help)
|
||||
MAKE_PSTR_WORD(settings)
|
||||
MAKE_PSTR_WORD(log)
|
||||
MAKE_PSTR_WORD(logout)
|
||||
MAKE_PSTR_WORD(enabled)
|
||||
MAKE_PSTR_WORD(disabled)
|
||||
MAKE_PSTR_WORD(yes)
|
||||
MAKE_PSTR_WORD(no)
|
||||
MAKE_PSTR_WORD(set)
|
||||
MAKE_PSTR_WORD(show)
|
||||
MAKE_PSTR_WORD(on)
|
||||
MAKE_PSTR_WORD(off)
|
||||
MAKE_PSTR_WORD(su)
|
||||
MAKE_PSTR_WORD(name)
|
||||
MAKE_PSTR_WORD(auto)
|
||||
MAKE_PSTR_WORD(scan)
|
||||
MAKE_PSTR_WORD(password)
|
||||
MAKE_PSTR_WORD(read)
|
||||
MAKE_PSTR_WORD(version)
|
||||
MAKE_PSTR_WORD(values)
|
||||
MAKE_PSTR_WORD(system)
|
||||
MAKE_PSTR_WORD(refresh)
|
||||
MAKE_PSTR_WORD(restart)
|
||||
MAKE_PSTR_WORD(raw)
|
||||
MAKE_PSTR_WORD(watch)
|
||||
MAKE_PSTR_WORD(mqtt)
|
||||
MAKE_PSTR_WORD(send)
|
||||
MAKE_PSTR_WORD(telegram)
|
||||
MAKE_PSTR_WORD(bus_id)
|
||||
MAKE_PSTR_WORD(tx_mode)
|
||||
MAKE_PSTR_WORD(ems)
|
||||
MAKE_PSTR_WORD(devices)
|
||||
|
||||
MAKE_PSTR(deep_optional, "[deep]")
|
||||
MAKE_PSTR(tx_mode_fmt, "Tx mode = %d")
|
||||
MAKE_PSTR(bus_id_fmt, "Bus ID = %02X")
|
||||
MAKE_PSTR(watchid_optional, "[ID]")
|
||||
MAKE_PSTR(watch_format_mandatory, "<off | on | raw>")
|
||||
MAKE_PSTR(invalid_watch, "Invalid watch type")
|
||||
MAKE_PSTR(data_mandatory, "<\"XX XX ...\">")
|
||||
MAKE_PSTR(percent, "%")
|
||||
MAKE_PSTR(degrees, "°C")
|
||||
MAKE_PSTR(degrees_mandatory, "<degrees>")
|
||||
MAKE_PSTR(asterisks, "********")
|
||||
MAKE_PSTR(n_mandatory, "<n>")
|
||||
MAKE_PSTR(n_optional, "[n]")
|
||||
MAKE_PSTR(bool_mandatory, "<on | off>")
|
||||
MAKE_PSTR(typeid_mandatory, "<type ID>")
|
||||
MAKE_PSTR(deviceid_mandatory, "<device ID>")
|
||||
MAKE_PSTR(deviceid_optional, "[device ID]")
|
||||
MAKE_PSTR(invalid_log_level, "Invalid log level")
|
||||
MAKE_PSTR(port_mandatory, "<port>")
|
||||
MAKE_PSTR(log_level_fmt, "Log level = %s")
|
||||
MAKE_PSTR(log_level_optional, "[level]")
|
||||
MAKE_PSTR(name_mandatory, "<name>")
|
||||
MAKE_PSTR(name_optional, "[name]")
|
||||
MAKE_PSTR(new_password_prompt1, "Enter new password: ")
|
||||
MAKE_PSTR(new_password_prompt2, "Retype new password: ")
|
||||
MAKE_PSTR(password_prompt, "Password: ")
|
||||
MAKE_PSTR(unset, "<unset>")
|
||||
|
||||
#ifdef LOCAL
|
||||
#undef LOCAL
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t INVALID_PASSWORD_DELAY_MS = 3000;
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using LogLevel = ::uuid::log::Level;
|
||||
using LogFacility = ::uuid::log::Facility;
|
||||
|
||||
enum CommandFlags : uint8_t {
|
||||
|
||||
USER = 0,
|
||||
ADMIN = (1 << 0),
|
||||
LOCAL = (1 << 1)
|
||||
|
||||
};
|
||||
|
||||
enum ShellContext : uint8_t {
|
||||
|
||||
MAIN = 0,
|
||||
SYSTEM,
|
||||
BOILER,
|
||||
THERMOSTAT,
|
||||
SOLAR,
|
||||
MIXING
|
||||
|
||||
};
|
||||
|
||||
class EMSESPShell : virtual public uuid::console::Shell {
|
||||
public:
|
||||
~EMSESPShell() override = default;
|
||||
|
||||
virtual std::string console_name() = 0;
|
||||
|
||||
static std::shared_ptr<uuid::console::Commands> commands;
|
||||
static std::shared_ptr<EMSESPShell> shell;
|
||||
|
||||
protected:
|
||||
EMSESPShell();
|
||||
|
||||
// our custom functions for Shell
|
||||
void started() override;
|
||||
void stopped() override;
|
||||
void display_banner() override;
|
||||
std::string hostname_text() override;
|
||||
std::string context_text() override;
|
||||
std::string prompt_suffix() override;
|
||||
void end_of_transmission() override;
|
||||
bool exit_context() override;
|
||||
|
||||
private:
|
||||
void add_console_commands();
|
||||
bool console_commands_loaded_ = false; // set to true when the initial commands are loaded
|
||||
std::string console_hostname_;
|
||||
};
|
||||
|
||||
class EMSESPStreamConsole : public uuid::console::StreamConsole, public EMSESPShell {
|
||||
public:
|
||||
EMSESPStreamConsole(Stream & stream, bool local);
|
||||
EMSESPStreamConsole(Stream & stream, const IPAddress & addr, uint16_t port);
|
||||
~EMSESPStreamConsole() override;
|
||||
|
||||
std::string console_name() override;
|
||||
|
||||
private:
|
||||
static std::vector<bool> ptys_;
|
||||
|
||||
std::string name_;
|
||||
size_t pty_;
|
||||
IPAddress addr_;
|
||||
uint16_t port_;
|
||||
};
|
||||
|
||||
class Console {
|
||||
public:
|
||||
void loop();
|
||||
void start();
|
||||
|
||||
uuid::log::Level log_level();
|
||||
|
||||
static void enter_custom_context(Shell & shell, unsigned int context);
|
||||
static void load_standard_commands(unsigned int context);
|
||||
|
||||
private:
|
||||
static constexpr unsigned long SERIAL_CONSOLE_BAUD_RATE = 115200;
|
||||
static constexpr auto & serial_console_ = Serial;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
261
src/custom.htm
261
src/custom.htm
@@ -1,261 +0,0 @@
|
||||
<div id="customcontent">
|
||||
<br>
|
||||
<legend>Custom Settings</legend>
|
||||
<h6 class="text-muted">Please refer to the Help for configuration options</h6>
|
||||
<br>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">LED<i style="margin-left: 10px;"
|
||||
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
|
||||
data-trigger="hover" data-placement="right"
|
||||
data-content="Please choose if you want to enable an LED to show status"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="led">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="led" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">LED Pin<i style="margin-left: 10px;"
|
||||
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
|
||||
data-trigger="hover" data-placement="right"
|
||||
data-content="Select with GPIO pin the LED is on"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<select class="form-control input-sm" id="led_gpio">
|
||||
<option value="0">GPIO-0</option>
|
||||
<option selected="selected" value="2">GPIO-2 (onboard LED)</option>
|
||||
<option value="4">GPIO-4</option>
|
||||
<option value="5">GPIO-5</option>
|
||||
<option value="12">GPIO-12</option>
|
||||
<option value="14">GPIO-14</option>
|
||||
<option value="16">GPIO-16</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Dallas Parasite<i style="margin-left: 10px;"
|
||||
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
|
||||
data-trigger="hover" data-placement="right"
|
||||
data-content="Enable if Dallas sensors are powered via parasite"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="dallas_parasite">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="dallas_parasite" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Dallas Pin<i style="margin-left: 10px;"
|
||||
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
|
||||
data-trigger="hover" data-placement="right"
|
||||
data-content="Select GPIO pin to where the Dallas sensor is connected"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<select class="form-control input-sm" id="dallas_gpio">
|
||||
<option value="0">GPIO-0</option>
|
||||
<option value="4">GPIO-4</option>
|
||||
<option value="5">GPIO-5</option>
|
||||
<option value="12">GPIO-12</option>
|
||||
<option selected="selected" value="14">GPIO-14</option>
|
||||
<option value="16">GPIO-16</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Listen Mode<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Listen mode disables Tx. Used when debugging."></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="listen_mode">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="listen_mode" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Shower Timer<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Monitors and sends MQTT message on shower duration"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="shower_timer">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="shower_timer" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Shower Alert<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Tells boiler to blast 3 shots of cold water after a specific duration has exceeded (7 mins)"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="shower_alert">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="shower_alert" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Publish Time<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="How often to send the MQTT topics in seconds. Must be at least 1"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="0" value="" style="display:inline;max-width:185px"
|
||||
id="publish_time" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Tx mode<i style="margin-left: 10px;"
|
||||
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
|
||||
data-trigger="hover" data-placement="right"
|
||||
data-content="Tx mode settings for sending data to the EMS bus"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<select class="form-control input-sm" id="tx_mode">
|
||||
<option selected="selected" value="1">1 (EMS generic)</option>
|
||||
<option value="2">2 (EMS+)</option>
|
||||
<option value="3">3 (Junkers Heatronic HT3)</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="row form-group">
|
||||
<div class="col-xs-9 col-md-8">
|
||||
<button onclick="savecustom()" class="btn btn-primary btn-sm pull-right">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h6 class="text-muted">Note: any setting marked with a <span
|
||||
class="glyphicon glyphicon-exclamation-sign text-danger"></span> requires a system restart after saving.
|
||||
</h6>
|
||||
</div>
|
||||
|
||||
<div id="custom_statuscontent">
|
||||
<br>
|
||||
<div class="row text-left">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h2>EMS Dashboard</h2>
|
||||
<h6>Real-time values from the EMS-ESP device are shown here</h6>
|
||||
</div>
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="panel panel-default table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed">
|
||||
<caption>EMS Bus Status</caption>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<b>
|
||||
<div id="msg" role="alert"></div>
|
||||
</b>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th id="devicesshow">Discovered Devices:</th>
|
||||
<td>
|
||||
<ul class="list-group">
|
||||
<div id="devices"></div>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel panel-success table-responsive" id="boiler_show">
|
||||
<div class="panel-heading"><b>Boiler</b>: <span id="bm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Hot Tap Water:</th>
|
||||
<td id="b1"></td>
|
||||
<th>Central Heating:</th>
|
||||
<td id="b2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Selected Flow Temperature:</th>
|
||||
<td id="b3"></td>
|
||||
<th>Current Flow Temperature:</th>
|
||||
<td id="b4"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>DHW Temperature:</th>
|
||||
<td id="b5"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-info table-responsive" id="thermostat_show">
|
||||
<div class="panel-heading"><b>Thermostat</b>: <span id="tm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Setpoint Temperature:</th>
|
||||
<td id="ts"></td>
|
||||
<th>Current Temperature:</th>
|
||||
<td id="tc"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mode:</th>
|
||||
<td colspan="3" id="tmode"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-warning table-responsive" id="sm_show">
|
||||
<div class="panel-heading"><b>Solar Module</b>: <span id="sm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Colector Temperature:</th>
|
||||
<td id="sm1"></td>
|
||||
<th>Bottom Temperature:</th>
|
||||
<td id="sm2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Pump Modulation:</th>
|
||||
<td id="sm3"></td>
|
||||
<th>Pump Active:</th>
|
||||
<td id="sm4"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Energy Last Hour:</th>
|
||||
<td id="sm5"></td>
|
||||
<th>Energy Today:</th>
|
||||
<td id="sm6"></td>
|
||||
<th>Energy Total:</th>
|
||||
<td id="sm7"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-success table-responsive" id="hp_show">
|
||||
<div class="panel-heading"><b>Heat Pump</b>: <span id="hm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Pump Modulation:</th>
|
||||
<td id="hp1"></td>
|
||||
<th>Pump Speed:</th>
|
||||
<td id="hp2"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group" style="text-align: center;">
|
||||
<button onclick="refreshCustomStatus()" class="btn btn-info">Refresh</button>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
167
src/custom.js
167
src/custom.js
@@ -1,167 +0,0 @@
|
||||
var custom_config = {
|
||||
"command": "custom_configfile",
|
||||
"settings": {
|
||||
"led": true,
|
||||
"led_gpio": 2,
|
||||
"dallas_gpio": 14,
|
||||
"dallas_parasite": false,
|
||||
"listen_mode": false,
|
||||
"shower_timer": false,
|
||||
"shower_alert": false,
|
||||
"publish_time": 10,
|
||||
"tx_mode": 1
|
||||
}
|
||||
};
|
||||
|
||||
function listcustom() {
|
||||
|
||||
document.getElementById("led_gpio").value = custom_config.settings.led_gpio;
|
||||
document.getElementById("dallas_gpio").value = custom_config.settings.dallas_gpio;
|
||||
document.getElementById("publish_time").value = custom_config.settings.publish_time;
|
||||
document.getElementById("tx_mode").value = custom_config.settings.tx_mode;
|
||||
|
||||
if (custom_config.settings.led) {
|
||||
$("input[name=\"led\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (custom_config.settings.dallas_parasite) {
|
||||
$("input[name=\"dallas_parasite\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (custom_config.settings.listen_mode) {
|
||||
$("input[name=\"listen_mode\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (custom_config.settings.shower_timer) {
|
||||
$("input[name=\"shower_timer\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (custom_config.settings.shower_alert) {
|
||||
$("input[name=\"shower_alert\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
}
|
||||
|
||||
function savecustom() {
|
||||
custom_config.settings.led_gpio = parseInt(document.getElementById("led_gpio").value);
|
||||
custom_config.settings.dallas_gpio = parseInt(document.getElementById("dallas_gpio").value);
|
||||
|
||||
custom_config.settings.dallas_parasite = false;
|
||||
if (parseInt($("input[name=\"dallas_parasite\"]:checked").val()) === 1) {
|
||||
custom_config.settings.dallas_parasite = true;
|
||||
}
|
||||
|
||||
custom_config.settings.listen_mode = false;
|
||||
if (parseInt($("input[name=\"listen_mode\"]:checked").val()) === 1) {
|
||||
custom_config.settings.listen_mode = true;
|
||||
}
|
||||
|
||||
custom_config.settings.shower_timer = false;
|
||||
if (parseInt($("input[name=\"shower_timer\"]:checked").val()) === 1) {
|
||||
custom_config.settings.shower_timer = true;
|
||||
}
|
||||
|
||||
custom_config.settings.shower_alert = false;
|
||||
if (parseInt($("input[name=\"shower_alert\"]:checked").val()) === 1) {
|
||||
custom_config.settings.shower_alert = true;
|
||||
}
|
||||
|
||||
custom_config.settings.led = false;
|
||||
if (parseInt($("input[name=\"led\"]:checked").val()) === 1) {
|
||||
custom_config.settings.led = true;
|
||||
}
|
||||
|
||||
custom_config.settings.publish_time = parseInt(document.getElementById("publish_time").value);
|
||||
custom_config.settings.tx_mode = parseInt(document.getElementById("tx_mode").value);
|
||||
|
||||
custom_saveconfig();
|
||||
}
|
||||
|
||||
function listCustomStats() {
|
||||
document.getElementById("msg").innerHTML = ajaxobj.emsbus.msg;
|
||||
if (ajaxobj.emsbus.ok) {
|
||||
document.getElementById("msg").className = "alert alert-success";
|
||||
} else {
|
||||
document.getElementById("msg").className = "alert alert-danger";
|
||||
document.getElementById("devicesshow").style.display = "none";
|
||||
document.getElementById("thermostat_show").style.display = "none";
|
||||
document.getElementById("boiler_show").style.display = "none";
|
||||
document.getElementById("sm_show").style.display = "none";
|
||||
document.getElementById("hp_show").style.display = "none";
|
||||
return;
|
||||
}
|
||||
|
||||
var list = document.getElementById("devices");
|
||||
var obj = ajaxobj.emsbus.devices;
|
||||
|
||||
document.getElementById("devicesshow").style.display = "block";
|
||||
|
||||
for (var i = 0; i < obj.length; i++) {
|
||||
var l = document.createElement("li");
|
||||
var type = obj[i].type;
|
||||
var color = "";
|
||||
if (type === "Boiler") {
|
||||
color = "list-group-item-success";
|
||||
} else if (type === "Thermostat") {
|
||||
color = "list-group-item-info";
|
||||
} else if (type === "Solar Module") {
|
||||
color = "list-group-item-warning";
|
||||
} else if (type === "Heat Pump") {
|
||||
color = "list-group-item-success";
|
||||
}
|
||||
l.innerHTML = obj[i].model + " (DeviceID: 0x" + obj[i].deviceid + ", ProductID: " + obj[i].productid + ", Version: " + obj[i].version + ")";
|
||||
l.className = "list-group-item " + color;
|
||||
list.appendChild(l);
|
||||
}
|
||||
|
||||
if (ajaxobj.boiler.ok) {
|
||||
document.getElementById("boiler_show").style.display = "block";
|
||||
|
||||
document.getElementById("bm").innerHTML = ajaxobj.boiler.bm;
|
||||
document.getElementById("b1").innerHTML = ajaxobj.boiler.b1;
|
||||
document.getElementById("b2").innerHTML = ajaxobj.boiler.b2;
|
||||
document.getElementById("b3").innerHTML = ajaxobj.boiler.b3 + " ℃";
|
||||
document.getElementById("b4").innerHTML = ajaxobj.boiler.b4 + " ℃";
|
||||
document.getElementById("b5").innerHTML = ajaxobj.boiler.b5 + " ℃";
|
||||
} else {
|
||||
document.getElementById("boiler_show").style.display = "none";
|
||||
}
|
||||
|
||||
if (ajaxobj.thermostat.ok) {
|
||||
document.getElementById("thermostat_show").style.display = "block";
|
||||
|
||||
document.getElementById("tm").innerHTML = ajaxobj.thermostat.tm;
|
||||
document.getElementById("ts").innerHTML = ajaxobj.thermostat.ts + " ℃";
|
||||
document.getElementById("tc").innerHTML = ajaxobj.thermostat.tc + " ℃";
|
||||
document.getElementById("tmode").innerHTML = ajaxobj.thermostat.tmode;
|
||||
} else {
|
||||
document.getElementById("thermostat_show").style.display = "none";
|
||||
}
|
||||
|
||||
if (ajaxobj.sm.ok) {
|
||||
document.getElementById("sm_show").style.display = "block";
|
||||
|
||||
document.getElementById("sm").innerHTML = ajaxobj.sm.sm;
|
||||
document.getElementById("sm1").innerHTML = ajaxobj.sm.sm1 + " ℃";
|
||||
document.getElementById("sm2").innerHTML = ajaxobj.sm.sm2 + " ℃";
|
||||
document.getElementById("sm3").innerHTML = ajaxobj.sm.sm3 + " %";
|
||||
document.getElementById("sm4").innerHTML = ajaxobj.sm.sm4;
|
||||
document.getElementById("sm5").innerHTML = ajaxobj.sm.sm5 + " Wh";
|
||||
document.getElementById("sm6").innerHTML = ajaxobj.sm.sm6 + " Wh";
|
||||
document.getElementById("sm7").innerHTML = ajaxobj.sm.sm7 + " KWh";
|
||||
} else {
|
||||
document.getElementById("sm_show").style.display = "none";
|
||||
}
|
||||
|
||||
if (ajaxobj.hp.ok) {
|
||||
document.getElementById("hp_show").style.display = "block";
|
||||
|
||||
document.getElementById("hm").innerHTML = ajaxobj.hp.hm;
|
||||
document.getElementById("hp1").innerHTML = ajaxobj.hp.hp1 + " %";
|
||||
document.getElementById("hp2").innerHTML = ajaxobj.hp.hp2 + " %";
|
||||
} else {
|
||||
document.getElementById("hp_show").style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
119
src/device_library.h
Normal file
119
src/device_library.h
Normal file
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
// clang-format off
|
||||
|
||||
/*
|
||||
* These is the EMS devices that we currently recognize
|
||||
* The types and flags are stored in emsdevice.h
|
||||
*/
|
||||
|
||||
// Boilers - 0x08
|
||||
{ 64, DeviceType::BOILER, F("BK13,BK15/Smartline/GB1x2"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{ 72, DeviceType::BOILER, F("GB125/MC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{ 84, DeviceType::BOILER, F("Logamax Plus GB022"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{ 95, DeviceType::BOILER, F("Condens 2500/Logamax/Logomatic/Cerapur Top/Greenstar/Generic HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{115, DeviceType::BOILER, F("Topline/GB162"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{122, DeviceType::BOILER, F("Proline"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{123, DeviceType::BOILER, F("GBx72/Trendline/Cerapur/Greenstar Si/27i"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{133, DeviceType::BOILER, F("GB125/Logamatic MC110"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{170, DeviceType::BOILER, F("Logano GB212"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{172, DeviceType::BOILER, F("Enviline/Compress 6000AW"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{195, DeviceType::BOILER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{203, DeviceType::BOILER, F("Logamax U122/Cerapur"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{208, DeviceType::BOILER, F("Logamax plus/GB192/Condens GC9000"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{234, DeviceType::BOILER, F("Logamax Plus GB122"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Thermostat - not currently supporting write operations, like the Easy/100 types - 0x18
|
||||
{202, DeviceType::THERMOSTAT, F("Logamatic TC100/Moduline Easy"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
{203, DeviceType::THERMOSTAT, F("EasyControl CT200"), DeviceFlags::EMS_DEVICE_FLAG_EASY | DeviceFlags::EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
|
||||
// Thermostat - Common for Buderus/Nefit/Bosch specific - 0x17 / 0x10 / 0x18
|
||||
{ 67, DeviceType::THERMOSTAT, F("RC30"), DeviceFlags::EMS_DEVICE_FLAG_RC30_1},// 0x10 - based on RC35
|
||||
{ 77, DeviceType::THERMOSTAT, F("RC20/Moduline 300"), DeviceFlags::EMS_DEVICE_FLAG_RC20},// 0x17
|
||||
{ 78, DeviceType::THERMOSTAT, F("Moduline 400"), DeviceFlags::EMS_DEVICE_FLAG_RC30}, // 0x10
|
||||
{ 79, DeviceType::THERMOSTAT, F("RC10/Moduline 100"), DeviceFlags::EMS_DEVICE_FLAG_RC10},// 0x17
|
||||
{ 80, DeviceType::THERMOSTAT, F("Moduline 200"), DeviceFlags::EMS_DEVICE_FLAG_RC10}, // 0x17
|
||||
{ 86, DeviceType::THERMOSTAT, F("RC35"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10
|
||||
{ 93, DeviceType::THERMOSTAT, F("RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_RC20}, // 0x19
|
||||
{157, DeviceType::THERMOSTAT, F("RC200/CW100"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18
|
||||
{158, DeviceType::THERMOSTAT, F("RC300/RC310/Moduline 3000/CW400/Sense II"), DeviceFlags::EMS_DEVICE_FLAG_RC300}, // 0x10
|
||||
{165, DeviceType::THERMOSTAT, F("RC100/Moduline 1000/1010"), DeviceFlags::EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
|
||||
|
||||
// Thermostat - Sieger - 0x10 / 0x17
|
||||
{ 76, DeviceType::THERMOSTAT, F("ES73"), DeviceFlags::EMS_DEVICE_FLAG_RC35}, // 0x10
|
||||
{113, DeviceType::THERMOSTAT, F("ES72/RC20"), DeviceFlags::EMS_DEVICE_FLAG_RC20_2}, // 0x17
|
||||
|
||||
// Thermostat - Junkers - 0x10
|
||||
{105, DeviceType::THERMOSTAT, F("FW100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
|
||||
{106, DeviceType::THERMOSTAT, F("FW200"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
|
||||
{107, DeviceType::THERMOSTAT, F("FR100"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
|
||||
{108, DeviceType::THERMOSTAT, F("FR110"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS | DeviceFlags::EMS_DEVICE_FLAG_JUNKERS_2}, // older model
|
||||
{111, DeviceType::THERMOSTAT, F("FR10"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
|
||||
{147, DeviceType::THERMOSTAT, F("FR50"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
|
||||
{191, DeviceType::THERMOSTAT, F("FR120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
|
||||
{192, DeviceType::THERMOSTAT, F("FW120"), DeviceFlags::EMS_DEVICE_FLAG_JUNKERS},
|
||||
|
||||
// Solar Modules - 0x30
|
||||
{ 73, DeviceType::SOLAR, F("SM10"), DeviceFlags::EMS_DEVICE_FLAG_SM10},
|
||||
{101, DeviceType::SOLAR, F("ISM1"), DeviceFlags::EMS_DEVICE_FLAG_ISM},
|
||||
{162, DeviceType::SOLAR, F("SM50"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
|
||||
{163, DeviceType::SOLAR, F("SM100"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
|
||||
{164, DeviceType::SOLAR, F("SM200"), DeviceFlags::EMS_DEVICE_FLAG_SM100},
|
||||
|
||||
// Mixing Modules - 0x20-0x27 for HC, 0x28-0x29 for WWC
|
||||
{ 69, DeviceType::MIXING, F("MM10"), DeviceFlags::EMS_DEVICE_FLAG_MM10},
|
||||
{102, DeviceType::MIXING, F("IPM"), DeviceFlags::EMS_DEVICE_FLAG_IPM},
|
||||
{159, DeviceType::MIXING, F("MM50"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
|
||||
{160, DeviceType::MIXING, F("MM100"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
|
||||
{161, DeviceType::MIXING, F("MM200"), DeviceFlags::EMS_DEVICE_FLAG_MMPLUS},
|
||||
|
||||
// Heat Pumps - 0x38
|
||||
{200, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{252, DeviceType::HEATPUMP, F("HP Module"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Controllers - 0x09 / 0x10 / 0x50
|
||||
{ 68, DeviceType::CONTROLLER, F("BC10/RFM20"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{ 89, DeviceType::CONTROLLER, F("BC10 GB142"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{ 95, DeviceType::CONTROLLER, F("HT3"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{114, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{125, DeviceType::CONTROLLER, F("BC25"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{152, DeviceType::CONTROLLER, F("Controller"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{169, DeviceType::CONTROLLER, F("BC40"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{190, DeviceType::CONTROLLER, F("BC10"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{207, DeviceType::CONTROLLER, F("Sense II/CS200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x10
|
||||
{209, DeviceType::CONTROLLER, F("ErP"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{218, DeviceType::CONTROLLER, F("M200/RFM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x50
|
||||
{224, DeviceType::CONTROLLER, F("9000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{230, DeviceType::CONTROLLER, F("BC Base"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{241, DeviceType::CONTROLLER, F("Condens 5000i"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
|
||||
// Connect devices - 0x02
|
||||
{171, DeviceType::CONNECT, F("OpenTherm Converter"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{205, DeviceType::CONNECT, F("Moduline Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
{206, DeviceType::CONNECT, F("Easy Connect"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Switches - 0x11
|
||||
{ 71, DeviceType::SWITCH, F("WM10"), DeviceFlags::EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
// Gateways - 0x48 / 0x18
|
||||
{ 94, DeviceType::GATEWAY, F("RFM20 Remote Base for RC20RF"), DeviceFlags::EMS_DEVICE_FLAG_NONE}, // 0x18
|
||||
{189, DeviceType::GATEWAY, F("KM200"), DeviceFlags::EMS_DEVICE_FLAG_NONE} // 0x48
|
||||
|
||||
|
||||
// clang-format on
|
||||
896
src/devices/boiler.cpp
Normal file
896
src/devices/boiler.cpp
Normal file
@@ -0,0 +1,896 @@
|
||||
/*
|
||||
* 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 "boiler.h"
|
||||
|
||||
MAKE_PSTR_WORD(boiler)
|
||||
MAKE_PSTR_WORD(wwtemp)
|
||||
MAKE_PSTR_WORD(flowtemp)
|
||||
MAKE_PSTR_WORD(wwactive)
|
||||
MAKE_PSTR_WORD(wwonetime)
|
||||
MAKE_PSTR_WORD(wwcirculation)
|
||||
MAKE_PSTR_WORD(comfort)
|
||||
MAKE_PSTR_WORD(eco)
|
||||
MAKE_PSTR_WORD(intelligent)
|
||||
MAKE_PSTR_WORD(hot)
|
||||
|
||||
MAKE_PSTR(comfort_mandatory, "<hot | eco | intelligent>")
|
||||
|
||||
// shower
|
||||
MAKE_PSTR_WORD(shower)
|
||||
MAKE_PSTR_WORD(timer)
|
||||
MAKE_PSTR_WORD(alert)
|
||||
MAKE_PSTR(shower_timer_fmt, "Shower Timer is %s")
|
||||
MAKE_PSTR(shower_alert_fmt, "Shower Alert is %s")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Boiler, EMSdevice::DeviceType::BOILER)
|
||||
MAKE_PSTR(logger_name, "boiler")
|
||||
uuid::log::Logger Boiler::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Boiler::Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Boiler with device ID 0x%02X"), device_id);
|
||||
|
||||
// the telegram handlers...
|
||||
register_telegram_type(0x10, F("UBAErrorMessage1"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
|
||||
register_telegram_type(0x11, F("UBAErrorMessage2"), false, std::bind(&Boiler::process_UBAErrorMessage, this, _1));
|
||||
register_telegram_type(0x18, F("UBAMonitorFast"), false, std::bind(&Boiler::process_UBAMonitorFast, this, _1));
|
||||
register_telegram_type(0x19, F("UBAMonitorSlow"), true, std::bind(&Boiler::process_UBAMonitorSlow, this, _1));
|
||||
register_telegram_type(0x34, F("UBAMonitorWW"), false, std::bind(&Boiler::process_UBAMonitorWW, this, _1));
|
||||
register_telegram_type(0x1C, F("UBAMaintenanceStatus"), false, std::bind(&Boiler::process_UBAMaintenanceStatus, this, _1));
|
||||
register_telegram_type(0x2A, F("MC10Status"), false, std::bind(&Boiler::process_MC10Status, this, _1));
|
||||
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(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));
|
||||
register_telegram_type(0xE3, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus2, this, _1));
|
||||
register_telegram_type(0xE4, F("UBAMonitorFastPlus"), false, std::bind(&Boiler::process_UBAMonitorFastPlus, this, _1));
|
||||
register_telegram_type(0xE5, F("UBAMonitorSlowPlus"), false, std::bind(&Boiler::process_UBAMonitorSlowPlus, this, _1));
|
||||
|
||||
register_telegram_type(0xE9, F("UBADHWStatus"), false, std::bind(&Boiler::process_UBADHWStatus, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
register_mqtt_topic("boiler_cmd", std::bind(&Boiler::boiler_cmd, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwactivated", std::bind(&Boiler::boiler_cmd_wwactivated, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwonetime", std::bind(&Boiler::boiler_cmd_wwonetime, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwcirculation", std::bind(&Boiler::boiler_cmd_wwcirculation, this, _1));
|
||||
register_mqtt_topic("boiler_cmd_wwtemp", std::bind(&Boiler::boiler_cmd_wwtemp, this, _1));
|
||||
}
|
||||
|
||||
// add submenu context
|
||||
void Boiler::add_context_menu() {
|
||||
EMSESPShell::commands->add_command(ShellContext::MAIN,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(boiler)},
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
Boiler::console_commands(shell, ShellContext::BOILER);
|
||||
});
|
||||
}
|
||||
|
||||
// boiler_cmd topic
|
||||
void Boiler::boiler_cmd(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
if (nullptr != doc["flowtemp"]) {
|
||||
uint8_t t = doc["flowtemp"];
|
||||
set_flow_temp(t);
|
||||
}
|
||||
if (nullptr != doc["wwtemp"]) {
|
||||
uint8_t t = doc["wwtemp"];
|
||||
set_warmwater_temp(t);
|
||||
}
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == 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) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (strcmp((char *)data, "intelligent") == 0) {
|
||||
set_warmwater_mode(3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// boiler flowtemp setting
|
||||
if (strcmp(command, "flowtemp") == 0) {
|
||||
uint8_t t = doc["data"];
|
||||
if (t) {
|
||||
set_flow_temp(t);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwactivated(const char * message) {
|
||||
if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) {
|
||||
set_warmwater_activated(true);
|
||||
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
|
||||
set_warmwater_activated(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwonetime(const char * message) {
|
||||
if (message[0] == '1' || strcmp(message, "on") == 0) {
|
||||
set_warmwater_onetime(true);
|
||||
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
|
||||
set_warmwater_onetime(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwcirculation(const char * message) {
|
||||
if (message[0] == '1' || strcmp(message, "on") == 0) {
|
||||
set_warmwater_circulation(true);
|
||||
} else if (message[0] == '0' || strcmp(message, "off") == 0) {
|
||||
set_warmwater_circulation(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Boiler::boiler_cmd_wwtemp(const char * message) {
|
||||
uint8_t t = atoi((char *)message);
|
||||
if (t) {
|
||||
set_warmwater_temp(t);
|
||||
}
|
||||
}
|
||||
|
||||
// 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/
|
||||
DynamicJsonDocument doc(capacity);
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
|
||||
if (Helpers::hasValue(wWComfort_)) {
|
||||
if (wWComfort_ == 0x00) {
|
||||
doc["wWComfort"] = "Hot";
|
||||
} else if (wWComfort_ == 0xD8) {
|
||||
doc["wWComfort"] = "Eco";
|
||||
} else if (wWComfort_ == 0xEC) {
|
||||
doc["wWComfort"] = "Intelligent";
|
||||
}
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(wWSelTemp_)) {
|
||||
doc["wWSelTemp"] = wWSelTemp_;
|
||||
}
|
||||
if (Helpers::hasValue(wWSetTmp_)) {
|
||||
doc["wWSetTemp"] = wWSetTmp_;
|
||||
}
|
||||
if (Helpers::hasValue(wWDisinfectTemp_)) {
|
||||
doc["wWDisinfectionTemp"] = wWDisinfectTemp_;
|
||||
}
|
||||
if (Helpers::hasValue(selFlowTemp_)) {
|
||||
doc["selFlowTemp"] = selFlowTemp_;
|
||||
}
|
||||
if (Helpers::hasValue(selBurnPow_)) {
|
||||
doc["selBurnPow"] = selBurnPow_;
|
||||
}
|
||||
if (Helpers::hasValue(curBurnPow_)) {
|
||||
doc["curBurnPow"] = curBurnPow_;
|
||||
}
|
||||
if (Helpers::hasValue(pumpMod_)) {
|
||||
doc["pumpMod"] = pumpMod_;
|
||||
}
|
||||
if (Helpers::hasValue(pumpMod2_)) {
|
||||
doc["pumpMod2"] = pumpMod2_;
|
||||
}
|
||||
if (Helpers::hasValue(wWCircPump_, true)) {
|
||||
doc["wWCircPump"] = Helpers::render_value(s, wWCircPump_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWCircPumpType_)) {
|
||||
doc["wWCiPuType"] = wWCircPumpType_ ? "valve" : "pump";
|
||||
}
|
||||
if (Helpers::hasValue(wWCircPumpMode_)) {
|
||||
doc["wWCiPuMode"] = wWCircPumpMode_;
|
||||
}
|
||||
if (Helpers::hasValue(wWCirc_)) {
|
||||
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(extTemp_)) {
|
||||
doc["outdoorTemp"] = (float)extTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(wWCurTmp_)) {
|
||||
doc["wWCurTmp"] = (float)wWCurTmp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(wWCurFlow_)) {
|
||||
doc["wWCurFlow"] = (float)wWCurFlow_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(curFlowTemp_)) {
|
||||
doc["curFlowTemp"] = (float)curFlowTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(retTemp_)) {
|
||||
doc["retTemp"] = (float)retTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(switchTemp_)) {
|
||||
doc["switchTemp"] = (float)switchTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(sysPress_)) {
|
||||
doc["sysPress"] = (float)sysPress_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(boilTemp_)) {
|
||||
doc["boilTemp"] = (float)boilTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(wwStorageTemp1_)) {
|
||||
doc["wwStorageTemp1"] = (float)wwStorageTemp1_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(wwStorageTemp2_)) {
|
||||
doc["wwStorageTemp2"] = (float)wwStorageTemp2_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(exhaustTemp_)) {
|
||||
doc["exhaustTemp"] = (float)exhaustTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(wWActivated_, true)) {
|
||||
doc["wWActivated"] = Helpers::render_value(s, wWActivated_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWOneTime_, true)) {
|
||||
doc["wWOnetime"] = Helpers::render_value(s, wWOneTime_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWDesinfecting_, true)) {
|
||||
doc["wWDesinfecting"] = Helpers::render_value(s, wWDesinfecting_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWReadiness_, true)) {
|
||||
doc["wWReady"] = Helpers::render_value(s, wWReadiness_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWRecharging_, true)) {
|
||||
doc["wWRecharge"] = Helpers::render_value(s, wWRecharging_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWTemperatureOK_, true)) {
|
||||
doc["wWTempOK"] = Helpers::render_value(s, wWTemperatureOK_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWCirc_, true)) {
|
||||
doc["wWCirc"] = Helpers::render_value(s, wWCirc_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(burnGas_, true)) {
|
||||
doc["burnGas"] = Helpers::render_value(s, burnGas_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(flameCurr_)) {
|
||||
doc["flameCurr"] = (float)(int16_t)flameCurr_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(heatPmp_, true)) {
|
||||
doc["heatPmp"] = Helpers::render_value(s, heatPmp_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(fanWork_, true)) {
|
||||
doc["fanWork"] = Helpers::render_value(s, fanWork_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(ignWork_, true)) {
|
||||
doc["ignWork"] = Helpers::render_value(s, ignWork_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(wWHeat_, true)) {
|
||||
doc["wWHeat"] = Helpers::render_value(s, wWHeat_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(heating_temp_)) {
|
||||
doc["heating_temp"] = heating_temp_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_mod_max_)) {
|
||||
doc["pump_mod_max"] = pump_mod_max_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_mod_min_)) {
|
||||
doc["pump_mod_min"] = pump_mod_min_;
|
||||
}
|
||||
if (Helpers::hasValue(wWStarts_)) {
|
||||
doc["wWStarts"] = wWStarts_;
|
||||
}
|
||||
if (Helpers::hasValue(wWWorkM_)) {
|
||||
doc["wWWorkM"] = wWWorkM_;
|
||||
}
|
||||
if (Helpers::hasValue(UBAuptime_)) {
|
||||
doc["UBAuptime"] = UBAuptime_;
|
||||
}
|
||||
if (Helpers::hasValue(burnStarts_)) {
|
||||
doc["burnStarts"] = burnStarts_;
|
||||
}
|
||||
if (Helpers::hasValue(burnWorkMin_)) {
|
||||
doc["burnWorkMin"] = burnWorkMin_;
|
||||
}
|
||||
if (Helpers::hasValue(heatWorkMin_)) {
|
||||
doc["heatWorkMin"] = heatWorkMin_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(serviceCode_)) {
|
||||
doc["serviceCode"] = serviceCodeChar_;
|
||||
doc["serviceCodeNumber"] = serviceCode_;
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Performing a boiler publish"));
|
||||
#endif
|
||||
|
||||
// if we have data, publish it
|
||||
if (!doc.isNull()) {
|
||||
Mqtt::publish("boiler_data", doc);
|
||||
}
|
||||
}
|
||||
|
||||
// called after a process command is called, to check values and see if we need to force an MQTT publish
|
||||
bool Boiler::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// print values to shell console
|
||||
void Boiler::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // for showing the header
|
||||
|
||||
if (Helpers::hasValue(tap_water_active_, true)) {
|
||||
print_value(shell, 2, F("Hot tap water"), tap_water_active_ ? F("running") : F("off"));
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(heating_active_, true)) {
|
||||
print_value(shell, 2, F("Central heating"), heating_active_ ? F("active") : F("off"));
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Warm Water activated"), wWActivated_, nullptr, EMS_VALUE_BOOL);
|
||||
if (Helpers::hasValue(wWCircPumpType_, true)) {
|
||||
print_value(shell, 2, F("Warm Water charging type"), wWCircPumpType_ ? F("3-way valve") : F("charge pump"));
|
||||
}
|
||||
print_value(shell, 2, F("Warm Water circulation pump available"), wWCircPump_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
if (Helpers::hasValue(wWCircPumpMode_)) {
|
||||
if (wWCircPumpMode_ == 7) {
|
||||
print_value(shell, 2, F("Warm Water circulation pump freq"), F("continuous"));
|
||||
} else {
|
||||
char s[7];
|
||||
char buffer[2];
|
||||
buffer[0] = (wWCircPumpMode_ % 10) + '0';
|
||||
buffer[1] = '\0';
|
||||
strlcpy(s, buffer, 7);
|
||||
strlcat(s, "x3min", 7);
|
||||
print_value(shell, 2, F("Warm Water circulation pump freq"), s);
|
||||
}
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Warm Water circulation active"), wWCirc_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
if (wWComfort_ == 0x00) {
|
||||
print_value(shell, 2, F("Warm Water comfort setting"), F("Hot"));
|
||||
} else if (wWComfort_ == 0xD8) {
|
||||
print_value(shell, 2, F("Warm Water comfort setting"), F("Eco"));
|
||||
} else if (wWComfort_ == 0xEC) {
|
||||
print_value(shell, 2, F("Warm Water comfort setting"), F("Intelligent"));
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Warm water mix temperature"), wwMixTemperature_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Warm water buffer boiler temperature"), wwBufferBoilerTemperature_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Warm Water disinfection temperature"), wWDisinfectTemp_, F_(degrees));
|
||||
print_value(shell, 2, F("Warm Water selected temperature"), wWSelTemp_, F_(degrees));
|
||||
print_value(shell, 2, F("Warm Water set temperature"), wWSetTmp_, F_(degrees));
|
||||
print_value(shell, 2, F("Warm Water current temperature (intern)"), wWCurTmp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Warm water storage temperature (intern)"), wwStorageTemp1_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Warm Water current temperature (extern)"), wWCurTmp2_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Warm water storage temperature (extern)"), wwStorageTemp2_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Warm Water current tap water flow"), wWCurFlow_, F("l/min"), 10);
|
||||
print_value(shell, 2, F("Warm Water # starts"), wWStarts_, nullptr);
|
||||
if (Helpers::hasValue(wWWorkM_)) {
|
||||
shell.printfln(F(" Warm Water active time: %d days %d hours %d minutes"), wWWorkM_ / 1440, (wWWorkM_ % 1440) / 60, wWWorkM_ % 60);
|
||||
}
|
||||
print_value(shell, 2, F("Warm Water charging"), wWHeat_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Warm Water disinfecting"), wWDesinfecting_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Selected flow temperature"), selFlowTemp_, F_(degrees));
|
||||
print_value(shell, 2, F("Current flow temperature"), curFlowTemp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Max boiler temperature"), boilTemp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Return temperature"), retTemp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Gas"), burnGas_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Boiler pump"), heatPmp_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Fan"), fanWork_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Ignition"), ignWork_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
print_value(shell, 2, F("Burner selected max power"), selBurnPow_, F_(percent));
|
||||
print_value(shell, 2, F("Burner current power"), curBurnPow_, F_(percent));
|
||||
print_value(shell, 2, F("Flame current"), flameCurr_, F("uA"), 10);
|
||||
print_value(shell, 2, F("System pressure"), sysPress_, F("bar"), 10);
|
||||
if (Helpers::hasValue(serviceCode_)) {
|
||||
shell.printfln(F(" System service code: %s (%d)"), serviceCodeChar_, serviceCode_);
|
||||
} else if (serviceCodeChar_[0] != '\0') {
|
||||
print_value(shell, 2, F("System service code"), serviceCodeChar_);
|
||||
}
|
||||
|
||||
// UBAParameters
|
||||
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));
|
||||
|
||||
// UBAMonitorSlow
|
||||
if (Helpers::hasValue(extTemp_)) {
|
||||
print_value(shell, 2, F("Outside temperature"), extTemp_, F_(degrees), 10);
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Exhaust temperature"), exhaustTemp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Pump modulation"), pumpMod_, F_(percent));
|
||||
print_value(shell, 2, F("Pump modulation2"), pumpMod2_, F_(percent));
|
||||
print_value(shell, 2, F("Burner # starts"), burnStarts_, nullptr);
|
||||
if (Helpers::hasValue(burnWorkMin_)) {
|
||||
shell.printfln(F(" Total burner operating time: %d days %d hours %d minutes"), burnWorkMin_ / 1440, (burnWorkMin_ % 1440) / 60, burnWorkMin_ % 60);
|
||||
}
|
||||
if (Helpers::hasValue(heatWorkMin_)) {
|
||||
shell.printfln(F(" Total heat operating time: %d days %d hours %d minutes"), heatWorkMin_ / 1440, (heatWorkMin_ % 1440) / 60, heatWorkMin_ % 60);
|
||||
}
|
||||
if (Helpers::hasValue(UBAuptime_)) {
|
||||
shell.printfln(F(" Total UBA working time: %d days %d hours %d minutes"), UBAuptime_ / 1440, (UBAuptime_ % 1440) / 60, UBAuptime_ % 60);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if hot tap water or heating is active
|
||||
* If a value has changed, post it immediately to MQTT so we get real time data
|
||||
*/
|
||||
void Boiler::check_active() {
|
||||
// hot tap water, using flow to check instead of the burner power
|
||||
// send these values back to the main EMSESP, so other classes (e.g. Shower) can use it
|
||||
if (Helpers::hasValue(wWCurFlow_) && Helpers::hasValue(burnGas_)) {
|
||||
tap_water_active_ = ((wWCurFlow_ != 0) && (burnGas_ != EMS_VALUE_BOOL_OFF));
|
||||
EMSESP::tap_water_active(tap_water_active_);
|
||||
}
|
||||
|
||||
// heating
|
||||
// using a quick hack for checking the heating by looking at the Selected Flow Temp, but doesn't work for all boilers apparently
|
||||
if (Helpers::hasValue(selFlowTemp_) && Helpers::hasValue(burnGas_)) {
|
||||
heating_active_ = (!tap_water_active_ && ((selFlowTemp_ >= EMS_BOILER_SELFLOWTEMP_HEATING) && (burnGas_ != EMS_VALUE_BOOL_OFF)));
|
||||
}
|
||||
|
||||
// see if the heating or hot tap water has changed, if so send
|
||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||
if (Helpers::hasValue(tap_water_active_, true) && Helpers::hasValue(heating_active_, true)) {
|
||||
uint8_t latest_boilerState = (tap_water_active_ << 1) + heating_active_;
|
||||
if (latest_boilerState != last_boilerState) {
|
||||
last_boilerState = latest_boilerState;
|
||||
Mqtt::publish("tapwater_active", tap_water_active_);
|
||||
Mqtt::publish("heating_active", heating_active_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 0x33
|
||||
void Boiler::process_UBAParameterWW(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wWActivated_, 1); // 0xFF means on
|
||||
telegram->read_value(wWCircPump_, 6); // 0xFF means on
|
||||
telegram->read_value(wWCircPumpMode_, 7); // 1=1x3min... 6=6x3min, 7=continuous
|
||||
telegram->read_value(wWCircPumpType_, 10); // 0 = charge pump, 0xff = 3-way valve
|
||||
telegram->read_value(wWSelTemp_, 2);
|
||||
telegram->read_value(wWDisinfectTemp_, 8);
|
||||
telegram->read_value(wWComfort_, 9);
|
||||
}
|
||||
|
||||
// 0x18
|
||||
void Boiler::process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(selFlowTemp_, 0);
|
||||
telegram->read_value(curFlowTemp_, 1);
|
||||
telegram->read_value(selBurnPow_, 3); // burn power max setting
|
||||
telegram->read_value(curBurnPow_, 4);
|
||||
|
||||
telegram->read_bitvalue(burnGas_, 7, 0);
|
||||
telegram->read_bitvalue(fanWork_, 7, 2);
|
||||
telegram->read_bitvalue(ignWork_, 7, 3);
|
||||
telegram->read_bitvalue(heatPmp_, 7, 5);
|
||||
telegram->read_bitvalue(wWHeat_, 7, 6);
|
||||
telegram->read_bitvalue(wWCirc_, 7, 7);
|
||||
|
||||
// warm water storage sensors (if present)
|
||||
// wwStorageTemp2 is also used by some brands as the boiler temperature - see https://github.com/proddy/EMS-ESP/issues/206
|
||||
telegram->read_value(wwStorageTemp1_, 9); // 0x8300 if not available
|
||||
telegram->read_value(wwStorageTemp2_, 11); // 0x8000 if not available - this is boiler temp
|
||||
|
||||
telegram->read_value(retTemp_, 13);
|
||||
telegram->read_value(flameCurr_, 15);
|
||||
telegram->read_value(serviceCode_, 20);
|
||||
|
||||
// system pressure. FF means missing
|
||||
telegram->read_value(sysPress_, 17); // is *10
|
||||
|
||||
// read the service code / installation status as appears on the display
|
||||
if ((telegram->message_length > 18) && (telegram->offset == 0)) {
|
||||
serviceCodeChar_[0] = char(telegram->message_data[18]); // ascii character 1
|
||||
serviceCodeChar_[1] = char(telegram->message_data[19]); // ascii character 2
|
||||
serviceCodeChar_[2] = '\0'; // null terminate string
|
||||
}
|
||||
|
||||
// at this point do a quick check to see if the hot water or heating is active
|
||||
check_active();
|
||||
}
|
||||
|
||||
/*
|
||||
* UBATotalUptime - type 0x14 - total uptime
|
||||
* received only after requested (not broadcasted)
|
||||
*/
|
||||
void Boiler::process_UBATotalUptime(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(UBAuptime_, 0, 3); // force to 3 bytes
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAParameters - type 0x16
|
||||
*/
|
||||
void Boiler::process_UBAParameters(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(heating_temp_, 1);
|
||||
telegram->read_value(pump_mod_max_, 9);
|
||||
telegram->read_value(pump_mod_min_, 10);
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAMonitorWW - type 0x34 - warm water monitor. 19 bytes long
|
||||
* received every 10 seconds
|
||||
*/
|
||||
void Boiler::process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wWSetTmp_, 0);
|
||||
telegram->read_value(wWCurTmp_, 1);
|
||||
telegram->read_value(wWCurTmp2_, 3);
|
||||
telegram->read_value(wWCurFlow_, 9);
|
||||
|
||||
telegram->read_value(wWWorkM_, 10, 3); // force to 3 bytes
|
||||
telegram->read_value(wWStarts_, 13, 3); // force to 3 bytes
|
||||
|
||||
telegram->read_bitvalue(wWOneTime_, 5, 1);
|
||||
telegram->read_bitvalue(wWDesinfecting_, 5, 2);
|
||||
telegram->read_bitvalue(wWReadiness_, 5, 3);
|
||||
telegram->read_bitvalue(wWRecharging_, 5, 4);
|
||||
telegram->read_bitvalue(wWTemperatureOK_, 5, 5);
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAMonitorFastPlus - type 0xE4 - central heating monitor EMS+
|
||||
* Still to figure out are: serviceCode, retTemp, sysPress
|
||||
*/
|
||||
void Boiler::process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(selFlowTemp_, 6);
|
||||
telegram->read_bitvalue(burnGas_, 11, 0);
|
||||
telegram->read_bitvalue(wWHeat_, 11, 2);
|
||||
telegram->read_value(curBurnPow_, 10);
|
||||
telegram->read_value(selBurnPow_, 9);
|
||||
telegram->read_value(curFlowTemp_, 7);
|
||||
telegram->read_value(flameCurr_, 19);
|
||||
|
||||
// read the service code / installation status as appears on the display
|
||||
if ((telegram->message_length > 4) && (telegram->offset == 0)) {
|
||||
serviceCodeChar_[0] = char(telegram->message_data[4]); // ascii character 1
|
||||
serviceCodeChar_[1] = char(telegram->message_data[5]); // ascii character 2
|
||||
serviceCodeChar_[2] = '\0';
|
||||
}
|
||||
|
||||
// at this point do a quick check to see if the hot water or heating is active
|
||||
check_active();
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAMonitorSlow - type 0x19 - central heating monitor part 2 (27 bytes long)
|
||||
* received every 60 seconds
|
||||
* e.g. 08 00 19 00 80 00 02 41 80 00 00 00 00 00 03 91 7B 05 B8 40 00 00 00 04 92 AD 00 5E EE 80 00
|
||||
* 08 0B 19 00 FF EA 02 47 80 00 00 00 00 62 03 CA 24 2C D6 23 00 00 00 27 4A B6 03 6E 43
|
||||
* 00 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 17 19 20 21 22 23 24
|
||||
*/
|
||||
void Boiler::process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(extTemp_, 0);
|
||||
telegram->read_value(boilTemp_, 2);
|
||||
telegram->read_value(exhaustTemp_, 4);
|
||||
telegram->read_value(switchTemp_, 25); // only if there is a mixing module present
|
||||
telegram->read_value(pumpMod_, 9);
|
||||
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
|
||||
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
|
||||
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAMonitorSlowPlus2 - type 0xE3
|
||||
*/
|
||||
void Boiler::process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(pumpMod2_, 13);
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAMonitorSlowPlus - type 0xE5 - central heating monitor EMS+
|
||||
*/
|
||||
void Boiler::process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_bitvalue(fanWork_, 2, 2);
|
||||
telegram->read_bitvalue(ignWork_, 2, 3);
|
||||
telegram->read_bitvalue(heatPmp_, 2, 5);
|
||||
telegram->read_bitvalue(wWCirc_, 2, 7);
|
||||
telegram->read_value(burnStarts_, 10, 3); // force to 3 bytes
|
||||
telegram->read_value(burnWorkMin_, 13, 3); // force to 3 bytes
|
||||
telegram->read_value(heatWorkMin_, 19, 3); // force to 3 bytes
|
||||
telegram->read_value(pumpMod_, 25);
|
||||
}
|
||||
|
||||
// 0xE9 - DHW Status
|
||||
// e.g. 08 00 E9 00 37 01 F6 01 ED 00 00 00 00 41 3C 00 00 00 00 00 00 00 00 00 00 00 00 37 00 00 00 (CRC=77) #data=27
|
||||
void Boiler::process_UBADHWStatus(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wWSetTmp_, 0);
|
||||
telegram->read_value(wWCurTmp_, 1);
|
||||
telegram->read_value(wWCurTmp2_, 3);
|
||||
|
||||
telegram->read_value(wWWorkM_, 17, 3); // force to 3 bytes
|
||||
telegram->read_value(wWStarts_, 14, 3); // force to 3 bytes
|
||||
|
||||
telegram->read_bitvalue(wWOneTime_, 12, 2);
|
||||
telegram->read_bitvalue(wWDesinfecting_, 12, 3);
|
||||
telegram->read_bitvalue(wWReadiness_, 12, 4);
|
||||
telegram->read_bitvalue(wWRecharging_, 13, 4);
|
||||
telegram->read_bitvalue(wWTemperatureOK_, 13, 5);
|
||||
telegram->read_bitvalue(wWCircPump_, 13, 2);
|
||||
|
||||
telegram->read_value(wWActivated_, 20);
|
||||
telegram->read_value(wWSelTemp_, 10);
|
||||
telegram->read_value(wWDisinfectTemp_, 9);
|
||||
}
|
||||
|
||||
// 0x2A - MC10Status
|
||||
// e.g. 88 00 2A 00 00 00 00 00 00 00 00 00 D2 00 00 80 00 00 01 08 80 00 02 47 00
|
||||
// see https://github.com/proddy/EMS-ESP/issues/397
|
||||
void Boiler::process_MC10Status(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(wwMixTemperature_, 14);
|
||||
telegram->read_value(wwBufferBoilerTemperature_, 18);
|
||||
}
|
||||
|
||||
/*
|
||||
* UBAOutdoorTemp - type 0xD1 - external temperature EMS+
|
||||
*/
|
||||
void Boiler::process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(extTemp_, 0);
|
||||
}
|
||||
|
||||
#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) {
|
||||
}
|
||||
|
||||
// 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) {
|
||||
}
|
||||
|
||||
// 0x10, 0x11, 0x12
|
||||
// not yet implemented
|
||||
void Boiler::process_UBAErrorMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
// data: displaycode(2), errornumner(2), year, month, hour, day, minute, duration(2), src-addr
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// flow temp
|
||||
void Boiler::set_flow_temp(const uint8_t temperature) {
|
||||
LOG_INFO(F("Setting boiler flow temperature to %d C"), temperature);
|
||||
write_command(EMS_TYPE_UBASetPoints, 0, temperature);
|
||||
}
|
||||
|
||||
// 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)
|
||||
void Boiler::set_warmwater_mode(const uint8_t comfort) {
|
||||
uint8_t set;
|
||||
if (comfort == 1) {
|
||||
LOG_INFO(F("Setting boiler warm water to Hot"));
|
||||
set = 0x00;
|
||||
} else if (comfort == 2) {
|
||||
LOG_INFO(F("Setting boiler warm water to Eco"));
|
||||
set = 0xD8;
|
||||
} else if (comfort == 3) {
|
||||
LOG_INFO(F("Setting boiler warm water to Intelligent"));
|
||||
set = 0xEC;
|
||||
} else {
|
||||
return; // do nothing
|
||||
}
|
||||
write_command(EMS_TYPE_UBAParameterWW, 9, set);
|
||||
}
|
||||
|
||||
// turn on/off warm water
|
||||
void Boiler::set_warmwater_activated(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm water %s"), activated ? "on" : "off");
|
||||
uint8_t value;
|
||||
|
||||
// https://github.com/proddy/EMS-ESP/issues/268
|
||||
if (EMSbus::is_ht3()) {
|
||||
value = (activated ? 0x08 : 0x00); // 0x08 is on, 0x00 is off
|
||||
} else {
|
||||
value = (activated ? 0xFF : 0x00); // 0xFF is on, 0x00 is off
|
||||
}
|
||||
write_command(EMS_TYPE_UBAParameterWW, 1, value);
|
||||
}
|
||||
|
||||
// Activate / De-activate the Warm Tap Water
|
||||
// true = on, false = off
|
||||
// Note: Using the type 0x1D to put the boiler into Test mode. This may be shown on the boiler with a flashing 'T'
|
||||
void Boiler::set_tapwarmwater_activated(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm tap water %s"), activated ? "on" : "off");
|
||||
uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH];
|
||||
for (uint8_t i = 0; i < sizeof(message_data); i++) {
|
||||
message_data[i] = 0x00;
|
||||
}
|
||||
|
||||
// we use the special test mode 0x1D for this. Setting the first data to 5A puts the system into test mode and
|
||||
// a setting of 0x00 puts it back into normal operating mode
|
||||
// when in test mode we're able to mess around with the 3-way valve settings
|
||||
if (!activated) {
|
||||
// on
|
||||
message_data[0] = 0x5A; // test mode on
|
||||
message_data[1] = 0x00; // burner output 0%
|
||||
message_data[3] = 0x64; // boiler pump capacity 100%
|
||||
message_data[4] = 0xFF; // 3-way valve hot water only
|
||||
} else {
|
||||
// get out of test mode. Send all zeros.
|
||||
// telegram: 0B 08 1D 00 00
|
||||
message_data[4] = 0x00; // test mode off
|
||||
}
|
||||
|
||||
write_command(EMS_TYPE_UBAFunctionTest, 0, message_data, sizeof(message_data), 0);
|
||||
}
|
||||
|
||||
// Activate / De-activate One Time warm water 0x35
|
||||
// true = on, false = off
|
||||
// See also https://github.com/proddy/EMS-ESP/issues/341#issuecomment-596245458 for Junkers
|
||||
void Boiler::set_warmwater_onetime(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm water OneTime loading %s"), activated ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 0, (activated ? 0x22 : 0x02));
|
||||
}
|
||||
|
||||
// Activate / De-activate circulation of warm water 0x35
|
||||
// true = on, false = off
|
||||
void Boiler::set_warmwater_circulation(const bool activated) {
|
||||
LOG_INFO(F("Setting boiler warm water circulation %s"), activated ? "on" : "off");
|
||||
write_command(EMS_TYPE_UBAFlags, 1, (activated ? 0x22 : 0x02));
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Boiler::console_commands(Shell & shell, unsigned int context) {
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(read)},
|
||||
flash_string_vector{F_(typeid_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
uint16_t type_id = Helpers::hextoint(arguments.front().c_str());
|
||||
EMSESP::send_read_request(type_id, device_id());
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwtemp)},
|
||||
flash_string_vector{F_(degrees_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_warmwater_temp(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(flowtemp)},
|
||||
flash_string_vector{F_(degrees_mandatory)},
|
||||
[=](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
set_flow_temp(Helpers::atoint(arguments.front().c_str()));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwactive)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
set_warmwater_activated(true);
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
set_warmwater_activated(false);
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwonetime)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
set_warmwater_onetime(true);
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
set_warmwater_onetime(false);
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(wwcirculation)},
|
||||
flash_string_vector{F_(bool_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(on))) {
|
||||
set_warmwater_circulation(true);
|
||||
} else if (arguments[0] == read_flash_string(F_(off))) {
|
||||
set_warmwater_circulation(false);
|
||||
} else {
|
||||
shell.println(F("Must be on or off"));
|
||||
return;
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(on)), read_flash_string(F_(off))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::BOILER,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(comfort)},
|
||||
flash_string_vector{F_(comfort_mandatory)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
if (arguments[0] == read_flash_string(F_(hot))) {
|
||||
set_warmwater_mode(1);
|
||||
} else if (arguments[0] == read_flash_string(F_(eco))) {
|
||||
set_warmwater_mode(2);
|
||||
} else if (arguments[0] == read_flash_string(F_(intelligent))) {
|
||||
set_warmwater_mode(3);
|
||||
} else {
|
||||
shell.println(F("Invalid value. Must be hot, eco or intelligent"));
|
||||
}
|
||||
},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) -> const std::vector<std::string> {
|
||||
return std::vector<std::string>{read_flash_string(F_(hot)), read_flash_string(F_(eco)), read_flash_string(F_(intelligent))};
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::BOILER,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show)},
|
||||
[&](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { show_values(shell); });
|
||||
|
||||
// enter the context
|
||||
Console::enter_custom_context(shell, context);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
168
src/devices/boiler.h
Normal file
168
src/devices/boiler.h
Normal file
@@ -0,0 +1,168 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_BOILER_H
|
||||
#define EMSESP_BOILER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "emsesp.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Boiler : public EMSdevice {
|
||||
public:
|
||||
Boiler(uint8_t device_type, int8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
|
||||
uint8_t last_boilerState = 0xFF; // remember last state of heating and warm water on/off
|
||||
|
||||
static constexpr uint8_t EMS_TYPE_UBAParameterWW = 0x33;
|
||||
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_BOILER_SELFLOWTEMP_HEATING = 20; // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
|
||||
|
||||
// UBAParameterWW
|
||||
uint8_t wWActivated_ = EMS_VALUE_BOOL_NOTSET; // Warm Water activated
|
||||
uint8_t wWSelTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water selected temperature
|
||||
uint8_t wWCircPump_ = EMS_VALUE_BOOL_NOTSET; // Warm Water circulation pump available
|
||||
uint8_t wWCircPumpMode_ = EMS_VALUE_UINT_NOTSET; // Warm Water circulation pump mode
|
||||
uint8_t wWCircPumpType_ = EMS_VALUE_BOOL_NOTSET; // Warm Water circulation pump type
|
||||
uint8_t wWDisinfectTemp_ = EMS_VALUE_UINT_NOTSET; // Warm Water disinfection temperature to prevent infection
|
||||
uint8_t wWComfort_ = EMS_VALUE_UINT_NOTSET; // WW comfort mode
|
||||
|
||||
// MC10Status
|
||||
uint16_t wwMixTemperature_ = EMS_VALUE_USHORT_NOTSET; // mengertemperatuur
|
||||
uint16_t wwBufferBoilerTemperature_ = EMS_VALUE_USHORT_NOTSET; // bufferboilertemperatuur
|
||||
|
||||
// UBAMonitorFast - 0x18 on EMS1
|
||||
uint8_t selFlowTemp_ = EMS_VALUE_UINT_NOTSET; // Selected flow temperature
|
||||
uint16_t curFlowTemp_ = EMS_VALUE_USHORT_NOTSET; // Current flow temperature
|
||||
uint16_t wwStorageTemp1_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 1
|
||||
uint16_t wwStorageTemp2_ = EMS_VALUE_USHORT_NOTSET; // warm water storage temp 2
|
||||
uint16_t retTemp_ = EMS_VALUE_USHORT_NOTSET; // Return temperature
|
||||
uint8_t burnGas_ = EMS_VALUE_BOOL_NOTSET; // Gas on/off
|
||||
uint8_t fanWork_ = EMS_VALUE_BOOL_NOTSET; // Fan on/off
|
||||
uint8_t ignWork_ = EMS_VALUE_BOOL_NOTSET; // Ignition on/off
|
||||
uint8_t heatPmp_ = EMS_VALUE_BOOL_NOTSET; // Boiler pump on/off
|
||||
uint8_t wWHeat_ = EMS_VALUE_BOOL_NOTSET; // 3-way valve on WW
|
||||
uint8_t wWCirc_ = EMS_VALUE_BOOL_NOTSET; // Circulation on/off
|
||||
uint8_t selBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner max power %
|
||||
uint8_t curBurnPow_ = EMS_VALUE_UINT_NOTSET; // Burner current power %
|
||||
uint16_t flameCurr_ = EMS_VALUE_USHORT_NOTSET; // Flame current in micro amps
|
||||
uint8_t sysPress_ = EMS_VALUE_UINT_NOTSET; // System pressure
|
||||
char serviceCodeChar_[3] = {'\0'}; // 2 character status/service code
|
||||
uint16_t serviceCode_ = EMS_VALUE_USHORT_NOTSET; // error/service code
|
||||
|
||||
// UBAMonitorSlow - 0x19 on EMS1
|
||||
int16_t extTemp_ = EMS_VALUE_SHORT_NOTSET; // Outside temperature
|
||||
uint16_t boilTemp_ = EMS_VALUE_USHORT_NOTSET; // Boiler temperature
|
||||
uint16_t exhaustTemp_ = EMS_VALUE_USHORT_NOTSET; // Exhaust temperature
|
||||
uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET; // Pump modulation %
|
||||
uint32_t burnStarts_ = EMS_VALUE_ULONG_NOTSET; // # burner restarts
|
||||
uint32_t burnWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total burner operating time
|
||||
uint32_t heatWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total heat operating time
|
||||
uint16_t switchTemp_ = EMS_VALUE_USHORT_NOTSET; // Switch temperature
|
||||
|
||||
// UBAMonitorWW
|
||||
uint8_t wWSetTmp_ = EMS_VALUE_UINT_NOTSET; // Warm Water set temperature
|
||||
uint16_t wWCurTmp_ = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature
|
||||
uint16_t wWCurTmp2_ = EMS_VALUE_USHORT_NOTSET; // Warm Water current temperature storage
|
||||
uint32_t wWStarts_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # starts
|
||||
uint32_t wWWorkM_ = EMS_VALUE_ULONG_NOTSET; // Warm Water # minutes
|
||||
uint8_t wWOneTime_ = EMS_VALUE_BOOL_NOTSET; // Warm Water one time function on/off
|
||||
uint8_t wWDesinfecting_ = EMS_VALUE_BOOL_NOTSET; // Warm Water disinfection on/off
|
||||
uint8_t wWReadiness_ = EMS_VALUE_BOOL_NOTSET; // Warm Water readiness on/off
|
||||
uint8_t wWRecharging_ = EMS_VALUE_BOOL_NOTSET; // Warm Water recharge on/off
|
||||
uint8_t wWTemperatureOK_ = EMS_VALUE_BOOL_NOTSET; // Warm Water temperature ok on/off
|
||||
uint8_t wWCurFlow_ = EMS_VALUE_UINT_NOTSET; // Warm Water current flow temp in l/min
|
||||
|
||||
// UBATotalUptime
|
||||
uint32_t UBAuptime_ = EMS_VALUE_ULONG_NOTSET; // Total UBA working hours
|
||||
|
||||
// UBAParameters
|
||||
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 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)
|
||||
|
||||
void process_UBAParameterWW(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorFast(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBATotalUptime(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAParameters(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorWW(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorFastPlus(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorSlow(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorSlowPlus(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBAMonitorSlowPlus2(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_UBAOutdoorTemp(std::shared_ptr<const Telegram> telegram);
|
||||
void process_UBASetPoints(std::shared_ptr<const Telegram> telegram);
|
||||
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_UBAErrorMessage(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_UBADHWStatus(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void check_active();
|
||||
|
||||
void set_warmwater_temp(const uint8_t temperature);
|
||||
void set_flow_temp(const uint8_t temperature);
|
||||
void set_warmwater_mode(const uint8_t comfort);
|
||||
void set_warmwater_activated(const bool activated);
|
||||
void set_tapwarmwater_activated(const bool activated);
|
||||
void set_warmwater_onetime(const bool activated);
|
||||
void set_warmwater_circulation(const bool activated);
|
||||
|
||||
// mqtt callbacks
|
||||
void boiler_cmd(const char * message);
|
||||
void boiler_cmd_wwactivated(const char * message);
|
||||
void boiler_cmd_wwonetime(const char * message);
|
||||
void boiler_cmd_wwcirculation(const char * message);
|
||||
void boiler_cmd_wwtemp(const char * message);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
58
src/devices/connect.cpp
Normal file
58
src/devices/connect.cpp
Normal file
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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 "connect.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Connect, EMSdevice::DeviceType::CONNECT);
|
||||
|
||||
MAKE_PSTR(logger_name, "connect")
|
||||
uuid::log::Logger Connect::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Connect::Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Connect::add_context_menu() {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Connect::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Connect::publish_values() {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Connect::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Connect::console_commands() {
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
51
src/devices/connect.h
Normal file
51
src/devices/connect.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_CONNECT_H
|
||||
#define EMSESP_CONNECT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Connect : public EMSdevice {
|
||||
public:
|
||||
Connect(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
60
src/devices/controller.cpp
Normal file
60
src/devices/controller.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "controller.h"
|
||||
|
||||
// MAKE_PSTR_WORD(controller)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Controller, EMSdevice::DeviceType::CONTROLLER);
|
||||
|
||||
MAKE_PSTR(logger_name, "controller")
|
||||
uuid::log::Logger Controller::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Controller::Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Controller::add_context_menu() {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Controller::publish_values() {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Controller::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Controller::console_commands() {
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
51
src/devices/controller.h
Normal file
51
src/devices/controller.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_CONTROLLER_H
|
||||
#define EMSESP_CONTROLLER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Controller : public EMSdevice {
|
||||
public:
|
||||
Controller(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
60
src/devices/gateway.cpp
Normal file
60
src/devices/gateway.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "gateway.h"
|
||||
|
||||
// MAKE_PSTR_WORD(gateway)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Gateway, EMSdevice::DeviceType::GATEWAY);
|
||||
|
||||
MAKE_PSTR(logger_name, "gateway")
|
||||
uuid::log::Logger Gateway::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Gateway::Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Gateway::add_context_menu() {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Gateway::publish_values() {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Gateway::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Gateway::console_commands() {
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
51
src/devices/gateway.h
Normal file
51
src/devices/gateway.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_GATEWAY_H
|
||||
#define EMSESP_GATEWAY_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Gateway : public EMSdevice {
|
||||
public:
|
||||
Gateway(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
92
src/devices/heatpump.cpp
Normal file
92
src/devices/heatpump.cpp
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* 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 "heatpump.h"
|
||||
|
||||
// MAKE_PSTR_WORD(heatpump)
|
||||
|
||||
/*
|
||||
example telegrams 0x32B, 0x37B
|
||||
"38 10 FF 00 03 7B 08 24 00 4B",
|
||||
"38 10 FF 00 03 2B 00 C7 07 C3 01",
|
||||
"38 10 FF 00 03 2B 00 D1 08 2A 01",
|
||||
*/
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Heatpump, EMSdevice::DeviceType::HEATPUMP);
|
||||
|
||||
MAKE_PSTR(logger_name, "heatpump")
|
||||
uuid::log::Logger Heatpump::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Heatpump::Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Heat Pump module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers
|
||||
register_telegram_type(0x047B, F("HP1"), true, std::bind(&Heatpump::process_HPMonitor1, this, _1));
|
||||
register_telegram_type(0x042B, F("HP2"), true, std::bind(&Heatpump::process_HPMonitor2, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Heatpump::cmd, this, _1));
|
||||
}
|
||||
|
||||
// context submenu
|
||||
void Heatpump::add_context_menu() {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Heatpump::publish_values() {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Heatpump::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Heatpump::console_commands() {
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
/*
|
||||
* Type 0x42B- HeatPump Monitor 1
|
||||
* e.g. "38 10 FF 00 03 2B 00 D1 08 2A 01"
|
||||
*/
|
||||
void Heatpump::process_HPMonitor1(std::shared_ptr<const Telegram> telegram) {
|
||||
// still to implement
|
||||
}
|
||||
|
||||
/*
|
||||
* Type 0x47B - HeatPump Monitor 2
|
||||
* e.g. "38 10 FF 00 03 7B 08 24 00 4B"
|
||||
*/
|
||||
void Heatpump::process_HPMonitor2(std::shared_ptr<const Telegram> telegram) {
|
||||
// still to implement
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace emsesp
|
||||
54
src/devices/heatpump.h
Normal file
54
src/devices/heatpump.h
Normal file
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_HEATPUMP_H
|
||||
#define EMSESP_HEATPUMP_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Heatpump : public EMSdevice {
|
||||
public:
|
||||
Heatpump(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
|
||||
void process_HPMonitor1(std::shared_ptr<const Telegram> telegram);
|
||||
void process_HPMonitor2(std::shared_ptr<const Telegram> telegram);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
213
src/devices/mixing.cpp
Normal file
213
src/devices/mixing.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
/*
|
||||
* 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 "mixing.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Mixing, EMSdevice::DeviceType::MIXING);
|
||||
|
||||
MAKE_PSTR(logger_name, "mixing")
|
||||
uuid::log::Logger Mixing::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Mixing::Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Mixing module with device ID 0x%02X"), device_id);
|
||||
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_MMPLUS) {
|
||||
if (device_id <= 0x27) {
|
||||
// telegram handlers 0x20 - 0x27 for HC
|
||||
register_telegram_type(device_id - 0x20 + 0x02D7, F("MMPLUSStatusMessage_HC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_HC, this, _1));
|
||||
} else {
|
||||
// telegram handlers for warm water/DHW 0x28, 0x29
|
||||
register_telegram_type(device_id - 0x28 + 0x0331, F("MMPLUSStatusMessage_WWC"), true, std::bind(&Mixing::process_MMPLUSStatusMessage_WWC, this, _1));
|
||||
}
|
||||
}
|
||||
// EMS 1.0
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_MM10) {
|
||||
register_telegram_type(0x00AA, F("MMConfigMessage"), false, std::bind(&Mixing::process_MMConfigMessage, this, _1));
|
||||
register_telegram_type(0x00AB, F("MMStatusMessage"), true, std::bind(&Mixing::process_MMStatusMessage, this, _1));
|
||||
register_telegram_type(0x00AC, F("MMSetMessage"), false, std::bind(&Mixing::process_MMSetMessage, this, _1));
|
||||
}
|
||||
// HT3
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_IPM) {
|
||||
register_telegram_type(0x010C, F("IPMSetMessage"), false, std::bind(&Mixing::process_IPMStatusMessage, this, _1));
|
||||
}
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Mixing::cmd, this, _1));
|
||||
}
|
||||
|
||||
// add context submenu
|
||||
void Mixing::add_context_menu() {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Mixing::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Mixing::console_commands() {
|
||||
}
|
||||
|
||||
// display all values into the shell console
|
||||
void Mixing::show_values(uuid::console::Shell & shell) {
|
||||
EMSdevice::show_values(shell); // always call this to show header
|
||||
|
||||
if (type_ == Type::NONE) {
|
||||
return; // don't have any values yet
|
||||
}
|
||||
|
||||
if (type_ == Type::WWC) {
|
||||
print_value(shell, 2, F("Warm Water Circuit"), hc_, nullptr);
|
||||
} else {
|
||||
print_value(shell, 2, F("Heating Circuit"), hc_, nullptr);
|
||||
}
|
||||
print_value(shell, 4, F("Current flow temperature"), flowTemp_, F_(degrees), 10);
|
||||
print_value(shell, 4, F("Setpoint flow temperature"), flowSetTemp_, F_(degrees));
|
||||
print_value(shell, 4, F("Current pump modulation"), pumpMod_, F_(percent));
|
||||
print_value(shell, 4, F("Current valve status"), status_, nullptr);
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
// ideally we should group up all the mixing units together into a nested JSON but for now we'll send them individually
|
||||
void Mixing::publish_values() {
|
||||
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_SMALL);
|
||||
|
||||
switch (type_) {
|
||||
case Type::HC:
|
||||
doc["type"] = "hc";
|
||||
break;
|
||||
case Type::WWC:
|
||||
doc["type"] = "wwc";
|
||||
break;
|
||||
case Type::NONE:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(flowTemp_)) {
|
||||
doc["flowTemp"] = (float)flowTemp_ / 10;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(pumpMod_)) {
|
||||
doc["pumpMod"] = pumpMod_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(status_)) {
|
||||
doc["status"] = status_;
|
||||
}
|
||||
|
||||
if (Helpers::hasValue(flowSetTemp_)) {
|
||||
doc["flowSetTemp"] = flowSetTemp_;
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Performing a mixing module publish"));
|
||||
#endif
|
||||
char topic[30];
|
||||
char s[3]; // for formatting strings
|
||||
strlcpy(topic, "mixing_data", 30);
|
||||
strlcat(topic, Helpers::itoa(s, device_id() - 0x20 + 1), 30); // append hc to topic
|
||||
Mqtt::publish(topic, doc);
|
||||
}
|
||||
|
||||
// heating circuits 0x02D7, 0x02D8 etc...
|
||||
// e.g. A0 00 FF 00 01 D7 00 00 00 80 00 00 00 00 03 C5
|
||||
// A0 0B FF 00 01 D7 00 00 00 80 00 00 00 00 03 80
|
||||
void Mixing::process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
hc_ = telegram->type_id - 0x02D7 + 1; // determine which circuit this is
|
||||
telegram->read_value(flowTemp_, 3); // is * 10
|
||||
telegram->read_value(flowSetTemp_, 5);
|
||||
telegram->read_value(pumpMod_, 2);
|
||||
telegram->read_value(status_, 1); // valve status
|
||||
}
|
||||
|
||||
// Mixing module warm water loading/DHW - 0x0331, 0x0332
|
||||
// e.g. A9 00 FF 00 02 32 02 6C 00 3C 00 3C 3C 46 02 03 03 00 3C // on 0x28
|
||||
// A8 00 FF 00 02 31 02 35 00 3C 00 3C 3C 46 02 03 03 00 3C // in 0x29
|
||||
void Mixing::process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::WWC;
|
||||
hc_ = telegram->type_id - 0x0331 + 1; // determine which circuit this is. There are max 2.
|
||||
telegram->read_value(flowTemp_, 0); // is * 10
|
||||
telegram->read_value(pumpMod_, 2);
|
||||
telegram->read_value(status_, 11); // temp status
|
||||
}
|
||||
|
||||
// Mixing IMP - 0x010C
|
||||
// e.g. A0 00 FF 00 00 0C 01 00 00 00 00 00 54
|
||||
// A1 00 FF 00 00 0C 02 04 00 01 1D 00 82
|
||||
void Mixing::process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
uint8_t ismixed = 0;
|
||||
telegram->read_value(ismixed, 0); // check if circuit is active, 0-off, 1-unmixed, 2-mixed
|
||||
if (ismixed == 0) {
|
||||
return;
|
||||
}
|
||||
if (ismixed == 2) { // we have a mixed circuit
|
||||
telegram->read_value(flowTemp_, 3); // is * 10
|
||||
telegram->read_value(flowSetTemp_, 5);
|
||||
telegram->read_value(status_, 2); // valve status
|
||||
}
|
||||
uint8_t pump = 0xFF;
|
||||
telegram->read_bitvalue(pump, 1, 0); // pump is also in unmixed circuits
|
||||
if (pump != 0xFF) {
|
||||
pumpMod_ = 100 * pump;
|
||||
}
|
||||
}
|
||||
|
||||
// Mixing on a MM10 - 0xAB
|
||||
// e.g. Mixing Module -> All, type 0xAB, telegram: 21 00 AB 00 2D 01 BE 64 04 01 00 (CRC=15) #data=7
|
||||
// see also https://github.com/proddy/EMS-ESP/issues/386
|
||||
void Mixing::process_MMStatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
type_ = Type::HC;
|
||||
|
||||
// the heating circuit is determine by which device_id it is, 0x20 - 0x23
|
||||
// 0x21 is position 2. 0x20 is typically reserved for the WM10 switch module
|
||||
// see https://github.com/proddy/EMS-ESP/issues/270 and https://github.com/proddy/EMS-ESP/issues/386#issuecomment-629610918
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
telegram->read_value(flowTemp_, 1); // is * 10
|
||||
telegram->read_value(pumpMod_, 3);
|
||||
telegram->read_value(flowSetTemp_, 0);
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
// Mixing on a MM10 - 0xAA
|
||||
// e.g. Thermostat -> Mixing Module, type 0xAA, telegram: 10 21 AA 00 FF 0C 0A 11 0A 32 xx
|
||||
void Mixing::process_MMConfigMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
// pos 0: active FF = on
|
||||
// pos 1: valve runtime 0C = 120 sec in units of 10 sec
|
||||
}
|
||||
|
||||
// Mixing on a MM10 - 0xAC
|
||||
// e.g. Thermostat -> Mixing Module, type 0xAC, telegram: 10 21 AC 00 1E 64 01 AB
|
||||
void Mixing::process_MMSetMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
hc_ = device_id() - 0x20 + 1;
|
||||
// pos 0: flowtemp setpoint 1E = 30°C
|
||||
// pos 1: position in %
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace emsesp
|
||||
72
src/devices/mixing.h
Normal file
72
src/devices/mixing.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_MIXING_H
|
||||
#define EMSESP_MIXING_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Mixing : public EMSdevice {
|
||||
public:
|
||||
Mixing(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
|
||||
void process_MMPLUSStatusMessage_HC(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MMPLUSStatusMessage_WWC(std::shared_ptr<const Telegram> telegram);
|
||||
void process_IPMStatusMessage(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MMStatusMessage(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MMConfigMessage(std::shared_ptr<const Telegram> telegram);
|
||||
void process_MMSetMessage(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
enum class Type {
|
||||
NONE,
|
||||
HC, // heating circuit
|
||||
WWC // warm water circuit
|
||||
};
|
||||
|
||||
private:
|
||||
uint16_t hc_ = EMS_VALUE_USHORT_NOTSET;
|
||||
uint16_t flowTemp_ = EMS_VALUE_USHORT_NOTSET;
|
||||
uint8_t pumpMod_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t status_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t flowSetTemp_ = EMS_VALUE_UINT_NOTSET;
|
||||
Type type_ = Type::NONE;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
236
src/devices/solar.cpp
Normal file
236
src/devices/solar.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* 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 "solar.h"
|
||||
|
||||
MAKE_PSTR(kwh, "kWh")
|
||||
MAKE_PSTR(wh, "Wh")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Solar, EMSdevice::DeviceType::SOLAR);
|
||||
|
||||
MAKE_PSTR(logger_name, "solar")
|
||||
uuid::log::Logger Solar::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Solar::Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
LOG_DEBUG(F("Registering new Solar module with device ID 0x%02X"), device_id);
|
||||
|
||||
// telegram handlers
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM10) {
|
||||
register_telegram_type(0x0097, F("SM10Monitor"), true, std::bind(&Solar::process_SM10Monitor, this, _1));
|
||||
}
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_SM100) {
|
||||
register_telegram_type(0x0362, F("SM100Monitor"), true, std::bind(&Solar::process_SM100Monitor, this, _1));
|
||||
register_telegram_type(0x0363, F("SM100Monitor2"), true, std::bind(&Solar::process_SM100Monitor2, this, _1));
|
||||
register_telegram_type(0x0366, F("SM100Config"), true, std::bind(&Solar::process_SM100Config, this, _1));
|
||||
|
||||
register_telegram_type(0x0364, F("SM100Status"), false, std::bind(&Solar::process_SM100Status, this, _1));
|
||||
register_telegram_type(0x036A, F("SM100Status2"), false, std::bind(&Solar::process_SM100Status2, this, _1));
|
||||
register_telegram_type(0x038E, F("SM100Energy"), true, std::bind(&Solar::process_SM100Energy, this, _1));
|
||||
}
|
||||
if (flags == EMSdevice::EMS_DEVICE_FLAG_ISM) {
|
||||
register_telegram_type(0x0103, F("ISM1StatusMessage"), true, std::bind(&Solar::process_ISM1StatusMessage, this, _1));
|
||||
register_telegram_type(0x0101, F("ISM1Set"), false, std::bind(&Solar::process_ISM1Set, this, _1));
|
||||
}
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Solar::cmd, this, _1));
|
||||
}
|
||||
|
||||
// context submenu
|
||||
void Solar::add_context_menu() {
|
||||
}
|
||||
|
||||
// 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
|
||||
|
||||
print_value(shell, 2, F("Collector temperature (TS1)"), collectorTemp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Bottom temperature (TS2)"), bottomTemp_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Bottom temperature (TS5)"), bottomTemp2_, F_(degrees), 10);
|
||||
print_value(shell, 2, F("Pump modulation"), pumpModulation_, F_(percent));
|
||||
print_value(shell, 2, F("Valve (VS2) status"), valveStatus_, nullptr, EMS_VALUE_BOOL);
|
||||
print_value(shell, 2, F("Pump (PS1) active"), pump_, nullptr, EMS_VALUE_BOOL);
|
||||
|
||||
if (Helpers::hasValue(pumpWorkMin_)) {
|
||||
shell.printfln(F(" Pump working time: %d days %d hours %d minutes"), pumpWorkMin_ / 1440, (pumpWorkMin_ % 1440) / 60, pumpWorkMin_ % 60);
|
||||
}
|
||||
|
||||
print_value(shell, 2, F("Energy last hour"), energyLastHour_, F_(wh), 10);
|
||||
print_value(shell, 2, F("Energy today"), energyToday_, F_(wh));
|
||||
print_value(shell, 2, F("Energy total"), energyTotal_, F_(kwh), 10);
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Solar::publish_values() {
|
||||
DynamicJsonDocument doc(EMSESP_MAX_JSON_SIZE_MEDIUM);
|
||||
|
||||
char s[10]; // for formatting strings
|
||||
|
||||
if (Helpers::hasValue(collectorTemp_)) {
|
||||
doc["collectortemp"] = (float)collectorTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(bottomTemp_)) {
|
||||
doc["bottomtemp"] = (float)bottomTemp_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(bottomTemp2_)) {
|
||||
doc["bottomtemp2"] = (float)bottomTemp2_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(pumpModulation_)) {
|
||||
doc["pumpmodulation"] = pumpModulation_;
|
||||
}
|
||||
if (Helpers::hasValue(pump_, true)) {
|
||||
doc["pump"] = Helpers::render_value(s, pump_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(valveStatus_, true)) {
|
||||
doc["valvestatus"] = Helpers::render_value(s, valveStatus_, EMS_VALUE_BOOL);
|
||||
}
|
||||
if (Helpers::hasValue(pumpWorkMin_)) {
|
||||
doc["pumpWorkMin"] = (float)pumpWorkMin_;
|
||||
}
|
||||
if (Helpers::hasValue(energyLastHour_)) {
|
||||
doc["energylasthour"] = (float)energyLastHour_ / 10;
|
||||
}
|
||||
if (Helpers::hasValue(energyToday_)) {
|
||||
doc["energytoday"] = energyToday_;
|
||||
}
|
||||
if (Helpers::hasValue(energyTotal_)) {
|
||||
doc["energytotal"] = (float)energyTotal_ / 10;
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Performing a solar module publish"));
|
||||
#endif
|
||||
|
||||
Mqtt::publish("sm_data", doc);
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Solar::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Solar::console_commands() {
|
||||
}
|
||||
|
||||
// SM10Monitor - type 0x97
|
||||
void Solar::process_SM10Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 2); // collector temp from SM10, is *10
|
||||
telegram->read_value(bottomTemp_, 5); // bottom temp from SM10, is *10
|
||||
telegram->read_value(pumpModulation_, 4); // modulation solar pump
|
||||
telegram->read_bitvalue(pump_, 7, 1);
|
||||
telegram->read_value(pumpWorkMin_, 8);
|
||||
}
|
||||
|
||||
/*
|
||||
* SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
|
||||
* e.g. B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
|
||||
* e.g, 30 00 FF 00 02 62 01 AC
|
||||
* 30 00 FF 18 02 62 80 00
|
||||
* 30 00 FF 00 02 62 01 A1 - for bottom temps
|
||||
* bytes 0+1 = TS1 Temperature sensor for collector
|
||||
* bytes 2+3 = TS2 Temperature sensor bottom cylinder 1
|
||||
* bytes 16+17 = TS5 Temperature sensor bottom cylinder 2
|
||||
*/
|
||||
void Solar::process_SM100Monitor(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 0); // is *10
|
||||
telegram->read_value(bottomTemp_, 2); // is *10
|
||||
telegram->read_value(bottomTemp2_, 16); // is *10
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
// SM100Monitor2 - 0x0363
|
||||
// e.g. B0 00 FF 00 02 63 80 00 80 00 00 00 80 00 80 00 80 00 00 80 00 5A
|
||||
void Solar::process_SM100Monitor2(std::shared_ptr<const Telegram> telegram) {
|
||||
// not implemented yet
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// SM100Config - 0x0366
|
||||
// e.g. B0 00 FF 00 02 66 01 62 00 13 40 14
|
||||
void Solar::process_SM100Config(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(availabilityFlag_, 0);
|
||||
telegram->read_value(configFlag_, 1);
|
||||
telegram->read_value(userFlag_, 2);
|
||||
}
|
||||
|
||||
/*
|
||||
* SM100Status - type 0x0364 EMS+ for pump modulation - for SM100 and SM200
|
||||
* e.g. 30 00 FF 09 02 64 64 = 100%
|
||||
* 30 00 FF 09 02 64 1E = 30%
|
||||
*/
|
||||
void Solar::process_SM100Status(std::shared_ptr<const Telegram> telegram) {
|
||||
uint8_t pumpmod = pumpModulation_;
|
||||
telegram->read_value(pumpModulation_, 9);
|
||||
if (pumpmod == 0 && pumpModulation_ == 100) { // mask out boosts
|
||||
pumpModulation_ = 15; // set to minimum
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* SM100Status2 - type 0x036A EMS+ for pump on/off at offset 0x0A - for SM100 and SM200
|
||||
* e.g. B0 00 FF 00 02 6A 03 03 03 03 01 03 03 03 03 03 01 03
|
||||
* byte 4 = VS2 3-way valve for cylinder 2 : test=01, on=04 and off=03
|
||||
* byte 10 = PS1 Solar circuit pump for collector array 1: test=b0001(1), on=b0100(4) and off=b0011(3)
|
||||
*/
|
||||
void Solar::process_SM100Status2(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_bitvalue(valveStatus_, 4, 2); // on if bit 2 set
|
||||
telegram->read_bitvalue(pump_, 10, 2); // on if bit 2 set
|
||||
}
|
||||
|
||||
/*
|
||||
* SM100Energy - type 0x038E EMS+ for energy readings
|
||||
* e.g. 30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35
|
||||
*/
|
||||
void Solar::process_SM100Energy(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(energyLastHour_, 0); // last hour / 10 in Wh
|
||||
telegram->read_value(energyToday_, 4); // todays in Wh
|
||||
telegram->read_value(energyTotal_, 8); // total / 10 in kWh
|
||||
}
|
||||
|
||||
/*
|
||||
* Junkers ISM1 Solar Module - type 0x0103 EMS+ for energy readings
|
||||
* e.g. B0 00 FF 00 00 03 32 00 00 00 00 13 00 D6 00 00 00 FB D0 F0
|
||||
*/
|
||||
void Solar::process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram) {
|
||||
telegram->read_value(collectorTemp_, 4); // Collector Temperature
|
||||
telegram->read_value(bottomTemp_, 6); // Temperature Bottom of Solar Boiler
|
||||
uint16_t Wh = 0xFFFF;
|
||||
telegram->read_value(Wh, 2); // Solar Energy produced in last hour only ushort, is not * 10
|
||||
if (Wh != 0xFFFF) {
|
||||
energyLastHour_ = Wh * 10; // set to *10
|
||||
}
|
||||
telegram->read_bitvalue(pump_, 8, 0); // Solar pump on (1) or off (0)
|
||||
telegram->read_value(pumpWorkMin_, 10, 3); // force to 3 bytes
|
||||
}
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
telegram->read_value(setpoint_maxBottomTemp_, 6);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
80
src/devices/solar.h
Normal file
80
src/devices/solar.h
Normal file
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_SOLAR_H
|
||||
#define EMSESP_SOLAR_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Solar : public EMSdevice {
|
||||
public:
|
||||
Solar(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
|
||||
int16_t collectorTemp_ = EMS_VALUE_SHORT_NOTSET; // collector temp (TS1)
|
||||
int16_t bottomTemp_ = EMS_VALUE_SHORT_NOTSET; // bottom temp (TS2)
|
||||
int16_t bottomTemp2_ = EMS_VALUE_SHORT_NOTSET; // bottom temp cylinder 2 (TS5)
|
||||
uint8_t pumpModulation_ = EMS_VALUE_UINT_NOTSET; // modulation solar pump
|
||||
uint8_t pump_ = EMS_VALUE_BOOL_NOTSET; // pump active
|
||||
uint8_t valveStatus_ = EMS_VALUE_BOOL_NOTSET; // valve status (VS2)
|
||||
int16_t setpoint_maxBottomTemp_ = EMS_VALUE_SHORT_NOTSET; // setpoint for maximum collector temp
|
||||
uint32_t energyLastHour_ = EMS_VALUE_ULONG_NOTSET;
|
||||
uint32_t energyToday_ = EMS_VALUE_ULONG_NOTSET;
|
||||
uint32_t energyTotal_ = EMS_VALUE_ULONG_NOTSET;
|
||||
uint32_t pumpWorkMin_ = EMS_VALUE_ULONG_NOTSET; // Total solar pump operating time
|
||||
|
||||
uint8_t availabilityFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t configFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
uint8_t userFlag_ = EMS_VALUE_BOOL_NOTSET;
|
||||
|
||||
void process_SM10Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Monitor2(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_SM100Config(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_SM100Status(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Status2(std::shared_ptr<const Telegram> telegram);
|
||||
void process_SM100Energy(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_ISM1StatusMessage(std::shared_ptr<const Telegram> telegram);
|
||||
void process_ISM1Set(std::shared_ptr<const Telegram> telegram);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
60
src/devices/switch.cpp
Normal file
60
src/devices/switch.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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 "switch.h"
|
||||
|
||||
// MAKE_PSTR_WORD(switch)
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
REGISTER_FACTORY(Switch, EMSdevice::DeviceType::SWITCH);
|
||||
|
||||
MAKE_PSTR(logger_name, "switch")
|
||||
uuid::log::Logger Switch::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
Switch::Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: EMSdevice(device_type, device_id, product_id, version, name, flags, brand) {
|
||||
// telegram handlers
|
||||
// register_telegram_type(EMS_TYPE_XX, "XX", false, std::bind(&Controller::process_XX, this, _1));
|
||||
|
||||
// MQTT callbacks
|
||||
// register_mqtt_topic("cmd", std::bind(&Controller::cmd, this, _1));
|
||||
}
|
||||
|
||||
void Switch::add_context_menu() {
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// publish values via MQTT
|
||||
void Switch::publish_values() {
|
||||
}
|
||||
|
||||
// check to see if values have been updated
|
||||
bool Switch::updated_values() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// add console commands
|
||||
void Switch::console_commands() {
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
51
src/devices/switch.h
Normal file
51
src/devices/switch.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_SWITCH_H
|
||||
#define EMSESP_SWITCH_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Switch : public EMSdevice {
|
||||
public:
|
||||
Switch(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
1888
src/devices/thermostat.cpp
Normal file
1888
src/devices/thermostat.cpp
Normal file
File diff suppressed because it is too large
Load Diff
272
src/devices/thermostat.h
Normal file
272
src/devices/thermostat.h
Normal file
@@ -0,0 +1,272 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_THERMOSTAT_H
|
||||
#define EMSESP_THERMOSTAT_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "telegram.h"
|
||||
#include "emsesp.h"
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Thermostat : public EMSdevice {
|
||||
public:
|
||||
Thermostat(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand);
|
||||
class HeatingCircuit {
|
||||
public:
|
||||
HeatingCircuit(const uint8_t hc_num, const uint16_t monitor_typeid, const uint16_t set_typeid)
|
||||
: hc_num_(hc_num)
|
||||
, monitor_typeid_(monitor_typeid)
|
||||
, set_typeid_(set_typeid) {
|
||||
}
|
||||
~HeatingCircuit() = default;
|
||||
|
||||
int16_t setpoint_roomTemp = EMS_VALUE_SHORT_NOTSET;
|
||||
int16_t curr_roomTemp = EMS_VALUE_SHORT_NOTSET;
|
||||
uint8_t mode = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t mode_type = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t summer_mode = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t holiday_mode = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t daytemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t nighttemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t holidaytemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t heatingtype = EMS_VALUE_UINT_NOTSET; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
|
||||
uint8_t targetflowtemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t summertemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t nofrosttemp = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t designtemp = EMS_VALUE_UINT_NOTSET; // heatingcurve design temp at MinExtTemp
|
||||
int8_t offsettemp = EMS_VALUE_INT_NOTSET; // heatingcurve offest temp at roomtemp signed!
|
||||
|
||||
uint8_t hc_num() const {
|
||||
return hc_num_; // 1..10
|
||||
}
|
||||
|
||||
uint8_t get_mode(uint8_t flags) const;
|
||||
uint8_t get_mode_type(uint8_t flags) const;
|
||||
|
||||
uint16_t monitor_typeid() const {
|
||||
return monitor_typeid_;
|
||||
}
|
||||
|
||||
uint16_t set_typeid() const {
|
||||
return set_typeid_;
|
||||
}
|
||||
|
||||
enum Mode : uint8_t { UNKNOWN, OFF, MANUAL, AUTO, DAY, NIGHT, HEAT, NOFROST, ECO, HOLIDAY, COMFORT, OFFSET, DESIGN, SUMMER };
|
||||
|
||||
// for sorting
|
||||
friend inline bool operator<(const std::shared_ptr<HeatingCircuit> & lhs, const std::shared_ptr<HeatingCircuit> & rhs) {
|
||||
return (lhs->hc_num_ < rhs->hc_num_);
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t hc_num_; // 1..10
|
||||
uint16_t monitor_typeid_;
|
||||
uint16_t set_typeid_;
|
||||
};
|
||||
|
||||
std::string mode_tostring(uint8_t mode) const;
|
||||
|
||||
virtual void show_values(uuid::console::Shell & shell);
|
||||
virtual void publish_values();
|
||||
virtual bool updated_values();
|
||||
virtual void add_context_menu();
|
||||
|
||||
bool can_write() const {
|
||||
return ((flags() & EMSdevice::EMS_DEVICE_FLAG_NO_WRITE) == EMSdevice::EMS_DEVICE_FLAG_NO_WRITE);
|
||||
}
|
||||
|
||||
// each thermostat has a list of heating controller type IDs for reading and writing
|
||||
std::vector<uint16_t> monitor_typeids;
|
||||
std::vector<uint16_t> set_typeids;
|
||||
std::vector<uint16_t> timer_typeids;
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
void console_commands(Shell & shell, unsigned int context);
|
||||
void init_mqtt();
|
||||
|
||||
std::string datetime_; // date and time stamp
|
||||
|
||||
uint8_t mqtt_format_; // single, nested or ha
|
||||
|
||||
// Installation parameters
|
||||
uint8_t ibaMainDisplay_ =
|
||||
EMS_VALUE_UINT_NOTSET; // 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
|
||||
uint8_t ibaLanguage_ = EMS_VALUE_UINT_NOTSET; // language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
|
||||
int8_t ibaCalIntTemperature_ = EMS_VALUE_INT_NOTSET; // offset int. temperature sensor, by * 0.1 Kelvin (-5.0 to 5.0K)
|
||||
int8_t ibaMinExtTemperature_ = EMS_VALUE_INT_NOTSET; // min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1
|
||||
uint8_t ibaBuildingType_ = EMS_VALUE_UINT_NOTSET; // building type: 0 = light, 1 = medium, 2 = heavy
|
||||
uint8_t ibaClockOffset_ = EMS_VALUE_UINT_NOTSET; // offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
|
||||
|
||||
int8_t dampedoutdoortemp_ = EMS_VALUE_INT_NOTSET;
|
||||
uint16_t tempsensor1_ = EMS_VALUE_USHORT_NOTSET;
|
||||
uint16_t tempsensor2_ = EMS_VALUE_USHORT_NOTSET;
|
||||
|
||||
uint8_t wwSystem_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t wwExtra_ = EMS_VALUE_UINT_NOTSET;
|
||||
uint8_t wwMode_ = EMS_VALUE_UINT_NOTSET;
|
||||
|
||||
std::vector<std::shared_ptr<HeatingCircuit>> heating_circuits_; // each thermostat can have multiple heating circuits
|
||||
|
||||
// Generic Types
|
||||
static constexpr uint16_t EMS_TYPE_RCTime = 0x06; // time
|
||||
static constexpr uint16_t EMS_TYPE_RCOutdoorTemp = 0xA3; // is an automatic thermostat broadcast, outdoor external temp
|
||||
|
||||
// Type offsets
|
||||
static constexpr uint8_t EMS_OFFSET_RC10StatusMessage_setpoint = 1; // setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC10StatusMessage_curr = 2; // current temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC10Set_temp = 4; // position of thermostat setpoint temperature
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_RC20StatusMessage_setpoint = 1; // setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC20StatusMessage_curr = 2; // current temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC20Set_mode = 23; // position of thermostat mode
|
||||
static constexpr uint8_t EMS_OFFSET_RC20Set_temp = 28; // position of thermostat setpoint temperature
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_mode = 3; // ES72 - see https://github.com/proddy/EMS-ESP/issues/334
|
||||
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_temp_night = 1; // ES72
|
||||
static constexpr uint8_t EMS_OFFSET_RC20_2_Set_temp_day = 2; // ES72
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_RC30StatusMessage_setpoint = 1; // setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC30StatusMessage_curr = 2; // current temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC30Set_mode = 23; // position of thermostat mode
|
||||
static constexpr uint8_t EMS_OFFSET_RC30Set_temp = 28; // position of thermostat setpoint temperature
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_RC35StatusMessage_setpoint = 2; // desired temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC35StatusMessage_curr = 3; // current temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC35StatusMessage_mode = 1; // day mode, also summer on RC3's
|
||||
static constexpr uint8_t EMS_OFFSET_RC35StatusMessage_mode1 = 0; // for holiday mode
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_mode = 7; // position of thermostat mode
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_day = 2; // position of thermostat setpoint temperature for day time
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_night = 1; // position of thermostat setpoint temperature for night time
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_holiday = 3; // temp during holiday mode
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_heatingtype = 0; // e.g. floor heating = 3
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_targetflowtemp = 14; // target flow temperature
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_seltemp = 37; // selected temp
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_offset = 6;
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_design = 17;
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_summer = 22;
|
||||
static constexpr uint8_t EMS_OFFSET_RC35Set_temp_nofrost = 23;
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_EasyStatusMessage_setpoint = 10; // setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_EasyStatusMessage_curr = 8; // current temp
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSStatusMessage_mode = 10; // thermostat mode (auto, manual)
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSStatusMessage_setpoint = 3; // setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSStatusMessage_curr = 0; // current temp
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSStatusMessage_currsetpoint = 6; // target setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_mode = 0; // operation mode(Auto=0xFF, Manual=0x00)
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_temp_comfort3 = 1; // comfort3 level
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_temp_comfort2 = 2; // comfort2 level
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_temp_comfort1 = 3; // comfort1 level
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_temp_eco = 4; // eco level
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_temp_setpoint = 8; // temp setpoint, when changing of templevel (in auto) value is reset to FF
|
||||
static constexpr uint8_t EMS_OFFSET_RCPLUSSet_manual_setpoint = 10; // manual setpoint
|
||||
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersStatusMessage_daymode = 0; // 3 = day, 2 = night, 1 = nofrost
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersStatusMessage_mode = 1; // current mode, 1 = manual, 2 = auto
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersStatusMessage_setpoint = 2; // setpoint temp
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersStatusMessage_curr = 4; // current temp
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage_day_temp = 17; // EMS offset to set temperature on thermostat for day mode
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage_night_temp = 16; // EMS offset to set temperature on thermostat for night mode
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage_no_frost_temp = 15; // EMS offset to set temperature on thermostat for no frost mode
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage_set_mode = 14; // EMS offset to set mode on thermostat
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_set_mode = 4; // EMS offset to set mode on thermostat
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_no_frost_temp = 5;
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage2_eco_temp = 6;
|
||||
static constexpr uint8_t EMS_OFFSET_JunkersSetMessage3_heat = 7;
|
||||
|
||||
#define AUTO_HEATING_CIRCUIT 0
|
||||
#define DEFAULT_HEATING_CIRCUIT 1
|
||||
|
||||
// Installation settings
|
||||
static constexpr uint8_t EMS_TYPE_IBASettings = 0xA5; // installation settings
|
||||
static constexpr uint8_t EMS_TYPE_wwSettings = 0x37; // ww settings
|
||||
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(std::shared_ptr<const Telegram> telegram);
|
||||
std::shared_ptr<Thermostat::HeatingCircuit> heating_circuit(const uint8_t hc_num);
|
||||
|
||||
void process_RCOutdoorTemp(std::shared_ptr<const Telegram> telegram);
|
||||
void process_IBASettings(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RCTime(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC35wwSettings(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC35Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC35Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC30Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC30Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC20Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC20Set(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC20Remote(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC20Monitor_2(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC20Set_2(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC10Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC10Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC300Monitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_RC300Set(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_JunkersMonitor(std::shared_ptr<const Telegram> telegram);
|
||||
void process_JunkersSet(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_EasyMonitor(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void process_RC300WWmode(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
// set functions
|
||||
void set_settings_minexttemp(const int8_t mt);
|
||||
void set_settings_calinttemp(const int8_t ct);
|
||||
void set_settings_clockoffset(const int8_t co);
|
||||
void set_settings_display(const uint8_t ds);
|
||||
void set_settings_building(const uint8_t bg);
|
||||
void set_settings_language(const uint8_t lg);
|
||||
void set_control(const uint8_t ctrl, const uint8_t hc_num);
|
||||
void set_ww_mode(const std::string & mode);
|
||||
void set_holiday(const char * hd, const uint8_t hc_num);
|
||||
void set_datetime(const char * dt);
|
||||
void set_pause(const uint8_t hrs, const uint8_t hc_num);
|
||||
void set_party(const uint8_t hrs, const uint8_t hc_num);
|
||||
void set_mode(const uint8_t mode, const uint8_t hc_num);
|
||||
void set_mode(const std::string & mode, const uint8_t hc_num);
|
||||
void set_temperature(const float temperature, const std::string & mode, const uint8_t hc_num);
|
||||
void set_temperature(const float temperature, const uint8_t mode, const uint8_t hc_num);
|
||||
|
||||
// MQTT functions
|
||||
void thermostat_cmd(const char * message);
|
||||
void thermostat_cmd_temp(const char * message);
|
||||
void thermostat_cmd_mode(const char * message);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
228
src/ds18.cpp
228
src/ds18.cpp
@@ -1,228 +0,0 @@
|
||||
/*
|
||||
* Dallas support for external settings
|
||||
* Copied from Espurna - Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ds18.h"
|
||||
|
||||
std::vector<ds_device_t> _devices;
|
||||
|
||||
DS18::DS18() {
|
||||
_wire = nullptr;
|
||||
_count = 0;
|
||||
_gpio = GPIO_NONE;
|
||||
_parasite = 0;
|
||||
}
|
||||
|
||||
DS18::~DS18() {
|
||||
if (_wire) {
|
||||
delete _wire;
|
||||
}
|
||||
}
|
||||
|
||||
// init
|
||||
void DS18::setup(uint8_t gpio, bool parasite) {
|
||||
_gpio = gpio;
|
||||
_parasite = (parasite ? 1 : 0);
|
||||
|
||||
// OneWire
|
||||
if (_wire) {
|
||||
delete _wire;
|
||||
}
|
||||
_wire = new OneWire(_gpio);
|
||||
}
|
||||
|
||||
// clear list and scan for devices
|
||||
uint8_t DS18::scan() {
|
||||
_devices.clear();
|
||||
|
||||
uint8_t count = loadDevices(); // start the search
|
||||
|
||||
// If no devices found check again pulling up the line
|
||||
if (count == 0) {
|
||||
pinMode(_gpio, INPUT_PULLUP);
|
||||
count = loadDevices();
|
||||
}
|
||||
|
||||
_count = count;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void DS18::loop() {
|
||||
// we either start a conversion or read the scratchpad
|
||||
static bool conversion = true;
|
||||
if (conversion) {
|
||||
_wire->reset();
|
||||
_wire->skip();
|
||||
_wire->write(DS18_CMD_START_CONVERSION, _parasite);
|
||||
} else {
|
||||
// Read scratchpads
|
||||
for (unsigned char index = 0; index < _devices.size(); index++) {
|
||||
if (_wire->reset() == 0) {
|
||||
_devices[index].data[0] = _devices[index].data[0] + 1; // Force a CRC check error
|
||||
return;
|
||||
}
|
||||
|
||||
// Read each scratchpad
|
||||
_wire->select(_devices[index].address);
|
||||
_wire->write(DS18_CMD_READ_SCRATCHPAD);
|
||||
|
||||
uint8_t data[DS18_DATA_SIZE];
|
||||
for (unsigned char i = 0; i < DS18_DATA_SIZE; i++) {
|
||||
data[i] = _wire->read();
|
||||
}
|
||||
|
||||
if (_wire->reset() != 1) {
|
||||
_devices[index].data[0] = _devices[index].data[0] + 1; // Force a CRC check error
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(_devices[index].data, data, DS18_DATA_SIZE);
|
||||
}
|
||||
}
|
||||
|
||||
conversion = !conversion;
|
||||
}
|
||||
|
||||
// return string of the device, with name and address
|
||||
char * DS18::getDeviceType(char * buffer, unsigned char index) {
|
||||
uint8_t size = 128;
|
||||
if (index < _count) {
|
||||
unsigned char chip_id = chip(index);
|
||||
if (chip_id == DS18_CHIP_DS18S20) {
|
||||
strlcpy(buffer, "DS18S20", size);
|
||||
} else if (chip_id == DS18_CHIP_DS18B20) {
|
||||
strlcpy(buffer, "DS18B20", size);
|
||||
} else if (chip_id == DS18_CHIP_DS1822) {
|
||||
strlcpy(buffer, "DS1822", size);
|
||||
} else if (chip_id == DS18_CHIP_DS1825) {
|
||||
strlcpy(buffer, "DS1825", size);
|
||||
} else {
|
||||
strlcpy(buffer, "Unknown", size);
|
||||
}
|
||||
} else {
|
||||
strlcpy(buffer, "invalid", size);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// return string of the device, with name and address
|
||||
char * DS18::getDeviceID(char * buffer, unsigned char index) {
|
||||
uint8_t size = 128;
|
||||
if (index < _count) {
|
||||
uint8_t * address = _devices[index].address;
|
||||
char a[30] = {0};
|
||||
snprintf(a, sizeof(a), "%02X%02X%02X%02X%02X%02X%02X%02X", address[0], address[1], address[2], address[3], address[4], address[5], address[6], address[7]);
|
||||
|
||||
strlcpy(buffer, a, size);
|
||||
} else {
|
||||
strlcpy(buffer, "invalid", size);
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read sensor values
|
||||
*
|
||||
* Registers:
|
||||
byte 0: temperature LSB
|
||||
byte 1: temperature MSB
|
||||
byte 2: high alarm temp
|
||||
byte 3: low alarm temp
|
||||
byte 4: DS18S20: store for crc
|
||||
DS18B20 & DS1822: configuration register
|
||||
byte 5: internal use & crc
|
||||
byte 6: DS18S20: COUNT_REMAIN
|
||||
DS18B20 & DS1822: store for crc
|
||||
byte 7: DS18S20: COUNT_PER_C
|
||||
DS18B20 & DS1822: store for crc
|
||||
byte 8: SCRATCHPAD_CRC
|
||||
*/
|
||||
int16_t DS18::getRawValue(unsigned char index) {
|
||||
if (index >= _count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint8_t * data = _devices[index].data;
|
||||
|
||||
if (OneWire::crc8(data, DS18_DATA_SIZE - 1) != data[DS18_DATA_SIZE - 1]) {
|
||||
return DS18_CRC_ERROR;
|
||||
}
|
||||
|
||||
int16_t raw = (data[1] << 8) | data[0];
|
||||
if (chip(index) == DS18_CHIP_DS18S20) {
|
||||
raw = raw << 3; // 9 bit resolution default
|
||||
if (data[7] == 0x10) {
|
||||
raw = (raw & 0xFFF0) + 12 - data[6]; // "count remain" gives full 12 bit resolution
|
||||
}
|
||||
} else {
|
||||
byte cfg = (data[4] & 0x60);
|
||||
if (cfg == 0x00) {
|
||||
raw = raw & ~7; // 9 bit res, 93.75 ms
|
||||
} else if (cfg == 0x20) {
|
||||
raw = raw & ~3; // 10 bit res, 187.5 ms
|
||||
} else if (cfg == 0x40) {
|
||||
raw = raw & ~1; // 11 bit res, 375 ms
|
||||
// 12 bit res, 750 ms
|
||||
}
|
||||
}
|
||||
|
||||
return raw;
|
||||
}
|
||||
|
||||
// return real value as a float, rounded to 2 decimal places
|
||||
float DS18::getValue(unsigned char index) {
|
||||
int16_t raw_value = getRawValue(index);
|
||||
|
||||
// check if valid
|
||||
if ((raw_value == DS18_CRC_ERROR) || (raw_value == DS18_DISCONNECTED)) {
|
||||
return (float)DS18_DISCONNECTED;
|
||||
}
|
||||
|
||||
// The raw temperature data is in units of sixteenths of a degree,
|
||||
// so the value must first be divided by 16 in order to convert it to degrees.
|
||||
float new_value = (float)(raw_value / 16.0);
|
||||
|
||||
// round to 2 decimal places
|
||||
// https://arduinojson.org/v6/faq/how-to-configure-the-serialization-of-floats/
|
||||
return (int)(new_value * 100 + 0.5) / 100.0;
|
||||
}
|
||||
|
||||
// check for a supported DS chip version
|
||||
bool DS18::validateID(unsigned char id) {
|
||||
return (id == DS18_CHIP_DS18S20) || (id == DS18_CHIP_DS18B20) || (id == DS18_CHIP_DS1822) || (id == DS18_CHIP_DS1825);
|
||||
}
|
||||
|
||||
// return the type
|
||||
unsigned char DS18::chip(unsigned char index) {
|
||||
if (index < _count) {
|
||||
return _devices[index].address[0];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// scan for DS sensors and load into the vector
|
||||
uint8_t DS18::loadDevices() {
|
||||
uint8_t address[8];
|
||||
_wire->reset();
|
||||
_wire->reset_search();
|
||||
|
||||
while (_wire->search(address)) {
|
||||
// Check CRC
|
||||
if (_wire->crc8(address, 7) == address[7]) {
|
||||
// Check ID
|
||||
if (validateID(address[0])) {
|
||||
ds_device_t device;
|
||||
memcpy(device.address, address, 8);
|
||||
_devices.push_back(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
return (_devices.size());
|
||||
}
|
||||
56
src/ds18.h
56
src/ds18.h
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Dallas support for external temperature sensors
|
||||
* Copyright (C) 2017-2018 by Xose Pérez <xose dot perez at gmail dot com>
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <OneWire.h>
|
||||
#include <vector>
|
||||
|
||||
#define DS18_CHIP_DS18S20 0x10
|
||||
#define DS18_CHIP_DS1822 0x22
|
||||
#define DS18_CHIP_DS18B20 0x28
|
||||
#define DS18_CHIP_DS1825 0x3B
|
||||
|
||||
#define DS18_DATA_SIZE 9
|
||||
#define DS18_DISCONNECTED -127
|
||||
#define DS18_CRC_ERROR -126
|
||||
|
||||
#define GPIO_NONE 0x99
|
||||
#define DS18_READ_INTERVAL 2000 // Force sensor read & cache every 2 seconds
|
||||
|
||||
#define DS18_CMD_START_CONVERSION 0x44
|
||||
#define DS18_CMD_READ_SCRATCHPAD 0xBE
|
||||
|
||||
typedef struct {
|
||||
uint8_t address[8];
|
||||
uint8_t data[DS18_DATA_SIZE];
|
||||
} ds_device_t;
|
||||
|
||||
class DS18 {
|
||||
public:
|
||||
DS18();
|
||||
~DS18();
|
||||
|
||||
void setup(uint8_t gpio, bool parasite);
|
||||
uint8_t scan();
|
||||
void loop();
|
||||
char * getDeviceType(char * s, unsigned char index);
|
||||
char * getDeviceID(char * buffer, unsigned char index);
|
||||
float getValue(unsigned char index);
|
||||
int16_t getRawValue(unsigned char index); // raw values, needs / 16
|
||||
|
||||
protected:
|
||||
bool validateID(unsigned char id);
|
||||
unsigned char chip(unsigned char index);
|
||||
uint8_t loadDevices();
|
||||
|
||||
OneWire * _wire;
|
||||
uint8_t _count; // # devices
|
||||
uint8_t _gpio; // the sensor pin
|
||||
uint8_t _parasite; // parasite mode
|
||||
};
|
||||
2691
src/ems-esp.cpp
2691
src/ems-esp.cpp
File diff suppressed because it is too large
Load Diff
3673
src/ems.cpp
3673
src/ems.cpp
File diff suppressed because it is too large
Load Diff
574
src/ems.h
574
src/ems.h
@@ -1,574 +0,0 @@
|
||||
/*
|
||||
* Header file for ems.cpp
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for history
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <list> // std::list
|
||||
|
||||
// EMS bus IDs
|
||||
#define EMS_BUSID_DEFAULT 0x0B // Default 0x0B (Service Key)
|
||||
|
||||
// EMS tx_mode types
|
||||
#define EMS_TXMODE_DEFAULT 1 // Default (was previously known as tx_mode 2 in v1.8.x)
|
||||
#define EMS_TXMODE_EMSPLUS 2 // EMS+
|
||||
#define EMS_TXMODE_HT3 3 // Junkers HT3
|
||||
|
||||
#define EMS_ID_NONE 0x00 // used as a dest in broadcast messages and empty device IDs
|
||||
|
||||
#define EMS_MIN_TELEGRAM_LENGTH 6 // minimal length for a validation telegram, including CRC
|
||||
#define EMS_MAX_TELEGRAM_LENGTH 32 // max length of a telegram, including CRC, for Rx and Tx.
|
||||
|
||||
// default values for null values
|
||||
#define EMS_VALUE_BOOL_ON 0x01 // boolean true
|
||||
#define EMS_VALUE_BOOL_ON2 0xFF // boolean true, EMS sometimes uses 0xFF for TRUE
|
||||
#define EMS_VALUE_BOOL_OFF 0x00 // boolean false
|
||||
#define EMS_VALUE_BOOL_NOTSET 0xFE // random number that's not 0, 1 or FF
|
||||
#define EMS_VALUE_INT_NOTSET 0xFF // for 8-bit unsigned ints/bytes
|
||||
#define EMS_VALUE_SHORT_NOTSET -32000 // 0x8300 for 2-byte signed shorts
|
||||
#define EMS_VALUE_USHORT_NOTSET 32000 // 0x7D00 (was 0x8000) for 2-byte unsigned shorts
|
||||
#define EMS_VALUE_USHORT_NOTVALID 0x8000 // 0x8000 for 2-byte unsigned shorts
|
||||
#define EMS_VALUE_LONG_NOTSET 0xFFFFFF // for 3-byte longs
|
||||
|
||||
// thermostat specific
|
||||
#define EMS_THERMOSTAT_MAXHC 4 // max number of heating circuits
|
||||
#define EMS_THERMOSTAT_DEFAULTHC 1 // default heating circuit is 1
|
||||
#define EMS_THERMOSTAT_WRITE_YES true
|
||||
#define EMS_THERMOSTAT_WRITE_NO false
|
||||
|
||||
// mixing specific
|
||||
#define EMS_MIXING_MAXHC 4 // max number of heating circuits
|
||||
#define EMS_MIXING_MAXWWC 2 // max number of warm water circuits
|
||||
|
||||
// Device Flags
|
||||
// They are unique to the model type (mixing, solar, thermostat etc)
|
||||
enum EMS_DEVICE_FLAG_TYPES : uint8_t {
|
||||
EMS_DEVICE_FLAG_NONE = 0,
|
||||
EMS_DEVICE_FLAG_MMPLUS = 20, // mixing EMS+
|
||||
EMS_DEVICE_FLAG_MM10 = 21, // mixing MM10
|
||||
EMS_DEVICE_FLAG_SM10 = 10,
|
||||
EMS_DEVICE_FLAG_SM100 = 11, // for SM100 and SM200
|
||||
EMS_DEVICE_FLAG_EASY = 1,
|
||||
EMS_DEVICE_FLAG_RC10 = 2,
|
||||
EMS_DEVICE_FLAG_RC20 = 3,
|
||||
EMS_DEVICE_FLAG_RC30 = 4,
|
||||
EMS_DEVICE_FLAG_RC30N = 5, // newer type of RC30 with RC35 circuit
|
||||
EMS_DEVICE_FLAG_RC35 = 6,
|
||||
EMS_DEVICE_FLAG_RC100 = 7,
|
||||
EMS_DEVICE_FLAG_RC300 = 8,
|
||||
EMS_DEVICE_FLAG_RC20N = 9,
|
||||
EMS_DEVICE_FLAG_JUNKERS1 = 31, // use 0x65 for HC
|
||||
EMS_DEVICE_FLAG_JUNKERS2 = 32, // use 0x79 for HC, older models
|
||||
EMS_DEVICE_FLAG_JUNKERS = (1 << 6), // 6th bit set if its junkers HT3
|
||||
EMS_DEVICE_FLAG_NO_WRITE = (1 << 7) // top bit set if thermostat write not supported
|
||||
};
|
||||
|
||||
// trigger settings to determine if hot tap water or the heating is active
|
||||
#define EMS_BOILER_BURNPOWER_TAPWATER 100
|
||||
#define EMS_BOILER_SELFLOWTEMP_HEATING 20 // was originally 70, changed to 30 for issue #193, then to 20 with issue #344
|
||||
|
||||
// define min & maximum setable tapwater temperatures
|
||||
#define EMS_BOILER_TAPWATER_TEMPERATURE_MAX 60
|
||||
#define EMS_BOILER_TAPWATER_TEMPERATURE_MIN 30
|
||||
|
||||
#define EMS_TX_TELEGRAM_QUEUE_MAX 50 // max size of Tx FIFO queue. Number of Tx records to send.
|
||||
|
||||
//#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE // turn on for debugging
|
||||
#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE
|
||||
|
||||
#define EMS_SYS_DEVICEMAP_LENGTH 15 // size of the 0x07 telegram data part which stores all active EMS devices
|
||||
|
||||
#define EMS_MODELTYPE_UNKNOWN_STRING "unknown?" // model type text to use when discovering an unknown device
|
||||
|
||||
/* EMS UART transfer status */
|
||||
typedef enum {
|
||||
EMS_RX_STATUS_IDLE,
|
||||
EMS_RX_STATUS_BUSY // Rx package is being received
|
||||
} _EMS_RX_STATUS;
|
||||
|
||||
typedef enum {
|
||||
EMS_TX_STATUS_OK,
|
||||
EMS_TX_STATUS_IDLE, // ready
|
||||
EMS_TX_STATUS_WAIT, // waiting for response from last Tx
|
||||
EMS_TX_WTD_TIMEOUT, // watchdog timeout during send
|
||||
EMS_TX_BRK_DETECT, // incoming BRK during Tx
|
||||
EMS_TX_REV_DETECT // waiting to detect reverse bit
|
||||
} _EMS_TX_STATUS;
|
||||
|
||||
#define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success
|
||||
#define EMS_TX_ERROR 0x04 // EMS single byte after a Tx Write indicating an error
|
||||
|
||||
typedef enum {
|
||||
EMS_TX_TELEGRAM_INIT, // just initialized
|
||||
EMS_TX_TELEGRAM_READ, // doing a read request
|
||||
EMS_TX_TELEGRAM_WRITE, // doing a write request
|
||||
EMS_TX_TELEGRAM_VALIDATE, // do a read but only to validate the last write
|
||||
EMS_TX_TELEGRAM_RAW // sending in raw mode
|
||||
} _EMS_TX_TELEGRAM_ACTION;
|
||||
|
||||
/* EMS logging */
|
||||
typedef enum {
|
||||
EMS_SYS_LOGGING_NONE, // no messages
|
||||
EMS_SYS_LOGGING_RAW, // raw data mode
|
||||
EMS_SYS_LOGGING_WATCH, // watch a specific type ID
|
||||
EMS_SYS_LOGGING_BASIC, // only basic read/write messages
|
||||
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
|
||||
EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from solar module
|
||||
EMS_SYS_LOGGING_MIXINGMODULE, // only telegrams sent from mixing module
|
||||
EMS_SYS_LOGGING_VERBOSE, // everything
|
||||
EMS_SYS_LOGGING_JABBER, // lots of debug output...
|
||||
EMS_SYS_LOGGING_DEVICE // watch the device ID
|
||||
} _EMS_SYS_LOGGING;
|
||||
|
||||
// status/counters since last power on
|
||||
typedef struct {
|
||||
_EMS_RX_STATUS emsRxStatus;
|
||||
_EMS_TX_STATUS emsTxStatus;
|
||||
uint16_t emsRxPgks; // # successfull received
|
||||
uint16_t emsTxPkgs; // # successfull sent
|
||||
uint16_t emxCrcErr; // CRC errors
|
||||
bool emsPollEnabled; // flag enable the response to poll messages
|
||||
_EMS_SYS_LOGGING emsLogging; // logging
|
||||
uint16_t emsLogging_ID; // the type or device ID to watch
|
||||
uint8_t emsRefreshedFlags; // fresh data, needs to be pushed out to MQTT
|
||||
bool emsBusConnected; // is there an active bus
|
||||
uint32_t emsRxTimestamp; // timestamp of last EMS message received
|
||||
uint32_t emsPollFrequency; // time between EMS polls
|
||||
bool emsTxCapable; // able to send via Tx
|
||||
bool emsTxDisabled; // true to prevent all Tx
|
||||
uint8_t txRetryCount; // # times the last Tx was re-sent
|
||||
uint8_t emsIDMask; // Buderus: 0x00, Junkers: 0x80
|
||||
uint8_t emsPollAck[1]; // acknowledge buffer for Poll
|
||||
uint8_t emsTxMode; // Tx mode 1, 2 or 3
|
||||
uint8_t emsbusid; // EMS bus ID, default 0x0B for Service Key
|
||||
uint8_t emsMasterThermostat; // product ID for the default thermostat to use
|
||||
} _EMS_Sys_Status;
|
||||
|
||||
// The Rx receive package
|
||||
typedef struct {
|
||||
unsigned long timestamp; // timestamp from millis()
|
||||
uint8_t * telegram; // the full data package
|
||||
uint8_t data_length; // length in bytes of the data
|
||||
uint8_t length; // full length of the complete telegram
|
||||
uint8_t src; // source ID
|
||||
uint8_t dest; // destination ID
|
||||
uint16_t type; // type ID as a 2-byte to support EMS+
|
||||
uint8_t offset; // offset
|
||||
uint8_t * data; // pointer to where telegram data starts
|
||||
bool emsplus; // true if ems+/ems 2.0
|
||||
uint8_t emsplus_type; // FF, F7 or F9
|
||||
} _EMS_RxTelegram;
|
||||
|
||||
// The Tx send package
|
||||
typedef struct {
|
||||
_EMS_TX_TELEGRAM_ACTION action; // read, write, validate, init
|
||||
uint8_t dest;
|
||||
uint16_t type;
|
||||
uint8_t offset;
|
||||
uint8_t length; // full length of complete telegram, including CRC
|
||||
bool emsplus; // true if ems+/ems 2.0
|
||||
uint8_t dataValue; // value to validate against
|
||||
uint16_t type_validate; // type to call after a successful Write command
|
||||
uint8_t comparisonValue; // value to compare against during a validate command
|
||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation
|
||||
uint16_t comparisonPostRead; // after a successful write, do a read from this type ID
|
||||
unsigned long timestamp; // when created
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
} _EMS_TxTelegram;
|
||||
|
||||
// default empty Tx, must match struct
|
||||
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||
EMS_TX_TELEGRAM_INIT, // action
|
||||
EMS_ID_NONE, // dest
|
||||
EMS_ID_NONE, // type
|
||||
0, // offset
|
||||
0, // length
|
||||
false, // emsplus (no)
|
||||
0, // dataValue
|
||||
EMS_ID_NONE, // type_validate
|
||||
0, // comparisonValue
|
||||
0, // comparisonOffset
|
||||
EMS_ID_NONE, // comparisonPostRead
|
||||
0, // timestamp
|
||||
{0x00} // data
|
||||
};
|
||||
|
||||
// flags for triggering changes when EMS data is received
|
||||
typedef enum : uint8_t {
|
||||
EMS_DEVICE_UPDATE_FLAG_NONE = 0,
|
||||
EMS_DEVICE_UPDATE_FLAG_BOILER = (1 << 0),
|
||||
EMS_DEVICE_UPDATE_FLAG_THERMOSTAT = (1 << 1),
|
||||
EMS_DEVICE_UPDATE_FLAG_MIXING = (1 << 2),
|
||||
EMS_DEVICE_UPDATE_FLAG_SOLAR = (1 << 3),
|
||||
EMS_DEVICE_UPDATE_FLAG_HEATPUMP = (1 << 4),
|
||||
EMS_DEVICE_UPDATE_FLAG_SETTINGS = (1 << 5)
|
||||
} _EMS_DEVICE_UPDATE_FLAG;
|
||||
|
||||
typedef enum : uint8_t {
|
||||
EMS_DEVICE_TYPE_NONE = 0,
|
||||
EMS_DEVICE_TYPE_SERVICEKEY,
|
||||
EMS_DEVICE_TYPE_BOILER,
|
||||
EMS_DEVICE_TYPE_THERMOSTAT,
|
||||
EMS_DEVICE_TYPE_MIXING,
|
||||
EMS_DEVICE_TYPE_SOLAR,
|
||||
EMS_DEVICE_TYPE_HEATPUMP,
|
||||
EMS_DEVICE_TYPE_GATEWAY,
|
||||
EMS_DEVICE_TYPE_SWITCH,
|
||||
EMS_DEVICE_TYPE_CONTROLLER,
|
||||
EMS_DEVICE_TYPE_CONNECT,
|
||||
EMS_DEVICE_TYPE_UNKNOWN
|
||||
} _EMS_DEVICE_TYPE;
|
||||
|
||||
// to store mapping of device_ids to their string name
|
||||
typedef struct {
|
||||
_EMS_DEVICE_TYPE device_type;
|
||||
char device_type_string[30];
|
||||
} _EMS_Device_Types;
|
||||
|
||||
// mapping for EMS_Devices_Type
|
||||
const _EMS_Device_Types EMS_Devices_Types[] = {
|
||||
|
||||
{EMS_DEVICE_TYPE_UNKNOWN, EMS_MODELTYPE_UNKNOWN_STRING}, // the first, at index 0 is reserved for "unknown"
|
||||
{EMS_DEVICE_TYPE_NONE, "All"},
|
||||
{EMS_DEVICE_TYPE_SERVICEKEY, "Me"},
|
||||
{EMS_DEVICE_TYPE_BOILER, "Boiler"},
|
||||
{EMS_DEVICE_TYPE_THERMOSTAT, "Thermostat"},
|
||||
{EMS_DEVICE_TYPE_MIXING, "Mixing Module"},
|
||||
{EMS_DEVICE_TYPE_SOLAR, "Solar Module"},
|
||||
{EMS_DEVICE_TYPE_HEATPUMP, "Heat Pump"},
|
||||
{EMS_DEVICE_TYPE_GATEWAY, "Gateway"},
|
||||
{EMS_DEVICE_TYPE_SWITCH, "Switching Module"},
|
||||
{EMS_DEVICE_TYPE_CONTROLLER, "Controller"},
|
||||
{EMS_DEVICE_TYPE_CONNECT, "Connect Module"}
|
||||
|
||||
};
|
||||
|
||||
// to store all known EMS devices to date
|
||||
typedef struct {
|
||||
uint8_t product_id;
|
||||
_EMS_DEVICE_TYPE type;
|
||||
char device_desc[100];
|
||||
uint8_t flags;
|
||||
} _EMS_Device;
|
||||
|
||||
// for storing all recognised EMS devices
|
||||
typedef struct {
|
||||
_EMS_DEVICE_TYPE device_type; // type (see above)
|
||||
uint8_t product_id; // product id
|
||||
uint8_t device_id; // device_id
|
||||
const char * device_desc_p; // pointer to description string in EMS_Devices table
|
||||
char version[10]; // the version number XX.XX
|
||||
bool known; // is this a known device?
|
||||
} _Detected_Device;
|
||||
|
||||
/*
|
||||
* Telegram package defintions
|
||||
*/
|
||||
typedef struct {
|
||||
// settings
|
||||
uint8_t device_id; // this is typically always 0x08
|
||||
uint8_t device_flags;
|
||||
const char * device_desc_p;
|
||||
uint8_t product_id;
|
||||
char version[10];
|
||||
|
||||
uint8_t brand; // 0=unknown, 1=bosch, 2=junkers, 3=buderus, 4=nefit, 5=sieger, 11=worcester
|
||||
|
||||
// UBAParameterWW
|
||||
uint8_t wWActivated; // Warm Water activated
|
||||
uint8_t wWSelTemp; // Warm Water selected temperature
|
||||
uint8_t wWCircPump; // Warm Water circulation pump Available
|
||||
uint8_t wWCircPumpMode; // Warm Water circulation pump mode (1 = 1x3min, ..., 6=6x3min, 7 continuous)
|
||||
uint8_t wWCircPumpType; // Warm Water circulation pump type (0 = charge pump, 0xff = 3-way pump)
|
||||
uint8_t wWDesinfectTemp; // Warm Water desinfection temperature
|
||||
uint8_t wWComfort; // Warm water comfort or ECO mode
|
||||
|
||||
// UBAMonitorFast
|
||||
uint8_t selFlowTemp; // Selected flow temperature
|
||||
uint16_t curFlowTemp; // Current flow temperature
|
||||
uint16_t wwStorageTemp1; // warm water storage temp 1
|
||||
uint16_t wwStorageTemp2; // warm water storage temp 2
|
||||
uint16_t retTemp; // Return temperature
|
||||
uint8_t burnGas; // Gas on/off
|
||||
uint8_t wWMode; // warm water mode
|
||||
uint8_t fanWork; // Fan on/off
|
||||
uint8_t ignWork; // Ignition on/off
|
||||
uint8_t heatPmp; // Circulating pump on/off
|
||||
uint8_t wWHeat; // 3-way valve on WW
|
||||
uint8_t wWCirc; // Circulation on/off
|
||||
uint8_t selBurnPow; // Burner max power
|
||||
uint8_t curBurnPow; // Burner current power
|
||||
uint16_t flameCurr; // Flame current in micro amps
|
||||
uint8_t sysPress; // System pressure
|
||||
char serviceCodeChar[3]; // 2 character status/service code
|
||||
uint16_t serviceCode; // error/service code
|
||||
|
||||
// UBAMonitorSlow
|
||||
int16_t extTemp; // Outside temperature
|
||||
uint16_t boilTemp; // Boiler temperature
|
||||
uint16_t exhaustTemp; // Exhaust temperature
|
||||
uint8_t pumpMod; // Pump modulation
|
||||
uint32_t burnStarts; // # burner starts
|
||||
uint32_t burnWorkMin; // Total burner operating time
|
||||
uint32_t heatWorkMin; // Total heat operating time
|
||||
uint16_t switchTemp; // Switch temperature
|
||||
|
||||
// UBAMonitorWWMessage
|
||||
uint8_t wWSetTmp; // set temp WW (DHW)
|
||||
uint16_t wWCurTmp; // Warm Water current temperature
|
||||
uint16_t wWCurTmp2; // Warm Water current temperature storage
|
||||
uint32_t wWStarts; // Warm Water # starts
|
||||
uint32_t wWWorkM; // Warm Water # minutes
|
||||
uint8_t wWOneTime; // Warm Water one time function on/off
|
||||
uint8_t wWDesinfecting; // Warm Water desinfection on/off
|
||||
uint8_t wWReadiness; // Warm Water readiness on/off
|
||||
uint8_t wWRecharging; // Warm Water recharge on/off
|
||||
uint8_t wWTemperatureOK; // Warm Water temperature ok on/off
|
||||
uint8_t wWCurFlow; // Warm Water current flow in l/min
|
||||
|
||||
// UBATotalUptimeMessage
|
||||
uint32_t UBAuptime; // Total UBA working hours
|
||||
|
||||
// UBAParametersMessage
|
||||
uint8_t heating_temp; // Heating temperature setting on the boiler
|
||||
uint8_t pump_mod_max; // Boiler circuit pump modulation max. power
|
||||
uint8_t pump_mod_min; // Boiler circuit pump modulation min. power
|
||||
|
||||
// calculated values
|
||||
uint8_t tapwaterActive; // Hot tap water is on/off
|
||||
uint8_t heatingActive; // Central heating is on/off
|
||||
} _EMS_Boiler;
|
||||
|
||||
/*
|
||||
* Telegram package defintions for Other EMS devices
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t device_id; // the device ID of the Heat Pump (e.g. 0x30)
|
||||
uint8_t device_flags;
|
||||
const char * device_desc_p;
|
||||
uint8_t product_id;
|
||||
char version[10];
|
||||
uint8_t HPModulation; // heatpump modulation in %
|
||||
uint8_t HPSpeed; // speed 0-100 %
|
||||
} _EMS_HeatPump;
|
||||
|
||||
// Mixing Module per HC
|
||||
typedef struct {
|
||||
uint8_t hc; // heating circuit 1, 2, 3 or 4
|
||||
bool active; // true if there is data for this HC
|
||||
uint16_t flowTemp;
|
||||
uint8_t pumpMod;
|
||||
uint8_t valveStatus;
|
||||
uint8_t flowSetTemp;
|
||||
} _EMS_MixingModule_HC;
|
||||
|
||||
// Mixing Module per WWC
|
||||
typedef struct {
|
||||
uint8_t wwc; // warm water circuit 1, 2
|
||||
bool active; // true if there is data for this WWC
|
||||
uint16_t flowTemp;
|
||||
uint8_t pumpMod;
|
||||
uint8_t tempStatus;
|
||||
} _EMS_MixingModule_WWC;
|
||||
|
||||
// Mixing data
|
||||
typedef struct {
|
||||
uint8_t device_id;
|
||||
uint8_t device_flags;
|
||||
const char * device_desc_p;
|
||||
uint8_t product_id;
|
||||
char version[10];
|
||||
_EMS_MixingModule_HC hc[EMS_MIXING_MAXHC]; // array for the 4 heating circuits
|
||||
_EMS_MixingModule_WWC wwc[EMS_MIXING_MAXWWC]; // array for the 2 ww circuits
|
||||
} _EMS_MixingModule;
|
||||
|
||||
// Solar Module - SM10/SM100/SM200/ISM1
|
||||
typedef struct {
|
||||
uint8_t device_id; // the device ID of the Solar Module
|
||||
uint8_t device_flags; // Solar Module flags
|
||||
const char * device_desc_p;
|
||||
uint8_t product_id;
|
||||
char version[10];
|
||||
int16_t collectorTemp; // collector temp (TS1)
|
||||
int16_t bottomTemp; // bottom temp (TS2)
|
||||
int16_t bottomTemp2; // bottom temp cylinder 2 (TS5)
|
||||
uint8_t pumpModulation; // modulation solar pump
|
||||
uint8_t pump; // pump active
|
||||
uint8_t valveStatus; // valve status (VS2)
|
||||
int16_t setpoint_maxBottomTemp; // setpoint for maximum collector temp
|
||||
uint32_t EnergyLastHour;
|
||||
uint32_t EnergyToday;
|
||||
uint32_t EnergyTotal;
|
||||
uint32_t pumpWorkMin; // Total solar pump operating time
|
||||
} _EMS_SolarModule;
|
||||
|
||||
// heating circuit
|
||||
typedef struct {
|
||||
uint8_t hc; // heating circuit 1,2, 3 or 4
|
||||
bool active; // true if there is data for this HC
|
||||
int16_t setpoint_roomTemp; // current set temp
|
||||
int16_t curr_roomTemp; // current room temp
|
||||
uint8_t mode; // 0=low, 1=manual, 2=auto (or night, day on RC35s)
|
||||
uint8_t mode_type; // 0=night/eco, 1=day/comfort
|
||||
uint8_t summer_mode;
|
||||
uint8_t holiday_mode;
|
||||
uint8_t daytemp;
|
||||
uint8_t nighttemp;
|
||||
uint8_t holidaytemp;
|
||||
uint8_t heatingtype; // type of heating: 1 radiator, 2 convectors, 3 floors, 4 room supply
|
||||
uint8_t circuitcalctemp; // calculated setpoint flow temperature
|
||||
} _EMS_Thermostat_HC;
|
||||
|
||||
// Thermostat data
|
||||
typedef struct {
|
||||
uint8_t device_id; // the device ID of the thermostat
|
||||
uint8_t device_flags; // thermostat model flags
|
||||
const char * device_desc_p;
|
||||
uint8_t product_id;
|
||||
char version[10];
|
||||
char datetime[25]; // HH:MM:SS DD/MM/YYYY
|
||||
bool write_supported;
|
||||
|
||||
// Installation parameters (tested on RC30)
|
||||
uint8_t ibaMainDisplay; // 00, 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
|
||||
uint8_t ibaLanguage; // 01, language on Thermostat: 0 german, 1 dutch, 2 french, 3 italian
|
||||
uint8_t ibaCalIntTemperature; // 02, offset int. temperature sensor, by * 0.1 Kelvin
|
||||
int16_t ibaMinExtTemperature; // 05, min ext temp for heating curve, in deg., 0xF6=-10, 0x0 = 0, 0xFF=-1 (actually a int8_t, coded as int16_t to benefit from negative value rendering)
|
||||
uint8_t ibaBuildingType; // 06, building type: 0 = light, 1 = medium, 2 = heavy
|
||||
uint8_t ibaClockOffset; // 12, offset (in sec) to clock, 0xff = -1 s, 0x02 = 2 s
|
||||
|
||||
_EMS_Thermostat_HC hc[EMS_THERMOSTAT_MAXHC]; // array for the 4 heating circuits
|
||||
} _EMS_Thermostat;
|
||||
|
||||
// call back function signature for processing telegram types
|
||||
typedef void (*EMS_processType_cb)(_EMS_RxTelegram * EMS_RxTelegram);
|
||||
|
||||
// Definition for each EMS type, including the relative callback function
|
||||
typedef struct {
|
||||
_EMS_DEVICE_UPDATE_FLAG device_flag;
|
||||
uint16_t type;
|
||||
const char typeString[30];
|
||||
EMS_processType_cb processType_cb;
|
||||
} _EMS_Type;
|
||||
|
||||
typedef enum : uint8_t {
|
||||
EMS_THERMOSTAT_MODE_UNKNOWN = 0,
|
||||
EMS_THERMOSTAT_MODE_OFF,
|
||||
EMS_THERMOSTAT_MODE_MANUAL,
|
||||
EMS_THERMOSTAT_MODE_AUTO,
|
||||
EMS_THERMOSTAT_MODE_HEAT, // 'heizen'
|
||||
EMS_THERMOSTAT_MODE_NIGHT,
|
||||
EMS_THERMOSTAT_MODE_DAY,
|
||||
EMS_THERMOSTAT_MODE_ECO, // 'sparen'
|
||||
EMS_THERMOSTAT_MODE_COMFORT,
|
||||
EMS_THERMOSTAT_MODE_HOLIDAY,
|
||||
EMS_THERMOSTAT_MODE_NOFROST
|
||||
} _EMS_THERMOSTAT_MODE;
|
||||
|
||||
#define EMS_THERMOSTAT_MODE_UNKNOWN_STR "unknown"
|
||||
#define EMS_THERMOSTAT_MODE_OFF_STR "off"
|
||||
#define EMS_THERMOSTAT_MODE_MANUAL_STR "manual"
|
||||
#define EMS_THERMOSTAT_MODE_AUTO_STR "auto"
|
||||
#define EMS_THERMOSTAT_MODE_HEAT_STR "heat"
|
||||
#define EMS_THERMOSTAT_MODE_NIGHT_STR "night"
|
||||
#define EMS_THERMOSTAT_MODE_DAY_STR "day"
|
||||
#define EMS_THERMOSTAT_MODE_ECO_STR "eco"
|
||||
#define EMS_THERMOSTAT_MODE_COMFORT_STR "comfort"
|
||||
#define EMS_THERMOSTAT_MODE_HOLIDAY_STR "holiday"
|
||||
#define EMS_THERMOSTAT_MODE_NOFROST_STR "nofrost"
|
||||
|
||||
// function definitions
|
||||
void ems_dumpBuffer(const char * prefix, uint8_t * telegram, uint8_t length);
|
||||
void ems_parseTelegram(uint8_t * telegram, uint8_t len);
|
||||
void ems_init();
|
||||
void ems_doReadCommand(uint16_t type, uint8_t dest, uint8_t offset = 0);
|
||||
void ems_sendRawTelegram(char * telegram);
|
||||
void ems_printDevices();
|
||||
uint8_t ems_printDevices_s(char * buffer, uint16_t len);
|
||||
void ems_printTxQueue();
|
||||
void ems_testTelegram(uint8_t test_num);
|
||||
void ems_startupTelegrams();
|
||||
bool ems_checkEMSBUSAlive();
|
||||
|
||||
void ems_setSettingsLanguage(uint8_t lg);
|
||||
void ems_setSettingsBuilding(uint8_t bg);
|
||||
void ems_setSettingsDisplay(uint8_t ds);
|
||||
|
||||
void ems_setThermostatTemp(float temperature, uint8_t hc, _EMS_THERMOSTAT_MODE temptype);
|
||||
void ems_setThermostatTemp(float temperature, uint8_t hc, const char * mode_s);
|
||||
void ems_setThermostatMode(_EMS_THERMOSTAT_MODE mode, uint8_t hc);
|
||||
void ems_setThermostatMode(const char * mode_s, uint8_t hc);
|
||||
void ems_setWarmWaterTemp(uint8_t temperature);
|
||||
void ems_setFlowTemp(uint8_t temperature);
|
||||
void ems_setWarmWaterActivated(bool activated);
|
||||
void ems_setWarmWaterOnetime(bool activated);
|
||||
void ems_setWarmWaterCirculation(bool activated);
|
||||
void ems_setWarmTapWaterActivated(bool activated);
|
||||
void ems_setPoll(bool b);
|
||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel, uint16_t type_id);
|
||||
void ems_setLogging(_EMS_SYS_LOGGING loglevel, bool quiet = false);
|
||||
void ems_setWarmWaterModeComfort(uint8_t comfort);
|
||||
void ems_setModels();
|
||||
void ems_setTxDisabled(bool b);
|
||||
void ems_setTxMode(uint8_t mode);
|
||||
void ems_setEMSbusid(uint8_t id);
|
||||
void ems_setMasterThermostat(uint8_t product_id);
|
||||
|
||||
char * ems_getDeviceDescription(_EMS_DEVICE_TYPE device_type, char * buffer, bool name_only = false);
|
||||
bool ems_getDeviceTypeDescription(uint8_t device_id, char * buffer);
|
||||
char * ems_getDeviceTypeName(_EMS_DEVICE_TYPE device_type, char * buffer);
|
||||
_EMS_THERMOSTAT_MODE ems_getThermostatMode(const char * mode_s);
|
||||
void ems_getThermostatValues();
|
||||
void ems_getBoilerValues();
|
||||
void ems_getSettingsValues();
|
||||
void ems_getSolarModuleValues();
|
||||
void ems_getMixingModuleValues();
|
||||
char * ems_getThermostatModeString(_EMS_THERMOSTAT_MODE mode, char * mode_str);
|
||||
|
||||
bool ems_getPoll();
|
||||
bool ems_getTxEnabled();
|
||||
|
||||
bool ems_getThermostatEnabled();
|
||||
bool ems_getMixingModuleEnabled();
|
||||
bool ems_getBoilerEnabled();
|
||||
bool ems_getSolarModuleEnabled();
|
||||
bool ems_getHeatPumpEnabled();
|
||||
|
||||
bool ems_getBusConnected();
|
||||
_EMS_SYS_LOGGING ems_getLogging();
|
||||
uint8_t ems_getThermostatFlags();
|
||||
uint8_t ems_getSolarModuleFlags();
|
||||
void ems_discoverModels();
|
||||
bool ems_getTxCapable();
|
||||
uint32_t ems_getPollFrequency();
|
||||
bool ems_getTxDisabled();
|
||||
void ems_Device_add_flags(unsigned int flags);
|
||||
bool ems_Device_has_flags(unsigned int flags);
|
||||
void ems_Device_remove_flags(unsigned int flags);
|
||||
bool ems_isHT3();
|
||||
void ems_scanDevices();
|
||||
|
||||
// private functions
|
||||
uint8_t _crcCalculator(uint8_t * data, uint8_t len);
|
||||
void _processType(_EMS_RxTelegram * EMS_RxTelegram);
|
||||
void _debugPrintPackage(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram, const char * color);
|
||||
void _ems_clearTxData();
|
||||
void _removeTxQueue();
|
||||
int8_t _getHeatingCircuit(_EMS_RxTelegram * EMS_RxTelegram);
|
||||
void _printMessage(_EMS_RxTelegram * EMS_RxTelegram, const int show_type = -1);
|
||||
|
||||
// global so can referenced in other classes
|
||||
extern _EMS_Sys_Status EMS_Sys_Status;
|
||||
extern _EMS_Boiler EMS_Boiler;
|
||||
extern _EMS_Thermostat EMS_Thermostat;
|
||||
extern _EMS_SolarModule EMS_SolarModule;
|
||||
extern _EMS_HeatPump EMS_HeatPump;
|
||||
extern _EMS_MixingModule EMS_MixingModule;
|
||||
|
||||
extern std::list<_Detected_Device> Devices;
|
||||
@@ -1,351 +0,0 @@
|
||||
/*
|
||||
* General information about known EMS devices
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for History
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ems.h"
|
||||
|
||||
// Fixed EMS Device IDs
|
||||
#define EMS_ID_BOILER 0x08 // all UBA Boilers have 0x08
|
||||
|
||||
/*
|
||||
* Common Type
|
||||
*/
|
||||
#define EMS_TYPE_Version 0x02
|
||||
#define EMS_TYPE_UBADevices 0x07 // EMS connected devices
|
||||
|
||||
/*
|
||||
* Boiler Telegram Types...
|
||||
*/
|
||||
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMonitorWWMessage 0x34 // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_UBAMaintenanceStatusMessage 0x1C // is an automatic monitor broadcast
|
||||
#define EMS_TYPE_MC10Status 0x2A
|
||||
#define EMS_TYPE_UBAParameterWW 0x33
|
||||
#define EMS_TYPE_UBATotalUptimeMessage 0x14
|
||||
#define EMS_TYPE_UBAFlags 0x35
|
||||
#define EMS_TYPE_UBAMaintenanceSettingsMessage 0x15
|
||||
#define EMS_TYPE_UBAParametersMessage 0x16
|
||||
#define EMS_TYPE_UBASetPoints 0x1A
|
||||
#define EMS_TYPE_UBAFunctionTest 0x1D
|
||||
|
||||
// EMS+ specific
|
||||
#define EMS_TYPE_UBAOutdoorTemp 0xD1 // external temp
|
||||
#define EMS_TYPE_UBAMonitorFast2 0xE4 // Monitor fast for newer EMS+
|
||||
#define EMS_TYPE_UBAMonitorSlow2 0xE5 // Monitor slow for newer EMS+
|
||||
|
||||
#define EMS_OFFSET_UBAParameterWW_wwtemp 2 // WW Temperature
|
||||
#define EMS_OFFSET_UBAParameterWW_wwactivated 1 // WW Activated
|
||||
#define EMS_OFFSET_UBAParameterWW_wwOneTime 0x00 // WW OneTime loading
|
||||
#define EMS_OFFSET_UBAParameterWW_wwCirulation 1 // WW circulation
|
||||
#define EMS_OFFSET_UBAParameterWW_wwComfort 9 // WW is in comfort or eco mode
|
||||
#define EMS_VALUE_UBAParameterWW_wwComfort_Hot 0x00 // the value for hot
|
||||
#define EMS_VALUE_UBAParameterWW_wwComfort_Eco 0xD8 // the value for eco
|
||||
#define EMS_VALUE_UBAParameterWW_wwComfort_Intelligent 0xEC // the value for intelligent
|
||||
|
||||
#define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp
|
||||
|
||||
// Installation settings
|
||||
#define EMS_TYPE_IBASettingsMessage 0xA5 // installation settings
|
||||
#define EMS_OFFSET_IBASettings_Display 0 // display
|
||||
#define EMS_OFFSET_IBASettings_Language 1 // language
|
||||
#define EMS_OFFSET_IBASettings_MinExtTemp 5 // min. ext. temperature
|
||||
#define EMS_OFFSET_IBASettings_Building 6 // building
|
||||
#define EMS_OFFSET_IBASettings_CalIntTemp 2 // cal. int. temperature
|
||||
#define EMS_OFFSET_IBASettings_ClockOffset 12 // clock offset
|
||||
|
||||
#define EMS_VALUE_IBASettings_LANG_GERMAN 0
|
||||
#define EMS_VALUE_IBASettings_LANG_DUTCH 1
|
||||
#define EMS_VALUE_IBASettings_LANG_FRENCH 2
|
||||
#define EMS_VALUE_IBASettings_LANG_ITALIAN 3
|
||||
|
||||
#define EMS_VALUE_IBASettings_BUILDING_LIGHT 0
|
||||
#define EMS_VALUE_IBASettings_BUILDING_MEDIUM 1
|
||||
#define EMS_VALUE_IBASettings_BUILDING_HEAVY 2
|
||||
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_INTTEMP 0
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_INTSETPOINT 1
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_EXTTEMP 2
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_BURNERTEMP 3
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_WWTEMP 4
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_FUNCMODE 5
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_TIME 6
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_DATE 7
|
||||
#define EMS_VALUE_IBASettings_DISPLAY_SMOKETEMP 9
|
||||
|
||||
// Mixing Modules
|
||||
// MM100/MM200 (EMS Plus)
|
||||
#define EMS_TYPE_MMPLUSStatusMessage_HC1 0x01D7 // mixing status HC1
|
||||
#define EMS_TYPE_MMPLUSStatusMessage_HC2 0x01D8 // mixing status HC2
|
||||
#define EMS_TYPE_MMPLUSStatusMessage_HC3 0x01D9 // mixing status HC3
|
||||
#define EMS_TYPE_MMPLUSStatusMessage_HC4 0x01DA // mixing status HC4
|
||||
#define EMS_TYPE_MMPLUSStatusMessage_WWC1 0x0231 // mixing status WWC1
|
||||
#define EMS_TYPE_MMPLUSStatusMessage_WWC2 0x0232 // mixing status WWC2
|
||||
#define EMS_OFFSET_MMPLUSStatusMessage_flow_temp 3 // flow temperature
|
||||
#define EMS_OFFSET_MMPLUSStatusMessage_pump_mod 5 // pump modulation
|
||||
#define EMS_OFFSET_MMPLUSStatusMessage_valve_status 2 // valve in percent
|
||||
#define EMS_OFFSET_MMPLUSStatusMessage_WW_flow_temp 0 // flow temperature
|
||||
#define EMS_OFFSET_MMPLUSStatusMessage_WW_pump_mod 2 // pump on 6, off 0
|
||||
#define EMS_OFFSET_MMPLUSStatusMessage_WW_temp_status 11 // 0,1,2
|
||||
|
||||
// MM10
|
||||
#define EMS_TYPE_MMStatusMessage 0xAB // mixing status
|
||||
#define EMS_OFFSET_MMStatusMessage_flow_set 0 // flow setpoint
|
||||
#define EMS_OFFSET_MMStatusMessage_flow_temp 1 // flow temperature
|
||||
#define EMS_OFFSET_MMStatusMessage_pump_mod 3 // pump modulation in percent
|
||||
#define EMS_OFFSET_MMStatusMessage_valve_status 4 // valve 0..255
|
||||
#define EMS_TYPE_MM10ParameterMessage 0xAC // mixing parameters
|
||||
|
||||
// Solar Module
|
||||
// Assuming here that the SM200 behaves like SM100
|
||||
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
|
||||
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
|
||||
#define EMS_TYPE_SM100Status 0x0264 // SM100Status
|
||||
#define EMS_TYPE_SM100Status2 0x026A // SM100Status2
|
||||
#define EMS_TYPE_SM100Energy 0x028E // SM100Energy
|
||||
// ISPM solar module
|
||||
#define EMS_TYPE_ISM1StatusMessage 0x0003 // Solar Module Junkers ISM1 Status
|
||||
#define EMS_TYPE_ISM1Set 0x0001 // for setting values of the solar module like max boiler temp
|
||||
#define EMS_OFFSET_ISM1Set_MaxBoilerTemp 6 // position of max boiler temp e.g. 50 in the following example: 90 30 FF 06 00 01 50 (CRC=2C)
|
||||
|
||||
// Heat Pump
|
||||
#define EMS_TYPE_HPMonitor1 0xE3 // HeatPump Monitor 1
|
||||
#define EMS_TYPE_HPMonitor2 0xE5 // HeatPump Monitor 2
|
||||
|
||||
/*
|
||||
* Thermostat Types
|
||||
*/
|
||||
|
||||
// Common for all thermostats
|
||||
#define EMS_TYPE_RCTime 0x06 // is an automatic thermostat broadcast
|
||||
#define EMS_TYPE_RCOutdoorTempMessage 0xA3 // is an automatic thermostat broadcast, outdoor external temp
|
||||
|
||||
// RC10 specific
|
||||
#define EMS_TYPE_RC10StatusMessage 0xB1 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_OFFSET_RC10StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_OFFSET_RC10StatusMessage_curr 2 // current temp
|
||||
|
||||
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
|
||||
|
||||
// RC20 specific
|
||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_OFFSET_RC20StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_OFFSET_RC20StatusMessage_curr 2 // current temp
|
||||
|
||||
#define EMS_TYPE_RC20NStatusMessage 0xAE
|
||||
#define EMS_OFFSET_RC20NStatusMessage_setpoint 2 // setpoint temp in AE
|
||||
#define EMS_OFFSET_RC20NStatusMessage_curr 3 // current temp in AE
|
||||
|
||||
#define EMS_TYPE_RC20NSet 0xAD
|
||||
#define EMS_OFFSET_RC20NSet_temp_day 2 // position of thermostat setpoint temperature for day time
|
||||
#define EMS_OFFSET_RC20NSet_temp_night 1 // position of thermostat setpoint temperature for night time
|
||||
#define EMS_OFFSET_RC20NSet_mode 3 // position mode
|
||||
#define EMS_OFFSET_RC20NSet_heatingtype 0
|
||||
|
||||
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
|
||||
|
||||
// RC30 specific
|
||||
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_OFFSET_RC30StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_OFFSET_RC30StatusMessage_curr 2 // current temp
|
||||
|
||||
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||
|
||||
// RC35 specific
|
||||
#define EMS_TYPE_RC35StatusMessage_HC1 0x3E // is an automatic thermostat broadcast giving us temps on HC1
|
||||
#define EMS_TYPE_RC35StatusMessage_HC2 0x48 // is an automatic thermostat broadcast giving us temps on HC2
|
||||
#define EMS_TYPE_RC35StatusMessage_HC3 0x52 // is an automatic thermostat broadcast giving us temps on HC3
|
||||
#define EMS_TYPE_RC35StatusMessage_HC4 0x5C // is an automatic thermostat broadcast giving us temps on HC4
|
||||
|
||||
#define EMS_OFFSET_RC35StatusMessage_setpoint 2 // desired temp
|
||||
#define EMS_OFFSET_RC35StatusMessage_curr 3 // current temp
|
||||
#define EMS_OFFSET_RC35StatusMessage_mode 1 // day mode, also summer on RC3's
|
||||
#define EMS_OFFSET_RC35StatusMessage_mode1 0 // for holiday mode
|
||||
|
||||
#define EMS_TYPE_RC35Set_HC1 0x3D // for setting values like temp and mode (Working mode HC1)
|
||||
#define EMS_TYPE_RC35Set_HC2 0x47 // for setting values like temp and mode (Working mode HC2)
|
||||
#define EMS_TYPE_RC35Set_HC3 0x51 // for setting values like temp and mode (Working mode HC3)
|
||||
#define EMS_TYPE_RC35Set_HC4 0x5B // for setting values like temp and mode (Working mode HC4)
|
||||
|
||||
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
|
||||
#define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time
|
||||
#define EMS_OFFSET_RC35Set_temp_holiday 3 // temp during holiday mode
|
||||
#define EMS_OFFSET_RC35Set_heatingtype 0 // e.g. floor heating = 3
|
||||
#define EMS_OFFSET_RC35Set_circuitcalctemp 14 // calculated circuit temperature
|
||||
#define EMS_OFFSET_RC35Set_seltemp 37 // selected temp
|
||||
|
||||
// Easy specific
|
||||
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
|
||||
#define EMS_OFFSET_EasyStatusMessage_setpoint 10 // setpoint temp
|
||||
#define EMS_OFFSET_EasyStatusMessage_curr 8 // current temp
|
||||
|
||||
// RC1010, RC310 and RC300 specific (EMS Plus)
|
||||
#define EMS_TYPE_RCPLUSStatusMessage_HC1 0x01A5 // is an automatic thermostat broadcast giving us temps for HC1
|
||||
#define EMS_TYPE_RCPLUSStatusMessage_HC2 0x01A6 // is an automatic thermostat broadcast giving us temps for HC2
|
||||
#define EMS_TYPE_RCPLUSStatusMessage_HC3 0x01A7 // is an automatic thermostat broadcast giving us temps for HC3
|
||||
#define EMS_TYPE_RCPLUSStatusMessage_HC4 0x01A8 // is an automatic thermostat broadcast giving us temps for HC4
|
||||
#define EMS_TYPE_RCPLUSStatusMode 0x1AF // summer/winter mode
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_mode 10 // thermostat mode (auto, manual)
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_setpoint 3 // setpoint temp
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_currsetpoint 6 // target setpoint temp
|
||||
|
||||
#define EMS_TYPE_RCPLUSSet 0x01B9 // setpoint temp message and mode
|
||||
#define EMS_OFFSET_RCPLUSSet_mode 0 // operation mode(Auto=0xFF, Manual=0x00)
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_comfort3 1 // comfort3 level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_comfort2 2 // comfort2 level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_comfort1 3 // comfort1 level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_eco 4 // eco level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_setpoint 8 // temp setpoint, when changing of templevel (in auto) value is reset and set to FF
|
||||
#define EMS_OFFSET_RCPLUSSet_manual_setpoint 10 // manual setpoint
|
||||
|
||||
// Junkers FR10, FR50, FW100, FW120 (EMS Plus)
|
||||
// HC1 = 0x6F-0x72
|
||||
#define EMS_TYPE_JunkersStatusMessage_HC1 0x6F
|
||||
#define EMS_TYPE_JunkersStatusMessage_HC2 0x70
|
||||
#define EMS_TYPE_JunkersStatusMessage_HC3 0x71
|
||||
#define EMS_TYPE_JunkersStatusMessage_HC4 0x72
|
||||
|
||||
#define EMS_OFFSET_JunkersStatusMessage_daymode 0 // 3 = day, 2 = night
|
||||
#define EMS_OFFSET_JunkersStatusMessage_mode 1 // current mode, 1 = manual, 2 = auto
|
||||
#define EMS_OFFSET_JunkersStatusMessage_setpoint 2 // setpoint temp
|
||||
#define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp
|
||||
|
||||
// HC1-4 0x65-0x68 - EMS_DEVICE_FLAG_JUNKERS1
|
||||
// Junkers FR10, FR50, FW100, FW120
|
||||
#define EMS_TYPE_JunkersSetMessage1_HC1 0x65
|
||||
#define EMS_TYPE_JunkersSetMessage1_HC2 0x66
|
||||
#define EMS_TYPE_JunkersSetMessage1_HC3 0x67
|
||||
#define EMS_TYPE_JunkersSetMessage1_HC4 0x68
|
||||
#define EMS_OFFSET_JunkersSetMessage_day_temp 0x11 // EMS offset to set temperature on thermostat for day mode
|
||||
#define EMS_OFFSET_JunkersSetMessage_night_temp 0x10 // EMS offset to set temperature on thermostat for night mode
|
||||
#define EMS_OFFSET_JunkersSetMessage_no_frost_temp 0x0F // EMS offset to set temperature on thermostat for no frost mode
|
||||
#define EMS_OFFSET_JunkersSetMessage_set_mode 0x0E // EMS offset to set mode on thermostat
|
||||
|
||||
// HC1-4 0x79-0x7C - EMS_DEVICE_FLAG_JUNKERS2
|
||||
// Junkers FR100
|
||||
#define EMS_TYPE_JunkersSetMessage2_HC1 0x79
|
||||
#define EMS_TYPE_JunkersSetMessage2_HC2 0x7A
|
||||
#define EMS_TYPE_JunkersSetMessage2_HC3 0x7B
|
||||
#define EMS_TYPE_JunkersSetMessage2_HC4 0x7C
|
||||
#define EMS_OFFSET_JunkersSetMessage2_no_frost_temp 0x05
|
||||
#define EMS_OFFSET_JunkersSetMessage2_eco_temp 0x06
|
||||
#define EMS_OFFSET_JunkersSetMessage3_heat 0x07
|
||||
|
||||
/*
|
||||
* Table of all known EMS Devices
|
||||
* ProductID, DeviceType, Description, Flags
|
||||
*/
|
||||
static const _EMS_Device EMS_Devices[] = {
|
||||
|
||||
//
|
||||
// UBA Masters - must have device_id of 0x08
|
||||
//
|
||||
{72, EMS_DEVICE_TYPE_BOILER, "MC10 Module", EMS_DEVICE_FLAG_NONE},
|
||||
{123, EMS_DEVICE_TYPE_BOILER, "Buderus GBx72/Nefit Trendline/Junkers Cerapur/Worcester Greenstar Si/27i", EMS_DEVICE_FLAG_NONE},
|
||||
{133, EMS_DEVICE_TYPE_BOILER, "Buderus GB125/Logamatic MC110", EMS_DEVICE_FLAG_NONE},
|
||||
{115, EMS_DEVICE_TYPE_BOILER, "Nefit Topline/Buderus GB162", EMS_DEVICE_FLAG_NONE},
|
||||
{203, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax U122/Junkers Cerapur", EMS_DEVICE_FLAG_NONE},
|
||||
{208, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax plus/GB192/Bosch Condens GC9000", EMS_DEVICE_FLAG_NONE},
|
||||
{64, EMS_DEVICE_TYPE_BOILER, "Sieger BK13,BK15/Nefit Smartline/Buderus GB1x2", EMS_DEVICE_FLAG_NONE},
|
||||
{234, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax Plus GB122", EMS_DEVICE_FLAG_NONE},
|
||||
{84, EMS_DEVICE_TYPE_BOILER, "Buderus Logamax Plus GB022", EMS_DEVICE_FLAG_NONE},
|
||||
{95, EMS_DEVICE_TYPE_BOILER, "Bosch Condens 2500/Buderus Logamax,Logomatic/Junkers Cerapur Top/Worcester Greenstar i/Generic HT3", EMS_DEVICE_FLAG_NONE},
|
||||
{122, EMS_DEVICE_TYPE_BOILER, "Nefit Proline", EMS_DEVICE_FLAG_NONE},
|
||||
{170, EMS_DEVICE_TYPE_BOILER, "Buderus Logano GB212", EMS_DEVICE_FLAG_NONE},
|
||||
{172, EMS_DEVICE_TYPE_BOILER, "Nefit Enviline", EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
//
|
||||
// Solar Modules - type 0x30
|
||||
//
|
||||
{73, EMS_DEVICE_TYPE_SOLAR, "SM10 Solar Module", EMS_DEVICE_FLAG_SM10},
|
||||
{163, EMS_DEVICE_TYPE_SOLAR, "SM100 Solar Module", EMS_DEVICE_FLAG_SM100},
|
||||
{164, EMS_DEVICE_TYPE_SOLAR, "SM200 Solar Module", EMS_DEVICE_FLAG_SM100},
|
||||
{101, EMS_DEVICE_TYPE_SOLAR, "Junkers ISM1 Solar Module", EMS_DEVICE_FLAG_SM100},
|
||||
{162, EMS_DEVICE_TYPE_SOLAR, "SM50 Solar Module", EMS_DEVICE_FLAG_SM100},
|
||||
|
||||
//
|
||||
// Mixing Devices - type 0x20 or 0x21
|
||||
//
|
||||
{160, EMS_DEVICE_TYPE_MIXING, "MM100 Mixing Module", EMS_DEVICE_FLAG_MMPLUS},
|
||||
{161, EMS_DEVICE_TYPE_MIXING, "MM200 Mixing Module", EMS_DEVICE_FLAG_MMPLUS},
|
||||
{69, EMS_DEVICE_TYPE_MIXING, "MM10 Mixing Module", EMS_DEVICE_FLAG_MM10},
|
||||
{159, EMS_DEVICE_TYPE_MIXING, "MM50 Mixing Module", EMS_DEVICE_FLAG_MMPLUS},
|
||||
|
||||
//
|
||||
// HeatPump - type 0x38
|
||||
//
|
||||
{252, EMS_DEVICE_TYPE_HEATPUMP, "HeatPump Module", EMS_DEVICE_FLAG_NONE},
|
||||
{200, EMS_DEVICE_TYPE_HEATPUMP, "HeatPump Module", EMS_DEVICE_FLAG_NONE},
|
||||
|
||||
//
|
||||
// Other devices like controllers and modems
|
||||
// such as 0x11 for Switching, 0x09 for controllers, 0x02 for Connect, 0x48 for Gateway
|
||||
//
|
||||
{71, EMS_DEVICE_TYPE_SWITCH, "WM10 Switch Module", EMS_DEVICE_FLAG_NONE}, // 0x11
|
||||
{68, EMS_DEVICE_TYPE_CONTROLLER, "BC10/RFM20 Receiver", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{218, EMS_DEVICE_TYPE_CONTROLLER, "Junkers M200/Buderus RFM200 Receiver", EMS_DEVICE_FLAG_NONE}, // 0x50
|
||||
{190, EMS_DEVICE_TYPE_CONTROLLER, "BC10 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{114, EMS_DEVICE_TYPE_CONTROLLER, "BC10 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{125, EMS_DEVICE_TYPE_CONTROLLER, "BC25 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{169, EMS_DEVICE_TYPE_CONTROLLER, "BC40 Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{152, EMS_DEVICE_TYPE_CONTROLLER, "Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{95, EMS_DEVICE_TYPE_CONTROLLER, "HT3 Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{209, EMS_DEVICE_TYPE_CONTROLLER, "W-B ErP Boiler Control Panel", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{230, EMS_DEVICE_TYPE_CONTROLLER, "BC Base Controller", EMS_DEVICE_FLAG_NONE}, // 0x09
|
||||
{205, EMS_DEVICE_TYPE_CONNECT, "Nefit Moduline Easy Connect", EMS_DEVICE_FLAG_NONE}, // 0x02
|
||||
{206, EMS_DEVICE_TYPE_CONNECT, "Bosch Easy Connect", EMS_DEVICE_FLAG_NONE}, // 0x02
|
||||
{171, EMS_DEVICE_TYPE_CONNECT, "EMS-OT OpenTherm converter", EMS_DEVICE_FLAG_NONE}, // 0x02
|
||||
{189, EMS_DEVICE_TYPE_GATEWAY, "Web Gateway KM200", EMS_DEVICE_FLAG_NONE}, // 0x48
|
||||
{94, EMS_DEVICE_TYPE_GATEWAY, "RC Remote Device", EMS_DEVICE_FLAG_NONE}, // 0x18
|
||||
{207, EMS_DEVICE_TYPE_CONTROLLER, "Worcester Sense II/Bosch CS200 Solar Controller", EMS_DEVICE_FLAG_NONE}, // 0x10
|
||||
|
||||
//
|
||||
// Thermostats, typically device id of 0x10, 0x17, 0x18, 0x38 (RC100), 0x39 (Easy)
|
||||
//
|
||||
|
||||
// Easy devices - not currently supporting write operations
|
||||
{202, EMS_DEVICE_TYPE_THERMOSTAT, "Logamatic TC100/Nefit Moduline Easy", EMS_DEVICE_FLAG_EASY | EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
{203, EMS_DEVICE_TYPE_THERMOSTAT, "Bosch EasyControl CT200", EMS_DEVICE_FLAG_EASY | EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
{157, EMS_DEVICE_TYPE_THERMOSTAT, "Buderus RC200/Bosch CW100/Junkers CW100", EMS_DEVICE_FLAG_NO_WRITE}, // 0x18, cannot write
|
||||
|
||||
// Buderus/Nefit specific
|
||||
{79, EMS_DEVICE_TYPE_THERMOSTAT, "RC10/Moduline 100", EMS_DEVICE_FLAG_RC10}, // 0x17
|
||||
{80, EMS_DEVICE_TYPE_THERMOSTAT, "Moduline 200", EMS_DEVICE_FLAG_RC10}, // 0x17
|
||||
{77, EMS_DEVICE_TYPE_THERMOSTAT, "RC20/Moduline 300", EMS_DEVICE_FLAG_RC20}, // 0x17
|
||||
{67, EMS_DEVICE_TYPE_THERMOSTAT, "RC30", EMS_DEVICE_FLAG_RC30N}, // 0x10 - based on RC35
|
||||
{78, EMS_DEVICE_TYPE_THERMOSTAT, "Moduline 400", EMS_DEVICE_FLAG_RC30}, // 0x10
|
||||
{86, EMS_DEVICE_TYPE_THERMOSTAT, "RC35", EMS_DEVICE_FLAG_RC35}, // 0x10
|
||||
{93, EMS_DEVICE_TYPE_THERMOSTAT, "RC20RF", EMS_DEVICE_FLAG_RC20}, // 0x19
|
||||
{158, EMS_DEVICE_TYPE_THERMOSTAT, "RC300/RC310/Moduline 3000/Bosch CW400/W-B Sense II", EMS_DEVICE_FLAG_RC300}, // 0x10
|
||||
{165, EMS_DEVICE_TYPE_THERMOSTAT, "RC100/Moduline 1000/1010", EMS_DEVICE_FLAG_RC100}, // 0x18, 0x38
|
||||
|
||||
// Sieger
|
||||
{76, EMS_DEVICE_TYPE_THERMOSTAT, "Sieger ES73", EMS_DEVICE_FLAG_RC35}, // 0x10
|
||||
{113, EMS_DEVICE_TYPE_THERMOSTAT, "Sieger ES72/Buderus RC20", EMS_DEVICE_FLAG_RC20N}, // 0x17
|
||||
|
||||
// Junkers - all 0x10
|
||||
{105, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW100", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
|
||||
{106, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW200", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
|
||||
{107, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR100", EMS_DEVICE_FLAG_JUNKERS2}, // 0x10
|
||||
{108, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR110", EMS_DEVICE_FLAG_JUNKERS2}, // 0x10
|
||||
{111, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR10", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
|
||||
{147, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR50", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
|
||||
{191, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FR120", EMS_DEVICE_FLAG_JUNKERS1}, // 0x10
|
||||
{192, EMS_DEVICE_TYPE_THERMOSTAT, "Junkers FW120", EMS_DEVICE_FLAG_JUNKERS1} // 0x10
|
||||
|
||||
|
||||
};
|
||||
@@ -1,304 +0,0 @@
|
||||
/*
|
||||
* Generic utils
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
*/
|
||||
|
||||
#include "ems_utils.h"
|
||||
|
||||
// convert float to char
|
||||
char * _float_to_char(char * a, float f, uint8_t precision) {
|
||||
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
||||
|
||||
char * ret = a;
|
||||
long whole = (long)f;
|
||||
itoa(whole, a, 10);
|
||||
while (*a != '\0')
|
||||
a++;
|
||||
*a++ = '.';
|
||||
long decimal = abs((long)((f - whole) * p[precision]));
|
||||
itoa(decimal, a, 10);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// convert bool to text. bools are stored as bytes
|
||||
char * _bool_to_char(char * s, uint8_t value) {
|
||||
if ((value == EMS_VALUE_BOOL_ON) || (value == EMS_VALUE_BOOL_ON2)) {
|
||||
strlcpy(s, "on", sizeof(s));
|
||||
} else if (value == EMS_VALUE_BOOL_OFF) {
|
||||
strlcpy(s, "off", sizeof(s));
|
||||
} else { // EMS_VALUE_BOOL_NOTSET
|
||||
strlcpy(s, "?", sizeof(s));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
// convert short (two bytes) to text string and returns string
|
||||
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
|
||||
// negative values are assumed stored as 1-compliment (https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c)
|
||||
char * _short_to_char(char * s, int16_t value, uint8_t decimals) {
|
||||
// remove errors or invalid values
|
||||
if (value == EMS_VALUE_SHORT_NOTSET) {
|
||||
strlcpy(s, "?", 10);
|
||||
return (s);
|
||||
}
|
||||
|
||||
// just print
|
||||
if (decimals == 0) {
|
||||
ltoa(value, s, 10);
|
||||
return (s);
|
||||
}
|
||||
|
||||
// check for negative values
|
||||
if (value < 0) {
|
||||
strlcpy(s, "-", 10);
|
||||
value *= -1; // convert to positive
|
||||
} else {
|
||||
strlcpy(s, "", 10);
|
||||
}
|
||||
|
||||
// do floating point
|
||||
char s2[10] = {0};
|
||||
if (decimals == 2) {
|
||||
// divide by 2
|
||||
strlcat(s, ltoa(value / 2, s2, 10), 10);
|
||||
strlcat(s, ".", 10);
|
||||
strlcat(s, ((value & 0x01) ? "5" : "0"), 10);
|
||||
|
||||
} else {
|
||||
strlcat(s, ltoa(value / (decimals * 10), s2, 10), 10);
|
||||
strlcat(s, ".", 10);
|
||||
strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// convert unsigned short (two bytes) to text string and prints it
|
||||
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
|
||||
char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals) {
|
||||
// remove errors or invalid values
|
||||
if (value == EMS_VALUE_USHORT_NOTSET) { // 0x7D00
|
||||
strlcpy(s, "?", 10);
|
||||
return (s);
|
||||
}
|
||||
|
||||
// just print
|
||||
if (decimals == 0) {
|
||||
ltoa(value, s, 10);
|
||||
return (s);
|
||||
}
|
||||
|
||||
// do floating point
|
||||
char s2[10] = {0};
|
||||
|
||||
if (decimals == 2) {
|
||||
// divide by 2
|
||||
strlcpy(s, ltoa(value / 2, s2, 10), 10);
|
||||
strlcat(s, ".", 10);
|
||||
strlcat(s, ((value & 0x01) ? "5" : "0"), 10);
|
||||
|
||||
} else {
|
||||
strlcpy(s, ltoa(value / (decimals * 10), s2, 10), 10);
|
||||
strlcat(s, ".", 10);
|
||||
strlcat(s, ltoa(value % (decimals * 10), s2, 10), 10);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// takes a signed short value (2 bytes), converts to a fraction and prints it
|
||||
// decimals: 0=no division, 1=divide value by 10 (default), 2=divide by 2, 10=divide value by 100
|
||||
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals) {
|
||||
static char buffer[200] = {0};
|
||||
static char s[20] = {0};
|
||||
strlcpy(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, prefix, sizeof(buffer));
|
||||
strlcat(buffer, ": ", sizeof(buffer));
|
||||
|
||||
strlcat(buffer, _short_to_char(s, value, decimals), sizeof(buffer));
|
||||
|
||||
if (postfix != nullptr) {
|
||||
strlcat(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, postfix, sizeof(buffer));
|
||||
}
|
||||
|
||||
myDebug(buffer);
|
||||
}
|
||||
|
||||
// takes a unsigned short value (2 bytes), converts to a fraction and prints it
|
||||
// decimals: 0 = no division, 1=divide value by 10, 2=divide by 2, 10=divide value by 100
|
||||
void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals) {
|
||||
static char buffer[200] = {0};
|
||||
static char s[20] = {0};
|
||||
strlcpy(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, prefix, sizeof(buffer));
|
||||
strlcat(buffer, ": ", sizeof(buffer));
|
||||
|
||||
strlcat(buffer, _ushort_to_char(s, value, decimals), sizeof(buffer));
|
||||
|
||||
if (postfix != nullptr) {
|
||||
strlcat(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, postfix, sizeof(buffer));
|
||||
}
|
||||
|
||||
myDebug(buffer);
|
||||
}
|
||||
|
||||
// convert int (single byte) to text value and returns it
|
||||
char * _int_to_char(char * s, uint8_t value, uint8_t div) {
|
||||
s[0] = '\0'; // reset
|
||||
if (value == EMS_VALUE_INT_NOTSET) {
|
||||
strlcpy(s, "?", sizeof(s));
|
||||
return (s);
|
||||
}
|
||||
|
||||
static char s2[5] = {0};
|
||||
|
||||
switch (div) {
|
||||
case 1:
|
||||
itoa(value, s, 10);
|
||||
break;
|
||||
|
||||
case 2:
|
||||
strlcpy(s, itoa(value >> 1, s2, 10), 5);
|
||||
strlcat(s, ".", sizeof(s));
|
||||
strlcat(s, ((value & 0x01) ? "5" : "0"), 5);
|
||||
break;
|
||||
|
||||
case 10:
|
||||
strlcpy(s, itoa(value / 10, s2, 10), 5);
|
||||
strlcat(s, ".", sizeof(s));
|
||||
strlcat(s, itoa(value % 10, s2, 10), 5);
|
||||
break;
|
||||
|
||||
default:
|
||||
itoa(value, s, 10);
|
||||
break;
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// takes an int value (1 byte), converts to a fraction and prints
|
||||
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div) {
|
||||
static char buffer[200] = {0};
|
||||
static char s[20] = {0};
|
||||
strlcpy(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, prefix, sizeof(buffer));
|
||||
strlcat(buffer, ": ", sizeof(buffer));
|
||||
|
||||
strlcat(buffer, _int_to_char(s, value, div), sizeof(buffer));
|
||||
|
||||
if (postfix != nullptr) {
|
||||
strlcat(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, postfix, sizeof(buffer));
|
||||
}
|
||||
|
||||
myDebug(buffer);
|
||||
}
|
||||
|
||||
// takes a long value at prints it to debug log and prints
|
||||
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value, uint8_t div) {
|
||||
static char buffer[200] = {0};
|
||||
strlcpy(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, prefix, sizeof(buffer));
|
||||
strlcat(buffer, ": ", sizeof(buffer));
|
||||
|
||||
if (value == EMS_VALUE_LONG_NOTSET) {
|
||||
strlcat(buffer, "?", sizeof(buffer));
|
||||
} else {
|
||||
char s[20] = {0};
|
||||
if (div == 0) {
|
||||
strlcat(buffer, ltoa(value, s, 10), sizeof(buffer));
|
||||
} else {
|
||||
strlcat(buffer, ltoa(value / 10, s, 10), sizeof(buffer));
|
||||
strlcat(buffer, ".", sizeof(buffer));
|
||||
strlcat(buffer, ltoa(value % 10, s, 10), sizeof(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
if (postfix != nullptr) {
|
||||
strlcat(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, postfix, sizeof(buffer));
|
||||
}
|
||||
|
||||
myDebug(buffer);
|
||||
}
|
||||
|
||||
// takes a bool value at prints it to debug log and prints
|
||||
void _renderBoolValue(const char * prefix, uint8_t value) {
|
||||
static char buffer[200] = {0};
|
||||
static char s[20] = {0};
|
||||
strlcpy(buffer, " ", sizeof(buffer));
|
||||
strlcat(buffer, prefix, sizeof(buffer));
|
||||
strlcat(buffer, ": ", sizeof(buffer));
|
||||
|
||||
strlcat(buffer, _bool_to_char(s, value), sizeof(buffer));
|
||||
|
||||
myDebug(buffer);
|
||||
}
|
||||
|
||||
// like itoa but for hex, and quicker
|
||||
char * _hextoa(uint8_t value, char * buffer) {
|
||||
char * p = buffer;
|
||||
byte nib1 = (value >> 4) & 0x0F;
|
||||
byte nib2 = (value >> 0) & 0x0F;
|
||||
*p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
|
||||
*p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
|
||||
*p = '\0'; // null terminate just in case
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// for decimals 0 to 99, printed as a 2 char string
|
||||
char * _smallitoa(uint8_t value, char * buffer) {
|
||||
buffer[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
|
||||
buffer[1] = (value % 10) + '0';
|
||||
buffer[2] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/* for decimals 0 to 999, printed as a string
|
||||
*/
|
||||
char * _smallitoa3(uint16_t value, char * buffer) {
|
||||
buffer[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
|
||||
buffer[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
|
||||
buffer[2] = (value % 10) + '0';
|
||||
buffer[3] = '\0';
|
||||
return buffer;
|
||||
}
|
||||
|
||||
// used to read the next string from an input buffer and convert to an 8 bit int
|
||||
uint8_t _readIntNumber() {
|
||||
char * numTextPtr = strtok(nullptr, ", \n");
|
||||
if (numTextPtr == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return atoi(numTextPtr);
|
||||
}
|
||||
|
||||
// used to read the next string from an input buffer and convert to a float
|
||||
float _readFloatNumber() {
|
||||
char * numTextPtr = strtok(nullptr, ", \n");
|
||||
if (numTextPtr == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return atof(numTextPtr);
|
||||
}
|
||||
|
||||
// used to read the next string from an input buffer as a hex value and convert to a 16 bit int
|
||||
uint16_t _readHexNumber() {
|
||||
char * numTextPtr = strtok(nullptr, ", \n");
|
||||
if (numTextPtr == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return (uint16_t)strtol(numTextPtr, 0, 16);
|
||||
}
|
||||
|
||||
// used to read the next string from an input buffer
|
||||
char * _readWord() {
|
||||
char * word = strtok(nullptr, ", \n");
|
||||
return word;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Generic utils
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "MyESP.h"
|
||||
#include "ems.h"
|
||||
|
||||
#define myDebug(...) myESP.myDebug(__VA_ARGS__)
|
||||
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
|
||||
|
||||
char * _float_to_char(char * a, float f, uint8_t precision = 2);
|
||||
char * _bool_to_char(char * s, uint8_t value);
|
||||
char * _short_to_char(char * s, int16_t value, uint8_t decimals = 1);
|
||||
char * _ushort_to_char(char * s, uint16_t value, uint8_t decimals = 1);
|
||||
void _renderShortValue(const char * prefix, const char * postfix, int16_t value, uint8_t decimals = 1);
|
||||
void _renderUShortValue(const char * prefix, const char * postfix, uint16_t value, uint8_t decimals = 1);
|
||||
char * _int_to_char(char * s, uint8_t value, uint8_t div = 1);
|
||||
void _renderIntValue(const char * prefix, const char * postfix, uint8_t value, uint8_t div = 1);
|
||||
void _renderLongValue(const char * prefix, const char * postfix, uint32_t value, uint8_t div = 0);
|
||||
void _renderBoolValue(const char * prefix, uint8_t value);
|
||||
char * _hextoa(uint8_t value, char * buffer);
|
||||
char * _smallitoa(uint8_t value, char * buffer);
|
||||
char * _smallitoa3(uint16_t value, char * buffer);
|
||||
uint8_t _readIntNumber();
|
||||
float _readFloatNumber();
|
||||
uint16_t _readHexNumber();
|
||||
char * _readWord();
|
||||
296
src/emsdevice.cpp
Normal file
296
src/emsdevice.cpp
Normal file
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* 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 "emsdevice.h"
|
||||
#include "emsesp.h"
|
||||
#include "mqtt.h" // for the mqtt_function_p
|
||||
|
||||
MAKE_PSTR(logger_name, "emsdevice")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger EMSdevice::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
std::string EMSdevice::brand_to_string() const {
|
||||
switch (brand_) {
|
||||
case EMSdevice::Brand::BOSCH:
|
||||
return read_flash_string(F("Bosch"));
|
||||
break;
|
||||
case EMSdevice::Brand::JUNKERS:
|
||||
return read_flash_string(F("Junkers"));
|
||||
break;
|
||||
case EMSdevice::Brand::BUDERUS:
|
||||
return read_flash_string(F("Buderus"));
|
||||
break;
|
||||
case EMSdevice::Brand::NEFIT:
|
||||
return read_flash_string(F("Nefit"));
|
||||
break;
|
||||
case EMSdevice::Brand::SIEGER:
|
||||
return read_flash_string(F("Sieger"));
|
||||
break;
|
||||
case EMSdevice::Brand::WORCESTER:
|
||||
return read_flash_string(F("Worcester"));
|
||||
break;
|
||||
case EMSdevice::Brand::NO_BRAND:
|
||||
default:
|
||||
return read_flash_string(F("?"));
|
||||
break;
|
||||
}
|
||||
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
std::string EMSdevice::device_type_name() const {
|
||||
switch (device_type_) {
|
||||
case DeviceType::BOILER:
|
||||
return read_flash_string(F("Boiler"));
|
||||
break;
|
||||
|
||||
case DeviceType::THERMOSTAT:
|
||||
return read_flash_string(F("Thermostat"));
|
||||
break;
|
||||
|
||||
case DeviceType::HEATPUMP:
|
||||
return read_flash_string(F("Heat Pump"));
|
||||
break;
|
||||
|
||||
case DeviceType::SOLAR:
|
||||
return read_flash_string(F("Solar Module"));
|
||||
break;
|
||||
|
||||
case DeviceType::CONNECT:
|
||||
return read_flash_string(F("Connect Module"));
|
||||
break;
|
||||
|
||||
case DeviceType::CONTROLLER:
|
||||
return read_flash_string(F("Controller"));
|
||||
break;
|
||||
|
||||
case DeviceType::MIXING:
|
||||
return read_flash_string(F("Mixing Module"));
|
||||
break;
|
||||
|
||||
case DeviceType::SWITCH:
|
||||
return read_flash_string(F("Switching Module"));
|
||||
break;
|
||||
|
||||
case DeviceType::GATEWAY:
|
||||
return read_flash_string(F("Gateway Module"));
|
||||
break;
|
||||
|
||||
default:
|
||||
return read_flash_string(F("Unknown"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 0=unknown, 1=bosch, 2=junkers, 3=buderus, 4=nefit, 5=sieger, 11=worcester
|
||||
uint8_t EMSdevice::decode_brand(uint8_t value) {
|
||||
switch (value) {
|
||||
case 1:
|
||||
return EMSdevice::Brand::BOSCH;
|
||||
break;
|
||||
case 2:
|
||||
return EMSdevice::Brand::JUNKERS;
|
||||
break;
|
||||
case 3:
|
||||
return EMSdevice::Brand::BUDERUS;
|
||||
break;
|
||||
case 4:
|
||||
return EMSdevice::Brand::NEFIT;
|
||||
break;
|
||||
case 5:
|
||||
return EMSdevice::Brand::SIEGER;
|
||||
break;
|
||||
case 11:
|
||||
return EMSdevice::Brand::WORCESTER;
|
||||
break;
|
||||
case 0:
|
||||
default:
|
||||
return EMSdevice::Brand::NO_BRAND;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// print human friendly description of the EMS device
|
||||
std::string EMSdevice::to_string() const {
|
||||
std::string str(160, '\0');
|
||||
|
||||
// for devices that haven't been lookup yet, don't show all details
|
||||
if (product_id_ == 0) {
|
||||
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s (DeviceID:0x%02X)"), name_.c_str(), device_id_);
|
||||
return str;
|
||||
}
|
||||
|
||||
if (brand_ == Brand::NO_BRAND) {
|
||||
snprintf_P(&str[0], str.capacity() + 1, PSTR("%s (DeviceID:0x%02X, ProductID:%d, Version:%s)"), name_.c_str(), device_id_, product_id_, version_.c_str());
|
||||
} else {
|
||||
snprintf_P(&str[0],
|
||||
str.capacity() + 1,
|
||||
PSTR("%s %s (DeviceID:0x%02X ProductID:%d, Version:%s)"),
|
||||
brand_to_string().c_str(),
|
||||
name_.c_str(),
|
||||
device_id_,
|
||||
product_id_,
|
||||
version_.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());
|
||||
}
|
||||
|
||||
// for each telegram that has the fetch value set (true) do a read request
|
||||
void EMSdevice::fetch_values() {
|
||||
LOG_DEBUG(F("Fetching values for device ID 0x%02X"), device_id());
|
||||
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if (tf.fetch_) {
|
||||
read_command(tf.telegram_type_id_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// toggle on/off automatic fetch for a telegram id
|
||||
void EMSdevice::toggle_fetch(uint16_t telegram_id, bool toggle) {
|
||||
LOG_DEBUG(F("Toggling fetch for device ID 0x%02X, telegram ID 0x%02X to %d"), device_id(), telegram_id, toggle);
|
||||
|
||||
for (auto & tf : telegram_functions_) {
|
||||
if (tf.telegram_type_id_ == telegram_id) {
|
||||
tf.fetch_ = toggle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// list all the telegram type IDs for this device
|
||||
void EMSdevice::show_telegram_handlers(uuid::console::Shell & shell) {
|
||||
if (telegram_functions_.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
shell.printf(F(" This %s will respond to telegram type IDs: "), device_type_name().c_str());
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
shell.printf(F("0x%02X "), tf.telegram_type_id_);
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// list all the mqtt handlers for this device
|
||||
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) {
|
||||
LOG_DEBUG(F("Registering MQTT topic %s for device ID %02X"), topic.c_str(), this->device_id_);
|
||||
Mqtt::subscribe(this->device_id_, topic, f);
|
||||
}
|
||||
|
||||
EMSdevice::TelegramFunction::TelegramFunction(uint16_t telegram_type_id,
|
||||
const __FlashStringHelper * telegram_type_name,
|
||||
bool fetch,
|
||||
process_function_p process_function)
|
||||
: telegram_type_id_(telegram_type_id)
|
||||
, telegram_type_name_(telegram_type_name)
|
||||
, fetch_(fetch)
|
||||
, process_function_(process_function) {
|
||||
}
|
||||
|
||||
// register a call back function for a specific telegram type
|
||||
void EMSdevice::register_telegram_type(const uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p f) {
|
||||
telegram_functions_.emplace_back(telegram_type_id, telegram_type_name, fetch, f);
|
||||
}
|
||||
|
||||
// return the name of the telegram type
|
||||
std::string EMSdevice::telegram_type_name(std::shared_ptr<const Telegram> telegram) {
|
||||
// see if it's one of the common ones, like Version
|
||||
if (telegram->type_id == EMS_TYPE_VERSION) {
|
||||
return read_flash_string(F("Version"));
|
||||
} else if (telegram->type_id == EMS_TYPE_UBADevices) {
|
||||
return read_flash_string(F("UBADevices"));
|
||||
}
|
||||
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if ((tf.telegram_type_id_ == telegram->type_id) && ((telegram->type_id & 0x0F0) != 0xF0)) {
|
||||
return uuid::read_flash_string(tf.telegram_type_name_);
|
||||
}
|
||||
}
|
||||
|
||||
return std::string{};
|
||||
}
|
||||
|
||||
// take a telegram_type_id and call the matching handler
|
||||
// return true if match found
|
||||
bool EMSdevice::handle_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
for (const auto & tf : telegram_functions_) {
|
||||
if (tf.telegram_type_id_ == telegram->type_id) {
|
||||
// if the data block is empty, assume that this telegram is not recognized by the bus master
|
||||
// so remove it from the automatic fetch list
|
||||
if (telegram->message_length == 0) {
|
||||
LOG_DEBUG(F("This telegram (%s) is not recognized by the EMS bus"), uuid::read_flash_string(tf.telegram_type_name_).c_str());
|
||||
toggle_fetch(tf.telegram_type_id_, false);
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_DEBUG(F("Processing %s..."), uuid::read_flash_string(tf.telegram_type_name_).c_str());
|
||||
tf.process_function_(telegram);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // type not found
|
||||
}
|
||||
|
||||
// send Tx write with a data block
|
||||
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid) {
|
||||
EMSESP::send_write_request(type_id, device_id(), offset, message_data, message_length, validate_typeid);
|
||||
}
|
||||
|
||||
// send Tx write with a single value
|
||||
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid) {
|
||||
uint8_t message_data[1];
|
||||
message_data[0] = value;
|
||||
EMSESP::send_write_request(type_id, device_id(), offset, message_data, 1, validate_typeid);
|
||||
}
|
||||
|
||||
// send Tx write with a single value, with no post validation
|
||||
void EMSdevice::write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value) {
|
||||
write_command(type_id, offset, value, 0);
|
||||
}
|
||||
|
||||
// send Tx read command to the device
|
||||
void EMSdevice::read_command(const uint16_t type_id) {
|
||||
EMSESP::send_read_request(type_id, device_id());
|
||||
}
|
||||
|
||||
// prints a string value to the console
|
||||
void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const __FlashStringHelper * value) {
|
||||
print_value(shell, padding, name, uuid::read_flash_string(value).c_str());
|
||||
}
|
||||
|
||||
void EMSdevice::print_value(uuid::console::Shell & shell, uint8_t padding, const __FlashStringHelper * name, const char * value) {
|
||||
uint8_t i = padding;
|
||||
while (i-- > 0) {
|
||||
shell.print(F(" "));
|
||||
}
|
||||
|
||||
shell.printfln(PSTR("%s: %s"), uuid::read_flash_string(name).c_str(), value);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
249
src/emsdevice.h
Normal file
249
src/emsdevice.h
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_EMSDEVICE_H_
|
||||
#define EMSESP_EMSDEVICE_H_
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
|
||||
#include "emsfactory.h"
|
||||
#include "telegram.h"
|
||||
#include "mqtt.h"
|
||||
#include "helpers.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using namespace std::placeholders; // for `_1`
|
||||
|
||||
class EMSdevice {
|
||||
public:
|
||||
// device_type defines which derived class to use, e.g. BOILER, THERMOSTAT etc..
|
||||
EMSdevice(uint8_t device_type, uint8_t device_id, uint8_t product_id, const std::string & version, const std::string & name, uint8_t flags, uint8_t brand)
|
||||
: device_type_(device_type)
|
||||
, device_id_(device_id)
|
||||
, product_id_(product_id)
|
||||
, version_(version)
|
||||
, name_(name)
|
||||
, flags_(flags)
|
||||
, brand_(brand) {
|
||||
}
|
||||
|
||||
virtual ~EMSdevice() = default; // destructor of base class must always be virtual because it's a polymorphic class
|
||||
|
||||
inline uint8_t device_id() const {
|
||||
return device_id_;
|
||||
}
|
||||
|
||||
std::string device_type_name() const;
|
||||
|
||||
inline uint8_t product_id() const {
|
||||
return product_id_;
|
||||
}
|
||||
|
||||
void product_id(uint8_t product_id) {
|
||||
product_id_ = product_id;
|
||||
}
|
||||
|
||||
inline bool is_device_id(uint8_t device_id) {
|
||||
return ((device_id & 0x7F) == (device_id_ & 0x7F));
|
||||
}
|
||||
|
||||
inline uint8_t flags() const {
|
||||
return flags_;
|
||||
}
|
||||
|
||||
inline void flags(uint8_t flags) {
|
||||
flags_ = flags;
|
||||
}
|
||||
|
||||
// see enum DeviceType below
|
||||
inline uint8_t device_type() const {
|
||||
return device_type_;
|
||||
}
|
||||
|
||||
inline void version(std::string & version) {
|
||||
version_ = version;
|
||||
}
|
||||
|
||||
inline void brand(uint8_t brand) {
|
||||
brand_ = brand;
|
||||
}
|
||||
|
||||
inline uint8_t brand() const {
|
||||
return brand_;
|
||||
}
|
||||
|
||||
inline void name(const std::string & name) {
|
||||
name_ = name;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
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);
|
||||
bool handle_telegram(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void write_command(const uint16_t type_id, const uint8_t offset, uint8_t * message_data, const uint8_t message_length, const uint16_t validate_typeid);
|
||||
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value, const uint16_t validate_typeid);
|
||||
void write_command(const uint16_t type_id, const uint8_t offset, const uint8_t value);
|
||||
|
||||
void read_command(const uint16_t type_id);
|
||||
|
||||
void register_mqtt_topic(const std::string & topic, mqtt_function_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;
|
||||
|
||||
std::string telegram_type_name(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
void fetch_values();
|
||||
void toggle_fetch(uint16_t telegram_id, bool toggle);
|
||||
|
||||
// prints a ems device value to the console, handling the correct rendering of the type
|
||||
// padding is # white space
|
||||
// name is the name of the parameter
|
||||
// suffix is any string to be appended after the value
|
||||
// format:
|
||||
// for ints its 0=no division, 255=handle as boolean, other divide by the value given and render with a decimal point
|
||||
// for floats its the precision in number of decimal places from 0 to 8
|
||||
template <typename Value>
|
||||
static void print_value(uuid::console::Shell & shell,
|
||||
uint8_t padding,
|
||||
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;
|
||||
}
|
||||
|
||||
uint8_t i = padding;
|
||||
while (i-- > 0) {
|
||||
shell.print(F(" "));
|
||||
}
|
||||
|
||||
shell.printf(PSTR("%s: %s"), uuid::read_flash_string(name).c_str(), buffer);
|
||||
|
||||
if (suffix != nullptr) {
|
||||
shell.println(uuid::read_flash_string(suffix).c_str());
|
||||
} else {
|
||||
shell.println();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
enum Brand : uint8_t {
|
||||
NO_BRAND, // 0
|
||||
BOSCH, // 1
|
||||
JUNKERS, // 2
|
||||
BUDERUS, // 3
|
||||
NEFIT, // 4
|
||||
SIEGER, // 5
|
||||
WORCESTER // 11
|
||||
};
|
||||
|
||||
enum DeviceType : uint8_t {
|
||||
UNKNOWN = 0,
|
||||
SERVICEKEY,
|
||||
BOILER,
|
||||
THERMOSTAT,
|
||||
MIXING,
|
||||
SOLAR,
|
||||
HEATPUMP,
|
||||
GATEWAY,
|
||||
SWITCH,
|
||||
CONTROLLER,
|
||||
CONNECT
|
||||
|
||||
};
|
||||
|
||||
// device IDs
|
||||
static constexpr uint8_t EMS_DEVICE_ID_BOILER = 0x08; // fixed device_id for Master Boiler/UBA
|
||||
static constexpr uint8_t EMS_DEVICE_ID_MODEM = 0x48; // gateways like the KM200
|
||||
|
||||
// type IDs
|
||||
static constexpr uint16_t EMS_TYPE_VERSION = 0x02; // type ID for Version information. Generic across all EMS devices.
|
||||
static constexpr uint16_t EMS_TYPE_UBADevices = 0x07; // EMS connected devices
|
||||
|
||||
// device flags: The lower 4 bits hold the unique identifier, the upper 4 bits are used for specific flags
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_NONE = 0;
|
||||
|
||||
// Solar Module
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_SM10 = 1;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_SM100 = 2;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_ISM = 3;
|
||||
|
||||
// Mixing Module
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_MMPLUS = 1;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_MM10 = 2;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_IPM = 3;
|
||||
|
||||
// Thermostats
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_NO_WRITE = (1 << 7); // last bit
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_EASY = 1;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC10 = 2;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC20 = 3;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC20_2 = 4; // Variation on RC20, Older, like ES72?
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC30_1 = 5; // variation on RC30, Newer?
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC30 = 6;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC35 = 7;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC300 = 8;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_RC100 = 9;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS = 10;
|
||||
static constexpr uint8_t EMS_DEVICE_FLAG_JUNKERS_2 = (1 << 6); // 6th bit set if older models
|
||||
|
||||
private:
|
||||
uint8_t device_type_ = DeviceType::UNKNOWN;
|
||||
uint8_t device_id_ = 0;
|
||||
uint8_t product_id_ = 0;
|
||||
std::string version_;
|
||||
std::string name_; // the long name of the EMS model
|
||||
uint8_t flags_ = 0;
|
||||
uint8_t brand_ = Brand::NO_BRAND;
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
class TelegramFunction {
|
||||
public:
|
||||
TelegramFunction(uint16_t telegram_type_id, const __FlashStringHelper * telegram_type_name, bool fetch, process_function_p process_function);
|
||||
~TelegramFunction() = default;
|
||||
|
||||
uint16_t telegram_type_id_; // it's type_id
|
||||
const __FlashStringHelper * telegram_type_name_; // e.g. RC20Message
|
||||
bool fetch_; // if this type_id be queried automatically
|
||||
process_function_p process_function_;
|
||||
};
|
||||
std::vector<TelegramFunction> telegram_functions_; // each EMS device has its own set of registered telegram types
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
735
src/emsesp.cpp
Normal file
735
src/emsesp.cpp
Normal file
@@ -0,0 +1,735 @@
|
||||
/*
|
||||
* 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 "emsesp.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "emsesp")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using DeviceFlags = emsesp::EMSdevice;
|
||||
using DeviceType = emsesp::EMSdevice::DeviceType;
|
||||
|
||||
AsyncWebServer webServer(80);
|
||||
|
||||
#if defined(ESP32)
|
||||
ESP8266React EMSESP::esp8266React(&webServer, &SPIFFS);
|
||||
EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &SPIFFS, EMSESP::esp8266React.getSecurityManager());
|
||||
#else
|
||||
ESP8266React EMSESP::esp8266React(&webServer, &LittleFS);
|
||||
EMSESPSettingsService EMSESP::emsespSettingsService = EMSESPSettingsService(&webServer, &LittleFS, EMSESP::esp8266React.getSecurityManager());
|
||||
#endif
|
||||
|
||||
EMSESPStatusService EMSESP::emsespStatusService =
|
||||
EMSESPStatusService(&webServer, EMSESP::esp8266React.getSecurityManager(), EMSESP::esp8266React.getMqttClient());
|
||||
|
||||
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
|
||||
|
||||
uuid::log::Logger EMSESP::logger_{FPSTR(__pstr__logger_name), uuid::log::Facility::KERN};
|
||||
|
||||
// The services
|
||||
RxService EMSESP::rxservice_; // incoming Telegram Rx handler
|
||||
TxService EMSESP::txservice_; // outgoing Telegram Tx handler
|
||||
Mqtt EMSESP::mqtt_; // mqtt handler
|
||||
System EMSESP::system_; // core system services
|
||||
Console EMSESP::console_; // telnet and serial console
|
||||
Sensors EMSESP::sensors_; // Dallas sensors
|
||||
Shower EMSESP::shower_; // Shower logic
|
||||
|
||||
// static/common variables
|
||||
uint8_t EMSESP::actual_master_thermostat_ = EMSESP_DEFAULT_MASTER_THERMOSTAT; // which thermostat leads when multiple found
|
||||
uint16_t EMSESP::watch_id_ = WATCH_ID_NONE; // for when log is TRACE. 0 means no trace set
|
||||
uint8_t EMSESP::watch_ = 0; // trace off
|
||||
bool EMSESP::tap_water_active_ = false; // for when Boiler states we having running warm water. used in Shower()
|
||||
uint32_t EMSESP::last_fetch_ = 0;
|
||||
|
||||
// for a specific EMS device go and request data values
|
||||
// or if device_id is 0 it will fetch from all known devices
|
||||
void EMSESP::fetch_device_values(const uint8_t device_id) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if ((device_id == 0) || emsdevice->is_device_id(device_id)) {
|
||||
emsdevice->fetch_values();
|
||||
if (device_id != 0) {
|
||||
return; // quit, we only want to return the selected device
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return number of devices of a known type
|
||||
uint8_t EMSESP::count_devices(const uint8_t device_type) {
|
||||
uint8_t count = 0;
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
count += (emsdevice->device_type() == device_type);
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void EMSESP::actual_master_thermostat(const uint8_t device_id) {
|
||||
actual_master_thermostat_ = device_id;
|
||||
}
|
||||
|
||||
uint8_t EMSESP::actual_master_thermostat() {
|
||||
return actual_master_thermostat_;
|
||||
}
|
||||
|
||||
// to watch both type IDs and device IDs
|
||||
void EMSESP::watch_id(uint16_t watch_id) {
|
||||
// if it's a device ID, which is a single byte, remove the MSB so to support both Buderus and HT3 protocols
|
||||
if (watch_id <= 0xFF) {
|
||||
watch_id_ = (watch_id & 0x7F);
|
||||
} else {
|
||||
watch_id_ = watch_id;
|
||||
}
|
||||
}
|
||||
|
||||
void EMSESP::reset_tx(uint8_t const tx_mode) {
|
||||
txservice_.telegram_read_count(0);
|
||||
txservice_.telegram_write_count(0);
|
||||
txservice_.telegram_fail_count(0);
|
||||
EMSuart::stop();
|
||||
EMSuart::start(tx_mode); // reset the UART
|
||||
}
|
||||
|
||||
// return status of bus: connected, connected but Tx is broken, disconnected
|
||||
uint8_t EMSESP::bus_status() {
|
||||
if (!rxservice_.bus_connected()) {
|
||||
return BUS_STATUS_OFFLINE;
|
||||
}
|
||||
|
||||
// check if we have Tx issues
|
||||
if (txservice_.telegram_read_count() < 2) {
|
||||
return BUS_STATUS_TX_ERRORS;
|
||||
}
|
||||
|
||||
return BUS_STATUS_CONNECTED;
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
uint8_t success_rate = 0;
|
||||
if (rxservice_.telegram_error_count()) {
|
||||
success_rate = ((float)rxservice_.telegram_error_count() / (float)rxservice_.telegram_count()) * 100;
|
||||
}
|
||||
|
||||
shell.printfln(F("EMS Bus info:"));
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { shell.printfln(F(" Tx mode: %d"), settings.tx_mode); });
|
||||
shell.printfln(F(" Bus protocol: %s"), EMSbus::is_ht3() ? F("HT3") : F("Buderus"));
|
||||
shell.printfln(F(" #telegrams received: %d"), rxservice_.telegram_count());
|
||||
shell.printfln(F(" #read requests sent: %d"), txservice_.telegram_read_count());
|
||||
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();
|
||||
|
||||
// Rx queue
|
||||
auto rx_telegrams = rxservice_.queue();
|
||||
if (rx_telegrams.empty()) {
|
||||
shell.printfln(F("Rx Queue is empty"));
|
||||
} else {
|
||||
shell.printfln(F("Rx Queue (%ld telegram%s):"), rx_telegrams.size(), rx_telegrams.size() == 1 ? "" : "s");
|
||||
for (const auto & it : rx_telegrams) {
|
||||
shell.printfln(F(" [%02d] %s"), it.id_, pretty_telegram(it.telegram_).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
shell.println();
|
||||
|
||||
// Tx queue
|
||||
auto tx_telegrams = txservice_.queue();
|
||||
if (tx_telegrams.empty()) {
|
||||
shell.printfln(F("Tx Queue is empty"));
|
||||
} else {
|
||||
shell.printfln(F("Tx Queue (%ld telegram%s):"), tx_telegrams.size(), tx_telegrams.size() == 1 ? "" : "s");
|
||||
|
||||
std::string op;
|
||||
for (const auto & it : tx_telegrams) {
|
||||
if ((it.telegram_->operation) == Telegram::Operation::TX_RAW) {
|
||||
op = read_flash_string(F("RAW "));
|
||||
} else if ((it.telegram_->operation) == Telegram::Operation::TX_READ) {
|
||||
op = read_flash_string(F("READ "));
|
||||
} else if ((it.telegram_->operation) == Telegram::Operation::TX_WRITE) {
|
||||
op = read_flash_string(F("WRITE"));
|
||||
}
|
||||
shell.printfln(F(" [%02d%c] %s %s (offset %d)"),
|
||||
it.id_,
|
||||
((it.retry_) ? '*' : ' '),
|
||||
op.c_str(),
|
||||
pretty_telegram(it.telegram_).c_str(),
|
||||
it.telegram_->offset);
|
||||
}
|
||||
}
|
||||
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// show EMS device values
|
||||
void EMSESP::show_device_values(uuid::console::Shell & shell) {
|
||||
if (emsdevices.empty()) {
|
||||
shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu."));
|
||||
shell.println();
|
||||
return;
|
||||
}
|
||||
|
||||
// do this in the order of factory classes to keep a consistent order when displaying
|
||||
for (const auto & device_class : EMSFactory::device_handlers()) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
emsdevice->show_values(shell);
|
||||
shell.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// show Dallas sensors
|
||||
void EMSESP::show_sensor_values(uuid::console::Shell & shell) {
|
||||
if (sensor_devices().empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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.println();
|
||||
}
|
||||
|
||||
// publish all values from each EMS device to MQTT
|
||||
// plus the heartbeat and sensor if activated
|
||||
void EMSESP::publish_all_values() {
|
||||
if (Mqtt::connected()) {
|
||||
// Dallas sensors first
|
||||
sensors_.publish_values();
|
||||
|
||||
// all the connected EMS devices we known about
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
emsdevice->publish_values();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// search for recognized device_ids : Me, All, otherwise print hex value
|
||||
std::string EMSESP::device_tostring(const uint8_t device_id) {
|
||||
if ((device_id & 0x7F) == rxservice_.ems_bus_id()) {
|
||||
return read_flash_string(F("Me"));
|
||||
} else if (device_id == 0x00) {
|
||||
return read_flash_string(F("All"));
|
||||
} else {
|
||||
char buffer[5];
|
||||
return Helpers::hextoa(buffer, device_id);
|
||||
}
|
||||
}
|
||||
|
||||
// created a pretty print telegram as a text string
|
||||
// e.g. Boiler(0x08) -> Me(0x0B), Version(0x02), data: 7B 06 01 00 00 00 00 00 00 04 (#data=10)
|
||||
std::string EMSESP::pretty_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
uint8_t src = telegram->src & 0x7F;
|
||||
uint8_t dest = telegram->dest & 0x7F;
|
||||
uint8_t offset = telegram->offset;
|
||||
|
||||
// find name for src and dest by looking up known devices
|
||||
std::string src_name;
|
||||
std::string dest_name;
|
||||
std::string type_name;
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
// get src & dest
|
||||
if (emsdevice->is_device_id(src)) {
|
||||
src_name = emsdevice->device_type_name();
|
||||
} else if (emsdevice->is_device_id(dest)) {
|
||||
dest_name = emsdevice->device_type_name();
|
||||
}
|
||||
// get the type name, any match will do
|
||||
if (type_name.empty()) {
|
||||
type_name = emsdevice->telegram_type_name(telegram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we can't find names for the devices, use their hex values
|
||||
if (src_name.empty()) {
|
||||
src_name = device_tostring(src);
|
||||
}
|
||||
|
||||
if (dest_name.empty()) {
|
||||
dest_name = device_tostring(dest);
|
||||
}
|
||||
|
||||
// check for global/common types like Version
|
||||
if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) {
|
||||
type_name = read_flash_string(F("Version"));
|
||||
}
|
||||
|
||||
// if we don't know the type show
|
||||
if (type_name.empty()) {
|
||||
type_name = read_flash_string(F("?"));
|
||||
}
|
||||
|
||||
std::string str(200, '\0');
|
||||
|
||||
if (offset) {
|
||||
snprintf_P(&str[0],
|
||||
str.capacity() + 1,
|
||||
PSTR("%s(0x%02X) -> %s(0x%02X), %s(0x%02X), data: %s @offset %d"),
|
||||
src_name.c_str(),
|
||||
src,
|
||||
dest_name.c_str(),
|
||||
dest,
|
||||
type_name.c_str(),
|
||||
telegram->type_id,
|
||||
telegram->to_string().c_str(),
|
||||
offset);
|
||||
} else {
|
||||
snprintf_P(&str[0],
|
||||
str.capacity() + 1,
|
||||
PSTR("%s(0x%02X) -> %s(0x%02X), %s(0x%02X), data: %s"),
|
||||
src_name.c_str(),
|
||||
src,
|
||||
dest_name.c_str(),
|
||||
dest,
|
||||
type_name.c_str(),
|
||||
telegram->type_id,
|
||||
telegram->to_string().c_str());
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
/*
|
||||
* Type 0x07 - UBADevices - shows us the connected EMS devices
|
||||
* e.g. 08 00 07 00 0B 80 00 00 00 00 00 00 00 00 00 00 00
|
||||
* Junkers has 15 bytes of data
|
||||
* each byte is a bitmask for which devices are active
|
||||
* byte 1 = range 0x08 - 0x0F, byte 2=0x10 - 0x17 etc...
|
||||
*/
|
||||
void EMSESP::process_UBADevices(std::shared_ptr<const Telegram> telegram) {
|
||||
// exit it length is incorrect (must be 13 or 15 bytes long)
|
||||
if (telegram->message_length > 15) {
|
||||
return;
|
||||
}
|
||||
|
||||
// for each byte, check the bits and determine the device_id
|
||||
for (uint8_t data_byte = 0; data_byte < telegram->message_length; data_byte++) {
|
||||
uint8_t next_byte = telegram->message_data[data_byte];
|
||||
|
||||
if (next_byte) {
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
if (next_byte & 0x01) {
|
||||
uint8_t device_id = ((data_byte + 1) * 8) + bit;
|
||||
// if we haven't already detected this device, request it's version details, unless its us (EMS-ESP)
|
||||
// when the version info is received, it will automagically add the device
|
||||
if ((device_id != EMSbus::ems_bus_id()) && !(EMSESP::device_exists(device_id))) {
|
||||
LOG_DEBUG(F("New EMS device detected with ID 0x%02X. Requesting version information."), device_id);
|
||||
send_read_request(EMSdevice::EMS_TYPE_VERSION, device_id);
|
||||
}
|
||||
}
|
||||
next_byte = next_byte >> 1; // advance 1 bit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// process the Version telegram (type 0x02), which is a common type
|
||||
// e.g. 09 0B 02 00 PP V1 V2
|
||||
void EMSESP::process_version(std::shared_ptr<const Telegram> telegram) {
|
||||
// check for valid telegram, just in case
|
||||
if (telegram->message_length < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for 2nd subscriber, e.g. 18 0B 02 00 00 00 00 5E 02 01
|
||||
uint8_t offset = 0;
|
||||
if (telegram->message_data[0] == 0x00) {
|
||||
// see if we have a 2nd subscriber
|
||||
if (telegram->message_data[3] != 0x00) {
|
||||
offset = 3;
|
||||
} else {
|
||||
return; // ignore whole telegram
|
||||
}
|
||||
}
|
||||
|
||||
// extra details from the telegram
|
||||
uint8_t device_id = telegram->src; // device ID
|
||||
uint8_t product_id = telegram->message_data[offset]; // product ID
|
||||
|
||||
// get version as XX.XX
|
||||
std::string version(5, '\0');
|
||||
snprintf_P(&version[0], version.capacity() + 1, PSTR("%02d.%02d"), telegram->message_data[offset + 1], telegram->message_data[offset + 2]);
|
||||
|
||||
// some devices store the protocol type (HT3, Buderus) in the last byte
|
||||
uint8_t brand;
|
||||
if (telegram->message_length >= 10) {
|
||||
brand = EMSdevice::decode_brand(telegram->message_data[9]);
|
||||
} else {
|
||||
brand = EMSdevice::Brand::NO_BRAND; // unknown
|
||||
}
|
||||
|
||||
// add it - will be overwritten if device already exists
|
||||
(void)add_device(device_id, product_id, version, brand);
|
||||
}
|
||||
|
||||
// find the device object that matches the device ID and see if it has a matching telegram type handler
|
||||
// but only process if the telegram is sent to us or it's a broadcast (dest=0x00=all)
|
||||
// We also check for common telgram types, like the Version(0x02)
|
||||
// returns false if there are none found
|
||||
bool EMSESP::process_telegram(std::shared_ptr<const Telegram> telegram) {
|
||||
// if watching...
|
||||
if (watch() == WATCH_ON) {
|
||||
if ((watch_id_ == WATCH_ID_NONE) || (telegram->src == watch_id_) || (telegram->dest == watch_id_) || (telegram->type_id == watch_id_)) {
|
||||
LOG_NOTICE(pretty_telegram(telegram).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
// only process broadcast telegrams or ones sent to us on request
|
||||
if ((telegram->dest != 0x00) && (telegram->dest != rxservice_.ems_bus_id())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for common types, like the Version(0x02)
|
||||
if (telegram->type_id == EMSdevice::EMS_TYPE_VERSION) {
|
||||
process_version(telegram);
|
||||
return true;
|
||||
} else if (telegram->type_id == EMSdevice::EMS_TYPE_UBADevices) {
|
||||
process_UBADevices(telegram);
|
||||
return true;
|
||||
}
|
||||
|
||||
// match device_id and type_id
|
||||
// calls the associated process function for that EMS device
|
||||
// returns false if the device_id doesn't recognize it
|
||||
// after the telegram has been processed, call the updated_values() function to see if we need to force an MQTT publish
|
||||
bool found = false;
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->is_device_id(telegram->src)) {
|
||||
found = emsdevice->handle_telegram(telegram);
|
||||
// check to see if we need to follow up after the telegram has been processed
|
||||
if (found) {
|
||||
if (emsdevice->updated_values()) {
|
||||
emsdevice->publish_values(); // publish to MQTT if we explicitly have too
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOG_DEBUG(F("No telegram type handler found for ID 0x%02X (src 0x%02X, dest 0x%02X)"), telegram->type_id, telegram->src, telegram->dest);
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
// return true if we have this device already registered
|
||||
bool EMSESP::device_exists(const uint8_t device_id) {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->is_device_id(device_id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // not found
|
||||
}
|
||||
|
||||
// for each device add its context menu for the console
|
||||
void EMSESP::add_context_menus() {
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
emsdevice->add_context_menu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for each associated EMS device go and get its system information
|
||||
void EMSESP::show_devices(uuid::console::Shell & shell) {
|
||||
if (emsdevices.empty()) {
|
||||
shell.printfln(F("No EMS devices detected. Try using 'scan devices' from the ems menu."));
|
||||
shell.println();
|
||||
return;
|
||||
}
|
||||
|
||||
shell.printfln(F("These EMS devices are currently active:"));
|
||||
shell.println();
|
||||
|
||||
// for all device objects from emsdevice.h (UNKNOWN, SERVICEKEY, BOILER, THERMOSTAT, MIXING, SOLAR, HEATPUMP, GATEWAY, SWITCH, CONTROLLER, CONNECT)
|
||||
// so we keep a consistent order
|
||||
for (const auto & device_class : EMSFactory::device_handlers()) {
|
||||
// shell.printf(F("[factory ID: %d] "), device_class.first);
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if ((emsdevice) && (emsdevice->device_type() == device_class.first)) {
|
||||
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 **"));
|
||||
}
|
||||
shell.println();
|
||||
emsdevice->show_telegram_handlers(shell);
|
||||
emsdevice->show_mqtt_handlers(shell);
|
||||
shell.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// add a new or update existing EMS device to our list of active EMS devices
|
||||
// if its not in our database, we don't add it
|
||||
bool EMSESP::add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand) {
|
||||
// don't add ourselves!
|
||||
if (device_id == rxservice_.ems_bus_id()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// first check to see if we already have it, if so update the record
|
||||
for (const auto & emsdevice : emsdevices) {
|
||||
if (emsdevice) {
|
||||
if (emsdevice->is_device_id(device_id)) {
|
||||
LOG_DEBUG(F("Updating details to already existing device ID 0x%02X"), device_id);
|
||||
emsdevice->product_id(product_id);
|
||||
emsdevice->version(version);
|
||||
// only set brand if it doesn't already exist
|
||||
if (emsdevice->brand() == EMSdevice::Brand::NO_BRAND) {
|
||||
emsdevice->brand(brand);
|
||||
}
|
||||
// find the name and flags in our database
|
||||
for (const auto & device : device_library_) {
|
||||
if (device.product_id == product_id) {
|
||||
emsdevice->name(uuid::read_flash_string(device.name));
|
||||
emsdevice->flags(device.flags);
|
||||
}
|
||||
}
|
||||
|
||||
return true; // finish up
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// look up the rest of the details using the product_id and create the new device object
|
||||
Device_record * device_p = nullptr;
|
||||
for (auto & device : device_library_) {
|
||||
if (device.product_id == product_id) {
|
||||
// sometimes boilers share the same product id as controllers
|
||||
// so only add boilers if the device_id is 0x08, which is fixed for EMS
|
||||
if (device.device_type == DeviceType::BOILER) {
|
||||
if (device_id == EMSdevice::EMS_DEVICE_ID_BOILER) {
|
||||
device_p = &device;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// it's not a boiler, but we have a match
|
||||
device_p = &device;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if we don't recognize the product ID report it, but don't add it.
|
||||
if (device_p == nullptr) {
|
||||
LOG_NOTICE(F("Unrecognized EMS device with device ID 0x%02X with product ID %d. Please report on GitHub."), device_id, product_id);
|
||||
return false; // not found
|
||||
} 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));
|
||||
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);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// send a read request, passing it into to the Tx Service, with no offset
|
||||
void EMSESP::send_read_request(const uint16_t type_id, const uint8_t dest) {
|
||||
txservice_.read_request(type_id, dest, 0); // 0 = no offset
|
||||
}
|
||||
|
||||
// sends write request
|
||||
void EMSESP::send_write_request(const uint16_t type_id,
|
||||
const uint8_t dest,
|
||||
const uint8_t offset,
|
||||
uint8_t * message_data,
|
||||
const uint8_t message_length,
|
||||
const uint16_t validate_typeid) {
|
||||
txservice_.add(Telegram::Operation::TX_WRITE, dest, type_id, offset, message_data, message_length);
|
||||
|
||||
txservice_.set_post_send_query(validate_typeid); // store which type_id to send Tx read after a write
|
||||
}
|
||||
|
||||
// this is main entry point when data is received on the Rx line, via emsuart library
|
||||
// we check if its a complete telegram or just a single byte (which could be a poll or a return status)
|
||||
// the CRC check is not done here, only when it's added to the Rx queue with add()
|
||||
void EMSESP::incoming_telegram(uint8_t * data, const uint8_t length) {
|
||||
#ifdef EMSESP_DEBUG
|
||||
static uint32_t rx_time_ = 0;
|
||||
#endif
|
||||
// check first for echo
|
||||
uint8_t first_value = data[0];
|
||||
if (((first_value & 0x7F) == txservice_.ems_bus_id()) && (length > 1)) {
|
||||
// if we ask ourself at roomcontrol for version e.g. 0B 98 02 00 20
|
||||
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
|
||||
#ifdef EMSESP_DEBUG
|
||||
// get_uptime is only updated once per loop, does not give the right time
|
||||
LOG_DEBUG(F("[DEBUG] Echo after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
|
||||
#endif
|
||||
return; // it's an echo
|
||||
}
|
||||
|
||||
// are we waiting for a response from a recent Tx Read or Write?
|
||||
uint8_t op = EMSbus::tx_waiting();
|
||||
if (op != Telegram::Operation::NONE) {
|
||||
bool tx_successful = false;
|
||||
EMSbus::tx_waiting(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 (first_value == TxService::TX_WRITE_SUCCESS) {
|
||||
LOG_DEBUG(F("Last Tx write successful"));
|
||||
txservice_.increment_telegram_write_count(); // last tx/write was confirmed ok
|
||||
txservice_.send_poll(); // close the bus
|
||||
txservice_.post_send_query(); // follow up with any post-read
|
||||
txservice_.reset_retry_count();
|
||||
tx_successful = true;
|
||||
} else if (first_value == TxService::TX_WRITE_FAIL) {
|
||||
LOG_ERROR(F("Last Tx write rejected by host"));
|
||||
txservice_.send_poll(); // close the bus
|
||||
txservice_.reset_retry_count();
|
||||
}
|
||||
} else if (op == 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];
|
||||
if (txservice_.is_last_tx(src, dest)) {
|
||||
LOG_DEBUG(F("Last Tx read successful"));
|
||||
txservice_.increment_telegram_read_count();
|
||||
txservice_.send_poll(); // close the bus
|
||||
txservice_.reset_retry_count();
|
||||
tx_successful = true;
|
||||
}
|
||||
}
|
||||
|
||||
// if Tx wasn't successful, retry or just give up
|
||||
if (!tx_successful) {
|
||||
txservice_.retry_tx(op, data, length);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// check for poll
|
||||
if (length == 1) {
|
||||
#ifdef EMSESP_DEBUG
|
||||
char s[4];
|
||||
if (first_value & 0x80) {
|
||||
LOG_TRACE(F("[DEBUG] next Poll %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_);
|
||||
// time measurement starts here, use millis because get_uptime is only updated once per loop
|
||||
rx_time_ = ::millis();
|
||||
} else {
|
||||
LOG_TRACE(F("[DEBUG] Poll ack %s after %d ms"), Helpers::hextoa(s, first_value), ::millis() - rx_time_);
|
||||
}
|
||||
#endif
|
||||
// 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
|
||||
Roomctrl::send(first_value ^ 0x80 ^ rxservice_.ems_mask());
|
||||
return;
|
||||
} else {
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_TRACE(F("[DEBUG] Reply after %d ms: %s"), ::millis() - rx_time_, Helpers::data_to_hex(data, length).c_str());
|
||||
#endif
|
||||
// check if there is a message for the roomcontroller
|
||||
Roomctrl::check((data[1] ^ 0x80 ^ rxservice_.ems_mask()), data);
|
||||
// add to RxQueue, what ever it is.
|
||||
// in add() the CRC will be checked
|
||||
rxservice_.add(data, length);
|
||||
}
|
||||
}
|
||||
|
||||
// sends raw data of bytes along the Tx line
|
||||
void EMSESP::send_raw_telegram(const char * data) {
|
||||
txservice_.send_raw(data);
|
||||
}
|
||||
|
||||
// kick off the party, start all the services
|
||||
void EMSESP::start() {
|
||||
// Load our library of known devices
|
||||
device_library_ = {
|
||||
#include "device_library.h"
|
||||
};
|
||||
|
||||
#ifdef ESP32
|
||||
SPIFFS.begin(true);
|
||||
#elif defined(ESP8266)
|
||||
LittleFS.begin();
|
||||
#endif
|
||||
|
||||
esp8266React.begin(); // starts wifi, ap, ota, security, mqtt services
|
||||
emsespSettingsService.begin(); // load settings
|
||||
console_.start(); // telnet and serial console
|
||||
system_.start(); // starts syslog, uart, sets version, initializes LED. Requires pre-loaded settings.
|
||||
mqtt_.start(EMSESP::esp8266React.getMqttClient()); // mqtt init
|
||||
sensors_.start(); // dallas external sensors
|
||||
shower_.start(); // initialize shower timer and shower alert
|
||||
txservice_.start(); // sets bus ID, sends out request for EMS devices
|
||||
webServer.begin(); // start web server
|
||||
}
|
||||
|
||||
// loop de loop
|
||||
void EMSESP::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
esp8266React.loop();
|
||||
#endif
|
||||
|
||||
system_.loop(); // does LED and checks system health, and syslog service
|
||||
mqtt_.loop(); // starts mqtt, and sends out anything in the queue
|
||||
rxservice_.loop(); // process what ever is in the rx queue
|
||||
txservice_.loop(); // check that the Tx is all ok
|
||||
shower_.loop(); // check for shower on/off
|
||||
sensors_.loop(); // this will also send out via MQTT
|
||||
console_.loop(); // telnet/serial console
|
||||
|
||||
// force a query on the EMS devices to fetch latest data at a set interval (1 min)
|
||||
if ((uuid::get_uptime() - last_fetch_ > EMS_FETCH_FREQUENCY)) {
|
||||
last_fetch_ = uuid::get_uptime();
|
||||
fetch_device_values();
|
||||
}
|
||||
|
||||
delay(1);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
179
src/emsesp.h
Normal file
179
src/emsesp.h
Normal file
@@ -0,0 +1,179 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_EMSESP_H
|
||||
#define EMSESP_EMSESP_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <vector>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
|
||||
#include <uuid/common.h>
|
||||
#include <uuid/console.h>
|
||||
#include <uuid/log.h>
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <uuid/telnet.h>
|
||||
#endif
|
||||
|
||||
#include <ESP8266React.h>
|
||||
#include "EMSESPStatusService.h"
|
||||
#include "EMSESPSettingsService.h"
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "emsfactory.h"
|
||||
#include "telegram.h"
|
||||
#include "mqtt.h"
|
||||
#include "system.h"
|
||||
#include "sensors.h"
|
||||
#include "console.h"
|
||||
#include "shower.h"
|
||||
#include "roomcontrol.h"
|
||||
|
||||
#include "devices/boiler.h"
|
||||
|
||||
#define WATCH_ID_NONE 0 // no watch id set
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Shower; // forward declaration for compiler
|
||||
|
||||
class EMSESP {
|
||||
public:
|
||||
static void start();
|
||||
static void loop();
|
||||
|
||||
static void publish_all_values();
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing
|
||||
static void dummy_mqtt_commands(const char * message);
|
||||
static void rx_telegram(const std::vector<uint8_t> & data);
|
||||
static void uart_telegram(const std::vector<uint8_t> & rx_data);
|
||||
#endif
|
||||
|
||||
static bool process_telegram(std::shared_ptr<const Telegram> telegram);
|
||||
static std::string pretty_telegram(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
static void send_read_request(const uint16_t type_id, const uint8_t dest);
|
||||
static void send_write_request(const uint16_t type_id,
|
||||
const uint8_t dest,
|
||||
const uint8_t offset,
|
||||
uint8_t * message_data,
|
||||
const uint8_t message_length,
|
||||
const uint16_t validate_typeid);
|
||||
static void send_raw_telegram(const char * data);
|
||||
static bool device_exists(const uint8_t device_id);
|
||||
|
||||
static uint8_t count_devices(const uint8_t device_type);
|
||||
|
||||
static uint8_t actual_master_thermostat();
|
||||
static void actual_master_thermostat(const uint8_t device_id);
|
||||
|
||||
static void show_device_values(uuid::console::Shell & shell);
|
||||
static void show_sensor_values(uuid::console::Shell & shell);
|
||||
|
||||
static void show_devices(uuid::console::Shell & shell);
|
||||
static void show_ems(uuid::console::Shell & shell);
|
||||
|
||||
static void add_context_menus();
|
||||
|
||||
static void reset_tx(uint8_t const tx_mode);
|
||||
|
||||
static void incoming_telegram(uint8_t * data, const uint8_t length);
|
||||
|
||||
static const std::vector<Sensors::Device> sensor_devices() {
|
||||
return sensors_.devices();
|
||||
}
|
||||
|
||||
static void watch_id(uint16_t id);
|
||||
|
||||
static uint16_t watch_id() {
|
||||
return watch_id_;
|
||||
}
|
||||
|
||||
static void watch(uint8_t watch) {
|
||||
watch_ = watch; // 0=off, 1=on, 2=raw
|
||||
}
|
||||
|
||||
enum Watch : uint8_t { WATCH_OFF, WATCH_ON, WATCH_RAW };
|
||||
static uint8_t watch() {
|
||||
return watch_;
|
||||
}
|
||||
|
||||
enum Bus_status : uint8_t { BUS_STATUS_CONNECTED = 0, BUS_STATUS_TX_ERRORS, BUS_STATUS_OFFLINE };
|
||||
static uint8_t bus_status();
|
||||
|
||||
static bool tap_water_active() {
|
||||
return tap_water_active_;
|
||||
}
|
||||
|
||||
static void tap_water_active(const bool tap_water_active) {
|
||||
tap_water_active_ = tap_water_active;
|
||||
}
|
||||
|
||||
static void fetch_device_values(const uint8_t device_id = 0);
|
||||
|
||||
static bool add_device(const uint8_t device_id, const uint8_t product_id, std::string & version, const uint8_t brand);
|
||||
|
||||
static Mqtt mqtt_;
|
||||
static System system_;
|
||||
static Sensors sensors_;
|
||||
static Console console_;
|
||||
static Shower shower_;
|
||||
static RxService rxservice_;
|
||||
static TxService txservice_;
|
||||
|
||||
static ESP8266React esp8266React;
|
||||
static EMSESPSettingsService emsespSettingsService;
|
||||
static EMSESPStatusService emsespStatusService;
|
||||
|
||||
private:
|
||||
EMSESP() = delete;
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
static std::string device_tostring(const uint8_t device_id);
|
||||
|
||||
static void process_UBADevices(std::shared_ptr<const Telegram> telegram);
|
||||
static void process_version(std::shared_ptr<const Telegram> telegram);
|
||||
|
||||
static constexpr uint32_t EMS_FETCH_FREQUENCY = 60000; // check every minute
|
||||
static uint32_t last_fetch_;
|
||||
|
||||
struct Device_record {
|
||||
uint8_t product_id;
|
||||
EMSdevice::DeviceType device_type;
|
||||
const __FlashStringHelper * name;
|
||||
uint8_t flags;
|
||||
};
|
||||
|
||||
static std::vector<std::unique_ptr<EMSdevice>> emsdevices;
|
||||
static std::vector<Device_record> device_library_;
|
||||
|
||||
static uint8_t actual_master_thermostat_;
|
||||
static uint16_t watch_id_;
|
||||
static uint8_t watch_;
|
||||
static bool tap_water_active_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
100
src/emsfactory.h
Normal file
100
src/emsfactory.h
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_EMSFACTORY_H_
|
||||
#define EMSESP_EMSFACTORY_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory> // for unique_ptr
|
||||
|
||||
#include "emsdevice.h"
|
||||
|
||||
// Macro for class registration
|
||||
// Anonymous namespace is used to make the definitions here private to the current
|
||||
// compilation unit (current file). It is equivalent to the old C static keyword.
|
||||
#define REGISTER_FACTORY(derivedClass, device_type) \
|
||||
namespace { \
|
||||
auto registry_##derivedClass = ConcreteEMSFactory<derivedClass>(device_type); \
|
||||
}
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class EMSdevice; // forward declaration, for gcc linking
|
||||
|
||||
class EMSFactory {
|
||||
public:
|
||||
virtual ~EMSFactory() = default;
|
||||
|
||||
// Register factory object of derived class
|
||||
// using the device_type as the unique identifier
|
||||
static auto registerFactory(const uint8_t device_type, EMSFactory * factory) -> void {
|
||||
auto & reg = EMSFactory::getRegister();
|
||||
reg[device_type] = factory;
|
||||
}
|
||||
|
||||
using FactoryMap = std::map<uint8_t, EMSFactory *>;
|
||||
|
||||
// returns all registered classes (really only for debugging)
|
||||
static auto device_handlers() -> FactoryMap {
|
||||
return EMSFactory::getRegister();
|
||||
}
|
||||
|
||||
// Construct derived class returning an unique ptr
|
||||
static auto add(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand)
|
||||
-> std::unique_ptr<EMSdevice> {
|
||||
return std::unique_ptr<EMSdevice>(EMSFactory::makeRaw(device_type, device_id, product_id, version, name, flags, brand));
|
||||
}
|
||||
|
||||
virtual 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 * = 0;
|
||||
|
||||
private:
|
||||
// Force global variable to be initialized, thus it avoids the "initialization order fiasco"
|
||||
static auto getRegister() -> FactoryMap & {
|
||||
static FactoryMap classRegister{};
|
||||
return classRegister;
|
||||
}
|
||||
|
||||
// Construct derived class returning a raw pointer
|
||||
// find which EMS device it is and use that class
|
||||
static auto makeRaw(const uint8_t device_type, uint8_t device_id, uint8_t product_id, std::string version, std::string name, uint8_t flags, uint8_t brand)
|
||||
-> EMSdevice * {
|
||||
auto it = EMSFactory::getRegister().find(device_type);
|
||||
if (it != EMSFactory::getRegister().end()) {
|
||||
return it->second->construct(device_type, device_id, product_id, version, name, flags, brand);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename DerivedClass>
|
||||
class ConcreteEMSFactory : EMSFactory {
|
||||
public:
|
||||
// Register this global object on the EMSFactory register
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
298
src/emsuart.cpp
298
src/emsuart.cpp
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* emsuart.cpp
|
||||
*
|
||||
* The low level UART code for ESP8266 to read and write to the EMS bus via uart
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
|
||||
#include "emsuart.h"
|
||||
#include "ems.h"
|
||||
#include <user_interface.h>
|
||||
|
||||
_EMSRxBuf * pEMSRxBuf;
|
||||
_EMSRxBuf * paEMSRxBuf[EMS_MAXBUFFERS];
|
||||
uint8_t emsRxBufIdx = 0;
|
||||
uint8_t phantomBreak = 0;
|
||||
|
||||
os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
|
||||
|
||||
//
|
||||
// Main interrupt handler
|
||||
// Important: do not use ICACHE_FLASH_ATTR !
|
||||
//
|
||||
static void emsuart_rx_intr_handler(void * para) {
|
||||
static uint8_t length;
|
||||
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE + 2];
|
||||
|
||||
// is a new buffer? if so init the thing for a new telegram
|
||||
if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) {
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_BUSY; // status set to busy
|
||||
length = 0;
|
||||
}
|
||||
// fill IRQ buffer, by emptying Rx FIFO
|
||||
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
|
||||
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
|
||||
uint8_t rx = USF(EMSUART_UART);
|
||||
if (length < EMS_MAXBUFFERSIZE)
|
||||
uart_buffer[length++] = rx;
|
||||
}
|
||||
|
||||
// clear Rx FIFO full and Rx FIFO timeout interrupts
|
||||
USIC(EMSUART_UART) = (1 << UIFF) | (1 << UITO);
|
||||
}
|
||||
|
||||
// BREAK detection = End of EMS data block
|
||||
if (USIS(EMSUART_UART) & ((1 << UIBD))) {
|
||||
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
|
||||
pEMSRxBuf->length = (length > EMS_MAXBUFFERSIZE) ? EMS_MAXBUFFERSIZE : length;
|
||||
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
|
||||
length = 0;
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package
|
||||
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
|
||||
|
||||
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* system task triggered on BRK interrupt
|
||||
* incoming received messages are always asynchronous
|
||||
* The full buffer is sent to the ems_parseTelegram() function in ems.cpp.
|
||||
*/
|
||||
static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
|
||||
_EMSRxBuf * pCurrent = pEMSRxBuf;
|
||||
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
|
||||
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
|
||||
pCurrent->length = 0;
|
||||
|
||||
if (phantomBreak) {
|
||||
phantomBreak = 0;
|
||||
length--; // remove phantom break from Rx buffer
|
||||
}
|
||||
|
||||
if (length == 2) {
|
||||
// it's a poll or status code, single byte and ok to send on
|
||||
ems_parseTelegram((uint8_t *)pCurrent->buffer, 1);
|
||||
} else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) {
|
||||
// ignore double BRK at the end, possibly from the Tx loopback
|
||||
// also telegrams with no data value
|
||||
ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* flush everything left over in buffer, this clears both rx and tx FIFOs
|
||||
*/
|
||||
static inline void ICACHE_FLASH_ATTR emsuart_flush_fifos() {
|
||||
uint32_t tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
|
||||
USC0(EMSUART_UART) |= (tmp); // set bits
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bits
|
||||
}
|
||||
|
||||
/*
|
||||
* init UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR emsuart_init() {
|
||||
ETS_UART_INTR_DISABLE();
|
||||
ETS_UART_INTR_ATTACH(nullptr, nullptr);
|
||||
|
||||
// allocate and preset EMS Receive buffers
|
||||
for (int i = 0; i < EMS_MAXBUFFERS; i++) {
|
||||
_EMSRxBuf * p = (_EMSRxBuf *)malloc(sizeof(_EMSRxBuf));
|
||||
paEMSRxBuf[i] = p;
|
||||
}
|
||||
pEMSRxBuf = paEMSRxBuf[0]; // preset EMS Rx Buffer
|
||||
|
||||
// pin settings
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0RXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD);
|
||||
|
||||
// set 9600, 8 bits, no parity check, 1 stop bit
|
||||
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
|
||||
USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1
|
||||
|
||||
emsuart_flush_fifos();
|
||||
|
||||
// conf1 params
|
||||
// UCTOE = RX TimeOut enable (default is 1)
|
||||
// UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2)
|
||||
// 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!
|
||||
USC1(EMSUART_UART) = 0; // reset config first
|
||||
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (0 << 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
|
||||
USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (0 << UITO);
|
||||
|
||||
// set up interrupt callbacks for Rx
|
||||
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
|
||||
|
||||
// disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
|
||||
system_set_os_print(0);
|
||||
|
||||
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
|
||||
system_uart_swap();
|
||||
|
||||
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
|
||||
ETS_UART_INTR_ENABLE();
|
||||
}
|
||||
|
||||
/*
|
||||
* stop UART0 driver
|
||||
* This is called prior to an OTA upload and also before a save to SPIFFS to prevent conflicts
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR emsuart_stop() {
|
||||
ETS_UART_INTR_DISABLE();
|
||||
}
|
||||
|
||||
/*
|
||||
* re-start UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR emsuart_start() {
|
||||
ETS_UART_INTR_ENABLE();
|
||||
}
|
||||
|
||||
/*
|
||||
* Send a BRK signal
|
||||
* Which is a 11-bit set of zero's (11 cycles)
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR emsuart_tx_brk() {
|
||||
uint32_t tmp;
|
||||
|
||||
// must make sure Tx FIFO is empty
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xFF))
|
||||
;
|
||||
|
||||
tmp = ((1 << UCRXRST) | (1 << UCTXRST)); // bit mask
|
||||
USC0(EMSUART_UART) |= (tmp); // set bits
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bits
|
||||
|
||||
// To create a 11-bit <BRK> we set TXD_BRK bit so the break signal will
|
||||
// automatically be sent when the tx fifo is empty
|
||||
tmp = (1 << UCBRK);
|
||||
USC0(EMSUART_UART) |= (tmp); // set bit
|
||||
|
||||
if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_EMSPLUS) { // EMS+ mode
|
||||
delayMicroseconds(EMSUART_TX_BRK_WAIT);
|
||||
} else if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_HT3) { // junkers mode
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BRK - EMSUART_TX_LAG); // 1144 (11 Bits)
|
||||
}
|
||||
|
||||
USC0(EMSUART_UART) &= ~(tmp); // clear bit
|
||||
}
|
||||
|
||||
/*
|
||||
* Send to Tx, ending with a <BRK>
|
||||
*/
|
||||
_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
|
||||
_EMS_TX_STATUS result = EMS_TX_STATUS_OK;
|
||||
|
||||
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_JABBER) {
|
||||
ems_dumpBuffer("emsuart_tx_buffer: ", buf, len); // validate and transmit the EMS buffer, excluding the BRK
|
||||
}
|
||||
|
||||
if (len) {
|
||||
if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_EMSPLUS) { // With extra tx delay for EMS+
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_BRK_WAIT); // https://github.com/proddy/EMS-ESP/issues/23#
|
||||
}
|
||||
emsuart_tx_brk(); // send <BRK>
|
||||
} else if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_HT3) { // Junkers logic by @philrich
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
|
||||
// just to be safe wait for tx fifo empty (needed?)
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xff))
|
||||
;
|
||||
|
||||
// wait until bits are sent on wire
|
||||
delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP);
|
||||
}
|
||||
emsuart_tx_brk(); // send <BRK>
|
||||
} else if (EMS_Sys_Status.emsTxMode == EMS_TXMODE_DEFAULT) {
|
||||
/*
|
||||
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
|
||||
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
|
||||
* after sending the last char we poll the Rx status until either
|
||||
* - size(Rx FIFO) == size(Tx-Telegram)
|
||||
* - <BRK> is detected
|
||||
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
|
||||
*
|
||||
* EMS-Bus error handling
|
||||
* 1. Busmaster stops echoing on Tx w/o permission
|
||||
* 2. Busmaster cancel telegram by sending a BRK
|
||||
*
|
||||
* Case 1. is handled by a watchdog counter which is reset on each
|
||||
* Tx attempt. The timeout should be 20x EMSUART_BIT_TIME plus
|
||||
* some smart guess for processing time on targeted EMS device.
|
||||
* We set EMS_Sys_Status.emsTxStatus to EMS_TX_WTD_TIMEOUT and return
|
||||
*
|
||||
* Case 2. is handled via a BRK chk during transmission.
|
||||
* We set EMS_Sys_Status.emsTxStatus to EMS_TX_BRK_DETECT and return
|
||||
*
|
||||
*/
|
||||
uint16_t wdc = EMS_TX_TO_COUNT;
|
||||
ETS_UART_INTR_DISABLE(); // disable rx interrupt
|
||||
|
||||
// clear Rx status register
|
||||
USC0(EMSUART_UART) |= (1 << UCRXRST); // reset uart rx fifo
|
||||
emsuart_flush_fifos();
|
||||
|
||||
// throw out the telegram...
|
||||
for (uint8_t i = 0; i < len && result == EMS_TX_STATUS_OK;) {
|
||||
wdc = EMS_TX_TO_COUNT;
|
||||
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
|
||||
USF(EMSUART_UART) = buf[i++]; // send each Tx byte
|
||||
// wait for echo from busmaster
|
||||
while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) {
|
||||
delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles...
|
||||
if (--wdc == 0) {
|
||||
EMS_Sys_Status.emsTxStatus = result = EMS_TX_WTD_TIMEOUT;
|
||||
break;
|
||||
}
|
||||
if (USIR(EMSUART_UART) & (1 << UIBD)) {
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
|
||||
EMS_Sys_Status.emsTxStatus = result = EMS_TX_BRK_DETECT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 in the loopback and re=enable Rx-INT.
|
||||
// worst case, we'll see an additional Rx-BRK...
|
||||
if (result == EMS_TX_STATUS_OK) {
|
||||
// neither bus collision nor timeout - send terminating BRK signal
|
||||
if (!(USIS(EMSUART_UART) & (1 << UIBD))) {
|
||||
// no bus collision - send terminating BRK signal
|
||||
USC0(EMSUART_UART) |= (1 << UCLBE) | (1 << UCBRK); // enable loopback & set <BRK>
|
||||
|
||||
// wait until BRK detected...
|
||||
while (!(USIR(EMSUART_UART) & (1 << UIBD))) {
|
||||
delayMicroseconds(EMSUART_BIT_TIME);
|
||||
}
|
||||
|
||||
USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK>
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
|
||||
phantomBreak = 1;
|
||||
}
|
||||
}
|
||||
ETS_UART_INTR_ENABLE(); // receive anything from FIFO...
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* emsuart.h
|
||||
*
|
||||
* Header file for emsuart.cpp
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <ems.h>
|
||||
|
||||
#define EMSUART_UART 0 // UART 0
|
||||
#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no stop bits, 1 parity)
|
||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||
|
||||
#define EMS_MAXBUFFERS 3 // buffers for circular filling to avoid collisions
|
||||
#define EMS_MAXBUFFERSIZE (EMS_MAX_TELEGRAM_LENGTH + 2) // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
|
||||
|
||||
#define EMSUART_BIT_TIME 104 // bit time @9600 baud
|
||||
|
||||
#define EMSUART_TX_BRK_WAIT 2070 // the BRK from Boiler master is roughly 1.039ms, so accounting for hardware lag using around 2078 (for half-duplex) - 8 (lag)
|
||||
#define EMSUART_TX_WAIT_BYTE (EMSUART_BIT_TIME * 10) // Time to send one Byte (8 Bits, 1 Start Bit, 1 Stop Bit)
|
||||
#define EMSUART_TX_WAIT_BRK (EMSUART_BIT_TIME * 11) // Time to send a BRK Signal (11 Bit)
|
||||
#define EMSUART_TX_WAIT_GAP (EMSUART_BIT_TIME * 7) // Gap between to Bytes
|
||||
#define EMSUART_TX_LAG 8
|
||||
|
||||
#define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8)
|
||||
#define EMS_TX_TO_CHARS (2 + 20)
|
||||
#define EMS_TX_TO_COUNT ((EMS_TX_TO_CHARS)*10 * 8)
|
||||
|
||||
#define EMSUART_recvTaskPrio 1
|
||||
#define EMSUART_recvTaskQueueLen 64
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t buffer[EMS_MAXBUFFERSIZE];
|
||||
} _EMSRxBuf;
|
||||
|
||||
void ICACHE_FLASH_ATTR emsuart_init();
|
||||
void ICACHE_FLASH_ATTR emsuart_stop();
|
||||
void ICACHE_FLASH_ATTR emsuart_start();
|
||||
_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
|
||||
357
src/helpers.cpp
Normal file
357
src/helpers.cpp
Normal file
@@ -0,0 +1,357 @@
|
||||
/*
|
||||
* 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 "helpers.h"
|
||||
#include "telegram.h" // for EMS_VALUE_* settings
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
// like itoa but for hex, and quicker
|
||||
char * Helpers::hextoa(char * result, const uint8_t value) {
|
||||
char * p = result;
|
||||
uint8_t nib1 = (value >> 4) & 0x0F;
|
||||
uint8_t nib2 = (value >> 0) & 0x0F;
|
||||
*p++ = nib1 < 0xA ? '0' + nib1 : 'A' + nib1 - 0xA;
|
||||
*p++ = nib2 < 0xA ? '0' + nib2 : 'A' + nib2 - 0xA;
|
||||
*p = '\0'; // null terminate just in case
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
// special function to work outside of ESP's libraries
|
||||
char * Helpers::ultostr(char * ptr, uint32_t value, const uint8_t base) {
|
||||
unsigned long t = 0, res = 0;
|
||||
unsigned long tmp = value;
|
||||
int count = 0;
|
||||
|
||||
if (NULL == ptr) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (tmp == 0) {
|
||||
count++;
|
||||
}
|
||||
|
||||
while (tmp > 0) {
|
||||
tmp = tmp / base;
|
||||
count++;
|
||||
}
|
||||
|
||||
ptr += count;
|
||||
|
||||
*ptr = '\0';
|
||||
|
||||
do {
|
||||
res = value - base * (t = value / base);
|
||||
if (res < 10) {
|
||||
*--ptr = '0' + res;
|
||||
} else if ((res >= 10) && (res < 16)) {
|
||||
*--ptr = 'A' - 10 + res;
|
||||
}
|
||||
} while ((value = t) != 0);
|
||||
|
||||
return (ptr);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
* itoa for 2 byte signed (short) integers
|
||||
* written by Lukás Chmela, Released under GPLv3. http://www.strudel.org.uk/itoa/ version 0.4
|
||||
*/
|
||||
char * Helpers::itoa(char * result, int16_t value, const uint8_t base) {
|
||||
// check that the base if valid
|
||||
if (base < 2 || base > 36) {
|
||||
*result = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
char * ptr = result, *ptr1 = result, tmp_char;
|
||||
int16_t tmp_value;
|
||||
|
||||
do {
|
||||
tmp_value = value;
|
||||
value /= base;
|
||||
*ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz"[35 + (tmp_value - value * base)];
|
||||
} while (value);
|
||||
|
||||
// Apply negative sign
|
||||
if (tmp_value < 0) {
|
||||
*ptr++ = '-';
|
||||
}
|
||||
|
||||
*ptr-- = '\0';
|
||||
while (ptr1 < ptr) {
|
||||
tmp_char = *ptr;
|
||||
*ptr-- = *ptr1;
|
||||
*ptr1++ = tmp_char;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// for decimals 0 to 99, printed as a 2 char string
|
||||
char * Helpers::smallitoa(char * result, const uint8_t value) {
|
||||
result[0] = ((value / 10) == 0) ? '0' : (value / 10) + '0';
|
||||
result[1] = (value % 10) + '0';
|
||||
result[2] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
// for decimals 0 to 999, printed as a string
|
||||
char * Helpers::smallitoa(char * result, const uint16_t value) {
|
||||
result[0] = ((value / 100) == 0) ? '0' : (value / 100) + '0';
|
||||
result[1] = (((value % 100) / 10) == 0) ? '0' : ((value % 100) / 10) + '0';
|
||||
result[2] = (value % 10) + '0';
|
||||
result[3] = '\0';
|
||||
return result;
|
||||
}
|
||||
|
||||
// convert unsigned int (single byte) to text value and returns it
|
||||
// format: 255=boolean, 0=no formatting, otherwise divide by format
|
||||
char * Helpers::render_value(char * result, uint8_t value, uint8_t format) {
|
||||
// special check if its a boolean
|
||||
if (format == EMS_VALUE_BOOL) {
|
||||
if (value == EMS_VALUE_BOOL_OFF) {
|
||||
strlcpy(result, "off", 5);
|
||||
} else if (value == EMS_VALUE_BOOL_NOTSET) {
|
||||
return nullptr;
|
||||
} else {
|
||||
strlcpy(result, "on", 5); // assume on. could have value 0x01 or 0xFF
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!hasValue(value)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!format) {
|
||||
itoa(result, value, 10); // format = 0
|
||||
return result;
|
||||
}
|
||||
|
||||
char s2[5];
|
||||
|
||||
// special case for / 2
|
||||
if (format == 2) {
|
||||
strlcpy(result, itoa(s2, value >> 1, 10), 5);
|
||||
strlcat(result, ".", 5);
|
||||
strlcat(result, ((value & 0x01) ? "5" : "0"), 5);
|
||||
return result;
|
||||
}
|
||||
|
||||
strlcpy(result, itoa(s2, value / format, 10), 5);
|
||||
strlcat(result, ".", 5);
|
||||
strlcat(result, itoa(s2, value % format, 10), 5);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// float: convert float to char
|
||||
// format is the precision, 0 to 8
|
||||
char * Helpers::render_value(char * result, const float value, const uint8_t format) {
|
||||
long p[] = {0, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000};
|
||||
|
||||
char * ret = result;
|
||||
long whole = (long)value;
|
||||
Helpers::itoa(result, whole, 10);
|
||||
while (*result != '\0') {
|
||||
result++;
|
||||
}
|
||||
*result++ = '.';
|
||||
long decimal = abs((long)((value - whole) * p[format]));
|
||||
itoa(result, decimal, 10);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// int16: convert short (two bytes) to text string and returns string
|
||||
// format: 0=no division, other divide by the value given and render with a decimal point
|
||||
char * Helpers::render_value(char * result, const int16_t value, const uint8_t format) {
|
||||
if (!hasValue(value)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// just print it if mo conversion required (format = 0)
|
||||
if (!format) {
|
||||
itoa(result, value, 10);
|
||||
return result;
|
||||
}
|
||||
|
||||
int16_t new_value = value;
|
||||
result[0] = '\0';
|
||||
|
||||
// check for negative values
|
||||
if (new_value < 0) {
|
||||
strlcpy(result, "-", 10);
|
||||
new_value *= -1; // convert to positive
|
||||
} else {
|
||||
strlcpy(result, "", 10);
|
||||
}
|
||||
|
||||
// do floating point
|
||||
char s2[10] = {0};
|
||||
if (format == 2) {
|
||||
// divide by 2
|
||||
strlcat(result, itoa(s2, new_value / 2, 10), 10);
|
||||
strlcat(result, ".", 10);
|
||||
strlcat(result, ((new_value & 0x01) ? "5" : "0"), 10);
|
||||
} else {
|
||||
strlcat(result, itoa(s2, new_value / format, 10), 10);
|
||||
strlcat(result, ".", 10);
|
||||
strlcat(result, itoa(s2, new_value % format, 10), 10);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// uint16: convert unsigned short (two bytes) to text string and prints it
|
||||
char * Helpers::render_value(char * result, const uint16_t value, const uint8_t format) {
|
||||
if (!hasValue(value)) {
|
||||
return nullptr;
|
||||
}
|
||||
return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int
|
||||
}
|
||||
|
||||
// int8: convert signed byte to text string and prints it
|
||||
char * Helpers::render_value(char * result, const int8_t value, const uint8_t format) {
|
||||
if (!hasValue(value)) {
|
||||
return nullptr;
|
||||
}
|
||||
return (render_value(result, (int16_t)value, format)); // use same code, force it to a signed int
|
||||
}
|
||||
|
||||
// uint32: render long (4 byte) unsigned values
|
||||
char * Helpers::render_value(char * result, const uint32_t value, const uint8_t format) {
|
||||
if (!hasValue(value)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
char s[20];
|
||||
result[0] = '\0';
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!format) {
|
||||
strlcat(result, ltoa(value, s, 10), 20); // format is 0
|
||||
} else {
|
||||
strlcat(result, ltoa(value / format, s, 10), 20);
|
||||
strlcat(result, ".", 20);
|
||||
strlcat(result, ltoa(value % format, s, 10), 20);
|
||||
}
|
||||
|
||||
#else
|
||||
if (!format) {
|
||||
strlcat(result, ultostr(s, value, 10), 20); // format is 0
|
||||
} else {
|
||||
strncat(result, ultostr(s, value / format, 10), 20);
|
||||
strlcat(result, ".", 20);
|
||||
strncat(result, ultostr(s, value % format, 10), 20);
|
||||
}
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// creates string of hex values from an arrray of bytes
|
||||
std::string Helpers::data_to_hex(const uint8_t * data, const uint8_t length) {
|
||||
if (length == 0) {
|
||||
return uuid::read_flash_string(F("<empty>"));
|
||||
}
|
||||
|
||||
std::string str(160, '\0');
|
||||
char buffer[4];
|
||||
char * p = &str[0];
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
Helpers::hextoa(buffer, data[i]);
|
||||
*p++ = buffer[0];
|
||||
*p++ = buffer[1];
|
||||
*p++ = ' '; // space
|
||||
}
|
||||
*--p = '\0'; // null terminate just in case, loosing the trailing space
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
// takes a hex string and convert it to an unsigned 32bit number (max 8 hex digits)
|
||||
// works with only positive numbers
|
||||
uint32_t Helpers::hextoint(const char * hex) {
|
||||
uint32_t val = 0;
|
||||
while (*hex) {
|
||||
// get current character then increment
|
||||
char byte = *hex++;
|
||||
// transform hex character to the 4bit equivalent number, using the ascii table indexes
|
||||
if (byte >= '0' && byte <= '9')
|
||||
byte = byte - '0';
|
||||
else if (byte >= 'a' && byte <= 'f')
|
||||
byte = byte - 'a' + 10;
|
||||
else if (byte >= 'A' && byte <= 'F')
|
||||
byte = byte - 'A' + 10;
|
||||
else
|
||||
return 0; // error
|
||||
// shift 4 to make space for new digit, and add the 4 bits of the new digit
|
||||
val = (val << 4) | (byte & 0xF);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
// quick char to long
|
||||
uint16_t Helpers::atoint(const char * value) {
|
||||
unsigned int x = 0;
|
||||
while (*value != '\0') {
|
||||
x = (x * 10) + (*value - '0');
|
||||
++value;
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
// rounds a number to 2 decimal places
|
||||
// example: round2(3.14159) -> 3.14
|
||||
double Helpers::round2(double value) {
|
||||
return (int)(value * 100 + 0.5) / 100.0;
|
||||
}
|
||||
|
||||
bool Helpers::check_abs(const int32_t i) {
|
||||
return ((i < 0 ? -i : i) != 0xFFFFFF);
|
||||
}
|
||||
|
||||
// for booleans, use isBool true
|
||||
bool Helpers::hasValue(const uint8_t v, bool isBool) {
|
||||
if (isBool) {
|
||||
return (v != EMS_VALUE_BOOL_NOTSET);
|
||||
}
|
||||
return (v != EMS_VALUE_UINT_NOTSET);
|
||||
}
|
||||
|
||||
bool Helpers::hasValue(const int8_t v) {
|
||||
return (v != EMS_VALUE_INT_NOTSET);
|
||||
}
|
||||
|
||||
// for short these are typically 0x8300, 0x7D00 and sometimes 0x8000
|
||||
bool Helpers::hasValue(const int16_t v) {
|
||||
return (abs(v) < EMS_VALUE_USHORT_NOTSET);
|
||||
}
|
||||
|
||||
bool Helpers::hasValue(const uint16_t v) {
|
||||
return (v < EMS_VALUE_USHORT_NOTSET);
|
||||
}
|
||||
|
||||
bool Helpers::hasValue(const uint32_t v) {
|
||||
return (v != EMS_VALUE_ULONG_NOTSET);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
60
src/helpers.h
Normal file
60
src/helpers.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_HELPERS_H
|
||||
#define EMSESP_HELPERS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <uuid/common.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Helpers {
|
||||
public:
|
||||
static char * hextoa(char * result, const uint8_t value);
|
||||
static std::string data_to_hex(const uint8_t * data, const uint8_t length);
|
||||
|
||||
static char * render_value(char * result, const float value, const uint8_t format); // format is the precision
|
||||
static char * render_value(char * result, const uint8_t value, const uint8_t format);
|
||||
static char * render_value(char * result, const int8_t value, const uint8_t format);
|
||||
static char * render_value(char * result, const uint16_t value, const uint8_t format);
|
||||
static char * render_value(char * result, const uint32_t value, const uint8_t format);
|
||||
static char * render_value(char * result, const int16_t value, const uint8_t format);
|
||||
|
||||
static char * smallitoa(char * result, const uint8_t value);
|
||||
static char * smallitoa(char * result, const uint16_t value);
|
||||
static char * itoa(char * result, int16_t value, const uint8_t base = 10);
|
||||
static uint32_t hextoint(const char * hex);
|
||||
static uint16_t atoint(const char * value);
|
||||
static bool check_abs(const int32_t i);
|
||||
static double round2(double value);
|
||||
|
||||
#ifdef EMSESP_STANDALONE
|
||||
static char * ultostr(char * ptr, uint32_t value, const uint8_t base);
|
||||
#endif
|
||||
|
||||
static bool hasValue(const uint8_t v, bool isBool = false); // use isBool=true for bool's
|
||||
static bool hasValue(const int8_t v);
|
||||
static bool hasValue(const int16_t v);
|
||||
static bool hasValue(const uint16_t v);
|
||||
static bool hasValue(const uint32_t v);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
27
src/main.cpp
Normal file
27
src/main.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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 "emsesp.h"
|
||||
|
||||
void setup() {
|
||||
emsesp::EMSESP::start();
|
||||
}
|
||||
|
||||
void loop() {
|
||||
emsesp::EMSESP::loop();
|
||||
}
|
||||
423
src/mqtt.cpp
Normal file
423
src/mqtt.cpp
Normal file
@@ -0,0 +1,423 @@
|
||||
/*
|
||||
* 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 "mqtt.h"
|
||||
#include "emsesp.h"
|
||||
#include "version.h"
|
||||
|
||||
MAKE_PSTR_WORD(connected)
|
||||
MAKE_PSTR_WORD(disconnected)
|
||||
|
||||
MAKE_PSTR(logger_name, "mqtt")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
AsyncMqttClient * Mqtt::mqttClient_;
|
||||
#endif
|
||||
|
||||
// static parameters we make global
|
||||
std::string Mqtt::hostname_;
|
||||
uint8_t Mqtt::mqtt_qos_;
|
||||
uint16_t Mqtt::publish_time_;
|
||||
|
||||
std::vector<Mqtt::MQTTFunction> Mqtt::mqtt_functions_;
|
||||
uint16_t Mqtt::mqtt_publish_fails_ = 0;
|
||||
size_t Mqtt::maximum_mqtt_messages_ = Mqtt::MAX_MQTT_MESSAGES;
|
||||
uint16_t Mqtt::mqtt_message_id_ = 0;
|
||||
std::deque<Mqtt::QueuedMqttMessage> Mqtt::mqtt_messages_;
|
||||
char will_topic_[Mqtt::MQTT_TOPIC_MAX_SIZE]; // because MQTT library keeps only char pointer
|
||||
|
||||
uuid::log::Logger Mqtt::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
|
||||
|
||||
Mqtt::QueuedMqttMessage::QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content)
|
||||
: id_(id)
|
||||
, content_(std::move(content)) {
|
||||
retry_count_ = 0;
|
||||
packet_id_ = 0;
|
||||
}
|
||||
|
||||
MqttMessage::MqttMessage(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)
|
||||
: device_id_(device_id)
|
||||
, topic_(topic)
|
||||
, mqtt_function_(mqtt_function) {
|
||||
}
|
||||
|
||||
// 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 is wasteful.
|
||||
// strip out everything until the last /
|
||||
size_t found = topic.find_last_of("/"); // returns npos which is -1
|
||||
mqtt_functions_.emplace_back(device_id, std::move(topic.substr(found + 1)), 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 device
|
||||
void Mqtt::subscribe(const std::string & topic, mqtt_function_p cb) {
|
||||
subscribe(0, topic, cb); // no device_id needed, if generic to EMS-ESP
|
||||
}
|
||||
|
||||
// Main MQTT loop
|
||||
// Checks for connection, establishes a connection if not
|
||||
// sends out top item on publish queue
|
||||
void Mqtt::loop() {
|
||||
// exit if MQTT is not enabled or ig there is no WIFI
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!connected()) {
|
||||
#else
|
||||
if (false) {
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t currentMillis = uuid::get_uptime();
|
||||
// create publish messages for each of the EMS device values, adding to queue
|
||||
if (publish_time_ && (currentMillis - last_publish_ > publish_time_)) {
|
||||
last_publish_ = currentMillis;
|
||||
EMSESP::publish_all_values();
|
||||
}
|
||||
|
||||
// publish top item from MQTT queue to stop flooding
|
||||
if ((uint32_t)(currentMillis - last_mqtt_poll_) > MQTT_PUBLISH_WAIT) {
|
||||
last_mqtt_poll_ = currentMillis;
|
||||
process_queue();
|
||||
}
|
||||
}
|
||||
|
||||
// print MQTT log and other stuff to console
|
||||
void Mqtt::show_mqtt(uuid::console::Shell & shell) {
|
||||
shell.printfln(F("MQTT is %s"), connected() ? uuid::read_flash_string(F_(connected)).c_str() : uuid::read_flash_string(F_(disconnected)).c_str());
|
||||
shell.printfln(F("MQTT publish fails: %lu"), mqtt_publish_fails_);
|
||||
shell.println();
|
||||
|
||||
// show queues
|
||||
if (mqtt_messages_.empty()) {
|
||||
shell.printfln(F("MQTT queue is empty"));
|
||||
shell.println();
|
||||
return;
|
||||
}
|
||||
|
||||
shell.printfln(F("MQTT queue (%d messages):"), mqtt_messages_.size());
|
||||
|
||||
for (const auto & message : mqtt_messages_) {
|
||||
auto content = message.content_;
|
||||
if (content->operation == Operation::PUBLISH) {
|
||||
// Publish messages
|
||||
if (message.retry_count_ == 0) {
|
||||
if (message.packet_id_ == 0) {
|
||||
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s"), message.id_, content->topic.c_str(), content->payload.c_str());
|
||||
} else {
|
||||
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d)"),
|
||||
message.id_,
|
||||
content->topic.c_str(),
|
||||
content->payload.c_str(),
|
||||
message.packet_id_);
|
||||
}
|
||||
} else {
|
||||
shell.printfln(F(" [%02d] (Pub) topic=%s payload=%s (pid %d, retry #%d)"),
|
||||
message.id_,
|
||||
content->topic.c_str(),
|
||||
content->payload.c_str(),
|
||||
message.packet_id_,
|
||||
message.retry_count_);
|
||||
}
|
||||
} else {
|
||||
// Subscribe messages
|
||||
shell.printfln(F(" [%02d] (Sub) topic=%s"), message.id_, content->topic.c_str());
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// simulate receiving a MQTT message, only for testing
|
||||
void Mqtt::incoming(char * topic, char * payload) {
|
||||
on_message(topic, payload, strlen(payload));
|
||||
}
|
||||
|
||||
// received MQTT message
|
||||
void Mqtt::on_message(char * topic, char * payload, size_t len) {
|
||||
if (len == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// convert payload to a null-terminated char string
|
||||
char message[len + 2];
|
||||
strlcpy(message, payload, len + 1);
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Received %s => %s (length %d)"), topic, message, len);
|
||||
#endif
|
||||
|
||||
// strip out everything until the last /
|
||||
char * topic_magnitude = strrchr(topic, '/');
|
||||
if (topic_magnitude != nullptr) {
|
||||
topic = topic_magnitude + 1;
|
||||
}
|
||||
|
||||
// Send message event to custom service
|
||||
// this will pick the first topic that matches, so for multiple devices of the same type it's gonna fail
|
||||
for (const auto & mf : mqtt_functions_) {
|
||||
if (strcmp(topic, mf.topic_.c_str()) == 0) {
|
||||
(mf.mqtt_function_)(message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// if we got here we didn't find a topic match
|
||||
LOG_DEBUG(F("No responding handler found for topic %s"), topic);
|
||||
}
|
||||
|
||||
// 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_; })
|
||||
== 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());
|
||||
}
|
||||
}
|
||||
shell.println();
|
||||
}
|
||||
|
||||
// called when an MQTT Publish ACK is received
|
||||
// its a poor man's QOS we assume the ACK represents the last Publish sent
|
||||
// check if ACK matches the last Publish we sent, if not report an error. Only if qos is 1 or 2
|
||||
// and always remove from queue
|
||||
void Mqtt::on_publish(uint16_t packetId) {
|
||||
// find the MQTT message in the queue and remove it
|
||||
if (mqtt_messages_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto mqtt_message = mqtt_messages_.front();
|
||||
|
||||
// if the last published failed, don't bother checking it. wait for the next retry
|
||||
if (mqtt_message.packet_id_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mqtt_message.packet_id_ != packetId) {
|
||||
LOG_DEBUG(F("Mismatch, expecting PID %d, got %d"), mqtt_message.packet_id_, packetId);
|
||||
mqtt_publish_fails_++; // increment error count
|
||||
}
|
||||
|
||||
mqtt_messages_.pop_front(); // always remove from queue, regardless if there was a successful ACK
|
||||
}
|
||||
|
||||
// builds up a topic by prefixing the hostname
|
||||
char * Mqtt::make_topic(char * result, const std::string & topic) {
|
||||
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(AsyncMqttClient * mqttClient) {
|
||||
mqttClient_ = mqttClient; // copy over from esp8266-react's MQTT service
|
||||
|
||||
// get the hostname, which we'll use to prefix to all topics
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { hostname_ = wifiSettings.hostname.c_str(); });
|
||||
|
||||
// fetch MQTT settings
|
||||
// EMSESP::esp8266React.getMqttSettingsService()->read([&](MqttSettings & mqttSettings) { mqtt_enabled_ = mqttSettings.enabled; });
|
||||
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
publish_time_ = settings.publish_time * 1000; // convert to milliseconds
|
||||
mqtt_qos_ = settings.mqtt_qos;
|
||||
});
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
mqttClient_->setWill(make_topic(will_topic_, "status"), 1, true, "offline"); // with qos 1, retain true
|
||||
#endif
|
||||
}
|
||||
|
||||
void Mqtt::set_publish_time(uint16_t publish_time) {
|
||||
publish_time_ = publish_time * 1000; // convert to milliseconds
|
||||
}
|
||||
|
||||
void Mqtt::set_qos(uint8_t mqtt_qos) {
|
||||
mqtt_qos_ = mqtt_qos;
|
||||
}
|
||||
|
||||
// MQTT onConnect - when a connect is established
|
||||
void Mqtt::on_connect() {
|
||||
// send info topic appended with the version information as JSON
|
||||
StaticJsonDocument<90> doc;
|
||||
doc["event"] = "start";
|
||||
doc["version"] = EMSESP_APP_VERSION;
|
||||
doc["ip"] = WiFi.localIP().toString();
|
||||
publish("info", doc, false); // send with retain off
|
||||
|
||||
publish("status", "online", true); // say we're alive to the Last Will topic, with retain on
|
||||
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
|
||||
if (topic.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto message = std::make_shared<MqttMessage>(Operation::PUBLISH, 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));
|
||||
}
|
||||
|
||||
// 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);
|
||||
LOG_DEBUG(F("Adding a subscription for %s"), topic.c_str());
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (mqtt_messages_.size() >= maximum_mqtt_messages_) {
|
||||
mqtt_messages_.pop_front();
|
||||
}
|
||||
|
||||
mqtt_messages_.emplace_back(mqtt_message_id_++, std::move(message));
|
||||
}
|
||||
|
||||
// MQTT Publish, using a specific retain flag
|
||||
void Mqtt::publish(const std::string & topic, const std::string & payload, bool retain) {
|
||||
queue_publish_message(topic, payload, retain);
|
||||
}
|
||||
|
||||
void Mqtt::publish(const std::string & topic, const JsonDocument & payload, bool retain) {
|
||||
// convert json to string
|
||||
std::string payload_text;
|
||||
serializeJson(payload, payload_text);
|
||||
queue_publish_message(topic, payload_text, retain);
|
||||
}
|
||||
|
||||
// for booleans, which get converted to string values 1 and 0
|
||||
void Mqtt::publish(const std::string & topic, const bool value) {
|
||||
queue_publish_message(topic, value ? "1" : "0", false);
|
||||
}
|
||||
|
||||
// no payload
|
||||
void Mqtt::publish(const std::string & topic) {
|
||||
queue_publish_message(topic, "", false);
|
||||
}
|
||||
|
||||
// publish all queued messages to MQTT
|
||||
void Mqtt::process_all_queue() {
|
||||
while (!mqtt_messages_.empty()) {
|
||||
process_queue();
|
||||
}
|
||||
}
|
||||
|
||||
// take top from queue and try and publish it
|
||||
// assumes there is an MQTT connection
|
||||
void Mqtt::process_queue() {
|
||||
if (mqtt_messages_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// fetch first from queue and create the full topic name
|
||||
auto mqtt_message = mqtt_messages_.front();
|
||||
auto message = mqtt_message.content_;
|
||||
|
||||
// append the hostname to the topic, unless we're doing native HA which has a different format
|
||||
// if the topic starts with "homeassistant" we leave it untouched, otherwise append host
|
||||
char full_topic[MQTT_TOPIC_MAX_SIZE];
|
||||
if (strncmp(message->topic.c_str(), "homeassistant/", 13) == 0) {
|
||||
strcpy(full_topic, message->topic.c_str());
|
||||
} else {
|
||||
make_topic(full_topic, message->topic);
|
||||
}
|
||||
|
||||
// if we're subscribing...
|
||||
if (message->operation == Operation::SUBSCRIBE) {
|
||||
LOG_DEBUG(F("Subscribing to topic: %s"), full_topic);
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint16_t packet_id = mqttClient_->subscribe(full_topic, mqtt_qos_);
|
||||
#else
|
||||
uint16_t packet_id = 1;
|
||||
#endif
|
||||
if (!packet_id) {
|
||||
LOG_DEBUG(F("Error subscribing to %s, error %d"), full_topic, packet_id);
|
||||
}
|
||||
|
||||
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if this has already been published and we're waiting for an ACK, don't publish again
|
||||
// it will have a real packet ID
|
||||
if (mqtt_message.packet_id_ > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// else try and publish it
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint16_t packet_id = mqttClient_->publish(full_topic, mqtt_qos_, message->retain, message->payload.c_str(), message->payload.size(), false, mqtt_message.id_);
|
||||
#else
|
||||
uint16_t packet_id = 1;
|
||||
#endif
|
||||
LOG_DEBUG(F("Publishing topic %s (#%02d, attempt #%d, pid %d)"), full_topic, mqtt_message.id_, mqtt_message.retry_count_ + 1, packet_id);
|
||||
|
||||
if (packet_id == 0) {
|
||||
// it failed. if we retried n times, give up. remove from queue
|
||||
if (mqtt_message.retry_count_ == (MQTT_PUBLISH_MAX_RETRY - 1)) {
|
||||
LOG_ERROR(F("Failed to publish to %s after %d attempts"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
mqtt_publish_fails_++; // increment failure counter
|
||||
mqtt_messages_.pop_front(); // delete
|
||||
return;
|
||||
} else {
|
||||
mqtt_messages_.front().retry_count_++;
|
||||
LOG_DEBUG(F("Failed to publish to %s. Trying again, #%d"), full_topic, mqtt_message.retry_count_ + 1);
|
||||
return; // leave on queue for next time so it gets republished
|
||||
}
|
||||
}
|
||||
|
||||
// if we have ACK set with QOS 1 or 2, leave on queue and let the ACK process remove it
|
||||
// but add the packet_id so we can check it later
|
||||
if (mqtt_qos_ != 0) {
|
||||
mqtt_messages_.front().packet_id_ = packet_id;
|
||||
// LOG_DEBUG(F("Setting packetID for ACK to %d"), packet_id);
|
||||
return;
|
||||
}
|
||||
|
||||
mqtt_messages_.pop_front(); // remove the message from the queue
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
172
src/mqtt.h
Normal file
172
src/mqtt.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_MQTT_H_
|
||||
#define EMSESP_MQTT_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <functional>
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <AsyncMqttClient.h>
|
||||
#endif
|
||||
|
||||
#include "helpers.h"
|
||||
#include "system.h"
|
||||
#include "console.h"
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
#define EMSESP_MAX_JSON_SIZE_SMALL 200 // for smaller json docs
|
||||
#define EMSESP_MAX_JSON_SIZE_MEDIUM 800 // for smaller json docs from ems devices
|
||||
#define EMSESP_MAX_JSON_SIZE_LARGE 1500 // for large json docs from ems devices, like boiler or thermostat data
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
using mqtt_function_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() = default;
|
||||
|
||||
const uint8_t operation;
|
||||
const std::string topic;
|
||||
const std::string payload;
|
||||
const bool retain;
|
||||
};
|
||||
|
||||
class Mqtt {
|
||||
public:
|
||||
void loop();
|
||||
|
||||
void start(AsyncMqttClient * mqttClient);
|
||||
|
||||
void set_publish_time(uint16_t publish_time);
|
||||
void set_qos(uint8_t mqtt_qos);
|
||||
|
||||
enum Operation { PUBLISH, SUBSCRIBE };
|
||||
|
||||
static constexpr uint8_t MQTT_TOPIC_MAX_SIZE = 60; // include host and base etc
|
||||
|
||||
// are static to be accessed from EMS devices
|
||||
static void subscribe(const uint8_t device_id, const std::string & topic, mqtt_function_p cb);
|
||||
static void subscribe(const std::string & topic, mqtt_function_p cb);
|
||||
|
||||
static void publish(const std::string & topic, const std::string & payload, bool retain = false);
|
||||
static void publish(const std::string & topic, const JsonDocument & payload, bool retain = false);
|
||||
static void publish(const std::string & topic, const bool value);
|
||||
static void publish(const std::string & topic);
|
||||
|
||||
static void show_topic_handlers(uuid::console::Shell & shell, const uint8_t device_id);
|
||||
|
||||
static void show_mqtt(uuid::console::Shell & shell);
|
||||
|
||||
static void on_connect();
|
||||
|
||||
void disconnect() {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return;
|
||||
#else
|
||||
mqttClient_->disconnect();
|
||||
#endif
|
||||
}
|
||||
|
||||
void incoming(char * topic, char * payload); // for testing
|
||||
|
||||
static bool connected() {
|
||||
#ifdef EMSESP_STANDALONE
|
||||
return true;
|
||||
#else
|
||||
return mqttClient_->connected();
|
||||
#endif
|
||||
}
|
||||
|
||||
static uint32_t publish_fails() {
|
||||
return mqtt_publish_fails_;
|
||||
}
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
class QueuedMqttMessage {
|
||||
public:
|
||||
QueuedMqttMessage(uint16_t id, std::shared_ptr<MqttMessage> && content);
|
||||
~QueuedMqttMessage() = default;
|
||||
|
||||
const uint16_t id_;
|
||||
const std::shared_ptr<const MqttMessage> content_;
|
||||
uint8_t retry_count_;
|
||||
uint16_t packet_id_;
|
||||
};
|
||||
static std::deque<QueuedMqttMessage> mqtt_messages_;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
static AsyncMqttClient * mqttClient_;
|
||||
#endif
|
||||
|
||||
static constexpr size_t MAX_MQTT_MESSAGES = 50;
|
||||
static size_t maximum_mqtt_messages_;
|
||||
static uint16_t mqtt_message_id_;
|
||||
static bool mqtt_retain_;
|
||||
|
||||
static constexpr uint8_t MQTT_QUEUE_MAX_SIZE = 50;
|
||||
static constexpr uint32_t MQTT_PUBLISH_WAIT = 250; // delay between sending publishes, to account for large payloads
|
||||
static constexpr uint8_t MQTT_PUBLISH_MAX_RETRY = 3; // max retries for giving up on publishing
|
||||
|
||||
static void queue_publish_message(const std::string & topic, const std::string & payload, const bool retain);
|
||||
static void queue_subscribe_message(const std::string & topic);
|
||||
|
||||
void on_publish(uint16_t packetId);
|
||||
void on_message(char * topic, char * payload, size_t len);
|
||||
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 {
|
||||
public:
|
||||
MQTTFunction(uint8_t device_id, const std::string && topic, mqtt_function_p mqtt_function);
|
||||
~MQTTFunction() = default;
|
||||
|
||||
uint8_t device_id_; // which device ID owns this
|
||||
std::string topic_;
|
||||
mqtt_function_p mqtt_function_;
|
||||
};
|
||||
static std::vector<MQTTFunction> mqtt_functions_; // list of mqtt 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_;
|
||||
}; // namespace emsesp
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
102
src/my_config.h
102
src/my_config.h
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* my_config.h
|
||||
*
|
||||
* All configurations and customization's go here
|
||||
*
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ems.h"
|
||||
|
||||
// TOPICS with *_CMD_* are used for receiving commands from an MQTT Broker
|
||||
// EMS-ESP will subscribe to these topics
|
||||
|
||||
#define TOPIC_GENERIC_CMD "generic_cmd" // for receiving generic system commands via MQTT
|
||||
|
||||
// MQTT for thermostat
|
||||
// these topics can be suffixed with a Heating Circuit number, e.g. thermostat_cmd_temp1 and thermostat_data1
|
||||
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
|
||||
|
||||
#define TOPIC_THERMOSTAT_CMD "thermostat_cmd" // for receiving thermostat commands via MQTT
|
||||
#define TOPIC_THERMOSTAT_CMD_TEMP_HA "thermostat_cmd_temp" // temp changes via MQTT, for HA climate component
|
||||
#define TOPIC_THERMOSTAT_CMD_MODE_HA "thermostat_cmd_mode" // mode changes via MQTT, for HA climate component
|
||||
#define TOPIC_THERMOSTAT_CMD_TEMP "temp" // temp changes via MQTT
|
||||
#define TOPIC_THERMOSTAT_CMD_MODE "mode" // mode changes via MQTT
|
||||
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "daytemp" // day temp (RC35 specific)
|
||||
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "nighttemp" // night temp (RC35 specific)
|
||||
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "holidayttemp" // holiday temp (RC35 specific)
|
||||
#define TOPIC_THERMOSTAT_CMD_NOFROSTTEMP "nofrosttemp" // frost temp (Junkers specific)
|
||||
#define TOPIC_THERMOSTAT_CMD_ECOTEMP "ecotemp" // eco temp (Junkers specific)
|
||||
#define TOPIC_THERMOSTAT_CMD_HEATTEMP "heattemp" // heat temp (Junkers specific)
|
||||
|
||||
#define THERMOSTAT_CURRTEMP "currtemp" // current temperature
|
||||
#define THERMOSTAT_SELTEMP "seltemp" // selected temperature
|
||||
#define THERMOSTAT_HC "hc" // which heating circuit number
|
||||
#define THERMOSTAT_MODE "mode" // mode
|
||||
#define THERMOSTAT_MODETYPE "modetype" // mode type
|
||||
#define THERMOSTAT_DAYTEMP "daytemp" // RC35 specific
|
||||
#define THERMOSTAT_NIGHTTEMP "nighttemp" // RC35 specific
|
||||
#define THERMOSTAT_HOLIDAYTEMP "holidayttemp" // RC35 specific
|
||||
#define THERMOSTAT_HEATINGTYPE "heatingtype" // RC35 specific (3=floorheating)
|
||||
#define THERMOSTAT_CIRCUITCALCTEMP "circuitcalctemp" // RC35 specific
|
||||
|
||||
// mixing module
|
||||
#define MIXING_HC "hc" // which heating circuit number
|
||||
#define MIXING_WWC "wwc" // which warm water circuit number
|
||||
|
||||
// MQTT for boiler
|
||||
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
|
||||
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
|
||||
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
|
||||
|
||||
#define TOPIC_BOILER_CMD "boiler_cmd" // for receiving boiler commands via MQTT
|
||||
#define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // change water on/off
|
||||
#define TOPIC_BOILER_CMD_WWONETIME "boiler_cmd_wwonetime" // warm warter one time loading
|
||||
#define TOPIC_BOILER_CMD_WWCIRCULATION "boiler_cmd_wwcirculation" // start warm warter circulation
|
||||
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // wwtemp changes via MQTT
|
||||
#define TOPIC_BOILER_CMD_COMFORT "comfort" // ww comfort setting via MQTT
|
||||
#define TOPIC_BOILER_CMD_FLOWTEMP "flowtemp" // flowtemp value via MQTT
|
||||
|
||||
// MQTT for settings
|
||||
#define TOPIC_SETTINGS_DATA "settings_data" // for sending settings values to MQTT
|
||||
#define TOPIC_SETTINGS_CMD "settings_cmd" // for receiving settings commands via MQTT
|
||||
#define TOPIC_SETTINGS_CMD_DISPLAY "display" // change display
|
||||
#define TOPIC_SETTINGS_CMD_LANGUAGE "language" // change language
|
||||
#define TOPIC_SETTINGS_CMD_BUILDING "building" // change building
|
||||
#define TOPIC_SETTINGS_CMD_MINEXTTEMP "minextTemp" // change min. ext. temp.
|
||||
|
||||
// MQTT for mixing device
|
||||
#define TOPIC_MIXING_DATA "mixing_data" // for sending mixing device values to MQTT
|
||||
|
||||
// MQTT for SM10/SM100/SM200 Solar Module
|
||||
#define TOPIC_SM_DATA "sm_data" // topic name
|
||||
#define SM_COLLECTORTEMP "collectortemp" // collector temp
|
||||
#define SM_BOTTOMTEMP "bottomtemp" // bottom temp1
|
||||
#define SM_BOTTOMTEMP2 "bottomtemp2" // bottom temp2
|
||||
#define SM_PUMPMODULATION "pumpmodulation" // pump modulation
|
||||
#define SM_PUMP "pump" // pump active
|
||||
#define SM_VALVESTATUS "valvestatus" // valve status
|
||||
#define SM_ENERGYLASTHOUR "energylasthour" // energy last hour
|
||||
#define SM_ENERGYTODAY "energytoday" // energy today
|
||||
#define SM_ENERGYTOTAL "energytotal" // energy total
|
||||
#define SM_PUMPWORKMIN "pumpWorkMin" // Total minutes
|
||||
|
||||
// MQTT for HP (HeatPump)
|
||||
#define TOPIC_HP_DATA "hp_data" // topic name
|
||||
#define HP_PUMPMODULATION "pumpmodulation" // pump modulation
|
||||
#define HP_PUMPSPEED "pumpspeed" // pump speed
|
||||
|
||||
// shower time
|
||||
#define TOPIC_SHOWER_DATA "shower_data" // for sending shower time results
|
||||
#define TOPIC_SHOWER_TIMER "timer" // toggle switch for enabling the shower logic
|
||||
#define TOPIC_SHOWER_ALERT "alert" // toggle switch for enabling the shower alarm logic
|
||||
#define TOPIC_SHOWER_COLDSHOT "coldshot" // used to trigger a coldshot from an MQTT command
|
||||
#define TOPIC_SHOWER_DURATION "duration" // duration of the last shower
|
||||
|
||||
// MQTT for External Sensors
|
||||
#define TOPIC_EXTERNAL_SENSORS "sensors" // topic for sending sensor values to MQTT
|
||||
#define PAYLOAD_EXTERNAL_SENSOR_NUM "sensor" // which sensor #
|
||||
#define PAYLOAD_EXTERNAL_SENSOR_ID "id"
|
||||
#define PAYLOAD_EXTERNAL_SENSOR_TEMP "temp"
|
||||
141
src/roomcontrol.cpp
Normal file
141
src/roomcontrol.cpp
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* 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 "roomcontrol.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
static uint32_t rc_time_ = 0;
|
||||
static int16_t remotetemp[4] = {EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET, EMS_VALUE_SHORT_NOTSET};
|
||||
|
||||
/**
|
||||
* set the temperature,
|
||||
*/
|
||||
void Roomctrl::set_remotetemp(const uint8_t hc, const int16_t temp) {
|
||||
if (hc > 3) {
|
||||
return;
|
||||
}
|
||||
remotetemp[hc] = temp;
|
||||
}
|
||||
|
||||
/**
|
||||
* if remote control is active send the temperature every minute
|
||||
*/
|
||||
void Roomctrl::send(const uint8_t addr) {
|
||||
uint8_t hc_ = addr - ADDR;
|
||||
// check address, reply only on addresses 0x18..0x1B
|
||||
if (hc_ > 3) {
|
||||
return;
|
||||
}
|
||||
// no reply if the temperature is not set
|
||||
if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) {
|
||||
return;
|
||||
}
|
||||
if (uuid::get_uptime() - rc_time_ > SEND_INTERVAL) { // send every minute
|
||||
rc_time_ = uuid::get_uptime(); // use EMS-ESP's millis() to prevent overhead
|
||||
temperature(addr, 0x00); // send to all
|
||||
} else {
|
||||
// acknowledge every poll, otherwise the master shows error A22-816
|
||||
EMSuart::send_poll(addr);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check if there is a message for the remote room controller
|
||||
*/
|
||||
void Roomctrl::check(const uint8_t addr, const uint8_t * data) {
|
||||
uint8_t hc_ = (addr & 0x7F) - ADDR;
|
||||
|
||||
// check address, reply only on addresses 0x18..0x1B
|
||||
if (hc_ > 3) {
|
||||
return;
|
||||
}
|
||||
// no reply if the temperature is not set
|
||||
if (remotetemp[hc_] == EMS_VALUE_SHORT_NOTSET) {
|
||||
return;
|
||||
}
|
||||
// reply to writes with write nack byte
|
||||
if (addr & 0x80) { // it's a write to us
|
||||
nack_write(); // we don't accept writes.
|
||||
return;
|
||||
}
|
||||
// for now we only reply to version and remote temperature
|
||||
if (data[2] == 0x02) {
|
||||
version(addr, data[0]);
|
||||
} else if (data[2] == 0xAF && data[3] == 0) {
|
||||
temperature(addr, data[0]);
|
||||
} else {
|
||||
unknown(addr, data[0], data[2], data[3]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send version info RC20 (Prod. 113, Ver. 02.01) or RC20RF (Prod. 93, Ver. 02.00)
|
||||
*/
|
||||
void Roomctrl::version(uint8_t addr, uint8_t dst) {
|
||||
uint8_t data[10];
|
||||
data[0] = addr;
|
||||
data[1] = dst;
|
||||
data[2] = 0x02;
|
||||
data[3] = 0;
|
||||
data[4] = 113; // set RC20 id 113, Ver 02.01
|
||||
data[5] = 0x02;
|
||||
data[6] = 0x01;
|
||||
data[7] = EMSbus::calculate_crc(data, 7); // apppend CRC
|
||||
EMSuart::transmit(data, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* unknown message id, we reply with empty message
|
||||
*/
|
||||
void Roomctrl::unknown(uint8_t addr, uint8_t dst, uint8_t type, uint8_t offset) {
|
||||
uint8_t data[10];
|
||||
data[0] = addr;
|
||||
data[1] = dst;
|
||||
data[2] = type;
|
||||
data[3] = offset;
|
||||
data[4] = EMSbus::calculate_crc(data, 4); // apppend CRC
|
||||
EMSuart::transmit(data, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* send the room temperature in message 0xAF
|
||||
*/
|
||||
void Roomctrl::temperature(uint8_t addr, uint8_t dst) {
|
||||
uint8_t data[10];
|
||||
uint8_t hc_ = addr - ADDR;
|
||||
data[0] = addr;
|
||||
data[1] = dst;
|
||||
data[2] = 0xAF;
|
||||
data[3] = 0;
|
||||
data[4] = (uint8_t)(remotetemp[hc_] >> 8);
|
||||
data[5] = (uint8_t)(remotetemp[hc_] & 0xFF);
|
||||
data[6] = 0;
|
||||
data[7] = EMSbus::calculate_crc(data, 7); // apppend CRC
|
||||
EMSuart::transmit(data, 8);
|
||||
}
|
||||
/**
|
||||
* send a nack if someone want to write to us.
|
||||
*/
|
||||
void Roomctrl::nack_write() {
|
||||
uint8_t data[1];
|
||||
data[0] = TxService::TX_WRITE_FAIL;
|
||||
EMSuart::transmit(data, 1);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
44
src/roomcontrol.h
Normal file
44
src/roomcontrol.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_ROOMCONTROL_H
|
||||
#define EMSESP_ROOMCONTROL_H
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Roomctrl {
|
||||
public:
|
||||
static void send(const uint8_t addr);
|
||||
static void check(const uint8_t addr, const uint8_t * data);
|
||||
static void set_remotetemp(const uint8_t hc, const int16_t temp);
|
||||
|
||||
private:
|
||||
static constexpr uint8_t ADDR = 0x18;
|
||||
static constexpr uint32_t SEND_INTERVAL = 60000; // 1 minute
|
||||
|
||||
static void version(uint8_t addr, uint8_t dst);
|
||||
static void unknown(uint8_t addr, uint8_t dst, uint8_t type, uint8_t offset);
|
||||
static void temperature(uint8_t addr, uint8_t dst);
|
||||
static void nack_write();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
301
src/sensors.cpp
Normal file
301
src/sensors.cpp
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// code written by nomis - https://github.com/nomis
|
||||
|
||||
#include "sensors.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "sensors")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Sensors::logger_{F_(logger_name), uuid::log::Facility::DAEMON};
|
||||
|
||||
void Sensors::start() {
|
||||
// copy over values from MQTT so we don't keep on quering the filesystem
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
mqtt_format_ = settings.mqtt_format; // single, nested or ha
|
||||
});
|
||||
|
||||
// if we're using HA MQTT Discovery, send out the config
|
||||
// currently we just do this for a single sensor (sensor1)
|
||||
if (mqtt_format_ == MQTT_format::HA) {
|
||||
// Mqtt::publish(topic); // empty payload, this remove any previous config sent to HA
|
||||
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_MEDIUM> doc;
|
||||
doc["dev_cla"] = "temperature";
|
||||
doc["name"] = "ems-esp-sensor1";
|
||||
doc["uniq_id"] = "ems-esp-sensor1";
|
||||
doc["~"] = "homeassistant/sensor/ems-esp/external";
|
||||
doc["stat_t"] = "~/state";
|
||||
doc["unit_of_meas"] = "°C";
|
||||
doc["val_tpl"] = "{{value_json.sensor1.temp}}";
|
||||
|
||||
Mqtt::publish("homeassistant/sensor/ems-esp/external/config", doc, true); // publish the config payload with retain flag
|
||||
}
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
bus_.begin(SENSOR_GPIO);
|
||||
#endif
|
||||
}
|
||||
|
||||
void Sensors::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint32_t time_now = uuid::get_uptime();
|
||||
|
||||
if (state_ == State::IDLE) {
|
||||
if (time_now - last_activity_ >= READ_INTERVAL_MS) {
|
||||
// LOG_DEBUG(F("Read sensor temperature")); // uncomment for debug
|
||||
if (bus_.reset()) {
|
||||
yield();
|
||||
bus_.skip();
|
||||
bus_.write(CMD_CONVERT_TEMP);
|
||||
|
||||
state_ = State::READING;
|
||||
} else {
|
||||
// no sensors found
|
||||
// LOG_ERROR(F("Bus reset failed")); // uncomment for debug
|
||||
devices_.clear(); // remove all know devices incase we have a disconnect
|
||||
}
|
||||
last_activity_ = time_now;
|
||||
}
|
||||
} else if (state_ == State::READING) {
|
||||
if (temperature_convert_complete() && (time_now - last_activity_ > CONVERSION_MS)) {
|
||||
// LOG_DEBUG(F("Scanning for sensors")); // uncomment for debug
|
||||
bus_.reset_search();
|
||||
found_.clear();
|
||||
|
||||
state_ = State::SCANNING;
|
||||
last_activity_ = time_now;
|
||||
} else if (time_now - last_activity_ > READ_TIMEOUT_MS) {
|
||||
LOG_ERROR(F("Sensor read timeout"));
|
||||
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
}
|
||||
} 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;
|
||||
} else {
|
||||
uint8_t addr[ADDR_LEN] = {0};
|
||||
|
||||
if (bus_.search(addr)) {
|
||||
bus_.depower();
|
||||
|
||||
if (bus_.crc8(addr, ADDR_LEN - 1) == addr[ADDR_LEN - 1]) {
|
||||
switch (addr[0]) {
|
||||
case TYPE_DS18B20:
|
||||
case TYPE_DS18S20:
|
||||
case TYPE_DS1822:
|
||||
case TYPE_DS1825:
|
||||
found_.emplace_back(addr);
|
||||
found_.back().temperature_c_ = get_temperature_c(addr);
|
||||
|
||||
/*
|
||||
// comment out for debugging
|
||||
char result[10];
|
||||
LOG_DEBUG(F("Temp of %s = %s"),
|
||||
found_.back().to_string().c_str(),
|
||||
Helpers::render_value(result, found_.back().temperature_c_, 2));
|
||||
*/
|
||||
break;
|
||||
|
||||
default:
|
||||
LOG_ERROR(F("Unknown sensor %s"), Device(addr).to_string().c_str());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(F("Invalid sensor %s"), Device(addr).to_string().c_str());
|
||||
}
|
||||
} else {
|
||||
bus_.depower();
|
||||
devices_ = std::move(found_);
|
||||
found_.clear();
|
||||
// LOG_DEBUG(F("Found %zu sensor(s). Adding them."), devices_.size()); // uncomment for debug
|
||||
state_ = State::IDLE;
|
||||
last_activity_ = time_now;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Sensors::temperature_convert_complete() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
return bus_.read_bit() == 1;
|
||||
#else
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
float Sensors::get_temperature_c(const uint8_t addr[]) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (!bus_.reset()) {
|
||||
LOG_ERROR(F("Bus reset failed before reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
yield();
|
||||
uint8_t scratchpad[SCRATCHPAD_LEN] = {0};
|
||||
bus_.select(addr);
|
||||
bus_.write(CMD_READ_SCRATCHPAD);
|
||||
bus_.read_bytes(scratchpad, SCRATCHPAD_LEN);
|
||||
yield();
|
||||
if (!bus_.reset()) {
|
||||
LOG_ERROR(F("Bus reset failed after reading scratchpad from %s"), Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
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],
|
||||
scratchpad[1],
|
||||
scratchpad[2],
|
||||
scratchpad[3],
|
||||
scratchpad[4],
|
||||
scratchpad[5],
|
||||
scratchpad[6],
|
||||
scratchpad[7],
|
||||
scratchpad[8],
|
||||
Device(addr).to_string().c_str());
|
||||
return NAN;
|
||||
}
|
||||
|
||||
int16_t raw_value = ((int16_t)scratchpad[SCRATCHPAD_TEMP_MSB] << 8) | scratchpad[SCRATCHPAD_TEMP_LSB];
|
||||
|
||||
// Adjust based on device resolution
|
||||
int resolution = 9 + ((scratchpad[SCRATCHPAD_CONFIG] >> 5) & 0x3);
|
||||
switch (resolution) {
|
||||
case 9:
|
||||
raw_value &= ~0x1;
|
||||
break;
|
||||
|
||||
case 10:
|
||||
raw_value &= ~0x3;
|
||||
break;
|
||||
|
||||
case 11:
|
||||
raw_value &= ~0x7;
|
||||
break;
|
||||
|
||||
case 12:
|
||||
break;
|
||||
}
|
||||
|
||||
return (float)raw_value / 16;
|
||||
#else
|
||||
return NAN;
|
||||
#endif
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
const std::vector<Sensors::Device> Sensors::devices() const {
|
||||
return devices_;
|
||||
}
|
||||
|
||||
Sensors::Device::Device(const uint8_t addr[])
|
||||
: id_(((uint64_t)addr[0] << 56) | ((uint64_t)addr[1] << 48) | ((uint64_t)addr[2] << 40) | ((uint64_t)addr[3] << 32) | ((uint64_t)addr[4] << 24)
|
||||
| ((uint64_t)addr[5] << 16) | ((uint64_t)addr[6] << 8) | (uint64_t)addr[7]) {
|
||||
}
|
||||
|
||||
uint64_t Sensors::Device::id() const {
|
||||
return id_;
|
||||
}
|
||||
|
||||
std::string Sensors::Device::to_string() const {
|
||||
std::string str(20, '\0');
|
||||
snprintf_P(&str[0],
|
||||
str.capacity() + 1,
|
||||
PSTR("%02X-%04X-%04X-%04X-%02X"),
|
||||
(unsigned int)(id_ >> 56) & 0xFF,
|
||||
(unsigned int)(id_ >> 40) & 0xFFFF,
|
||||
(unsigned int)(id_ >> 24) & 0xFFFF,
|
||||
(unsigned int)(id_ >> 8) & 0xFFFF,
|
||||
(unsigned int)(id_)&0xFF);
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
// send all dallas sensor values as a JSON package to MQTT
|
||||
// assumes there are devices
|
||||
void Sensors::publish_values() {
|
||||
uint8_t num_devices = devices_.size();
|
||||
|
||||
if (num_devices == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're not using nested JSON, send each sensor out seperately
|
||||
// sensor1, sensor2 etc...
|
||||
// e.g. sensor_1 = {"temp":20.2}
|
||||
if (mqtt_format_ == MQTT_format::SINGLE) {
|
||||
StaticJsonDocument<100> doc;
|
||||
for (const auto & device : devices_) {
|
||||
char s[5];
|
||||
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);
|
||||
Mqtt::publish(topic, doc);
|
||||
doc.clear(); // clear json doc so we can reuse the buffer again
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// group all sensors together - https://github.com/proddy/EMS-ESP/issues/327
|
||||
// This is used for both NESTED and HA modes
|
||||
// sensors = {
|
||||
// "sensor1":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
|
||||
// "sensor2":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
|
||||
// "sensor3":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"},
|
||||
// "sensor4":{"id":"28-EA41-9497-0E03-5F","temp":"23.25"}
|
||||
// }
|
||||
|
||||
// const size_t capacity = num_devices * JSON_OBJECT_SIZE(2) + JSON_OBJECT_SIZE(num_devices);
|
||||
DynamicJsonDocument doc(100 * num_devices);
|
||||
|
||||
uint8_t i = 1;
|
||||
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);
|
||||
} else {
|
||||
char sensorID[10]; // sensor{1-n}
|
||||
strlcpy(sensorID, "sensor", 10);
|
||||
char s[5];
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
if (mqtt_format_ == MQTT_format::HA) {
|
||||
Mqtt::publish("homeassistant/sensor/ems-esp/external/state", doc);
|
||||
} else {
|
||||
Mqtt::publish("sensors", doc);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
118
src/sensors.h
Normal file
118
src/sensors.h
Normal file
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// code written by nomis - https://github.com/nomis
|
||||
|
||||
#ifndef EMSESP_SENSORS_H
|
||||
#define EMSESP_SENSORS_H
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "mqtt.h"
|
||||
#include "console.h"
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <OneWire.h>
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Sensors {
|
||||
public:
|
||||
class Device {
|
||||
public:
|
||||
Device(const uint8_t addr[]);
|
||||
~Device() = default;
|
||||
|
||||
uint64_t id() const;
|
||||
std::string to_string() const;
|
||||
|
||||
float temperature_c_ = NAN;
|
||||
|
||||
private:
|
||||
const uint64_t id_;
|
||||
};
|
||||
|
||||
Sensors() = default;
|
||||
~Sensors() = default;
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
void publish_values();
|
||||
|
||||
const std::vector<Device> devices() const;
|
||||
|
||||
private:
|
||||
#if defined(ESP8266)
|
||||
static constexpr uint8_t SENSOR_GPIO = 14; // D5
|
||||
#elif defined(ESP32)
|
||||
#ifdef WEMOS_D1_32
|
||||
static constexpr uint8_t SENSOR_GPIO = 18; // Wemos D1-32 for compatibility D5
|
||||
#else
|
||||
static constexpr uint8_t SENSOR_GPIO = 14; // D5 is LED on wemos lolin D32, so use GPIO14
|
||||
#endif
|
||||
#endif
|
||||
|
||||
enum class State { IDLE, READING, SCANNING };
|
||||
|
||||
static constexpr size_t ADDR_LEN = 8;
|
||||
|
||||
static constexpr size_t SCRATCHPAD_LEN = 9;
|
||||
static constexpr size_t SCRATCHPAD_TEMP_MSB = 1;
|
||||
static constexpr size_t SCRATCHPAD_TEMP_LSB = 0;
|
||||
static constexpr size_t SCRATCHPAD_CONFIG = 4;
|
||||
|
||||
// dallas chips
|
||||
static constexpr uint8_t TYPE_DS18B20 = 0x28;
|
||||
static constexpr uint8_t TYPE_DS18S20 = 0x10;
|
||||
static constexpr uint8_t TYPE_DS1822 = 0x22;
|
||||
static constexpr uint8_t TYPE_DS1825 = 0x3B;
|
||||
|
||||
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 uint8_t CMD_CONVERT_TEMP = 0x44;
|
||||
static constexpr uint8_t CMD_READ_SCRATCHPAD = 0xBE;
|
||||
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
OneWire bus_;
|
||||
#endif
|
||||
|
||||
bool temperature_convert_complete();
|
||||
float get_temperature_c(const uint8_t addr[]);
|
||||
|
||||
uint32_t last_activity_ = uuid::get_uptime();
|
||||
uint32_t last_publish_ = uuid::get_uptime();
|
||||
State state_ = State::IDLE;
|
||||
std::vector<Device> found_;
|
||||
std::vector<Device> devices_;
|
||||
|
||||
uint8_t mqtt_format_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
137
src/shower.cpp
Normal file
137
src/shower.cpp
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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 "shower.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "shower")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger Shower::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
void Shower::start() {
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
shower_timer_ = settings.shower_timer;
|
||||
shower_alert_ = settings.shower_alert;
|
||||
});
|
||||
}
|
||||
|
||||
void Shower::loop() {
|
||||
if (!shower_timer_) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t time_now = uuid::get_uptime();
|
||||
|
||||
// if already in cold mode, ignore all this logic until we're out of the cold blast
|
||||
if (!doing_cold_shot_) {
|
||||
// is the hot water running?
|
||||
if (EMSESP::tap_water_active()) {
|
||||
// if heater was previously off, start the timer
|
||||
if (timer_start_ == 0) {
|
||||
// hot water just started...
|
||||
timer_start_ = time_now;
|
||||
timer_pause_ = 0; // remove any last pauses
|
||||
doing_cold_shot_ = false;
|
||||
duration_ = 0;
|
||||
shower_on_ = false;
|
||||
} else {
|
||||
// hot water has been on for a while
|
||||
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath
|
||||
if (!shower_on_ && (time_now - timer_start_) > SHOWER_MIN_DURATION) {
|
||||
shower_on_ = true;
|
||||
Mqtt::publish("shower_active", (bool)true);
|
||||
LOG_DEBUG(F("[Shower] hot water still running, starting shower timer"));
|
||||
}
|
||||
// check if the shower has been on too long
|
||||
else if ((((time_now - timer_start_) > SHOWER_MAX_DURATION) && !doing_cold_shot_) && shower_alert_) {
|
||||
shower_alert_start();
|
||||
}
|
||||
}
|
||||
} else { // hot water is off
|
||||
// if it just turned off, record the time as it could be a short pause
|
||||
if ((timer_start_) && (timer_pause_ == 0)) {
|
||||
timer_pause_ = time_now;
|
||||
}
|
||||
|
||||
// if shower has been off for longer than the wait time
|
||||
if ((timer_pause_) && ((time_now - timer_pause_) > SHOWER_PAUSE_TIME)) {
|
||||
// it is over the wait period, so assume that the shower has finished and calculate the total time and publish
|
||||
// because its unsigned long, can't have negative so check if length is less than OFFSET_TIME
|
||||
if ((timer_pause_ - timer_start_) > SHOWER_OFFSET_TIME) {
|
||||
duration_ = (timer_pause_ - timer_start_ - SHOWER_OFFSET_TIME);
|
||||
if (duration_ > SHOWER_MIN_DURATION) {
|
||||
Mqtt::publish("shower_active", (bool)false);
|
||||
LOG_DEBUG(F("[Shower] finished with duration %d"), duration_);
|
||||
publish_values();
|
||||
}
|
||||
}
|
||||
|
||||
// reset everything
|
||||
timer_start_ = 0;
|
||||
timer_pause_ = 0;
|
||||
shower_on_ = false;
|
||||
shower_alert_stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// turn back on the hot water for the shower
|
||||
void Shower::shower_alert_stop() {
|
||||
if (doing_cold_shot_) {
|
||||
LOG_DEBUG(F("Shower Alert stopped"));
|
||||
// Boiler::set_tapwarmwater_activated(true);
|
||||
doing_cold_shot_ = false;
|
||||
// showerColdShotStopTimer.detach(); // disable the timer
|
||||
}
|
||||
}
|
||||
|
||||
// turn off hot water to send a shot of cold
|
||||
void Shower::shower_alert_start() {
|
||||
if (shower_alert_) {
|
||||
LOG_DEBUG(F("Shower Alert started!"));
|
||||
// Boiler::set_tapwarmwater_activated(false);
|
||||
doing_cold_shot_ = true;
|
||||
// start the timer for n seconds which will reset the water back to hot
|
||||
// showerColdShotStopTimer.attach(SHOWER_COLDSHOT_DURATION, _showerColdShotStop);
|
||||
}
|
||||
}
|
||||
|
||||
// Publish shower data
|
||||
// returns true if added to MQTT queue went ok
|
||||
void Shower::publish_values() {
|
||||
StaticJsonDocument<90> doc;
|
||||
doc["shower_timer"] = shower_timer_ ? "1" : "0";
|
||||
doc["shower_alert"] = shower_alert_ ? "1" : "0";
|
||||
|
||||
// only publish shower duration if there is a value
|
||||
char s[50];
|
||||
if (duration_ > SHOWER_MIN_DURATION) {
|
||||
char buffer[16] = {0};
|
||||
strlcpy(s, Helpers::itoa(buffer, (uint8_t)((duration_ / (1000 * 60)) % 60), 10), 50);
|
||||
strlcat(s, " minutes and ", 50);
|
||||
strlcat(s, Helpers::itoa(buffer, (uint8_t)((duration_ / 1000) % 60), 10), 50);
|
||||
strlcat(s, " seconds", 50);
|
||||
doc["duration"] = s;
|
||||
}
|
||||
|
||||
Mqtt::publish("shower_data", doc);
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
83
src/shower.h
Normal file
83
src/shower.h
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_SHOWER_H
|
||||
#define EMSESP_SHOWER_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "console.h"
|
||||
#include "mqtt.h"
|
||||
#include "telegram.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Shower {
|
||||
public:
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
bool shower_alert() const {
|
||||
return shower_alert_;
|
||||
}
|
||||
|
||||
void shower_alert(const bool shower_alert) {
|
||||
shower_alert_ = shower_alert;
|
||||
}
|
||||
|
||||
bool shower_timer() const {
|
||||
return shower_timer_;
|
||||
}
|
||||
|
||||
void shower_timer(const bool shower_timer) {
|
||||
shower_timer_ = shower_timer;
|
||||
}
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
static constexpr uint32_t SHOWER_PAUSE_TIME = 15000; // in ms. 15 seconds, max time if water is switched off & on during a shower
|
||||
static constexpr uint32_t SHOWER_MIN_DURATION = 120000; // in ms. 2 minutes, before recognizing its a shower
|
||||
static constexpr uint32_t SHOWER_OFFSET_TIME = 5000; // in ms. 5 seconds grace time, to calibrate actual time under the shower
|
||||
static constexpr uint32_t SHOWER_COLDSHOT_DURATION = 10; // in seconds. 10 seconds for cold water before turning back hot water
|
||||
static constexpr uint32_t SHOWER_MAX_DURATION = 420000; // in ms. 7 minutes, before trigger a shot of cold water
|
||||
|
||||
void console_commands();
|
||||
|
||||
void publish_values();
|
||||
void shower_alert_start();
|
||||
void shower_alert_stop();
|
||||
|
||||
bool shower_timer_; // true if we want to report back on shower times
|
||||
bool shower_alert_; // true if we want the alert of cold water
|
||||
|
||||
bool shower_on_;
|
||||
uint32_t timer_start_; // ms
|
||||
uint32_t timer_pause_; // ms
|
||||
uint32_t duration_; // ms
|
||||
bool doing_cold_shot_; // true if we've just sent a jolt of cold water
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
583
src/system.cpp
Normal file
583
src/system.cpp
Normal file
@@ -0,0 +1,583 @@
|
||||
/*
|
||||
* 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 "system.h"
|
||||
#include "emsesp.h" // for send_raw_telegram() command
|
||||
|
||||
#include "version.h" // firmware version of EMS-ESP
|
||||
|
||||
MAKE_PSTR_WORD(passwd)
|
||||
MAKE_PSTR_WORD(hostname)
|
||||
MAKE_PSTR_WORD(wifi)
|
||||
MAKE_PSTR_WORD(ssid)
|
||||
MAKE_PSTR_WORD(heartbeat)
|
||||
|
||||
MAKE_PSTR(host_fmt, "Host = %s")
|
||||
MAKE_PSTR(hostname_fmt, "Hostname = %s")
|
||||
MAKE_PSTR(mark_interval_fmt, "Mark interval = %lus");
|
||||
MAKE_PSTR(wifi_ssid_fmt, "WiFi SSID = %s");
|
||||
MAKE_PSTR(wifi_password_fmt, "WiFi Password = %S")
|
||||
MAKE_PSTR(system_heartbeat_fmt, "Heartbeat = %s")
|
||||
|
||||
MAKE_PSTR(logger_name, "system")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
uuid::log::Logger System::logger_{F_(logger_name), uuid::log::Facility::KERN};
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uuid::syslog::SyslogService System::syslog_;
|
||||
#endif
|
||||
|
||||
uint32_t System::heap_start_ = 0;
|
||||
int System::reset_counter_;
|
||||
|
||||
// handle generic system related MQTT commands
|
||||
void System::mqtt_commands(const char * message) {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
DeserializationError error = deserializeJson(doc, message);
|
||||
if (error) {
|
||||
LOG_DEBUG(F("MQTT error: payload %s, error %s"), message, error.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
// restart EMS-ESP
|
||||
if (strcmp(message, "restart") == 0) {
|
||||
LOG_INFO(F("Restart command received"));
|
||||
restart();
|
||||
}
|
||||
|
||||
if (doc["send"] != nullptr) {
|
||||
const char * data = doc["send"];
|
||||
EMSESP::send_raw_telegram(data);
|
||||
LOG_INFO(F("Sending raw: %s"), data);
|
||||
}
|
||||
|
||||
#if defined(ESP8266)
|
||||
const uint8_t d0_ = 16;
|
||||
const uint8_t d1_ = 5;
|
||||
const uint8_t d2_ = 4;
|
||||
const uint8_t d3_ = 0;
|
||||
#elif defined(ESP32)
|
||||
const uint8_t d0_ = 26;
|
||||
const uint8_t d1_ = 22;
|
||||
const uint8_t d2_ = 21;
|
||||
const uint8_t d3_ = 17;
|
||||
#endif
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
if (doc["D0"] != nullptr) {
|
||||
const int8_t set = doc["D0"];
|
||||
pinMode(d0_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d0_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d0_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D0 set to %d"), set);
|
||||
}
|
||||
|
||||
if (doc["D1"] != nullptr) {
|
||||
const int8_t set = doc["D1"];
|
||||
pinMode(d1_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d1_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d1_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D1 set to %d"), set);
|
||||
}
|
||||
|
||||
if (doc["D2"] != nullptr) {
|
||||
const int8_t set = doc["D2"];
|
||||
pinMode(d2_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d2_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d2_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D2 set to %d"), set);
|
||||
}
|
||||
|
||||
if (doc["D3"] != nullptr) {
|
||||
const int8_t set = doc["D3"];
|
||||
pinMode(d3_, OUTPUT);
|
||||
if (set == 1) {
|
||||
digitalWrite(d3_, HIGH);
|
||||
} else if (set == 0) {
|
||||
digitalWrite(d3_, LOW);
|
||||
}
|
||||
LOG_INFO(F("Port D3 set to %d"), set);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
const char * command = doc["cmd"];
|
||||
if (command == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// send raw command
|
||||
if (strcmp(command, "send") == 0) {
|
||||
const char * data = doc["data"];
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
EMSESP::send_raw_telegram(data);
|
||||
LOG_INFO(F("Sending raw: %s"), data);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// restart EMS-ESP
|
||||
void System::restart() {
|
||||
LOG_NOTICE("Restarting system...");
|
||||
|
||||
Shell::loop_all();
|
||||
delay(1000); // wait a second
|
||||
#if defined(ESP8266)
|
||||
ESP.reset();
|
||||
#elif defined(ESP32)
|
||||
ESP.restart();
|
||||
#endif
|
||||
}
|
||||
|
||||
// return free heap mem as a percentage
|
||||
uint8_t System::free_mem() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
uint32_t free_memory = ESP.getFreeHeap();
|
||||
#else
|
||||
uint32_t free_memory = 1000;
|
||||
#endif
|
||||
|
||||
return (100 * free_memory / heap_start_);
|
||||
}
|
||||
|
||||
void System::syslog_init() {
|
||||
// fetch settings
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
syslog_level_ = settings.syslog_level;
|
||||
syslog_mark_interval_ = settings.syslog_mark_interval;
|
||||
syslog_host_ = settings.syslog_host;
|
||||
});
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
syslog_.start(); // syslog service
|
||||
|
||||
// configure syslog
|
||||
IPAddress addr;
|
||||
|
||||
if (!addr.fromString(syslog_host_.c_str())) {
|
||||
addr = (uint32_t)0;
|
||||
}
|
||||
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) { syslog_.hostname(wifiSettings.hostname.c_str()); });
|
||||
syslog_.log_level((uuid::log::Level)syslog_level_);
|
||||
syslog_.mark_interval(syslog_mark_interval_);
|
||||
syslog_.destination(addr);
|
||||
#endif
|
||||
}
|
||||
|
||||
void System::set_heartbeat(bool system_heartbeat) {
|
||||
system_heartbeat_ = system_heartbeat;
|
||||
}
|
||||
|
||||
// first call. Sets memory and starts up the UART Serial bridge
|
||||
void System::start() {
|
||||
// set the inital free mem
|
||||
if (heap_start_ == 0) {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
heap_start_ = ESP.getFreeHeap();
|
||||
#else
|
||||
heap_start_ = 2000;
|
||||
#endif
|
||||
}
|
||||
|
||||
// fetch settings
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
tx_mode_ = settings.tx_mode;
|
||||
system_heartbeat_ = settings.system_heartbeat;
|
||||
});
|
||||
|
||||
syslog_init();
|
||||
|
||||
#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
|
||||
|
||||
if (LED_GPIO) {
|
||||
pinMode(LED_GPIO, OUTPUT); // LED pin, 0 means disabled
|
||||
}
|
||||
|
||||
// register MQTT system commands
|
||||
Mqtt::subscribe("cmd", std::bind(&System::mqtt_commands, this, _1));
|
||||
|
||||
#ifndef EMSESP_FORCE_SERIAL
|
||||
EMSuart::start(tx_mode_); // start UART
|
||||
#endif
|
||||
}
|
||||
|
||||
// checks system health and handles LED flashing wizardry
|
||||
void System::loop() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
syslog_.loop();
|
||||
#endif
|
||||
led_monitor(); // check status and report back using the LED
|
||||
system_check(); // check system health
|
||||
|
||||
// send out heartbeat
|
||||
uint32_t currentMillis = uuid::get_uptime();
|
||||
if (!last_heartbeat_ || (currentMillis - last_heartbeat_ > SYSTEM_HEARTBEAT_INTERVAL)) {
|
||||
last_heartbeat_ = currentMillis;
|
||||
if (system_heartbeat_) {
|
||||
send_heartbeat();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// send periodic MQTT message with system information
|
||||
void System::send_heartbeat() {
|
||||
StaticJsonDocument<EMSESP_MAX_JSON_SIZE_SMALL> doc;
|
||||
|
||||
int rssid = wifi_quality();
|
||||
if (rssid != -1) {
|
||||
doc["rssid"] = rssid;
|
||||
}
|
||||
doc["uptime"] = uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3);
|
||||
doc["uptime_sec"] = uuid::get_uptime_sec();
|
||||
doc["freemem"] = free_mem();
|
||||
doc["mqttpublishfails"] = Mqtt::publish_fails();
|
||||
|
||||
Mqtt::publish("heartbeat", doc, false); // send to MQTT with retain off
|
||||
}
|
||||
|
||||
// sets rate of led flash
|
||||
void System::set_led_speed(uint32_t speed) {
|
||||
led_flash_speed_ = speed;
|
||||
led_monitor();
|
||||
}
|
||||
|
||||
// check health of system, done every few seconds
|
||||
void System::system_check() {
|
||||
static uint32_t last_system_check_ = 0;
|
||||
|
||||
if (!last_system_check_ || ((uint32_t)(uuid::get_uptime() - last_system_check_) >= SYSTEM_CHECK_FREQUENCY)) {
|
||||
last_system_check_ = uuid::get_uptime();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
set_led_speed(LED_WARNING_BLINK_FAST);
|
||||
system_healthy_ = false;
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// not healthy if bus not connected
|
||||
if (!EMSbus::bus_connected()) {
|
||||
system_healthy_ = false;
|
||||
set_led_speed(LED_WARNING_BLINK); // flash every 1/2 second from now on
|
||||
// LOG_ERROR(F("Error: No connection to the EMS bus"));
|
||||
} else {
|
||||
// if it was unhealthy but now we're better, make sure the LED is solid again cos we've been healed
|
||||
if (!system_healthy_) {
|
||||
system_healthy_ = true;
|
||||
if (LED_GPIO) {
|
||||
digitalWrite(LED_GPIO, LED_ON); // LED on, for ever
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// flashes the LED
|
||||
void System::led_monitor() {
|
||||
if (!LED_GPIO) {
|
||||
return;
|
||||
}
|
||||
|
||||
static uint32_t led_last_blink_ = 0;
|
||||
|
||||
if (!led_last_blink_ || (uint32_t)(uuid::get_uptime() - led_last_blink_) >= led_flash_speed_) {
|
||||
led_last_blink_ = uuid::get_uptime();
|
||||
|
||||
// if bus_not_connected or network not connected, start flashing
|
||||
if (!system_healthy_) {
|
||||
digitalWrite(LED_GPIO, !digitalRead(LED_GPIO));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the quality (Received Signal Strength Indicator) of the WiFi network as a %. Or -1 if disconnected.
|
||||
// High quality: 90% ~= -55dBm
|
||||
// Medium quality: 50% ~= -75dBm
|
||||
// Low quality: 30% ~= -85dBm
|
||||
// Unusable quality: 8% ~= -96dBm
|
||||
int8_t System::wifi_quality() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if (WiFi.status() != WL_CONNECTED) {
|
||||
return -1;
|
||||
}
|
||||
int dBm = WiFi.RSSI();
|
||||
#else
|
||||
int8_t dBm = -70;
|
||||
#endif
|
||||
if (dBm <= -100) {
|
||||
return 0;
|
||||
}
|
||||
if (dBm >= -50) {
|
||||
return 100;
|
||||
}
|
||||
return 2 * (dBm + 100);
|
||||
}
|
||||
|
||||
void System::show_system(uuid::console::Shell & shell) {
|
||||
shell.print(F("Uptime: "));
|
||||
shell.print(uuid::log::format_timestamp_ms(uuid::get_uptime_ms(), 3));
|
||||
shell.println();
|
||||
|
||||
#if defined(ESP8266)
|
||||
shell.printfln(F("Chip ID: 0x%08x"), ESP.getChipId());
|
||||
shell.printfln(F("SDK version: %s"), ESP.getSdkVersion());
|
||||
shell.printfln(F("Core version: %s"), ESP.getCoreVersion().c_str());
|
||||
shell.printfln(F("Full version: %s"), ESP.getFullVersion().c_str());
|
||||
shell.printfln(F("Boot version: %u"), ESP.getBootVersion());
|
||||
shell.printfln(F("Boot mode: %u"), ESP.getBootMode());
|
||||
shell.printfln(F("CPU frequency: %u MHz"), ESP.getCpuFreqMHz());
|
||||
shell.printfln(F("Flash chip: 0x%08X (%u bytes)"), ESP.getFlashChipId(), ESP.getFlashChipRealSize());
|
||||
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.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());
|
||||
shell.printfln(F("Heap fragmentation: %u%"), ESP.getHeapFragmentation());
|
||||
shell.printfln(F("Free continuations stack: %lu bytes"), (unsigned long)ESP.getFreeContStack());
|
||||
#elif defined(ESP32)
|
||||
shell.printfln(F("SDK version: %s"), ESP.getSdkVersion());
|
||||
shell.printfln(F("CPU frequency: %u MHz"), ESP.getCpuFreqMHz());
|
||||
shell.printfln(F("Sketch size: %u bytes (%u bytes free)"), ESP.getSketchSize(), ESP.getFreeSketchSpace());
|
||||
shell.printfln(F("Free heap: %lu bytes"), (unsigned long)ESP.getFreeHeap());
|
||||
shell.printfln(F("Free mem: %d %%"), free_mem());
|
||||
#endif
|
||||
|
||||
if (shell.has_flags(CommandFlags::ADMIN)) {
|
||||
shell.println();
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
|
||||
switch (WiFi.status()) {
|
||||
case WL_IDLE_STATUS:
|
||||
shell.printfln(F("WiFi: idle"));
|
||||
break;
|
||||
|
||||
case WL_NO_SSID_AVAIL:
|
||||
shell.printfln(F("WiFi: network not found"));
|
||||
break;
|
||||
|
||||
case WL_SCAN_COMPLETED:
|
||||
shell.printfln(F("WiFi: network scan complete"));
|
||||
break;
|
||||
|
||||
case WL_CONNECTED: {
|
||||
shell.printfln(F("WiFi: connected"));
|
||||
shell.println();
|
||||
|
||||
shell.printfln(F("SSID: %s"), WiFi.SSID().c_str());
|
||||
shell.printfln(F("BSSID: %s"), WiFi.BSSIDstr().c_str());
|
||||
shell.printfln(F("RSSI: %d dBm (%d %%)"), WiFi.RSSI(), wifi_quality());
|
||||
shell.println();
|
||||
|
||||
shell.printfln(F("MAC address: %s"), WiFi.macAddress().c_str());
|
||||
#if defined(ESP8266)
|
||||
shell.printfln(F("Hostname: %s"), WiFi.hostname().c_str());
|
||||
#elif defined(ESP32)
|
||||
shell.printfln(F("Hostname: %s"), WiFi.getHostname());
|
||||
#endif
|
||||
|
||||
shell.println();
|
||||
shell.printfln(F("IPv4 address: %s/%s"), uuid::printable_to_string(WiFi.localIP()).c_str(), uuid::printable_to_string(WiFi.subnetMask()).c_str());
|
||||
shell.printfln(F("IPv4 gateway: %s"), uuid::printable_to_string(WiFi.gatewayIP()).c_str());
|
||||
shell.printfln(F("IPv4 nameserver: %s"), uuid::printable_to_string(WiFi.dnsIP()).c_str());
|
||||
} break;
|
||||
|
||||
case WL_CONNECT_FAILED:
|
||||
shell.printfln(F("WiFi: connection failed"));
|
||||
break;
|
||||
|
||||
case WL_CONNECTION_LOST:
|
||||
shell.printfln(F("WiFi: connection lost"));
|
||||
break;
|
||||
|
||||
case WL_DISCONNECTED:
|
||||
shell.printfln(F("WiFi: disconnected"));
|
||||
break;
|
||||
|
||||
case WL_NO_SHIELD:
|
||||
default:
|
||||
shell.printfln(F("WiFi: unknown"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
// console commands to add
|
||||
void System::console_commands(Shell & shell, unsigned int context) {
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(restart)},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
restart();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(passwd)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
|
||||
if (completed) {
|
||||
shell.enter_password(F_(new_password_prompt2),
|
||||
[password1](Shell & shell, bool completed, const std::string & password2) {
|
||||
if (completed) {
|
||||
if (password1 == password2) {
|
||||
EMSESP::esp8266React.getSecuritySettingsService()->update(
|
||||
[&](SecuritySettings & securitySettings) {
|
||||
securitySettings.jwtSecret = password2.c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
shell.println(F("Admin password updated"));
|
||||
} else {
|
||||
shell.println(F("Passwords do not match"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show)},
|
||||
[=](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
show_system(shell); // has to be static
|
||||
shell.println();
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(set), F_(wifi), F_(hostname)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell __attribute__((unused)), const std::vector<std::string> & arguments) {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.hostname = arguments.front().c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(set), F_(wifi), F_(ssid)},
|
||||
flash_string_vector{F_(name_mandatory)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments) {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.ssid = arguments.front().c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
shell.println(F("WiFi SSID updated"));
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::ADMIN,
|
||||
flash_string_vector{F_(set), F_(wifi), F_(password)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
shell.enter_password(F_(new_password_prompt1), [](Shell & shell, bool completed, const std::string & password1) {
|
||||
if (completed) {
|
||||
shell.enter_password(F_(new_password_prompt2),
|
||||
[password1](Shell & shell, bool completed, const std::string & password2) {
|
||||
if (completed) {
|
||||
if (password1 == password2) {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->update(
|
||||
[&](WiFiSettings & wifiSettings) {
|
||||
wifiSettings.password = password2.c_str();
|
||||
return StateUpdateResult::CHANGED;
|
||||
},
|
||||
"local");
|
||||
shell.println(F("WiFi password updated"));
|
||||
} else {
|
||||
shell.println(F("Passwords do not match"));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(
|
||||
ShellContext::SYSTEM,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(set)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) {
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) {
|
||||
shell.printfln(F_(hostname_fmt), wifiSettings.hostname.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : wifiSettings.hostname.c_str());
|
||||
});
|
||||
|
||||
if (shell.has_flags(CommandFlags::ADMIN)) {
|
||||
shell.printfln("Wifi:");
|
||||
EMSESP::esp8266React.getWiFiSettingsService()->read([&](WiFiSettings & wifiSettings) {
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(wifi_ssid_fmt), wifiSettings.ssid.isEmpty() ? uuid::read_flash_string(F_(unset)).c_str() : wifiSettings.ssid.c_str());
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(wifi_password_fmt), wifiSettings.ssid.isEmpty() ? F_(unset) : F_(asterisks));
|
||||
});
|
||||
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) {
|
||||
shell.printfln(F("Syslog:"));
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(host_fmt), !settings.syslog_host.isEmpty() ? settings.syslog_host.c_str() : uuid::read_flash_string(F_(unset)).c_str());
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(log_level_fmt), uuid::log::format_level_uppercase(static_cast<uuid::log::Level>(settings.syslog_level)));
|
||||
shell.print(" ");
|
||||
shell.printfln(F_(mark_interval_fmt), settings.syslog_mark_interval);
|
||||
shell.println();
|
||||
shell.printfln(F_(system_heartbeat_fmt), settings.system_heartbeat ? F_(enabled) : F_(disabled));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
EMSESPShell::commands->add_command(ShellContext::SYSTEM,
|
||||
CommandFlags::USER,
|
||||
flash_string_vector{F_(show), F_(mqtt)},
|
||||
[](Shell & shell, const std::vector<std::string> & arguments __attribute__((unused))) { Mqtt::show_mqtt(shell); });
|
||||
|
||||
|
||||
// enter the context
|
||||
Console::enter_custom_context(shell, context);
|
||||
}
|
||||
|
||||
|
||||
} // namespace emsesp
|
||||
107
src/system.h
Normal file
107
src/system.h
Normal file
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_SYSTEM_H_
|
||||
#define EMSESP_SYSTEM_H_
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ArduinoJson.h>
|
||||
|
||||
#include "helpers.h"
|
||||
#include "console.h"
|
||||
#include "mqtt.h"
|
||||
#include "telegram.h"
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
#include <uuid/syslog.h>
|
||||
#endif
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
using uuid::console::Shell;
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class System {
|
||||
public:
|
||||
void start();
|
||||
void loop();
|
||||
|
||||
static void restart();
|
||||
static uint8_t free_mem();
|
||||
void syslog_init();
|
||||
void set_heartbeat(bool system_heartbeat);
|
||||
static void console_commands(Shell & shell, unsigned int context);
|
||||
|
||||
private:
|
||||
static uuid::log::Logger logger_;
|
||||
|
||||
#ifndef EMSESP_STANDALONE
|
||||
static uuid::syslog::SyslogService syslog_;
|
||||
#endif
|
||||
|
||||
static constexpr uint32_t SYSTEM_CHECK_FREQUENCY = 10000; // check every 10 seconds
|
||||
static constexpr uint32_t LED_WARNING_BLINK = 1000; // pulse to show no connection, 1 sec
|
||||
static constexpr uint32_t LED_WARNING_BLINK_FAST = 100; // flash quickly for boot up sequence
|
||||
static constexpr uint32_t SYSTEM_HEARTBEAT_INTERVAL = 60000; // in milliseconds, how often the MQTT heartbeat is sent (1 min)
|
||||
|
||||
// internal LED
|
||||
#ifndef EMSESP_NO_LED
|
||||
#if defined(ESP8266)
|
||||
static constexpr uint8_t LED_GPIO = 2;
|
||||
static constexpr uint8_t LED_ON = LOW;
|
||||
#elif defined(ESP32)
|
||||
#ifdef WEMOS_D1_32
|
||||
static constexpr uint8_t LED_GPIO = 2; // on Wemos D1-32
|
||||
static constexpr uint8_t LED_ON = HIGH;
|
||||
#else
|
||||
static constexpr uint8_t LED_GPIO = 5;
|
||||
static constexpr uint8_t LED_ON = LOW;
|
||||
#endif
|
||||
#endif
|
||||
#else
|
||||
static constexpr uint8_t LED_GPIO = 0; // no LED
|
||||
static constexpr uint8_t LED_ON = 0;
|
||||
#endif
|
||||
|
||||
void led_monitor();
|
||||
void set_led_speed(uint32_t speed);
|
||||
void mqtt_commands(const char * message);
|
||||
void send_heartbeat();
|
||||
void system_check();
|
||||
|
||||
static void show_system(uuid::console::Shell & shell);
|
||||
static int8_t wifi_quality();
|
||||
|
||||
bool system_healthy_ = false;
|
||||
uint32_t led_flash_speed_ = LED_WARNING_BLINK_FAST; // default boot flashes quickly
|
||||
static uint32_t heap_start_;
|
||||
static int reset_counter_;
|
||||
uint32_t last_heartbeat_ = 0;
|
||||
|
||||
// settings
|
||||
uint8_t tx_mode_;
|
||||
bool system_heartbeat_;
|
||||
uint8_t syslog_level_;
|
||||
uint32_t syslog_mark_interval_;
|
||||
String syslog_host_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
614
src/telegram.cpp
Normal file
614
src/telegram.cpp
Normal file
@@ -0,0 +1,614 @@
|
||||
/*
|
||||
* 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 "telegram.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
MAKE_PSTR(logger_name, "telegram")
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
// CRC lookup table with poly 12 for faster checking
|
||||
const uint8_t ems_crc_table[] = {0x00, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E, 0x20, 0x22, 0x24, 0x26,
|
||||
0x28, 0x2A, 0x2C, 0x2E, 0x30, 0x32, 0x34, 0x36, 0x38, 0x3A, 0x3C, 0x3E, 0x40, 0x42, 0x44, 0x46, 0x48, 0x4A, 0x4C, 0x4E,
|
||||
0x50, 0x52, 0x54, 0x56, 0x58, 0x5A, 0x5C, 0x5E, 0x60, 0x62, 0x64, 0x66, 0x68, 0x6A, 0x6C, 0x6E, 0x70, 0x72, 0x74, 0x76,
|
||||
0x78, 0x7A, 0x7C, 0x7E, 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8E, 0x90, 0x92, 0x94, 0x96, 0x98, 0x9A, 0x9C, 0x9E,
|
||||
0xA0, 0xA2, 0xA4, 0xA6, 0xA8, 0xAA, 0xAC, 0xAE, 0xB0, 0xB2, 0xB4, 0xB6, 0xB8, 0xBA, 0xBC, 0xBE, 0xC0, 0xC2, 0xC4, 0xC6,
|
||||
0xC8, 0xCA, 0xCC, 0xCE, 0xD0, 0xD2, 0xD4, 0xD6, 0xD8, 0xDA, 0xDC, 0xDE, 0xE0, 0xE2, 0xE4, 0xE6, 0xE8, 0xEA, 0xEC, 0xEE,
|
||||
0xF0, 0xF2, 0xF4, 0xF6, 0xF8, 0xFA, 0xFC, 0xFE, 0x19, 0x1B, 0x1D, 0x1F, 0x11, 0x13, 0x15, 0x17, 0x09, 0x0B, 0x0D, 0x0F,
|
||||
0x01, 0x03, 0x05, 0x07, 0x39, 0x3B, 0x3D, 0x3F, 0x31, 0x33, 0x35, 0x37, 0x29, 0x2B, 0x2D, 0x2F, 0x21, 0x23, 0x25, 0x27,
|
||||
0x59, 0x5B, 0x5D, 0x5F, 0x51, 0x53, 0x55, 0x57, 0x49, 0x4B, 0x4D, 0x4F, 0x41, 0x43, 0x45, 0x47, 0x79, 0x7B, 0x7D, 0x7F,
|
||||
0x71, 0x73, 0x75, 0x77, 0x69, 0x6B, 0x6D, 0x6F, 0x61, 0x63, 0x65, 0x67, 0x99, 0x9B, 0x9D, 0x9F, 0x91, 0x93, 0x95, 0x97,
|
||||
0x89, 0x8B, 0x8D, 0x8F, 0x81, 0x83, 0x85, 0x87, 0xB9, 0xBB, 0xBD, 0xBF, 0xB1, 0xB3, 0xB5, 0xB7, 0xA9, 0xAB, 0xAD, 0xAF,
|
||||
0xA1, 0xA3, 0xA5, 0xA7, 0xD9, 0xDB, 0xDD, 0xDF, 0xD1, 0xD3, 0xD5, 0xD7, 0xC9, 0xCB, 0xCD, 0xCF, 0xC1, 0xC3, 0xC5, 0xC7,
|
||||
0xF9, 0xFB, 0xFD, 0xFF, 0xF1, 0xF3, 0xF5, 0xF7, 0xE9, 0xEB, 0xED, 0xEF, 0xE1, 0xE3, 0xE5, 0xE7};
|
||||
|
||||
uint32_t EMSbus::last_bus_activity_ = 0; // timestamp of last time a valid Rx came in
|
||||
bool EMSbus::bus_connected_ = false; // start assuming the bus hasn't been connected
|
||||
uint8_t EMSbus::ems_mask_ = EMS_MASK_UNSET; // unset so its triggered when booting, the its 0x00=buderus, 0x80=junker/ht3
|
||||
uint8_t EMSbus::ems_bus_id_ = EMSESP_DEFAULT_EMS_BUS_ID;
|
||||
uint8_t EMSbus::tx_waiting_ = Telegram::Operation::NONE;
|
||||
bool EMSbus::tx_active_ = false;
|
||||
|
||||
uuid::log::Logger EMSbus::logger_{F_(logger_name), uuid::log::Facility::CONSOLE};
|
||||
|
||||
// Calculates CRC checksum using lookup table for speed
|
||||
// length excludes the last byte (which mainly is the CRC)
|
||||
uint8_t EMSbus::calculate_crc(const uint8_t * data, const uint8_t length) {
|
||||
uint8_t i = 0;
|
||||
uint8_t crc = 0;
|
||||
while (i < length) {
|
||||
crc = ems_crc_table[crc];
|
||||
crc ^= data[i++];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
// creates a telegram object
|
||||
// stores header in separate member objects and the rest in the message_data block
|
||||
Telegram::Telegram(const uint8_t operation,
|
||||
const uint8_t src,
|
||||
const uint8_t dest,
|
||||
const uint16_t type_id,
|
||||
const uint8_t offset,
|
||||
const uint8_t * data,
|
||||
const uint8_t message_length)
|
||||
: operation(operation)
|
||||
, src(src)
|
||||
, dest(dest)
|
||||
, type_id(type_id)
|
||||
, offset(offset)
|
||||
, message_length(message_length) {
|
||||
// copy complete telegram data over, preventing buffer overflow
|
||||
for (uint8_t i = 0; ((i < message_length) && (i != EMS_MAX_TELEGRAM_MESSAGE_LENGTH - 1)); i++) {
|
||||
message_data[i] = data[i];
|
||||
}
|
||||
}
|
||||
|
||||
// returns telegram's message data bytes in hex
|
||||
std::string Telegram::to_string() const {
|
||||
if (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();
|
||||
if (this->operation == Telegram::Operation::TX_READ) {
|
||||
data[1] = this->dest | 0x80;
|
||||
data[4] = this->message_data[0];
|
||||
if (this->type_id > 0xFF) {
|
||||
data[2] = 0xFF;
|
||||
data[5] = (this->type_id >> 8) - 1;
|
||||
data[6] = this->type_id & 0xFF;
|
||||
length = 7;
|
||||
} else {
|
||||
data[2] = this->type_id;
|
||||
length = 5;
|
||||
}
|
||||
}
|
||||
if (this->operation == Telegram::Operation::TX_WRITE) {
|
||||
data[1] = this->dest;
|
||||
if (this->type_id > 0xFF) {
|
||||
data[2] = 0xFF;
|
||||
data[4] = (this->type_id >> 8) - 1;
|
||||
data[5] = this->type_id & 0xFF;
|
||||
length = 6;
|
||||
} else {
|
||||
data[2] = this->type_id;
|
||||
length = 4;
|
||||
}
|
||||
for (uint8_t i = 0; i < this->message_length; i++) {
|
||||
data[length++] = this->message_data[i];
|
||||
}
|
||||
}
|
||||
return Helpers::data_to_hex(data, length);
|
||||
}
|
||||
|
||||
// returns telegram's full telegram message in hex
|
||||
std::string Telegram::to_string(const uint8_t * telegram, uint8_t length) const {
|
||||
return Helpers::data_to_hex(telegram, length);
|
||||
}
|
||||
|
||||
RxService::QueuedRxTelegram::QueuedRxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram)
|
||||
: id_(id)
|
||||
, telegram_(std::move(telegram)) {
|
||||
}
|
||||
|
||||
// empty queue, don't process them
|
||||
void RxService::flush_rx_queue() {
|
||||
rx_telegrams_.clear();
|
||||
rx_telegram_id_ = 0;
|
||||
}
|
||||
|
||||
// Rx loop, run as many times as you can
|
||||
// processes all telegrams on the queue. Assumes there are valid (i.e. CRC checked)
|
||||
void RxService::loop() {
|
||||
while (!rx_telegrams_.empty()) {
|
||||
auto telegram = rx_telegrams_.front().telegram_;
|
||||
|
||||
(void)EMSESP::process_telegram(telegram); // further process the telegram
|
||||
|
||||
increment_telegram_count(); // increase count
|
||||
|
||||
rx_telegrams_.pop_front(); // remove it from the queue
|
||||
}
|
||||
}
|
||||
|
||||
// add a new rx telegram object
|
||||
// data is the whole telegram, assuming last byte holds the CRC
|
||||
// length includes the CRC
|
||||
// for EMS+ the type_id has the value + 256. We look for these type of telegrams with F7, F9 and FF in 3rd byte
|
||||
void RxService::add(uint8_t * data, uint8_t length) {
|
||||
if (length < 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
increment_telegram_error_count();
|
||||
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
|
||||
if (ems_mask() == EMS_MASK_UNSET) {
|
||||
ems_mask(data[0]);
|
||||
}
|
||||
|
||||
// src, dest and offset are always in fixed positions
|
||||
uint8_t src = data[0] & 0x7F; // strip MSB (HT3 adds it)
|
||||
uint8_t dest = data[1] & 0x7F; // strip MSB, don't care if its read or write for processing
|
||||
uint8_t offset = data[3]; // offset is always 4th byte
|
||||
|
||||
uint16_t type_id;
|
||||
uint8_t * message_data; // where the message block starts
|
||||
uint8_t message_length; // length of the message block, excluding CRC
|
||||
|
||||
// work out depending on the type, where the data message block starts and the message length
|
||||
// EMS 1 has type_id always in data[2], if it gets a ems+ inquiry it will reply with FF but short length
|
||||
// i.e. sending 0B A1 FF 00 01 D8 20 CRC to a MM10 Mixer (ems1.0), the reply is 21 0B FF 00 CRC
|
||||
// see: https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007
|
||||
if (data[2] < 0xF0 || length < 6) {
|
||||
// EMS 1.0
|
||||
type_id = data[2];
|
||||
message_data = data + 4;
|
||||
message_length = length - 5;
|
||||
} else {
|
||||
// EMS 2.0 / EMS+
|
||||
uint8_t shift = 0; // default when data[2] is 0xFF
|
||||
if (data[2] != 0xFF) {
|
||||
// its F9 or F7, re-calculate shift. If the 5th byte is not 0xFF then telegram is 1 byte longer
|
||||
shift = (data[4] != 0xFF) ? 2 : 1;
|
||||
}
|
||||
type_id = (data[4 + shift] << 8) + data[5 + shift] + 256;
|
||||
message_data = data + 6 + shift;
|
||||
message_length = length - 6 - shift;
|
||||
}
|
||||
|
||||
// if we don't have a type_id or empty data block, exit
|
||||
if ((type_id == 0) || (message_length == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if we're watching and "raw" print out actual telegram as bytes to the console
|
||||
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
|
||||
uint16_t trace_watch_id = EMSESP::watch_id();
|
||||
if ((trace_watch_id == WATCH_ID_NONE) || (src == trace_watch_id) || (dest == trace_watch_id) || (type_id == trace_watch_id)) {
|
||||
LOG_NOTICE(F("Rx: %s"), Helpers::data_to_hex(data, length).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] New Rx [#%d] telegram, message length %d"), rx_telegram_id_, message_length);
|
||||
#endif
|
||||
|
||||
// create the telegram
|
||||
auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, src, dest, type_id, offset, message_data, message_length);
|
||||
|
||||
// check if queue is full, if so remove top item to make space
|
||||
if (rx_telegrams_.size() >= MAX_RX_TELEGRAMS) {
|
||||
rx_telegrams_.pop_front();
|
||||
}
|
||||
|
||||
rx_telegrams_.emplace_back(rx_telegram_id_++, std::move(telegram)); // add to queue
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// Tx CODE starts here...
|
||||
//
|
||||
|
||||
TxService::QueuedTxTelegram::QueuedTxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram, bool retry)
|
||||
: id_(id)
|
||||
, telegram_(std::move(telegram))
|
||||
, retry_(retry) {
|
||||
}
|
||||
|
||||
// empty queue, don't process
|
||||
void TxService::flush_tx_queue() {
|
||||
tx_telegrams_.clear();
|
||||
tx_telegram_id_ = 0;
|
||||
}
|
||||
|
||||
// start and initialize Tx
|
||||
void TxService::start() {
|
||||
// grab the bus ID
|
||||
EMSESP::emsespSettingsService.read([&](EMSESPSettings & settings) { ems_bus_id(settings.ems_bus_id); });
|
||||
|
||||
// send first Tx request to bus master (boiler) for its registered devices
|
||||
// this will be added to the queue and sent during the first tx loop()
|
||||
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());
|
||||
}
|
||||
|
||||
// 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
|
||||
if (tx_telegrams_.empty()) {
|
||||
send_poll();
|
||||
return;
|
||||
}
|
||||
|
||||
// send next telegram in the queue (which is actually a list!)
|
||||
send_telegram(tx_telegrams_.front());
|
||||
|
||||
// remove the telegram from the queue
|
||||
tx_telegrams_.pop_front();
|
||||
}
|
||||
|
||||
// process a Tx telegram
|
||||
void TxService::send_telegram(const QueuedTxTelegram & tx_telegram) {
|
||||
static uint8_t telegram_raw[EMS_MAX_TELEGRAM_LENGTH];
|
||||
|
||||
// build the header
|
||||
auto telegram = tx_telegram.telegram_;
|
||||
|
||||
// src - set MSB if it's Junkers/HT3
|
||||
uint8_t src = telegram->src;
|
||||
if (ems_mask() != EMS_MASK_UNSET) {
|
||||
src ^= ems_mask();
|
||||
}
|
||||
telegram_raw[0] = src;
|
||||
|
||||
// dest - for READ the MSB must be set
|
||||
// fix the READ or WRITE depending on the operation
|
||||
uint8_t dest = telegram->dest;
|
||||
if (telegram->operation == Telegram::Operation::TX_READ) {
|
||||
dest |= 0x80; // read has 8th bit set for the destination
|
||||
}
|
||||
telegram_raw[1] = dest;
|
||||
|
||||
uint8_t message_p = 0; // this is the position in the telegram where we want to put our message data
|
||||
bool copy_data = true; // true if we want to copy over the data message block to the end of the telegram header
|
||||
|
||||
if (telegram->type_id > 0xFF) {
|
||||
// it's EMS 2.0/+
|
||||
telegram_raw[2] = 0xFF; // fixed value indicating an extended message
|
||||
telegram_raw[3] = telegram->offset;
|
||||
|
||||
// EMS+ has different format for read and write. See https://github.com/proddy/EMS-ESP/wiki/RC3xx-Thermostats
|
||||
if (telegram->operation == Telegram::Operation::TX_WRITE) {
|
||||
// WRITE
|
||||
telegram_raw[4] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
|
||||
telegram_raw[5] = telegram->type_id & 0xFF; // type, 2nd byte, low-byte
|
||||
message_p = 6;
|
||||
} else {
|
||||
// READ
|
||||
telegram_raw[4] = telegram->message_data[0]; // #bytes to return, which we assume is the only byte in the message block
|
||||
telegram_raw[5] = (telegram->type_id >> 8) - 1; // type, 1st byte, high-byte, subtract 0x100
|
||||
telegram_raw[6] = telegram->type_id & 0xFF; // type, 2nd byte, low-byte
|
||||
message_p = 7;
|
||||
copy_data = false; // there are no more data values after the type_id when reading on EMS+
|
||||
}
|
||||
} else {
|
||||
// EMS 1.0
|
||||
telegram_raw[2] = telegram->type_id;
|
||||
telegram_raw[3] = telegram->offset;
|
||||
message_p = 4;
|
||||
}
|
||||
|
||||
if (copy_data) {
|
||||
if (telegram->message_length > EMS_MAX_TELEGRAM_MESSAGE_LENGTH) {
|
||||
return; // too big
|
||||
}
|
||||
|
||||
// add the data to send to to the end of the header
|
||||
for (uint8_t i = 0; i < telegram->message_length; i++) {
|
||||
telegram_raw[message_p++] = telegram->message_data[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t length = message_p;
|
||||
|
||||
telegram_last_ = std::make_shared<Telegram>(*telegram); // make a copy of the telegram
|
||||
|
||||
telegram_raw[length] = calculate_crc(telegram_raw, length); // generate and append CRC to the end
|
||||
|
||||
length++; // add one since we want to now include the CRC
|
||||
|
||||
LOG_DEBUG(F("Sending %s Tx [#%d], telegram: %s"),
|
||||
(telegram->operation == Telegram::Operation::TX_WRITE) ? F("write") : F("read"),
|
||||
tx_telegram.id_,
|
||||
telegram->to_string(telegram_raw, length).c_str());
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
// if watching in 'raw' mode
|
||||
if (EMSESP::watch() == EMSESP::Watch::WATCH_RAW) {
|
||||
LOG_NOTICE(F("[DEBUG] Tx: %s"), Helpers::data_to_hex(telegram_raw, length).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// send the telegram to the UART Tx
|
||||
uint16_t status = EMSuart::transmit(telegram_raw, length);
|
||||
|
||||
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
|
||||
return;
|
||||
}
|
||||
|
||||
tx_waiting(telegram->operation); // tx now in a wait state
|
||||
}
|
||||
|
||||
// send an array of bytes as a telegram
|
||||
// we need to calculate the CRC and append it before sending
|
||||
// this function is fire-and-forget. there are no checks or post-send validations
|
||||
void TxService::send_telegram(const uint8_t * data, const uint8_t length) {
|
||||
uint8_t telegram_raw[EMS_MAX_TELEGRAM_LENGTH];
|
||||
|
||||
for (uint8_t i = 0; i < length; i++) {
|
||||
telegram_raw[i] = data[i];
|
||||
}
|
||||
telegram_raw[length] = calculate_crc(telegram_raw, length); // apppend CRC
|
||||
|
||||
tx_waiting(Telegram::Operation::NONE); // no post validation needed
|
||||
|
||||
// send the telegram to the UART Tx
|
||||
uint16_t status = EMSuart::transmit(telegram_raw, length);
|
||||
|
||||
if (status == EMS_TX_STATUS_ERR) {
|
||||
LOG_ERROR(F("Failed to transmit Tx via UART."));
|
||||
increment_telegram_fail_count(); // another Tx fail
|
||||
}
|
||||
}
|
||||
|
||||
// builds a Tx telegram and adds to queue
|
||||
// given some details like the destination, type, offset and message block
|
||||
void TxService::add(const uint8_t operation,
|
||||
const uint8_t dest,
|
||||
const uint16_t type_id,
|
||||
const uint8_t offset,
|
||||
uint8_t * message_data,
|
||||
const uint8_t message_length,
|
||||
const bool front) {
|
||||
auto telegram = std::make_shared<Telegram>(operation, ems_bus_id(), dest, type_id, offset, message_data, message_length);
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
|
||||
#endif
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
|
||||
tx_telegrams_.pop_front();
|
||||
}
|
||||
|
||||
if (front) {
|
||||
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false); // add to back of queue
|
||||
} else {
|
||||
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false); // add to back of queue
|
||||
}
|
||||
}
|
||||
|
||||
// builds a Tx telegram and adds to queue
|
||||
// this is used by the retry() function to put the last failed Tx back into the queue
|
||||
// format is EMS 1.0 (src, dest, type_id, offset, data)
|
||||
// length is the length of the whole telegram data, excluding the CRC
|
||||
void TxService::add(uint8_t operation, const uint8_t * data, const uint8_t length, const bool front) {
|
||||
// build header. src, dest and offset have fixed positions
|
||||
uint8_t src = data[0];
|
||||
uint8_t dest = data[1];
|
||||
uint8_t offset = data[3];
|
||||
|
||||
uint16_t type_id;
|
||||
const uint8_t * message_data; // where the message block starts
|
||||
uint8_t message_length; // length of the message block, excluding CRC
|
||||
|
||||
// work out depending on the type, where the data message block starts and the message length
|
||||
// same logic as in RxService::add(), but adjusted for no appended CRC
|
||||
if (data[2] < 0xF0) {
|
||||
// EMS 1.0
|
||||
type_id = data[2];
|
||||
message_data = data + 4;
|
||||
message_length = length - 4;
|
||||
} else {
|
||||
// EMS 2.0 / EMS+
|
||||
uint8_t shift = 0; // default when data[2] is 0xFF
|
||||
if (data[2] != 0xFF) {
|
||||
// its F9 or F7, re-calculate shift. If the 5th byte is not 0xFF then telegram is 1 byte longer
|
||||
shift = (data[4] != 0xFF) ? 2 : 1;
|
||||
}
|
||||
type_id = (data[4 + shift] << 8) + data[5 + shift] + 256;
|
||||
message_data = data + 6 + shift;
|
||||
message_length = length - 6 - shift;
|
||||
}
|
||||
|
||||
// if we don't have a type_id or empty data block, exit
|
||||
if ((type_id == 0) || (message_length == 0)) {
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] Tx telegram type %d failed, length %d"), type_id, message_length);
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (operation == Telegram::Operation::TX_RAW) {
|
||||
if (dest & 0x80) {
|
||||
operation = Telegram::Operation::TX_READ;
|
||||
} else {
|
||||
operation = Telegram::Operation::TX_WRITE;
|
||||
}
|
||||
}
|
||||
|
||||
auto telegram = std::make_shared<Telegram>(operation, src, dest, type_id, offset, message_data, message_length); // operation is TX_WRITE or TX_READ
|
||||
|
||||
// if the queue is full, make room but removing the last one
|
||||
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
|
||||
tx_telegrams_.pop_front();
|
||||
}
|
||||
|
||||
#ifdef EMSESP_DEBUG
|
||||
LOG_DEBUG(F("[DEBUG] New Tx [#%d] telegram, length %d"), tx_telegram_id_, message_length);
|
||||
#endif
|
||||
|
||||
if (front) {
|
||||
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram), false); // add to back of queue
|
||||
} else {
|
||||
tx_telegrams_.emplace_back(tx_telegram_id_++, std::move(telegram), false); // add to back of queue
|
||||
}
|
||||
}
|
||||
|
||||
// send a Tx telegram to request data from an EMS device
|
||||
void TxService::read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset) {
|
||||
LOG_DEBUG(F("Tx read request to device 0x%02X for type ID 0x%02X"), dest, type_id);
|
||||
|
||||
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
|
||||
add(Telegram::Operation::TX_READ, dest, type_id, offset, message_data, 1);
|
||||
}
|
||||
|
||||
// Send a raw telegram to the bus, telegram is a text string of hex values
|
||||
void TxService::send_raw(const char * telegram_data) {
|
||||
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
|
||||
char telegram[EMS_MAX_TELEGRAM_LENGTH * 3];
|
||||
for (uint8_t i = 0; i < strlen(telegram_data); i++) {
|
||||
telegram[i] = telegram_data[i];
|
||||
}
|
||||
telegram[strlen(telegram_data)] = '\0'; // make sure its terminated
|
||||
|
||||
uint8_t count = 0;
|
||||
char * p;
|
||||
char value[10] = {0};
|
||||
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
|
||||
// get first value, which should be the src
|
||||
if ((p = strtok(telegram, " ,"))) { // delimiter
|
||||
strlcpy(value, p, 10);
|
||||
data[0] = (uint8_t)strtol(value, 0, 16);
|
||||
}
|
||||
|
||||
// and iterate until end
|
||||
while (p != 0) {
|
||||
if ((p = strtok(nullptr, " ,"))) {
|
||||
strlcpy(value, p, 10);
|
||||
uint8_t val = (uint8_t)strtol(value, 0, 16);
|
||||
data[++count] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return; // nothing to send
|
||||
}
|
||||
|
||||
add(Telegram::Operation::TX_RAW, data, count + 1); // add to Tx queue
|
||||
}
|
||||
|
||||
// add last Tx to tx queue and increment count
|
||||
// returns retry count, or 0 if all done
|
||||
void TxService::retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length) {
|
||||
// have we reached the limit? if so, reset count and give up
|
||||
if (++retry_count_ > MAXIMUM_TX_RETRIES) {
|
||||
reset_retry_count(); // give up
|
||||
increment_telegram_fail_count(); // another Tx fail
|
||||
|
||||
LOG_ERROR(F("Last Tx %s operation failed after %d retries. Ignoring request."),
|
||||
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
|
||||
MAXIMUM_TX_RETRIES);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_DEBUG(F("[DEBUG] Last Tx %s operation failed. Retry #%d. sent message: %s, received: %s"),
|
||||
(operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
|
||||
retry_count_,
|
||||
telegram_last_->to_string().c_str(),
|
||||
Helpers::data_to_hex(data, length).c_str());
|
||||
|
||||
// add to the top of the queue
|
||||
if (tx_telegrams_.size() >= MAX_TX_TELEGRAMS) {
|
||||
tx_telegrams_.pop_back();
|
||||
}
|
||||
|
||||
tx_telegrams_.emplace_front(tx_telegram_id_++, std::move(telegram_last_), true);
|
||||
}
|
||||
|
||||
// checks if a telegram is sent to us matches the last Tx request
|
||||
// incoming Rx src must match the last Tx dest
|
||||
// and incoming Rx dest must be us (our ems_bus_id)
|
||||
// for both src and dest we strip the MSB 8th bit
|
||||
// returns true if the src/dest match the last Tx sent
|
||||
bool TxService::is_last_tx(const uint8_t src, const uint8_t dest) const {
|
||||
return (((telegram_last_->dest & 0x7F) == (src & 0x7F)) && ((dest & 0x7F) == ems_bus_id()));
|
||||
}
|
||||
|
||||
// sends a type_id read request to fetch values after a successful Tx write operation
|
||||
void TxService::post_send_query() {
|
||||
if (telegram_last_post_send_query_) {
|
||||
uint8_t dest = (telegram_last_->dest & 0x7F);
|
||||
uint8_t message_data[1] = {EMS_MAX_TELEGRAM_LENGTH}; // request all data, 32 bytes
|
||||
add(Telegram::Operation::TX_READ, dest, telegram_last_post_send_query_, 0, message_data, 1, true);
|
||||
// read_request(telegram_last_post_send_query_, dest, 0); // no offset
|
||||
LOG_DEBUG(F("Sending post validate read, type ID 0x%02X to dest 0x%02X"), telegram_last_post_send_query_, dest);
|
||||
}
|
||||
}
|
||||
|
||||
// print out the last Tx that was sent
|
||||
void TxService::print_last_tx() {
|
||||
LOG_DEBUG(F("Last Tx %s operation: %s"),
|
||||
(telegram_last_->operation == Telegram::Operation::TX_WRITE) ? F("Write") : F("Read"),
|
||||
telegram_last_->to_string().c_str());
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
384
src/telegram.h
Normal file
384
src/telegram.h
Normal file
@@ -0,0 +1,384 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_TELEGRAM_H
|
||||
#define EMSESP_TELEGRAM_H
|
||||
|
||||
#include <string>
|
||||
#include <deque>
|
||||
#include <memory> // for unique ptrs
|
||||
#include <vector>
|
||||
|
||||
// UART drivers
|
||||
#if defined(ESP8266)
|
||||
#include "uart/emsuart_esp8266.h"
|
||||
#elif defined(ESP32)
|
||||
#include "uart/emsuart_esp32.h"
|
||||
#elif defined(EMSESP_STANDALONE)
|
||||
#include <emsuart_standalone.h>
|
||||
#endif
|
||||
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "helpers.h"
|
||||
|
||||
// default values for null values
|
||||
static constexpr uint8_t EMS_VALUE_BOOL = 0xFF; // boolean
|
||||
static constexpr uint8_t EMS_VALUE_BOOL_OFF = 0x00; // boolean false. True can be 0x01 or 0xFF sometimes.
|
||||
|
||||
static constexpr uint8_t EMS_VALUE_BOOL_NOTSET = 0xFE; // random number for booleans, that's not 0, 1 or FF
|
||||
static constexpr uint8_t EMS_VALUE_UINT_NOTSET = 0xFF; // for 8-bit unsigned ints/bytes
|
||||
static constexpr int8_t EMS_VALUE_INT_NOTSET = 0x7F; // for signed 8-bit ints/bytes
|
||||
static constexpr uint16_t EMS_VALUE_USHORT_NOTSET = 0x7D00; // 32000: for 2-byte unsigned shorts
|
||||
static constexpr int16_t EMS_VALUE_SHORT_NOTSET = 0x7D00; // 32000: for 2-byte signed shorts
|
||||
static constexpr uint32_t EMS_VALUE_ULONG_NOTSET = 0xFFFFFFFF; // for 3-byte and 4-byte longs
|
||||
|
||||
static constexpr uint8_t EMS_MAX_TELEGRAM_LENGTH = 32; // max length of a complete EMS telegram
|
||||
static constexpr uint8_t EMS_MAX_TELEGRAM_MESSAGE_LENGTH = 27; // max length of message block, assuming EMS1.0
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
// creates a telegram
|
||||
// from Rx (receiving one) or Tx for preparing one for sending
|
||||
class Telegram {
|
||||
public:
|
||||
Telegram(const uint8_t operation,
|
||||
const uint8_t src,
|
||||
const uint8_t dest,
|
||||
const uint16_t type_id,
|
||||
const uint8_t offset,
|
||||
const uint8_t * message_data,
|
||||
const uint8_t message_length);
|
||||
~Telegram() = default;
|
||||
|
||||
const uint8_t operation; // is Operation mode
|
||||
const uint8_t src; // device_id
|
||||
const uint8_t dest; // device_id
|
||||
const uint16_t type_id;
|
||||
const uint8_t offset;
|
||||
const uint8_t message_length;
|
||||
uint8_t message_data[EMS_MAX_TELEGRAM_MESSAGE_LENGTH];
|
||||
|
||||
enum Operation : uint8_t {
|
||||
NONE = 0,
|
||||
RX,
|
||||
TX_RAW,
|
||||
TX_READ,
|
||||
TX_WRITE,
|
||||
};
|
||||
|
||||
std::string to_string() const;
|
||||
std::string to_string(const uint8_t * telegram, uint8_t length) const;
|
||||
|
||||
// reads a bit value from a given telegram position
|
||||
void read_bitvalue(uint8_t & value, const uint8_t index, const uint8_t bit) const {
|
||||
uint8_t abs_index = (index - offset);
|
||||
if (abs_index >= message_length - 1) {
|
||||
return; // out of bounds
|
||||
}
|
||||
|
||||
value = (uint8_t)(((message_data[abs_index]) >> (bit)) & 0x01);
|
||||
}
|
||||
|
||||
// read values from a telegram. We always store the value, regardless if its garbage
|
||||
template <typename Value>
|
||||
// assuming negative numbers are stored as 2's-complement
|
||||
// https://medium.com/@LeeJulija/how-integers-are-stored-in-memory-using-twos-complement-5ba04d61a56c
|
||||
// 2-compliment : https://www.rapidtables.com/convert/number/decimal-to-hex.html
|
||||
// https://en.wikipedia.org/wiki/Two%27s_complement
|
||||
// s is to override number of bytes read (e.g. use 3 to simulat a uint24_t)
|
||||
void read_value(Value & value, const uint8_t index, uint8_t s = 0) const {
|
||||
uint8_t size = (!s) ? sizeof(Value) : s;
|
||||
int8_t abs_index = ((index - offset + size - 1) >= message_length - 1) ? -1 : (index - offset);
|
||||
if (abs_index < 0) {
|
||||
return; // out of bounds, we don't change the value
|
||||
}
|
||||
|
||||
value = 0;
|
||||
for (uint8_t i = 0; i < size; i++) {
|
||||
// shift
|
||||
value = (value << 8) + message_data[abs_index + i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
int8_t _getDataPosition(const uint8_t index, const uint8_t size) const;
|
||||
};
|
||||
|
||||
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 bool bus_connected() {
|
||||
#ifndef EMSESP_STANDALONE
|
||||
if ((uuid::get_uptime() - last_bus_activity_) > EMS_BUS_TIMEOUT) {
|
||||
bus_connected_ = false;
|
||||
}
|
||||
return bus_connected_;
|
||||
#else
|
||||
return true;
|
||||
#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 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 uint8_t calculate_crc(const uint8_t * data, const uint8_t length);
|
||||
|
||||
private:
|
||||
static constexpr uint32_t EMS_BUS_TIMEOUT = 30000; // timeout in ms before recognizing the ems bus is offline (30 seconds)
|
||||
|
||||
static uint32_t last_bus_activity_; // timestamp of last time a valid Rx came in
|
||||
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
|
||||
};
|
||||
|
||||
class RxService : public EMSbus {
|
||||
public:
|
||||
static constexpr size_t MAX_RX_TELEGRAMS = 20;
|
||||
|
||||
RxService() = default;
|
||||
~RxService() = default;
|
||||
|
||||
void loop();
|
||||
|
||||
void add(uint8_t * data, uint8_t length);
|
||||
|
||||
void flush_rx_queue();
|
||||
|
||||
uint16_t telegram_count() const {
|
||||
return telegram_count_;
|
||||
}
|
||||
|
||||
uint16_t telegram_error_count() const {
|
||||
return telegram_error_count_;
|
||||
}
|
||||
|
||||
void increment_telegram_count() {
|
||||
telegram_count_++;
|
||||
}
|
||||
|
||||
void increment_telegram_error_count() {
|
||||
telegram_error_count_++;
|
||||
}
|
||||
|
||||
class QueuedRxTelegram {
|
||||
public:
|
||||
QueuedRxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram);
|
||||
~QueuedRxTelegram() = default;
|
||||
|
||||
uint16_t id_; // sequential identifier
|
||||
const std::shared_ptr<const Telegram> telegram_;
|
||||
};
|
||||
|
||||
const std::deque<QueuedRxTelegram> queue() const {
|
||||
return rx_telegrams_;
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr uint32_t RX_LOOP_WAIT = 800; // delay in processing Rx queue
|
||||
uint32_t last_rx_check_ = 0;
|
||||
|
||||
uint8_t rx_telegram_id_ = 0; // queue counter
|
||||
|
||||
uint16_t telegram_count_ = 0; // # Rx received
|
||||
uint16_t telegram_error_count_ = 0; // # Rx CRC errors
|
||||
|
||||
std::deque<QueuedRxTelegram> rx_telegrams_;
|
||||
};
|
||||
|
||||
class TxService : public EMSbus {
|
||||
public:
|
||||
static constexpr size_t MAX_TX_TELEGRAMS = 40; // size of Tx queue
|
||||
|
||||
static constexpr uint8_t TX_WRITE_FAIL = 4;
|
||||
static constexpr uint8_t TX_WRITE_SUCCESS = 1;
|
||||
|
||||
TxService() = default;
|
||||
~TxService() = default;
|
||||
|
||||
void start();
|
||||
void loop();
|
||||
void send();
|
||||
|
||||
void add(const uint8_t operation,
|
||||
const uint8_t dest,
|
||||
const uint16_t type_id,
|
||||
const uint8_t offset,
|
||||
uint8_t * message_data,
|
||||
const uint8_t message_length,
|
||||
const bool front = false);
|
||||
void add(const uint8_t operation, const uint8_t * data, const uint8_t length, const bool front = false);
|
||||
|
||||
void read_request(const uint16_t type_id, const uint8_t dest, const uint8_t offset = 0);
|
||||
|
||||
void send_raw(const char * telegram_data);
|
||||
|
||||
void send_poll();
|
||||
|
||||
void flush_tx_queue();
|
||||
|
||||
void retry_tx(const uint8_t operation, const uint8_t * data, const uint8_t length);
|
||||
|
||||
uint8_t retry_count() const {
|
||||
return retry_count_;
|
||||
}
|
||||
|
||||
void reset_retry_count() {
|
||||
retry_count_ = 0;
|
||||
}
|
||||
|
||||
bool is_last_tx(const uint8_t src, const uint8_t dest) const;
|
||||
|
||||
void set_post_send_query(uint16_t type_id) {
|
||||
telegram_last_post_send_query_ = type_id;
|
||||
}
|
||||
|
||||
uint16_t telegram_read_count() const {
|
||||
return telegram_read_count_;
|
||||
}
|
||||
|
||||
void telegram_read_count(uint8_t telegram_read_count) {
|
||||
telegram_read_count_ = telegram_read_count;
|
||||
}
|
||||
|
||||
void increment_telegram_read_count() {
|
||||
telegram_read_count_++;
|
||||
}
|
||||
|
||||
uint16_t telegram_fail_count() const {
|
||||
return telegram_fail_count_;
|
||||
}
|
||||
|
||||
void telegram_fail_count(uint8_t telegram_fail_count) {
|
||||
telegram_fail_count_ = telegram_fail_count;
|
||||
}
|
||||
|
||||
void increment_telegram_fail_count() {
|
||||
telegram_fail_count_++;
|
||||
}
|
||||
|
||||
uint16_t telegram_write_count() const {
|
||||
return telegram_write_count_;
|
||||
}
|
||||
|
||||
void telegram_write_count(uint8_t telegram_write_count) {
|
||||
telegram_write_count_ = telegram_write_count;
|
||||
}
|
||||
|
||||
void increment_telegram_write_count() {
|
||||
telegram_write_count_++;
|
||||
}
|
||||
|
||||
void post_send_query();
|
||||
|
||||
void print_last_tx();
|
||||
|
||||
class QueuedTxTelegram {
|
||||
public:
|
||||
QueuedTxTelegram(uint16_t id, std::shared_ptr<Telegram> && telegram, bool retry);
|
||||
~QueuedTxTelegram() = default;
|
||||
|
||||
uint16_t id_; // sequential identifier
|
||||
const std::shared_ptr<const Telegram> telegram_;
|
||||
bool retry_; // is a retry
|
||||
};
|
||||
|
||||
const std::deque<QueuedTxTelegram> queue() const {
|
||||
return tx_telegrams_;
|
||||
}
|
||||
|
||||
static constexpr uint8_t MAXIMUM_TX_RETRIES = 3;
|
||||
|
||||
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;
|
||||
|
||||
std::deque<QueuedTxTelegram> tx_telegrams_;
|
||||
|
||||
uint16_t telegram_read_count_ = 0; // # Tx successful reads
|
||||
uint16_t telegram_write_count_ = 0; // # Tx successful writes
|
||||
uint16_t telegram_fail_count_ = 0; // # Tx unsuccessful transmits
|
||||
|
||||
std::shared_ptr<Telegram> telegram_last_;
|
||||
uint16_t telegram_last_post_send_query_; // which type ID to query after a successful send, to read back the values just written
|
||||
uint8_t retry_count_ = 0; // count for # Tx retries
|
||||
|
||||
void send_telegram(const QueuedTxTelegram & tx_telegram);
|
||||
void send_telegram(const uint8_t * data, const uint8_t length);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
695
src/test/test.cpp
Normal file
695
src/test/test.cpp
Normal file
@@ -0,0 +1,695 @@
|
||||
|
||||
/*
|
||||
* 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 "test.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
// create some fake test data
|
||||
// used with the 'test' command, under su/admin
|
||||
void Test::run_test(uuid::console::Shell & shell, const std::string & command) {
|
||||
if (command == "render") {
|
||||
uint8_t test1 = 12;
|
||||
int8_t test2 = -12;
|
||||
uint16_t test3 = 456;
|
||||
int16_t test4 = -456;
|
||||
uint8_t test5 = 1; // bool = on
|
||||
uint32_t test6 = 305419896;
|
||||
float test7 = 89.43;
|
||||
|
||||
uint8_t test1u = EMS_VALUE_UINT_NOTSET;
|
||||
int8_t test2u = EMS_VALUE_INT_NOTSET;
|
||||
|
||||
uint8_t test5u = EMS_VALUE_BOOL_NOTSET;
|
||||
uint32_t test6u = EMS_VALUE_ULONG_NOTSET;
|
||||
|
||||
uint16_t test3u = EMS_VALUE_USHORT_NOTSET;
|
||||
int16_t test4u = EMS_VALUE_SHORT_NOTSET;
|
||||
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature1"), test1, F_(degrees)); // 12
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature2"), test2, F_(degrees)); // -12
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature3"), test3, F_(degrees), 10); // 45.6
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature4"), test4, F_(degrees), 10); // -45.6
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature5"), test5, nullptr, EMS_VALUE_BOOL); // on
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature6"), test6, F_(degrees)); //
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature7"), test7, F_(degrees), 2); // 89.43
|
||||
EMSdevice::print_value(shell, 2, F("Warm Water comfort setting"), F("Intelligent"));
|
||||
char s[100];
|
||||
strcpy(s, "Not very intelligent");
|
||||
EMSdevice::print_value(shell, 2, F("Warm Water comfort setting2"), s);
|
||||
|
||||
shell.println();
|
||||
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature1u"), test1u, F_(degrees));
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature2u"), test2u, F_(degrees));
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature3u"), test3u, F_(degrees), 10);
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature4u"), test4u, F_(degrees), 10);
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature5u"), test5u, F_(degrees), EMS_VALUE_BOOL);
|
||||
EMSdevice::print_value(shell, 2, F("Selected flow temperature6u"), test6u, F_(degrees), 100);
|
||||
|
||||
shell.println();
|
||||
|
||||
// check read_value to make sure it handles all the data type correctly
|
||||
uint8_t message_data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; // message_length is 9
|
||||
auto telegram = std::make_shared<Telegram>(Telegram::Operation::RX, 0x10, 0x11, 0x1234, 0, message_data, sizeof(message_data));
|
||||
|
||||
uint8_t uint8b = EMS_VALUE_UINT_NOTSET;
|
||||
telegram->read_value(uint8b, 0);
|
||||
shell.printfln("uint8: expecting %02X, got:%02X", 1, uint8b);
|
||||
|
||||
int8_t int8b = EMS_VALUE_INT_NOTSET;
|
||||
telegram->read_value(int8b, 0);
|
||||
shell.printfln("int8: expecting %02X, got:%02X", 1, int8b);
|
||||
|
||||
uint16_t uint16b = EMS_VALUE_USHORT_NOTSET;
|
||||
telegram->read_value(uint16b, 1);
|
||||
shell.printfln("uint16: expecting %02X, got:%02X", 0x0203, uint16b);
|
||||
|
||||
int16_t int16b = EMS_VALUE_SHORT_NOTSET;
|
||||
telegram->read_value(int16b, 1);
|
||||
shell.printfln("int16: expecting %02X, got:%02X", 0x0203, int16b);
|
||||
|
||||
int16_t int16b8 = EMS_VALUE_SHORT_NOTSET;
|
||||
telegram->read_value(int16b8, 1, 1); // force to 1 byte
|
||||
shell.printfln("int16 1 byte: expecting %02X, got:%02X", 0x02, int16b8);
|
||||
|
||||
uint32_t uint32b = EMS_VALUE_ULONG_NOTSET;
|
||||
telegram->read_value(uint32b, 1, 3);
|
||||
shell.printfln("uint32 3 bytes: expecting %02X, got:%02X", 0x020304, uint32b);
|
||||
|
||||
uint32b = EMS_VALUE_ULONG_NOTSET;
|
||||
telegram->read_value(uint32b, 1);
|
||||
shell.printfln("uint32 4 bytes: expecting %02X, got:%02X", 0x02030405, uint32b);
|
||||
|
||||
// check out of bounds
|
||||
uint16_t uint16 = EMS_VALUE_USHORT_NOTSET;
|
||||
telegram->read_value(uint16, 9);
|
||||
shell.printfln("uint16 out-of-bounds: was:%02X, new:%02X", EMS_VALUE_USHORT_NOTSET, uint16);
|
||||
uint8_t uint8oob = EMS_VALUE_UINT_NOTSET;
|
||||
telegram->read_value(uint8oob, 9);
|
||||
shell.printfln("uint8 out-of-bounds: was:%02X, new:%02X", EMS_VALUE_UINT_NOTSET, uint8oob);
|
||||
|
||||
// check read bit
|
||||
uint8_t uint8bitb = EMS_VALUE_UINT_NOTSET;
|
||||
telegram->read_bitvalue(uint8bitb, 1, 1); // value is 0x02 = 0000 0010
|
||||
shell.printfln("uint8 bit read: expecting 1, got:%d", uint8bitb);
|
||||
uint8bitb = EMS_VALUE_UINT_NOTSET;
|
||||
telegram->read_bitvalue(uint8bitb, 0, 0); // value is 0x01 = 0000 0001
|
||||
shell.printfln("uint8 bit read: expecting 1, got:%d", uint8bitb);
|
||||
|
||||
shell.loop_all();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (command == "devices") {
|
||||
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS); // this is important otherwise nothing will be picked up!
|
||||
|
||||
//emsdevices.push_back(EMSFactory::add(EMSdevice::DeviceType::BOILER, EMSdevice::EMS_DEVICE_ID_BOILER, 0, "", "My Boiler", 0, 0));
|
||||
|
||||
// A fake response - UBADevices(0x07)
|
||||
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x0B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
}
|
||||
|
||||
if (command == "boiler2") {
|
||||
// question: do we need to set the mask?
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x08, 123, version, EMSdevice::Brand::BUDERUS); // Nefit Trendline
|
||||
}
|
||||
|
||||
// unknown device -
|
||||
if ((command == "unknown") || (command == "u")) {
|
||||
// question: do we need to set the mask?
|
||||
std::string version("1.2.3");
|
||||
|
||||
// add boiler
|
||||
EMSESP::add_device(0x08, 84, version, EMSdevice::Brand::BUDERUS);
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// add Controller - BC10 GB142 - but using the same device_id to see what happens
|
||||
EMSESP::add_device(0x09, 84, version, EMSdevice::Brand::BUDERUS);
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// simulate getting version information back from an unknown device
|
||||
// note there is no brand (byte 9)
|
||||
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x59, 0x09, 0x0a});
|
||||
|
||||
shell.loop_all();
|
||||
EMSESP::show_device_values(shell);
|
||||
}
|
||||
|
||||
if (command == "unknown2") {
|
||||
// simulate getting version information back from an unknown device
|
||||
rx_telegram({0x09, 0x0B, 0x02, 0x00, 0x5A, 0x01, 0x02}); // product id is 90 which doesn't exist
|
||||
}
|
||||
|
||||
if (command == "gateway") {
|
||||
// add 0x48 KM200, via a version command
|
||||
rx_telegram({0x48, 0x0B, 0x02, 0x00, 0xBD, 0x04, 0x06, 00, 00, 00, 00, 00, 00, 00});
|
||||
|
||||
// Boiler(0x08) -> All(0x00), UBADevices(0x07), data: 09 01 00 00 00 00 00 00 01 00 00 00 00
|
||||
// check: make sure 0x48 is not detected again !
|
||||
rx_telegram({0x08, 0x00, 0x07, 0x00, 0x09, 01, 00, 00, 00, 00, 00, 00, 01, 00, 00, 00, 00});
|
||||
|
||||
// add thermostat - Thermostat: RC300/RC310/Moduline 3000/CW400/Sense II (DeviceID:0x10, ProductID:158, Version:03.03) ** master device **
|
||||
std::string version("01.03");
|
||||
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS);
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// simulate incoming telegram
|
||||
// Thermostat(0x10) -> 48(0x48), ?(0x26B), data: 6B 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03
|
||||
rx_telegram({0x10, 0x48, 0xFF, 00, 01, 0x6B, 00, 0x6B, 0x08, 0x4F, 00, 00, 00, 02, 00, 00, 00, 02, 00, 03, 00, 03, 00, 03});
|
||||
}
|
||||
|
||||
if (command == "thermostat") {
|
||||
shell.printfln(F("Testing adding devices on the EMS bus..."));
|
||||
|
||||
// create some fake devices
|
||||
std::string version("1.2.3");
|
||||
|
||||
// add_device(0x10, 165, version, EMSdevice::Brand::BUDERUS);
|
||||
// add_device(0x17, 125, version, EMSdevice::Brand::BUDERUS); // test unknown class test
|
||||
// add_device(0x17, 93, version, EMSdevice::Brand::BUDERUS);
|
||||
// add_device(0x17, 254, version, EMSdevice::Brand::BUDERUS); // test unknown product_id
|
||||
|
||||
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
}
|
||||
|
||||
if (command == "solar") {
|
||||
shell.printfln(F("Testing Solar"));
|
||||
|
||||
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
|
||||
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x30, 163, version, EMSdevice::Brand::BUDERUS); // SM100
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// SM100Monitor - type 0x0362 EMS+ - for SM100 and SM200
|
||||
// B0 0B FF 00 02 62 00 44 02 7A 80 00 80 00 80 00 80 00 80 00 80 00 00 7C 80 00 80 00 80 00 80
|
||||
rx_telegram({0xB0, 0x0B, 0xFF, 00, 0x02, 0x62, 00, 0x44, 0x02, 0x7A, 0x80, 00, 0x80, 0x00, 0x80, 00,
|
||||
0x80, 00, 0x80, 00, 0x80, 00, 00, 0x7C, 0x80, 00, 0x80, 00, 0x80, 00, 0x80});
|
||||
EMSESP::show_device_values(shell);
|
||||
|
||||
rx_telegram({0xB0, 0x0B, 0xFF, 0x00, 0x02, 0x62, 0x01, 0x44, 0x03, 0x30, 0x80, 00, 0x80, 00, 0x80, 00,
|
||||
0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 00, 0x80, 0x33});
|
||||
EMSESP::show_device_values(shell);
|
||||
|
||||
rx_telegram({0xB0, 00, 0xFF, 0x18, 02, 0x62, 0x80, 00, 0xB8});
|
||||
EMSESP::show_device_values(shell);
|
||||
|
||||
EMSESP::send_raw_telegram("B0 00 FF 18 02 62 80 00 B8");
|
||||
|
||||
uart_telegram("30 00 FF 0A 02 6A 04"); // SM100 pump on 1
|
||||
uart_telegram("30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00"); // SM100 modulation
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::show_device_values(shell);
|
||||
|
||||
uart_telegram("30 00 FF 0A 02 6A 03"); // SM100 pump off 0
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::show_device_values(shell);
|
||||
}
|
||||
|
||||
if (command == "km") {
|
||||
shell.printfln(F("Testing KM200 Gateway"));
|
||||
|
||||
emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
|
||||
|
||||
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
|
||||
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x10, 158, version, EMSdevice::Brand::BUDERUS); // RC300
|
||||
EMSESP::add_device(0x48, 189, version, EMSdevice::Brand::BUDERUS); // KM200
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// see https://github.com/proddy/EMS-ESP/issues/390
|
||||
|
||||
/*
|
||||
|
||||
uart_telegram_withCRC("90 48 FF 04 01 A6 5C");
|
||||
uart_telegram_withCRC("90 48 FF 00 01 A6 4C");
|
||||
uart_telegram_withCRC("90 48 FF 08 01 A7 6D");
|
||||
|
||||
uart_telegram("90 48 F9 00 FF 01 B0 08 0B 00 00 00 14 00 00 00 19 00 00 00 4B 00 00");
|
||||
uart_telegram("90 48 F9 00 FF 01 9C 08 03 00 00 00 1E 00 00 00 4B 00 00 00 55 00 00");
|
||||
uart_telegram("90 48 F9 00 FF 01 9C 07 03 00 00 00 1E 00 00 00 30 00 00 00 3C 00 00");
|
||||
|
||||
uart_telegram_withCRC("90 48 F9 00 FF 01 9D 00 43 00 00 00 01 00 00 00 02 00 03 00 06 00 03 00 02 05");
|
||||
uart_telegram_withCRC("90 48 F9 00 FF 01 9D 07 03 00 00 00 1E 00 00 00 30 00 00 00 3C 00 00 00 30 C4");
|
||||
uart_telegram_withCRC("90 48 F9 00 FF 01 9D 08 03 00 00 00 1E 00 00 00 4B 00 00 00 55 00 00 00 4B C8");
|
||||
uart_telegram_withCRC("90 48 F9 00 FF 01 B1 08 0B 00 00 00 14 00 00 00 19 00 00 00 4B 00 00 00 19 A2");
|
||||
|
||||
uart_telegram_withCRC("90 48 FF 07 01 A7 51");
|
||||
uart_telegram_withCRC("90 48 FF 08 01 A7 6D");
|
||||
uart_telegram_withCRC("90 48 FF 00 01 A7 4D");
|
||||
uart_telegram_withCRC("90 48 FF 25 01 A6 D8");
|
||||
uart_telegram_withCRC("90 48 FF 07 01 A7 51");
|
||||
uart_telegram_withCRC("90 0B 06 00 14 06 17 08 03 22 00 01 10 FF 00 18"); // time
|
||||
|
||||
uart_telegram("90 0B FF 00 01 A5 80 00 01 28 17 00 28 2A 05 A0 02 03 03 05 A0 05 A0 00 00 11 01 02 FF FF 00");
|
||||
uart_telegram("90 0B FF 00 01 B9 00 2E 26 26 1B 03 00 FF FF 05 28 01 E1 20 01 0F 05 2A");
|
||||
uart_telegram("90 0B FF 00 01 A6 90 0B FF 00 01 A6 18");
|
||||
uart_telegram("90 0B FF 00 01 B9 00 2E 26 26 1B 03 00 FF FF 05 28 01 E1 20 01 0F 05 2A");
|
||||
uart_telegram("90 0B FF 00 01 A6 90 0B FF 00 01 A6 18");
|
||||
uart_telegram("90 0B FF 00 01 BA 00 2E 2A 26 1E 03 00 FF FF 05 2A 01 E1 20 01 0F 05 2A");
|
||||
uart_telegram("90 0B FF 00 01 A7 90 0B FF 00 01 A7 19");
|
||||
uart_telegram("90 0B FF 00 01 BB 00 2E 2A 26 1E 03 00 FF FF 05 2A 01 E1 20 01 0F 05 2A");
|
||||
uart_telegram("90 0B FF 00 01 A8 90 0B FF 00 01 A8 16");
|
||||
uart_telegram("90 0B FF 00 01 BC 00 2E 2A 26 1E 03 00 FF FF 05 2A 01 E1 20 01 0F 05 2A");
|
||||
uart_telegram("90 0B FF 00 01 A5 80 00 01 28 17 00 28 2A 05 A0 02 03 03 05 A0 05 A0 00 00 11 01 02 FF FF 00");
|
||||
|
||||
uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA");
|
||||
uart_telegram_withCRC("90 48 FF 03 01 A6 40");
|
||||
uart_telegram_withCRC("C8 90 FF 00 02 01 A6 D0");
|
||||
|
||||
// uart_telegram_withCRC("10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00");
|
||||
|
||||
*/
|
||||
|
||||
uart_telegram_withCRC("C8 90 F7 02 01 FF 01 A6 BA");
|
||||
|
||||
uart_telegram_withCRC("90 48 FF 04 01 A6 5C");
|
||||
uart_telegram_withCRC("90 00 FF 00 01 A5 80 00 01 27 16 00 27 2A 05 A0 02 03 03 05 A0 05 A0 00 00 11 01 02 FF FF 00 9A");
|
||||
uart_telegram_withCRC("90 00 FF 19 01 A5 01 04 00 00 00 00 FF 64 2A 00 3C 01 FF 92");
|
||||
uart_telegram_withCRC("90 0B FF 00 01 A5 80 00 01 26 15 00 26 2A 05 A0 03 03 03 05 A0 05 A0 00 00 11 01 03 FF FF 00 FE");
|
||||
uart_telegram_withCRC("90 00 FF 19 01 A5 01 04 00 00 00 00 FF 64 2A 00 3C 01 FF 92");
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::show_device_values(shell);
|
||||
}
|
||||
|
||||
if (command == "cr100") {
|
||||
shell.printfln(F("Testing CR100"));
|
||||
|
||||
emsesp::EMSESP::watch(EMSESP::Watch::WATCH_RAW); // raw
|
||||
|
||||
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_HT3); // switch to junkers
|
||||
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x18, 157, version, EMSdevice::Brand::BOSCH); // Bosch CR100 - https://github.com/proddy/EMS-ESP/issues/355
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// RCPLUSStatusMessage_HC1(0x01A5)
|
||||
// 98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 (no CRC)
|
||||
uart_telegram({0x98, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xCF, 0x21, 0x2E, 0x00, 0x00, 0x2E, 0x24,
|
||||
0x03, 0x25, 0x03, 0x03, 0x01, 0x03, 0x25, 0x00, 0xC8, 0x00, 0x00, 0x11, 0x01, 0x03});
|
||||
|
||||
uart_telegram("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03"); // without CRC
|
||||
uart_telegram_withCRC("98 00 FF 00 01 A5 00 CF 21 2E 00 00 2E 24 03 25 03 03 01 03 25 00 C8 00 00 11 01 03 13"); // with CRC
|
||||
|
||||
shell.loop_all();
|
||||
EMSESP::rxservice_.loop();
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
shell.loop_all();
|
||||
EMSESP::show_device_values(shell);
|
||||
|
||||
shell.invoke_command("thermostat");
|
||||
shell.loop_all();
|
||||
|
||||
// shell.invoke_command("set temp 20");
|
||||
shell.invoke_command("set mode auto");
|
||||
|
||||
shell.loop_all();
|
||||
EMSESP::show_ems(shell);
|
||||
shell.loop_all();
|
||||
|
||||
EMSESP::txservice_.send(); // send it to UART
|
||||
}
|
||||
|
||||
if (command == "rx") {
|
||||
shell.printfln(F("Testing Rx..."));
|
||||
|
||||
// fake telegrams. length includes CRC
|
||||
// Boiler -> Me, UBAMonitorFast(0x18), telegram: 08 00 18 00 00 02 5A 73 3D 0A 10 65 40 02 1A 80 00 01 E1 01 76 0E 3D 48 00 C9 44 02 00 (#data=25)
|
||||
uart_telegram({0x08, 0x00, 0x18, 0x00, 0x00, 0x02, 0x5A, 0x73, 0x3D, 0x0A, 0x10, 0x65, 0x40, 0x02, 0x1A,
|
||||
0x80, 0x00, 0x01, 0xE1, 0x01, 0x76, 0x0E, 0x3D, 0x48, 0x00, 0xC9, 0x44, 0x02, 0x00});
|
||||
|
||||
// Boiler -> Thermostat, UBAParameterWW(0x33), telegram: 08 97 33 00 23 24 (#data=2)
|
||||
uart_telegram({0x08, 0x97, 0x33, 0x00, 0x23, 0x24});
|
||||
|
||||
// Boiler -> Me, UBAParameterWW(0x33), telegram: 08 0B 33 00 08 FF 34 FB 00 28 00 00 46 00 FF FF 00 (#data=13)
|
||||
uart_telegram({0x08, 0x0B, 0x33, 0x00, 0x08, 0xFF, 0x34, 0xFB, 0x00, 0x28, 0x00, 0x00, 0x46, 0x00, 0xFF, 0xFF, 0x00});
|
||||
|
||||
// Thermostat -> Me, RC20StatusMessage(0x91), telegram: 17 0B 91 05 44 45 46 47 (#data=4)
|
||||
uart_telegram({0x17, 0x0B, 0x91, 0x05, 0x44, 0x45, 0x46, 0x47});
|
||||
|
||||
// bad CRC - corrupt telegram - CRC should be 0x8E
|
||||
uint8_t t5[] = {0x17, 0x0B, 0x91, 0x05, 0x44, 0x45, 0x46, 0x47, 0x99};
|
||||
EMSESP::rxservice_.add(t5, sizeof(t5));
|
||||
|
||||
// simulating a Tx record
|
||||
uart_telegram({0x0B, 0x88, 0x07, 0x00, 0x20});
|
||||
|
||||
// Version Boiler
|
||||
uart_telegram({0x08, 0x0B, 0x02, 0x00, 0x7B, 0x06, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04});
|
||||
|
||||
// Version Thermostat, device_id 0x11
|
||||
uart_telegram({0x11, 0x0B, 0x02, 0x00, 0x4D, 0x03, 0x03});
|
||||
|
||||
// Thermostat -> all, telegram: 10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00
|
||||
// 0x1A5 test ems+
|
||||
uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xD7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84,
|
||||
0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00});
|
||||
|
||||
// setting temp from 21.5 to 19.9C
|
||||
uart_telegram({0x10, 0x00, 0xFF, 0x00, 0x01, 0xA5, 0x00, 0xC7, 0x21, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x84,
|
||||
0x01, 0x01, 0x03, 0x01, 0x84, 0x01, 0xF1, 0x00, 0x00, 0x11, 0x01, 0x00, 0x08, 0x63, 0x00});
|
||||
|
||||
// Thermostat -> Boiler, UBAFlags(0x35), telegram: 17 08 35 00 11 00 (#data=2)
|
||||
uart_telegram({0x17, 0x08, 0x35, 0x00, 0x11, 0x00});
|
||||
|
||||
// Thermostat -> Boiler, UBASetPoints(0x1A), telegram: 17 08 1A 00 00 00 00 00 (#data=4)
|
||||
uart_telegram({0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||
|
||||
// Thermostat -> Me, RC20Set(0xA8), telegram: 17 0B A8 00 01 00 FF F6 01 06 00 01 0D 01 00 FF FF 01 02 02 02 00 00 05 1F 05 1F 02 0E 00 FF (#data=27)
|
||||
uart_telegram({0x17, 0x0B, 0xA8, 0x00, 0x01, 0x00, 0xFF, 0xF6, 0x01, 0x06, 0x00, 0x01, 0x0D, 0x01, 0x00, 0xFF,
|
||||
0xFF, 0x01, 0x02, 0x02, 0x02, 0x00, 0x00, 0x05, 0x1F, 0x05, 0x1F, 0x02, 0x0E, 0x00, 0xFF});
|
||||
|
||||
// Boiler(0x08) -> All(0x00), UBAMonitorWW(0x34), data: 36 01 A5 80 00 21 00 00 01 00 01 3E 8D 03 77 91 00 80 00
|
||||
uart_telegram(
|
||||
{0x08, 0x00, 0x34, 0x00, 0x36, 0x01, 0xA5, 0x80, 0x00, 0x21, 0x00, 0x00, 0x01, 0x00, 0x01, 0x3E, 0x8D, 0x03, 0x77, 0x91, 0x00, 0x80, 0x00});
|
||||
|
||||
// test 0x2A - DHWStatus3
|
||||
uart_telegram({0x88, 00, 0x2A, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0xD2, 00, 00, 0x80, 00, 00, 01, 0x9D, 0x80, 0x00, 0x02, 0x79, 00});
|
||||
}
|
||||
|
||||
if (command == "send") {
|
||||
shell.printfln(F("Sending to Tx..."));
|
||||
EMSESP::show_ems(shell);
|
||||
EMSESP::txservice_.send(); // send it to UART
|
||||
}
|
||||
|
||||
if (command == "tx") {
|
||||
shell.printfln(F("Testing Tx..."));
|
||||
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
|
||||
// TX queue example - Me -> Thermostat, (0x91), telegram: 0B 17 91 05 44 45 46 47 (#data=4)
|
||||
uint8_t t11[] = {0x44, 0x45, 0x46, 0x47};
|
||||
EMSESP::txservice_.add(Telegram::Operation::TX_RAW, 0x17, 0x91, 0x05, t11, sizeof(t11));
|
||||
|
||||
// TX - raw example test
|
||||
uint8_t t12[] = {0x10, 0x08, 0x63, 0x04, 0x64};
|
||||
EMSESP::txservice_.add(Telegram::Operation::TX_RAW, t12, sizeof(t12));
|
||||
|
||||
// TX - sending raw string
|
||||
EMSESP::txservice_.send_raw("10 08 63 03 64 65 66");
|
||||
|
||||
// TX - send a read request
|
||||
EMSESP::send_read_request(0x18, 0x08);
|
||||
|
||||
// TX - send a write request
|
||||
uint8_t t18[] = {0x52, 0x79};
|
||||
EMSESP::send_write_request(0x91, 0x17, 0x00, t18, sizeof(t18), 0x00);
|
||||
|
||||
// TX - send EMS+
|
||||
const uint8_t t13[] = {0x90, 0x0B, 0xFF, 00, 01, 0xBA, 00, 0x2E, 0x2A, 0x26, 0x1E, 0x03,
|
||||
00, 0xFF, 0xFF, 05, 0x2A, 01, 0xE1, 0x20, 0x01, 0x0F, 05, 0x2A};
|
||||
EMSESP::txservice_.add(Telegram::Operation::TX_RAW, t13, sizeof(t13));
|
||||
|
||||
// EMS+ Junkers read request
|
||||
EMSESP::send_read_request(0x16F, 0x10);
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
|
||||
// process whole Tx queue
|
||||
for (uint8_t i = 0; i < 10; i++) {
|
||||
EMSESP::txservice_.send(); // send it to UART
|
||||
}
|
||||
|
||||
shell.loop_all();
|
||||
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
}
|
||||
|
||||
if (command == "poll") {
|
||||
shell.printfln(F("Testing Poll..."));
|
||||
|
||||
// check if sending works when a poll comes in, with retries
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
|
||||
// simulate sending a read request
|
||||
// uint8_t t16[] = {0x44, 0x45, 0x46, 0x47}; // Me -> Thermostat, (0x91), telegram: 0B 17 91 05 44 45 46 47 (#data=4)
|
||||
// EMSESP::txservice_.add(Telegram::Operation::TX_RAW, 0x17, 0x91, 0x05, t16, sizeof(t16));
|
||||
EMSESP::send_read_request(0x91, 0x17);
|
||||
// EMSESP::txservice_.show_tx_queue();
|
||||
|
||||
// Simulate adding a Poll, so read request is sent
|
||||
uint8_t poll[1] = {0x8B};
|
||||
EMSESP::incoming_telegram(poll, 1);
|
||||
|
||||
// incoming Rx
|
||||
uart_telegram({0x17, 0x08, 0x1A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3A});
|
||||
|
||||
// Simulate adding a Poll - should send retry
|
||||
EMSESP::incoming_telegram(poll, 1);
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
uint8_t t2[] = {0x21, 0x22};
|
||||
EMSESP::send_write_request(0x91, 0x17, 0x00, t2, sizeof(t2), 0);
|
||||
EMSESP::show_ems(shell);
|
||||
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
}
|
||||
|
||||
if (command == "mqtt1") {
|
||||
shell.printfln(F("Testing MQTT..."));
|
||||
|
||||
// MQTT test
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
EMSESP::EMSESP::mqtt_.publish("boiler_cmd", "test me");
|
||||
// EMSESP::mqtt_.show_queue();
|
||||
// simulate an incoming mqtt msg
|
||||
char topic[Mqtt::MQTT_TOPIC_MAX_SIZE];
|
||||
char payload[100];
|
||||
|
||||
strcpy(topic, "boiler_cmd");
|
||||
strcpy(payload, "12345");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
EMSESP::mqtt_.incoming(payload, payload); // should report error
|
||||
|
||||
strcpy(topic, "thermostat_cmd_mode");
|
||||
strcpy(payload, "auto");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "thermostat_cmd_temp");
|
||||
strcpy(payload, "20");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "thermostat_cmd");
|
||||
strcpy(payload, "{\"cmd\":\"temp\",\"data\":22.52}");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "boiler_cmd_wwtemp");
|
||||
strcpy(payload, "66");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "thermostat_cmd");
|
||||
strcpy(payload, "{\"cmd\":\"temp\",\"hc\":2,\"data\":22}");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
strcpy(topic, "home/ems-esp/cmd");
|
||||
strcpy(payload, "restart");
|
||||
EMSESP::mqtt_.incoming(topic, payload);
|
||||
|
||||
// EMSESP::txservice_.show_tx_queue();
|
||||
|
||||
EMSESP::publish_all_values();
|
||||
}
|
||||
|
||||
if (command == "poll2") {
|
||||
shell.printfln(F("Testing Tx Sending last message on queue..."));
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
|
||||
uint8_t poll[1] = {0x8B};
|
||||
EMSESP::incoming_telegram(poll, 1);
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
EMSESP::show_ems(shell);
|
||||
EMSESP::txservice_.flush_tx_queue();
|
||||
}
|
||||
|
||||
if (command == "rx2") {
|
||||
uart_telegram({0x1B, 0x5B, 0xFD, 0x2D, 0x9E, 0x3A, 0xB6, 0xE5, 0x02, 0x20, 0x33, 0x30, 0x32, 0x3A, 0x20, 0x5B,
|
||||
0x73, 0xFF, 0xFF, 0xCB, 0xDF, 0xB7, 0xA7, 0xB5, 0x67, 0x77, 0x77, 0xE4, 0xFF, 0xFD, 0x77, 0xFF});
|
||||
}
|
||||
|
||||
// https://github.com/proddy/EMS-ESP/issues/380#issuecomment-633663007
|
||||
if (command == "rx3") {
|
||||
uart_telegram({0x21, 0x0B, 0xFF, 0x00});
|
||||
}
|
||||
|
||||
if (command == "mqtt2") {
|
||||
// Mqtt::subscribe("cmd", std::bind(&EMSESP::dummy_EMSESP::mqtt_commands, this, _1));
|
||||
for (uint8_t i = 0; i < 30; i++) {
|
||||
Mqtt::subscribe("cmd", dummy_mqtt_commands);
|
||||
}
|
||||
}
|
||||
|
||||
// testing the UART tx command, without a queue
|
||||
if (command == "tx2") {
|
||||
uint8_t t[] = {0x0B, 0x88, 0x18, 0x00, 0x20, 0xD4}; // including CRC
|
||||
EMSuart::transmit(t, sizeof(t));
|
||||
}
|
||||
|
||||
// send read request with offset
|
||||
if (command == "offset") {
|
||||
// send_read_request(0x18, 0x08);
|
||||
EMSESP::txservice_.read_request(0x18, 0x08, 27); // no offset
|
||||
}
|
||||
|
||||
if (command == "mixing") {
|
||||
shell.printfln(F("Testing Mixing..."));
|
||||
|
||||
EMSESP::rxservice_.ems_mask(EMSbus::EMS_MASK_BUDERUS);
|
||||
|
||||
std::string version("1.2.3");
|
||||
EMSESP::add_device(0x28, 160, version, EMSdevice::Brand::BUDERUS); // MM100, WWC
|
||||
EMSESP::add_device(0x29, 161, version, EMSdevice::Brand::BUDERUS); // MM200, WWC
|
||||
|
||||
EMSESP::add_device(0x20, 160, version, EMSdevice::Brand::BOSCH); // MM100
|
||||
|
||||
EMSESP::rxservice_.loop();
|
||||
|
||||
// WWC1 on 0x29
|
||||
rx_telegram({0xA9, 0x00, 0xFF, 0x00, 0x02, 0x32, 0x02, 0x6C, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C});
|
||||
|
||||
// WWC2 on 0x28
|
||||
rx_telegram({0xA8, 0x00, 0xFF, 0x00, 0x02, 0x31, 0x02, 0x35, 0x00, 0x3C, 0x00, 0x3C, 0x3C, 0x46, 0x02, 0x03, 0x03, 0x00, 0x3C});
|
||||
|
||||
// check for error "[emsesp] No telegram type handler found for ID 0x255 (src 0x20, dest 0x00)"
|
||||
rx_telegram({0xA0, 0x00, 0xFF, 0x00, 0x01, 0x55, 0x00, 0x1A});
|
||||
}
|
||||
|
||||
// finally dump to console
|
||||
shell.loop_all();
|
||||
}
|
||||
|
||||
// simulates a telegram in the Rx queue, but without the CRC which is added automatically
|
||||
void Test::rx_telegram(const std::vector<uint8_t> & rx_data) {
|
||||
uint8_t len = rx_data.size();
|
||||
uint8_t data[50];
|
||||
std::copy(rx_data.begin(), rx_data.end(), data);
|
||||
data[len] = EMSESP::rxservice_.calculate_crc(rx_data.data(), len);
|
||||
EMSESP::rxservice_.add(data, len + 1);
|
||||
EMSESP::rxservice_.loop();
|
||||
}
|
||||
|
||||
// simulates a telegram straight from UART, but without the CRC which is added automatically
|
||||
void Test::uart_telegram(const std::vector<uint8_t> & rx_data) {
|
||||
uint8_t len = rx_data.size();
|
||||
uint8_t data[50];
|
||||
std::copy(rx_data.begin(), rx_data.end(), data);
|
||||
data[len] = EMSESP::rxservice_.calculate_crc(rx_data.data(), len);
|
||||
EMSESP::incoming_telegram(data, len + 1);
|
||||
EMSESP::rxservice_.loop();
|
||||
}
|
||||
|
||||
// takes raw string, assuming it contains the CRC. This is what is output from 'watch raw'
|
||||
void Test::uart_telegram_withCRC(const char * rx_data) {
|
||||
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
|
||||
char telegram[(EMS_MAX_TELEGRAM_LENGTH * 3) + 1];
|
||||
for (uint8_t i = 0; i < strlen(rx_data); i++) {
|
||||
telegram[i] = rx_data[i];
|
||||
}
|
||||
telegram[strlen(rx_data)] = '\0'; // make sure its terminated
|
||||
|
||||
uint8_t count = 0;
|
||||
char * p;
|
||||
char value[10] = {0};
|
||||
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
|
||||
// get first value, which should be the src
|
||||
if ((p = strtok(telegram, " ,"))) { // delimiter
|
||||
strlcpy(value, p, 10);
|
||||
data[0] = (uint8_t)strtol(value, 0, 16);
|
||||
}
|
||||
|
||||
// and iterate until end
|
||||
while (p != 0) {
|
||||
if ((p = strtok(nullptr, " ,"))) {
|
||||
strlcpy(value, p, 10);
|
||||
uint8_t val = (uint8_t)strtol(value, 0, 16);
|
||||
data[++count] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return; // nothing to send
|
||||
}
|
||||
|
||||
EMSESP::incoming_telegram(data, count + 1);
|
||||
}
|
||||
|
||||
// takes raw string, adds CRC to end
|
||||
void Test::uart_telegram(const char * rx_data) {
|
||||
// since the telegram data is a const, make a copy. add 1 to grab the \0 EOS
|
||||
char telegram[(EMS_MAX_TELEGRAM_LENGTH * 3) + 1];
|
||||
for (uint8_t i = 0; i < strlen(rx_data); i++) {
|
||||
telegram[i] = rx_data[i];
|
||||
}
|
||||
telegram[strlen(rx_data)] = '\0'; // make sure its terminated
|
||||
|
||||
uint8_t count = 0;
|
||||
char * p;
|
||||
char value[10] = {0};
|
||||
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
|
||||
// get first value, which should be the src
|
||||
if ((p = strtok(telegram, " ,"))) { // delimiter
|
||||
strlcpy(value, p, 10);
|
||||
data[0] = (uint8_t)strtol(value, 0, 16);
|
||||
}
|
||||
|
||||
// and iterate until end
|
||||
while (p != 0) {
|
||||
if ((p = strtok(nullptr, " ,"))) {
|
||||
strlcpy(value, p, 10);
|
||||
uint8_t val = (uint8_t)strtol(value, 0, 16);
|
||||
data[++count] = val;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == 0) {
|
||||
return; // nothing to send
|
||||
}
|
||||
|
||||
data[count + 1] = EMSESP::rxservice_.calculate_crc(data, count + 1); // add CRC
|
||||
|
||||
EMSESP::incoming_telegram(data, count + 2);
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wunused-parameter"
|
||||
|
||||
void Test::dummy_mqtt_commands(const char * message) {
|
||||
//
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
} // namespace emsesp
|
||||
51
src/test/test.h
Normal file
51
src/test/test.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* EMS-ESP - https://github.com/proddy/EMS-ESP
|
||||
* Copyright 2019 Paul Derbyshire
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef EMSESP_TEST_H
|
||||
#define EMSESP_TEST_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
#include <uuid/common.h>
|
||||
#include <uuid/console.h>
|
||||
#include <uuid/log.h>
|
||||
|
||||
#include "emsdevice.h"
|
||||
#include "emsfactory.h"
|
||||
#include "telegram.h"
|
||||
#include "mqtt.h"
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
class Test {
|
||||
public:
|
||||
static void run_test(uuid::console::Shell & shell, const std::string & command); // only for testing
|
||||
static void dummy_mqtt_commands(const char * message);
|
||||
static void rx_telegram(const std::vector<uint8_t> & data);
|
||||
static void uart_telegram(const std::vector<uint8_t> & rx_data);
|
||||
static void uart_telegram(const char * rx_data);
|
||||
static void uart_telegram_withCRC(const char * rx_data);
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
@@ -1,63 +0,0 @@
|
||||
|
||||
#ifdef TESTS
|
||||
|
||||
static const char * TEST_DATA[] = {
|
||||
|
||||
"08 00 34 00 3E 02 1D 80 00 31 00 00 01 00 01 0B AE 02", // test 1 - EMS
|
||||
"10 00 FF 00 01 A5 80 00 01 30 28 00 30 28 01 54 03 03 01 01 54 02 A8 00 00 11 01 03 FF FF 00", // test 2 - RC310 ems+
|
||||
"10 00 FF 19 01 A5 06 04 00 00 00 00 FF 64 37 00 3C 01 FF 01", // test 3 - RC310 ems+
|
||||
"30 00 FF 00 02 62 00 A1 01 3F 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00", // test 4 - SM100
|
||||
"10 00 FF 00 01 A5 00 D7 21 00 00 00 00 30 01 84 01 01 03 01 84 01 F1 00 00 11 01 00 08 63 00", // test 5 - Moduline 1010
|
||||
"18 00 FF 00 01 A5 00 DD 21 23 00 00 23 00 00 00 00 00 00 00 00 00 00 00 00 00 01 00", // test 6 - RC300
|
||||
"90 00 FF 00 00 6F 01 01 00 46 00 B9", // test 7 - FR10
|
||||
"30 00 FF 00 02 62 01 FB 01 9E 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 2B", // test 8 - SM100
|
||||
"30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0C 20 64 00 00 00 00 E9", // test 9 - SM100
|
||||
"30 09 FF 00 00 01", // test 10 - EMS+
|
||||
"30 0B 97 00", // test 11 - SM100
|
||||
"30 00 FF 00 02 62 01 CA", // test 12 - SM100
|
||||
"30 00 FF 00 02 8E 00 00 00 00 00 00 05 19 00 00 75 D3", // test 13 - SM100
|
||||
"30 00 FF 00 02 63 80 00 80 00 00 00 80 00 80 00 80 00 00", // test 14 - SM100
|
||||
"30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 0B 09 64 00 00 00 00", // test 15 - SM100
|
||||
"30 00 FF 00 02 62 01 CA 01 93 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00", // test 16 - SM100
|
||||
"30 00 FF 00 02 6A 03 03 03 00 03 03 03 03 03 00 03 03", // test 17 - SM100
|
||||
"30 00 FF 00 02 6A 03 03 03 00 03 03 03 03 03 00 04 03", // test 18 - SM100
|
||||
"30 00 FF 00 02 64 00 00 00 04 00 00 FF 00 00 1E 09 08 64 00 00 00 00", // test 19 - SM100
|
||||
"10 00 FF 07 01 A5 32", // test 20 - RC EMS+
|
||||
"38 10 FF 00 03 2B 00 D1 08 2A 01", // test 21 - heatpump
|
||||
"38 10 FF 00 03 7B 08 24 00 4B", // test 22 - heatpump
|
||||
"08 00 FF 31 03 94 00 00 00 00 00 00 00", // test 23 - heatpump
|
||||
"08 00 FF 00 03 95 00 6D C5 0E 00 05 BA 7C 00 68 0A 92 00 00 00 00 00 00 00 00 00 00 00 CD", // test 24 - heatpump
|
||||
"08 00 FF 48 03 95 00 00 01 47 00 00 00 00 00 00 00 00", // test 25 - heatpump
|
||||
"08 00 FF 00 03 A2 10 01 02 02 00", // test 26 - heatpump
|
||||
"08 00 FF 00 03 A3 00 0B 00 00 00 00 00 00 00 00 00 09 00 00 00 00 00 00 00 00 08 00 00 00 0A", // test 27 - heatpump
|
||||
"30 00 FF 0A 02 6A 04", // test 28 - SM100 pump on
|
||||
"30 00 FF 0A 02 6A 03", // test 29 - SM100 pump off
|
||||
"48 90 02 00 01", // test 30 - version test
|
||||
"10 48 02 00 9E", // test 31 - version test
|
||||
"30 00 FF 00 02 8E 00 00 0C F3 00 00 06 02 00 00 76 33", // test 32 - SM100 energy
|
||||
"30 00 FF 00 02 8E 00 00 07 9E 00 00 06 C5 00 00 76 35", // test 33 - SM100 energy
|
||||
"30 00 FF 00 02 8E 00 00 00 00 00 00 06 C5 00 00 76 35", // test 34 - SM100 energy
|
||||
"10 48 F9 00 FF 01 6C 08 4F 00 00 00 02 00 00 00 02 00 03 00 03 00 03 00 02", // test 35 - F9
|
||||
"48 90 F9 00 11 FF 01 6D 08", // test 36 - F9
|
||||
"10 48 F9 00 FF 01 6D 08", // test 37 - F9
|
||||
"10 00 F7 00 FF 01 B9 35 19", // test 38 - F7
|
||||
"30 00 FF 00 02 62 00 E7 01 AE 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00 80 00", // test 39 - SM100
|
||||
"30 00 FF 00 02 62 00 E4", // test 40 - SM100
|
||||
"10 48 F7 00 FF 01 A5 DF FF F7 7F 1F", // test 41 - gateway
|
||||
"30 00 FF 09 02 64 1E", // test 42 - SM100
|
||||
"08 00 18 00 05 03 30 00 00 00 00 04 40 80 00 02 17 80 00 00 00 FF 30 48 00 CB 00 00 00", // test 43 - sys pressure
|
||||
"90 00 FF 00 00 6F 03 01 00 BE 00 BF", // test 44 - FR10
|
||||
"08 00 E3 00 01 00 01 00 00 00 00 00 00 00 00 00 DF 00 64 55", // test 45 - heatpump Enviline
|
||||
"08 00 E5 00 00 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0A", // test 46 - heatpump Enviline
|
||||
"38 10 FF 00 03 2B 00 C7 07 C3 01", // test 47 - heatpump Enviline
|
||||
"08 0B 19 00 00 F7 80 00 80 00 00 00 00 00 03 58 97 0C 7B 1F 00 00 00 06 C4 DF 02 64 48 80 00", // test 48 - outdoor temp check
|
||||
"88 00 19 00 00 DC 80 00 80 00 FF FF 00 00 00 21 9A 06 E1 7C 00 00 00 06 C2 13 00 1E 90 80 00", // test 49 - check max length
|
||||
"30 00 FF 00 02 8E 00 00 41 82 00 00 28 36 00 00 82 21", // test 50 - SM100
|
||||
"10 00 FF 08 01 B9 26", // test 51 - EMS+ 0x1B9 set temp
|
||||
"10 00 F7 00 FF 01 B9 21 E9", // test 52 - EMS+ 0x1B9 F7 test
|
||||
"08 00 D1 00 00 80" // test 53 - outdoor temp
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
270
src/uart/emsuart_esp32.cpp
Normal file
270
src/uart/emsuart_esp32.cpp
Normal file
@@ -0,0 +1,270 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* ESP32 UART port by @ArwedL and improved by @MichaelDvP. See https://github.com/proddy/EMS-ESP/issues/380
|
||||
*/
|
||||
|
||||
#if defined(ESP32)
|
||||
|
||||
#include "uart/emsuart_esp32.h"
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
static intr_handle_t uart_handle;
|
||||
static RingbufHandle_t buf_handle = NULL;
|
||||
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;
|
||||
uint32_t emsTxWait;
|
||||
|
||||
/*
|
||||
* Task to handle the incoming data
|
||||
*/
|
||||
void EMSuart::emsuart_recvTask(void * para) {
|
||||
while (1) {
|
||||
size_t item_size;
|
||||
uint8_t * telegram = (uint8_t *)xRingbufferReceive(buf_handle, &item_size, portMAX_DELAY);
|
||||
uint8_t telegramSize = item_size;
|
||||
|
||||
if (telegram) {
|
||||
EMSESP::incoming_telegram(telegram, telegramSize);
|
||||
vRingbufferReturnItem(buf_handle, (void *)telegram);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* UART interrupt, on break read the fifo and put the whole telegram to ringbuffer
|
||||
*/
|
||||
void IRAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
|
||||
static uint8_t rxbuf[EMS_MAXBUFFERSIZE];
|
||||
static uint8_t length;
|
||||
|
||||
if (EMS_UART.int_st.brk_det) {
|
||||
EMS_UART.int_clr.brk_det = 1; // clear flag
|
||||
EMS_UART.conf0.txd_brk = 0; // disable <brk>
|
||||
if (emsTxBufIdx < emsTxBufLen) { // timer tx_mode is interrupted by <brk>
|
||||
emsTxBufIdx = emsTxBufLen; // stop timer mode
|
||||
drop_next_rx = true; // we have trash in buffer
|
||||
}
|
||||
length = 0;
|
||||
while (EMS_UART.status.rxfifo_cnt) {
|
||||
uint8_t rx = EMS_UART.fifo.rw_byte; // read all bytes from fifo
|
||||
if (length < EMS_MAXBUFFERSIZE) {
|
||||
rxbuf[length++] = rx;
|
||||
} else {
|
||||
drop_next_rx = true; // we have a overflow
|
||||
}
|
||||
}
|
||||
if ((!drop_next_rx) && ((length == 2) || (length > 4))) {
|
||||
int baseType = 0;
|
||||
xRingbufferSendFromISR(buf_handle, rxbuf, length - 1, &baseType);
|
||||
}
|
||||
drop_next_rx = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void IRAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
if (emsTxBufLen == 0) {
|
||||
return;
|
||||
}
|
||||
if (tx_mode_ > 50) {
|
||||
for (uint8_t i = 0; i < emsTxBufLen; i++) {
|
||||
EMS_UART.fifo.rw_byte = emsTxBuf[i];
|
||||
}
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send
|
||||
} else {
|
||||
if (emsTxBufIdx + 1 < emsTxBufLen) {
|
||||
EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx];
|
||||
timerAlarmWrite(timer, emsTxWait, false);
|
||||
timerAlarmEnable(timer);
|
||||
} else if (emsTxBufIdx + 1 == emsTxBufLen) {
|
||||
EMS_UART.fifo.rw_byte = emsTxBuf[emsTxBufIdx];
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send
|
||||
}
|
||||
emsTxBufIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* init UART driver
|
||||
*/
|
||||
void EMSuart::start(const uint8_t tx_mode) {
|
||||
if (tx_mode_ != 0xFF) { // uart already initialized
|
||||
tx_mode_ = tx_mode;
|
||||
restart();
|
||||
return;
|
||||
}
|
||||
tx_mode_ = tx_mode;
|
||||
|
||||
uart_config_t uart_config = {
|
||||
.baud_rate = EMSUART_BAUD,
|
||||
.data_bits = UART_DATA_8_BITS,
|
||||
.parity = UART_PARITY_DISABLE,
|
||||
.stop_bits = UART_STOP_BITS_1,
|
||||
.flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
|
||||
};
|
||||
uart_param_config(EMSUART_UART, &uart_config);
|
||||
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.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, &uart_handle);
|
||||
xTaskCreate(emsuart_recvTask, "emsuart_recvTask", 2048, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
|
||||
timer = timerBegin(1, 80, true); // timer prescale to 1 µs, countup
|
||||
timerAttachInterrupt(timer, &emsuart_tx_timer_intr_handler, true); // Timer with edge interrupt
|
||||
restart();
|
||||
}
|
||||
|
||||
/*
|
||||
* Stop, disables interrupt
|
||||
*/
|
||||
void EMSuart::stop() {
|
||||
EMS_UART.int_ena.val = 0; // disable all intr.
|
||||
// timerAlarmDisable(timer);
|
||||
};
|
||||
|
||||
/*
|
||||
* Restart Interrupt
|
||||
*/
|
||||
void EMSuart::restart() {
|
||||
if (EMS_UART.int_raw.brk_det) {
|
||||
EMS_UART.int_clr.brk_det = 1; // clear flag
|
||||
drop_next_rx = true; // and drop first frame
|
||||
}
|
||||
EMS_UART.int_ena.brk_det = 1; // activate only break
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 0;
|
||||
if (tx_mode_ == 1) {
|
||||
EMS_UART.idle_conf.tx_idle_num = 5;
|
||||
} else if (tx_mode_ == 2) {
|
||||
EMS_UART.idle_conf.tx_idle_num = 10;
|
||||
} else if (tx_mode_ == 3) {
|
||||
EMS_UART.idle_conf.tx_idle_num = 7;
|
||||
} else if (tx_mode_ == 4) {
|
||||
EMS_UART.idle_conf.tx_idle_num = 2;
|
||||
} else if (tx_mode_ == 5) {
|
||||
EMS_UART.idle_conf.tx_idle_num = 2;
|
||||
} else if (tx_mode_ <= 50) {
|
||||
EMS_UART.idle_conf.tx_idle_num = tx_mode_;
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ + 10);
|
||||
} else {
|
||||
EMS_UART.idle_conf.tx_idle_num = 2;
|
||||
emsTxWait = EMSUART_TX_BIT_TIME * (tx_mode_ - 50);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a 1-byte poll, ending with a <BRK>
|
||||
*/
|
||||
void EMSuart::send_poll(const uint8_t data) {
|
||||
EMS_UART.fifo.rw_byte = data;
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send data to Tx line, ending with a <BRK>
|
||||
* buf contains the CRC and len is #bytes including the CRC
|
||||
* returns code, 1=success
|
||||
*/
|
||||
uint16_t EMSuart::transmit(const uint8_t * buf, const uint8_t len) {
|
||||
if (len == 0 || len >= EMS_MAXBUFFERSIZE) {
|
||||
return EMS_TX_STATUS_ERR;
|
||||
}
|
||||
// needs to be disabled for the delayed modes otherwise the uart makes a <brk> after every byte
|
||||
EMS_UART.conf0.txd_brk = 0;
|
||||
|
||||
if (tx_mode_ > 5) { // timer controlled modes
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
emsTxBuf[i] = buf[i];
|
||||
}
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = len;
|
||||
timerAlarmWrite(timer, emsTxWait, false);
|
||||
timerAlarmEnable(timer);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
if (tx_mode_ == 5) { // wait before sending
|
||||
vTaskDelay(4 / portTICK_PERIOD_MS);
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
}
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
if (tx_mode_ == EMS_TXMODE_NEW) { // hardware controlled modes
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
}
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
if (tx_mode_ == EMS_TXMODE_EMSPLUS) { // EMS+ with long delay
|
||||
for (uint8_t i = 0; i < len - 1; i++) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
}
|
||||
EMS_UART.fifo.rw_byte = buf[len - 1];
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
if (tx_mode_ == EMS_TXMODE_HT3) { // HT3 with 7 bittimes delay
|
||||
for (uint8_t i = 0; i < len - 1; i++) {
|
||||
EMS_UART.fifo.rw_byte = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
}
|
||||
EMS_UART.fifo.rw_byte = buf[len - 1];
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// mode 1
|
||||
// flush fifos -- not supported in ESP32 uart #2!
|
||||
// EMS_UART.conf0.rxfifo_rst = 1;
|
||||
// EMS_UART.conf0.txfifo_rst = 1;
|
||||
for (uint8_t i = 0; i < len - 1; i++) {
|
||||
volatile uint8_t _usrxc = EMS_UART.status.rxfifo_cnt;
|
||||
EMS_UART.fifo.rw_byte = buf[i]; // send each Tx byte
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
while ((EMS_UART.status.rxfifo_cnt == _usrxc) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT); // burn CPU cycles...
|
||||
}
|
||||
}
|
||||
EMS_UART.fifo.rw_byte = buf[len - 1]; // send each Tx byte
|
||||
EMS_UART.conf0.txd_brk = 1; // <brk> after send, cleard by hardware after send
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
98
src/uart/emsuart_esp32.h
Normal file
98
src/uart/emsuart_esp32.h
Normal file
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* ESP32 UART port by @ArwedL and improved by @MichaelDvP. See https://github.com/proddy/EMS-ESP/issues/380
|
||||
*/
|
||||
#if defined(ESP32)
|
||||
|
||||
#ifndef EMSESP_EMSUART_H
|
||||
#define EMSESP_EMSUART_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/ringbuf.h"
|
||||
#include "freertos/queue.h"
|
||||
#include <driver/uart.h>
|
||||
#include <driver/timer.h>
|
||||
|
||||
#define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra for BRK
|
||||
|
||||
#define EMSUART_UART UART_NUM_2 // on the ESP32 we're using UART2
|
||||
#define EMS_UART UART2 // for intr setting
|
||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||
|
||||
#define EMS_TXMODE_DEFAULT 1
|
||||
#define EMS_TXMODE_EMSPLUS 2
|
||||
#define EMS_TXMODE_HT3 3
|
||||
#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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
|
||||
// customize the GPIO pins for RX and TX here
|
||||
#ifdef WEMOS_D1_32
|
||||
#define EMSUART_RXPIN 23 // 17 is UART2 RX. Use 23 for D7 on a Wemos D1-32 mini for backwards compatabilty
|
||||
#define EMSUART_TXPIN 5 // 16 is UART2 TX. Use 5 for D8 on a Wemos D1-32 mini for backwards compatabilty
|
||||
#else
|
||||
#define EMSUART_RXPIN 17 // 17 is UART2 RX. Use 23 for D7 on a Wemos D1-32 mini for backwards compatabilty
|
||||
#define EMSUART_TXPIN 16 // 16 is UART2 TX. Use 5 for D8 on a Wemos D1-32 mini for backwards compatabilty
|
||||
#endif
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
#define EMS_TX_STATUS_OK 1
|
||||
#define EMS_TX_STATUS_ERR 0
|
||||
|
||||
class EMSuart {
|
||||
public:
|
||||
EMSuart() = default;
|
||||
~EMSuart() = default;
|
||||
|
||||
static void start(const uint8_t tx_mode);
|
||||
static void send_poll(const uint8_t data);
|
||||
static void stop();
|
||||
static void restart();
|
||||
static uint16_t transmit(const uint8_t * buf, const uint8_t len);
|
||||
|
||||
private:
|
||||
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();
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
437
src/uart/emsuart_esp8266.cpp
Normal file
437
src/uart/emsuart_esp8266.cpp
Normal file
@@ -0,0 +1,437 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#if defined(ESP8266)
|
||||
|
||||
#include "uart/emsuart_esp8266.h"
|
||||
|
||||
#include "emsesp.h"
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
|
||||
EMSuart::EMSRxBuf_t * pEMSRxBuf;
|
||||
EMSuart::EMSRxBuf_t * paEMSRxBuf[EMS_MAXBUFFERS];
|
||||
uint8_t emsRxBufIdx = 0;
|
||||
uint8_t tx_mode_ = 0xFF;
|
||||
bool drop_next_rx = true;
|
||||
uint8_t emsTxBuf[EMS_MAXBUFFERSIZE];
|
||||
uint8_t emsTxBufIdx;
|
||||
uint8_t emsTxBufLen;
|
||||
uint32_t emsTxWait;
|
||||
bool EMSuart::sending_ = false;
|
||||
|
||||
//
|
||||
// Main interrupt handler
|
||||
// Important: must not use ICACHE_FLASH_ATTR
|
||||
//
|
||||
void ICACHE_RAM_ATTR EMSuart::emsuart_rx_intr_handler(void * para) {
|
||||
static uint8_t length = 0;
|
||||
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE + 2];
|
||||
|
||||
if (USIS(EMSUART_UART) & ((1 << UIBD))) { // BREAK detection = End of EMS data block
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // reset tx-brk
|
||||
if (emsTxBufIdx < emsTxBufLen) { // irq tx_mode is interrupted by <brk>
|
||||
emsTxBufIdx = emsTxBufLen; // stop tx
|
||||
drop_next_rx = true; // we have trash in buffer
|
||||
}
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
USIE(EMSUART_UART) &= ~(1 << UIFF); // disable fifo-full irq
|
||||
length = 0;
|
||||
while ((USS(EMSUART_UART) >> USRXC) & 0x0FF) { // read fifo into buffer
|
||||
uint8_t rx = USF(EMSUART_UART);
|
||||
if (length < EMS_MAXBUFFERSIZE) {
|
||||
uart_buffer[length++] = rx;
|
||||
} else {
|
||||
drop_next_rx = true;
|
||||
}
|
||||
}
|
||||
if (!drop_next_rx) {
|
||||
pEMSRxBuf->length = length;
|
||||
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
|
||||
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
|
||||
}
|
||||
drop_next_rx = false;
|
||||
sending_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* system task triggered on BRK interrupt
|
||||
* incoming received messages are always asynchronous
|
||||
* The full buffer is sent to EMSESP::incoming_telegram()
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::emsuart_recvTask(os_event_t * events) {
|
||||
EMSRxBuf_t * pCurrent = pEMSRxBuf;
|
||||
pEMSRxBuf = paEMSRxBuf[++emsRxBufIdx % EMS_MAXBUFFERS]; // next free EMS Receive buffer
|
||||
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
|
||||
pCurrent->length = 0;
|
||||
|
||||
// Ignore telegrams with no data value, then transmit EMS buffer, excluding the BRK
|
||||
if (length > 4 || length == 2) {
|
||||
EMSESP::incoming_telegram((uint8_t *)pCurrent->buffer, length - 1);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* flush everything left over in buffer, this clears both rx and tx FIFOs
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::emsuart_flush_fifos() {
|
||||
USC0(EMSUART_UART) |= ((1 << UCRXRST) | (1 << UCTXRST)); // set bits
|
||||
USC0(EMSUART_UART) &= ~((1 << UCRXRST) | (1 << UCTXRST)); // clear bits
|
||||
}
|
||||
|
||||
// ISR to Fire when Timer is triggered
|
||||
void ICACHE_RAM_ATTR EMSuart::emsuart_tx_timer_intr_handler() {
|
||||
if (tx_mode_ > 50) {
|
||||
for (uint8_t i = 0; i < emsTxBufLen; i++) {
|
||||
USF(EMSUART_UART) = emsTxBuf[i];
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
|
||||
} else {
|
||||
if (emsTxBufIdx > emsTxBufLen) {
|
||||
return;
|
||||
}
|
||||
if (emsTxBufIdx < emsTxBufLen) {
|
||||
USF(EMSUART_UART) = emsTxBuf[emsTxBufIdx];
|
||||
timer1_write(emsTxWait);
|
||||
} else if (emsTxBufIdx == emsTxBufLen) {
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
|
||||
}
|
||||
emsTxBufIdx++;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* init UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::start(uint8_t tx_mode) {
|
||||
if (tx_mode > 50) {
|
||||
emsTxWait = 5 * EMSUART_TX_BIT_TIME * (tx_mode - 50); // bittimes wait before sending
|
||||
} else if (tx_mode > 5) {
|
||||
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();
|
||||
return;
|
||||
}
|
||||
tx_mode_ = tx_mode;
|
||||
// allocate and preset EMS Receive buffers
|
||||
for (int i = 0; i < EMS_MAXBUFFERS; i++) {
|
||||
EMSRxBuf_t * p = (EMSRxBuf_t *)malloc(sizeof(EMSRxBuf_t));
|
||||
p->length = 0;
|
||||
paEMSRxBuf[i] = p;
|
||||
}
|
||||
pEMSRxBuf = paEMSRxBuf[0]; // reset EMS Rx Buffer
|
||||
|
||||
ETS_UART_INTR_DISABLE();
|
||||
ETS_UART_INTR_ATTACH(nullptr, nullptr);
|
||||
|
||||
// pin settings
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0TXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0TXD_U, FUNC_U0TXD);
|
||||
PIN_PULLUP_DIS(PERIPHS_IO_MUX_U0RXD_U);
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_U0RXD_U, FUNC_U0RXD);
|
||||
|
||||
// set 9600, 8 bits, no parity check, 1 stop bit
|
||||
USD(EMSUART_UART) = (UART_CLK_FREQ / EMSUART_BAUD);
|
||||
if (tx_mode_ == 5) {
|
||||
USC0(EMSUART_UART) = 0x2C; // 8N1.5
|
||||
} else {
|
||||
USC0(EMSUART_UART) = EMSUART_CONFIG; // 8N1
|
||||
}
|
||||
emsuart_flush_fifos();
|
||||
|
||||
// conf1 params
|
||||
// UCTOE = RX TimeOut enable (default is 1)
|
||||
// UCTOT = RX TimeOut Threshold (7 bit) = want this when no more data after 1 characters (default is 2)
|
||||
// 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) = (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);
|
||||
|
||||
// disable esp debug which will go to Tx and mess up the line - see https://github.com/espruino/Espruino/issues/655
|
||||
system_set_os_print(0);
|
||||
|
||||
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
|
||||
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
|
||||
}
|
||||
|
||||
/*
|
||||
* stop UART0 driver
|
||||
* This is called prior to an OTA upload and also before a save to the filesystem to prevent conflicts
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::stop() {
|
||||
ETS_UART_INTR_DISABLE();
|
||||
timer1_disable();
|
||||
}
|
||||
|
||||
/*
|
||||
* re-start UART0 driver
|
||||
*/
|
||||
void ICACHE_FLASH_ATTR EMSuart::restart() {
|
||||
if (USIR(EMSUART_UART) & ((1 << UIBD))) {
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
drop_next_rx = true;
|
||||
}
|
||||
ETS_UART_INTR_ENABLE();
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 0;
|
||||
timer1_enable(TIM_DIV16, TIM_EDGE, TIM_SINGLE);
|
||||
}
|
||||
|
||||
/*
|
||||
* 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)) {
|
||||
}
|
||||
|
||||
// To create a 11-bit <BRK> we set TXD_BRK bit so the break signal will
|
||||
// automatically be sent when the tx fifo is empty
|
||||
ETS_UART_INTR_DISABLE();
|
||||
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
|
||||
ETS_UART_INTR_ENABLE();
|
||||
}
|
||||
|
||||
/*
|
||||
* 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_ > 50) { // timer controlled modes
|
||||
emsTxBuf[0] = data;
|
||||
emsTxBufLen = 1;
|
||||
timer1_write(emsTxWait);
|
||||
} else if (tx_mode_ > 5) { // timer controlled modes
|
||||
emsTxBuf[0] = data;
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = 1;
|
||||
timer1_write(emsTxWait);
|
||||
} else if (tx_mode_ == 5) {
|
||||
delayMicroseconds(3000);
|
||||
USF(EMSUART_UART) = data;
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK);
|
||||
} 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>
|
||||
} else if (tx_mode_ == EMS_TXMODE_EMSPLUS) {
|
||||
USF(EMSUART_UART) = data;
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS);
|
||||
tx_brk(); // send <BRK>
|
||||
} else {
|
||||
// tx_mode 1
|
||||
// EMS1.0, same logic as in transmit
|
||||
ETS_UART_INTR_DISABLE();
|
||||
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...
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
|
||||
timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
while (!(USIR(EMSUART_UART) & (1 << UIBD)) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT);
|
||||
}
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK>
|
||||
ETS_UART_INTR_ENABLE();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Send data to Tx line, ending with a <BRK>
|
||||
* buf contains the CRC and len is #bytes including the CRC
|
||||
* returns code, 0=success, 1=brk error, 2=watchdog timeout
|
||||
*/
|
||||
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;
|
||||
|
||||
// all at once after a inititial timer delay
|
||||
if (tx_mode_ > 50) {
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
emsTxBuf[i] = buf[i];
|
||||
}
|
||||
emsTxBufLen = len;
|
||||
timer1_write(emsTxWait);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
// timer controlled modes with extra delay
|
||||
if (tx_mode_ > 5) {
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
emsTxBuf[i] = buf[i];
|
||||
}
|
||||
emsTxBufIdx = 0;
|
||||
emsTxBufLen = len;
|
||||
timer1_write(emsTxWait);
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
// fixed dealy before sending
|
||||
if (tx_mode_ == 5) {
|
||||
delayMicroseconds(3000);
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// new code from Michael. See https://github.com/proddy/EMS-ESP/issues/380
|
||||
if (tx_mode_ == EMS_TXMODE_NEW) { // tx_mode 4
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
}
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // send <BRK> at the end
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// EMS+ https://github.com/proddy/EMS-ESP/issues/23#
|
||||
if (tx_mode_ == EMS_TXMODE_EMSPLUS) { // tx_mode 2, With extra tx delay for EMS+
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
delayMicroseconds(EMSUART_TX_WAIT_PLUS); // 2070
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
// Junkers logic by @philrich, tx_mode 3
|
||||
if (tx_mode_ == EMS_TXMODE_HT3) {
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
USF(EMSUART_UART) = buf[i];
|
||||
// just to be safe wait for tx fifo empty (still needed?)
|
||||
while (((USS(EMSUART_UART) >> USTXC) & 0xff)) {
|
||||
}
|
||||
// wait until bits are sent on wire
|
||||
delayMicroseconds(EMSUART_TX_WAIT_HT3);
|
||||
}
|
||||
tx_brk(); // send <BRK>
|
||||
return EMS_TX_STATUS_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Logic for tx_mode of 1
|
||||
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
|
||||
*
|
||||
* Logic:
|
||||
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
|
||||
* after sending the last char we poll the Rx status until either
|
||||
* - size(Rx FIFO) == size(Tx-Telegram)
|
||||
* - <BRK> is detected
|
||||
* At end of receive we re-enable Rx-INT and send a Tx-BRK in loopback mode.
|
||||
*
|
||||
* EMS-Bus error handling
|
||||
* 1. Busmaster stops echoing on Tx w/o permission
|
||||
* 2. Busmaster cancel telegram by sending a BRK
|
||||
*
|
||||
* Case 1. is handled by a watchdog counter which is reset on each
|
||||
* Tx attempt. The timeout should be 20x EMSUART_TX_BIT_TIME plus
|
||||
* some smart guess for processing time on targeted EMS device.
|
||||
* We set Status to EMS_TX_WTD_TIMEOUT and return
|
||||
*
|
||||
* Case 2. is handled via a BRK chk during transmission.
|
||||
* We set Status to EMS_TX_BRK_DETECT and return
|
||||
*
|
||||
*/
|
||||
|
||||
// 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
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
volatile uint8_t _usrxc = (USS(EMSUART_UART) >> USRXC) & 0xFF;
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
USF(EMSUART_UART) = buf[i]; // send each Tx byte
|
||||
// wait for echo
|
||||
while ((((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) && (--timeoutcnt > 0)) {
|
||||
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 in the loopback and re=enable Rx-INT.
|
||||
// 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
|
||||
USC0(EMSUART_UART) |= (1 << UCBRK); // set <BRK>
|
||||
uint16_t timeoutcnt = EMSUART_TX_TIMEOUT;
|
||||
// wait until BRK detected...
|
||||
while (!(USIR(EMSUART_UART) & (1 << UIBD)) && (--timeoutcnt > 0)) {
|
||||
delayMicroseconds(EMSUART_TX_BUSY_WAIT);
|
||||
}
|
||||
|
||||
USC0(EMSUART_UART) &= ~(1 << UCBRK); // clear <BRK>
|
||||
}
|
||||
|
||||
ETS_UART_INTR_ENABLE(); // open up the FIFO again to start receiving
|
||||
return EMS_TX_STATUS_OK; // send the Tx ok status back
|
||||
}
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
94
src/uart/emsuart_esp8266.h
Normal file
94
src/uart/emsuart_esp8266.h
Normal file
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*/
|
||||
#if defined(ESP8266)
|
||||
|
||||
#ifndef EMSESP_EMSUART_H
|
||||
#define EMSESP_EMSUART_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#include <user_interface.h>
|
||||
|
||||
#define EMSUART_UART 0 // UART 0
|
||||
#define EMSUART_CONFIG 0x1C // 8N1 (8 bits, no parity, 1 stop bit)
|
||||
#define EMSUART_BAUD 9600 // uart baud rate for the EMS circuit
|
||||
|
||||
#define EMS_MAXBUFFERS 3 // buffers for circular filling to avoid collisions
|
||||
#define EMS_MAXBUFFERSIZE 33 // max size of the buffer. EMS packets are max 32 bytes, plus extra 2 for BRKs
|
||||
|
||||
#define EMSUART_recvTaskPrio 2 // 0, 1 or 2. 0 being the lowest
|
||||
#define EMSUART_recvTaskQueueLen 10 // number of queued Rx triggers
|
||||
|
||||
#define EMS_TXMODE_DEFAULT 1
|
||||
#define EMS_TXMODE_EMSPLUS 2
|
||||
#define EMS_TXMODE_HT3 3
|
||||
#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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
|
||||
namespace emsesp {
|
||||
|
||||
#define EMS_TX_STATUS_ERR 0
|
||||
#define EMS_TX_STATUS_OK 1
|
||||
|
||||
class EMSuart {
|
||||
public:
|
||||
EMSuart() = default;
|
||||
~EMSuart() = default;
|
||||
|
||||
static void ICACHE_FLASH_ATTR start(uint8_t tx_mode);
|
||||
static void ICACHE_FLASH_ATTR stop();
|
||||
static void ICACHE_FLASH_ATTR restart();
|
||||
static void ICACHE_FLASH_ATTR send_poll(uint8_t data);
|
||||
static uint16_t ICACHE_FLASH_ATTR transmit(uint8_t * buf, uint8_t len);
|
||||
static bool sending() {
|
||||
return sending_;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
uint8_t length;
|
||||
uint8_t buffer[EMS_MAXBUFFERSIZE];
|
||||
} EMSRxBuf_t;
|
||||
|
||||
private:
|
||||
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_;
|
||||
};
|
||||
|
||||
} // namespace emsesp
|
||||
|
||||
#endif
|
||||
#endif
|
||||
@@ -1 +1 @@
|
||||
#define APP_VERSION "1.9.5"
|
||||
#define EMSESP_APP_VERSION "2.0.0_b1" // based off b32
|
||||
|
||||
File diff suppressed because one or more lines are too long
1
src/websrc/3rdparty/css/sidebar.css
vendored
1
src/websrc/3rdparty/css/sidebar.css
vendored
@@ -1 +0,0 @@
|
||||
html {position: relative;overflow: scroll;overflow-x: hidden;min-height: 100% }::-webkit-scrollbar {width: 0px;background: transparent;}::-webkit-scrollbar-thumb {background: #e8e8e8;}body {background: #f1f3f6;margin-bottom: 60px }p {font-size: 1.1em;font-weight: 300;line-height: 1.7em;color: #999 }a, a:focus, a:hover {color: inherit;text-decoration: none;transition: all .3s }.navbar {padding: 15px 10px;background: #fff;border: none;border-radius: 0;margin-bottom: 40px;box-shadow: 1px 1px 3px rgba(0, 0, 0, .1) }#dismiss, #sidebar {background: #337ab7 }#content.navbar-btn {box-shadow: none;outline: 0;border: none }.line {width: 100%;height: 1px;border-bottom: 1px dashed #ddd;margin: 40px 0 }#sidebar {width: 250px;position: fixed;top: 0;left: -250px;height: 100vh;z-index: 999;color: #fff;transition: all .3s;overflow-y: auto;box-shadow: 3px 3px 3px rgba(0, 0, 0, .2) }@media screen and (min-width:768px) {#sidebar {left: 0 }.footer {margin-left: 250px }#ajaxcontent {margin-left: 250px }#dismiss, .navbar-btn {display: none }}#sidebar.active {left: 0 }#dismiss {width: 35px;height: 35px;line-height: 35px;text-align: center;position: absolute;top: 10px;right: 10px;cursor: pointer;-webkit-transition: all .3s;-o-transition: all .3s;transition: all .3s }#dismiss:hover {background: #fff;color: #337ab7 }.overlay {position: fixed;width: 100vw;height: 100vh;background: rgba(0, 0, 0, .7);z-index: 998;display: none }#sidebar .sidebar-header {padding: 20px;background: #337ab7 }#sidebar ul.components {padding: 20px 0;border-bottom: 1px solid #47748b }#content, ul.CTAs {padding: 20px }#sidebar ul p {color: #fff;padding: 10px }#sidebar ul li a {padding: 10px;font-size: 1.1em;display: block }#sidebar ul li a:hover {color: #337ab7;background: #fff }#sidebar ul li.active>a, a[aria-expanded=true] {color: #fff;background: #2e6da4 }a[data-toggle=collapse] {position: relative }a[aria-expanded=false]::before, a[aria-expanded=true]::before {content: '\e259';display: block;position: absolute;right: 20px;font-family: 'Glyphicons Halflings';font-size: .6em }#sidebar ul ul a, ul.CTAs a {font-size: .9em }a[aria-expanded=true]::before {content: '\e260' }#sidebar ul ul a {padding-left: 30px;background: #2e6da4 }ul.CTAs a {text-align: center;display: block;border-radius: 5px;margin-bottom: 5px }a.download {background: #fff;color: #337ab7 }#sidebar a.article, a.article:hover {background: #2e6da4;color: #fff }#content {width: 100%;min-height: 100vh;transition: all .3s;position: absolute;top: 0;right: 0 }.footer {position: fixed;bottom: 0;width: 100%;height: 45px;background-color: #f1f3f6 }i {margin-right: 1em }
|
||||
Binary file not shown.
File diff suppressed because one or more lines are too long
5
src/websrc/3rdparty/js/jquery-1.12.4.min.js
vendored
5
src/websrc/3rdparty/js/jquery-1.12.4.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -1,244 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link
|
||||
href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAYFBMVEX///8zMzM1NTU0NDQ2Njby8vJ4eHhBQUGSkpJdXV36+vqbm5tpaWlPT0/Ly8uGhoZxcXHAwMBVVVVJSUm3t7fu7u6mpqY8PDzo6OiPj4/U1NR+fn5ra2tGRkbb29vExMQQm/MfAAAA4klEQVQ4jd1SbXaDMAwD2/kAElpKIRTa7v63nGPY9gjhAKt+OZGeItspik9D3Vqt9eU+nPCtC0CMsh/rHN8EwjICga5Thicof4Dk1ME/CA8EYkOPNF9FYu5dhVGJYU4MSozOzVSrzkcF2b3AxktopJ4Ni6Had6L5BfDbAC58QKMOAnLbYTwR/LrajEBcQye1unEg9HvBHWMw33I5aOli2Xcx9NKGWUZ7WyfWJZOKwWQN68Tpme6rvm6rEtDrXaSY3J+C+q8Dz+Ft4E0hvwLPOcMzZlsZY/zSZf/LmkQxTtl/jG/cCAezKUvMMwAAAABJRU5ErkJggg=="
|
||||
rel="icon" type="image/x-icon" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
<title id="customname"></title>
|
||||
<link href="css/required.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="wrapper">
|
||||
<nav id="sidebar">
|
||||
<div id="dismiss">
|
||||
<i class="glyphicon glyphicon-arrow-left"></i>
|
||||
</div>
|
||||
<div class="sidebar-header">
|
||||
<h1 id="customname2" class="text-center" onclick="home();"></h1>
|
||||
<h6 id="mainver" class="text-center"></h6>
|
||||
</div>
|
||||
<ul class="list-unstyled components">
|
||||
<li class="active">
|
||||
<a href="#" id="custom_status"><i class="glyphicon glyphicon-home"></i>Dashboard</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="status"><i class="glyphicon glyphicon-equalizer"></i>System Status</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#homeSubmenu" data-toggle="collapse" aria-expanded="false"><i
|
||||
class="glyphicon glyphicon-cog"></i>Settings</a>
|
||||
<ul class="collapse list-unstyled" id="homeSubmenu">
|
||||
<li>
|
||||
<a href="#" id="network"><i class="glyphicon glyphicon-signal"></i>Wireless Network</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="general"><i class="glyphicon glyphicon-list-alt"></i>General Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="mqtt"><i class="glyphicon glyphicon-link"></i>MQTT Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="ntp"><i class="glyphicon glyphicon-hourglass"></i>Time Settings</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="custom"><i class="glyphicon glyphicon-wrench"></i>Custom Settings</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="backup"><i class="glyphicon glyphicon-floppy-disk"></i>Backup & Restore</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="reset"><i class="glyphicon glyphicon-repeat"></i>Factory Reset</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" id="restart"><i class="glyphicon glyphicon-refresh"></i>Restart System</a>
|
||||
</li>
|
||||
<li>
|
||||
<a data-toggle="modal" href="#update"><i class="glyphicon glyphicon-open"></i>Update Firmware</a>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="list-unstyled CTAs">
|
||||
<li>
|
||||
<a id="helpurl" target="_blank" class="article">Help</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="article" onclick="logout();">Logout</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-xs-3">
|
||||
<div style="position:fixed; top:0; bottom:40px; float:none;">
|
||||
<hr>
|
||||
</hr>
|
||||
<footer style="position:fixed; bottom: 0; height:40px;"><a id="appurl"
|
||||
href="https://github.com/proddy" target="_blank">
|
||||
<h6 id="appurl2"></h6>
|
||||
</a></footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</nav>
|
||||
|
||||
<div id="content">
|
||||
<button type="button" id="sidebarCollapse" class="btn btn-info navbar-btn">
|
||||
<i class="glyphicon glyphicon-menu-hamburger"></i>
|
||||
<span>Menu</span>
|
||||
</button>
|
||||
<div id="ajaxcontent">
|
||||
</div>
|
||||
|
||||
<div id="destroy" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Factory Reset</h4>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
<h5><b>Warning!</b> This action <strong>cannot</strong> be undone. This will permanently
|
||||
delete <strong>all
|
||||
the settings and logs.</strong> Please make sure you've made a backup before
|
||||
resetting!</h5>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5>Please type in the hostname of the device to confirm.</h5>
|
||||
<div style="clear:both;">
|
||||
<br>
|
||||
</div>
|
||||
<p>
|
||||
<input type="text" class="form-control input-block" id="compare"
|
||||
oninput="compareDestroy()">
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="destroybtn" type="button" disabled="" onclick="destroy();"
|
||||
class="btn btn-block btn-danger">I understand, reset all my settings</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="reboot" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Restart System</h4>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
<h5><b>Are you sure you want to restart the system?</b></h5>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button id="restartbtn" type="button" onclick="restart();"
|
||||
class="btn btn-block btn-danger">Restart</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="signin" class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog"
|
||||
aria-labelledby="myModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">Please enter password to login to the system</h4>
|
||||
</div>
|
||||
<div class="row">
|
||||
<br>
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="login-panel panel panel-default">
|
||||
<div class="panel-body">
|
||||
<form role="form" onsubmit="login(); return false">
|
||||
<fieldset>
|
||||
<div class="form-group">
|
||||
<input id="password" class="form-control" placeholder="Password"
|
||||
name="password" type="password" value="" required=""
|
||||
title="Please enter the password">
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="btn btn-success btn-md pull-right">Login</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="update" class="modal fade" role="dialog">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Update Firmware</h4>
|
||||
</div>
|
||||
<div class="alert alert-warning">
|
||||
<strong>Warning!</strong> Please make sure you've made a backup first before updating
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<div>
|
||||
<button id="updateb" onclick="switchfirmware()" type="submit" class="btn btn-success btn-sm pull-right">Release</button>
|
||||
</div>
|
||||
|
||||
<div id="onlineupdate">
|
||||
<h5 id=releasehead></h5>
|
||||
<div style="clear:both;">
|
||||
<br>
|
||||
</div>
|
||||
<pre id="releasebody">Getting update information from GitHub...</pre>
|
||||
<div class="pull-right">
|
||||
<a class="pull-right" id="downloadupdate">
|
||||
<button type="button" class="btn btn-primary">Download</button>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;">
|
||||
<br>
|
||||
</div>
|
||||
<div>
|
||||
<div class="form-group">
|
||||
<input id="binform" onchange="allowUpload();" type="file" name="update"
|
||||
accept=".bin">
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button onclick="upload();" class="btn btn-primary" id="upbtn"
|
||||
disabled="">Update</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;">
|
||||
<br>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="footer">
|
||||
<div id="commit" class="container"></div>
|
||||
</footer>
|
||||
<div class="overlay"></div>
|
||||
<script src="js/required.js"></script>
|
||||
<script src="js/myesp.js"></script>
|
||||
<script>start();</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,519 +0,0 @@
|
||||
<div id="backupcontent">
|
||||
<br>
|
||||
<br>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<legend>Backup</legend>
|
||||
<h6 class="text-muted">Please make sure that you make regular backups.</h6>
|
||||
<div>
|
||||
<button class="btn btn-link btn-sm" onclick="backupset();">Backup System Settings</button>
|
||||
<a id="downloadSet" style="display:none"></a>
|
||||
<button class="btn btn-link btn-sm" onclick="backupCustomSet();">Backup Custom Settings</button>
|
||||
<a id="downloadCustomSet" style="display:none"></a>
|
||||
</div>
|
||||
<br>
|
||||
<div>
|
||||
<legend>Restore</legend>
|
||||
<h6 class="text-muted">Restore system and custom settings.</h6>
|
||||
<label for="restoreSet" class="btn btn-link btn-sm">Restore System Settings</label>
|
||||
<input id="restoreSet" type="file" accept="text/json" onchange="restoreSet();" style="display:none;">
|
||||
<label for="restoreCustomSet" class="btn btn-link btn-sm">Restore Custom Settings</label>
|
||||
<input id="restoreCustomSet" type="file" accept="text/json" onchange="restoreCustomSet();"
|
||||
style="display:none;">
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div id="progresscontent">
|
||||
<br>
|
||||
<br>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<br>
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Please wait for about 10 seconds while the system restarts...</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="progress">
|
||||
<div id="updateprog" class="progress-bar progress-bar-striped active" role="progressbar"
|
||||
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:0%">0%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-footer text-center" id="reconnect" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="progressupload">
|
||||
<br>
|
||||
<br>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<br>
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Please wait for about a minute while the firmware uploads...</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="progress">
|
||||
<div id="updateprog" class="progress-bar progress-bar-striped active" role="progressbar"
|
||||
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width:0%">0%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="generalcontent">
|
||||
<br>
|
||||
<legend>General Settings</legend>
|
||||
<h6 class="text-muted">Setup general system settings</h6>
|
||||
<br>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Admin Password<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Password for logging into the web console"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input class="form-control input-sm" placeholder="Administrator Password" id="adminpwd" type="password">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Host Name<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Unique name of the device, used in MQTT, AP SSID and web/telnet hostname"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input class="form-control input-sm" placeholder="Hostname" id="hostname" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Serial Port<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Enabling Serial port will echo Telnet output to the monitoring console via USB"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="serialenabled">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="serialenabled" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Event Logging<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Enabling logging of all events to a remote SysLog"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="logeventsenabled">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="logeventsenabled" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Event Log Server IP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="IP address of the SysLog server running on UDP port 514"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" value="" style="display:inline;max-width:185px" id="log_ip"
|
||||
type="text">
|
||||
</span>
|
||||
</div>
|
||||
<br>
|
||||
<br>
|
||||
<div class="col-xs-9 col-md-8">
|
||||
<button onclick="savegeneral()" class="btn btn-primary btn-sm pull-right">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
<div style="clear:both;">
|
||||
<br>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div id="mqttcontent">
|
||||
<br>
|
||||
<legend>MQTT Settings</legend>
|
||||
<h6 class="text-muted">Set up access to an MQTT broker</h6>
|
||||
<br>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">MQTT<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Enable or Disable MQTT support"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="mqttenabled">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="mqttenabled" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">IP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" aria-hidden="true"
|
||||
data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT Server IP Address"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="IP address" style="display:inline;max-width:185px"
|
||||
id="mqttip" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Port<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT Server port number (default 1883)"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
|
||||
id="mqttport" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">QOS<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT QOS 0, 1 or 2 (default 0)"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
|
||||
id="mqttqos" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Keep Alive<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT Keep Alive time in seconds (default 60)"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
|
||||
id="mqttkeepalive" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Retain<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT Retain Flag"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="mqttretain">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="mqttretain" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Username<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT Server username (can be blank)"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
|
||||
id="mqttuser" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Password<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT Server password (can be blank)"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
|
||||
id="mqttpwd" type="password">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Base<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="MQTT topic prefix (<mqtt base>/<host name>/)"></i></label>
|
||||
<span class="col-xs-9">
|
||||
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
|
||||
id="mqttbase" type="text">
|
||||
</span>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Heartbeat<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Enable or Disable an automatic MQTT topic publish with system status"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="mqttheartbeat">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="mqttheartbeat" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
<div class="col-xs-9 col-md-8">
|
||||
<button onclick="savemqtt()" class="btn btn-primary btn-sm pull-right">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="networkcontent">
|
||||
<br>
|
||||
<legend>Wireless Settings</legend>
|
||||
<h6 class="text-muted">Enter the wireless network settings here, or use Scan to find nearby networks to join
|
||||
</h6>
|
||||
<br>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Mode<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="You can run your ESP in AP Mode (non-captive) or join an existing wireless network"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" name="wmode" id="wmodeap" onclick="handleAP();" checked>Access
|
||||
Point
|
||||
</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" name="wmode" id="wmodesta" onclick="handleSTA();">Client</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group" style="display:none" id="hideclient">
|
||||
<label class="col-xs-3">SSID<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="WiFi Network Name"></i></label>
|
||||
<span class="col-xs-7 col-md-5">
|
||||
<input class="form-control input-sm" id="inputtohide" type="text" name="ap_ssid">
|
||||
<select class="form-control input-sm" style="display:none;" id="ssid" onchange="listBSSID();"></select>
|
||||
</span>
|
||||
<span class="col-xs-2">
|
||||
<button id="scanb" type="button" class="btn btn-info btn-xs" style="display:none;"
|
||||
onclick="scanWifi()">Scan...</button>
|
||||
</span>
|
||||
|
||||
<br><br>
|
||||
<label class="col-xs-3">Password<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="WiFi Password"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input id="wifipass" class="form-control input-sm" name="ap_passwd" type="password">
|
||||
</span>
|
||||
|
||||
<br><br>
|
||||
<h6 style="margin-left: 10px;" class="text-muted">If you're not using DHCP and want a fixed IP address enter the
|
||||
values below:</h6>
|
||||
|
||||
<br>
|
||||
<label class="col-xs-3">Static IP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="static IP address"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input type="text" class="form-control input-sm" id="staticip" placeholder="">
|
||||
</span>
|
||||
|
||||
<br><br>
|
||||
<label class="col-xs-3">Gateway IP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="gateway IP address"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input type="text" class="form-control input-sm" id="gatewayip" placeholder="">
|
||||
</span>
|
||||
|
||||
<br><br>
|
||||
<label class="col-xs-3">Network Mask<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="network mask"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input type="text" class="form-control input-sm" id="nmask" placeholder="255.255.255.0">
|
||||
</span>
|
||||
|
||||
<br><br>
|
||||
<label class="col-xs-3">DNS IP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="DNS IP address"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input type="text" class="form-control input-sm" id="dnsip" placeholder="">
|
||||
</span>
|
||||
</div>
|
||||
<br><br>
|
||||
<div class="col-xs-9 col-md-8">
|
||||
<button onclick="savenetwork()" class="btn btn-primary btn-sm pull-right">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ntpcontent">
|
||||
<br>
|
||||
<legend>Time Settings</legend>
|
||||
<h6 class="text-muted">With Network Time Protocol (NTP) enabled, all times are adjusted to the local timezone
|
||||
and
|
||||
respect daylight saving time (DST)</h6>
|
||||
<br>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Device Time</label>
|
||||
<span id="utc" class="col-xs-9 col-md-5"></span>
|
||||
<div class="col-xs-3">
|
||||
<button onclick="forcentp()" id="forcentp" class="btn btn-info btn-sm">Sync Device with Internet
|
||||
Time</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">NTP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Enable NTP - requires an internet connection"></i></label>
|
||||
<div class="col-xs-9">
|
||||
<form>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="1" onclick="handleNTPON();" name="ntpenabled">Enabled</label>
|
||||
<label class="radio-inline">
|
||||
<input type="radio" value="0" onclick="handleNTPOFF();" name="ntpenabled" checked>Disabled</label>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">NTP Server Address<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="The URL or IP of the NTP server. Choose the nearest server for better accuracy (see https://www.ntppool.org)"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input class="form-control input-sm" placeholder="eg. pool.ntp.org" value="pool.ntp.org" id="ntpserver"
|
||||
type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Recalibrate Time<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
|
||||
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
|
||||
data-content="Time in minutes between scheduled NTP refreshes"></i></label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<input class="form-control input-sm" placeholder="in Minutes" value="30" id="interval" type="text">
|
||||
</span>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="row form-group">
|
||||
<label class="col-xs-3">Time Zone</label>
|
||||
<span class="col-xs-9 col-md-5">
|
||||
<select class="form-control input-sm" name="timzeone" id="timezone">
|
||||
<option value="0">Australia Eastern (Sydney, Melbourne)</option>
|
||||
<option value="1">Moscow (MSK, does not observe DST)</option>
|
||||
<option selected="selected" value="2">Central European Time (Frankfurt, Paris, Amsterdam)</option>
|
||||
<option value="3">United Kingdom (London, Belfast)</option>
|
||||
<option value="4">UTC</option>
|
||||
<option value="5">US Eastern (New York, Detroit)</option>
|
||||
<option value="6">US Central (Chicago, Houston)</option>
|
||||
<option value="7">US Mountain (Denver, Salt Lake City)</option>
|
||||
<option value="8">Arizona (no DST)</option>
|
||||
<option value="9">US Pacific (Las Vegas, Los Angeles)</option>
|
||||
<option value="10">Turkey</option>
|
||||
</select>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
<div class="col-xs-9 col-md-8">
|
||||
<button onclick="saventp()" class="btn btn-primary btn-sm pull-right">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statuscontent">
|
||||
<br>
|
||||
<div class="row text-left">
|
||||
<div class="col-md-8 col-md-offset-2">
|
||||
<h2>System Status</h2>
|
||||
<h6>Current health of the ESP is displayed here</h6>
|
||||
<div class="panel panel-default table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed">
|
||||
<caption>General</caption>
|
||||
<tr>
|
||||
<th>Uptime</th>
|
||||
<td id="uptime"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>System Load</th>
|
||||
<td id="systemload"> %</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel panel-default table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed">
|
||||
<caption>Storage</caption>
|
||||
<tr>
|
||||
<th>Free Heap</th>
|
||||
<td>
|
||||
<div class="progress" style="margin-bottom: 0 !important;">
|
||||
<div id="heap" class="progress-bar progress-bar-primary" role="progressbar">
|
||||
Progress
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Free Flash</th>
|
||||
<td>
|
||||
<div class='progress' style="margin-bottom: 0 !important;">
|
||||
<div id="flash" class="progress-bar progress-bar-primary" role="progressbar">
|
||||
Progress
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Free SPIFFS</th>
|
||||
<td>
|
||||
<div class='progress' style="margin-bottom: 0 !important;">
|
||||
<div id="spiffs" class="progress-bar progress-bar-primary" role="progressbar">
|
||||
Progress
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel panel-default table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed">
|
||||
<caption>Wireless Network</caption>
|
||||
<tr>
|
||||
<th>SSID</th>
|
||||
<td id="ssidstat"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<td id="ip"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>MAC Address</th>
|
||||
<td id="mac"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Signal Strength</th>
|
||||
<td id="signalstr"> %</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-default table-responsive">
|
||||
<table class="table table-hover table-striped table-condensed" border=1>
|
||||
<caption>MQTT <div id="mqttconnected"></div> <div id="mqttheartbeat"></div>
|
||||
</caption>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group" style="text-align: center;">
|
||||
<button onclick="refreshStatus()" class="btn btn-info">Refresh</button>
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
@@ -1,786 +0,0 @@
|
||||
var version = "";
|
||||
|
||||
var websock = null;
|
||||
var wsUri = "ws://" + window.location.host + "/ws";
|
||||
var ntpSeconds;
|
||||
var ajaxobj;
|
||||
|
||||
var custom_config = {};
|
||||
|
||||
var xDown = null;
|
||||
var yDown = null;
|
||||
|
||||
var file = {};
|
||||
var backupstarted = false;
|
||||
var updateurl = "";
|
||||
var updateurl_dev = "";
|
||||
|
||||
var use_beta_firmware = false;
|
||||
|
||||
var myespcontent;
|
||||
|
||||
var formData = new FormData();
|
||||
|
||||
var nextIsNotJson = false;
|
||||
|
||||
var config = {};
|
||||
|
||||
function deviceTime() {
|
||||
var t = new Date(0); // The 0 there is the key, which sets the date to the epoch
|
||||
t.setUTCSeconds(ntpSeconds);
|
||||
document.getElementById("utc").innerHTML = t.toUTCString().slice(0, -3);
|
||||
}
|
||||
|
||||
function handleNTPON() {
|
||||
document.getElementById("forcentp").style.display = "block";
|
||||
}
|
||||
|
||||
function handleNTPOFF() {
|
||||
document.getElementById("forcentp").style.display = "none";
|
||||
}
|
||||
|
||||
function listntp() {
|
||||
websock.send("{\"command\":\"gettime\"}");
|
||||
|
||||
document.getElementById("ntpserver").value = config.ntp.server;
|
||||
document.getElementById("interval").value = config.ntp.interval;
|
||||
document.getElementById("timezone").value = config.ntp.timezone;
|
||||
|
||||
if (config.ntp.enabled) {
|
||||
$("input[name=\"ntpenabled\"][value=\"1\"]").prop("checked", true);
|
||||
handleNTPON();
|
||||
} else {
|
||||
handleNTPOFF();
|
||||
}
|
||||
|
||||
browserTime();
|
||||
deviceTime();
|
||||
}
|
||||
|
||||
function home() {
|
||||
window.location = '/';
|
||||
}
|
||||
|
||||
function restart_alert() {
|
||||
$("#commit").fadeOut(200, function () {
|
||||
$(this).css("background", "gold").fadeIn(1000);
|
||||
});
|
||||
document.getElementById("commit").innerHTML = "<h6>Settings have changed. It's recommended to reboot the system. Click here to restart.</h6>";
|
||||
|
||||
$("#commit").click(function () {
|
||||
$("#reboot").modal("show");
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
function saveconfig() {
|
||||
websock.send(JSON.stringify(config));
|
||||
restart_alert();
|
||||
}
|
||||
|
||||
function custom_saveconfig() {
|
||||
websock.send(JSON.stringify(custom_config));
|
||||
restart_alert();
|
||||
}
|
||||
|
||||
function saventp() {
|
||||
config.ntp.server = document.getElementById("ntpserver").value;
|
||||
config.ntp.interval = parseInt(document.getElementById("interval").value);
|
||||
config.ntp.timezone = parseInt(document.getElementById("timezone").value);
|
||||
|
||||
config.ntp.enabled = false;
|
||||
if (parseInt($("input[name=\"ntpenabled\"]:checked").val()) === 1) {
|
||||
config.ntp.enabled = true;
|
||||
}
|
||||
|
||||
saveconfig();
|
||||
}
|
||||
|
||||
function forcentp() {
|
||||
websock.send("{\"command\":\"forcentp\"}");
|
||||
}
|
||||
|
||||
function savegeneral() {
|
||||
var a = document.getElementById("adminpwd").value;
|
||||
if (a === null || a === "") {
|
||||
alert("Administrator password cannot be empty");
|
||||
return;
|
||||
}
|
||||
config.general.password = a;
|
||||
config.general.hostname = document.getElementById("hostname").value;
|
||||
|
||||
config.general.serial = false;
|
||||
if (parseInt($("input[name=\"serialenabled\"]:checked").val()) === 1) {
|
||||
config.general.serial = true;
|
||||
}
|
||||
|
||||
config.general.log_events = false;
|
||||
if (parseInt($("input[name=\"logeventsenabled\"]:checked").val()) === 1) {
|
||||
config.general.log_events = true;
|
||||
}
|
||||
|
||||
config.general.log_ip = document.getElementById("log_ip").value;
|
||||
|
||||
saveconfig();
|
||||
}
|
||||
|
||||
function savemqtt() {
|
||||
config.mqtt.enabled = false;
|
||||
if (parseInt($("input[name=\"mqttenabled\"]:checked").val()) === 1) {
|
||||
config.mqtt.enabled = true;
|
||||
}
|
||||
|
||||
config.mqtt.heartbeat = false;
|
||||
if (parseInt($("input[name=\"mqttheartbeat\"]:checked").val()) === 1) {
|
||||
config.mqtt.heartbeat = true;
|
||||
}
|
||||
|
||||
config.mqtt.retain = false;
|
||||
if (parseInt($("input[name=\"mqttretain\"]:checked").val()) === 1) {
|
||||
config.mqtt.retain = true;
|
||||
}
|
||||
|
||||
config.mqtt.ip = document.getElementById("mqttip").value;
|
||||
config.mqtt.port = parseInt(document.getElementById("mqttport").value);
|
||||
config.mqtt.qos = parseInt(document.getElementById("mqttqos").value);
|
||||
config.mqtt.keepalive = parseInt(document.getElementById("mqttkeepalive").value);
|
||||
config.mqtt.base = document.getElementById("mqttbase").value;
|
||||
config.mqtt.user = document.getElementById("mqttuser").value;
|
||||
config.mqtt.password = document.getElementById("mqttpwd").value;
|
||||
|
||||
saveconfig();
|
||||
}
|
||||
|
||||
function savenetwork() {
|
||||
var wmode = 0;
|
||||
if (document.getElementById("inputtohide").style.display === "none") {
|
||||
var b = document.getElementById("ssid");
|
||||
config.network.ssid = b.options[b.selectedIndex].value;
|
||||
} else {
|
||||
config.network.ssid = document.getElementById("inputtohide").value;
|
||||
}
|
||||
|
||||
if (document.getElementById("wmodeap").checked) {
|
||||
wmode = 1;
|
||||
}
|
||||
|
||||
config.network.wmode = wmode;
|
||||
config.network.password = document.getElementById("wifipass").value;
|
||||
config.network.staticip = document.getElementById("staticip").value;
|
||||
config.network.gatewayip = document.getElementById("gatewayip").value;
|
||||
config.network.nmask = document.getElementById("nmask").value;
|
||||
config.network.dnsip = document.getElementById("dnsip").value;
|
||||
|
||||
saveconfig();
|
||||
}
|
||||
|
||||
function inProgress(callback) {
|
||||
$("body").load("myesp.html #progresscontent", function (responseTxt, statusTxt, xhr) {
|
||||
if (statusTxt === "success") {
|
||||
$(".progress").css("height", "40");
|
||||
$(".progress").css("font-size", "xx-large");
|
||||
var i = 0;
|
||||
var prg = setInterval(function () {
|
||||
$(".progress-bar").css("width", i + "%").attr("aria-valuenow", i).html(i + "%");
|
||||
i = i + 5;
|
||||
if (i === 105) {
|
||||
clearInterval(prg);
|
||||
var a = document.createElement("a");
|
||||
a.href = "http://" + config.general.hostname + ".local";
|
||||
a.innerText = "Re-connect...";
|
||||
document.getElementById("reconnect").appendChild(a);
|
||||
document.getElementById("reconnect").style.display = "block";
|
||||
document.getElementById("updateprog").className = "progress-bar progress-bar-success";
|
||||
document.getElementById("updateprog").innerHTML = "Completed";
|
||||
}
|
||||
}, 500);
|
||||
switch (callback) {
|
||||
case "upload":
|
||||
$.ajax({
|
||||
url: "/update",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false
|
||||
});
|
||||
break;
|
||||
case "destroy":
|
||||
websock.send("{\"command\":\"destroy\"}");
|
||||
break;
|
||||
case "restart":
|
||||
websock.send("{\"command\":\"restart\"}");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
}).hide().fadeIn();
|
||||
}
|
||||
|
||||
function inProgressUpload() {
|
||||
$("body").load("myesp.html #progressupload", function (responseTxt, statusTxt, xhr) {
|
||||
if (statusTxt === "success") {
|
||||
$(".progress").css("height", "40");
|
||||
$(".progress").css("font-size", "xx-large");
|
||||
var i = 0;
|
||||
var prg = setInterval(function () {
|
||||
$(".progress-bar").css("width", i + "%").attr("aria-valuenow", i).html(i + "%");
|
||||
i = i + 1;
|
||||
if (i === 101) {
|
||||
clearInterval(prg);
|
||||
document.getElementById("updateprog").className = "progress-bar progress-bar-success";
|
||||
document.getElementById("updateprog").innerHTML = "Completed";
|
||||
}
|
||||
}, 500);
|
||||
$.ajax({
|
||||
url: "/update",
|
||||
type: "POST",
|
||||
data: formData,
|
||||
processData: false,
|
||||
contentType: false
|
||||
});
|
||||
}
|
||||
}).hide().fadeIn();
|
||||
}
|
||||
|
||||
function handleSTA() {
|
||||
document.getElementById("scanb").style.display = "block";
|
||||
document.getElementById("hideclient").style.display = "block";
|
||||
}
|
||||
|
||||
function handleAP() {
|
||||
document.getElementById("ssid").style.display = "none";
|
||||
document.getElementById("scanb").style.display = "none";
|
||||
document.getElementById("hideclient").style.display = "none";
|
||||
|
||||
document.getElementById("inputtohide").style.display = "block";
|
||||
}
|
||||
|
||||
function listnetwork() {
|
||||
document.getElementById("inputtohide").value = config.network.ssid;
|
||||
document.getElementById("wifipass").value = config.network.password;
|
||||
if (config.network.wmode === 1) {
|
||||
document.getElementById("wmodeap").checked = true;
|
||||
handleAP();
|
||||
} else {
|
||||
document.getElementById("wmodesta").checked = true;
|
||||
handleSTA();
|
||||
}
|
||||
|
||||
document.getElementById("staticip").value = config.network.staticip;
|
||||
document.getElementById("gatewayip").value = config.network.gatewayip;
|
||||
document.getElementById("nmask").value = config.network.nmask;
|
||||
document.getElementById("dnsip").value = config.network.dnsip;
|
||||
|
||||
}
|
||||
|
||||
function listgeneral() {
|
||||
document.getElementById("adminpwd").value = config.general.password;
|
||||
document.getElementById("hostname").value = config.general.hostname;
|
||||
|
||||
if (config.general.serial) {
|
||||
$("input[name=\"serialenabled\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (config.general.log_events) {
|
||||
$("input[name=\"logeventsenabled\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
document.getElementById("log_ip").value = config.general.log_ip;
|
||||
|
||||
}
|
||||
|
||||
function listmqtt() {
|
||||
if (config.mqtt.enabled) {
|
||||
$("input[name=\"mqttenabled\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (config.mqtt.heartbeat) {
|
||||
$("input[name=\"mqttheartbeat\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
if (config.mqtt.retain) {
|
||||
$("input[name=\"mqttretain\"][value=\"1\"]").prop("checked", true);
|
||||
}
|
||||
|
||||
document.getElementById("mqttip").value = config.mqtt.ip;
|
||||
document.getElementById("mqttport").value = config.mqtt.port;
|
||||
document.getElementById("mqttqos").value = config.mqtt.qos;
|
||||
document.getElementById("mqttkeepalive").value = config.mqtt.keepalive;
|
||||
document.getElementById("mqttbase").value = config.mqtt.base;
|
||||
document.getElementById("mqttuser").value = config.mqtt.user;
|
||||
document.getElementById("mqttpwd").value = config.mqtt.password;
|
||||
}
|
||||
|
||||
function listBSSID() {
|
||||
var select = document.getElementById("ssid");
|
||||
document.getElementById("wifibssid").value = select.options[select.selectedIndex].bssidvalue;
|
||||
}
|
||||
|
||||
function listSSID(obj) {
|
||||
var select = document.getElementById("ssid");
|
||||
for (var i = 0; i < obj.list.length; i++) {
|
||||
var x = parseInt(obj.list[i].rssi);
|
||||
var percentage = Math.min(Math.max(2 * (x + 100), 0), 100);
|
||||
var opt = document.createElement("option");
|
||||
opt.value = obj.list[i].ssid;
|
||||
opt.bssidvalue = obj.list[i].bssid;
|
||||
opt.innerHTML = "BSSID: " + obj.list[i].bssid + ", Signal Strength: %" + percentage + ", Network: " + obj.list[i].ssid;
|
||||
select.appendChild(opt);
|
||||
}
|
||||
document.getElementById("scanb").innerHTML = "Re-scan...";
|
||||
listBSSID();
|
||||
}
|
||||
|
||||
function scanWifi() {
|
||||
websock.send("{\"command\":\"scan\"}");
|
||||
document.getElementById("scanb").innerHTML = "...";
|
||||
document.getElementById("inputtohide").style.display = "none";
|
||||
var node = document.getElementById("ssid");
|
||||
node.style.display = "inline";
|
||||
while (node.hasChildNodes()) {
|
||||
node.removeChild(node.lastChild);
|
||||
}
|
||||
}
|
||||
|
||||
function isVisible(e) {
|
||||
return !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length);
|
||||
}
|
||||
|
||||
function colorStatusbar(ref) {
|
||||
var percentage = ref.style.width.slice(0, -1);
|
||||
if (percentage > 50) { ref.className = "progress-bar progress-bar-success"; } else if (percentage > 25) { ref.className = "progress-bar progress-bar-warning"; } else { ref.class = "progress-bar progress-bar-danger"; }
|
||||
}
|
||||
|
||||
function listStats() {
|
||||
|
||||
document.getElementById("uptime").innerHTML = ajaxobj.uptime;
|
||||
|
||||
document.getElementById("heap").innerHTML = ajaxobj.heap + " bytes";
|
||||
document.getElementById("heap").style.width = (ajaxobj.heap * 100) / ajaxobj.initheap + "%";
|
||||
colorStatusbar(document.getElementById("heap"));
|
||||
|
||||
document.getElementById("flash").innerHTML = ajaxobj.availsize + " KB";
|
||||
document.getElementById("flash").style.width = (ajaxobj.availsize * 100) / (ajaxobj.availsize + ajaxobj.sketchsize) + "%";
|
||||
colorStatusbar(document.getElementById("flash"));
|
||||
|
||||
document.getElementById("spiffs").innerHTML = ajaxobj.availspiffs + " KB";
|
||||
document.getElementById("spiffs").style.width = (ajaxobj.availspiffs * 100) / ajaxobj.spiffssize + "%";
|
||||
colorStatusbar(document.getElementById("spiffs"));
|
||||
|
||||
document.getElementById("ssidstat").innerHTML = ajaxobj.ssid;
|
||||
document.getElementById("ip").innerHTML = ajaxobj.ip;
|
||||
document.getElementById("mac").innerHTML = ajaxobj.mac;
|
||||
document.getElementById("signalstr").innerHTML = ajaxobj.signalstr + " %";
|
||||
document.getElementById("systemload").innerHTML = ajaxobj.systemload + " %";
|
||||
|
||||
if (ajaxobj.mqttconnected) {
|
||||
document.getElementById("mqttconnected").innerHTML = "MQTT is connected";
|
||||
document.getElementById("mqttconnected").className = "label label-success";
|
||||
} else {
|
||||
document.getElementById("mqttconnected").innerHTML = "MQTT is not connected";
|
||||
document.getElementById("mqttconnected").className = "label label-danger";
|
||||
}
|
||||
|
||||
if (ajaxobj.mqttheartbeat) {
|
||||
document.getElementById("mqttheartbeat").innerHTML = "MQTT hearbeat is enabled";
|
||||
document.getElementById("mqttheartbeat").className = "label label-success";
|
||||
} else {
|
||||
document.getElementById("mqttheartbeat").innerHTML = "MQTT hearbeat is disabled";
|
||||
document.getElementById("mqttheartbeat").className = "label label-primary";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getContent(contentname) {
|
||||
$("#dismiss").click();
|
||||
$(".overlay").fadeOut().promise().done(function () {
|
||||
var content = $(contentname).html();
|
||||
$("#ajaxcontent").html(content).promise().done(function () {
|
||||
switch (contentname) {
|
||||
case "#statuscontent":
|
||||
listStats();
|
||||
break;
|
||||
case "#backupcontent":
|
||||
break;
|
||||
case "#ntpcontent":
|
||||
listntp();
|
||||
break;
|
||||
case "#mqttcontent":
|
||||
listmqtt();
|
||||
break;
|
||||
case "#generalcontent":
|
||||
listgeneral();
|
||||
break;
|
||||
case "#networkcontent":
|
||||
listnetwork();
|
||||
break;
|
||||
case "#customcontent":
|
||||
listcustom();
|
||||
break;
|
||||
case "#custom_statuscontent":
|
||||
var version = "version " + ajaxobj.version;
|
||||
$("#mainver").text(version);
|
||||
$("#customname").text(ajaxobj.customname);
|
||||
var customname2 = " " + ajaxobj.customname;
|
||||
$("#customname2").text(customname2);
|
||||
|
||||
var elem;
|
||||
|
||||
if (config.network.wmode === 0) {
|
||||
elem = document.getElementById("helpurl");
|
||||
var helpurl = ajaxobj.appurl + "/wiki";
|
||||
elem.setAttribute("href", helpurl);
|
||||
document.getElementById("helpurl").style.display = "block";
|
||||
} else {
|
||||
document.getElementById("helpurl").style.display = "none";
|
||||
}
|
||||
|
||||
elem = document.getElementById("appurl");
|
||||
elem.setAttribute("href", ajaxobj.appurl);
|
||||
$("#appurl2").text(ajaxobj.appurl);
|
||||
|
||||
updateurl = ajaxobj.updateurl;
|
||||
updateurl_dev = ajaxobj.updateurl_dev;
|
||||
listCustomStats();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$("[data-toggle=\"popover\"]").popover({
|
||||
container: "body"
|
||||
});
|
||||
$(this).hide().fadeIn();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function backupset() {
|
||||
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(config, null, 2));
|
||||
var dlAnchorElem = document.getElementById("downloadSet");
|
||||
dlAnchorElem.setAttribute("href", dataStr);
|
||||
dlAnchorElem.setAttribute("download", "system_config.json");
|
||||
dlAnchorElem.click();
|
||||
}
|
||||
|
||||
function backupCustomSet() {
|
||||
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(custom_config, null, 2));
|
||||
var dlAnchorElem = document.getElementById("downloadCustomSet");
|
||||
dlAnchorElem.setAttribute("href", dataStr);
|
||||
dlAnchorElem.setAttribute("download", "custom_config.json");
|
||||
dlAnchorElem.click();
|
||||
}
|
||||
|
||||
function restoreSet() {
|
||||
var input = document.getElementById("restoreSet");
|
||||
var reader = new FileReader();
|
||||
if ("files" in input) {
|
||||
if (input.files.length === 0) {
|
||||
alert("You did not select file to restore!");
|
||||
} else {
|
||||
reader.onload = function () {
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(reader.result);
|
||||
} catch (e) {
|
||||
alert("Not a valid backup file!");
|
||||
return;
|
||||
}
|
||||
if (json.command === "configfile") {
|
||||
var x = confirm("System Config file seems to be valid, do you wish to continue?");
|
||||
if (x) {
|
||||
config = json;
|
||||
saveconfig();
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.readAsText(input.files[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function restoreCustomSet() {
|
||||
var input = document.getElementById("restoreCustomSet");
|
||||
var reader = new FileReader();
|
||||
if ("files" in input) {
|
||||
if (input.files.length === 0) {
|
||||
alert("You did not select file to restore!");
|
||||
} else {
|
||||
reader.onload = function () {
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(reader.result);
|
||||
} catch (e) {
|
||||
alert("Not a valid backup file!");
|
||||
return;
|
||||
}
|
||||
if (json.command === "custom_configfile") {
|
||||
var x = confirm("Custom Config file seems to be valid, do you wish to continue?");
|
||||
if (x) {
|
||||
custom_config = json;
|
||||
custom_saveconfig();
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.readAsText(input.files[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function twoDigits(value) {
|
||||
if (value < 10) {
|
||||
return "0" + value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
function socketMessageListener(evt) {
|
||||
var obj = JSON.parse(evt.data);
|
||||
if (obj.hasOwnProperty("command")) {
|
||||
switch (obj.command) {
|
||||
case "status":
|
||||
ajaxobj = obj;
|
||||
getContent("#statuscontent");
|
||||
break;
|
||||
case "custom_settings":
|
||||
ajaxobj = obj;
|
||||
break;
|
||||
case "custom_status":
|
||||
ajaxobj = obj;
|
||||
getContent("#custom_statuscontent");
|
||||
break;
|
||||
case "gettime":
|
||||
ntpSeconds = obj.epoch;
|
||||
deviceTime();
|
||||
break;
|
||||
case "ssidlist":
|
||||
listSSID(obj);
|
||||
break;
|
||||
case "configfile":
|
||||
config = obj;
|
||||
break;
|
||||
case "custom_configfile":
|
||||
custom_config = obj;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function compareDestroy() {
|
||||
if (config.general.hostname === document.getElementById("compare").value) {
|
||||
$("#destroybtn").prop("disabled", false);
|
||||
} else { $("#destroybtn").prop("disabled", true); }
|
||||
}
|
||||
|
||||
function destroy() {
|
||||
inProgress("destroy");
|
||||
}
|
||||
|
||||
function restart() {
|
||||
inProgress("restart");
|
||||
}
|
||||
|
||||
function handleTouchStart(evt) {
|
||||
xDown = evt.touches[0].clientX;
|
||||
yDown = evt.touches[0].clientY;
|
||||
}
|
||||
|
||||
function handleTouchMove(evt) {
|
||||
if (!xDown || !yDown) {
|
||||
return;
|
||||
}
|
||||
|
||||
var xUp = evt.touches[0].clientX;
|
||||
var yUp = evt.touches[0].clientY;
|
||||
|
||||
var xDiff = xDown - xUp;
|
||||
var yDiff = yDown - yUp;
|
||||
|
||||
if (Math.abs(xDiff) > Math.abs(yDiff)) { /*most significant*/
|
||||
if (xDiff > 0) {
|
||||
$("#dismiss").click();
|
||||
} else {
|
||||
$("#sidebarCollapse").click();
|
||||
/* right swipe */
|
||||
}
|
||||
} else {
|
||||
if (yDiff > 0) {
|
||||
/* up swipe */
|
||||
} else {
|
||||
/* down swipe */
|
||||
}
|
||||
}
|
||||
/* reset values */
|
||||
xDown = null;
|
||||
yDown = null;
|
||||
}
|
||||
|
||||
function logout() {
|
||||
jQuery.ajax({
|
||||
type: "GET",
|
||||
url: "/login",
|
||||
async: false,
|
||||
username: "logmeout",
|
||||
password: "logmeout",
|
||||
})
|
||||
.done(function () {
|
||||
// If we don"t get an error, we actually got an error as we expect an 401!
|
||||
})
|
||||
.fail(function () {
|
||||
// We expect to get an 401 Unauthorized error! In this case we are successfully
|
||||
// logged out and we redirect the user.
|
||||
document.location = "index.html";
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function connectWS() {
|
||||
if (window.location.protocol === "https:") {
|
||||
wsUri = "wss://" + window.location.host + "/ws";
|
||||
} else if (window.location.protocol === "file:") {
|
||||
wsUri = "ws://" + "localhost" + "/ws";
|
||||
}
|
||||
|
||||
websock = new WebSocket(wsUri);
|
||||
websock.addEventListener("message", socketMessageListener);
|
||||
|
||||
websock.onopen = function (evt) {
|
||||
websock.send("{\"command\":\"getconf\"}");
|
||||
websock.send("{\"command\":\"custom_status\"}");
|
||||
};
|
||||
}
|
||||
|
||||
function upload() {
|
||||
formData.append("bin", $("#binform")[0].files[0]);
|
||||
inProgressUpload();
|
||||
}
|
||||
|
||||
function login() {
|
||||
if (document.getElementById("password").value === "neo") {
|
||||
$("#signin").modal("hide");
|
||||
connectWS();
|
||||
} else {
|
||||
var username = "admin";
|
||||
var password = document.getElementById("password").value;
|
||||
var url = "/login";
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("get", url, true, username, password);
|
||||
xhr.onload = function (e) {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
$("#signin").modal("hide");
|
||||
connectWS();
|
||||
} else {
|
||||
alert("Incorrect password!");
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
}
|
||||
|
||||
function getfirmware() {
|
||||
if (use_beta_firmware) {
|
||||
use_beta_firmware = false;
|
||||
document.getElementById("updateb").innerHTML = "Switch to Development build";
|
||||
} else {
|
||||
use_beta_firmware = true;
|
||||
document.getElementById("updateb").innerHTML = "Switch to Stable release";
|
||||
}
|
||||
getLatestReleaseInfo();
|
||||
}
|
||||
|
||||
function getLatestReleaseInfo() {
|
||||
|
||||
if (use_beta_firmware) {
|
||||
var url = updateurl_dev;
|
||||
} else {
|
||||
var url = updateurl;
|
||||
}
|
||||
|
||||
$.getJSON(url).done(function (release) {
|
||||
var asset = release.assets[0];
|
||||
var downloadCount = 0;
|
||||
for (var i = 0; i < release.assets.length; i++) {
|
||||
downloadCount += release.assets[i].download_count;
|
||||
}
|
||||
var oneHour = 60 * 60 * 1000;
|
||||
var oneDay = 24 * oneHour;
|
||||
var dateDiff = new Date() - new Date(release.published_at);
|
||||
var timeAgo;
|
||||
if (dateDiff < oneDay) {
|
||||
timeAgo = (dateDiff / oneHour).toFixed(1) + " hours ago";
|
||||
} else {
|
||||
timeAgo = (dateDiff / oneDay).toFixed(1) + " days ago";
|
||||
}
|
||||
|
||||
var releaseInfo = release.name + " was updated " + timeAgo + " and downloaded " + downloadCount.toLocaleString() + " times.";
|
||||
$("#downloadupdate").attr("href", asset.browser_download_url);
|
||||
$("#releasehead").text(releaseInfo);
|
||||
$("#releasebody").text(release.body);
|
||||
$("#releaseinfo").fadeIn("slow");
|
||||
}).error(function () { $("#onlineupdate").html("<h5>Couldn't get release details. Make sure there is an Internet connection.</h5>"); });
|
||||
}
|
||||
|
||||
function allowUpload() {
|
||||
$("#upbtn").prop("disabled", false);
|
||||
}
|
||||
|
||||
function start() {
|
||||
myespcontent = document.createElement("div");
|
||||
myespcontent.id = "mastercontent";
|
||||
myespcontent.style.display = "none";
|
||||
document.body.appendChild(myespcontent);
|
||||
$("#signin").on("shown.bs.modal", function () {
|
||||
$("#password").focus().select();
|
||||
});
|
||||
|
||||
$("#mastercontent").load("myesp.html", function (responseTxt, statusTxt, xhr) {
|
||||
if (statusTxt === "success") {
|
||||
$("#signin").modal({ backdrop: "static", keyboard: false });
|
||||
$("[data-toggle=\"popover\"]").popover({
|
||||
container: "body"
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function refreshCustomStatus() {
|
||||
websock.send("{\"command\":\"custom_status\"}");
|
||||
}
|
||||
|
||||
function refreshStatus() {
|
||||
websock.send("{\"command\":\"status\"}");
|
||||
}
|
||||
|
||||
$("#dismiss, .overlay").on("click", function () {
|
||||
$("#sidebar").removeClass("active");
|
||||
$(".overlay").fadeOut();
|
||||
});
|
||||
|
||||
$("#sidebarCollapse").on("click", function () {
|
||||
$("#sidebar").addClass("active");
|
||||
$(".overlay").fadeIn();
|
||||
$(".collapse.in").toggleClass("in");
|
||||
$("a[aria-expanded=true]").attr("aria-expanded", "false");
|
||||
});
|
||||
|
||||
$("#custom_status").click(function () { websock.send("{\"command\":\"custom_status\"}"); return false; });
|
||||
$("#status").click(function () { websock.send("{\"command\":\"status\"}"); return false; });
|
||||
$("#custom").click(function () { getContent("#customcontent"); return false; });
|
||||
$("#network").on("click", (function () { getContent("#networkcontent"); return false; }));
|
||||
$("#general").click(function () { getContent("#generalcontent"); return false; });
|
||||
$("#mqtt").click(function () { getContent("#mqttcontent"); return false; });
|
||||
$("#ntp").click(function () { getContent("#ntpcontent"); return false; });
|
||||
$("#backup").click(function () { getContent("#backupcontent"); return false; });
|
||||
$("#reset").click(function () { $("#destroy").modal("show"); return false; });
|
||||
$("#restart").click(function () { $("#reboot").modal("show"); return false; });
|
||||
$(".noimp").on("click", function () { $("#noimp").modal("show"); });
|
||||
$("#update").on("shown.bs.modal", function (e) { getfirmware(); });
|
||||
|
||||
document.addEventListener("touchstart", handleTouchStart, false);
|
||||
document.addEventListener("touchmove", handleTouchMove, false);
|
||||
Reference in New Issue
Block a user