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

2640
src/MyESP.cpp Normal file

File diff suppressed because it is too large Load Diff

450
src/MyESP.h Normal file
View File

@@ -0,0 +1,450 @@
/*
* MyESP.h
*
* Paul Derbyshire - December 2018
*/
#pragma once
#ifndef MyESP_h
#define MyESP_h
#define MYESP_VERSION "1.2"
#include <ArduinoJson.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 <ESPAsyncUDP.h>
#include <ESPAsyncWebServer.h>
#include <FS.h>
#include <JustWifi.h> // https://github.com/xoseperez/justwifi
#include "Ntp.h"
#include "TelnetSpy.h" // modified from https://github.com/yasheena/telnetspy
#ifdef CRASH
#include <EEPROM_Rotate.h>
#endif
extern "C" {
void custom_crash_callback(struct rst_info *, uint32_t, uint32_t);
#include "user_interface.h"
extern struct rst_info resetInfo;
}
#if defined(ARDUINO_ARCH_ESP32)
#include <SPIFFS.h> // added for ESP32
#define ets_vsnprintf vsnprintf // added for ESP32
#define OTA_PORT 3232
#else
#include <ESPAsyncTCP.h>
#define OTA_PORT 8266
#endif
// web files
// reference libs
#include "webh/glyphicons-halflings-regular.woff.gz.h"
#include "webh/required.css.gz.h"
#include "webh/required.js.gz.h"
// custom stuff
#include "webh/index.html.gz.h"
#include "webh/myesp.html.gz.h"
#include "webh/myesp.js.gz.h"
#define MYESP_CONFIG_FILE "/myesp.json"
#define MYESP_CUSTOMCONFIG_FILE "/customconfig.json"
#define MYESP_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
#define MYESP_WIFI_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds)
#define MYESP_WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
// MQTT
#define MQTT_PORT 1883 // MQTT port
#define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
#define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
#define MQTT_TOPIC_START "start"
#define MQTT_TOPIC_HEARTBEAT "heartbeat"
#define MQTT_TOPIC_START_PAYLOAD "start"
#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
#define MQTT_CONNECT_EVENT 0
#define MQTT_DISCONNECT_EVENT 1
#define MQTT_MESSAGE_EVENT 2
// Telnet
#define TELNET_SERIAL_BAUD 115200
#define TELNET_MAX_COMMAND_LENGTH 80 // length of a command
#define TELNET_EVENT_CONNECT 1
#define TELNET_EVENT_DISCONNECT 0
#define TELNET_EVENT_SHOWCMD 10
#define TELNET_EVENT_SHOWSET 20
// ANSI Colors
#define COLOR_RESET "\x1B[0m"
#define COLOR_BLACK "\x1B[0;30m"
#define COLOR_RED "\x1B[0;31m"
#define COLOR_GREEN "\x1B[0;32m"
#define COLOR_YELLOW "\x1B[0;33m"
#define COLOR_BLUE "\x1B[0;34m"
#define COLOR_MAGENTA "\x1B[0;35m"
#define COLOR_CYAN "\x1B[0;36m"
#define COLOR_WHITE "\x1B[0;37m"
#define COLOR_BOLD_ON "\x1B[1m"
#define COLOR_BOLD_OFF "\x1B[22m"
#define COLOR_BRIGHT_BLACK "\x1B[0;90m"
#define COLOR_BRIGHT_RED "\x1B[0;91m"
#define COLOR_BRIGHT_GREEN "\x1B[0;92m"
#define COLOR_BRIGHT_YELLOW "\x1B[0;99m"
#define COLOR_BRIGHT_BLUE "\x1B[0;94m"
#define COLOR_BRIGHT_MAGENTA "\x1B[0;95m"
#define COLOR_BRIGHT_CYAN "\x1B[0;96m"
#define COLOR_BRIGHT_WHITE "\x1B[0;97m"
// reset reason codes
PROGMEM const char custom_reset_hardware[] = "Hardware button";
PROGMEM const char custom_reset_terminal[] = "Restart from terminal";
PROGMEM const char custom_reset_mqtt[] = "Restart from MQTT";
PROGMEM const char custom_reset_ota[] = "Restart after successful OTA update";
PROGMEM const char custom_reset_factory[] = "Factory reset";
PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, custom_reset_terminal, custom_reset_mqtt, custom_reset_ota, custom_reset_factory};
#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button
#define CUSTOM_RESET_TERMINAL 2 // Reset from terminal
#define CUSTOM_RESET_MQTT 3 // Reset via MQTT
#define CUSTOM_RESET_OTA 4 // Reset after successful OTA update
#define CUSTOM_RESET_FACTORY 5 // Factory reset
#define CUSTOM_RESET_MAX 5
// SPIFFS
#define MYESP_SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/
#define MYESP_JSON_MAXSIZE 2000 // for large Dynamic json files
// CRASH
/**
* Structure of the single crash data set
*
* 1. Crash time
* 2. Restart reason
* 3. Exception cause
* 4. epc1
* 5. epc2
* 6. epc3
* 7. excvaddr
* 8. depc
* 9. address of stack start
* 10. address of stack end
* 11. stack trace bytes
* ...
*/
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
// Base address of USER RTC memory
// https://github.com/esp8266/esp8266-wiki/wiki/Memory-Map#memmory-mapped-io-registers
#define RTCMEM_ADDR_BASE (0x60001200)
// RTC memory is accessed using blocks of 4 bytes.
// Blocks 0..63 are reserved by the SDK, 64..192 are available to the user.
// Blocks 64..96 are reserved by the eboot 'struct eboot_command' (128 -> (128 / 4) -> 32):
// https://github.com/esp8266/Arduino/blob/master/bootloaders/eboot/eboot_command.h
#define RTCMEM_OFFSET 32u
#define RTCMEM_ADDR (RTCMEM_ADDR_BASE + (RTCMEM_OFFSET * 4u))
#define RTCMEM_BLOCKS 96u
#define RTCMEM_MAGIC 0x45535076
struct RtcmemData {
uint32_t magic; // RTCMEM_MAGIC
uint32_t sys; // system details
};
static_assert(sizeof(RtcmemData) <= (RTCMEM_BLOCKS * 4u), "RTCMEM struct is too big");
#define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute)
#define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot
#define MYESP_HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins)
typedef struct {
bool set; // is it a set command
char key[50];
char description[100];
} command_t;
typedef enum { MYESP_FSACTION_SET, MYESP_FSACTION_LIST, MYESP_FSACTION_SAVE, MYESP_FSACTION_LOAD } MYESP_FSACTION;
typedef enum {
MYESP_BOOTSTATUS_POWERON = 0,
MYESP_BOOTSTATUS_BOOTED = 1,
MYESP_BOOTSTATUS_BOOTING = 2,
MYESP_BOOTSTATUS_RESETNEEDED = 3
} MYESP_BOOTSTATUS; // boot messages
typedef std::function<void(unsigned int, const char *, const char *)> mqtt_callback_f;
typedef std::function<void()> wifi_callback_f;
typedef std::function<void()> ota_callback_f;
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
typedef std::function<void(uint8_t)> telnet_callback_f;
typedef std::function<bool(MYESP_FSACTION, JsonObject json)> fs_loadsave_callback_f;
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_setlist_callback_f;
typedef std::function<void(JsonObject root)> web_callback_f;
// calculates size of an 2d array at compile time
template <typename T, size_t N>
constexpr size_t ArraySize(T (&)[N]) {
return N;
}
#define MYESP_UPTIME_OVERFLOW 4294967295 // Uptime overflow value
// web min and max length of wifi ssid and password
#define MYESP_MAX_STR_LEN 16
#define MYESP_BOOTUP_FLASHDELAY 50 // flash duration for LED at bootup sequence
#define MYESP_BOOTUP_DELAY 2000 // time before we open the window to reset. This is to stop resetting values when uploading firmware via USB
// class definition
class MyESP {
protected:
// webserver
AsyncWebServer * _webServer;
AsyncWebSocket * _ws;
// NTP
NtpClient NTP;
public:
MyESP();
~MyESP();
// write event called from within lambda classs
static void _writeEvent(const char * type, const char * src, const char * desc, const char * data);
// wifi
void setWIFICallback(void (*callback)());
void setWIFI(wifi_callback_f callback);
bool isWifiConnected();
bool isAPmode();
// mqtt
bool isMQTTConnected();
void mqttSubscribe(const char * topic);
void mqttUnsubscribe(const char * topic);
void mqttPublish(const char * topic, const char * payload);
void setMQTT(mqtt_callback_f callback);
// OTA
void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post);
// debug & telnet
void myDebug(const char * format, ...);
void myDebug_P(PGM_P format_P, ...);
void setTelnet(telnetcommand_callback_f callback_cmd, telnet_callback_f callback);
bool getUseSerial();
void setUseSerial(bool toggle);
// FS
void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true);
bool fs_saveConfig(JsonObject root);
bool fs_saveCustomConfig(JsonObject root);
// Web
void setWeb(web_callback_f callback_web);
// Crash
void crashClear();
void crashDump();
void crashTest(uint8_t t);
void crashInfo();
// general
void end();
void loop();
void begin(const char * app_hostname, const char * app_name, const char * app_version, const char * app_helpurl, const char * app_updateurl);
void resetESP();
int getWifiQuality();
void showSystemStats();
bool getHeartbeat();
uint32_t getSystemLoadAverage();
uint32_t getSystemResetReason();
uint8_t getSystemBootStatus();
bool _have_ntp_time;
private:
// mqtt
AsyncMqttClient mqttClient;
unsigned long _mqtt_reconnect_delay;
void _mqttOnMessage(char * topic, char * payload, size_t len);
void _mqttConnect();
void _mqtt_setup();
mqtt_callback_f _mqtt_callback_f;
void _mqttOnConnect();
void _sendStart();
char * _mqttTopic(const char * topic);
char * _mqtt_ip;
char * _mqtt_user;
char * _mqtt_password;
int _mqtt_port;
char * _mqtt_base;
bool _mqtt_enabled;
unsigned long _mqtt_keepalive;
unsigned char _mqtt_qos;
bool _mqtt_retain;
char * _mqtt_will_topic;
char * _mqtt_will_online_payload;
char * _mqtt_will_offline_payload;
unsigned long _mqtt_last_connection;
bool _mqtt_connecting;
bool _mqtt_heartbeat;
// wifi
void _wifiCallback(justwifi_messages_t code, char * parameter);
void _wifi_setup();
wifi_callback_f _wifi_callback_f;
char * _network_ssid;
char * _network_password;
uint8_t _network_wmode;
bool _wifi_connected;
String _getESPhostname();
// ota
ota_callback_f _ota_pre_callback_f;
ota_callback_f _ota_post_callback_f;
void _ota_setup();
void _OTACallback();
// crash
void _eeprom_setup();
// telnet & debug
TelnetSpy SerialAndTelnet;
void _telnetConnected();
void _telnetDisconnected();
void _telnetHandle();
void _telnetCommand(char * commandLine);
char * _telnet_readWord(bool allow_all_chars);
void _telnet_setup();
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
void _consoleShowHelp();
telnetcommand_callback_f _telnetcommand_callback_f; // Callable for projects commands
telnet_callback_f _telnet_callback_f; // callback for connect/disconnect
bool _changeSetting(uint8_t wc, const char * setting, const char * value);
// fs and settings
void _fs_setup();
bool _fs_loadConfig();
bool _fs_loadCustomConfig();
void _fs_printFile(const char * file);
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;
void _printSetCommands();
// general
char * _general_hostname;
char * _app_name;
char * _app_version;
char * _app_helpurl;
char * _app_updateurl;
bool _suspendOutput;
bool _general_serial;
unsigned long _getUptime();
char * _getBuildTime();
char * _buildTime;
bool _timerequest;
bool _formatreq;
bool _hasValue(char * s);
// reset reason and rtcmem
bool _rtcmem_status;
bool _rtcmemStatus();
bool _getRtcmemStatus();
void _rtcmemInit();
void _rtcmemSetup();
void _deferredReset(unsigned long delay, uint8_t reason);
uint8_t _getSystemStabilityCounter();
void _setSystemStabilityCounter(uint8_t counter);
void _setSystemResetReason(uint8_t reason);
uint8_t _getCustomResetReason();
void _setCustomResetReason(uint8_t reason);
uint8_t _getSystemResetReason();
void _setSystemBootStatus(uint8_t status);
bool _systemStable;
void _bootupSequence();
bool _getSystemCheck();
void _systemCheckLoop();
void _setSystemCheck(bool stable);
// load average (0..100) and heap ram
void _calculateLoad();
uint32_t _load_average;
uint32_t _getInitialFreeHeap();
uint32_t _getUsedHeap();
// heartbeat
void _heartbeatCheck(bool force);
// 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 _webRootPage();
void _webResetPage();
void _webResetAllPage();
// ntp
uint8_t _ntp_timezone;
char * _ntp_server;
uint8_t _ntp_interval;
bool _ntp_enabled;
};
extern MyESP myESP;
#endif

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

655
src/TelnetSpy.cpp Normal file
View File

@@ -0,0 +1,655 @@
/*
* TELNET SERVER FOR ESP8266 / ESP32
* Cloning the serial port via Telnet.
*
* Written by Wolfgang Mattis (arduino@yasheena.de).
* Version 1.1 / September 7, 2018.
* MIT license, all text above must be included in any redistribution.
*/
#ifdef ESP8266
extern "C" {
#include "user_interface.h"
}
#endif
#include "TelnetSpy.h"
#ifndef min
#define min(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef max
#define max(a, b) ((a) > (b) ? (a) : (b))
#endif
static TelnetSpy * actualObject = NULL;
static void TelnetSpy_putc(char c) {
if (actualObject) {
actualObject->write(c);
}
}
static void TelnetSpy_ignore_putc(char c) {
;
}
TelnetSpy::TelnetSpy() {
port = TELNETSPY_PORT;
telnetServer = NULL;
started = false;
listening = false;
firstMainLoop = true;
usedSer = &Serial;
storeOffline = true;
connected = false;
callbackConnect = NULL;
callbackDisconnect = NULL;
welcomeMsg = strdup(TELNETSPY_WELCOME_MSG);
rejectMsg = strdup(TELNETSPY_REJECT_MSG);
minBlockSize = TELNETSPY_MIN_BLOCK_SIZE;
collectingTime = TELNETSPY_COLLECTING_TIME;
maxBlockSize = TELNETSPY_MAX_BLOCK_SIZE;
pingTime = TELNETSPY_PING_TIME;
pingRef = 0xFFFFFFFF;
waitRef = 0xFFFFFFFF;
telnetBuf = NULL;
bufLen = 0;
uint16_t size = TELNETSPY_BUFFER_LEN;
while (!setBufferSize(size)) {
size = size >> 1;
if (size < minBlockSize) {
setBufferSize(minBlockSize);
break;
}
}
debugOutput = TELNETSPY_CAPTURE_OS_PRINT;
if (debugOutput) {
setDebugOutput(true);
}
}
TelnetSpy::~TelnetSpy() {
end();
}
// added by proddy
void TelnetSpy::disconnectClient() {
if (client.connected()) {
client.flush();
client.stop();
}
if (connected && (callbackDisconnect != NULL)) {
callbackDisconnect();
}
connected = false;
}
void TelnetSpy::setPort(uint16_t portToUse) {
port = portToUse;
if (listening) {
if (client.connected()) {
client.flush();
client.stop();
}
if (connected && (callbackDisconnect != NULL)) {
callbackDisconnect();
}
connected = false;
telnetServer->close();
delete telnetServer;
telnetServer = new WiFiServer(port);
if (started) {
telnetServer->begin();
telnetServer->setNoDelay(bufLen > 0);
}
}
}
void TelnetSpy::setWelcomeMsg(const char * msg) {
if (welcomeMsg) {
free(welcomeMsg);
}
welcomeMsg = strdup(msg);
}
void TelnetSpy::setRejectMsg(const char * msg) {
if (rejectMsg) {
free(rejectMsg);
}
rejectMsg = strdup(msg);
}
void TelnetSpy::setMinBlockSize(uint16_t minSize) {
minBlockSize = min(max((uint16_t)1, minSize), maxBlockSize);
}
void TelnetSpy::setCollectingTime(uint16_t colTime) {
collectingTime = colTime;
}
void TelnetSpy::setMaxBlockSize(uint16_t maxSize) {
maxBlockSize = max(maxSize, minBlockSize);
}
bool TelnetSpy::setBufferSize(uint16_t newSize) {
if (telnetBuf && (bufLen == newSize)) {
return true;
}
if (newSize == 0) {
bufLen = 0;
if (telnetBuf) {
free(telnetBuf);
telnetBuf = NULL;
}
if (telnetServer) {
telnetServer->setNoDelay(false);
}
return true;
}
newSize = max(newSize, minBlockSize);
uint16_t oldBufLen = bufLen;
bufLen = newSize;
uint16_t tmp;
if (!telnetBuf || (bufUsed == 0)) {
bufRdIdx = 0;
bufWrIdx = 0;
bufUsed = 0;
} else {
if (bufLen < oldBufLen) {
if (bufRdIdx < bufWrIdx) {
if (bufWrIdx > bufLen) {
tmp = min(bufLen, (uint16_t)(bufWrIdx - max(bufLen, bufRdIdx)));
memcpy(telnetBuf, &telnetBuf[bufWrIdx - tmp], tmp);
bufWrIdx = tmp;
if (bufWrIdx > bufRdIdx) {
bufRdIdx = bufWrIdx;
} else {
if (bufRdIdx > bufLen) {
bufRdIdx = 0;
}
}
if (bufRdIdx == bufWrIdx) {
bufUsed = bufLen;
} else {
bufUsed = bufWrIdx - bufRdIdx;
}
}
} else {
if (bufWrIdx > bufLen) {
memcpy(telnetBuf, &telnetBuf[bufWrIdx - bufLen], bufLen);
bufRdIdx = 0;
bufWrIdx = 0;
bufUsed = bufLen;
} else {
tmp = min(bufLen - bufWrIdx, oldBufLen - bufRdIdx);
memcpy(&telnetBuf[bufLen - tmp], &telnetBuf[oldBufLen - tmp], tmp);
bufRdIdx = bufLen - tmp;
bufUsed = bufWrIdx + tmp;
}
}
}
}
char * temp = (char *)realloc(telnetBuf, bufLen);
if (!temp) {
return false;
}
telnetBuf = temp;
if (telnetBuf && (bufLen > oldBufLen) && (bufRdIdx > bufWrIdx)) {
tmp = bufLen - (oldBufLen - bufRdIdx);
memcpy(&telnetBuf[tmp], &telnetBuf[bufRdIdx], oldBufLen - bufRdIdx);
bufRdIdx = tmp;
}
if (telnetServer) {
telnetServer->setNoDelay(true);
}
return true;
}
uint16_t TelnetSpy::getBufferSize() {
if (!telnetBuf) {
return 0;
}
return bufLen;
}
void TelnetSpy::setStoreOffline(bool store) {
storeOffline = store;
}
bool TelnetSpy::getStoreOffline() {
return storeOffline;
}
void TelnetSpy::setPingTime(uint16_t pngTime) {
pingTime = pngTime;
if (pingTime == 0) {
pingRef = 0xFFFFFFFF;
} else {
pingRef = (millis() & 0x7FFFFFF) + pingTime;
}
}
void TelnetSpy::setSerial(HardwareSerial * usedSerial) {
usedSer = usedSerial;
}
size_t TelnetSpy::write(uint8_t data) {
if (telnetBuf) {
if (storeOffline || client.connected()) {
if (bufUsed == bufLen) {
if (client.connected()) {
sendBlock();
}
if (bufUsed == bufLen) {
char c;
while (bufUsed > 0) {
c = pullTelnetBuf();
if (c == '\n') {
addTelnetBuf('\r');
break;
}
}
if (peekTelnetBuf() == '\r') {
pullTelnetBuf();
}
}
}
addTelnetBuf(data);
/*
if (data == '\n') {
addTelnetBuf('\r'); // added by proddy, fix for Windows
}
*/
}
} else {
if (client.connected()) {
client.write(data);
}
}
if (usedSer) {
return usedSer->write(data);
}
return 1;
}
// this still needs some work
bool TelnetSpy::isSerialAvailable(void) {
if (usedSer) {
usedSer->write("test");
//Wait for four seconds or till data is available on serial, whichever occurs first.
while (usedSer->available() == 0 && millis() < 4000)
;
if (usedSer->available() > 0) {
(void)usedSer->read(); // We then clear the input buffer
return true;
}
}
return false;
}
int TelnetSpy::available(void) {
if (usedSer) {
int avail = usedSer->available();
if (avail > 0) {
return avail;
}
}
if (client.connected()) {
return telnetAvailable();
}
return 0;
}
int TelnetSpy::read(void) {
int val = 0;
if (usedSer) {
val = usedSer->read();
if (val != -1) {
return val;
}
}
if (client.connected()) {
if (telnetAvailable()) {
val = client.read();
}
}
return val;
}
int TelnetSpy::peek(void) {
int val = 0;
if (usedSer) {
val = usedSer->peek();
if (val != -1) {
return val;
}
}
if (client.connected()) {
if (telnetAvailable()) {
val = client.peek();
}
}
return val;
}
void TelnetSpy::flush(void) {
if (usedSer) {
usedSer->flush();
}
}
#ifdef ESP8266
void TelnetSpy::begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin) {
if (usedSer) {
usedSer->begin(baud, config, mode, tx_pin);
}
started = true;
}
#else // ESP32
void TelnetSpy::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert) {
if (usedSer) {
usedSer->begin(baud, config, rxPin, txPin, invert);
}
started = true;
}
#endif
void TelnetSpy::end() {
if (debugOutput) {
setDebugOutput(false);
}
if (usedSer) {
usedSer->end();
}
if (client.connected()) {
client.flush();
client.stop();
}
if (connected && (callbackDisconnect != NULL)) {
callbackDisconnect();
}
connected = false;
telnetServer->close();
delete telnetServer;
telnetServer = NULL;
listening = false;
started = false;
}
#ifdef ESP8266
void TelnetSpy::swap(uint8_t tx_pin) {
if (usedSer) {
usedSer->swap(tx_pin);
}
}
void TelnetSpy::set_tx(uint8_t tx_pin) {
if (usedSer) {
usedSer->set_tx(tx_pin);
}
}
void TelnetSpy::pins(uint8_t tx, uint8_t rx) {
if (usedSer) {
usedSer->pins(tx, rx);
}
}
bool TelnetSpy::isTxEnabled(void) {
if (usedSer) {
return usedSer->isTxEnabled();
}
return true;
}
bool TelnetSpy::isRxEnabled(void) {
if (usedSer) {
return usedSer->isRxEnabled();
}
return true;
}
#endif
int TelnetSpy::availableForWrite(void) {
if (usedSer) {
return min(usedSer->availableForWrite(), bufLen - bufUsed);
}
return bufLen - bufUsed;
}
TelnetSpy::operator bool() const {
if (usedSer) {
return (bool)*usedSer;
}
return true;
}
void TelnetSpy::setDebugOutput(bool en) {
debugOutput = en;
if (debugOutput) {
actualObject = this;
#ifdef ESP8266
os_install_putc1((void *)TelnetSpy_putc); // Set system printing (os_printf) to TelnetSpy
system_set_os_print(true);
#endif
} else {
if (actualObject == this) {
#ifdef ESP8266
system_set_os_print(false);
os_install_putc1((void *)TelnetSpy_ignore_putc); // Ignore system printing
#endif
actualObject = NULL;
}
}
}
uint32_t TelnetSpy::baudRate(void) {
if (usedSer) {
return usedSer->baudRate();
}
return 115200;
}
void TelnetSpy::sendBlock() {
uint16_t len = bufUsed;
if (len > maxBlockSize) {
len = maxBlockSize;
}
len = min(len, (uint16_t)(bufLen - bufRdIdx));
client.write(&telnetBuf[bufRdIdx], len);
bufRdIdx += len;
if (bufRdIdx >= bufLen) {
bufRdIdx = 0;
}
bufUsed -= len;
if (bufUsed == 0) {
bufRdIdx = 0;
bufWrIdx = 0;
}
waitRef = 0xFFFFFFFF;
if (pingRef != 0xFFFFFFFF) {
pingRef = (millis() & 0x7FFFFFF) + pingTime;
if (pingRef > 0x7FFFFFFF) {
pingRef -= 0x80000000;
}
}
}
void TelnetSpy::addTelnetBuf(char c) {
telnetBuf[bufWrIdx] = c;
if (bufUsed == bufLen) {
bufRdIdx++;
if (bufRdIdx >= bufLen) {
bufRdIdx = 0;
}
} else {
bufUsed++;
}
bufWrIdx++;
if (bufWrIdx >= bufLen) {
bufWrIdx = 0;
}
}
char TelnetSpy::pullTelnetBuf() {
if (bufUsed == 0) {
return 0;
}
char c = telnetBuf[bufRdIdx++];
if (bufRdIdx >= bufLen) {
bufRdIdx = 0;
}
bufUsed--;
return c;
}
char TelnetSpy::peekTelnetBuf() {
if (bufUsed == 0) {
return 0;
}
return telnetBuf[bufRdIdx];
}
int TelnetSpy::telnetAvailable() {
int n = client.available();
while (n > 0) {
if (0xff == client.peek()) { // If esc char for telnet NVT protocol data remove that telegram:
client.read(); // Remove esc char
n--;
if (0xff == client.peek()) { // If esc sequence for 0xFF data byte...
return n; // ...return info about available data (just this 0xFF data byte)
}
client.read(); // Skip the rest of the telegram of the telnet NVT protocol data
client.read();
n--;
n--;
} else { // If next char is a normal data byte...
return n; // ...return info about available data
}
}
return 0;
}
bool TelnetSpy::isClientConnected() {
return connected;
}
void TelnetSpy::setCallbackOnConnect(telnetSpyCallback callback) {
callbackConnect = callback;
}
void TelnetSpy::setCallbackOnDisconnect(telnetSpyCallback callback) {
callbackDisconnect = callback;
}
void TelnetSpy::serialPrint(char c) {
if (usedSer) {
usedSer->print(c);
}
}
void TelnetSpy::handle() {
if (firstMainLoop) {
firstMainLoop = false;
// Between setup() and loop() the configuration for os_print may be changed so it must be renewed
if (debugOutput && (actualObject == this)) {
setDebugOutput(true);
}
}
if (!started) {
return;
}
if (!listening) {
if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.getMode() & WIFI_AP)) {
if (usedSer) {
usedSer->println("[TELNET] in AP mode"); // added by Proddy
}
} else if (WiFi.status() != WL_CONNECTED) {
return;
}
telnetServer = new WiFiServer(port);
telnetServer->begin();
telnetServer->setNoDelay(bufLen > 0);
listening = true;
if (usedSer) {
usedSer->println("[TELNET] Telnet server started"); // added by Proddy
}
}
if (telnetServer->hasClient()) {
if (client.connected()) {
WiFiClient rejectClient = telnetServer->available();
if (strlen(rejectMsg) > 0) {
rejectClient.write((const uint8_t *)rejectMsg, strlen(rejectMsg));
}
rejectClient.flush();
rejectClient.stop();
} else {
client = telnetServer->available();
if (strlen(welcomeMsg) > 0) {
client.write((const uint8_t *)welcomeMsg, strlen(welcomeMsg));
}
}
}
if (client.connected()) {
if (!connected) {
connected = true;
if (pingTime != 0) {
pingRef = (millis() & 0x7FFFFFF) + pingTime;
}
if (callbackConnect != NULL) {
callbackConnect();
}
}
} else {
if (connected) {
connected = false;
client.flush();
client.stop();
pingRef = 0xFFFFFFFF;
waitRef = 0xFFFFFFFF;
if (callbackDisconnect != NULL) {
callbackDisconnect();
}
}
}
if (client.connected() && (bufUsed > 0)) {
if (bufUsed >= minBlockSize) {
sendBlock();
} else {
unsigned long m = millis() & 0x7FFFFFF;
if (waitRef == 0xFFFFFFFF) {
waitRef = m + collectingTime;
if (waitRef > 0x7FFFFFFF) {
waitRef -= 0x80000000;
}
} else {
if (!((waitRef < 0x20000000) && (m > 0x60000000)) && (m >= waitRef)) {
sendBlock();
}
}
}
}
if (client.connected() && (pingRef != 0xFFFFFFFF)) {
unsigned long m = millis() & 0x7FFFFFF;
if (!((pingRef < 0x20000000) && (m > 0x60000000)) && (m >= pingRef)) {
addTelnetBuf(0);
sendBlock();
}
}
}

281
src/TelnetSpy.h Normal file
View File

@@ -0,0 +1,281 @@
/*
* TELNET SERVER FOR ESP8266 / ESP32
* Cloning the serial port via Telnet.
*
* Written by Wolfgang Mattis (arduino@yasheena.de).
* Version 1.1 / September 7, 2018.
* MIT license, all text above must be included in any redistribution.
*/
/*
* DESCRIPTION
*
* This module allows you "Debugging over the air". So if you already use
* ArduinoOTA this is a helpful extension for wireless development. Use
* "TelnetSpy" instead of "Serial" to send data to the serial port and a copy
* to a telnet connection. There is a circular buffer which allows to store the
* data while the telnet connection is not established. So its possible to
* collect data even when the Wifi and Telnet connections are still not
* established. Its also possible to create a telnet session only if it is
* neccessary: then you will get the already collected data as far as it is
* still stored in the circular buffer. Data send from telnet terminal to
* ESP8266 / ESP32 will be handled as data received by serial port. It is also
* possible to use more than one instance of TelnetSpy, for example to send
* control information on the first instance and data dumps on the second
* instance.
*
* USAGE
*
* Add the following line to your sketch:
* #include <TelnetSpy.h>
* TelnetSpy LOG;
*
* Add the following line to your initialisation block ( void setup() ):
* LOG.begin();
*
* Add the following line at the beginning of your main loop ( void loop() ):
* LOG.handle();
*
* Use the following functions of the TelnetSpy object to modify behavior
*
* Change the port number of this telnet server. If a client is already
* connected it will be disconnected.
* Default: 23
* void setPort(uint16_t portToUse);
*
* Change the message which will be send to the telnet client after a session
* is established.
* Default: "Connection established via TelnetSpy.\n"
* void setWelcomeMsg(char* msg);
*
* Change the message which will be send to the telnet client if another
* session is already established.
* Default: "TelnetSpy: Only one connection possible.\n"
* void setRejectMsg(char* msg);
*
* Change the amount of characters to collect before sending a telnet block.
* Default: 64
* void setMinBlockSize(uint16_t minSize);
*
* Change the time (in ms) to wait before sending a telnet block if its size is
* less than <minSize> (defined by setMinBlockSize).
* Default: 100
* void setCollectingTime(uint16_t colTime);
*
* Change the maximum size of the telnet packets to send.
* Default: 512
* void setMaxBlockSize(uint16_t maxSize);
*
* Change the size of the ring buffer. Set it to 0 to disable buffering.
* Changing size tries to preserve the already collected data. If the new
* buffer size is too small the youngest data will be preserved only. Returns
* false if the requested buffer size cannot be set.
* Default: 3000
* bool setBufferSize(uint16_t newSize);
*
* This function returns the actual size of the ring buffer.
* uint16_t getBufferSize();
*
* Enable / disable storing new data in the ring buffer if no telnet connection
* is established. This function allows you to store important data only. You
* can do this by disabling "storeOffline" for sending less important data.
* Default: true
* void setStoreOffline(bool store);
*
* Get actual state of storing data when offline.
* bool getStoreOffline();
*
* If no data is sent via TelnetSpy the detection of a disconnected client has
* a long timeout. Use setPingTime to define the time (in ms) without traffic
* after which a ping (chr(0)) is sent to the telnet client to detect a
* disconnect earlier. Use 0 as parameter to disable pings.
* Default: 1500
* void setPingTime(uint16_t pngTime);
*
* Set the serial port you want to use with this object (especially for ESP32)
* or NULL if no serial port should be used (telnet only).
* Default: Serial
* void setSerial(HardwareSerial* usedSerial);
*
* This function returns true, if a telnet client is connected.
* bool isClientConnected();
*
* This function installs a callback function which will be called on every
* telnet connect of this object (except rejected connect tries). Use NULL to
* remove the callback.
* Default: NULL
* void setCallbackOnConnect(void (*callback)());
*
* This function installs a callback function which will be called on every
* telnet disconnect of this object (except rejected connect tries). Use NULL
* to remove the callback.
* Default: NULL
* void setCallbackOnDisconnect(void (*callback)());
*
* HINT
*
* Add the following lines to your sketch:
* #define SERIAL TelnetSpy
* //#define SERIAL Serial
*
* Replace "Serial" with "SERIAL" in your sketch. Now you can switch between
* serial only and serial with telnet by changing the comments of the defines
* only.
*
* IMPORTANT
*
* To connect to the telnet server you have to:
* - establish the Wifi connection
* - execute "TelnetSpy.begin(WhatEverYouWant);"
*
* The order is not important.
*
* All you do with "Serial" you can also do with "TelnetSpy", but remember:
* Transfering data also via telnet will need more performance than the serial
* port only. So time critical things may be influenced.
*
* It is not possible to establish more than one telnet connection at the same
* time. But its possible to use more than one instance of TelnetSpy.
*
* If you have problems with low memory you may reduce the value of the define
* TELNETSPY_BUFFER_LEN for a smaller ring buffer on initialisation.
*
* Usage of void setDebugOutput(bool) to enable / disable of capturing of
* os_print calls when you have more than one TelnetSpy instance: That
* TelnetSpy object will handle this functionality where you used
* setDebugOutput at last. On default TelnetSpy has the capturing of OS_print
* calls enabled. So if you have more instances the last created instance will
* handle the capturing.
*/
#ifndef TelnetSpy_h
#define TelnetSpy_h
#define TELNETSPY_BUFFER_LEN 3000
#define TELNETSPY_MIN_BLOCK_SIZE 64
#define TELNETSPY_COLLECTING_TIME 100
#define TELNETSPY_MAX_BLOCK_SIZE 512
#define TELNETSPY_PING_TIME 1500
#define TELNETSPY_PORT 23
#define TELNETSPY_CAPTURE_OS_PRINT true
#define TELNETSPY_WELCOME_MSG "Connection established via Telnet.\n"
#define TELNETSPY_REJECT_MSG "Telnet: Only one connection possible.\n"
#ifdef ESP8266
#include <ESP8266WiFi.h>
#else // ESP32
#include <WiFi.h>
#endif
#include <WiFiClient.h>
class TelnetSpy : public Stream {
public:
TelnetSpy();
~TelnetSpy();
void handle(void);
void setPort(uint16_t portToUse);
void setWelcomeMsg(const char * msg);
void setRejectMsg(const char * msg);
void setMinBlockSize(uint16_t minSize);
void setCollectingTime(uint16_t colTime);
void setMaxBlockSize(uint16_t maxSize);
bool setBufferSize(uint16_t newSize);
uint16_t getBufferSize();
void setStoreOffline(bool store);
bool getStoreOffline();
void setPingTime(uint16_t pngTime);
void setSerial(HardwareSerial * usedSerial);
bool isClientConnected();
void serialPrint(char c);
void disconnectClient(); // added by Proddy
typedef std::function<void()> telnetSpyCallback; // added by Proddy
void setCallbackOnConnect(telnetSpyCallback callback); // changed by proddy
void setCallbackOnDisconnect(telnetSpyCallback callback); // changed by proddy
// Functions offered by HardwareSerial class:
#ifdef ESP8266
void begin(unsigned long baud) {
begin(baud, SERIAL_8N1, SERIAL_FULL, 1);
}
void begin(unsigned long baud, SerialConfig config) {
begin(baud, config, SERIAL_FULL, 1);
}
void begin(unsigned long baud, SerialConfig config, SerialMode mode) {
begin(baud, config, mode, 1);
}
void begin(unsigned long baud, SerialConfig config, SerialMode mode, uint8_t tx_pin);
#else // ESP32
void begin(unsigned long baud, uint32_t config = SERIAL_8N1, int8_t rxPin = -1, int8_t txPin = -1, bool invert = false);
#endif
void end();
#ifdef ESP8266
void swap() {
swap(1);
}
void swap(uint8_t tx_pin);
void set_tx(uint8_t tx_pin);
void pins(uint8_t tx, uint8_t rx);
bool isTxEnabled(void);
bool isRxEnabled(void);
#endif
int available(void) override;
int peek(void) override;
int read(void) override;
int availableForWrite(void);
void flush(void) override;
size_t write(uint8_t) override;
inline size_t write(unsigned long n) {
return write((uint8_t)n);
}
inline size_t write(long n) {
return write((uint8_t)n);
}
inline size_t write(unsigned int n) {
return write((uint8_t)n);
}
inline size_t write(int n) {
return write((uint8_t)n);
}
using Print::write;
operator bool() const;
void setDebugOutput(bool);
uint32_t baudRate(void);
bool isSerialAvailable(void);
protected:
void sendBlock(void);
void addTelnetBuf(char c);
char pullTelnetBuf();
char peekTelnetBuf();
int telnetAvailable();
WiFiServer * telnetServer;
WiFiClient client;
uint16_t port;
HardwareSerial * usedSer;
bool storeOffline;
bool started;
bool listening;
bool firstMainLoop;
unsigned long waitRef;
unsigned long pingRef;
uint16_t pingTime;
char * welcomeMsg;
char * rejectMsg;
uint16_t minBlockSize;
uint16_t collectingTime;
uint16_t maxBlockSize;
bool debugOutput;
char * telnetBuf;
uint16_t bufLen;
uint16_t bufUsed;
uint16_t bufRdIdx;
uint16_t bufWrIdx;
bool connected;
telnetSpyCallback callbackConnect; // added by proddy
telnetSpyCallback callbackDisconnect; // added by proddy
};
#endif

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
#include "ds18.h"
#include "ems.h"
#include "ems_devices.h"
#include "emsuart.h"
#include "my_config.h"
#include "version.h"
#include <MyESP.h>
// Dallas external temp sensors
#include "ds18.h"
DS18 ds18;
// shared libraries
#include <MyESP.h>
// public libraries
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
#include <CRC32.h> // https://github.com/bakercp/CRC32
@@ -32,7 +30,7 @@ DS18 ds18;
#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.
#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
@@ -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_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 {
uint32_t timestamp; // for internal timings, via millis()
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_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_parasite <on | off>", "set to on if powering Dallas sesnsors 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, "dallas_parasite <on | off>", "set to on if powering Dallas sensors via parasite power"},
{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_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()) {
myDebug_P(PSTR("")); // newline
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("Bottom temperature", "C", EMS_SolarModule.bottomTemp);
_renderIntValue("Pump modulation", "%", EMS_SolarModule.pumpModulation);
@@ -561,16 +559,16 @@ void showInfo() {
(EMS_SolarModule.pumpWorkMin % 1440) / 60,
EMS_SolarModule.pumpWorkMin % 60);
}
_renderUShortValue("Energy Last Hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10
_renderUShortValue("Energy Today", "Wh", EMS_SolarModule.EnergyToday, 0);
_renderUShortValue("Energy Total", "kWH", EMS_SolarModule.EnergyTotal, 1); // *10
_renderUShortValue("Energy last hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10
_renderUShortValue("Energy today", "Wh", EMS_SolarModule.EnergyToday, 0);
_renderUShortValue("Energy total", "kWH", EMS_SolarModule.EnergyTotal, 1); // *10
}
// For HeatPumps
if (ems_getHeatPumpEnabled()) {
myDebug_P(PSTR("")); // newline
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 speed", "%", EMS_HeatPump.HPSpeed);
}
@@ -579,7 +577,7 @@ void showInfo() {
if (ems_getThermostatEnabled()) {
myDebug_P(PSTR("")); // newline
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
if (ems_getThermostatModel() == EMS_MODEL_EASY) {
@@ -683,8 +681,6 @@ void publishSensorValues() {
}
}
// send values via MQTT
// 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
@@ -1131,44 +1127,42 @@ void runUnitTest(uint8_t test_num) {
}
// 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) {
EMSESP_Status.led = json["led"];
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;
const JsonObject & settings = json["settings"];
EMS_Thermostat.device_id = json["thermostat_type"] | EMSESP_THERMOSTAT_TYPE;
EMS_Boiler.device_id = json["boiler_type"] | EMSESP_BOILER_TYPE;
EMSESP_Status.led = settings["led"];
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"];
EMSESP_Status.shower_alert = json["shower_alert"];
EMSESP_Status.publish_time = json["publish_time"] | DEFAULT_PUBLISHTIME;
ems_setTxMode(settings["tx_mode"]);
ems_setTxMode(json["tx_mode"]);
EMSESP_Status.listen_mode = json["listen_mode"];
EMSESP_Status.listen_mode = settings["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);
return true; // return false if some settings are missing and we need to rebuild the file
return true;
}
if (action == MYESP_FSACTION_SAVE) {
json["thermostat_type"] = EMS_Thermostat.device_id;
json["boiler_type"] = EMS_Boiler.device_id;
json["led"] = EMSESP_Status.led;
json["led_gpio"] = EMSESP_Status.led_gpio;
json["dallas_gpio"] = EMSESP_Status.dallas_gpio;
json["dallas_parasite"] = EMSESP_Status.dallas_parasite;
json["listen_mode"] = EMSESP_Status.listen_mode;
json["shower_timer"] = EMSESP_Status.shower_timer;
json["shower_alert"] = EMSESP_Status.shower_alert;
json["publish_time"] = EMSESP_Status.publish_time;
json["heating_circuit"] = EMSESP_Status.heating_circuit;
json["tx_mode"] = ems_getTxMode();
JsonObject settings = json.createNestedObject("settings");
settings["led"] = EMSESP_Status.led;
settings["led_gpio"] = EMSESP_Status.led_gpio;
settings["dallas_gpio"] = EMSESP_Status.dallas_gpio;
settings["dallas_parasite"] = EMSESP_Status.dallas_parasite;
settings["listen_mode"] = EMSESP_Status.listen_mode;
settings["shower_timer"] = EMSESP_Status.shower_timer;
settings["shower_alert"] = EMSESP_Status.shower_alert;
settings["publish_time"] = EMSESP_Status.publish_time;
settings["heating_circuit"] = EMSESP_Status.heating_circuit;
settings["tx_mode"] = ems_getTxMode();
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
// 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
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;
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
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
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(" dallas_gpio=%d"), EMSESP_Status.dallas_gpio);
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);
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(" shower_timer=%s"), EMSESP_Status.shower_timer ? "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
void WIFICallback() {
// 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
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()) {
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 {
emsuart_init();
myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
if (!EMSESP_Status.listen_mode) {
// go and find the boiler and thermostat types, if not in listen mode
ems_discoverModels();
if (ems_getBusConnected()) {
if (ems_getTxCapable()) {
emsbus["ok"] = true;
emsbus["msg"] = "EMS Bus Connected, Rx and Tx active";
} 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
@@ -1862,25 +1882,14 @@ void setup() {
systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if EMS is reachable
// set up myESP for Wifi, MQTT, MDNS and Telnet
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
// MQTT host, username and password taken from the SPIFFS settings
myESP.setMQTT(
NULL, NULL, NULL, MQTT_BASE, MQTT_KEEPALIVE, MQTT_QOS, MQTT_RETAIN, MQTT_WILL_TOPIC, MQTT_WILL_ONLINE_PAYLOAD, MQTT_WILL_OFFLINE_PAYLOAD, MQTTCallback);
// 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);
// set up myESP for Wifi, MQTT, MDNS and Telnet callbacks
myESP.setTelnet(TelnetCommandCallback, TelnetCallback); // set up Telnet commands
myESP.setWIFI(WIFICallback); // wifi callback
myESP.setMQTT(MQTTCallback); // MQTT ip, username and password taken from the SPIFFS settings
myESP.setSettings(LoadSaveCallback, SetListCallback, true); // default is Serial off
myESP.setWeb(WebCallback); // web custom settings
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);
// at this point we have all the settings from our internall SPIFFS config file
// fire up the UART now
@@ -1889,7 +1898,8 @@ void setup() {
} else {
Serial.println("Note: Serial output will now be disabled. Please use Telnet.");
Serial.flush();
emsuart_init();
myESP.setUseSerial(false);
emsuart_init(); // start EMS bus transmissions
myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
if (!EMSESP_Status.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 <CircularBuffer.h> // https://github.com/rlogiacco/CircularBuffer
#include <MyESP.h>
#include <list> // std::list
#ifdef TESTS
#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.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;
}
@@ -1609,8 +1608,9 @@ void ems_clearDeviceList() {
/*
* 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;
// if its a duplicate don't add
bool found = false;
@@ -1620,6 +1620,7 @@ void _addDevice(uint8_t product_id, uint8_t device_id, char * version, const cha
}
}
if (!found) {
device.model_type = model_type;
device.product_id = product_id;
device.device_id = device_id;
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);
// 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
// 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;
}
myESP.fs_saveConfig(); // save config to SPIFFS
ems_getBoilerValues(); // get Boiler values that we would usually have to wait for
}
return;
@@ -1707,7 +1706,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
}
// 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 (((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;
strlcpy(EMS_Thermostat.version, version, sizeof(EMS_Thermostat.version));
myESP.fs_saveConfig(); // save config to SPIFFS
// get Thermostat values (if supported)
ems_getThermostatValues();
}
@@ -1752,7 +1749,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
version);
// 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."));
EMS_SolarModule.device_id = SolarModule_Devices[i].device_id;
@@ -1782,7 +1779,7 @@ void _process_Version(_EMS_RxTelegram * EMS_RxTelegram) {
version);
// 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."));
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);
// 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;
} else {
myDebug_P(PSTR("Unrecognized device found: %s (DeviceID:0x%02X ProductID:%d Version:%s)"), EMS_RxTelegram->src, product_id, version);
// 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
* by looking up the product_id
*/
char * ems_getThermostatDescription(char * buffer) {
char * ems_getThermostatDescription(char * buffer, bool name_only) {
uint8_t size = 128;
if (!ems_getThermostatEnabled()) {
strlcpy(buffer, "<not enabled>", size);
@@ -1998,6 +1995,9 @@ char * ems_getThermostatDescription(char * buffer) {
if (found) {
strlcpy(buffer, Thermostat_Devices[i].model_string, size);
if (name_only) {
return buffer; // only interested in the model name
}
} else {
strlcpy(buffer, "DeviceID: 0x", 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
*/
char * ems_getBoilerDescription(char * buffer) {
char * ems_getBoilerDescription(char * buffer, bool name_only) {
uint8_t size = 128;
if (!ems_getBoilerEnabled()) {
strlcpy(buffer, "<not enabled>", size);
@@ -2039,6 +2039,9 @@ char * ems_getBoilerDescription(char * buffer) {
}
if (found) {
strlcpy(buffer, Boiler_Devices[i].model_string, size);
if (name_only) {
return buffer; // only interested in the model name
}
} else {
strlcpy(buffer, "DeviceID: 0x", 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
* 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
* Still experimental
* Still experimental and not used yet!
*/
void ems_startupTelegrams() {
if ((EMS_Sys_Status.emsTxDisabled) || (!EMS_Sys_Status.emsBusConnected)) {

View File

@@ -11,6 +11,7 @@
#pragma once
#include <Arduino.h>
#include <list> // std::list
/* debug helper for logic analyzer
* create marker puls on GPIOx
@@ -250,6 +251,7 @@ typedef struct {
// for consolidating all types
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 device_id;
char version[10];
@@ -421,8 +423,8 @@ void ems_setTxDisabled(bool b);
bool ems_getTxDisabled();
uint8_t ems_getTxMode();
char * ems_getThermostatDescription(char * buffer);
char * ems_getBoilerDescription(char * buffer);
char * ems_getThermostatDescription(char * buffer, bool name_only = false);
char * ems_getBoilerDescription(char * buffer, bool name_only = false);
char * ems_getSolarModuleDescription(char * buffer);
char * ems_getHeatPumpDescription(char * buffer);
void ems_getThermostatValues();
@@ -457,3 +459,5 @@ extern _EMS_Thermostat EMS_Thermostat;
extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump;
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)
// 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!
USC1(EMSUART_UART) = 0; // reset config first
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);
// 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_ENABLE();

View File

@@ -10,18 +10,6 @@
#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
#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

View File

@@ -1,10 +1,6 @@
/**
*
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*/
#pragma once
#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_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);