This commit is contained in:
Paul
2019-09-01 12:39:40 +02:00
56 changed files with 17769 additions and 3322 deletions

47
.gitignore vendored
View File

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

View File

@@ -5,20 +5,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.8.3] 2019-08-12 ## [1.9.0] 2019-09-01
### Changed
- New web interface with more features showing Boiler, Thermostat, Solar Module and Heat Pump. See https://github.com/proddy/EMS-ESP/wiki/Running-and-Monitoring
- Merged with @susisstrolch's TxMode2 branch for improved support for sending EMS packages. This is the default tx mode.
- Upgraded MyESP library optimizations for WiFi, AP and error handling
- `reboot` command renamed to `restart` to keep consistent with web interface
- Renamed `heartbeat` to `mqtt_heartbeat` in config settings
- Renamed MQTT topic "wwactivated" to "boiler_cmd_wwactivated"
### Fixed ### Fixed
- Added write support for RC3000 - Handle Read and Write to EMS+ device logic changed, tested with RC3000
## [1.8.2] 2019-08-10 ## [1.8.1] 2019-07-27
### Fixed
- Show correct temperatures for FW120. [Issue 166](https://github.com/proddy/EMS-ESP/pull/166)
- LED off works after reboot [Issue 167](https://github.com/proddy/EMS-ESP/issues/167)
## [1.8.1] 2019-07-29
### Added ### Added

View File

@@ -2,29 +2,31 @@
EMS-ESP is a open-source system to communicate with **EMS** (Energy Management System) based boilers, thermostats and other modules from manufacturers like Bosch, Buderus, Nefit, Junkers and Sieger. EMS-ESP is a open-source system to communicate with **EMS** (Energy Management System) based boilers, thermostats and other modules from manufacturers like Bosch, Buderus, Nefit, Junkers and Sieger.
The code is writen for the Espressif **ESP8266** microcontroller and supports a telnet console for real-time monitoring and configuration and customizable MQTT support for publishing the information to a home automation system such as Home Assistant or Domoticz. The code is written for the Espressif **ESP8266** microcontroller and supports a telnet console for real-time monitoring and configuration and customizable MQTT support for publishing the information to a home automation system such as Home Assistant or Domoticz.
### Please reference the [Wiki](https://github.com/proddy/EMS-ESP/wiki) for further details and instructions on how to build and configure the firmware. #### Please reference the [Wiki](https://github.com/proddy/EMS-ESP/wiki) for further details and instructions on how to build and configure the firmware.
--- ---
**An example of the Home Assistant integration:** ## Features
#### A web interface for easy configuration and real-time monitoring of the EMS bus
| ![web menu](https://github.com/proddy/EMS-ESP/raw/master/doc/web/system_status.PNG) | ![web menu](https://github.com/proddy/EMS-ESP/raw/master/doc/web/ems_dashboard.PNG) |
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
#### MQTT support for Home Assistant and Domoticz
![ha](https://github.com/proddy/EMS-ESP/raw/master/doc/home_assistant/ha.png) ![ha](https://github.com/proddy/EMS-ESP/raw/master/doc/home_assistant/ha.png)
**Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) circuit:** #### Telnet for advanced configuration and verbose traffic logging
| ![on boiler](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/on-boiler.jpg) | ![kit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-kit-2.jpg) | ![basic circuit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-board-white.jpg) |
| - | - | - |
**Example of the EMS-ESP's telnet console:**
| ![telnet menu](https://github.com/proddy/EMS-ESP/raw/master/doc/telnet/telnet_menu.jpg) | ![telnet menu](https://github.com/proddy/EMS-ESP/raw/master/doc/telnet/telnet_stats.PNG) | | ![telnet menu](https://github.com/proddy/EMS-ESP/raw/master/doc/telnet/telnet_menu.jpg) | ![telnet menu](https://github.com/proddy/EMS-ESP/raw/master/doc/telnet/telnet_stats.PNG) |
| - | - | | --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
--- ---
## The latest list of support EMS devices ## Current list of supported EMS devices
### Thermostats: ### Thermostats:
@@ -34,7 +36,7 @@ The code is writen for the Espressif **ESP8266** microcontroller and supports a
* RC20F * RC20F
* RC30/Nefit Moduline 400 * RC30/Nefit Moduline 400
* RC35 (only a single HC) * RC35 (only a single HC)
* RC300/RC310/Nefit Moduline 3000 * RC300/RC310/RC3000
* Nefit Moduline 1010 * Nefit Moduline 1010
* Junkers FR10 * Junkers FR10
* TC100/Nefit Easy (read-only) * TC100/Nefit Easy (read-only)
@@ -72,3 +74,11 @@ The code is writen for the Espressif **ESP8266** microcontroller and supports a
* EMS-OT OpenTherm converter * EMS-OT OpenTherm converter
* Web Gateway KM200 * Web Gateway KM200
* HeatPump Module * HeatPump Module
## Compatible with EMS Gateway
Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1:
| ![on boiler](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/on-boiler.jpg) | ![kit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-kit-2.jpg) | ![basic circuit](https://github.com/proddy/EMS-ESP/raw/master/doc/ems%20gateway/ems-board-white.jpg) |
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |

View File

@@ -1,113 +0,0 @@
# Based on https://github.com/emontnemery/domoticz_mqtt_discovery
import Domoticz
import time
class MqttClient:
Address = ""
Port = ""
mqttConn = None
isConnected = False
mqttConnectedCb = None
mqttDisconnectedCb = None
mqttPublishCb = None
def __init__(self, destination, port, mqttConnectedCb, mqttDisconnectedCb, mqttPublishCb, mqttSubackCb):
Domoticz.Debug("MqttClient::__init__")
self.Address = destination
self.Port = port
self.mqttConnectedCb = mqttConnectedCb
self.mqttDisconnectedCb = mqttDisconnectedCb
self.mqttPublishCb = mqttPublishCb
self.mqttSubackCb = mqttSubackCb
self.Open()
def __str__(self):
Domoticz.Debug("MqttClient::__str__")
if (self.mqttConn != None):
return str(self.mqttConn)
else:
return "None"
def Open(self):
Domoticz.Debug("MqttClient::Open")
if (self.mqttConn != None):
self.Close()
self.isConnected = False
self.mqttConn = Domoticz.Connection(Name=self.Address, Transport="TCP/IP", Protocol="MQTT", Address=self.Address, Port=self.Port)
self.mqttConn.Connect()
def Connect(self):
Domoticz.Debug("MqttClient::Connect")
if (self.mqttConn == None):
self.Open()
else:
ID = 'Domoticz_'+str(int(time.time()))
Domoticz.Log("MQTT CONNECT ID: '" + ID + "'")
self.mqttConn.Send({'Verb': 'CONNECT', 'ID': ID})
def Ping(self):
Domoticz.Debug("MqttClient::Ping")
if (self.mqttConn == None or not self.isConnected):
self.Open()
else:
self.mqttConn.Send({'Verb': 'PING'})
def Publish(self, topic, payload, retain = 0):
Domoticz.Log("MqttClient::Publish " + topic + " (" + payload + ")")
if (self.mqttConn == None or not self.isConnected):
self.Open()
else:
self.mqttConn.Send({'Verb': 'PUBLISH', 'Topic': topic, 'Payload': bytearray(payload, 'utf-8'), 'Retain': retain})
def Subscribe(self, topics):
Domoticz.Debug("MqttClient::Subscribe")
subscriptionlist = []
for topic in topics:
subscriptionlist.append({'Topic':topic, 'QoS':0})
if (self.mqttConn == None or not self.isConnected):
self.Open()
else:
self.mqttConn.Send({'Verb': 'SUBSCRIBE', 'Topics': subscriptionlist})
def Close(self):
Domoticz.Log("MqttClient::Close")
#TODO: Disconnect from server
self.mqttConn = None
self.isConnected = False
def onConnect(self, Connection, Status, Description):
Domoticz.Debug("MqttClient::onConnect")
if (Status == 0):
Domoticz.Log("Successful connect to: "+Connection.Address+":"+Connection.Port)
self.Connect()
else:
Domoticz.Log("Failed to connect to: "+Connection.Address+":"+Connection.Port+", Description: "+Description)
def onDisconnect(self, Connection):
Domoticz.Log("MqttClient::onDisonnect Disconnected from: "+Connection.Address+":"+Connection.Port)
self.Close()
# TODO: Reconnect?
if self.mqttDisconnectedCb != None:
self.mqttDisconnectedCb()
def onMessage(self, Connection, Data):
topic = ''
if 'Topic' in Data:
topic = Data['Topic']
payloadStr = ''
if 'Payload' in Data:
payloadStr = Data['Payload'].decode('utf8','replace')
payloadStr = str(payloadStr.encode('unicode_escape'))
if Data['Verb'] == "CONNACK":
self.isConnected = True
if self.mqttConnectedCb != None:
self.mqttConnectedCb()
if Data['Verb'] == "SUBACK":
if self.mqttSubackCb != None:
self.mqttSubackCb()
if Data['Verb'] == "PUBLISH":
if self.mqttPublishCb != None:
self.mqttPublishCb(topic, Data['Payload'])

View File

@@ -1,165 +0,0 @@
"""
<plugin key="nefit" name="Nefit EMS-ESP with Proddy firmware" version="0.0.1">
<description>
Plugin to control Nefit EMS-ESP with '<a href="https://github.com/proddy/EMS-ESP"> Proddy</a>' firmware<br/>
<br/>
Automatically creates Domoticz devices for connected device.<br/>
Do not forget to "Accept new Hardware Devices" on first run<br/>
</description>
<params>
<param field="Address" label="MQTT Server address" width="300px" required="true" default="127.0.0.1"/>
<param field="Port" label="Port" width="300px" required="true" default="1883"/>
<param field="Mode6" label="Debug" width="75px">
<options>
<option label="Extra verbose" value="Verbose+"/>
<option label="Verbose" value="Verbose"/>
<option label="True" value="Debug"/>
<option label="False" value="Normal" default="true" />
</options>
</param>
</params>
</plugin>
"""
import Domoticz
import json
import time
from mqtt import MqttClient
class Thermostat:
def checkDevices(self):
if 1 not in Devices:
Domoticz.Debug("Create Temperature Device")
Domoticz.Device(Name="Woonkamer", Unit=1, Type=80, Subtype=5).Create()
if 2 not in Devices:
Domoticz.Debug("Create System Pressure Device")
Domoticz.Device(Name="System Pressure", Unit=2, Type=243, Subtype=9).Create()
if 3 not in Devices:
Domoticz.Debug("Create Thermostat Device")
Domoticz.Device(Name="Nefit", Unit=3, Type=242, Subtype=1).Create()
def onMqttMessage(self, topic, payload):
if "thermostat_currtemp" in payload:
temp=round(float(payload["thermostat_currtemp"]),1)
Domoticz.Debug("Current temp: {}".format(temp))
if Devices[1].sValue != temp:
Devices[1].Update(nValue=1, sValue=str(temp))
if "sysPress" in payload:
pressure=payload["sysPress"]
Domoticz.Debug("System Pressure: {}".format(pressure))
if Devices[2].sValue != pressure:
Devices[2].Update(nValue=1, sValue=str(pressure))
if "thermostat_seltemp" in payload:
temp=payload["thermostat_seltemp"]
Domoticz.Debug("Temp setting: {}".format(temp))
if Devices[3].sValue != temp:
Devices[3].Update(nValue=1, sValue=str(temp))
def onCommand(self, mqttClient, unit, command, level, color):
topic = "home/ems-esp/thermostat_cmd_temp"
if (command == "Set Level"):
mqttClient.Publish(topic, str(level))
class BasePlugin:
mqttClient = None
def onStart(self):
self.debugging = Parameters["Mode6"]
if self.debugging == "Verbose+":
Domoticz.Debugging(2+4+8+16+64)
if self.debugging == "Verbose":
Domoticz.Debugging(2+4+8+16+64)
if self.debugging == "Debug":
Domoticz.Debugging(2+4+8)
self.controller = Thermostat()
self.controller.checkDevices()
self.topics = list(["home/ems-esp/thermostat_data", "home/ems-esp/boiler_data", "home/ems-esp/STATE"])
self.mqttserveraddress = Parameters["Address"].replace(" ", "")
self.mqttserverport = Parameters["Port"].replace(" ", "")
self.mqttClient = MqttClient(self.mqttserveraddress, self.mqttserverport, self.onMQTTConnected, self.onMQTTDisconnected, self.onMQTTPublish, self.onMQTTSubscribed)
def checkDevices(self):
Domoticz.Log("checkDevices called")
def onStop(self):
Domoticz.Log("onStop called")
def onCommand(self, Unit, Command, Level, Color):
Domoticz.Debug("Command: " + Command + " (" + str(Level))
self.controller.onCommand(self.mqttClient, Unit, Command, Level, Color)
def onConnect(self, Connection, Status, Description):
self.mqttClient.onConnect(Connection, Status, Description)
def onDisconnect(self, Connection):
self.mqttClient.onDisconnect(Connection)
def onMessage(self, Connection, Data):
self.mqttClient.onMessage(Connection, Data)
def onHeartbeat(self):
Domoticz.Debug("Heartbeating...")
# Reconnect if connection has dropped
if self.mqttClient.mqttConn is None or (not self.mqttClient.mqttConn.Connecting() and not self.mqttClient.mqttConn.Connected() or not self.mqttClient.isConnected):
Domoticz.Debug("Reconnecting")
self.mqttClient.Open()
else:
self.mqttClient.Ping()
def onMQTTConnected(self):
Domoticz.Debug("onMQTTConnected")
self.mqttClient.Subscribe(self.topics)
def onMQTTDisconnected(self):
Domoticz.Debug("onMQTTDisconnected")
def onMQTTSubscribed(self):
Domoticz.Debug("onMQTTSubscribed")
def onMQTTPublish(self, topic, rawmessage):
Domoticz.Debug("MQTT message: " + topic + " " + str(rawmessage))
message = ""
try:
message = json.loads(rawmessage.decode('utf8'))
except ValueError:
message = rawmessage.decode('utf8')
if (topic in self.topics):
self.controller.onMqttMessage(topic, message)
global _plugin
_plugin = BasePlugin()
def onStart():
global _plugin
_plugin.onStart()
def onStop():
global _plugin
_plugin.onStop()
def onConnect(Connection, Status, Description):
global _plugin
_plugin.onConnect(Connection, Status, Description)
def onDisconnect(Connection):
global _plugin
_plugin.onDisconnect(Connection)
def onMessage(Connection, Data):
global _plugin
_plugin.onMessage(Connection, Data)
def onCommand(Unit, Command, Level, Color):
global _plugin
_plugin.onCommand(Unit, Command, Level, Color)
def onHeartbeat():
global _plugin
_plugin.onHeartbeat()

View File

@@ -1,10 +0,0 @@
to install the plugin:
- copy the directory 'nefit' to the domoticz/plugins directory
- make sure that 'Accept new Hardware Devices' is enabeled in settings/sysem
- create new hardware with type 'Nefit EMS-ESP with Proddy firmware'
- set MQTT server and port
The plugin crrently creates 3 devices:
- a room temperature meter
- a system pressure meter
- a thermostat setpoint control

7862
doc/HT3-Bus_Telegramme.html Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

BIN
doc/web/ems_dashboard.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

BIN
doc/web/system_status.PNG Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

File diff suppressed because it is too large Load Diff

View File

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

3
scripts/buildweb.py Normal file
View File

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

View File

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

2826
src/MyESP.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

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

46
src/Ntp.cpp Normal file
View File

@@ -0,0 +1,46 @@
/*
* Ntp.cpp
*/
#include "Ntp.h"
char * NtpClient::TimeServerName;
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, time_t syncMins) {
TimeServerName = strdup(server);
syncInterval = syncMins * 60; // convert to seconds
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;
}

33
src/Ntp.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* 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, time_t syncMins);
ICACHE_FLASH_ATTR virtual ~NtpClient();
static char * TimeServerName;
static IPAddress timeServer;
static time_t syncInterval;
static AsyncUDP udpListener;
static byte NTPpacket[NTP_PACKET_SIZE];
static ICACHE_FLASH_ATTR time_t getNtpTime();
};
#endif

View File

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

143
src/TimeLib.cpp Normal file
View File

@@ -0,0 +1,143 @@
#include "TimeLib.h"
static tmElements_t tm; // a cache of time elements
static time_t cacheTime; // the time the cache was updated
static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds
static uint32_t sysTime = 0;
static uint32_t prevMillis = 0;
static uint32_t nextSyncTime = 0;
static timeStatus_t Status = timeNotSet;
getExternalTime getTimePtr; // pointer to external sync function
#define LEAP_YEAR(Y) (((1970 + (Y)) > 0) && !((1970 + (Y)) % 4) && (((1970 + (Y)) % 100) || !((1970 + (Y)) % 400)))
static const uint8_t monthDays[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; // API starts months from 1, this array starts from 0
time_t now() {
// calculate number of seconds passed since last call to now()
while (millis() - prevMillis >= 1000) {
// millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference
sysTime++;
prevMillis += 1000;
}
if (nextSyncTime <= sysTime) {
if (getTimePtr != 0) {
time_t t = getTimePtr();
if (t != 0) {
setTime(t);
} else {
nextSyncTime = sysTime + syncInterval;
Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync;
}
}
}
return (time_t)sysTime;
}
void setSyncProvider(getExternalTime getTimeFunction) {
getTimePtr = getTimeFunction;
nextSyncTime = sysTime;
now(); // this will sync the clock
}
void setSyncInterval(time_t interval) { // set the number of seconds between re-sync
syncInterval = (uint32_t)interval;
nextSyncTime = sysTime + syncInterval;
}
void breakTime(time_t timeInput, tmElements_t & tm) {
// break the given time_t into time components
// this is a more compact version of the C library localtime function
// note that year is offset from 1970 !!!
uint8_t year;
uint8_t month, monthLength;
uint32_t time;
unsigned long days;
time = (uint32_t)timeInput;
tm.Second = time % 60;
time /= 60; // now it is minutes
tm.Minute = time % 60;
time /= 60; // now it is hours
tm.Hour = time % 24;
time /= 24; // now it is days
tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1
year = 0;
days = 0;
while ((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) {
year++;
}
tm.Year = year; // year is offset from 1970
days -= LEAP_YEAR(year) ? 366 : 365;
time -= days; // now it is days in this year, starting at 0
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
}
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 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

275
src/custom.htm Normal file
View File

@@ -0,0 +1,275 @@
<div id="customcontent">
<br>
<legend>Custom Settings</legend>
<h6 class="text-muted">Please refer to the Help for configuration options</h6>
<br>
<div class="row form-group">
<label class="col-xs-3">LED<i style="margin-left: 10px;"
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
data-trigger="hover" data-placement="right"
data-content="Please choose if you want to enable an LED to show status"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="led">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="led" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">LED Pin<i style="margin-left: 10px;"
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
data-trigger="hover" data-placement="right"
data-content="Select with GPIO pin the LED is on"></i></label>
<span class="col-xs-9 col-md-5">
<select class="form-control input-sm" id="led_gpio">
<option value="0">GPIO-0</option>
<option selected="selected" value="2">GPIO-2 (onboard LED)</option>
<option value="4">GPIO-4</option>
<option value="5">GPIO-5</option>
<option value="12">GPIO-12</option>
<option value="14">GPIO-14</option>
<option value="16">GPIO-16</option>
</select>
</span>
</div>
<div class="row form-group">
<label class="col-xs-3">Dallas Parasite<i style="margin-left: 10px;"
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
data-trigger="hover" data-placement="right"
data-content="Enable if Dallas sensors are powered via parasite"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="dallas_parasite">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="dallas_parasite" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">Dallas Pin<i style="margin-left: 10px;"
class="glyphicon glyphicon-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
data-trigger="hover" data-placement="right"
data-content="Select GPIO pin to where the Dallas sensor is connected"></i></label>
<span class="col-xs-9 col-md-5">
<select class="form-control input-sm" id="dallas_gpio">
<option value="0">GPIO-0</option>
<option value="4">GPIO-4</option>
<option value="5">GPIO-5</option>
<option value="12">GPIO-12</option>
<option selected="selected" value="14">GPIO-14</option>
<option value="16">GPIO-16</option>
</select>
</span>
</div>
<div class="row form-group">
<label class="col-xs-3">Listen Mode<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Listen mode disables Tx. Used when debugging."></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="listen_mode">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="listen_mode" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">Shower Timer<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Monitors and sends MQTT message on shower duration"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="shower_timer">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="shower_timer" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">Shower Alert<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Tells boiler to blast 3 shots of cold water after a specific duration has exceeded (7 mins)"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="shower_alert">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="shower_alert" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">Publish Time<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="How often to send MQTT topics with stats (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 the main heating circuit to use. Default is HC1."></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</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-exclamation-sign text-danger" aria-hidden="true" data-toggle="popover"
data-trigger="hover" data-placement="right"
data-content="Tx mode settings for sending data to the EMS bus"></i></label>
<span class="col-xs-9 col-md-5">
<select class="form-control input-sm" id="tx_mode">
<option selected="selected" value="1">1 (EMS generic)</option>
<option value="2">2 (EMS+/EMS2.0)</option>
<option value="3">3 (Junkers Heatronics)</option>
</select>
</span>
</div>
<br>
<div class="row form-group">
<div class="col-xs-9 col-md-8">
<button onclick="savecustom()" class="btn btn-primary btn-sm pull-right">Save</button>
</div>
</div>
<h6 class="text-muted">Note: any setting marked with a <span
class="glyphicon glyphicon-exclamation-sign text-danger"></span> requires a system restart after saving.
</h6>
</div>
<div id="custom_statuscontent">
<br>
<div class="row text-left">
<div class="col-md-8 col-md-offset-2">
<h2>EMS Dashboard</h2>
<h6>Real-time values from the EMS-ESP device are shown here</h6>
</div>
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default table-responsive">
<table class="table table-hover table-striped table-condensed">
<caption>EMS Bus Status</caption>
<tr>
<td colspan="2">
<b>
<div id="msg" role="alert"></div>
</b>
</td>
</tr>
<tr>
<th id="devicesshow">Discovered Devices:</th>
<td>
<ul class="list-group">
<div id="devices"></div>
</ul>
</td>
</tr>
</table>
</div>
<div class="panel panel-success table-responsive" id="boiler_show">
<div class="panel-heading"><b>Boiler</b>:&nbsp;<span id="bm"></span></div>
<table class="table table-hover table-bordered table-condensed">
<tr>
<th>Hot Tap Water:</th>
<td id="b1"></td>
<th>Central Heating:</th>
<td id="b2"></td>
</tr>
<tr>
<th>Selected Flow Temperature:</th>
<td id="b3"></td>
<th>Current Flow Temperature:</th>
<td id="b4"></td>
</tr>
<tr>
<th>Boiler Temperature:</th>
<td id="b5"></td>
<th>Return Temperature:</th>
<td id="b6"></td>
</tr>
</table>
</div>
<div class="panel panel-info table-responsive" id="thermostat_show">
<div class="panel-heading"><b>Thermostat</b>:&nbsp;<span id="tm"></span></div>
<table class="table table-hover table-bordered table-condensed">
<tr>
<th>Setpoint Temperature:</th>
<td id="ts"></td>
<th>Current Temperature:</th>
<td id="tc"></td>
</tr>
<tr>
<th>Mode:</th>
<td colspan="3" id="tmode"></td>
</tr>
</table>
</div>
<div class="panel panel-warning table-responsive" id="sm_show">
<div class="panel-heading"><b>Solar Module</b>:&nbsp;<span id="sm"></span></div>
<table class="table table-hover table-bordered table-condensed">
<tr>
<th>Colector Temperature:</th>
<td id="sm1"></td>
<th>Bottom Temperature:</th>
<td id="sm2"></td>
</tr>
<tr>
<th>Pump Modulation:</th>
<td id="sm3"></td>
<th>Pump Active:</th>
<td id="sm4"></td>
</tr>
<tr>
<th>Energy Last Hour:</th>
<td id="sm5"></td>
<th>Energy Today:</th>
<td id="sm6"></td>
<th>Energy Total:</th>
<td id="sm7"></td>
</tr>
</table>
</div>
<div class="panel panel-success table-responsive" id="hp_show">
<div class="panel-heading"><b>Heat Pump</b>:&nbsp;<span id="hm"></span></div>
<table class="table table-hover table-bordered table-condensed">
<tr>
<th>Pump Modulation:</th>
<td id="hp1"></td>
<th>Pump Speed:</th>
<td id="hp2"></td>
</tr>
</table>
</div>
</div>
</div>
<div class="row form-group" style="text-align: center;">
<button onclick="refreshEMS()" class="btn btn-info">Refresh</button>
</div>
</div>

169
src/custom.js Normal file
View File

@@ -0,0 +1,169 @@
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
}
}
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 = "alert alert-success";
} else {
document.getElementById("msg").className = "alert alert-danger";
document.getElementById("devicesshow").style.display = "none";
document.getElementById("thermostat_show").style.display = "none";
document.getElementById("boiler_show").style.display = "none";
return;
}
var list = document.getElementById("devices");
var obj = ajaxobj.emsbus.devices;
document.getElementById("devicesshow").style.display = "block";
for (var i = 0; i < obj.length; i++) {
var l = document.createElement("li");
var type = obj[i].type;
if (type == 1) {
var color = "list-group-item-success";
} else if (type == 2) {
var color = "list-group-item-info";
} else if (type == 3) {
var color = "list-group-item-warning";
} else if (type == 4) {
var color = "list-group-item-success";
} else {
var color = "";
}
l.innerHTML = obj[i].model + " (Version:" + obj[i].version + " ProductID:" + obj[i].productid + " DeviceID:0x" + obj[i].deviceid + ")";
l.className = "list-group-item " + color;
list.appendChild(l);
}
if (ajaxobj.boiler.ok) {
document.getElementById("boiler_show").style.display = "block";
document.getElementById("bm").innerHTML = ajaxobj.boiler.bm;
document.getElementById("b1").innerHTML = ajaxobj.boiler.b1;
document.getElementById("b2").innerHTML = ajaxobj.boiler.b2;
document.getElementById("b3").innerHTML = ajaxobj.boiler.b3 + " &#8451;";
document.getElementById("b4").innerHTML = ajaxobj.boiler.b4 + " &#8451;";
document.getElementById("b5").innerHTML = ajaxobj.boiler.b5 + " &#8451;";
document.getElementById("b6").innerHTML = ajaxobj.boiler.b6 + " &#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";
}
if (ajaxobj.sm.ok) {
document.getElementById("sm_show").style.display = "block";
document.getElementById("sm").innerHTML = ajaxobj.sm.sm;
document.getElementById("sm1").innerHTML = ajaxobj.sm.sm1 + " &#8451;";
document.getElementById("sm2").innerHTML = ajaxobj.sm.sm2 + " &#8451;";
document.getElementById("sm3").innerHTML = ajaxobj.sm.sm3 + " &#37;";
document.getElementById("sm4").innerHTML = ajaxobj.sm.sm4;
document.getElementById("sm5").innerHTML = ajaxobj.sm.sm5 + " Wh";
document.getElementById("sm6").innerHTML = ajaxobj.sm.sm6 + " Wh";
document.getElementById("sm7").innerHTML = ajaxobj.sm.sm7 + " KWh";
} else {
document.getElementById("sm_show").style.display = "none";
}
if (ajaxobj.hp.ok) {
document.getElementById("hp_show").style.display = "block";
document.getElementById("hm").innerHTML = ajaxobj.hp.hm;
document.getElementById("hp1").innerHTML = ajaxobj.hp.hp1 + " &#37;";
document.getElementById("hp2").innerHTML = ajaxobj.hp.hp2 + " &#37;";
} else {
document.getElementById("hp_show").style.display = "none";
}
}

View File

@@ -4,11 +4,11 @@
* Paul Derbyshire - https://github.com/proddy/EMS-ESP * Paul Derbyshire - https://github.com/proddy/EMS-ESP
* *
* See ChangeLog.md for history * See ChangeLog.md for history
* See README.md for Acknowledgments * See wiki at https://github.com/proddy/EMS-ESP/Wiki for Acknowledgments
*/ */
// local libraries // local libraries
#include "ds18.h" #include "MyESP.h"
#include "ems.h" #include "ems.h"
#include "ems_devices.h" #include "ems_devices.h"
#include "emsuart.h" #include "emsuart.h"
@@ -16,11 +16,9 @@
#include "version.h" #include "version.h"
// Dallas external temp sensors // Dallas external temp sensors
#include "ds18.h"
DS18 ds18; DS18 ds18;
// shared libraries
#include <MyESP.h>
// public libraries // public libraries
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson #include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
#include <CRC32.h> // https://github.com/bakercp/CRC32 #include <CRC32.h> // https://github.com/bakercp/CRC32
@@ -28,11 +26,18 @@ DS18 ds18;
// standard arduino libs // standard arduino libs
#include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker #include <Ticker.h> // https://github.com/esp8266/Arduino/tree/master/libraries/Ticker
// default APP params
#define APP_NAME "EMS-ESP"
#define APP_HOSTNAME "ems-esp"
#define APP_URL "https://github.com/proddy/EMS-ESP"
#define APP_UPDATEURL "https://api.github.com/repos/proddy/EMS-ESP/releases/latest"
// macros for easy debugging
#define myDebug(...) myESP.myDebug(__VA_ARGS__) #define myDebug(...) myESP.myDebug(__VA_ARGS__)
#define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__) #define myDebug_P(...) myESP.myDebug_P(__VA_ARGS__)
// set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1. // set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1.
#define EMSESP_DELAY 1 // initially set to 0 for no delay #define EMSESP_DELAY 0 // initially set to 0 for no delay. Change to 1 if getting WDT resets from wifi
#define DEFAULT_HEATINGCIRCUIT 1 // default to HC1 for thermostats that support multiple heating circuits like the RC35 #define DEFAULT_HEATINGCIRCUIT 1 // default to HC1 for thermostats that support multiple heating circuits like the RC35
@@ -69,6 +74,21 @@ Ticker showerColdShotStopTimer;
#define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water #define SHOWER_COLDSHOT_DURATION 10 // in seconds. 10 seconds for cold water before turning back hot water
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water #define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
#ifdef LOGICANALYZER
#define EMSESP_DALLAS_GPIO D1
#define EMSESP_DALLAS_PARASITE false
#else
// set this if using an external temperature sensor like a DS18B20
// D5 is the default on a bbqkees board
#define EMSESP_DALLAS_GPIO D5
#define EMSESP_DALLAS_PARASITE false
#endif
// Set LED pin used for showing the EMS bus connection status. Solid means EMS bus working, flashing is an error
// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED (e.g. D1 on a bbqkees' board)
// can be enabled and disabled via the 'set led' command and pin set by 'set led_gpio'
#define EMSESP_LED_GPIO LED_BUILTIN
typedef struct { typedef struct {
uint32_t timestamp; // for internal timings, via millis() uint32_t timestamp; // for internal timings, via millis()
uint8_t dallas_sensors; // count of dallas sensors uint8_t dallas_sensors; // count of dallas sensors
@@ -83,6 +103,7 @@ typedef struct {
uint8_t dallas_gpio; // pin for attaching external dallas temperature sensors uint8_t dallas_gpio; // pin for attaching external dallas temperature sensors
bool dallas_parasite; // on/off is using parasite bool dallas_parasite; // on/off is using parasite
uint8_t heating_circuit; // number of heating circuit, 1 or 2 uint8_t heating_circuit; // number of heating circuit, 1 or 2
uint8_t tx_mode; // TX mode 1,2 or 3
} _EMSESP_Status; } _EMSESP_Status;
typedef struct { typedef struct {
@@ -98,15 +119,13 @@ static const command_t project_cmds[] PROGMEM = {
{true, "led <on | off>", "toggle status LED on/off"}, {true, "led <on | off>", "toggle status LED on/off"},
{true, "led_gpio <gpio>", "set the LED pin. Default is the onboard LED 2. For external D1 use 5"}, {true, "led_gpio <gpio>", "set the LED pin. Default is the onboard LED 2. For external D1 use 5"},
{true, "dallas_gpio <gpio>", "set the external Dallas temperature sensors pin. Default is 14 for D5"}, {true, "dallas_gpio <gpio>", "set the external Dallas temperature sensors pin. Default is 14 for D5"},
{true, "dallas_parasite <on | off>", "set to on if powering Dallas sesnsors via parasite power"}, {true, "dallas_parasite <on | off>", "set to on if powering Dallas sensors via parasite power"},
{true, "thermostat_type <device ID>", "set the thermostat type ID (e.g. 10 for 0x10)"},
{true, "boiler_type <device ID>", "set the boiler type ID (e.g. 8 for 0x08)"},
{true, "listen_mode <on | off>", "when set to on all automatic Tx are disabled"}, {true, "listen_mode <on | off>", "when set to on all automatic Tx are disabled"},
{true, "shower_timer <on | off>", "send MQTT notification on all shower durations"}, {true, "shower_timer <on | off>", "send MQTT notification on all shower durations"},
{true, "shower_alert <on | off>", "stop hot water to send 3 cold burst warnings after max shower time is exceeded"}, {true, "shower_alert <on | off>", "stop hot water to send 3 cold burst warnings after max shower time is exceeded"},
{true, "publish_time <seconds>", "set frequency for publishing data to MQTT (0=off)"}, {true, "publish_time <seconds>", "set frequency for publishing data to MQTT (0=off)"},
{true, "heating_circuit <1 | 2>", "set the main thermostat HC to work with (if using multiple heating circuits)"}, {true, "heating_circuit <1 | 2>", "set the main thermostat HC to work with (if using multiple heating circuits)"},
{true, "tx_mode <n>", "changes Tx logic. 0=ems 1.0, 1=ems+, 2=generic (experimental!), 3=HT3"}, {true, "tx_mode <n>", "changes Tx logic. 1=ems generic, 2=ems+, 3=Junkers HT3"},
{false, "info", "show current captured on the devices"}, {false, "info", "show current captured on the devices"},
{false, "log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"}, {false, "log <n | b | t | r | v>", "set logging mode to none, basic, thermostat only, raw or verbose"},
@@ -419,6 +438,8 @@ void showInfo() {
myDebug_P(PSTR(" System logging set to Thermostat only")); myDebug_P(PSTR(" System logging set to Thermostat only"));
} else if (sysLog == EMS_SYS_LOGGING_SOLARMODULE) { } else if (sysLog == EMS_SYS_LOGGING_SOLARMODULE) {
myDebug_P(PSTR(" System logging set to Solar Module only")); myDebug_P(PSTR(" System logging set to Solar Module only"));
} else if (sysLog == EMS_SYS_LOGGING_JABBER) {
myDebug_P(PSTR(" System logging set to Jabber"));
} else { } else {
myDebug_P(PSTR(" System logging set to None")); myDebug_P(PSTR(" System logging set to None"));
} }
@@ -509,7 +530,7 @@ void showInfo() {
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow); _renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
_renderShortValue("Flame current", "uA", EMS_Boiler.flameCurr); _renderShortValue("Flame current", "uA", EMS_Boiler.flameCurr);
_renderIntValue("System pressure", "bar", EMS_Boiler.sysPress, 10); _renderIntValue("System pressure", "bar", EMS_Boiler.sysPress, 10);
if (EMS_Boiler.serviceCode == EMS_VALUE_SHORT_NOTSET) { if (EMS_Boiler.serviceCode == EMS_VALUE_USHORT_NOTSET) {
myDebug_P(PSTR(" System service code: %s"), EMS_Boiler.serviceCodeChar); myDebug_P(PSTR(" System service code: %s"), EMS_Boiler.serviceCodeChar);
} else { } else {
myDebug_P(PSTR(" System service code: %s (%d)"), EMS_Boiler.serviceCodeChar, EMS_Boiler.serviceCode); myDebug_P(PSTR(" System service code: %s (%d)"), EMS_Boiler.serviceCodeChar, EMS_Boiler.serviceCode);
@@ -550,7 +571,7 @@ void showInfo() {
if (ems_getSolarModuleEnabled()) { if (ems_getSolarModuleEnabled()) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Solar Module: %s"), ems_getSolarModuleDescription(buffer_type)); myDebug_P(PSTR(" Solar module: %s"), ems_getSolarModuleDescription(buffer_type));
_renderShortValue("Collector temperature", "C", EMS_SolarModule.collectorTemp); _renderShortValue("Collector temperature", "C", EMS_SolarModule.collectorTemp);
_renderShortValue("Bottom temperature", "C", EMS_SolarModule.bottomTemp); _renderShortValue("Bottom temperature", "C", EMS_SolarModule.bottomTemp);
_renderIntValue("Pump modulation", "%", EMS_SolarModule.pumpModulation); _renderIntValue("Pump modulation", "%", EMS_SolarModule.pumpModulation);
@@ -561,16 +582,16 @@ void showInfo() {
(EMS_SolarModule.pumpWorkMin % 1440) / 60, (EMS_SolarModule.pumpWorkMin % 1440) / 60,
EMS_SolarModule.pumpWorkMin % 60); EMS_SolarModule.pumpWorkMin % 60);
} }
_renderUShortValue("Energy Last Hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10 _renderUShortValue("Energy last hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10
_renderUShortValue("Energy Today", "Wh", EMS_SolarModule.EnergyToday, 0); _renderUShortValue("Energy today", "Wh", EMS_SolarModule.EnergyToday, 0);
_renderUShortValue("Energy Total", "kWH", EMS_SolarModule.EnergyTotal, 1); // *10 _renderUShortValue("Energy total", "kWh", EMS_SolarModule.EnergyTotal, 1); // *10
} }
// For HeatPumps // For HeatPumps
if (ems_getHeatPumpEnabled()) { if (ems_getHeatPumpEnabled()) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sHeat Pump stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sHeat Pump stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Solar Module: %s"), ems_getHeatPumpDescription(buffer_type)); myDebug_P(PSTR(" Heat Pump module: %s"), ems_getHeatPumpDescription(buffer_type));
_renderIntValue("Pump modulation", "%", EMS_HeatPump.HPModulation); _renderIntValue("Pump modulation", "%", EMS_HeatPump.HPModulation);
_renderIntValue("Pump speed", "%", EMS_HeatPump.HPSpeed); _renderIntValue("Pump speed", "%", EMS_HeatPump.HPSpeed);
} }
@@ -579,7 +600,7 @@ void showInfo() {
if (ems_getThermostatEnabled()) { if (ems_getThermostatEnabled()) {
myDebug_P(PSTR("")); // newline myDebug_P(PSTR("")); // newline
myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF); myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type)); myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type, false));
// Render Current & Setpoint Room Temperature // Render Current & Setpoint Room Temperature
if (ems_getThermostatModel() == EMS_MODEL_EASY) { if (ems_getThermostatModel() == EMS_MODEL_EASY) {
@@ -684,23 +705,22 @@ void publishSensorValues() {
} }
} }
// send values via MQTT // send values via MQTT
// a json object is created for the boiler and one for the thermostat // a json object is created for the boiler and one for the thermostat
// CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic // CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic
// a check is done against the previous values and if there are changes only then they are published. Unless force=true // a check is done against the previous values and if there are changes only then they are published. Unless force=true
void publishValues(bool force) { void publishValues(bool force) {
// don't send if MQTT is connected // don't send if MQTT is not connected
if (!myESP.isMQTTConnected()) { if (!myESP.isMQTTConnected()) {
return; return;
} }
char s[20] = {0}; // for formatting strings char s[20] = {0}; // for formatting strings
StaticJsonDocument<MQTT_MAX_SIZE> doc; StaticJsonDocument<MQTT_MAX_PAYLOAD_SIZE> doc;
char data[MQTT_MAX_SIZE] = {0}; char data[MQTT_MAX_PAYLOAD_SIZE] = {0};
CRC32 crc; CRC32 crc;
uint32_t fchecksum; uint32_t fchecksum;
uint8_t jsonSize;
static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off static uint8_t last_boilerActive = 0xFF; // for remembering last setting of the tap water or heating on/off
static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values static uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values
@@ -772,22 +792,28 @@ void publishValues(bool force) {
if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET) if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET)
rootBoiler["heatWorkMin"] = (double)EMS_Boiler.heatWorkMin; rootBoiler["heatWorkMin"] = (double)EMS_Boiler.heatWorkMin;
rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar; if (EMS_Boiler.serviceCode != EMS_VALUE_USHORT_NOTSET) {
rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode; rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar;
rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode;
}
serializeJson(doc, data, sizeof(data)); serializeJson(doc, data, sizeof(data));
// calculate hash and send values if something has changed, to save unnecessary wifi traffic // check for empty json
for (size_t i = 0; i < measureJson(doc) - 1; i++) { jsonSize = measureJson(doc);
crc.update(data[i]); if (jsonSize > 2) {
} // calculate hash and send values if something has changed, to save unnecessary wifi traffic
fchecksum = crc.finalize(); for (uint8_t i = 0; i < (jsonSize - 1); i++) {
if ((previousBoilerPublishCRC != fchecksum) || force) { crc.update(data[i]);
previousBoilerPublishCRC = fchecksum; }
myDebugLog("Publishing boiler data via MQTT"); fchecksum = crc.finalize();
if ((previousBoilerPublishCRC != fchecksum) || force) {
previousBoilerPublishCRC = fchecksum;
myDebugLog("Publishing boiler data via MQTT");
// send values via MQTT // send values via MQTT
myESP.mqttPublish(TOPIC_BOILER_DATA, data); myESP.mqttPublish(TOPIC_BOILER_DATA, data);
}
} }
// see if the heating or hot tap water has changed, if so send // see if the heating or hot tap water has changed, if so send
@@ -859,18 +885,22 @@ void publishValues(bool force) {
data[0] = '\0'; // reset data for next package data[0] = '\0'; // reset data for next package
serializeJson(doc, data, sizeof(data)); serializeJson(doc, data, sizeof(data));
// calculate new CRC // check for empty json
crc.reset(); jsonSize = measureJson(doc);
for (size_t i = 0; i < measureJson(doc) - 1; i++) { if (jsonSize > 2) {
crc.update(data[i]); // calculate new CRC
} crc.reset();
fchecksum = crc.finalize(); for (uint8_t i = 0; i < (jsonSize - 1); i++) {
if ((previousThermostatPublishCRC != fchecksum) || force) { crc.update(data[i]);
previousThermostatPublishCRC = fchecksum; }
myDebugLog("Publishing thermostat data via MQTT"); fchecksum = crc.finalize();
if ((previousThermostatPublishCRC != fchecksum) || force) {
previousThermostatPublishCRC = fchecksum;
myDebugLog("Publishing thermostat data via MQTT");
// send values via MQTT // send values via MQTT
myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data); myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data);
}
} }
} }
@@ -909,18 +939,22 @@ void publishValues(bool force) {
data[0] = '\0'; // reset data for next package data[0] = '\0'; // reset data for next package
serializeJson(doc, data, sizeof(data)); serializeJson(doc, data, sizeof(data));
// calculate new CRC // check for empty json
crc.reset(); jsonSize = measureJson(doc);
for (size_t i = 0; i < measureJson(doc) - 1; i++) { if (jsonSize > 2) {
crc.update(data[i]); // calculate new CRC
} crc.reset();
fchecksum = crc.finalize(); for (uint8_t i = 0; i < (jsonSize - 1); i++) {
if ((previousSMPublishCRC != fchecksum) || force) { crc.update(data[i]);
previousSMPublishCRC = fchecksum; }
myDebugLog("Publishing SM data via MQTT"); fchecksum = crc.finalize();
if ((previousSMPublishCRC != fchecksum) || force) {
previousSMPublishCRC = fchecksum;
myDebugLog("Publishing SM data via MQTT");
// send values via MQTT // send values via MQTT
myESP.mqttPublish(TOPIC_SM_DATA, data); myESP.mqttPublish(TOPIC_SM_DATA, data);
}
} }
} }
@@ -1045,8 +1079,6 @@ void do_regularUpdates() {
ems_getThermostatValues(); ems_getThermostatValues();
ems_getBoilerValues(); ems_getBoilerValues();
ems_getSolarModuleValues(); ems_getSolarModuleValues();
} else {
myDebugLog("System is either not connect to the EMS bus or listen_mode is enabled");
} }
} }
@@ -1133,44 +1165,43 @@ void runUnitTest(uint8_t test_num) {
} }
// callback for loading/saving settings to the file system (SPIFFS) // callback for loading/saving settings to the file system (SPIFFS)
bool FSCallback(MYESP_FSACTION action, const JsonObject json) { bool LoadSaveCallback(MYESP_FSACTION action, JsonObject json) {
if (action == MYESP_FSACTION_LOAD) { if (action == MYESP_FSACTION_LOAD) {
EMSESP_Status.led = json["led"]; const JsonObject & settings = json["settings"];
EMSESP_Status.led_gpio = json["led_gpio"] | EMSESP_LED_GPIO;
EMSESP_Status.dallas_gpio = json["dallas_gpio"] | EMSESP_DALLAS_GPIO;
EMSESP_Status.dallas_parasite = json["dallas_parasite"] | EMSESP_DALLAS_PARASITE;
EMS_Thermostat.device_id = json["thermostat_type"] | EMSESP_THERMOSTAT_TYPE; EMSESP_Status.led = settings["led"];
EMS_Boiler.device_id = json["boiler_type"] | EMSESP_BOILER_TYPE; EMSESP_Status.led_gpio = settings["led_gpio"] | EMSESP_LED_GPIO;
EMSESP_Status.dallas_gpio = settings["dallas_gpio"] | EMSESP_DALLAS_GPIO;
EMSESP_Status.dallas_parasite = settings["dallas_parasite"] | EMSESP_DALLAS_PARASITE;
EMSESP_Status.shower_timer = settings["shower_timer"];
EMSESP_Status.shower_alert = settings["shower_alert"];
EMSESP_Status.publish_time = settings["publish_time"] | DEFAULT_PUBLISHTIME;
EMSESP_Status.shower_timer = json["shower_timer"]; EMSESP_Status.listen_mode = settings["listen_mode"];
EMSESP_Status.shower_alert = json["shower_alert"];
EMSESP_Status.publish_time = json["publish_time"] | DEFAULT_PUBLISHTIME;
ems_setTxMode(json["tx_mode"]);
EMSESP_Status.listen_mode = json["listen_mode"];
ems_setTxDisabled(EMSESP_Status.listen_mode); ems_setTxDisabled(EMSESP_Status.listen_mode);
EMSESP_Status.heating_circuit = json["heating_circuit"] | DEFAULT_HEATINGCIRCUIT; EMSESP_Status.heating_circuit = settings["heating_circuit"] | DEFAULT_HEATINGCIRCUIT;
ems_setThermostatHC(EMSESP_Status.heating_circuit); ems_setThermostatHC(EMSESP_Status.heating_circuit);
return true; // return false if some settings are missing and we need to rebuild the file EMSESP_Status.tx_mode = settings["tx_mode"] | 1; // default to 1 (generic)
ems_setTxMode(EMSESP_Status.tx_mode);
return true;
} }
if (action == MYESP_FSACTION_SAVE) { if (action == MYESP_FSACTION_SAVE) {
json["thermostat_type"] = EMS_Thermostat.device_id; JsonObject settings = json.createNestedObject("settings");
json["boiler_type"] = EMS_Boiler.device_id;
json["led"] = EMSESP_Status.led; settings["led"] = EMSESP_Status.led;
json["led_gpio"] = EMSESP_Status.led_gpio; settings["led_gpio"] = EMSESP_Status.led_gpio;
json["dallas_gpio"] = EMSESP_Status.dallas_gpio; settings["dallas_gpio"] = EMSESP_Status.dallas_gpio;
json["dallas_parasite"] = EMSESP_Status.dallas_parasite; settings["dallas_parasite"] = EMSESP_Status.dallas_parasite;
json["listen_mode"] = EMSESP_Status.listen_mode; settings["listen_mode"] = EMSESP_Status.listen_mode;
json["shower_timer"] = EMSESP_Status.shower_timer; settings["shower_timer"] = EMSESP_Status.shower_timer;
json["shower_alert"] = EMSESP_Status.shower_alert; settings["shower_alert"] = EMSESP_Status.shower_alert;
json["publish_time"] = EMSESP_Status.publish_time; settings["publish_time"] = EMSESP_Status.publish_time;
json["heating_circuit"] = EMSESP_Status.heating_circuit; settings["heating_circuit"] = EMSESP_Status.heating_circuit;
json["tx_mode"] = ems_getTxMode(); settings["tx_mode"] = EMSESP_Status.tx_mode;
return true; return true;
} }
@@ -1181,7 +1212,7 @@ bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
// callback for custom settings when showing Stored Settings with the 'set' command // callback for custom settings when showing Stored Settings with the 'set' command
// wc is number of arguments after the 'set' command // wc is number of arguments after the 'set' command
// returns true if the setting was recognized and changed and should be saved back to SPIFFs // returns true if the setting was recognized and changed and should be saved back to SPIFFs
bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) { bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) {
bool ok = false; bool ok = false;
if (action == MYESP_FSACTION_SET) { if (action == MYESP_FSACTION_SET) {
@@ -1245,18 +1276,6 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
} }
} }
// thermostat_type
if (strcmp(setting, "thermostat_type") == 0) {
EMS_Thermostat.device_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE);
ok = true;
}
// boiler_type
if (strcmp(setting, "boiler_type") == 0) {
EMS_Boiler.device_id = ((wc == 2) ? (uint8_t)strtol(value, 0, 16) : EMS_ID_NONE);
ok = true;
}
// shower timer // shower timer
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) { if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
if (strcmp(value, "on") == 0) { if (strcmp(value, "on") == 0) {
@@ -1301,10 +1320,16 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
} }
} }
// tx delay/ tx mode // tx_mode
if (((strcmp(setting, "tx_mode") == 0) || (strcmp(setting, "tx_delay") == 0)) && (wc == 2)) { if ((strcmp(setting, "tx_mode") == 0) && (wc == 2)) {
ems_setTxMode(atoi(value)); uint8_t mode = atoi(value);
ok = true; if ((mode >= 1) && (mode <= 3)) {
EMSESP_Status.tx_mode = mode;
ems_setTxMode(mode);
ok = true;
} else {
myDebug_P(PSTR("Error. Usage: set tx_mode <1 | 2 | 3>"));
}
} }
} }
@@ -1313,26 +1338,12 @@ bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, c
myDebug_P(PSTR(" led_gpio=%d"), EMSESP_Status.led_gpio); myDebug_P(PSTR(" led_gpio=%d"), EMSESP_Status.led_gpio);
myDebug_P(PSTR(" dallas_gpio=%d"), EMSESP_Status.dallas_gpio); myDebug_P(PSTR(" dallas_gpio=%d"), EMSESP_Status.dallas_gpio);
myDebug_P(PSTR(" dallas_parasite=%s"), EMSESP_Status.dallas_parasite ? "on" : "off"); myDebug_P(PSTR(" dallas_parasite=%s"), EMSESP_Status.dallas_parasite ? "on" : "off");
if (EMS_Thermostat.device_id == EMS_ID_NONE) {
myDebug_P(PSTR(" thermostat_type=<not set>"));
} else {
myDebug_P(PSTR(" thermostat_type=%02X"), EMS_Thermostat.device_id);
}
myDebug_P(PSTR(" heating_circuit=%d"), EMSESP_Status.heating_circuit); myDebug_P(PSTR(" heating_circuit=%d"), EMSESP_Status.heating_circuit);
myDebug_P(PSTR(" tx_mode=%d"), EMSESP_Status.tx_mode);
if (EMS_Boiler.device_id == EMS_ID_NONE) {
myDebug_P(PSTR(" boiler_type=<not set>"));
} else {
myDebug_P(PSTR(" boiler_type=%02X"), EMS_Boiler.device_id);
}
myDebug_P(PSTR(" listen_mode=%s"), EMSESP_Status.listen_mode ? "on" : "off"); myDebug_P(PSTR(" listen_mode=%s"), EMSESP_Status.listen_mode ? "on" : "off");
myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off"); myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off");
myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off"); myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off");
myDebug_P(PSTR(" publish_time=%d"), EMSESP_Status.publish_time); myDebug_P(PSTR(" publish_time=%d"), EMSESP_Status.publish_time);
myDebug_P(PSTR(" tx_mode=%d"), ems_getTxMode());
} }
return ok; return ok;
@@ -1469,6 +1480,9 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) {
} else if (strcmp(second_cmd, "n") == 0) { } else if (strcmp(second_cmd, "n") == 0) {
ems_setLogging(EMS_SYS_LOGGING_NONE); ems_setLogging(EMS_SYS_LOGGING_NONE);
ok = true; ok = true;
} else if (strcmp(second_cmd, "j") == 0) {
ems_setLogging(EMS_SYS_LOGGING_JABBER);
ok = true;
} }
} }
@@ -1562,18 +1576,20 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
if (type == MQTT_CONNECT_EVENT) { if (type == MQTT_CONNECT_EVENT) {
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_TEMP); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_TEMP);
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_MODE); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_MODE);
myESP.mqttSubscribe(TOPIC_BOILER_WWACTIVATED);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_FLOWTEMP);
myESP.mqttSubscribe(TOPIC_SHOWER_TIMER);
myESP.mqttSubscribe(TOPIC_SHOWER_ALERT);
myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT);
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_HC); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_HC);
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_DAYTEMP); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_DAYTEMP);
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_NIGHTTEMP); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_NIGHTTEMP);
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP); myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWACTIVATED);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_WWTEMP);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_COMFORT);
myESP.mqttSubscribe(TOPIC_BOILER_CMD_FLOWTEMP);
myESP.mqttSubscribe(TOPIC_SHOWER_TIMER);
myESP.mqttSubscribe(TOPIC_SHOWER_ALERT);
myESP.mqttSubscribe(TOPIC_SHOWER_COLDSHOT);
// publish the status of the Shower parameters // publish the status of the Shower parameters
myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Status.shower_timer ? "1" : "0"); myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Status.shower_timer ? "1" : "0");
myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Status.shower_alert ? "1" : "0"); myESP.mqttPublish(TOPIC_SHOWER_ALERT, EMSESP_Status.shower_alert ? "1" : "0");
@@ -1637,7 +1653,7 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
} }
// wwActivated // wwActivated
if (strcmp(topic, TOPIC_BOILER_WWACTIVATED) == 0) { if (strcmp(topic, TOPIC_BOILER_CMD_WWACTIVATED) == 0) {
if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) { if ((message[0] == '1' || strcmp(message, "on") == 0) || (strcmp(message, "auto") == 0)) {
ems_setWarmWaterActivated(true); ems_setWarmWaterActivated(true);
} else if (message[0] == '0' || strcmp(message, "off") == 0) { } else if (message[0] == '0' || strcmp(message, "off") == 0) {
@@ -1699,58 +1715,175 @@ void MQTTCallback(unsigned int type, const char * topic, const char * message) {
} }
} }
// web information for diagnostics
void WebCallback(char * body) {
strlcpy(body, "<b>EMS stats:</b><br>", MYESP_MAXCHARBUFFER);
if (ems_getBusConnected()) {
char s[10];
strlcat(body, "EMS Bus is connected<br>", MYESP_MAXCHARBUFFER);
strlcat(body, "Rx: # successful read requests=", MYESP_MAXCHARBUFFER);
strlcat(body, itoa(EMS_Sys_Status.emsRxPgks, s, 10), MYESP_MAXCHARBUFFER);
strlcat(body, ", # CRC errors=", MYESP_MAXCHARBUFFER);
strlcat(body, itoa(EMS_Sys_Status.emxCrcErr, s, 10), MYESP_MAXCHARBUFFER);
if (ems_getTxCapable()) {
strlcat(body, "<br>Tx: # successful write requests=", MYESP_MAXCHARBUFFER);
strlcat(body, itoa(EMS_Sys_Status.emsTxPkgs, s, 10), MYESP_MAXCHARBUFFER);
} else {
strlcat(body, "<br>Tx: no signal<br><br>", MYESP_MAXCHARBUFFER);
}
// show device list
strlcpy(body, "<b>EMS devices found:</b><br>", MYESP_MAXCHARBUFFER);
char buffer[MYESP_MAXCHARBUFFER] = {0};
uint8_t num_devices = ems_printDevices_s(buffer, MYESP_MAXCHARBUFFER);
if (num_devices == 0) {
strlcat(body, "(any detected and compatible EMS devices will show up here)", MYESP_MAXCHARBUFFER);
} else {
strlcat(body, buffer, MYESP_MAXCHARBUFFER);
}
} else {
strlcat(body, "Unable to establish a connection to the EMS Bus.", MYESP_MAXCHARBUFFER);
}
}
// Init callback, which is used to set functions and call methods after a wifi connection has been established // Init callback, which is used to set functions and call methods after a wifi connection has been established
void WIFICallback() { void WIFICallback() {
// This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals // This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals
// This is done after we have a WiFi signal to avoid any resource conflicts // This is done after we have a WiFi signal to avoid any resource conflicts
system_uart_swap(); // TODO check // TODO see if EMS bus is blocked during startup and whether we still need to delay the UART with the swap below?
// system_uart_swap();
}
// web information for diagnostics
void WebCallback(JsonObject root) {
JsonObject emsbus = root.createNestedObject("emsbus");
/*
if (myESP.getUseSerial()) { if (myESP.getUseSerial()) {
myDebug_P(PSTR("Warning! EMS bus communication disabled when Serial mode enabled. Use 'set serial off' to start communication.")); emsbus["ok"] = false;
emsbus["msg"] = "EMS Bus is disabled when in Serial mode. Check Settings->General Settings->Serial Port";
} else { } else {
emsuart_init(); if (ems_getBusConnected()) {
myDebug_P(PSTR("[UART] Opened Rx/Tx connection")); if (ems_getTxDisabled()) {
if (!EMSESP_Status.listen_mode) { emsbus["ok"] = false;
// go and find the boiler and thermostat types, if not in listen mode emsbus["msg"] = "EMS Bus Connected with Rx active but Tx has been disabled (in listen only mode).";
ems_discoverModels(); } else if (ems_getTxCapable()) {
emsbus["ok"] = true;
emsbus["msg"] = "EMS Bus Connected with both Rx and Tx active.";
} else {
emsbus["ok"] = false;
emsbus["msg"] = "EMS Bus Connected but Tx is not working.";
}
} 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["version"] = (it)->version;
item["productid"] = (it)->product_id;
char s[10];
itoa((it)->device_id, s, 16);
item["deviceid"] = s; // convert to hex
}
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)
|| (ems_getThermostatModel() == EMS_MODEL_FW120)) {
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.curFlowTemp != EMS_VALUE_INT_NOTSET)
boiler["b4"] = EMS_Boiler.curFlowTemp / 10;
if (EMS_Boiler.boilTemp != EMS_VALUE_USHORT_NOTSET)
boiler["b5"] = (double)EMS_Boiler.boilTemp / 10;
if (EMS_Boiler.retTemp != EMS_VALUE_USHORT_NOTSET)
boiler["b6"] = (double)EMS_Boiler.retTemp / 10;
} else {
boiler["ok"] = false;
}
// For SM10/SM100 Solar Module
JsonObject sm = root.createNestedObject("sm");
if (ems_getSolarModuleEnabled()) {
sm["ok"] = true;
char buffer[200];
sm["sm"] = ems_getSolarModuleDescription(buffer, true);
if (EMS_SolarModule.collectorTemp != EMS_VALUE_SHORT_NOTSET)
sm["sm1"] = (double)EMS_SolarModule.collectorTemp / 10; // Collector temperature oC
if (EMS_SolarModule.bottomTemp != EMS_VALUE_SHORT_NOTSET)
sm["sm2"] = (double)EMS_SolarModule.bottomTemp / 10; // Bottom temperature oC
if (EMS_SolarModule.pumpModulation != EMS_VALUE_INT_NOTSET)
sm["sm3"] = EMS_SolarModule.pumpModulation; // Pump modulation %
if (EMS_SolarModule.pump != EMS_VALUE_INT_NOTSET) {
char s[10];
sm["sm4"] = _bool_to_char(s, EMS_SolarModule.pump); // Pump active on/off
}
if (EMS_SolarModule.EnergyLastHour != EMS_VALUE_USHORT_NOTSET)
sm["sm5"] = (double)EMS_SolarModule.EnergyLastHour / 10; // Energy last hour Wh
if (EMS_SolarModule.EnergyToday != EMS_VALUE_USHORT_NOTSET) // Energy today Wh
sm["sm6"] = EMS_SolarModule.EnergyToday;
if (EMS_SolarModule.EnergyTotal != EMS_VALUE_USHORT_NOTSET) // Energy total KWh
sm["sm7"] = (double)EMS_SolarModule.EnergyTotal / 10;
} else {
sm["ok"] = false;
}
// For HeatPumps
JsonObject hp = root.createNestedObject("hp");
if (ems_getHeatPumpEnabled()) {
hp["ok"] = true;
char buffer[200];
hp["hm"] = ems_getHeatPumpDescription(buffer, true);
if (EMS_HeatPump.HPModulation != EMS_VALUE_INT_NOTSET)
hp["hp1"] = EMS_HeatPump.HPModulation; // Pump modulation %
if (EMS_HeatPump.HPSpeed != EMS_VALUE_INT_NOTSET)
hp["hp2"] = EMS_HeatPump.HPSpeed; // Pump speed %
} else {
hp["ok"] = false;
}
// serializeJsonPretty(root, Serial); // turn on for debugging
} }
// Initialize the boiler settings and shower settings // Initialize the boiler settings and shower settings
@@ -1864,25 +1997,14 @@ void setup() {
systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if EMS is reachable systemCheckTimer.attach(SYSTEMCHECK_TIME, do_systemCheck); // check if EMS is reachable
// set up myESP for Wifi, MQTT, MDNS and Telnet // set up myESP for Wifi, MQTT, MDNS and Telnet callbacks
myESP.setTelnet(TelnetCommandCallback, TelnetCallback); // set up Telnet commands myESP.setTelnet(TelnetCommandCallback, TelnetCallback); // set up Telnet commands
myESP.setWIFI(NULL, NULL, WIFICallback); // empty ssid and password as we take this from the config file myESP.setWIFI(WIFICallback); // wifi callback
myESP.setMQTT(MQTTCallback); // MQTT ip, username and password taken from the SPIFFS settings
// MQTT host, username and password taken from the SPIFFS settings myESP.setSettings(LoadSaveCallback, SetListCallback, false); // default is Serial off
myESP.setMQTT( myESP.setWeb(WebCallback); // web custom settings
NULL, NULL, NULL, MQTT_BASE, MQTT_KEEPALIVE, MQTT_QOS, MQTT_RETAIN, MQTT_WILL_TOPIC, MQTT_WILL_ONLINE_PAYLOAD, MQTT_WILL_OFFLINE_PAYLOAD, MQTTCallback); myESP.setOTA(OTACallback_pre, OTACallback_post); // OTA callback which is called when OTA is starting and stopping
myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION, APP_URL, APP_UPDATEURL);
// OTA callback which is called when OTA is starting and stopping
myESP.setOTA(OTACallback_pre, OTACallback_post);
// custom settings in SPIFFS
myESP.setSettings(FSCallback, SettingsCallback);
// web custom settings
myESP.setWeb(WebCallback);
// start up all the services
myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION);
// at this point we have all the settings from our internall SPIFFS config file // at this point we have all the settings from our internall SPIFFS config file
// fire up the UART now // fire up the UART now
@@ -1891,7 +2013,8 @@ void setup() {
} else { } else {
Serial.println("Note: Serial output will now be disabled. Please use Telnet."); Serial.println("Note: Serial output will now be disabled. Please use Telnet.");
Serial.flush(); Serial.flush();
emsuart_init(); myESP.setUseSerial(false);
emsuart_init(); // start EMS bus transmissions
myDebug_P(PSTR("[UART] Opened Rx/Tx connection")); myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
if (!EMSESP_Status.listen_mode) { if (!EMSESP_Status.listen_mode) {
// go and find the boiler and thermostat types, if not in listen mode // go and find the boiler and thermostat types, if not in listen mode

File diff suppressed because it is too large Load Diff

158
src/ems.h
View File

@@ -11,12 +11,14 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <list> // std::list
/* debug helper for logic analyzer /* debug helper for logic analyzer
* create marker puls on GPIOx * create marker puls on GPIOx
* ° for Rx, we use GPIO14 * ° for Rx, we use GPIO14
* ° for Tx, we use GPIO12 * ° for Tx, we use GPIO12
*/ */
// clang-format off // clang-format off
#ifdef LOGICANALYZER #ifdef LOGICANALYZER
#define RX_MARK_PIN 14 #define RX_MARK_PIN 14
@@ -29,39 +31,39 @@
#define GPIO_H(mask) (GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (mask))) #define GPIO_H(mask) (GPIO_REG_WRITE(GPIO_OUT_W1TS_ADDRESS, (mask)))
#define GPIO_L(mask) (GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (mask))) #define GPIO_L(mask) (GPIO_REG_WRITE(GPIO_OUT_W1TC_ADDRESS, (mask)))
#define RX_PULSE(pulse) \ #define RX_PULSE(pulse) \
do { \ do { \
GPIO_H(RX_MARK_MASK); \ GPIO_H(RX_MARK_MASK); \
delayMicroseconds(pulse); \ delayMicroseconds(pulse); \
GPIO_L(RX_MARK_MASK); \ GPIO_L(RX_MARK_MASK); \
} while (0) } while (0)
#define TX_PULSE(pulse) \ #define TX_PULSE(pulse) \
do { \ do { \
GPIO_H(TX_MARK_MASK); \ GPIO_H(TX_MARK_MASK); \
delayMicroseconds(pulse); \ delayMicroseconds(pulse); \
GPIO_L(TX_MARK_MASK); \ GPIO_L(TX_MARK_MASK); \
} while (0) } while (0)
#define LA_PULSE(pulse) \ #define LA_PULSE(pulse) \
do { \ do { \
GPIO_H(MARKERS_MASK); \ GPIO_H(MARKERS_MASK); \
delayMicroseconds(pulse); \ delayMicroseconds(pulse); \
GPIO_L(MARKERS_MASK); \ GPIO_L(MARKERS_MASK); \
} while (0) } while (0)
#define INIT_MARKERS(void) \ #define INIT_MARKERS(void) \
do { \ do { \
pinMode(RX_MARK_PIN, OUTPUT); \ pinMode(RX_MARK_PIN, OUTPUT); \
pinMode(TX_MARK_PIN, OUTPUT); \ pinMode(TX_MARK_PIN, OUTPUT); \
GPIO_L(MARKERS_MASK); \ GPIO_L(MARKERS_MASK); \
} while (0) } while (0)
#else #else
#define RX_PULSE(pulse) \ #define RX_PULSE(pulse) \
{} {}
#define TX_PULSE(pulse) \ #define TX_PULSE(pulse) \
{} {}
#define LA_PULSE(pulse) \ #define LA_PULSE(pulse) \
{} {}
#define INIT_MARKERS(void) \ #define INIT_MARKERS(void) \
{} {}
#define RX_MARK_MASK #define RX_MARK_MASK
#define TX_MARK_MASK #define TX_MARK_MASK
@@ -113,6 +115,13 @@
//#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE //#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE
#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE #define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_NONE
// define the model types which get rendered to html colors in the web interface
#define EMS_MODELTYPE_BOILER 1 // success color
#define EMS_MODELTYPE_THERMOSTAT 2 // info color
#define EMS_MODELTYPE_SM 3 // warning color
#define EMS_MODELTYPE_HP 4 // success color
#define EMS_MODELTYPE_OTHER 5 // no color
/* EMS UART transfer status */ /* EMS UART transfer status */
typedef enum { typedef enum {
EMS_RX_STATUS_IDLE, EMS_RX_STATUS_IDLE,
@@ -124,7 +133,8 @@ typedef enum {
EMS_TX_STATUS_IDLE, // ready EMS_TX_STATUS_IDLE, // ready
EMS_TX_STATUS_WAIT, // waiting for response from last Tx EMS_TX_STATUS_WAIT, // waiting for response from last Tx
EMS_TX_WTD_TIMEOUT, // watchdog timeout during send EMS_TX_WTD_TIMEOUT, // watchdog timeout during send
EMS_TX_BRK_DETECT // incoming BRK during Tx EMS_TX_BRK_DETECT, // incoming BRK during Tx
EMS_TX_REV_DETECT // waiting to detect reverse bit
} _EMS_TX_STATUS; } _EMS_TX_STATUS;
#define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success #define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success
@@ -145,15 +155,16 @@ typedef enum {
EMS_SYS_LOGGING_BASIC, // only basic read/write messages EMS_SYS_LOGGING_BASIC, // only basic read/write messages
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from thermostat EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from thermostat
EMS_SYS_LOGGING_VERBOSE // everything EMS_SYS_LOGGING_VERBOSE, // everything
EMS_SYS_LOGGING_JABBER // lots of debug output...
} _EMS_SYS_LOGGING; } _EMS_SYS_LOGGING;
// status/counters since last power on // status/counters since last power on
typedef struct { typedef struct {
_EMS_RX_STATUS emsRxStatus; _EMS_RX_STATUS emsRxStatus;
_EMS_TX_STATUS emsTxStatus; _EMS_TX_STATUS emsTxStatus;
uint16_t emsRxPgks; // received uint16_t emsRxPgks; // # successfull received
uint16_t emsTxPkgs; // sent uint16_t emsTxPkgs; // # successfull sent
uint16_t emxCrcErr; // CRC errors uint16_t emxCrcErr; // CRC errors
bool emsPollEnabled; // flag enable the response to poll messages bool emsPollEnabled; // flag enable the response to poll messages
_EMS_SYS_LOGGING emsLogging; // logging _EMS_SYS_LOGGING emsLogging; // logging
@@ -164,8 +175,9 @@ typedef struct {
bool emsTxCapable; // able to send via Tx bool emsTxCapable; // able to send via Tx
bool emsTxDisabled; // true to prevent all Tx bool emsTxDisabled; // true to prevent all Tx
uint8_t txRetryCount; // # times the last Tx was re-sent uint8_t txRetryCount; // # times the last Tx was re-sent
bool emsReverse; // if true, poll logic is reversed uint8_t emsIDMask; // Buderus: 0x00, Junkers: 0x80
uint8_t emsTxMode; // handles Tx logic uint8_t emsPollAck[1]; // acknowledge buffer for Poll
uint8_t emsTxMode; // Tx mode 1, 2 or 3
} _EMS_Sys_Status; } _EMS_Sys_Status;
// The Tx send package // The Tx send package
@@ -174,12 +186,12 @@ typedef struct {
uint8_t dest; uint8_t dest;
uint16_t type; uint16_t type;
uint8_t offset; uint8_t offset;
uint8_t length; // full length of complete telegram uint8_t length; // full length of complete telegram, including CRC
uint8_t dataValue; // value to validate against uint8_t dataValue; // value to validate against
uint16_t type_validate; // type to call after a successful Write command uint16_t type_validate; // type to call after a successful Write command
uint8_t comparisonValue; // value to compare against during a validate uint8_t comparisonValue; // value to compare against during a validate command
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation
uint16_t comparisonPostRead; // after a successful write call this to read from this type ID uint16_t comparisonPostRead; // after a successful write, do a read from this type ID
bool forceRefresh; // should we send to MQTT after a successful Tx? bool forceRefresh; // should we send to MQTT after a successful Tx?
uint32_t timestamp; // when created uint32_t timestamp; // when created
uint8_t data[EMS_MAX_TELEGRAM_LENGTH]; uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
@@ -187,19 +199,20 @@ typedef struct {
// The Rx receive package // The Rx receive package
typedef struct { typedef struct {
uint32_t timestamp; // timestamp from millis() uint32_t timestamp; // timestamp from millis()
uint8_t * telegram; // the full data package uint8_t * telegram; // the full data package
uint8_t data_length; // length in bytes of the data uint8_t data_length; // length in bytes of the data
uint8_t length; // full length of the complete telegram uint8_t length; // full length of the complete telegram
uint8_t src; // source ID uint8_t src; // source ID
uint8_t dest; // destination ID uint8_t dest; // destination ID
uint16_t type; // type ID as a double byte to support EMS+ uint16_t type; // type ID as a double byte to support EMS+
uint8_t offset; // offset uint8_t offset; // offset
uint8_t * data; // pointer to where telegram data starts uint8_t * data; // pointer to where telegram data starts
bool emsplus; // true if ems+/ems 2.0 bool emsplus; // true if ems+/ems 2.0
uint8_t emsplus_type; // FF, F7 or F9
} _EMS_RxTelegram; } _EMS_RxTelegram;
// default empty Tx // default empty Tx, must match struct
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = { const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
EMS_TX_TELEGRAM_INIT, // action EMS_TX_TELEGRAM_INIT, // action
EMS_ID_NONE, // dest EMS_ID_NONE, // dest
@@ -220,25 +233,25 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
typedef struct { typedef struct {
uint8_t product_id; uint8_t product_id;
char model_string[50]; char model_string[50];
} _Boiler_Type; } _Boiler_Device;
typedef struct { typedef struct {
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
char model_string[50]; char model_string[50];
} _SolarModule_Type; } _SolarModule_Device;
typedef struct { typedef struct {
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
char model_string[50]; char model_string[50];
} _Other_Type; } _Other_Device;
typedef struct { typedef struct {
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
char model_string[50]; char model_string[50];
} _HeatPump_Type; } _HeatPump_Device;
typedef struct { typedef struct {
uint8_t model_id; uint8_t model_id;
@@ -246,15 +259,16 @@ typedef struct {
uint8_t device_id; uint8_t device_id;
char model_string[50]; char model_string[50];
bool write_supported; bool write_supported;
} _Thermostat_Type; } _Thermostat_Device;
// for consolidating all types // for consolidating all types
typedef struct { typedef struct {
uint8_t model_type; // 1=boiler, 2=thermostat, 3=sm, 4=other, 5=unknown
uint8_t product_id; uint8_t product_id;
uint8_t device_id; uint8_t device_id;
char version[10]; char version[10];
char model_string[50]; char model_string[50];
} _Generic_Type; } _Generic_Device;
/* /*
* Telegram package defintions * Telegram package defintions
@@ -390,6 +404,7 @@ typedef struct {
} _EMS_Type; } _EMS_Type;
// function definitions // function definitions
extern void ems_dumpBuffer(const char * prefix, uint8_t * telegram, uint8_t length);
extern void ems_parseTelegram(uint8_t * telegram, uint8_t len); extern void ems_parseTelegram(uint8_t * telegram, uint8_t len);
void ems_init(); void ems_init();
void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh = false); void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh = false);
@@ -403,28 +418,27 @@ void ems_testTelegram(uint8_t test_num);
void ems_startupTelegrams(); void ems_startupTelegrams();
bool ems_checkEMSBUSAlive(); bool ems_checkEMSBUSAlive();
void ems_clearDeviceList(); void ems_clearDeviceList();
void ems_setTxMode(uint8_t mode);
void ems_setThermostatTemp(float temperature, uint8_t temptype = 0); void ems_setThermostatTemp(float temperature, uint8_t temptype = 0);
void ems_setThermostatMode(uint8_t mode); void ems_setThermostatMode(uint8_t mode);
void ems_setThermostatHC(uint8_t hc); void ems_setThermostatHC(uint8_t hc);
void ems_setWarmWaterTemp(uint8_t temperature); void ems_setWarmWaterTemp(uint8_t temperature);
void ems_setFlowTemp(uint8_t temperature); void ems_setFlowTemp(uint8_t temperature);
void ems_setWarmWaterActivated(bool activated); void ems_setWarmWaterActivated(bool activated);
void ems_setWarmTapWaterActivated(bool activated); void ems_setWarmTapWaterActivated(bool activated);
void ems_setPoll(bool b); void ems_setPoll(bool b);
void ems_setLogging(_EMS_SYS_LOGGING loglevel); void ems_setLogging(_EMS_SYS_LOGGING loglevel);
void ems_setEmsRefreshed(bool b); void ems_setEmsRefreshed(bool b);
void ems_setWarmWaterModeComfort(uint8_t comfort); void ems_setWarmWaterModeComfort(uint8_t comfort);
void ems_setModels(); void ems_setModels();
void ems_setTxDisabled(bool b); void ems_setTxDisabled(bool b);
bool ems_getTxDisabled(); bool ems_getTxDisabled();
uint8_t ems_getTxMode(); void ems_setTxMode(uint8_t mode);
char * ems_getThermostatDescription(char * buffer); char * ems_getThermostatDescription(char * buffer, bool name_only = false);
char * ems_getBoilerDescription(char * buffer); char * ems_getBoilerDescription(char * buffer, bool name_only = false);
char * ems_getSolarModuleDescription(char * buffer); char * ems_getSolarModuleDescription(char * buffer, bool name_only = false);
char * ems_getHeatPumpDescription(char * buffer); char * ems_getHeatPumpDescription(char * buffer, bool name_only = false);
void ems_getThermostatValues(); void ems_getThermostatValues();
void ems_getBoilerValues(); void ems_getBoilerValues();
void ems_getSolarModuleValues(); void ems_getSolarModuleValues();
@@ -457,3 +471,5 @@ extern _EMS_Thermostat EMS_Thermostat;
extern _EMS_SolarModule EMS_SolarModule; extern _EMS_SolarModule EMS_SolarModule;
extern _EMS_HeatPump EMS_HeatPump; extern _EMS_HeatPump EMS_HeatPump;
extern _EMS_Other EMS_Other; extern _EMS_Other EMS_Other;
extern std::list<_Generic_Device> Devices;

View File

@@ -13,12 +13,12 @@
#include "ems.h" #include "ems.h"
/* /*
* Common * Common Type
*/ */
#define EMS_TYPE_Version 0x02 #define EMS_TYPE_Version 0x02
/* /*
* Boiler... * Boiler Telegram Types...
*/ */
#define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast #define EMS_TYPE_UBAMonitorFast 0x18 // is an automatic monitor broadcast
#define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast #define EMS_TYPE_UBAMonitorSlow 0x19 // is an automatic monitor broadcast
@@ -40,7 +40,7 @@
#define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp #define EMS_OFFSET_UBASetPoints_flowtemp 0 // flow temp
// Other // SM and HP Types
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor #define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor #define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
#define EMS_TYPE_SM100Status 0x0264 // SM100Status #define EMS_TYPE_SM100Status 0x0264 // SM100Status
@@ -54,7 +54,7 @@
#define EMS_OFFSET_ISM1Set_MaxBoilerTemp 6 // position of max boiler temp e.g. 50 in the following example: 90 30 FF 06 00 01 50 (CRC=2C) #define EMS_OFFSET_ISM1Set_MaxBoilerTemp 6 // position of max boiler temp e.g. 50 in the following example: 90 30 FF 06 00 01 50 (CRC=2C)
/* /*
* Thermostats... * Thermostat Types
*/ */
// Common for all thermostats // Common for all thermostats
@@ -63,41 +63,46 @@
// RC10 specific // RC10 specific
#define EMS_TYPE_RC10StatusMessage 0xB1 // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_RC10StatusMessage 0xB1 // is an automatic thermostat broadcast giving us temps
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
#define EMS_OFFSET_RC10StatusMessage_setpoint 1 // setpoint temp #define EMS_OFFSET_RC10StatusMessage_setpoint 1 // setpoint temp
#define EMS_OFFSET_RC10StatusMessage_curr 2 // current temp #define EMS_OFFSET_RC10StatusMessage_curr 2 // current temp
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
// RC20 specific // RC20 specific
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
#define EMS_OFFSET_RC20StatusMessage_setpoint 1 // setpoint temp #define EMS_OFFSET_RC20StatusMessage_setpoint 1 // setpoint temp
#define EMS_OFFSET_RC20StatusMessage_curr 2 // current temp #define EMS_OFFSET_RC20StatusMessage_curr 2 // current temp
#define EMS_TYPE_RC20Set 0xA8 // for setting values like temp and mode
#define EMS_OFFSET_RC20Set_mode 23 // position of thermostat mode
#define EMS_OFFSET_RC20Set_temp 28 // position of thermostat setpoint temperature
// RC30 specific // RC30 specific
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
#define EMS_OFFSET_RC30StatusMessage_setpoint 1 // setpoint temp #define EMS_OFFSET_RC30StatusMessage_setpoint 1 // setpoint temp
#define EMS_OFFSET_RC30StatusMessage_curr 2 // current temp #define EMS_OFFSET_RC30StatusMessage_curr 2 // current temp
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
// RC35 specific // RC35 specific
#define EMS_TYPE_RC35StatusMessage_HC1 0x3E // is an automatic thermostat broadcast giving us temps on HC1 #define EMS_TYPE_RC35StatusMessage_HC1 0x3E // is an automatic thermostat broadcast giving us temps on HC1
#define EMS_TYPE_RC35StatusMessage_HC2 0x48 // is an automatic thermostat broadcast giving us temps on HC2 #define EMS_TYPE_RC35StatusMessage_HC2 0x48 // is an automatic thermostat broadcast giving us temps on HC2
#define EMS_TYPE_RC35Set_HC1 0x3D // for setting values like temp and mode (Working mode HC1)
#define EMS_TYPE_RC35Set_HC2 0x47 // for setting values like temp and mode (Working mode HC2)
#define EMS_OFFSET_RC35StatusMessage_setpoint 2 // desired temp #define EMS_OFFSET_RC35StatusMessage_setpoint 2 // desired temp
#define EMS_OFFSET_RC35StatusMessage_curr 3 // current temp #define EMS_OFFSET_RC35StatusMessage_curr 3 // current temp
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode #define EMS_OFFSET_RC35StatusMessage_mode 1 //day mode
#define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
#define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time #define EMS_TYPE_RC35Set_HC1 0x3D // for setting values like temp and mode (Working mode HC1)
#define EMS_OFFSET_RC35Get_mode_day 1 // position of thermostat day mode #define EMS_TYPE_RC35Set_HC2 0x47 // for setting values like temp and mode (Working mode HC2)
#define EMS_OFFSET_RC35Set_temp_holiday 3 // temp during holiday 0x47 #define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
#define EMS_OFFSET_RC35Set_heatingtype 0 // floor heating = 3 0x47 #define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
#define EMS_OFFSET_RC35Set_circuitcalctemp 14 // calculated circuit temperature 0x48 #define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time
#define EMS_OFFSET_RC35Set_temp_holiday 3 // temp during holiday 0x47
#define EMS_OFFSET_RC35Set_heatingtype 0 // floor heating = 3 0x47
#define EMS_OFFSET_RC35Set_circuitcalctemp 14 // calculated circuit temperature 0x48
// Easy specific // Easy specific
#define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat #define EMS_TYPE_EasyStatusMessage 0x0A // reading values on an Easy Thermostat
@@ -105,14 +110,21 @@
#define EMS_OFFSET_EasyStatusMessage_curr 8 // current temp #define EMS_OFFSET_EasyStatusMessage_curr 8 // current temp
// RC1010, RC310 and RC300 specific (EMS Plus) // RC1010, RC310 and RC300 specific (EMS Plus)
#define EMS_TYPE_RCPLUSStatusMessage 0x01A5 // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_RCPLUSStatusMessage 0x01A5 // is an automatic thermostat broadcast giving us temps, also reading
#define EMS_TYPE_RCPLUSStatusHeating 0x01B9 // heating mode #define EMS_TYPE_RCPLUSStatusMode 0x1AF // summer/winter mode
#define EMS_TYPE_RCPLUSStatusMode 0x1AF // summer/winter mode #define EMS_OFFSET_RCPLUSStatusMessage_mode 10 // thermostat mode (auto, manual)
#define EMS_TYPE_RCPLUSSet 0x03 // setpoint temp message #define EMS_OFFSET_RCPLUSStatusMessage_setpoint 3 // setpoint temp
#define EMS_OFFSET_RCPLUSStatusMessage_setpoint 3 // setpoint temp #define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp
#define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp #define EMS_OFFSET_RCPLUSStatusMessage_currsetpoint 6 // target setpoint temp
#define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode
#define EMS_OFFSET_RCPLUSStatusMessage_mode 0x0A // thermostat mode (auto, manual) #define EMS_TYPE_RCPLUSSet 0x01B9 // setpoint temp message and mode
#define EMS_OFFSET_RCPLUSSet_mode 0 // operation mode(Auto=0xFF, Manual=0x00)
#define EMS_OFFSET_RCPLUSSet_temp_comfort3 1 // comfort3 level
#define EMS_OFFSET_RCPLUSSet_temp_comfort2 2 // comfort2 level
#define EMS_OFFSET_RCPLUSSet_temp_comfort1 3 // comfort1 level
#define EMS_OFFSET_RCPLUSSet_temp_eco 4 // eco level
#define EMS_OFFSET_RCPLUSSet_temp_setpoint 8 // temp setpoint, when changing of templevel (in auto) value is reset and set to FF
#define EMS_OFFSET_RCPLUSSet_manual_setpoint 10 // manual setpoint
// Junkers FR10, FW100 (EMS Plus) // Junkers FR10, FW100 (EMS Plus)
#define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps #define EMS_TYPE_JunkersStatusMessage 0x6F // is an automatic thermostat broadcast giving us temps
@@ -120,7 +132,7 @@
#define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp #define EMS_OFFSET_JunkersStatusMessage_curr 4 // current temp
// Known EMS types // Known EMS devices
typedef enum { typedef enum {
EMS_MODEL_NONE, // unset EMS_MODEL_NONE, // unset
EMS_MODEL_ALL, // common for all devices EMS_MODEL_ALL, // common for all devices
@@ -157,7 +169,7 @@ typedef enum {
// EMS types for known boilers. This list will be extended when new devices are recognized. // EMS types for known boilers. This list will be extended when new devices are recognized.
// The device_id is always 0x08 // The device_id is always 0x08
// format is PRODUCT ID, DESCRIPTION // format is PRODUCT ID, DESCRIPTION
const _Boiler_Type Boiler_Types[] = { const _Boiler_Device Boiler_Devices[] = {
{72, "MC10 Module"}, {72, "MC10 Module"},
{123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"}, {123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"},
@@ -175,7 +187,7 @@ const _Boiler_Type Boiler_Types[] = {
* Known Solar Module types * Known Solar Module types
* format is PRODUCT ID, DEVICE ID, DESCRIPTION * format is PRODUCT ID, DEVICE ID, DESCRIPTION
*/ */
const _SolarModule_Type SolarModule_Types[] = { const _SolarModule_Device SolarModule_Devices[] = {
{EMS_PRODUCTID_SM10, EMS_ID_SM, "SM10 Solar Module"}, {EMS_PRODUCTID_SM10, EMS_ID_SM, "SM10 Solar Module"},
{EMS_PRODUCTID_SM100, EMS_ID_SM, "SM100 Solar Module"}, {EMS_PRODUCTID_SM100, EMS_ID_SM, "SM100 Solar Module"},
@@ -185,7 +197,7 @@ const _SolarModule_Type SolarModule_Types[] = {
// Other EMS devices which are not considered boilers, thermostats or solar modules // Other EMS devices which are not considered boilers, thermostats or solar modules
// format is PRODUCT ID, DEVICE ID, DESCRIPTION // format is PRODUCT ID, DEVICE ID, DESCRIPTION
const _Other_Type Other_Types[] = { const _Other_Device Other_Devices[] = {
{69, 0x21, "MM10 Mixer Module"}, {69, 0x21, "MM10 Mixer Module"},
{71, 0x11, "WM10 Switch Module"}, {71, 0x11, "WM10 Switch Module"},
@@ -207,13 +219,13 @@ const _Other_Type Other_Types[] = {
// heatpump // heatpump
// format is PRODUCT ID, DEVICE ID, DESCRIPTION // format is PRODUCT ID, DEVICE ID, DESCRIPTION
const _HeatPump_Type HeatPump_Types[] = {{252, EMS_ID_HP, "HeatPump Module"}}; const _HeatPump_Device HeatPump_Devices[] = {{252, EMS_ID_HP, "HeatPump Module"}};
/* /*
* Known thermostat types and their capabilities * Known thermostat types and their capabilities
* format is MODEL_ID, PRODUCT ID, DEVICE ID, DESCRIPTION * format is MODEL_ID, PRODUCT ID, DEVICE ID, DESCRIPTION
*/ */
const _Thermostat_Type Thermostat_Types[] = { const _Thermostat_Device Thermostat_Devices[] = {
// Easy devices - not currently supporting write operations // Easy devices - not currently supporting write operations
{EMS_MODEL_EASY, 202, 0x18, "Logamatic TC100/Nefit Moduline Easy", EMS_THERMOSTAT_WRITE_NO}, {EMS_MODEL_EASY, 202, 0x18, "Logamatic TC100/Nefit Moduline Easy", EMS_THERMOSTAT_WRITE_NO},

View File

@@ -7,12 +7,12 @@
#include "emsuart.h" #include "emsuart.h"
#include "ems.h" #include "ems.h"
#include <Arduino.h>
#include <user_interface.h> #include <user_interface.h>
_EMSRxBuf * pEMSRxBuf; _EMSRxBuf * pEMSRxBuf;
_EMSRxBuf * paEMSRxBuf[EMS_MAXBUFFERS]; _EMSRxBuf * paEMSRxBuf[EMS_MAXBUFFERS];
uint8_t emsRxBufIdx = 0; uint8_t emsRxBufIdx = 0;
uint8_t phantomBreak = 0;
os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
@@ -22,7 +22,7 @@ os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
// //
static void emsuart_rx_intr_handler(void * para) { static void emsuart_rx_intr_handler(void * para) {
static uint8_t length; static uint8_t length;
static uint8_t uart_buffer[EMS_MAXBUFFERSIZE]; static uint8_t uart_buffer[EMS_MAXBUFFERSIZE + 2];
// is a new buffer? if so init the thing for a new telegram // is a new buffer? if so init the thing for a new telegram
if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) { if (EMS_Sys_Status.emsRxStatus == EMS_RX_STATUS_IDLE) {
@@ -33,7 +33,9 @@ static void emsuart_rx_intr_handler(void * para) {
// fill IRQ buffer, by emptying Rx FIFO // fill IRQ buffer, by emptying Rx FIFO
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) { if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
while ((USS(EMSUART_UART) >> USRXC) & 0xFF) { while ((USS(EMSUART_UART) >> USRXC) & 0xFF) {
uart_buffer[length++] = USF(EMSUART_UART); uint8_t rx = USF(EMSUART_UART);
if (length < EMS_MAXBUFFERSIZE)
uart_buffer[length++] = rx;
} }
// clear Rx FIFO full and Rx FIFO timeout interrupts // clear Rx FIFO full and Rx FIFO timeout interrupts
@@ -46,10 +48,11 @@ static void emsuart_rx_intr_handler(void * para) {
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
pEMSRxBuf->length = length; pEMSRxBuf->length = (length > EMS_MAXBUFFERSIZE) ? EMS_MAXBUFFERSIZE : length;
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package length = 0;
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity system_os_post(EMSUART_recvTaskPrio, 0, 0); // call emsuart_recvTask() at next opportunity
RX_PULSE(EMSUART_BIT_TIME / 2); RX_PULSE(EMSUART_BIT_TIME / 2);
@@ -67,18 +70,21 @@ static void ICACHE_FLASH_ATTR emsuart_recvTask(os_event_t * events) {
uint8_t length = pCurrent->length; // number of bytes including the BRK at the end uint8_t length = pCurrent->length; // number of bytes including the BRK at the end
pCurrent->length = 0; pCurrent->length = 0;
// validate and transmit the EMS buffer, excluding the BRK if (phantomBreak) {
phantomBreak = 0;
length--; // remove phantom break from Rx buffer
}
if (length == 2) { if (length == 2) {
RX_PULSE(20); RX_PULSE(20);
// it's a poll or status code, single byte and ok to send on // it's a poll or status code, single byte and ok to send on
ems_parseTelegram((uint8_t *)pCurrent->buffer, 1); ems_parseTelegram((uint8_t *)pCurrent->buffer, 1);
} else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1) && (pCurrent->buffer[length - 2] != 0x00)) { } else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) {
// ignore double BRK at the end, possibly from the Tx loopback // ignore double BRK at the end, possibly from the Tx loopback
// also telegrams with no data value // also telegrams with no data value
RX_PULSE(40); RX_PULSE(40);
ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK ems_parseTelegram((uint8_t *)pCurrent->buffer, length - 1); // transmit EMS buffer, excluding the BRK
} }
// memset(pCurrent->buffer, 0x00, EMS_MAXBUFFERSIZE); // wipe memory just to be safe
} }
/* /*
@@ -122,10 +128,10 @@ void ICACHE_FLASH_ATTR emsuart_init() {
// UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127) // UCFFT = RX FIFO Full Threshold (7 bit) = want this to be 31 for 32 bytes of buffer (default was 127)
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf // see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
// //
// change: we set UCFFT to 1 to get an immediate indicator about incoming trafffic. // change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
// Otherwise, we're only noticed by UCTOT or RxBRK! // Otherwise, we're only noticed by UCTOT or RxBRK!
USC1(EMSUART_UART) = 0; // reset config first USC1(EMSUART_UART) = 0; // reset config first
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (0 << UCTOE); // enable interupts
// set interrupts for triggers // set interrupts for triggers
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
@@ -133,7 +139,8 @@ void ICACHE_FLASH_ATTR emsuart_init() {
// enable rx break, fifo full and timeout. // enable rx break, fifo full and timeout.
// but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes // but not frame error UIFR (because they are too frequent) or overflow UIOF because our buffer is only max 32 bytes
USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (1 << UITO); // change: we don't care about Rx Timeout - it may lead to wrong readouts
USIE(EMSUART_UART) = (1 << UIBD) | (1 << UIFF) | (0 << UITO);
// set up interrupt callbacks for Rx // set up interrupt callbacks for Rx
system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen); system_os_task(emsuart_recvTask, EMSUART_recvTaskPrio, recvTaskQueue, EMSUART_recvTaskQueueLen);
@@ -142,7 +149,7 @@ void ICACHE_FLASH_ATTR emsuart_init() {
system_set_os_print(0); system_set_os_print(0);
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively // swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
//system_uart_swap(); system_uart_swap();
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr); ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
ETS_UART_INTR_ENABLE(); ETS_UART_INTR_ENABLE();
@@ -199,16 +206,15 @@ void ICACHE_FLASH_ATTR emsuart_tx_brk() {
*/ */
_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) { _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
_EMS_TX_STATUS result = EMS_TX_STATUS_OK; _EMS_TX_STATUS result = EMS_TX_STATUS_OK;
if (EMS_Sys_Status.emsLogging == EMS_SYS_LOGGING_JABBER) {
ems_dumpBuffer("emsuart_tx_buffer: ", buf, len); // validate and transmit the EMS buffer, excluding the BRK
}
if (len) { if (len) {
LA_PULSE(50); LA_PULSE(50);
// temp code until we get mode 2 working without resets
if (EMS_Sys_Status.emsTxMode == 0) { // classic mode logic if (EMS_Sys_Status.emsTxMode == 2) { // With extra tx delay for EMS+
for (uint8_t i = 0; i < len; i++) {
TX_PULSE(EMSUART_BIT_TIME / 4);
USF(EMSUART_UART) = buf[i];
}
emsuart_tx_brk(); // send <BRK>
} else if (EMS_Sys_Status.emsTxMode == 1) { // With extra tx delay for EMS+
for (uint8_t i = 0; i < len; i++) { for (uint8_t i = 0; i < len; i++) {
TX_PULSE(EMSUART_BIT_TIME / 4); TX_PULSE(EMSUART_BIT_TIME / 4);
USF(EMSUART_UART) = buf[i]; USF(EMSUART_UART) = buf[i];
@@ -228,9 +234,8 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP); delayMicroseconds(EMSUART_TX_WAIT_BYTE - EMSUART_TX_LAG + EMSUART_TX_WAIT_GAP);
} }
emsuart_tx_brk(); // send <BRK> emsuart_tx_brk(); // send <BRK>
} else if (EMS_Sys_Status.emsTxMode == 2) { } else if (EMS_Sys_Status.emsTxMode == 1) {
/* /*
*
* based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch * based on code from https://github.com/proddy/EMS-ESP/issues/103 by @susisstrolch
* we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO. * we emit the whole telegram, with Rx interrupt disabled, collecting busmaster response in FIFO.
* after sending the last char we poll the Rx status until either * after sending the last char we poll the Rx status until either
@@ -254,9 +259,9 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
// shorter busy poll... // shorter busy poll...
#define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8) #define EMSUART_BUSY_WAIT (EMSUART_BIT_TIME / 8)
#define EMS_TX_TO_COUNT ((20 + 10000 / EMSUART_BIT_TIME) * 8) #define EMS_TX_TO_CHARS (2 + 20)
#define EMS_TX_TO_COUNT ((EMS_TX_TO_CHARS)*10 * 8)
uint16_t wdc = EMS_TX_TO_COUNT; uint16_t wdc = EMS_TX_TO_COUNT;
ETS_UART_INTR_DISABLE(); // disable rx interrupt ETS_UART_INTR_DISABLE(); // disable rx interrupt
// clear Rx status register // clear Rx status register
@@ -272,7 +277,6 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
USF(EMSUART_UART) = buf[i++]; // send each Tx byte USF(EMSUART_UART) = buf[i++]; // send each Tx byte
// wait for echo from busmaster // wait for echo from busmaster
GPIO_L(TX_MARK_MASK); GPIO_L(TX_MARK_MASK);
while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) { while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) {
delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles... delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles...
if (--wdc == 0) { if (--wdc == 0) {
@@ -301,11 +305,13 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
// wait until BRK detected... // wait until BRK detected...
while (!(USIR(EMSUART_UART) & (1 << UIBD))) { while (!(USIR(EMSUART_UART) & (1 << UIBD))) {
delayMicroseconds(EMSUART_BUSY_WAIT); // delayMicroseconds(EMSUART_BUSY_WAIT);
delayMicroseconds(EMSUART_BIT_TIME);
} }
USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK> USC0(EMSUART_UART) &= ~((1 << UCBRK) | (1 << UCLBE)); // disable loopback & clear <BRK>
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
phantomBreak = 1;
} }
GPIO_L(TX_MARK_MASK); GPIO_L(TX_MARK_MASK);
} }
@@ -314,16 +320,3 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
} }
return result; return result;
} }
/*
* Send the Poll (our own ID) to Tx as a single byte and end with a <BRK>
*/
void ICACHE_FLASH_ATTR emsuart_tx_poll() {
static uint8_t buf[1];
if (EMS_Sys_Status.emsReverse) {
buf[0] = {EMS_ID_ME | 0x80};
} else {
buf[0] = {EMS_ID_ME};
}
emsuart_tx_buffer(buf, 1);
}

View File

@@ -7,7 +7,6 @@
*/ */
#pragma once #pragma once
#include <Arduino.h>
#include <ems.h> #include <ems.h>
#define EMSUART_UART 0 // UART 0 #define EMSUART_UART 0 // UART 0
@@ -37,4 +36,3 @@ void ICACHE_FLASH_ATTR emsuart_init();
void ICACHE_FLASH_ATTR emsuart_stop(); void ICACHE_FLASH_ATTR emsuart_stop();
void ICACHE_FLASH_ATTR emsuart_start(); void ICACHE_FLASH_ATTR emsuart_start();
_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len); _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
void ICACHE_FLASH_ATTR emsuart_tx_poll();

View File

@@ -10,26 +10,14 @@
#include "ems.h" #include "ems.h"
// MQTT base name
#define MQTT_BASE "home" // all MQTT topics are prefix with this string, in the format <MQTT_BASE>/<app name>/<topic>
// MQTT general settings
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
#define MQTT_RETAIN false
#define MQTT_KEEPALIVE 120 // 2 minutes
#define MQTT_QOS 1
#define MQTT_MAX_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
// MQTT for thermostat // MQTT for thermostat
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT #define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT #define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT
#define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // for received thermostat mode changes via MQTT #define TOPIC_THERMOSTAT_CMD_MODE "thermostat_cmd_mode" // for received thermostat mode changes via MQTT
#define TOPIC_THERMOSTAT_CMD_HC "thermostat_cmd_hc" // for received thermostat hc number changes via MQTT #define TOPIC_THERMOSTAT_CMD_HC "thermostat_cmd_hc" // for received thermostat hc number changes via MQTT
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "thermostat_daytemp" // RC35 specific #define TOPIC_THERMOSTAT_CMD_DAYTEMP "thermostat_daytemp" // for received thermostat day temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // RC35 specific #define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // for received thermostat night temp (RC35 specific)
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "thermostat_holidayttemp" // RC35 specific #define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "thermostat_holidayttemp" // for received thermostat holiday temp (RC35 specific)
#define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature #define THERMOSTAT_CURRTEMP "thermostat_currtemp" // current temperature
#define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature #define THERMOSTAT_SELTEMP "thermostat_seltemp" // selected temperature
#define THERMOSTAT_HC "thermostat_hc" // which heating circuit number #define THERMOSTAT_HC "thermostat_hc" // which heating circuit number
@@ -41,13 +29,13 @@
#define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific #define THERMOSTAT_CIRCUITCALCTEMP "thermostat_circuitcalctemp" // RC35 specific
// MQTT for boiler // MQTT for boiler
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT #define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running #define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on #define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
#define TOPIC_BOILER_WWACTIVATED "wwactivated" // for receiving MQTT message to change water on/off #define TOPIC_BOILER_CMD_WWACTIVATED "boiler_cmd_wwactivated" // for received message to change water on/off
#define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT #define TOPIC_BOILER_CMD_WWTEMP "boiler_cmd_wwtemp" // for received boiler wwtemp changes via MQTT
#define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // for received boiler ww comfort setting via MQTT #define TOPIC_BOILER_CMD_COMFORT "boiler_cmd_comfort" // for received boiler ww comfort setting via MQTT
#define TOPIC_BOILER_CMD_FLOWTEMP "boiler_cmd_flowtemp" // for received boiler flowtemp value via MQTT #define TOPIC_BOILER_CMD_FLOWTEMP "boiler_cmd_flowtemp" // for received boiler flowtemp value via MQTT
// MQTT for SM10/SM100 Solar Module // MQTT for SM10/SM100 Solar Module
#define TOPIC_SM_DATA "sm_data" // topic name #define TOPIC_SM_DATA "sm_data" // topic name
@@ -74,28 +62,3 @@
// MQTT for EXTERNAL SENSORS // MQTT for EXTERNAL SENSORS
#define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT #define TOPIC_EXTERNAL_SENSORS "sensors" // for sending sensor values to MQTT
#define PAYLOAD_EXTERNAL_SENSORS "temp_%d" // for formatting the payload for each external dallas sensor #define PAYLOAD_EXTERNAL_SENSORS "temp_%d" // for formatting the payload for each external dallas sensor
////////////////////////////////////////////////////////////////////////////////////////////////////
// THESE DEFAULT VALUES CAN ALSO BE SET AND STORED WITHTIN THE APPLICATION (see 'set' command) //
////////////////////////////////////////////////////////////////////////////////////////////////////
// Set LED pin used for showing the EMS bus connection status. Solid means EMS bus working, flashing is an error
// can be either the onboard LED on the ESP8266 (LED_BULLETIN) or external via an external pull-up LED (e.g. D1 on a bbqkees' board)
// can be enabled and disabled via the 'set led' command and pin set by 'set led_gpio'
#define EMSESP_LED_GPIO LED_BUILTIN
#ifdef LOGICANALYZER
#define EMSESP_DALLAS_GPIO D1
#define EMSESP_DALLAS_PARASITE false
#else
// set this if using an external temperature sensor like a DS18B20
// D5 is the default on a bbqkees board
#define EMSESP_DALLAS_GPIO D5
#define EMSESP_DALLAS_PARASITE false
#endif
// By default the EMS bus will be scanned for known devices based on the product ids in ems_devices.h
// You can override the Thermostat and Boiler types here
#define EMSESP_BOILER_TYPE EMS_ID_NONE
#define EMSESP_THERMOSTAT_TYPE EMS_ID_NONE

View File

@@ -52,7 +52,9 @@ static const char * TEST_DATA[] = {
"38 10 FF 00 03 2B 00 C7 07 C3 01", // test 47 - heatpump Enviline "38 10 FF 00 03 2B 00 C7 07 C3 01", // test 47 - heatpump Enviline
"08 0B 19 00 00 F7 80 00 80 00 00 00 00 00 03 58 97 0C 7B 1F 00 00 00 06 C4 DF 02 64 48 80 00", // test 48 - outdoor temp check "08 0B 19 00 00 F7 80 00 80 00 00 00 00 00 03 58 97 0C 7B 1F 00 00 00 06 C4 DF 02 64 48 80 00", // test 48 - outdoor temp check
"88 00 19 00 00 DC 80 00 80 00 FF FF 00 00 00 21 9A 06 E1 7C 00 00 00 06 C2 13 00 1E 90 80 00", // test 49 - check max length "88 00 19 00 00 DC 80 00 80 00 FF FF 00 00 00 21 9A 06 E1 7C 00 00 00 06 C2 13 00 1E 90 80 00", // test 49 - check max length
"30 00 FF 00 02 8E 00 00 41 82 00 00 28 36 00 00 82 21" // test 50 - SM100 "30 00 FF 00 02 8E 00 00 41 82 00 00 28 36 00 00 82 21", // test 50 - SM100
"10 00 FF 08 01 B9 26", // test 51 - EMS+ 0x1B9 set temp
"10 00 F7 00 FF 01 B9 21 E9" // test 52 - EMS+ 0x1B9 F7 test
}; };

View File

@@ -1,10 +1 @@
/** #define APP_VERSION "1.9.0"
*
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
*/
#pragma once
#define APP_NAME "EMS-ESP"
#define APP_VERSION "1.8.3"
#define APP_HOSTNAME "ems-esp"

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

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

@@ -0,0 +1,293 @@
<!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>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>Update Firmware</a>
</li>
</ul>
<ul class="list-unstyled CTAs">
<li>
<a id="helpurl" target="_blank" class="download">Help</a>
</li>
<li>
<a href="#" class="article" onclick="logout();">Logout</a>
</li>
</ul>
<div class="container-fluid">
<div class="row">
<div class="col-xs-3">
<div style="position:fixed; top:0; bottom:40px; overflow-y:scroll; float:none;">
<hr>
</hr>
<footer style="position:fixed; bottom: 0; height:40px;"><a id="appurl"
href="https://github.com/proddy" target="_blank">
<h6 id="appurl2"></h6>
</a></footer>
</div>
</div>
</div>
</div>
</nav>
<!-- 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 before
resetting!</h5>
</div>
<div class="modal-body">
<h5>Please type in the hostname of the device to confirm.</h5>
<div style="clear:both;">
<br>
</div>
<p>
<input type="text" class="form-control input-block" id="compare"
oninput="compareDestroy()">
</p>
</div>
<div class="modal-footer">
<button id="destroybtn" type="button" disabled="" onclick="destroy();"
class="btn btn-block btn-danger">I understand, reset all my settings</button>
</div>
</div>
</div>
</div>
<div id="reboot" class="modal fade" role="dialog">
<div class="modal-dialog">
<!-- 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 before updating
</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>
<div class="form-group">
<input id="binform" onchange="allowUpload();" type="file" name="update"
accept=".bin">
</div>
<div class="pull-right">
<button onclick="upload();" class="btn btn-primary" id="upbtn"
disabled="">Update</button>
</div>
</div>
</div>
<div style="clear:both;">
<br>
</div>
<div class="modal-footer">
</div>
</div>
</div>
</div>
</div>
<footer class="footer">
<div id="commit" class="container"></div>
</footer>
<div class="overlay"></div>
<script src="js/required.js"></script>
<script src="js/myesp.js"></script>
<script>start();</script>
</body>
</html>

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

@@ -0,0 +1,450 @@
<div id="backupcontent">
<br>
<br>
<div class="row">
<div class="col-sm-6">
<legend>Backup</legend>
<h6 class="text-muted">Please make sure that you make regular backups.</h6>
<div>
<button class="btn btn-link btn-sm" onclick="backupset();">Backup System Settings</button>
<a id="downloadSet" style="display:none"></a>
<button class="btn btn-link btn-sm" onclick="backupCustomSet();">Backup Custom Settings</button>
<a id="downloadCustomSet" style="display:none"></a>
</div>
<br>
<div>
<legend>Restore</legend>
<h6 class="text-muted">Restore system and custom settings.</h6>
<label for="restoreSet" class="btn btn-link btn-sm">Restore System Settings</label>
<input id="restoreSet" type="file" accept="text/json" onchange="restoreSet();" style="display:none;">
<label for="restoreCustomSet" class="btn btn-link btn-sm">Restore Custom Settings</label>
<input id="restoreCustomSet" type="file" accept="text/json" onchange="restoreCustomSet();"
style="display:none;">
</div>
<br>
<div 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 a few 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>
<legend>General Settings</legend>
<h6 class="text-muted">Setup general system settings</h6>
<br>
<div class="row form-group">
<label class="col-xs-3">Admin Password<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Password for logging into the web console"></i></label>
<span class="col-xs-9 col-md-5">
<input class="form-control input-sm" placeholder="Administrator Password" id="adminpwd" type="password">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Host Name<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Unique name of the device, used in MQTT, AP SSID and web/telnet hostname"></i></label>
<span class="col-xs-9 col-md-5">
<input class="form-control input-sm" placeholder="Hostname" id="hostname" type="text">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Serial Port<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Enabling Serial port will echo Telnet output to the monitoring console via USB"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="serialenabled">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="serialenabled" checked>Disabled</label>
</form>
</div>
<br>
<br>
<div class="col-xs-9 col-md-8">
<button onclick="savegeneral()" class="btn btn-primary btn-sm pull-right">Save</button>
</div>
</div>
<div style="clear:both;">
<br>
<br>
</div>
</div>
<br>
</div>
<div id="eventcontent">
<div class="text-center" id="loading-img">
<h5>Please wait while processing the log data...<span id="loadpages"></span></h5>
<br>
</div>
<div>
<br>
<legend>Event Log</legend>
<h6 class="text-muted">Dates shown in () represent elapsed time in seconds when NTP Time is disabled</h6>
<br>
<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>
<legend>MQTT Settings</legend>
<h6 class="text-muted">Set up access to an MQTT broker</h6>
<br>
<div class="row form-group">
<label class="col-xs-3">MQTT<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Enable or Disable MQTT support"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="mqttenabled">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="mqttenabled" checked>Disabled</label>
</form>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">IP<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign" aria-hidden="true"
data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Server IP Address"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="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 number"></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 Server username (can be blank)"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
id="mqttuser" type="text">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Password<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT Server password (can be blank)"></i></label>
<span class="col-xs-9">
<input class="form-control input-sm" placeholder="" value="" style="display:inline;max-width:185px"
id="mqttpwd" type="password">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Base<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="MQTT topic prefix (&lt;mqtt base&gt;/&lt;host name&gt;/)"></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="Enable or Disable an automatic MQTT topic publish with system stats"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="mqttheartbeat">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" name="mqttheartbeat" checked>Disabled</label>
</form>
</div>
</div>
<br>
<div class="col-xs-9 col-md-8">
<button onclick="savemqtt()" class="btn btn-primary btn-sm pull-right">Save</button>
</div>
</div>
<div id="networkcontent">
<br>
<legend>Wireless Settings</legend>
<h6 class="text-muted">Enter the wireless network settings here, or use Scan to find nearby networks to join</h6>
<br>
<div class="row form-group">
<label class="col-xs-3">Mode<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="You can run your ESP in AP Mode (non-captive) or join an existing wireless network"></i></label>
<div class="col-xs-9">
<form>
<label class="radio-inline">
<input type="radio" value="1" name="wmode" id="wmodeap" onclick="handleAP();" checked>Access Point
</label>
<label class="radio-inline">
<input type="radio" value="0" name="wmode" id="wmodesta" onclick="handleSTA();">Client</label>
</form>
</div>
</div>
<div class="row form-group" style="display:none" id="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="wifi Network Name"></i></label>
<span class="col-xs-7 col-md-5">
<input class="form-control input-sm" id="inputtohide" type="text" name="ap_ssid">
<select class="form-control input-sm" style="display:none;" id="ssid" onchange="listBSSID();"></select>
</span>
<span class="col-xs-2">
<button id="scanb" type="button" class="btn btn-info btn-xs" style="display:none;"
onclick="scanWifi()">Scan...</button>
</span>
</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="wifi Password"></i></label>
<span class="col-xs-9 col-md-5">
<input id="wifipass" class="form-control input-sm" name="ap_passwd" type="password">
</span>
</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>
<legend>Time Settings</legend>
<h6 class="text-muted">With Network Time Protocol (NTP) enabled, all times are adjusted to the local timezone and
respect daylight saving
time (DST)</h6>
<br>
<div class="row form-group">
<label class="col-xs-3">Device Time</label>
<span id="utc" class="col-xs-9 col-md-5">
</span>
</div>
<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-info 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" onclick="handleNTPON();" name="ntpenabled">Enabled</label>
<label class="radio-inline">
<input type="radio" value="0" onclick="handleNTPOFF();" name="ntpenabled" checked>Disabled</label>
</form>
<button onclick="forcentp()" id="forcentp" class="btn btn-info btn-sm">Sync Device with Internet
Time</button>
</div>
</div>
<div class="row form-group">
<label class="col-xs-3">NTP Server Address<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="The URL or IP of the NTP server. Choose the nearest server for better accuracy (see https://www.ntppool.org)"></i></label>
<span class="col-xs-9 col-md-5">
<input class="form-control input-sm" placeholder="eg. pool.ntp.org" value="pool.ntp.org" id="ntpserver"
type="text">
</span>
<br>
</div>
<div class="row form-group">
<label class="col-xs-3">Recalibrate Time<i style="margin-left: 10px;" class="glyphicon glyphicon-info-sign"
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Time in minutes between scheduled NTP refreshes"></i></label>
<span class="col-xs-9 col-md-5">
<input class="form-control input-sm" placeholder="in Minutes" value="30" id="intervals" type="text">
</span>
<br>
</div>
<br>
<div class="col-xs-9 col-md-8">
<button onclick="saventp()" class="btn btn-primary btn-sm pull-right">Save</button>
</div>
</div>
<div id="statuscontent">
<br>
<div class="row text-left">
<div class="col-md-8 col-md-offset-2">
<h2>System Status</h2>
<h6>Current health of the ESP is displayed here</h6>
<div class="panel panel-default table-responsive">
<table class="table table-hover table-striped table-condensed">
<caption>General</caption>
<tr>
<th>Uptime</th>
<td id="uptime"></td>
</tr>
<tr>
<th>System Load</th>
<td id="systemload"> %</td>
</tr>
</table>
</div>
<div class="panel panel-default table-responsive">
<table class="table table-hover table-striped table-condensed">
<caption>Storage</caption>
<tr>
<th>Free Heap</th>
<td>
<div class="progress" style="margin-bottom: 0 !important;">
<div id="heap" class="progress-bar progress-bar-primary" role="progressbar">
Progress
</div>
</div>
</td>
</tr>
<tr>
<th>Free Flash</th>
<td>
<div class='progress' style="margin-bottom: 0 !important;">
<div id="flash" class="progress-bar progress-bar-primary" role="progressbar">
Progress
</div>
</div>
</td>
</tr>
<tr>
<th>Free SPIFFS</th>
<td>
<div class='progress' style="margin-bottom: 0 !important;">
<div id="spiffs" class="progress-bar progress-bar-primary" role="progressbar">
Progress
</div>
</div>
</td>
</tr>
</table>
</div>
<div class="panel panel-default table-responsive">
<table class="table table-hover table-striped table-condensed">
<caption>Wireless Network</caption>
<tr>
<th>SSID</th>
<td id="ssidstat"></td>
</tr>
<tr>
<th>IP Address</th>
<td id="ip"></td>
</tr>
<tr>
<th>MAC Address</th>
<td id="mac"></td>
</tr>
<tr>
<th>Signal Strength</th>
<td id="signalstr"> %</td>
</tr>
</table>
</div>
<div class="panel panel-default table-responsive">
<table class="table">
<caption>MQTT</caption>
<tr>
<td>
<div id="mqttconnected"></div>
</td>
<td>
<div id="mqttheartbeat"></div>
</td>
</tr>
</table>
<div class="panel-heading">
<div class="panel-title" id="mqttloghdr"></div>
</div>
<div>
<table id="mqttlogtable" class="table" data-paging="false" data-filtering="false"
data-sorting="false" data-editing="false" data-state="true"></table>
</div>
</div>
</div>
</div>
<div class="row form-group" style="text-align: center;">
<button onclick="refreshStatus()" class="btn btn-info">Refresh</button>
</div>
<br>
</div>

1013
src/websrc/myesp.js Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

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

File diff suppressed because it is too large Load Diff

View File

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

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

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

View File

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

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

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

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

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

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

@@ -0,0 +1,284 @@
console.log("[INFO] Starting MyESP WebSocket Emulation Server");
const WebSocket = require("ws");
console.log("[INFO] You can connect to ws://localhost or load URL .../src/websrc/temp/index.html");
console.log("[INFO] Password is 'neo'");
const wss = new WebSocket.Server({
port: 80
});
wss.broadcast = function broadcast(data) {
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
};
var networks = {
"command": "ssidlist",
"list": [{
"ssid": "Company's Network",
"bssid": "4c:f4:39:a1:41",
"rssi": "-84"
},
{
"ssid": "Home Router",
"bssid": "8a:e6:63:a8:15",
"rssi": "-42"
},
{
"ssid": "SSID Shown Here",
"bssid": "8a:f5:86:c3:12",
"rssi": "-77"
},
{
"ssid": "Great Wall of WPA",
"bssid": "9c:f1:90:c5:15",
"rssi": "-80"
},
{
"ssid": "Not Internet",
"bssid": "8c:e4:57:c5:16",
"rssi": "-87"
}
]
}
var eventlog = {
"command": "eventlist",
"page": 1,
"haspages": 1,
"list": [
"{ \"type\": \"WARN\", \"src\": \"sys\", \"desc\": \"Event log cleared!\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"WARN\", \"src\": \"sys\", \"desc\": \"Event log cleared!\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"INFO\", \"src\": \"wifi\", \"desc\": \"WiFi is connected\", \"data\": \"SMC\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"sys\", \"desc\": \"System setup completed, running\", \"data\": \"\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"wifi\", \"desc\": \"WiFi is connected\", \"data\": \"SMC\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"sys\", \"desc\": \"System setup completed, running\", \"data\": \"\", \"time\": 13 }",
"{ \"type\": \"WARN\", \"src\": \"websrv\", \"desc\": \"New login attempt\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"INFO\", \"src\": \"websrv\", \"desc\": \"Login success!\", \"data\": \"\", \"time\": 1563371160 }",
"{ \"type\": \"INFO\", \"src\": \"wifi\", \"desc\": \"WiFi is connected\", \"data\": \"SMC\", \"time\": 13 }",
"{ \"type\": \"INFO\", \"src\": \"sys\", \"desc\": \"System setup completed, running\", \"data\": \"\", \"time\": 13 }",
"{ \"type\": \"WARN\", \"src\": \"websrv\", \"desc\": \"New login attempt\", \"data\": \"\", \"time\": 1563371160 }"
]
}
var configfile = {
"command": "configfile",
"network": {
"ssid": "myssid",
"wmode": 0,
"password": "password"
},
"general": {
"hostname": "myesp",
"password": "admin",
"serial": true
},
"mqtt": {
"enabled": false,
"ip": "ip",
"port": "port",
"base": "base",
"user": "user",
"password": "password",
"heartbeat": false
},
"ntp": {
"server": "pool.ntp.org",
"interval": "30",
"enabled": false
}
};
var custom_configfile = {
"command": "custom_configfile",
"settings": {
"led": true,
"led_gpio": 2,
"dallas_gpio": 14,
"dallas_parasite": false,
"listen_mode": false,
"shower_timer": true,
"shower_alert": false,
"publish_time": 120,
"heating_circuit": 1,
"tx_mode": 1
}
};
function sendEventLog() {
wss.broadcast(eventlog);
var res = {
"command": "result",
"resultof": "eventlist",
"result": true
};
wss.broadcast(res);
}
function sendStatus() {
var stats = {
"command": "status",
"availspiffs": 948,
"spiffssize": 957,
"initheap": 25392,
"heap": 13944,
"sketchsize": 673,
"availsize": 2469,
"ip": "10.10.10.198",
"ssid": "my_ssid",
"mac": "DC:4F:11:22:93:06",
"signalstr": 62,
"systemload": 0,
"mqttconnected": true,
"mqttheartbeat": false,
"uptime": "0 days 0 hours 1 minute 45 seconds",
"mqttloghdr": "home/ems-esp/",
"mqttlog": [
{ "topic": "start", "payload": "start", "time": 1565956388 },
{ "topic": "shower_timer", "payload": "1", "time": 1565956388 },
{ "topic": "shower_alert", "payload": "0", "time": 1565956388 },
{ "topic": "boiler_data", "payload": "{\"wWComfort\":\"Hot\",\"wWSelTemp\":60,\"selFlowTemp\":5,\"selBurnPow\":0,\"curBurnPow\":0,\"pumpMod\":0,\"wWCurTmp\":48.4,\"wWCurFlow\":0,\"curFlowTemp\":49.3,\"retTemp\":49.3,\"sysPress\":1.8,\"boilTemp\":50.5,\"wWActivated\":\"on\",\"burnGas\":\"off\",\"heatPmp\":\"off\",\"fanWork\":\"off\",\"ignWork\":\"off\",\"wWCirc\":\"off\",\"wWHeat\":\"on\",\"burnStarts\":223397,\"burnWorkMin\":366019,\"heatWorkMin\":294036,\"ServiceCode\":\"0H\",\"ServiceCodeNumber\":203}", "time": 1565956463 },
{ "topic": "tapwater_active", "payload": "0", "time": 1565956408 },
{ "topic": "heating_active", "payload": "0", "time": 1565956408 },
{ "topic": "thermostat_data", "payload": "{\"thermostat_hc\":\"1\",\"thermostat_seltemp\":15,\"thermostat_currtemp\":23,\"thermostat_mode\":\"auto\"}", "time": 1565956444 }
]
};
wss.broadcast(stats);
}
function sendCustomStatus() {
var stats = {
"command": "custom_status",
"version": "1.9.0",
"customname": "EMS-ESP",
"appurl": "https://github.com/proddy/EMS-ESP",
"updateurl": "https://api.github.com/repos/proddy/EMS-ESP/releases/latest",
"emsbus": {
"ok": true,
"msg": "EMS Bus Connected with both Rx and Tx active.",
"devices": [
{ "type": 1, "model": "Buderus GB172/Nefit Trendline/Junkers Cerapur", "version": "06.01", "productid": 123, "deviceid": "8" },
{ "type": 5, "model": "BC10 Base Controller", "version": "01.03", "productid": 190, "deviceid": "9" },
{ "type": 2, "model": "RC20/Nefit Moduline 300", "version": "03.03", "productid": 77, "deviceid": "17" },
{ "type": 3, "model": "SM100 Solar Module", "version": "01.01", "productid": 163, "deviceid": "30" },
{ "type": 4, "model": "HeatPump Module", "version": "01.01", "productid": 252, "deviceid": "38" }
]
},
"thermostat": {
"ok": true,
"tm": "RC20/Nefit Moduline 300",
"ts": 15,
"tc": 24.5,
"tmode": "auto"
},
"boiler": {
"ok": true,
"bm": "Buderus GB172/Nefit Trendline/Junkers Cerapur",
"b1": "off",
"b2": "off",
"b3": 0,
"b4": 53,
"b5": 54.4,
"b6": 53.3
},
"sm": {
"ok": true,
"sm": "SM100 Solar Module",
"sm1": 34,
"sm2": 24,
"sm3": 60,
"sm4": "on",
"sm5": 2000,
"sm6": 3000,
"sm7": 123456
},
"hp": {
"ok": true,
"hm": "HeatPump Module",
"hp1": 66,
"hp2": 77
}
};
wss.broadcast(stats);
}
wss.on('connection', function connection(ws) {
ws.on("error", () => console.log("[WARN] WebSocket Error - Assume a client is disconnected."));
ws.on('message', function incoming(message) {
var obj = JSON.parse(message);
console.log("[INFO] Got Command: " + obj.command);
switch (obj.command) {
case "configfile":
console.log("[INFO] New system settings file received");
configfile = obj;
break;
case "custom_configfile":
console.log("[INFO] New custom config received");
custom_configfile = obj;
break;
case "status":
console.log("[INFO] Sending Fake Emulator Status");
sendStatus();
break;
case "custom_status":
console.log("[INFO] Sending custom status");
sendCustomStatus();
break;
case "scan":
console.log("[INFO] Sending Fake Wireless Networks");
wss.broadcast(networks);
break;
case "gettime":
console.log("[INFO] Sending time");
var res = {};
res.command = "gettime";
res.epoch = Math.floor((new Date).getTime() / 1000);
//res.epoch = 1567107755;
wss.broadcast(res);
break;
case "settime":
console.log("[INFO] Setting time (fake)");
var res = {};
res.command = "gettime";
res.epoch = Math.floor((new Date).getTime() / 1000);
wss.broadcast(res);
break;
case "getconf":
console.log("[INFO] Sending system configuration file (if set any)");
wss.broadcast(configfile);
break;
case "geteventlog":
console.log("[INFO] Sending eventlog");
sendEventLog();
break;
case "clearevent":
console.log("[INFO] Clearing eventlog");
break;
case "restart":
console.log("[INFO] Restart");
break;
case "destroy":
console.log("[INFO] Destroy");
break;
case "forcentp":
console.log("[INFO] getting ntp time");
break;
default:
console.log("[WARN] Unknown command ");
break;
}
});
});