1.9.0 web - new implementation

This commit is contained in:
Paul
2019-08-02 09:26:13 +02:00
parent 89818b23bd
commit fc52f05453
41 changed files with 7206 additions and 931 deletions

47
.gitignore vendored
View File

@@ -1,11 +1,50 @@
.pio* # vscode
.vscode
.vscode/.browse.c_cpp.db*
.vscode/c_cpp_properties.json
.vscode/launch.json
.vscode/*
# platformio
.pio
.clang_complete .clang_complete
.gcc-flags.json .gcc-flags.json
.vscode
.env
.DS_Store
platformio.ini platformio.ini
lib/readme.txt lib/readme.txt
.travis.yml .travis.yml
# web stuff compiled
src/websrc/css/required.css
src/websrc/js/required.js
src/websrc/fonts
src/websrc/gzipped
src/websrc/temp
*.gz.h
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Bower dependency directory (https://bower.io/)
bower_components
# Dependency directories
node_modules/
jspm_packages/
# Optional npm cache directory
.npm
# Output of 'npm pack'
*.tgz
# dotenv environment variables file
.env
# project specfic
.DS_Store
scripts/stackdmp.txt scripts/stackdmp.txt
*.bin *.bin

View File

@@ -3,45 +3,59 @@
; ;
[platformio] [platformio]
;default_envs = release default_envs = release
default_envs = debug ;default_envs = debug
[common] [common]
; -DMYESP_TIMESTAMP -DTESTS -DCRASH -DFORCE_SERIAL -DNO_GLOBAL_EEPROM -DLOGICANALYZER ; -DMYESP_TIMESTAMP -DTESTS -DCRASH -DFORCE_SERIAL -DNO_GLOBAL_EEPROM -DLOGICANALYZER
extra_flags = -DNO_GLOBAL_EEPROM general_flags = -DNO_GLOBAL_EEPROM
[env] [env]
board = d1_mini board = d1_mini
; board = nodemcuv2 ; board = nodemcuv2
; board = d1_mini_pro
framework = arduino framework = arduino
platform = espressif8266 platform = espressif8266
lib_deps = lib_deps =
CRC32 CRC32
CircularBuffer CircularBuffer
OneWire
JustWifi JustWifi
AsyncMqttClient AsyncMqttClient
ArduinoJson ArduinoJson
OneWire
EEPROM_rotate EEPROM_rotate
ESP Async WebServer
ESPAsyncTCP
ESPAsyncUDP
upload_speed = 921600 upload_speed = 921600
monitor_speed = 115200 monitor_speed = 115200
; example ports for OSX
;upload_port = /dev/cu.wchusbserial14403
;upload_port = /dev/cu.usbserial-1440
; uncomment next 2 lines for OTA ; uncomment next 2 lines for OTA
;upload_protocol = espota upload_protocol = espota
;upload_port = ems-esp.local upload_port = ems-esp.local
[env:buildweb]
extra_scripts = pre:scripts/buildweb.py
[env:debug] [env:debug]
build_type = debug build_type = debug
build_flags = ${common.extra_flags} -DCRASH build_flags = ${common.general_flags} -DCRASH -DTESTS
extra_scripts = pre:scripts/rename_fw.py extra_scripts =
pre:scripts/rename_fw.py
pre:scripts/buildweb.py
[env:clean] [env:clean]
extra_scripts = pre:scripts/clean_fw.py extra_scripts = pre:scripts/clean_fw.py
[env:release] [env:release]
build_flags = ${common.extra_flags} build_flags = ${common.general_flags}
extra_scripts = pre:scripts/rename_fw.py extra_scripts = pre:scripts/rename_fw.py
[env:checkcode] [env:checkcode]
build_flags = ${common.extra_flags} build_type = debug
build_flags = ${common.general_flags} -Wall
extra_scripts = scripts/checkcode.py extra_scripts = scripts/checkcode.py

3
scripts/buildweb.py Normal file
View File

@@ -0,0 +1,3 @@
Import("env")
env.Execute("node ./tools/webfilesbuilder/node_modules/gulp/bin/gulp.js --cwd ./tools/webfilesbuilder")

View File

@@ -1,7 +1,13 @@
#!/usr/bin/env python #!/usr/bin/env python
from subprocess import call
import os
import re import re
Import("env") Import("env")
def build_web(source, target, env):
print("\n** Build web...")
call(["gulp", "-f", os.getcwd()+"/tools/webfilesbuilder/gulpfile.js"])
bag = {} bag = {}
exprs = [ exprs = [
(re.compile(r'^#define APP_VERSION\s+"(\S+)"'), 'app_version'), (re.compile(r'^#define APP_VERSION\s+"(\S+)"'), 'app_version'),
@@ -22,5 +28,9 @@ app_hostname = bag.get('app_hostname')
board = env['BOARD'] board = env['BOARD']
branch = env['PIOENV'] branch = env['PIOENV']
# build the web files
env.AddPreAction("buildprog", build_web)
# build filename, replacing . with _ for the version # build filename, replacing . with _ for the version
env.Replace(PROGNAME="firmware_%s" % branch + "_" + app_version.replace(".", "_")) env.Replace(PROGNAME="firmware_%s" % branch + "_" + app_version.replace(".", "_"))

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,18 @@
#ifndef MyESP_h #ifndef MyESP_h
#define MyESP_h #define MyESP_h
#define MYESP_VERSION "1.1.24" #define MYESP_VERSION "1.2"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127 #include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
#include <ESP8266WebServer.h> #include <ESPAsyncUDP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h> #include <FS.h>
#include <JustWifi.h> // https://github.com/xoseperez/justwifi #include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include <TelnetSpy.h> // modified from https://github.com/yasheena/telnetspy
#include "Ntp.h"
#include "TelnetSpy.h" // modified from https://github.com/yasheena/telnetspy
#ifdef CRASH #ifdef CRASH
#include <EEPROM_Rotate.h> #include <EEPROM_Rotate.h>
@@ -30,7 +33,6 @@ extern struct rst_info resetInfo;
} }
#if defined(ARDUINO_ARCH_ESP32) #if defined(ARDUINO_ARCH_ESP32)
//#include <ESPmDNS.h>
#include <SPIFFS.h> // added for ESP32 #include <SPIFFS.h> // added for ESP32
#define ets_vsnprintf vsnprintf // added for ESP32 #define ets_vsnprintf vsnprintf // added for ESP32
#define OTA_PORT 3232 #define OTA_PORT 3232
@@ -39,13 +41,31 @@ extern struct rst_info resetInfo;
#define OTA_PORT 8266 #define OTA_PORT 8266
#endif #endif
#define MYEMS_CONFIG_FILE "/config.json" // web files
// reference libs
#include "webh/glyphicons-halflings-regular.woff.gz.h"
#include "webh/required.css.gz.h"
#include "webh/required.js.gz.h"
#define LOADAVG_INTERVAL 30000 // Interval between calculating load average (in ms) = 30 seconds // 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_EVENTLOG_FILE "/eventlog.json"
#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 // WIFI
#define WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds) #define MYESP_WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds)
#define WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes #define MYESP_WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
// MQTT // MQTT
#define MQTT_PORT 1883 // MQTT port #define MQTT_PORT 1883 // MQTT port
@@ -57,6 +77,12 @@ extern struct rst_info resetInfo;
#define MQTT_TOPIC_HEARTBEAT "heartbeat" #define MQTT_TOPIC_HEARTBEAT "heartbeat"
#define MQTT_TOPIC_START_PAYLOAD "start" #define MQTT_TOPIC_START_PAYLOAD "start"
#define MQTT_TOPIC_RESTART "restart" #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_RETAIN false
#define MQTT_KEEPALIVE 60 // 1 minute
#define MQTT_QOS 1
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
// Internal MQTT events // Internal MQTT events
#define MQTT_CONNECT_EVENT 0 #define MQTT_CONNECT_EVENT 0
@@ -94,18 +120,21 @@ extern struct rst_info resetInfo;
// reset reason codes // reset reason codes
PROGMEM const char custom_reset_hardware[] = "Hardware button"; PROGMEM const char custom_reset_hardware[] = "Hardware button";
PROGMEM const char custom_reset_terminal[] = "Reboot from terminal"; PROGMEM const char custom_reset_terminal[] = "Restart from terminal";
PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT"; PROGMEM const char custom_reset_mqtt[] = "Restart from MQTT";
PROGMEM const char custom_reset_ota[] = "Reboot after successful OTA update"; PROGMEM const char custom_reset_ota[] = "Restart after successful OTA update";
PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, custom_reset_terminal, custom_reset_mqtt, custom_reset_ota}; 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_HARDWARE 1 // Reset from hardware button
#define CUSTOM_RESET_TERMINAL 2 // Reset from terminal #define CUSTOM_RESET_TERMINAL 2 // Reset from terminal
#define CUSTOM_RESET_MQTT 3 // Reset via MQTT #define CUSTOM_RESET_MQTT 3 // Reset via MQTT
#define CUSTOM_RESET_OTA 4 // Reset after successful OTA update #define CUSTOM_RESET_OTA 4 // Reset after successful OTA update
#define CUSTOM_RESET_MAX 4 #define CUSTOM_RESET_FACTORY 5 // Factory reset
#define CUSTOM_RESET_MAX 5
// SPIFFS // SPIFFS
#define SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/ #define MYESP_SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/
#define MYESP_JSON_MAXSIZE 2000 // for large Dynamic json files
// CRASH // CRASH
/** /**
@@ -157,9 +186,9 @@ struct RtcmemData {
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big"); static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
#define SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute) #define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute)
#define SYSTEM_CHECK_MAX 5 // After this many crashes on boot #define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot
#define HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins) #define MYESP_HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins)
typedef struct { typedef struct {
bool set; // is it a set command bool set; // is it a set command
@@ -181,9 +210,9 @@ typedef std::function<void()>
typedef std::function<void()> ota_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, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f; typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f; typedef std::function<bool(MYESP_FSACTION, JsonObject json)> fs_loadsave_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f; typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_setlist_callback_f;
typedef std::function<void(char *)> web_callback_f; typedef std::function<void(JsonObject root)> web_callback_f;
// calculates size of an 2d array at compile time // calculates size of an 2d array at compile time
template <typename T, size_t N> template <typename T, size_t N>
@@ -191,58 +220,34 @@ constexpr size_t ArraySize(T (&)[N]) {
return N; return N;
} }
template <typename T> #define MYESP_UPTIME_OVERFLOW 4294967295 // Uptime overflow value
void PROGMEM_readAnything(const T * sce, T & dest) {
memcpy_P(&dest, sce, sizeof(T));
}
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// web min and max length of wifi ssid and password // web min and max length of wifi ssid and password
#define MAX_SSID_LEN 32 #define MYESP_MAX_STR_LEN 16
#define MAX_PWD_LEN 64
#define MYESP_BOOTUP_FLASHDELAY 50 // flash duration for LED at bootup sequence #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 #define MYESP_BOOTUP_DELAY 2000 // time before we open the window to reset. This is to stop resetting values when uploading firmware via USB
// max size of char buffer for storing web page
#define MYESP_MAXCHARBUFFER 800
// Holds the admin webpage in the program memory
const char webCommonPage_start[] = "<html>"
"<head>"
"<style>input {font-size: 1.2em; width: 100%; max-width: 350px; display: block; margin: 5px auto; }"
"body {background-color: #FFA500;font: normal 18px Verdana, Arial, sans-serif;} </style>";
const char webCommonPage_start_body[] = "</head><body>";
const char webCommonPage_end[] = "</body></html>";
const char webResetPage_form[] = "<form id='form' action='/reset' method='post'>"
"<input name='newssid' type='text' maxlength='32' placeholder='SSID'>"
"<input name='newpassword' id='password1' type='password' maxlength='64' placeholder='Password'>"
"<input type='submit' value='Save and reboot'>"
"</form>";
const char webResetPage_post[] =
"<p>New wifi credentials set. System is now rebooting. Please wait a few seconds and then reconnect via telnet or browser to its new IP given address.</p>";
const char webResetAllPage_form[] = "<form id='resetform' action='/resetall' method='post'>"
"<input name='confirm' type='text' minlength='3' maxlength='16' placeholder='yes'>"
"<input type='submit' value='Reset All'>"
"</form>";
// class definition // class definition
class MyESP { class MyESP {
protected:
// webserver
AsyncWebServer * _webServer;
AsyncWebSocket * _ws;
// NTP
NtpClient NTP;
public: public:
MyESP(); MyESP();
~MyESP(); ~MyESP();
ESP8266WebServer webServer; // Web server on port 80 // write event called from within lambda classs
static void _writeEvent(const char * type, const char * src, const char * desc, const char * data);
// wifi // wifi
void setWIFICallback(void (*callback)()); void setWIFICallback(void (*callback)());
void setWIFI(const char * wifi_ssid, const char * wifi_password, wifi_callback_f callback); void setWIFI(wifi_callback_f callback);
bool isWifiConnected(); bool isWifiConnected();
bool isAPmode(); bool isAPmode();
@@ -251,17 +256,7 @@ class MyESP {
void mqttSubscribe(const char * topic); void mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic); void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload); void mqttPublish(const char * topic, const char * payload);
void setMQTT(const char * mqtt_host, void setMQTT(mqtt_callback_f callback);
const char * mqtt_username,
const char * mqtt_password,
const char * mqtt_base,
unsigned long mqtt_keepalive,
unsigned char mqtt_qos,
bool mqtt_retain,
const char * mqtt_will_topic,
const char * mqtt_will_online_payload,
const char * mqtt_will_offline_payload,
mqtt_callback_f callback);
// OTA // OTA
void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post); void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post);
@@ -274,8 +269,9 @@ class MyESP {
void setUseSerial(bool toggle); void setUseSerial(bool toggle);
// FS // FS
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback); void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true);
bool fs_saveConfig(); bool fs_saveConfig(JsonObject root);
bool fs_saveCustomConfig(JsonObject root);
// Web // Web
void setWeb(web_callback_f callback_web); void setWeb(web_callback_f callback_web);
@@ -289,8 +285,7 @@ class MyESP {
// general // general
void end(); void end();
void loop(); void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version); void begin(const char * app_hostname, const char * app_name, const char * app_version, const char * app_helpurl, const char * app_updateurl);
void setBoottime(const char * boottime);
void resetESP(); void resetESP();
int getWifiQuality(); int getWifiQuality();
void showSystemStats(); void showSystemStats();
@@ -298,6 +293,7 @@ class MyESP {
uint32_t getSystemLoadAverage(); uint32_t getSystemLoadAverage();
uint32_t getSystemResetReason(); uint32_t getSystemResetReason();
uint8_t getSystemBootStatus(); uint8_t getSystemBootStatus();
bool _have_ntp_time;
private: private:
// mqtt // mqtt
@@ -306,40 +302,41 @@ class MyESP {
void _mqttOnMessage(char * topic, char * payload, size_t len); void _mqttOnMessage(char * topic, char * payload, size_t len);
void _mqttConnect(); void _mqttConnect();
void _mqtt_setup(); void _mqtt_setup();
mqtt_callback_f _mqtt_callback; mqtt_callback_f _mqtt_callback_f;
void _mqttOnConnect(); void _mqttOnConnect();
void _sendStart(); void _sendStart();
char * _mqttTopic(const char * topic); char * _mqttTopic(const char * topic);
char * _mqtt_host; char * _mqtt_ip;
char * _mqtt_username; char * _mqtt_user;
char * _mqtt_password; char * _mqtt_password;
int _mqtt_port;
char * _mqtt_base; char * _mqtt_base;
bool _mqtt_enabled;
unsigned long _mqtt_keepalive; unsigned long _mqtt_keepalive;
unsigned char _mqtt_qos; unsigned char _mqtt_qos;
bool _mqtt_retain; bool _mqtt_retain;
char * _mqtt_will_topic; char * _mqtt_will_topic;
char * _mqtt_will_online_payload; char * _mqtt_will_online_payload;
char * _mqtt_will_offline_payload; char * _mqtt_will_offline_payload;
char * _mqtt_topic;
unsigned long _mqtt_last_connection; unsigned long _mqtt_last_connection;
bool _mqtt_connecting; bool _mqtt_connecting;
bool _rtcmem_status; bool _mqtt_heartbeat;
// wifi // wifi
void _wifiCallback(justwifi_messages_t code, char * parameter); void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup(); void _wifi_setup();
wifi_callback_f _wifi_callback; wifi_callback_f _wifi_callback_f;
char * _wifi_ssid; char * _network_ssid;
char * _wifi_password; char * _network_password;
uint8_t _network_wmode;
bool _wifi_connected; bool _wifi_connected;
String _getESPhostname(); String _getESPhostname();
// ota // ota
ota_callback_f _ota_pre_callback; ota_callback_f _ota_pre_callback_f;
ota_callback_f _ota_post_callback; ota_callback_f _ota_post_callback_f;
void _ota_setup(); void _ota_setup();
void _OTACallback(); void _OTACallback();
bool _ota_doing_update;
// crash // crash
void _eeprom_setup(); void _eeprom_setup();
@@ -354,37 +351,41 @@ class MyESP {
void _telnet_setup(); void _telnet_setup();
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
void _consoleShowHelp(); void _consoleShowHelp();
telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands telnetcommand_callback_f _telnetcommand_callback_f; // Callable for projects commands
telnet_callback_f _telnet_callback; // callback for connect/disconnect telnet_callback_f _telnet_callback_f; // callback for connect/disconnect
bool _changeSetting(uint8_t wc, const char * setting, const char * value); bool _changeSetting(uint8_t wc, const char * setting, const char * value);
// fs // fs and settings
void _fs_setup(); void _fs_setup();
bool _fs_loadConfig(); bool _fs_loadConfig();
void _fs_printConfig(); bool _fs_loadCustomConfig();
void _fs_printFile(const char * file);
void _fs_eraseConfig(); void _fs_eraseConfig();
bool _fs_writeConfig();
bool _fs_createCustomConfig();
bool _fs_sendConfig();
fs_loadsave_callback_f _fs_loadsave_callback_f;
fs_setlist_callback_f _fs_setlist_callback_f;
// settings
fs_callback_f _fs_callback;
fs_settings_callback_f _fs_settings_callback;
void _printSetCommands(); void _printSetCommands();
// web
web_callback_f _web_callback;
// general // general
char * _app_hostname; char * _general_hostname;
char * _app_name; char * _app_name;
char * _app_version; char * _app_version;
char * _boottime; char * _app_helpurl;
char * _app_updateurl;
bool _suspendOutput; bool _suspendOutput;
bool _serial; bool _general_serial;
bool _heartbeat;
unsigned long _getUptime(); unsigned long _getUptime();
String _buildTime(); char * _getBuildTime();
bool _firstInstall; char * _buildTime;
bool _timerequest;
bool _formatreq;
bool _hasValue(char * s);
// reset reason and rtcmem // reset reason and rtcmem
bool _rtcmem_status;
bool _rtcmemStatus(); bool _rtcmemStatus();
bool _getRtcmemStatus(); bool _getRtcmemStatus();
@@ -418,11 +419,30 @@ class MyESP {
// heartbeat // heartbeat
void _heartbeatCheck(bool force); void _heartbeatCheck(bool force);
// webserver // web
web_callback_f _web_callback_f;
const char * _http_username;
// log
void _sendEventLog(uint8_t page);
// 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(); void _webserver_setup();
void _webRootPage(); void _webRootPage();
void _webResetPage(); void _webResetPage();
void _webResetAllPage(); void _webResetAllPage();
// ntp
uint8_t _ntp_timezone;
char * _ntp_server;
uint8_t _ntp_interval;
bool _ntp_enabled;
}; };
extern MyESP myESP; extern MyESP myESP;

49
src/Ntp.cpp Normal file
View File

@@ -0,0 +1,49 @@
/*
* Ntp.cpp
*/
#include "Ntp.h"
char * NtpClient::TimeServerName;
int8_t NtpClient::timezone;
time_t NtpClient::syncInterval;
IPAddress NtpClient::timeServer;
AsyncUDP NtpClient::udpListener;
byte NtpClient::NTPpacket[NTP_PACKET_SIZE];
void ICACHE_FLASH_ATTR NtpClient::Ntp(const char * server, int8_t tz, time_t syncSecs) {
TimeServerName = strdup(server);
timezone = tz;
syncInterval = syncSecs;
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;
setTime(UnixUTCtime);
});
}
udpListener.write(NTPpacket, sizeof(NTPpacket));
return 0;
}

35
src/Ntp.h Normal file
View File

@@ -0,0 +1,35 @@
/*
* 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
#define NTP_PACKET_SIZE 48 // NTP time is in the first 48 bytes of message
class NtpClient {
public:
void ICACHE_FLASH_ATTR Ntp(const char * server, int8_t tz, time_t syncSecs);
ICACHE_FLASH_ATTR virtual ~NtpClient();
static char * TimeServerName;
static IPAddress timeServer;
static int8_t timezone;
static time_t syncInterval;
static AsyncUDP udpListener;
static byte NTPpacket[NTP_PACKET_SIZE];
private:
static ICACHE_FLASH_ATTR time_t getNtpTime();
};
#endif

View File

@@ -303,7 +303,7 @@ int TelnetSpy::available(void) {
} }
int TelnetSpy::read(void) { int TelnetSpy::read(void) {
int val; int val = 0;
if (usedSer) { if (usedSer) {
val = usedSer->read(); val = usedSer->read();
if (val != -1) { if (val != -1) {
@@ -319,7 +319,7 @@ int TelnetSpy::read(void) {
} }
int TelnetSpy::peek(void) { int TelnetSpy::peek(void) {
int val; int val = 0;
if (usedSer) { if (usedSer) {
val = usedSer->peek(); val = usedSer->peek();
if (val != -1) { if (val != -1) {

185
src/TimeLib.cpp Normal file
View File

@@ -0,0 +1,185 @@
#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;
}
// indicates if time has been set and recently synchronized
timeStatus_t timeStatus() {
now(); // required to actually update the status
return Status;
}
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
days = 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
}
time_t makeTime(const tmElements_t & tm) {
// assemble time elements into time_t
// note year argument is offset from 1970 (see macros in time.h to convert to other formats)
// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9
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;
}
}
int day(time_t t) { // the day for the given time (0-6)
refreshCache(t);
return tm.Day;
}
int month(time_t t) { // the month for the given time
refreshCache(t);
return tm.Month;
}
int second(time_t t) { // the second for the given time
refreshCache(t);
return tm.Second;
}
int minute(time_t t) { // the minute for the given time
refreshCache(t);
return tm.Minute;
}
int hour(time_t t) { // the hour for the given time
refreshCache(t);
return tm.Hour;
}
int weekday(time_t t) {
refreshCache(t);
return tm.Wday;
}
int year(time_t t) { // the year for the given time
refreshCache(t);
return tmYearToCalendar(tm.Year);
}
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)
}

49
src/TimeLib.h Normal file
View File

@@ -0,0 +1,49 @@
#ifndef _Time_h
#define _Time_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, TimeElements, *tmElementsPtr_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
int hour(time_t t); // the hour for the given time
int minute(time_t t); // the minute for the given time
int second(time_t t); // the second for the given time
int day(time_t t); // the day for the given time
int month(time_t t); // the month for the given time
int weekday(time_t t); // the weekday for the given time
int year(time_t t); // the year for the given time
}
#endif

239
src/custom.htm Normal file
View File

@@ -0,0 +1,239 @@
<div id="customcontent">
<br>
<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 the LED"></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="Choose &quot;LED pin&quot; pin"></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 (LED_BUILTIN)</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 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="Choose &quot;Dallas pin&quot; pin."></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"></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 alerts 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="Sends blasts of cold water after a fixed shower duration"></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="Publish Time in seconds"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="120" 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">Heating circuit<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Select main heating circuit to use"></i></label>
<span class="col-xs-9 col-md-5">
<select class="form-control input-sm" id="heating_circuit">
<option selected="selected" value="1">HC1 (default)</option>
<option value="2">HC2</option>
<option value="3">HC3</option>
</select>
</span>
</div>
<div class="row form-group">
<label class="col-xs-3">TX 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="TX mode settings for various EMS brands"></i></label>
<span class="col-xs-9 col-md-5">
<select class="form-control input-sm" id="tx_mode">
<option selected="selected" value="0">0=EMS 1.0 (default)</option>
<option value="1">1=EMS+</option>
<option value="2">2=Generic (experimental!)</option>
<option value="3">3=Junkers</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 restart after saving.</h6>
</div>
<div id="custom_statuscontent">
<br>
<div class="row text-center">
<div class="col-md-8 col-md-offset-2">
<h1>Dashboard</h1>
<p>Real-time values from the EMS-ESP device are shown here.</p>
</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 stats</caption>
<tr>
<td colspan="2">
<h3><span id="msg"></span></h3>
</td>
</tr>
<tr>
<th>Detected Devices:</th>
<td>
<ul class="list-group">
<div id="devices"></div>
</ul>
</td>
</tr>
</table>
</div>
<div class="panel panel-info table-responsive" id="boiler_show">
<div class="panel-heading">Boiler</div>
<table class="table table-hover table-bordered table-condensed">
<tr>
<th>Model:</th>
<td colspan="3" id="bm"></td>
</tr>
<tr>
<th>Hot Tap Water:</th>
<td id="b1"></td>
<th>Central Heating:</th>
<td id="b2"></td>
</tr>
<th>Selected Flow Temperature:</th>
<td id="b3"></td>
<th>Boiler Temperature:</th>
<td id="b4"></td>
</tr>
</table>
</div>
<div class="panel panel-success table-responsive" id="thermostat_show">
<div class="panel-heading">Thermostat</div>
<table class="table table-hover table-bordered table-condensed">
<tr>
<th>Model:</th>
<td colspan="3" id="tm"></td>
</tr>
<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>
<br>
<br>
<div class="row form-group">
<div class="col-xs-9 col-md-8">
<button onclick="refreshEMS()" class="btn btn-primary btn-sm pull-center">Refresh...</button>
</div>
</div>
</div>
</div>

141
src/custom.js Normal file
View File

@@ -0,0 +1,141 @@
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": 120,
"heating_circuit": 1,
"tx_mode": 0
}
}
function custom_commit() {
websock.send(JSON.stringify(custom_config));
}
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("heating_circuit").value = custom_config.settings.heating_circuit;
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.heating_circuit = parseInt(document.getElementById("heating_circuit").value);
custom_config.settings.tx_mode = parseInt(document.getElementById("tx_mode").value);
custom_uncommited();
}
function listCustomStats() {
document.getElementById("msg").innerHTML = ajaxobj.emsbus.msg;
if (ajaxobj.emsbus.ok) {
document.getElementById("msg").className = "label label-success";
} else {
document.getElementById("msg").className = "label label-danger";
document.getElementById("thermostat_show").style.display = "none";
document.getElementById("boiler_show").style.display = "none";
return;
}
var list = document.getElementById("devices");
var obj = ajaxobj.emsbus.devices;
for (var i = 0; i < obj.length; i++) {
var l = document.createElement("li");
var type = obj[i].type;
if (type == 1) {
var color = "info";
} else if (type == 2) {
var color = "success";
} else if (type == 3) {
var color = "warning";
} else if (type == 4) {
var color = "danger";
} else {
var color = "";
}
l.innerHTML = "Model:" + obj[i].model + ", Version:" + obj[i].version + ", ProductID:" + obj[i].productid + ", DeviceID:" + obj[i].deviceid;
l.className = "list-group-item 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 + " &#8451;";
document.getElementById("b4").innerHTML = ajaxobj.boiler.b4 + " &#8451;";
} 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 + " &#8451;";
document.getElementById("tc").innerHTML = ajaxobj.thermostat.tc + " &#8451;";
document.getElementById("tmode").innerHTML = ajaxobj.thermostat.tmode;
} else {
document.getElementById("thermostat_show").style.display = "none";
}
}

View File

@@ -8,19 +8,17 @@
*/ */
// local libraries // local libraries
#include "ds18.h"
#include "ems.h" #include "ems.h"
#include "ems_devices.h" #include "ems_devices.h"
#include "emsuart.h" #include "emsuart.h"
#include "my_config.h" #include "my_config.h"
#include "version.h" #include "version.h"
#include <MyESP.h>
// Dallas external temp sensors // Dallas external temp sensors
#include "ds18.h"
DS18 ds18; DS18 ds18;
// shared libraries
#include <MyESP.h>
// public libraries // public libraries
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson #include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
#include <CRC32.h> // https://github.com/bakercp/CRC32 #include <CRC32.h> // https://github.com/bakercp/CRC32
@@ -32,7 +30,7 @@ DS18 ds18;
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) #define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
// set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1. // set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1.
#define EMSESP_DELAY 1 // initially set to 0 for no delay #define EMSESP_DELAY 1 // initially set to 0 for no delay // TODO change delay to 0?
#define DEFAULT_HEATINGCIRCUIT 1 // default to HC1 for thermostats that support multiple heating circuits like the RC35 #define DEFAULT_HEATINGCIRCUIT 1 // default to HC1 for thermostats that support multiple heating circuits like the RC35
@@ -69,6 +67,8 @@ Ticker showerColdShotStopTimer;
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water #define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water #define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
#define MQTT_MAX_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
typedef struct { typedef struct {
uint32_t timestamp; // for internal timings, via millis() uint32_t timestamp; // for internal timings, via millis()
uint8_t dallas_sensors; // count of dallas sensors uint8_t dallas_sensors; // count of dallas sensors
@@ -98,9 +98,7 @@ static const command_t project_cmds[] PROGMEM = {
{true, "led <on | off>", "toggle status LED on/off"}, {true, "led <on | off>", "toggle status LED on/off"},
{true, "led_gpio <gpio>", "set the LED pin. Default is the onboard LED 2. For external D1 use 5"}, {true, "led_gpio <gpio>", "set the LED pin. Default is the onboard LED 2. For external D1 use 5"},
{true, "dallas_gpio <gpio>", "set the external Dallas temperature sensors pin. Default is 14 for D5"}, {true, "dallas_gpio <gpio>", "set the external Dallas temperature sensors pin. Default is 14 for D5"},
{true, "dallas_parasite <on | off>", "set to on if powering Dallas sesnsors via parasite power"}, {true, "dallas_parasite <on | off>", "set to on if powering Dallas sensors via parasite power"},
{true, "thermostat_type <device ID>", "set the thermostat type ID (e.g. 10 for 0x10)"},
{true, "boiler_type <device ID>", "set the boiler type ID (e.g. 8 for 0x08)"},
{true, "listen_mode <on | off>", "when set to on all automatic Tx are disabled"}, {true, "listen_mode <on | off>", "when set to on all automatic Tx are disabled"},
{true, "shower_timer <on | off>", "send MQTT notification on all shower durations"}, {true, "shower_timer <on | off>", "send MQTT notification on all shower durations"},
{true, "shower_alert <on | off>", "stop hot water to send 3 cold burst warnings after max shower time is exceeded"}, {true, "shower_alert <on | off>", "stop hot water to send 3 cold burst warnings after max shower time is exceeded"},
@@ -550,7 +548,7 @@ void showInfo() {
if (ems_getSolarModuleEnabled()) { if (ems_getSolarModuleEnabled()) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Solar Module: %s"), ems_getSolarModuleDescription(buffer_type)); myDebug_P(PSTR(" Solar module: %s"), ems_getSolarModuleDescription(buffer_type));
_renderShortValue("Collector temperature", "C", EMS_SolarModule.collectorTemp); _renderShortValue("Collector temperature", "C", EMS_SolarModule.collectorTemp);
_renderShortValue("Bottom temperature", "C", EMS_SolarModule.bottomTemp); _renderShortValue("Bottom temperature", "C", EMS_SolarModule.bottomTemp);
_renderIntValue("Pump modulation", "%", EMS_SolarModule.pumpModulation); _renderIntValue("Pump modulation", "%", EMS_SolarModule.pumpModulation);
@@ -561,16 +559,16 @@ void showInfo() {
(EMS_SolarModule.pumpWorkMin % 1440) / 60, (EMS_SolarModule.pumpWorkMin % 1440) / 60,
EMS_SolarModule.pumpWorkMin % 60); EMS_SolarModule.pumpWorkMin % 60);
} }
_renderUShortValue("Energy Last Hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10 _renderUShortValue("Energy last hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10
_renderUShortValue("Energy Today", "Wh", EMS_SolarModule.EnergyToday, 0); _renderUShortValue("Energy today", "Wh", EMS_SolarModule.EnergyToday, 0);
_renderUShortValue("Energy Total", "kWH", EMS_SolarModule.EnergyTotal, 1); // *10 _renderUShortValue("Energy total", "kWH", EMS_SolarModule.EnergyTotal, 1); // *10
} }
// For HeatPumps // For HeatPumps
if (ems_getHeatPumpEnabled()) { if (ems_getHeatPumpEnabled()) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sHeat Pump stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sHeat Pump stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Solar Module: %s"), ems_getHeatPumpDescription(buffer_type)); myDebug_P(PSTR(" Heat Pump module: %s"), ems_getHeatPumpDescription(buffer_type));
_renderIntValue("Pump modulation", "%", EMS_HeatPump.HPModulation); _renderIntValue("Pump modulation", "%", EMS_HeatPump.HPModulation);
_renderIntValue("Pump speed", "%", EMS_HeatPump.HPSpeed); _renderIntValue("Pump speed", "%", EMS_HeatPump.HPSpeed);
} }
@@ -579,7 +577,7 @@ void showInfo() {
if (ems_getThermostatEnabled()) { if (ems_getThermostatEnabled()) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type)); myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type, false));
// Render Current & Setpoint Room Temperature // Render Current & Setpoint Room Temperature
if (ems_getThermostatModel() == EMS_MODEL_EASY) { if (ems_getThermostatModel() == EMS_MODEL_EASY) {
@@ -683,8 +681,6 @@ void publishSensorValues() {
} }
} }
// send values via MQTT // send values via MQTT
// a json object is created for the boiler and one for the thermostat // a json object is created for the boiler and one for the thermostat
// CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic // CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic
@@ -1131,44 +1127,42 @@ void runUnitTest(uint8_t test_num) {
} }
// callback for loading/saving settings to the file system (SPIFFS) // callback for loading/saving settings to the file system (SPIFFS)
bool FSCallback(MYESP_FSACTION action, const JsonObject json) { bool LoadSaveCallback(MYESP_FSACTION action, JsonObject json) {
if (action == MYESP_FSACTION_LOAD) { if (action == MYESP_FSACTION_LOAD) {
EMSESP_Status.led = json["led"]; const JsonObject & settings = json["settings"];
EMSESP_Status.led_gpio = json["led_gpio"] | EMSESP_LED_GPIO;
EMSESP_Status.dallas_gpio = json["dallas_gpio"] | EMSESP_DALLAS_GPIO;
EMSESP_Status.dallas_parasite = json["dallas_parasite"] | EMSESP_DALLAS_PARASITE;
EMS_Thermostat.device_id = json["thermostat_type"] | EMSESP_THERMOSTAT_TYPE; EMSESP_Status.led = settings["led"];
EMS_Boiler.device_id = json["boiler_type"] | EMSESP_BOILER_TYPE; EMSESP_Status.led_gpio = settings["led_gpio"] | EMSESP_LED_GPIO;
EMSESP_Status.dallas_gpio = settings["dallas_gpio"] | EMSESP_DALLAS_GPIO;
EMSESP_Status.dallas_parasite = settings["dallas_parasite"] | EMSESP_DALLAS_PARASITE;
EMSESP_Status.shower_timer = settings["shower_timer"];
EMSESP_Status.shower_alert = settings["shower_alert"];
EMSESP_Status.publish_time = settings["publish_time"] | DEFAULT_PUBLISHTIME;
EMSESP_Status.shower_timer = json["shower_timer"]; ems_setTxMode(settings["tx_mode"]);
EMSESP_Status.shower_alert = json["shower_alert"];
EMSESP_Status.publish_time = json["publish_time"] | DEFAULT_PUBLISHTIME;
ems_setTxMode(json["tx_mode"]); EMSESP_Status.listen_mode = settings["listen_mode"];
EMSESP_Status.listen_mode = json["listen_mode"];
ems_setTxDisabled(EMSESP_Status.listen_mode); ems_setTxDisabled(EMSESP_Status.listen_mode);
EMSESP_Status.heating_circuit = json["heating_circuit"] | DEFAULT_HEATINGCIRCUIT; EMSESP_Status.heating_circuit = settings["heating_circuit"] | DEFAULT_HEATINGCIRCUIT;
ems_setThermostatHC(EMSESP_Status.heating_circuit); ems_setThermostatHC(EMSESP_Status.heating_circuit);
return true; // return false if some settings are missing and we need to rebuild the file return true;
} }
if (action == MYESP_FSACTION_SAVE) { if (action == MYESP_FSACTION_SAVE) {
json["thermostat_type"] = EMS_Thermostat.device_id; JsonObject settings = json.createNestedObject("settings");
json["boiler_type"] = EMS_Boiler.device_id;
json["led"] = EMSESP_Status.led; settings["led"] = EMSESP_Status.led;
json["led_gpio"] = EMSESP_Status.led_gpio; settings["led_gpio"] = EMSESP_Status.led_gpio;
json["dallas_gpio"] = EMSESP_Status.dallas_gpio; settings["dallas_gpio"] = EMSESP_Status.dallas_gpio;
json["dallas_parasite"] = EMSESP_Status.dallas_parasite; settings["dallas_parasite"] = EMSESP_Status.dallas_parasite;
json["listen_mode"] = EMSESP_Status.listen_mode; settings["listen_mode"] = EMSESP_Status.listen_mode;
json["shower_timer"] = EMSESP_Status.shower_timer; settings["shower_timer"] = EMSESP_Status.shower_timer;
json["shower_alert"] = EMSESP_Status.shower_alert; settings["shower_alert"] = EMSESP_Status.shower_alert;
json["publish_time"] = EMSESP_Status.publish_time; settings["publish_time"] = EMSESP_Status.publish_time;
json["heating_circuit"] = EMSESP_Status.heating_circuit; settings["heating_circuit"] = EMSESP_Status.heating_circuit;
json["tx_mode"] = ems_getTxMode(); settings["tx_mode"] = ems_getTxMode();
return true; return true;
} }
@@ -1179,7 +1173,7 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
// callback for custom settings when showing Stored Settings with the 'set' command // callback for custom settings when showing Stored Settings with the 'set' command
// wc is number of arguments after the 'set' command // wc is number of arguments after the 'set' command
// returns true if the setting was recognized and changed and should be saved back to SPIFFs // returns true if the setting was recognized and changed and should be saved back to SPIFFs
bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) { bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) {
bool ok = false; bool ok = false;
if (action == MYESP_FSACTION_SET) { if (action == MYESP_FSACTION_SET) {
@@ -1243,18 +1237,6 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
} }
} }
// thermostat_type
if (strcmp(setting, "thermostat_type") == 0) {
EMS_Thermostat.device_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE);
ok = true;
}
// boiler_type
if (strcmp(setting, "boiler_type") == 0) {
EMS_Boiler.device_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE);
ok = true;
}
// shower timer // shower timer
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) { if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
if (strcmp(value, "on") == 0) { if (strcmp(value, "on") == 0) {
@@ -1311,21 +1293,7 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
myDebug_P(PSTR(" led_gpio=%d"), EMSESP_Status.led_gpio); myDebug_P(PSTR(" led_gpio=%d"), EMSESP_Status.led_gpio);
myDebug_P(PSTR(" dallas_gpio=%d"), EMSESP_Status.dallas_gpio); myDebug_P(PSTR(" dallas_gpio=%d"), EMSESP_Status.dallas_gpio);
myDebug_P(PSTR(" dallas_parasite=%s"), EMSESP_Status.dallas_parasite ? "on" : "off"); myDebug_P(PSTR(" dallas_parasite=%s"), EMSESP_Status.dallas_parasite ? "on" : "off");
if (EMS_Thermostat.device_id == EMS_ID_NONE) {
myDebug_P(PSTR(" thermostat_type=<not set>"));
} else {
myDebug_P(PSTR(" thermostat_type=%02X"), EMS_Thermostat.device_id);
}
myDebug_P(PSTR(" heating_circuit=%d"), EMSESP_Status.heating_circuit); myDebug_P(PSTR(" heating_circuit=%d"), EMSESP_Status.heating_circuit);
if (EMS_Boiler.device_id == EMS_ID_NONE) {
myDebug_P(PSTR(" boiler_type=<not set>"));
} else {
myDebug_P(PSTR(" boiler_type=%02X"), EMS_Boiler.device_id);
}
myDebug_P(PSTR(" listen_mode=%s"), EMSESP_Status.listen_mode ? "on" : "off"); myDebug_P(PSTR(" listen_mode=%s"), EMSESP_Status.listen_mode ? "on" : "off");
myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off"); myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off");
myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off"); myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off");
@@ -1697,58 +1665,110 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
} }
} }
// web information for diagnostics
void WebCallback(char * body) {
strlcpy(body, "<b>EMS stats:</b><br>", MYESP_MAXCHARBUFFER);
if (ems_getBusConnected()) {
char s[10];
strlcat(body, "EMS Bus is connected<br>", MYESP_MAXCHARBUFFER);
strlcat(body, "Rx: # successful read requests=", MYESP_MAXCHARBUFFER);
strlcat(body, itoa(EMS_Sys_Status.emsRxPgks, s, 10), MYESP_MAXCHARBUFFER);
strlcat(body, ", # CRC errors=", MYESP_MAXCHARBUFFER);
strlcat(body, itoa(EMS_Sys_Status.emxCrcErr, s, 10), MYESP_MAXCHARBUFFER);
if (ems_getTxCapable()) {
strlcat(body, "<br>Tx: # successful write requests=", MYESP_MAXCHARBUFFER);
strlcat(body, itoa(EMS_Sys_Status.emsTxPkgs, s, 10), MYESP_MAXCHARBUFFER);
} else {
strlcat(body, "<br>Tx: no signal<br><br>", MYESP_MAXCHARBUFFER);
}
// show device list
strlcpy(body, "<b>EMS devices found:</b><br>", MYESP_MAXCHARBUFFER);
char buffer[MYESP_MAXCHARBUFFER] = {0};
uint8_t num_devices = ems_printDevices_s(buffer, MYESP_MAXCHARBUFFER);
if (num_devices == 0) {
strlcat(body, "(any detected and compatible EMS devices will show up here)", MYESP_MAXCHARBUFFER);
} else {
strlcat(body, buffer, MYESP_MAXCHARBUFFER);
}
} else {
strlcat(body, "Unable to establish a connection to the EMS Bus.", MYESP_MAXCHARBUFFER);
}
}
// Init callback, which is used to set functions and call methods after a wifi connection has been established // Init callback, which is used to set functions and call methods after a wifi connection has been established
void WIFICallback() { void WIFICallback() {
// This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals // This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals
// This is done after we have a WiFi signal to avoid any resource conflicts // This is done after we have a WiFi signal to avoid any resource conflicts
system_uart_swap(); // TODO check // system_uart_swap(); // TODO see if this still blocks the EMS lines
}
// web information for diagnostics
void WebCallback(JsonObject root) {
JsonObject emsbus = root.createNestedObject("emsbus");
/*
if (myESP.getUseSerial()) { if (myESP.getUseSerial()) {
myDebug_P(PSTR("Warning! EMS bus communication disabled when Serial mode enabled. Use 'set serial off' to start communication.")); emsbus["ok"] = false;
emsbus["msg"] = "EMS Bus is disabled when in Serial mode. Check Settings->General Settings";
} else { } else {
emsuart_init(); if (ems_getBusConnected()) {
myDebug_P(PSTR("[UART] Opened Rx/Tx connection")); if (ems_getTxCapable()) {
if (!EMSESP_Status.listen_mode) { emsbus["ok"] = true;
// go and find the boiler and thermostat types, if not in listen mode emsbus["msg"] = "EMS Bus Connected, Rx and Tx active";
ems_discoverModels(); } else {
emsbus["ok"] = false;
emsbus["msg"] = "EMS Bus Connected, Tx is failing";
}
} else {
emsbus["ok"] = false;
emsbus["msg"] = "EMS Bus is not connected. Check event logs for errors.";
} }
} }
*/
JsonArray list = emsbus.createNestedArray("devices");
for (std::list<_Generic_Device>::iterator it = Devices.begin(); it != Devices.end(); it++) {
JsonObject item = list.createNestedObject();
item["type"] = (it)->model_type;
item["model"] = (it)->model_string;
item["deviceid"] = (it)->device_id;
item["version"] = (it)->version;
item["productid"] = (it)->product_id;
}
JsonObject thermostat = root.createNestedObject("thermostat");
if (ems_getThermostatEnabled()) {
thermostat["ok"] = true;
char buffer[200];
thermostat["tm"] = ems_getThermostatDescription(buffer, true);
// Render Current & Setpoint Room Temperature
if (ems_getThermostatModel() == EMS_MODEL_EASY) {
if (EMS_Thermostat.setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET)
thermostat["ts"] = (double)EMS_Thermostat.setpoint_roomTemp / 100;
if (EMS_Thermostat.curr_roomTemp != EMS_VALUE_SHORT_NOTSET)
thermostat["tc"] = (double)EMS_Thermostat.curr_roomTemp / 100;
} else if ((ems_getThermostatModel() == EMS_MODEL_FR10) || (ems_getThermostatModel() == EMS_MODEL_FW100)) {
if (EMS_Thermostat.setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET)
thermostat["ts"] = (double)EMS_Thermostat.setpoint_roomTemp / 10;
if (EMS_Thermostat.curr_roomTemp != EMS_VALUE_SHORT_NOTSET)
thermostat["tc"] = (double)EMS_Thermostat.curr_roomTemp / 10;
} else {
if (EMS_Thermostat.setpoint_roomTemp != EMS_VALUE_SHORT_NOTSET)
thermostat["ts"] = (double)EMS_Thermostat.setpoint_roomTemp / 2;
if (EMS_Thermostat.curr_roomTemp != EMS_VALUE_SHORT_NOTSET)
thermostat["tc"] = (double)EMS_Thermostat.curr_roomTemp / 10;
}
// Render Termostat Mode, if we have a mode
uint8_t thermoMode = _getThermostatMode(); // 0xFF=unknown, 0=low, 1=manual, 2=auto, 3=night, 4=day
if (thermoMode == 0) {
thermostat["tmode"] = "low";
} else if (thermoMode == 1) {
thermostat["tmode"] = "manual";
} else if (thermoMode == 2) {
thermostat["tmode"] = "auto";
} else if (thermoMode == 3) {
thermostat["tmode"] = "night";
} else if (thermoMode == 4) {
thermostat["tmode"] = "day";
}
} else {
thermostat["ok"] = false;
}
JsonObject boiler = root.createNestedObject("boiler");
if (ems_getBoilerEnabled()) {
boiler["ok"] = true;
char buffer[200];
boiler["bm"] = ems_getBoilerDescription(buffer, true);
boiler["b1"] = (EMS_Boiler.tapwaterActive ? "running" : "off");
boiler["b2"] = (EMS_Boiler.heatingActive ? "active" : "off");
if (EMS_Boiler.selFlowTemp != EMS_VALUE_INT_NOTSET)
boiler["b3"] = EMS_Boiler.selFlowTemp;
if (EMS_Boiler.boilTemp != EMS_VALUE_USHORT_NOTSET)
boiler["b4"] = (double)EMS_Boiler.boilTemp / 10;
} else {
boiler["ok"] = false;
}
// serializeJsonPretty(root, Serial); // turn on for debugging
} }
// Initialize the boiler settings and shower settings // Initialize the boiler settings and shower settings
@@ -1862,25 +1882,14 @@ void setup() {
systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if EMS is reachable systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if EMS is reachable
// set up myESP for Wifi, MQTT, MDNS and Telnet // set up myESP for Wifi, MQTT, MDNS and Telnet callbacks
myESP.setTelnet(TelnetCommandCallback, TelnetCallback); // set up Telnet commands myESP.setTelnet(TelnetCommandCallback, TelnetCallback); // set up Telnet commands
myESP.setWIFI(NULL, NULL, WIFICallback); // empty ssid and password as we take this from the config file myESP.setWIFI(WIFICallback); // wifi callback
myESP.setMQTT(MQTTCallback); // MQTT ip, username and password taken from the SPIFFS settings
// MQTT host, username and password taken from the SPIFFS settings myESP.setSettings(LoadSaveCallback, SetListCallback, true); // default is Serial off
myESP.setMQTT( myESP.setWeb(WebCallback); // web custom settings
NULL, NULL, NULL, MQTT_BASE, MQTT_KEEPALIVE, MQTT_QOS, MQTT_RETAIN, MQTT_WILL_TOPIC, MQTT_WILL_ONLINE_PAYLOAD, MQTT_WILL_OFFLINE_PAYLOAD, MQTTCallback); myESP.setOTA(OTACallback_pre, OTACallback_post); // OTA callback which is called when OTA is starting and stopping
myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION, APP_HELPURL, APP_UPDATEURL);
// OTA callback which is called when OTA is starting and stopping
myESP.setOTA(OTACallback_pre, OTACallback_post);
// custom settings in SPIFFS
myESP.setSettings(FSCallback, SettingsCallback);
// web custom settings
myESP.setWeb(WebCallback);
// start up all the services
myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION);
// at this point we have all the settings from our internall SPIFFS config file // at this point we have all the settings from our internall SPIFFS config file
// fire up the UART now // fire up the UART now
@@ -1889,7 +1898,8 @@ void setup() {
} else { } else {
Serial.println("Note: Serial output will now be disabled. Please use Telnet."); Serial.println("Note: Serial output will now be disabled. Please use Telnet.");
Serial.flush(); Serial.flush();
emsuart_init(); myESP.setUseSerial(false);
emsuart_init(); // start EMS bus transmissions
myDebug_P(PSTR("[UART] Opened Rx/Tx connection")); myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
if (!EMSESP_Status.listen_mode) { if (!EMSESP_Status.listen_mode) {
// go and find the boiler and thermostat types, if not in listen mode // go and find the boiler and thermostat types, if not in listen mode

View File

@@ -11,7 +11,6 @@
#include "emsuart.h" #include "emsuart.h"
#include <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer #include <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer
#include <MyESP.h> #include <MyESP.h>
#include <list> // std::list
#ifdef TESTS #ifdef TESTS
#include "test_data.h" #include "test_data.h"
@@ -739,7 +738,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
*/ */
if (EMS_Sys_Status.emsRxStatus != EMS_RX_STATUS_IDLE) { if (EMS_Sys_Status.emsRxStatus != EMS_RX_STATUS_IDLE) {
if (EMS_Sys_Status.emsLogging > EMS_SYS_LOGGING_NONE) { if (EMS_Sys_Status.emsLogging > EMS_SYS_LOGGING_NONE) {
myDebug_P(PSTR("** [DEBUG MODE] We missed the bus - Rx non-idle!")); //TODO tidy up error logging myDebug_P(PSTR("** [DEBUG MODE] Warning, we missed the bus - Rx non-idle!")); // TODO tidy up error logging
} }
return; return;
} }
@@ -1609,8 +1608,9 @@ void ems_clearDeviceList() {
/* /*
* add an EMS device to our list of detected devices * add an EMS device to our list of detected devices
* model_type = 1=info=boiler, 2=success=thermostat, 3=warning=sm, 4=danger=other, 5=none=unknown
*/ */
void _addDevice(uint8_t product_id, uint8_t device_id, char * version, const char * model_string) { void _addDevice(uint8_t model_type, uint8_t product_id, uint8_t device_id, char * version, const char * model_string) {
_Generic_Device device; _Generic_Device device;
// if its a duplicate don't add // if its a duplicate don't add
bool found = false; bool found = false;
@@ -1620,6 +1620,7 @@ void _addDevice(uint8_t product_id, uint8_t device_id, char * version, const cha
} }
} }
if (!found) { if (!found) {
device.model_type = model_type;
device.product_id = product_id; device.product_id = product_id;
device.device_id = device_id; device.device_id = device_id;
strlcpy(device.version, version, sizeof(device.version)); strlcpy(device.version, version, sizeof(device.version));
@@ -1659,7 +1660,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
myDebug_P(PSTR("Boiler found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Boiler_Devices[i].model_string, EMS_ID_BOILER, product_id, version); myDebug_P(PSTR("Boiler found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Boiler_Devices[i].model_string, EMS_ID_BOILER, product_id, version);
// add to list // add to list
_addDevice(product_id, EMS_ID_BOILER, version, Boiler_Devices[i].model_string); _addDevice(1, product_id, EMS_ID_BOILER, version, Boiler_Devices[i].model_string); // type 1 = boiler
// if its a boiler set it, unless it already has been set by checking for a productID // if its a boiler set it, unless it already has been set by checking for a productID
// it will take the first one found in the list // it will take the first one found in the list
@@ -1679,8 +1680,6 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Sys_Status.emsReverse = true; EMS_Sys_Status.emsReverse = true;
} }
myESP.fs_saveConfig(); // save config to SPIFFS
ems_getBoilerValues(); // get Boiler values that we would usually have to wait for ems_getBoilerValues(); // get Boiler values that we would usually have to wait for
} }
return; return;
@@ -1707,7 +1706,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
} }
// add to list // add to list
_addDevice(product_id, Thermostat_Devices[i].device_id, version, Thermostat_Devices[i].model_string); _addDevice(2, product_id, Thermostat_Devices[i].device_id, version, Thermostat_Devices[i].model_string); // type 2 = thermostat
// if we don't have a thermostat set, use this one // if we don't have a thermostat set, use this one
if (((EMS_Thermostat.device_id == EMS_ID_NONE) || (EMS_Thermostat.model_id == EMS_MODEL_NONE) if (((EMS_Thermostat.device_id == EMS_ID_NONE) || (EMS_Thermostat.model_id == EMS_MODEL_NONE)
@@ -1725,8 +1724,6 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
EMS_Thermostat.product_id = product_id; EMS_Thermostat.product_id = product_id;
strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version)); strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version));
myESP.fs_saveConfig(); // save config to SPIFFS
// get Thermostat values (if supported) // get Thermostat values (if supported)
ems_getThermostatValues(); ems_getThermostatValues();
} }
@@ -1752,7 +1749,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
version); version);
// add to list // add to list
_addDevice(product_id, SolarModule_Devices[i].device_id, version, SolarModule_Devices[i].model_string); _addDevice(3, product_id, SolarModule_Devices[i].device_id, version, SolarModule_Devices[i].model_string); // type 3 = other
myDebug_P(PSTR("Solar Module support enabled.")); myDebug_P(PSTR("Solar Module support enabled."));
EMS_SolarModule.device_id = SolarModule_Devices[i].device_id; EMS_SolarModule.device_id = SolarModule_Devices[i].device_id;
@@ -1782,7 +1779,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
version); version);
// add to list // add to list
_addDevice(product_id, HeatPump_Devices[i].device_id, version, HeatPump_Devices[i].model_string); _addDevice(3, product_id, HeatPump_Devices[i].device_id, version, HeatPump_Devices[i].model_string); // type 3 = other
myDebug_P(PSTR("Heat Pump support enabled.")); myDebug_P(PSTR("Heat Pump support enabled."));
EMS_HeatPump.device_id = SolarModule_Devices[i].device_id; EMS_HeatPump.device_id = SolarModule_Devices[i].device_id;
@@ -1805,12 +1802,12 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
myDebug_P(PSTR("Device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Other_Devices[i].model_string, Other_Devices[i].device_id, product_id, version); myDebug_P(PSTR("Device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), Other_Devices[i].model_string, Other_Devices[i].device_id, product_id, version);
// add to list // add to list
_addDevice(product_id, Other_Devices[i].device_id, version, Other_Devices[i].model_string); _addDevice(4, product_id, Other_Devices[i].device_id, version, Other_Devices[i].model_string); // type 3 = other
return; return;
} else { } else {
myDebug_P(PSTR("Unrecognized device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), EMS_RxTelegram->src, product_id, version); myDebug_P(PSTR("Unrecognized device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), EMS_RxTelegram->src, product_id, version);
// add to list // add to list
_addDevice(product_id, EMS_RxTelegram->src, version, "unknown?"); _addDevice(5, product_id, EMS_RxTelegram->src, version, "unknown?"); // type 4 = unknown
} }
} }
@@ -1978,7 +1975,7 @@ void ems_getSolarModuleValues() {
* returns current thermostat type as a string * returns current thermostat type as a string
* by looking up the product_id * by looking up the product_id
*/ */
char * ems_getThermostatDescription(char * buffer) { char * ems_getThermostatDescription(char * buffer, bool name_only) {
uint8_t size = 128; uint8_t size = 128;
if (!ems_getThermostatEnabled()) { if (!ems_getThermostatEnabled()) {
strlcpy(buffer, "<not enabled>", size); strlcpy(buffer, "<not enabled>", size);
@@ -1998,6 +1995,9 @@ char * ems_getThermostatDescription(char * buffer) {
if (found) { if (found) {
strlcpy(buffer, Thermostat_Devices[i].model_string, size); strlcpy(buffer, Thermostat_Devices[i].model_string, size);
if (name_only) {
return buffer; // only interested in the model name
}
} else { } else {
strlcpy(buffer, "DeviceID: 0x", size); strlcpy(buffer, "DeviceID: 0x", size);
strlcat(buffer, _hextoa(EMS_Thermostat.device_id, tmp), size); strlcat(buffer, _hextoa(EMS_Thermostat.device_id, tmp), size);
@@ -2020,7 +2020,7 @@ char * ems_getThermostatDescription(char * buffer) {
/** /**
* returns current boiler type as a string * returns current boiler type as a string
*/ */
char * ems_getBoilerDescription(char * buffer) { char * ems_getBoilerDescription(char * buffer, bool name_only) {
uint8_t size = 128; uint8_t size = 128;
if (!ems_getBoilerEnabled()) { if (!ems_getBoilerEnabled()) {
strlcpy(buffer, "<not enabled>", size); strlcpy(buffer, "<not enabled>", size);
@@ -2039,6 +2039,9 @@ char * ems_getBoilerDescription(char * buffer) {
} }
if (found) { if (found) {
strlcpy(buffer, Boiler_Devices[i].model_string, size); strlcpy(buffer, Boiler_Devices[i].model_string, size);
if (name_only) {
return buffer; // only interested in the model name
}
} else { } else {
strlcpy(buffer, "DeviceID: 0x", size); strlcpy(buffer, "DeviceID: 0x", size);
strlcat(buffer, _hextoa(EMS_Boiler.device_id, tmp), size); strlcat(buffer, _hextoa(EMS_Boiler.device_id, tmp), size);
@@ -2261,23 +2264,6 @@ void ems_printDevices() {
} }
} }
/*
* prints the device list to a string for html parsing
*/
uint8_t ems_printDevices_s(char * buffer, uint16_t len) {
if (Devices.size() == 0) {
return 0;
}
char s[100];
for (std::list<_Generic_Device>::iterator it = Devices.begin(); it != Devices.end(); it++) {
sprintf(s, "%s (DeviceID:0x%02X ProductID:%d Version:%s)<br>", (it)->model_string, (it)->device_id, (it)->product_id, (it)->version);
strlcat(buffer, s, len);
}
return Devices.size();
}
/** /**
* Send a command to UART Tx to Read from another device * Send a command to UART Tx to Read from another device
* Read commands when sent must respond by the destination (target) immediately (or within 10ms) * Read commands when sent must respond by the destination (target) immediately (or within 10ms)
@@ -2695,7 +2681,7 @@ void ems_setWarmTapWaterActivated(bool activated) {
/** /**
* Start up sequence for UBA Master, hopefully to initialize a handshake * Start up sequence for UBA Master, hopefully to initialize a handshake
* Still experimental * Still experimental and not used yet!
*/ */
void ems_startupTelegrams() { void ems_startupTelegrams() {
if ((EMS_Sys_Status.emsTxDisabled) || (!EMS_Sys_Status.emsBusConnected)) { if ((EMS_Sys_Status.emsTxDisabled) || (!EMS_Sys_Status.emsBusConnected)) {

View File

@@ -11,6 +11,7 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <list> // std::list
/* debug helper for logic analyzer /* debug helper for logic analyzer
* create marker puls on GPIOx * create marker puls on GPIOx
@@ -250,6 +251,7 @@ typedef struct {
// for consolidating all types // for consolidating all types
typedef struct { typedef struct {
uint8_t model_type; // 1=info=boiler, 2=success=thermostat, 3=warning=sm, 4=danger=other, 5=none=unknown
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
char version[10]; char version[10];
@@ -421,8 +423,8 @@ void ems_setTxDisabled(bool b);
bool ems_getTxDisabled(); bool ems_getTxDisabled();
uint8_t ems_getTxMode(); uint8_t ems_getTxMode();
char * ems_getThermostatDescription(char * buffer); char * ems_getThermostatDescription(char * buffer, bool name_only = false);
char * ems_getBoilerDescription(char * buffer); char * ems_getBoilerDescription(char * buffer, bool name_only = false);
char * ems_getSolarModuleDescription(char * buffer); char * ems_getSolarModuleDescription(char * buffer);
char * ems_getHeatPumpDescription(char * buffer); char * ems_getHeatPumpDescription(char * buffer);
void ems_getThermostatValues(); void ems_getThermostatValues();
@@ -457,3 +459,5 @@ extern _EMS_Thermostat EMS_Thermostat;
extern _EMS_SolarModule EMS_SolarModule; extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump; extern _EMS_HeatPump EMS_HeatPump;
extern _EMS_Other EMS_Other; extern _EMS_Other EMS_Other;
extern std::list<_Generic_Device> Devices;

View File

@@ -121,7 +121,7 @@ void ICACHE_FLASH_ATTR emsuart_init() {
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) // 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 // 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 trafffic. // change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
// Otherwise, we're only noticed by UCTOT or RxBRK! // Otherwise, we're only noticed by UCTOT or RxBRK!
USC1(EMSUART_UART) = 0; // reset config first USC1(EMSUART_UART) = 0; // reset config first
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts
@@ -141,7 +141,7 @@ void ICACHE_FLASH_ATTR emsuart_init() {
system_set_os_print(0); system_set_os_print(0);
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
//system_uart_swap(); system_uart_swap();
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr); ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
ETS_UART_INTR_ENABLE(); ETS_UART_INTR_ENABLE();

View File

@@ -10,18 +10,6 @@
#include "ems.h" #include "ems.h"
// MQTT base name
#define MQTT_BASE "home" // all MQTT topics are prefix with this string, in the format <MQTT_BASE>/<app name>/<topic>
// MQTT general settings
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
#define MQTT_RETAIN false
#define MQTT_KEEPALIVE 120 // 2 minutes
#define MQTT_QOS 1
#define MQTT_MAX_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
// MQTT for thermostat // MQTT for thermostat
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT #define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT #define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT

View File

@@ -1,10 +1,6 @@
/**
*
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*/
#pragma once
#define APP_NAME "EMS-ESP" #define APP_NAME "EMS-ESP"
#define APP_VERSION "1.9.0" #define APP_VERSION "1.9.0b_web"
#define APP_HOSTNAME "ems-esp" #define APP_HOSTNAME "ems-esp"
#define APP_HELPURL "https://github.com/proddy/EMS-ESP/wiki"
#define APP_UPDATEURL "https://api.github.com/repos/proddy/EMS-ESP/releases/latest"

0
src/webh/.gitkeep Normal file
View File

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
src/websrc/3rdparty/css/sidebar.css vendored Normal file
View File

@@ -0,0 +1 @@
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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

280
src/websrc/index.html Normal file
View File

@@ -0,0 +1,280 @@
<!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>
<!-- Bootstrap core CSS -->
<link href="css/required.css" rel="stylesheet">
</head>
<body>
<div class="wrapper">
<!-- Sidebar Holder -->
<nav id="sidebar">
<div id="dismiss">
<i class="glyphicon glyphicon-arrow-left"></i>
</div>
<div class="sidebar-header">
<h1 id="customname2" class="text-center"></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>NTP (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="eventlog"><i class="glyphicon glyphicon-transfer"></i>Event Log</a>
</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>Check for Updates</a>
</li>
</ul>
<ul class="list-unstyled CTAs">
<li>
<a id="helpurl" href="https://github.com/proddy" class="download">Help</a>
</li>
<li>
<a href="#" class="article" onclick="logout();">Logout</a>
</li>
</ul>
</nav>
<!-- Page Content Holder -->
<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="revcommit" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Please review your system changes</h4>
</div>
<div class="modal-body">
<pre id="jsonholder"></pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" onclick="commit();" class="btn btn-success" data-dismiss="modal">Save
& Restart</button>
</div>
</div>
</div>
</div>
<div id="custom_revcommit" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Please review your custom changes</h4>
</div>
<div class="modal-body">
<pre id="jsonholder2"></pre>
Note: some settings my require a <b>Restart System</b> first to take effect.
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" onclick="custom_commit();" class="btn btn-success"
data-dismiss="modal">Save
</button>
</div>
</div>
</div>
</div>
<div id="destroy" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</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 first.</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">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</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" role="dialog">
<div class="modal-dialog">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</button>
<h4 class="modal-title">Please log in</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 your 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">
<!-- Modal content-->
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal">&times;</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
</div>
<div class="modal-body">
<div>
<h4>Latest Stable Release</h4>
<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>
<h4>Current Version:</h4>
<h5 id="versionhead"></h5>
<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">
<h6 class="text-muted">(running on <a href="https://github.com/proddy/MyESP">MyESP</a>)</h6>
</div>
</footer>
<div class="overlay"></div>
<script src="js/required.js"></script>
<script src="js/myesp.js"></script>
<script>start();</script>
</body>
</html>

484
src/websrc/myesp.htm Normal file
View File

@@ -0,0 +1,484 @@
<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 have made a backup on regular basis.</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>
<legend>Restart</legend>
<h6 class="text-muted">Click to restart your device without saving changes.</h6>
<label for="restart" class="btn btn-link btn-sm">Restart Device</label>
<button id="restart" class="btn btn-link btn-sm" onclick="restartESP();" style="display:none;"></button>
</div>
<br>
<div id="restoremodal" 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">&times;</button>
<h4 class="modal-title">Please wait while data is restoring...</h4>
</div>
<div class="modal-body">
<div id="pbar" class="progress">
<div id="dynamic" class="progress-bar progress-bar-primary progress-bar-striped active">
Restoring...</div>
</div>
</div>
<div class="modal-footer">
<button type="button" id="restoreclose" style="display:none;" class="btn btn-default"
data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</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 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="generalcontent">
<br>
<br>
<legend>General Settings</legend>
<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="Log On password"></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="Hostname. When Bonjour is installed on your computer you can access via http://hostname.local"></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<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Please choose if you want to enable Serial output for debugging"></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>
<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>
<br>
</div>
<div id="eventcontent">
<br>
<br>
<div class="text-center" id="loading-img">
<h5>Please wait while fetching data...<span id="loadpages"></span></h5>
<br>
</div>
<div>
<legend>Event Log</legend>
<div class="panel panel-default">
<div>
<table id="eventtable" class="table" data-paging="true" data-filtering="true" data-sorting="true"
data-editing="false" data-state="true"></table>
</div>
</div>
<button onclick="clearevent()" class="btn btn-primary btn-sm">Clear Log</button>
<div style="clear:both;">
<br>
<br>
</div>
</div>
</div>
<div id="mqttcontent">
<br>
<br>
<legend>MQTT Settings</legend>
<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="Please choose if you want to enable MQTT"></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="MQTT IP" 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"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="MQTT Port" value="" style="display:inline;max-width:185px"
id="mqttport" type="text">
</span>
<br>
</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 username"></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 if any"></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 base prefix (optional)"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="MQTT base" 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="Please choose if you want to enable the MQTT heartbeat"></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>
<br>
<legend>Wi-Fi Settings</legend>
<h6 class="text-muted">Type your Wi-Fi Network's SSID or Scan for nerby Wireless Networks to join.</h6>
<br>
<div class="row form-group">
<label class="col-xs-3">Wi-Fi 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 or Client Mode. In client mode you will need to connect to an existing Wi-Fi network, in AP Mode ESP creates a Wi-Fi network itself."></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="hidessid">
<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="Wi-Fi Network's 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-primary btn-xs" style="display:none;"
onclick="scanWifi()">Scan</button>
</span>
</div>
<div class="row form-group" style="display:none" id="hidepasswd">
<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="Wi-Fi 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>
</div>
<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>
<br>
<legend>Time Settings</legend>
<h6 class="text-muted">Daylight saving times are taken into account</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>
<div class="row form-group">
<label class="col-xs-3">Browser Time</label>
<span id="rtc" class="col-xs-9 col-md-5">
</span>
</div>
<div class="row form-group">
<div class="col-xs-3">
<button onclick="syncBrowserTime()" class="btn btn-link btn-sm">Sync Browser Time to Device</button><i
style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" aria-hidden="true"
data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Use your browser time. Useful when the system does not have an internet connection."></i>
</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" name="ntpenabled">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="ntpenabled" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">NTP Server<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 server for the time sync. Choose nearest server for better accuracy, see https://www.ntppool.org for servers nearby."></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">Intervals<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Intervals between Time Sync in Minutes"></i></label>
<span class="col-xs-9 col-md-5">
<input class="form-control input-sm" placeholder="in Minutes" value="30" id="intervals" 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="DropDownTimezone" id="DropDownTimezone">
<option value="-12">(GMT -12:00) Eniwetok, Kwajalein</option>
<option value="-11">(GMT -11:00) Midway Island, Samoa</option>
<option value="-10">(GMT -10:00) Hawaii</option>
<option value="-9">(GMT -9:00) Alaska</option>
<option value="-8">(GMT -8:00) Pacific Time (US &amp; Canada)</option>
<option value="-7">(GMT -7:00) Mountain Time (US &amp; Canada)</option>
<option value="-6">(GMT -6:00) Central Time (US &amp; Canada), Mexico City</option>
<option value="-5">(GMT -5:00) Eastern Time (US &amp; Canada), Bogota, Lima</option>
<option value="-4">(GMT -4:00) Atlantic Time (Canada), Caracas, La Paz</option>
<option value="-3.5">(GMT -3:30) Newfoundland</option>
<option value="-3">(GMT -3:00) Brazil, Buenos Aires, Georgetown</option>
<option value="-2">(GMT -2:00) Mid-Atlantic</option>
<option value="-1">(GMT -1:00 hour) Azores, Cape Verde Islands</option>
<option value="0">(GMT) Western Europe Time, London, Lisbon, Casablanca</option>
<option selected="selected" value="1">(GMT +1:00 hour) Brussels, Copenhagen, Madrid, Paris</option>
<option value="2">(GMT +2:00) Kaliningrad, South Africa</option>
<option value="3">(GMT +3:00) Baghdad, Riyadh, Moscow, St. Petersburg</option>
<option value="3.5">(GMT +3:30) Tehran</option>
<option value="4">(GMT +4:00) Abu Dhabi, Muscat, Baku, Tbilisi</option>
<option value="4.5">(GMT +4:30) Kabul</option>
<option value="5">(GMT +5:00) Ekaterinburg, Islamabad, Karachi, Tashkent</option>
<option value="5.5">(GMT +5:30) Bombay, Calcutta, Madras, New Delhi</option>
<option value="5.75">(GMT +5:45) Kathmandu</option>
<option value="6">(GMT +6:00) Almaty, Dhaka, Colombo</option>
<option value="7">(GMT +7:00) Bangkok, Hanoi, Jakarta</option>
<option value="8">(GMT +8:00) Beijing, Perth, Singapore, Hong Kong</option>
<option value="9">(GMT +9:00) Tokyo, Seoul, Osaka, Sapporo, Yakutsk</option>
<option value="9.5">(GMT +9:30) Adelaide, Darwin</option>
<option value="10">(GMT +10:00) Eastern Australia, Guam, Vladivostok</option>
<option value="11">(GMT +11:00) Magadan, Solomon Islands, New Caledonia</option>
<option value="12">(GMT +12:00) Auckland, Wellington, Fiji, Kamchatka</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><br>
<legend>System Status</legend>
<br>
<div class="row text-center">
<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>System</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">
<caption>MQTT</caption>
<tr>
<td>
<div id="mqttconnected"></div>
</td>
<td>
<div id="mqttheartbeat"></div>
</td>
</tr>
</table>
</div>
</div>
<br>
<br>
</div>
</div>

934
src/websrc/myesp.js Normal file
View File

@@ -0,0 +1,934 @@
var version = "";
var websock = null;
var wsUri = "ws://" + window.location.host + "/ws";
var utcSeconds;
var timezone;
var data = [];
var ajaxobj;
var config = {
"command": "configfile",
"network": {
"ssid": "",
"wmode": 1,
"password": ""
},
"general": {
"hostname": "",
"serial": false,
"password": "admin"
},
"mqtt": {
"enabled": false,
"ip": "",
"port": 1883,
"base": "",
"user": "",
"password": "",
"heartbeat": false
},
"ntp": {
"server": "pool.ntp.org",
"interval": 30,
"timezone": 1,
"enabled": true
}
};
var page = 1;
var haspages;
var file = {};
var backupstarted = false;
var updateurl = "";
var myespcontent;
function browserTime() {
var d = new Date(0);
var c = new Date();
var timestamp = Math.floor((c.getTime() / 1000) + ((c.getTimezoneOffset() * 60) * -1));
d.setUTCSeconds(timestamp);
document.getElementById("rtc").innerHTML = d.toUTCString().slice(0, -3);
}
function deviceTime() {
var t = new Date(0); // The 0 there is the key, which sets the date to the epoch,
var devTime = Math.floor(utcSeconds + ((t.getTimezoneOffset() * 60) * -1));
t.setUTCSeconds(devTime);
document.getElementById("utc").innerHTML = t.toUTCString().slice(0, -3);
}
function syncBrowserTime() {
var d = new Date();
var timestamp = Math.floor((d.getTime() / 1000));
var datatosend = {};
datatosend.command = "settime";
datatosend.epoch = timestamp;
websock.send(JSON.stringify(datatosend));
$("#ntp").click();
}
function listntp() {
websock.send("{\"command\":\"gettime\"}");
document.getElementById("ntpserver").value = config.ntp.server;
document.getElementById("intervals").value = config.ntp.interval;
document.getElementById("DropDownTimezone").value = config.ntp.timezone;
if (config.ntp.enabled) {
$("input[name=\"ntpenabled\"][value=\"1\"]").prop("checked", true);
}
browserTime();
deviceTime();
}
function revcommit() {
document.getElementById("jsonholder").innerText = JSON.stringify(config, null, 2);
$("#revcommit").modal("show");
}
function uncommited() {
$("#commit").fadeOut(200, function () {
$(this).css("background", "gold").fadeIn(1000);
});
document.getElementById("commit").innerHTML = "<h6>Settings have changed. Click here to review and save.</h6>";
$("#commit").click(function () {
revcommit();
return false;
});
}
function custom_uncommited() {
document.getElementById("jsonholder2").innerText = JSON.stringify(custom_config.settings, null, 2);
$("#custom_revcommit").modal("show");
}
function saventp() {
config.ntp.server = document.getElementById("ntpserver").value;
config.ntp.interval = parseInt(document.getElementById("intervals").value);
config.ntp.timezone = parseInt(document.getElementById("DropDownTimezone").value);
config.ntp.enabled = false;
if (parseInt($("input[name=\"ntpenabled\"]:checked").val()) === 1) {
config.ntp.enabled = true;
}
uncommited();
}
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;
}
uncommited();
}
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.ip = document.getElementById("mqttip").value;
config.mqtt.port = parseInt(document.getElementById("mqttport").value);
config.mqtt.base = document.getElementById("mqttbase").value;
config.mqtt.user = document.getElementById("mqttuser").value;
config.mqtt.password = document.getElementById("mqttpwd").value;
uncommited();
}
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;
uncommited();
}
var formData = new FormData();
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++;
if (i === 101) {
clearInterval(prg);
var a = document.createElement("a");
a.href = "http://" + config.general.hostname + ".local";
a.innerText = "Try to reconnect ESP";
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 "commit":
websock.send(JSON.stringify(config));
break;
case "destroy":
websock.send("{\"command\":\"destroy\"}");
break;
case "restart":
websock.send("{\"command\":\"restart\"}");
break;
default:
break;
}
}
}).hide().fadeIn();
}
function commit() {
inProgress("commit");
}
function handleSTA() {
document.getElementById("scanb").style.display = "block";
document.getElementById("hidessid").style.display = "block";
document.getElementById("hidepasswd").style.display = "block";
}
function handleAP() {
document.getElementById("ssid").style.display = "none";
document.getElementById("scanb").style.display = "none";
document.getElementById("hidessid").style.display = "none";
document.getElementById("hidepasswd").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();
}
}
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);
}
}
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);
}
document.getElementById("mqttip").value = config.mqtt.ip;
document.getElementById("mqttport").value = config.mqtt.port;
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 getEvents() {
websock.send("{\"command\":\"geteventlog\", \"page\":" + page + "}");
}
function isVisible(e) {
return !!(e.offsetWidth || e.offsetHeight || e.getClientRects().length);
}
function getnextpage(mode) {
if (!backupstarted) {
document.getElementById("loadpages").innerHTML = "Loading " + page + "/" + haspages;
}
if (page < haspages) {
page = page + 1;
var commandtosend = {};
commandtosend.command = mode;
commandtosend.page = page;
websock.send(JSON.stringify(commandtosend));
}
}
function builddata(obj) {
data = data.concat(obj.list);
}
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 + " KB";
document.getElementById("heap").style.width = (ajaxobj.heap * 100) / 41 + "%";
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 "#eventcontent":
page = 1;
data = [];
getEvents();
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 = document.getElementById("helpurl");
elem.setAttribute("href", ajaxobj.helpurl);
updateurl = ajaxobj.updateurl;
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("File seems to be valid, do you wish to continue?");
if (x) {
config = json;
uncommited();
}
}
};
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("File seems to be valid, do you wish to continue?");
if (x) {
custom_config = json;
custom_uncommited();
}
}
};
reader.readAsText(input.files[0]);
}
}
}
function twoDigits(value) {
if (value < 10) {
return "0" + value;
}
return value;
}
function initEventTable() {
var newlist = [];
for (var i = 0; i < data.length; i++) {
var dup = JSON.parse(data[i]);
dup.uid = i;
newlist[i] = {};
newlist[i].options = {};
newlist[i].value = {};
newlist[i].value = dup;
var c = dup.type;
switch (c) {
case "WARN":
newlist[i].options.classes = "warning";
break;
case "INFO":
newlist[i].options.classes = "info";
break;
case "ERRO":
newlist[i].options.classes = "danger";
break;
default:
break;
}
}
jQuery(function ($) {
window.FooTable.init("#eventtable", {
columns: [{
"name": "uid",
"title": "ID",
"type": "text",
"sorted": true,
"direction": "DESC"
},
{
"name": "type",
"title": "Event Type",
"type": "text"
},
{
"name": "src",
"title": "Source"
},
{
"name": "desc",
"title": "Description"
},
{
"name": "data",
"title": "Additional Data",
"breakpoints": "xs sm"
},
{
"name": "time",
"title": "Date",
"parser": function (value) {
if (value < 1563300000) {
return "(" + value + ")";
} else {
var comp = new Date();
value = Math.floor(value + ((comp.getTimezoneOffset() * 60) * -1));
var vuepoch = new Date(value * 1000);
var formatted = vuepoch.getUTCFullYear() +
"-" + twoDigits(vuepoch.getUTCMonth() + 1) +
"-" + twoDigits(vuepoch.getUTCDate()) +
" " + twoDigits(vuepoch.getUTCHours()) +
":" + twoDigits(vuepoch.getUTCMinutes()) +
":" + twoDigits(vuepoch.getUTCSeconds());
return formatted;
}
},
"breakpoints": "xs sm"
}
],
rows: newlist
});
});
}
function restartESP() {
inProgress("restart");
}
var nextIsNotJson = false;
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 "eventlist":
haspages = obj.haspages;
if (haspages === 0) {
document.getElementById("loading-img").style.display = "none";
initEventTable();
break;
}
builddata(obj);
break;
case "gettime":
utcSeconds = obj.epoch;
timezone = obj.timezone;
deviceTime();
break;
case "ssidlist":
listSSID(obj);
break;
case "configfile":
config = obj;
break;
case "custom_configfile":
custom_config = obj;
break;
default:
break;
}
}
if (obj.hasOwnProperty("resultof")) {
switch (obj.resultof) {
case "eventlist":
if (page < haspages && obj.result === true) {
getnextpage("geteventlog");
} else if (page === haspages) {
initEventTable();
document.getElementById("loading-img").style.display = "none";
}
break;
default:
break;
}
}
}
function clearevent() {
websock.send("{\"command\":\"clearevent\"}");
$("#eventlog").click();
}
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");
}
$("#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; });
$("#eventlog").click(function () { getContent("#eventcontent"); return false; });
$(".noimp").on("click", function () {
$("#noimp").modal("show");
});
window.FooTable.MyFiltering = window.FooTable.Filtering.extend({
construct: function (instance) {
this._super(instance);
this.acctypes = ["1", "99", "0"];
this.acctypesstr = ["Always", "Admin", "Disabled"];
this.def = "Access Type";
this.$acctype = null;
},
$create: function () {
this._super();
var self = this,
$formgrp = $("<div/>", {
"class": "form-group"
})
.append($("<label/>", {
"class": "sr-only",
text: "Status"
}))
.prependTo(self.$form);
self.$acctype = $("<select/>", {
"class": "form-control"
})
.on("change", {
self: self
}, self._onStatusDropdownChanged)
.append($("<option/>", {
text: self.def
}))
.appendTo($formgrp);
$.each(self.acctypes, function (i, acctype) {
self.$acctype.append($("<option/>").text(self.acctypesstr[i]).val(self.acctypes[i]));
});
},
_onStatusDropdownChanged: function (e) {
var self = e.data.self,
selected = $(this).val();
if (selected !== self.def) {
self.addFilter("acctype", selected, ["acctype"]);
} else {
self.removeFilter("acctype");
}
self.filter();
},
draw: function () {
this._super();
var acctype = this.find("acctype");
if (acctype instanceof window.FooTable.Filter) {
this.$acctype.val(acctype.query.val());
} else {
this.$acctype.val(this.def);
}
}
});
var xDown = null;
var yDown = null;
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]);
inProgress("upload");
}
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 getLatestReleaseInfo() {
$.getJSON(updateurl).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");
$("#versionhead").text(version);
}).error(function () { $("#onlineupdate").html("<h5>Couldn't get release details. Make sure there is an Internet connection.</h5>"); });
}
$("#update").on("shown.bs.modal", function (e) {
getLatestReleaseInfo();
});
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 refreshEMS() {
websock.send("{\"command\":\"custom_status\"}");
}
document.addEventListener("touchstart", handleTouchStart, false);
document.addEventListener("touchmove", handleTouchMove, false);

View File

@@ -0,0 +1,19 @@
/* eslint-disable no-path-concat */
'use strict';
var path = require('path');
process.env.NODE_PATH = (process.env.NODE_PATH || '').split(path.delimiter)
.filter((p) => p).concat(__dirname + '/node_modules').join(path.delimiter);
require('module')._initPaths(); // eslint-disable-line no-underscore-dangle
require('gulp');
require('gulp-concat');
require('gulp/bin/gulp.js');
require('fs');
require('gulp-gzip');
require('gulp-flatmap');
require('path');
require('gulp-htmlmin');
require('gulp-uglify');
require('pump');

View File

@@ -0,0 +1,9 @@
'use strict';
module.exports = function () {
return {
packages: ['gulp-concat', 'gulp-htmlmin', 'gulp-flatmap', 'gulp-gzip', 'gulp-uglify', 'fs', 'path', 'pump'],
deployFiles: ['gulpfile.js'],
take: 'last-line'
};
};

View File

@@ -0,0 +1,232 @@
var gulp = require('gulp');
var fs = require('fs');
var concat = require('gulp-concat');
var gzip = require('gulp-gzip');
var flatmap = require('gulp-flatmap');
var path = require('path');
var htmlmin = require('gulp-htmlmin');
var uglify = require('gulp-uglify');
var pump = require('pump');
gulp.task('myespjs-concat', function () {
return gulp.src(['../../src/websrc/myesp.js', '../../src/custom.js'])
.pipe(concat({
path: 'myesp.js',
stat: {
mode: 0666
}
}))
.pipe(gulp.dest('../../src/websrc/temp/js'))
});
gulp.task('myespjsminify', ["myespjs-concat"], function (cb) {
pump([
gulp.src('../../src/websrc/temp/js/myesp.js'),
uglify(),
gulp.dest('../../src/websrc/temp/js/ugly'),
],
cb
);
});
gulp.task("myespjsgz", ["myespjsminify"], function () {
return gulp.src("../../src/websrc/temp/js/ugly/myesp.js")
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/js'));
});
gulp.task('myespjsgzh', ["myespjsgz"], function () {
var source = "../../src/websrc/temp/gzipped/js/" + "myesp.js.gz";
var destination = "../../src/webh/" + "myesp.js.gz.h";
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define myesp_js_gz_len ' + data.length + '\n');
wstream.write('const uint8_t myesp_js_gz[] PROGMEM = {')
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
});
gulp.task("scripts", ["scripts-concat"], function () {
var source = "../../src/websrc/temp/gzipped/js/" + "required.js.gz";
var destination = "../../src/webh/" + "required.js.gz.h";
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define required_js_gz_len ' + data.length + '\n');
wstream.write('const uint8_t required_js_gz[] PROGMEM = {')
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
});
gulp.task('scripts-concat', ["myespjsgzh"], function () {
return gulp.src(['../../src/websrc/3rdparty/js/jquery-1.12.4.min.js', '../../src/websrc/3rdparty/js/bootstrap-3.3.7.min.js', '../../src/websrc/3rdparty/js/footable-3.1.6.min.js'])
.pipe(concat({
path: 'required.js',
stat: {
mode: 0666
}
}))
.pipe(gulp.dest('../../src/websrc/temp/js/'))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/js/'));
});
gulp.task('styles-concat', function () {
return gulp.src(['../../src/websrc/3rdparty/css/bootstrap-3.3.7.min.css', '../../src/websrc/3rdparty/css/footable.bootstrap-3.1.6.min.css', '../../src/websrc/3rdparty/css/sidebar.css'])
.pipe(concat({
path: 'required.css',
stat: {
mode: 0666
}
}))
.pipe(gulp.dest('../../src/websrc/temp/css/'))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/css/'));
});
gulp.task("styles", ["styles-concat"], function () {
var source = "../../src/websrc/temp/gzipped/css/" + "required.css.gz";
var destination = "../../src/webh/" + "required.css.gz.h";
var wstream = fs.createWriteStream(destination);
wstream.on('error', function (err) {
console.log(err);
});
var data = fs.readFileSync(source);
wstream.write('#define required_css_gz_len ' + data.length + '\n');
wstream.write('const uint8_t required_css_gz[] PROGMEM = {')
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write('\n};')
wstream.end();
});
gulp.task("fontgz", function () {
return gulp.src("../../src/websrc/3rdparty/fonts/*.*")
.pipe(gulp.dest("../../src/websrc/temp/fonts/"))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/fonts/'));
});
gulp.task("fonts", ["fontgz"], function () {
return gulp.src("../../src/websrc/temp/gzipped/fonts/*.*")
.pipe(flatmap(function (stream, file) {
var filename = path.basename(file.path);
var wstream = fs.createWriteStream("../../src/webh/" + filename + ".h");
wstream.on("error", function (err) {
gutil.log(err);
});
var data = file.contents;
wstream.write("#define " + filename.replace(/\.|-/g, "_") + "_len " + data.length + "\n");
wstream.write("const uint8_t " + filename.replace(/\.|-/g, "_") + "[] PROGMEM = {")
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write("\n};")
wstream.end();
return stream;
}));
});
gulp.task('html-concat', function () {
return gulp.src(['../../src/websrc/myesp.htm', '../../src/custom.htm'])
.pipe(concat({
path: 'myesp.html',
stat: {
mode: 0666
}
}))
.pipe(htmlmin({ collapseWhitespace: true, minifyJS: true }))
.pipe(gulp.dest('../../src/websrc/temp/'))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/'));
});
gulp.task('htmlsprep', ["html-concat"], function () {
return gulp.src('../../src/websrc/index.html')
.pipe(htmlmin({ collapseWhitespace: true, minifyJS: true }))
.pipe(gulp.dest('../../src/websrc/temp/'))
.pipe(gzip({
append: true
}))
.pipe(gulp.dest('../../src/websrc/temp/gzipped/'));
});
gulp.task("htmls", ["htmlsprep"], function () {
return gulp.src("../../src/websrc/temp/gzipped/*.gz")
.pipe(flatmap(function (stream, file) {
var filename = path.basename(file.path);
var wstream = fs.createWriteStream("../../src/webh/" + filename + ".h");
wstream.on("error", function (err) {
gutil.log(err);
});
var data = file.contents;
wstream.write("#define " + filename.replace(/\.|-/g, "_") + "_len " + data.length + "\n");
wstream.write("const uint8_t " + filename.replace(/\.|-/g, "_") + "[] PROGMEM = {")
for (i = 0; i < data.length; i++) {
if (i % 1000 == 0) wstream.write("\n");
wstream.write('0x' + ('00' + data[i].toString(16)).slice(-2));
if (i < data.length - 1) wstream.write(',');
}
wstream.write("\n};")
wstream.end();
return stream;
}));
});
gulp.task('default', ['scripts', 'styles', "fonts", "htmls"]);

2715
tools/webfilesbuilder/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,24 @@
{
"name": "uglifier",
"version": "0.0.1",
"description": "Combine all js and css files into one and gzip them for the myESP project",
"main": "unglify.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "gulp"
},
"author": "proddy",
"license": "UNLICENSED",
"dependencies": {
"gulp-concat": "^2.6.1",
"gulp-flatmap": "^1.0.2",
"gulp-gzip": "^1.4.2",
"gulp-htmlmin": "^4.0.0",
"gulp-uglify": "^3.0.2",
"pump": "^3.0.0"
},
"bin": "node_modules\\gulp\\bin\\gulp.js",
"devDependencies": {
"gulp": "^3.9.1"
}
}

27
tools/wsemulator/package-lock.json generated Normal file
View File

@@ -0,0 +1,27 @@
{
"name": "wsemulator",
"version": "0.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "~1.0.0",
"safe-buffer": "~5.1.0"
}
}
}
}

View File

@@ -0,0 +1,14 @@
{
"name": "wsemulator",
"version": "0.0.1",
"description": "Emulate websocket communication ",
"main": "wserver.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "proddy",
"license": "UNLICENSED",
"dependencies": {
"ws": "^4.1.0"
}
}

15
tools/wsemulator/run.ps1 Normal file
View File

@@ -0,0 +1,15 @@
$ScriptDir = Split-Path $script:MyInvocation.MyCommand.Path
# build web
$webfilesbuilder = $ScriptDir + "\..\webfilesbuilder"
node $webfilesbuilder\node_modules\gulp\bin\gulp.js --cwd $webfilesbuilder
# run chrome
$pathToChrome = 'C:\Program Files (x86)\Google\Chrome\Application\chrome.exe'
$tempFolder = '--user-data-dir=c:\temp'
$startmode = '--remote-debugging-port=9222 --disable-web-security --disable-gpu'
$startPage = $ScriptDir + "\..\..\src\websrc\temp\index.html"
Start-Process -FilePath $pathToChrome -ArgumentList $tempFolder, $startmode, $startPage
# run ws fake server
node wserver.js

7
tools/wsemulator/run.sh Normal file
View File

@@ -0,0 +1,7 @@
#!/bin/sh
node $PWD/../webfilesbuilder/node_modules/gulp/bin/gulp.js --cwd $PWD/../webfilesbuilder
open -na Google\ Chrome --args --disable-web-security --remote-debugging-port=9222 --user-data-dir="/tmp/chrome_dev" $PWD/../../src/websrc/temp/index.html
node wserver.js

250
tools/wsemulator/wserver.js Normal file
View File

@@ -0,0 +1,250 @@
console.log("[INFO] Starting MyESP WebSocket Emulation Server");
const WebSocket = require("ws");
console.log("[INFO] You can connect to ws://localhost or load URL .../src/websrc/temp/index.html");
console.log("[INFO] Password is 'neo'");
const wss = new WebSocket.Server({
port: 80
});
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
};
var networks = {
"command": "ssidlist",
"list": [{
"ssid": "Company's Network",
"bssid": "4c:f4:39:a1:41",
"rssi": "-84"
},
{
"ssid": "Home Router",
"bssid": "8a:e6:63:a8:15",
"rssi": "-42"
},
{
"ssid": "SSID Shown Here",
"bssid": "8a:f5:86:c3:12",
"rssi": "-77"
},
{
"ssid": "Great Wall of WPA",
"bssid": "9c:f1:90:c5:15",
"rssi": "-80"
},
{
"ssid": "Not Internet",
"bssid": "8c:e4:57:c5:16",
"rssi": "-87"
}
]
}
var eventlog = {
"command": "eventlist",
"page": 1,
"haspages": 1,
"list": [
"{ \"type\": \"WARN\", \"src\": \"sys\", \"desc\": \"Event log cleared!\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"WARN\", \"src\": \"sys\", \"desc\": \"Event log cleared!\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"INFO\", \"src\": \"wifi\", \"desc\": \"WiFi is connected\", \"data\": \"SMC\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"sys\", \"desc\": \"System setup completed, running\", \"data\": \"\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"wifi\", \"desc\": \"WiFi is connected\", \"data\": \"SMC\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"sys\", \"desc\": \"System setup completed, running\", \"data\": \"\", \"time\": 13 }",
"{ \"type\": \"WARN\", \"src\": \"websrv\", \"desc\": \"New login attempt\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"INFO\", \"src\": \"websrv\", \"desc\": \"Login success!\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"INFO\", \"src\": \"wifi\", \"desc\": \"WiFi is connected\", \"data\": \"SMC\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"sys\", \"desc\": \"System setup completed, running\", \"data\": \"\", \"time\": 13 }",
"{ \"type\": \"WARN\", \"src\": \"websrv\", \"desc\": \"New login attempt\", \"data\": \"\", \"time\": 1563371160 }"
]
}
var configfile = {
"command": "configfile",
"network": {
"ssid": "myssid",
"wmode": "0",
"password": "password"
},
"general": {
"hostname": "myesp",
"password": "admin",
"serial": true
},
"mqtt": {
"enabled": false,
"ip": "ip",
"port": "port",
"base": "base",
"user": "user",
"password": "password",
"heartbeat": false
},
"ntp": {
"server": "pool.ntp.org",
"interval": "30",
"timezone": "0",
"enabled": false
}
};
var custom_configfile = {
"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": 120,
"heating_circuit": 1,
"tx_mode": 2
}
};
function sendEventLog() {
wss.broadcast(eventlog);
var res = {
"command": "result",
"resultof": "eventlist",
"result": true
};
wss.broadcast(res);
}
function sendStatus() {
var stats = {
"command": "status",
"heap": 30,
"availsize": 555,
"availspiffs": 445,
"spiffssize": 888,
"sketchsize": 222,
"uptime": "1 Day 6 Hours",
"ssid": "SSID",
"mac": "EM:44:11:33:22",
"ip": "192.168.2.2",
"signalstr": 66,
"systemload": 10,
"mqttconnected": true,
"mqttheartbeat": false
};
wss.broadcast(stats);
}
function sendCustomStatus() {
var stats = {
"command": "custom_status",
"version": "1.9.0b",
"customname": "ems-esp",
"helpurl": "https://github.com/proddy/EMS-ESP/wiki",
"updateurl": "https://api.github.com/repos/proddy/EMS-ESP/releases/latest",
"emsbus": {
"ok": true,
"msg": "everything is OK",
"devices": [
{ "type": 1, "model": "model 1", "deviceid": "device id1", "version": "version id1", "productid": "product id1" },
{ "type": 2, "model": "model 2", "deviceid": "device id2", "version": "version id2", "productid": "product id2" },
{ "type": 3, "model": "model 3", "deviceid": "device id3", "version": "version id3", "productid": "product id3" },
{ "type": 4, "model": "model 4", "deviceid": "device id3", "version": "version id3", "productid": "product id3" },
{ "type": 5, "model": "model 5", "deviceid": "device id3", "version": "version id3", "productid": "product id3" }
]
},
"thermostat": {
"ok": true,
"tm": "model abc",
"ts": "23",
"tc": "27.5",
"tmode": "manual"
},
"boiler": {
"ok": true,
"bm": "mode boiler",
"b1": "on",
"b2": "off",
"b3": 5.8,
"b4": 61.5
}
};
wss.broadcast(stats);
}
wss.on('connection', function connection(ws) {
ws.on("error", () => console.log("[WARN] WebSocket Error - Assume a client is disconnected."));
ws.on('message', function incoming(message) {
var obj = JSON.parse(message);
console.log("[INFO] Got Command: " + obj.command);
switch (obj.command) {
case "configfile":
console.log("[INFO] New system settings file received");
configfile = obj;
break;
case "custom_configfile":
console.log("[INFO] New custom config file received");
custom_configfile = obj;
break;
case "status":
console.log("[INFO] Sending Fake Emulator Status");
sendStatus();
break;
case "custom_status":
console.log("[INFO] Sending custom status");
sendCustomStatus();
break;
case "scan":
console.log("[INFO] Sending Fake Wireless Networks");
wss.broadcast(networks);
break;
case "gettime":
console.log("[INFO] Sending time");
var res = {};
res.command = "gettime";
res.epoch = Math.floor((new Date).getTime() / 1000);
res.timezone = configfile.timezone;
wss.broadcast(res);
break;
case "settime":
console.log("[INFO] Setting time (fake)");
var res = {};
res.command = "gettime";
res.epoch = Math.floor((new Date).getTime() / 1000);
res.timezone = configfile.timezone;
wss.broadcast(res);
break;
case "getconf":
console.log("[INFO] Sending system configuration file (if set any)");
wss.broadcast(configfile);
break;
case "geteventlog":
console.log("[INFO] Sending eventlog");
sendEventLog();
break;
case "clearevent":
console.log("[INFO] Clearing eventlog");
break;
case "restart":
console.log("[INFO] Restart");
break;
case "destroy":
console.log("[INFO] Destroy");
break;
default:
console.log("[WARN] Unknown command ");
break;
}
});
});