1.9.0
47
.gitignore
vendored
@@ -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
|
||||
.gcc-flags.json
|
||||
.vscode
|
||||
.env
|
||||
.DS_Store
|
||||
platformio.ini
|
||||
lib/readme.txt
|
||||
.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
|
||||
*.bin
|
||||
|
||||
22
CHANGELOG.md
@@ -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/),
|
||||
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
|
||||
|
||||
- Added write support for RC3000
|
||||
- Handle Read and Write to EMS+ device logic changed, tested with RC3000
|
||||
|
||||
## [1.8.2] 2019-08-10
|
||||
|
||||
### 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
|
||||
## [1.8.1] 2019-07-27
|
||||
|
||||
### Added
|
||||
|
||||
|
||||
34
README.md
@@ -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.
|
||||
|
||||
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
|
||||
|
||||
|  |  |
|
||||
| -------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
|
||||
|
||||
#### MQTT support for Home Assistant and Domoticz
|
||||
|
||||

|
||||
|
||||
**Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) circuit:**
|
||||
|
||||
|  |  |  |
|
||||
| - | - | - |
|
||||
|
||||
**Example of the EMS-ESP's telnet console:**
|
||||
#### Telnet for advanced configuration and verbose traffic logging
|
||||
|
||||
|  |  |
|
||||
| - | - |
|
||||
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
|
||||
|
||||
---
|
||||
|
||||
## The latest list of support EMS devices
|
||||
## Current list of supported EMS devices
|
||||
|
||||
### Thermostats:
|
||||
|
||||
@@ -34,7 +36,7 @@ The code is writen for the Espressif **ESP8266** microcontroller and supports a
|
||||
* RC20F
|
||||
* RC30/Nefit Moduline 400
|
||||
* RC35 (only a single HC)
|
||||
* RC300/RC310/Nefit Moduline 3000
|
||||
* RC300/RC310/RC3000
|
||||
* Nefit Moduline 1010
|
||||
* Junkers FR10
|
||||
* 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
|
||||
* Web Gateway KM200
|
||||
* HeatPump Module
|
||||
|
||||
## Compatible with EMS Gateway
|
||||
|
||||
Using BBQKees' [EMS Gateway](https://shop.hotgoodies.nl/ems/) board with integrated Wemos D1:
|
||||
|
||||
|  |  |  |
|
||||
| ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
|
||||
|
||||
|
||||
@@ -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'])
|
||||
@@ -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()
|
||||
@@ -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
|
Before Width: | Height: | Size: 130 KiB After Width: | Height: | Size: 134 KiB |
|
Before Width: | Height: | Size: 90 KiB After Width: | Height: | Size: 100 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
BIN
doc/web/ems_dashboard.PNG
Normal file
|
After Width: | Height: | Size: 92 KiB |
BIN
doc/web/system_status.PNG
Normal file
|
After Width: | Height: | Size: 93 KiB |
2173
lib/MyESP/MyESP.cpp
@@ -8,40 +8,69 @@ default_envs = release
|
||||
|
||||
[common]
|
||||
; -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]
|
||||
;board = esp12e
|
||||
board = d1_mini
|
||||
; board = nodemcuv2
|
||||
; board = d1_mini_pro
|
||||
framework = arduino
|
||||
platform = espressif8266
|
||||
lib_deps =
|
||||
CRC32
|
||||
CircularBuffer
|
||||
OneWire
|
||||
JustWifi
|
||||
AsyncMqttClient
|
||||
ArduinoJson
|
||||
OneWire
|
||||
EEPROM_rotate
|
||||
ESP Async WebServer
|
||||
ESPAsyncTCP
|
||||
ESPAsyncUDP
|
||||
upload_speed = 921600
|
||||
monitor_speed = 115200
|
||||
|
||||
; example ports for OSX
|
||||
;upload_port = /dev/cu.wchusbserial14403
|
||||
;upload_port = /dev/cu.usbserial-1440
|
||||
|
||||
; uncomment next 2 lines for OTA
|
||||
;upload_protocol = espota
|
||||
;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]
|
||||
build_type = debug
|
||||
build_flags = ${common.extra_flags} -DCRASH
|
||||
extra_scripts = pre:scripts/rename_fw.py
|
||||
build_flags = ${common.general_flags}
|
||||
extra_scripts =
|
||||
pre:scripts/rename_fw.py
|
||||
pre:scripts/buildweb.py
|
||||
|
||||
[env:clean]
|
||||
extra_scripts = pre:scripts/clean_fw.py
|
||||
|
||||
[env:release]
|
||||
build_flags = ${common.extra_flags}
|
||||
build_flags = ${common.general_flags}
|
||||
extra_scripts = pre:scripts/rename_fw.py
|
||||
|
||||
[env:checkcode]
|
||||
build_flags = ${common.extra_flags}
|
||||
build_type = debug
|
||||
build_flags = ${common.general_flags} -Wall
|
||||
extra_scripts = scripts/checkcode.py
|
||||
|
||||
|
||||
3
scripts/buildweb.py
Normal file
@@ -0,0 +1,3 @@
|
||||
Import("env")
|
||||
|
||||
env.Execute("node ./tools/webfilesbuilder/node_modules/gulp/bin/gulp.js --cwd ./tools/webfilesbuilder")
|
||||
@@ -1,7 +1,13 @@
|
||||
#!/usr/bin/env python
|
||||
from subprocess import call
|
||||
import os
|
||||
import re
|
||||
Import("env")
|
||||
|
||||
def build_web(source, target, env):
|
||||
print("\n** Build web...")
|
||||
call(["gulp", "-f", os.getcwd()+"/tools/webfilesbuilder/gulpfile.js"])
|
||||
|
||||
bag = {}
|
||||
exprs = [
|
||||
(re.compile(r'^#define APP_VERSION\s+"(\S+)"'), 'app_version'),
|
||||
@@ -22,5 +28,9 @@ app_hostname = bag.get('app_hostname')
|
||||
board = env['BOARD']
|
||||
branch = env['PIOENV']
|
||||
|
||||
# build the web files
|
||||
env.AddPreAction("buildprog", build_web)
|
||||
|
||||
# build filename, replacing . with _ for the version
|
||||
env.Replace(PROGNAME="firmware_%s" % branch + "_" + app_version.replace(".", "_"))
|
||||
|
||||
|
||||
2826
src/MyESP.cpp
Normal file
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* MyESP.h
|
||||
*
|
||||
* Paul Derbyshire - December 2018
|
||||
* Paul Derbyshire - first version December 2018
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
@@ -9,15 +9,18 @@
|
||||
#ifndef MyESP_h
|
||||
#define MyESP_h
|
||||
|
||||
#define MYESP_VERSION "1.1.24"
|
||||
#define MYESP_VERSION "1.2.0"
|
||||
|
||||
#include <ArduinoJson.h>
|
||||
#include <ArduinoOTA.h>
|
||||
#include <AsyncMqttClient.h> // https://github.com/marvinroger/async-mqtt-client and for ESP32 see https://github.com/marvinroger/async-mqtt-client/issues/127
|
||||
#include <ESP8266WebServer.h>
|
||||
#include <ESPAsyncUDP.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
#include <FS.h>
|
||||
#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
|
||||
#include <EEPROM_Rotate.h>
|
||||
@@ -30,7 +33,6 @@ extern struct rst_info resetInfo;
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
//#include <ESPmDNS.h>
|
||||
#include <SPIFFS.h> // added for ESP32
|
||||
#define ets_vsnprintf vsnprintf // added for ESP32
|
||||
#define OTA_PORT 3232
|
||||
@@ -39,24 +41,54 @@ extern struct rst_info resetInfo;
|
||||
#define OTA_PORT 8266
|
||||
#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
|
||||
#define 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_CONNECT_TIMEOUT 10000 // Connecting timeout for WIFI in ms (10 seconds)
|
||||
#define MYESP_WIFI_RECONNECT_INTERVAL 600000 // If could not connect to WIFI, retry after this time in ms. 10 minutes
|
||||
|
||||
// MQTT
|
||||
#define MQTT_PORT 1883 // MQTT port
|
||||
#define MQTT_RECONNECT_DELAY_MIN 2000 // Try to reconnect in 3 seconds upon disconnection
|
||||
#define MQTT_RECONNECT_DELAY_STEP 3000 // Increase the reconnect delay in 3 seconds after each failed attempt
|
||||
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
|
||||
#define MQTT_MAX_TOPIC_SIZE 50 // max length of MQTT topic
|
||||
#define MQTT_TOPIC_START "start"
|
||||
#define MQTT_TOPIC_HEARTBEAT "heartbeat"
|
||||
#define MQTT_TOPIC_START_PAYLOAD "start"
|
||||
#define MQTT_TOPIC_RESTART "restart"
|
||||
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
|
||||
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
|
||||
#define MQTT_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
|
||||
#define MQTT_CONNECT_EVENT 0
|
||||
@@ -94,18 +126,20 @@ extern struct rst_info resetInfo;
|
||||
|
||||
// reset reason codes
|
||||
PROGMEM const char custom_reset_hardware[] = "Hardware button";
|
||||
PROGMEM const char custom_reset_terminal[] = "Reboot from terminal";
|
||||
PROGMEM const char custom_reset_mqtt[] = "Reboot from MQTT";
|
||||
PROGMEM const char custom_reset_ota[] = "Reboot 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_terminal[] = "Restart from terminal";
|
||||
PROGMEM const char custom_reset_mqtt[] = "Restart from MQTT";
|
||||
PROGMEM const char custom_reset_ota[] = "Restart after successful OTA update";
|
||||
PROGMEM const char custom_reset_factory[] = "Factory reset";
|
||||
PROGMEM const char * const custom_reset_string[] = {custom_reset_hardware, custom_reset_terminal, custom_reset_mqtt, custom_reset_ota, custom_reset_factory};
|
||||
#define CUSTOM_RESET_HARDWARE 1 // Reset from hardware button
|
||||
#define CUSTOM_RESET_TERMINAL 2 // Reset from terminal
|
||||
#define CUSTOM_RESET_MQTT 3 // Reset via MQTT
|
||||
#define CUSTOM_RESET_OTA 4 // Reset after successful OTA update
|
||||
#define CUSTOM_RESET_MAX 4
|
||||
#define CUSTOM_RESET_FACTORY 5 // Factory reset
|
||||
#define CUSTOM_RESET_MAX 5
|
||||
|
||||
// SPIFFS
|
||||
#define SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/
|
||||
#define MYESP_SPIFFS_MAXSIZE 800 // https://arduinojson.org/v6/assistant/
|
||||
|
||||
// CRASH
|
||||
/**
|
||||
@@ -157,9 +191,9 @@ struct RtcmemData {
|
||||
|
||||
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 SYSTEM_CHECK_MAX 5 // After this many crashes on boot
|
||||
#define HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins)
|
||||
#define MYESP_SYSTEM_CHECK_TIME 60000 // The system is considered stable after these many millis (1 minute)
|
||||
#define MYESP_SYSTEM_CHECK_MAX 10 // After this many crashes on boot
|
||||
#define MYESP_HEARTBEAT_INTERVAL 120000 // in milliseconds, how often the MQTT heartbeat is sent (2 mins)
|
||||
|
||||
typedef struct {
|
||||
bool set; // is it a set command
|
||||
@@ -176,14 +210,21 @@ typedef enum {
|
||||
MYESP_BOOTSTATUS_RESETNEEDED = 3
|
||||
} 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()> wifi_callback_f;
|
||||
typedef std::function<void()> ota_callback_f;
|
||||
typedef std::function<void(uint8_t, const char *)> telnetcommand_callback_f;
|
||||
typedef std::function<void(uint8_t)> telnet_callback_f;
|
||||
typedef std::function<bool(MYESP_FSACTION, const JsonObject json)> fs_callback_f;
|
||||
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_settings_callback_f;
|
||||
typedef std::function<void(char *)> web_callback_f;
|
||||
typedef std::function<bool(MYESP_FSACTION, JsonObject json)> fs_loadsave_callback_f;
|
||||
typedef std::function<bool(MYESP_FSACTION, uint8_t, const char *, const char *)> fs_setlist_callback_f;
|
||||
typedef std::function<void(JsonObject root)> web_callback_f;
|
||||
|
||||
// calculates size of an 2d array at compile time
|
||||
template <typename T, size_t N>
|
||||
@@ -191,58 +232,34 @@ constexpr size_t ArraySize(T (&)[N]) {
|
||||
return N;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void PROGMEM_readAnything(const T * sce, T & dest) {
|
||||
memcpy_P(&dest, sce, sizeof(T));
|
||||
}
|
||||
|
||||
#define UPTIME_OVERFLOW 4294967295 // Uptime overflow value
|
||||
#define MYESP_UPTIME_OVERFLOW 4294967295 // Uptime overflow value
|
||||
|
||||
// web min and max length of wifi ssid and password
|
||||
#define MAX_SSID_LEN 32
|
||||
#define MAX_PWD_LEN 64
|
||||
#define MYESP_MAX_STR_LEN 16
|
||||
|
||||
#define MYESP_BOOTUP_FLASHDELAY 50 // flash duration for LED at bootup sequence
|
||||
#define MYESP_BOOTUP_DELAY 2000 // time before we open the window to reset. This is to stop resetting values when uploading firmware via USB
|
||||
|
||||
// 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 MyESP {
|
||||
protected:
|
||||
// webserver
|
||||
AsyncWebServer * _webServer;
|
||||
AsyncWebSocket * _ws;
|
||||
|
||||
// NTP
|
||||
NtpClient NTP;
|
||||
|
||||
public:
|
||||
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
|
||||
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 isAPmode();
|
||||
|
||||
@@ -251,17 +268,7 @@ class MyESP {
|
||||
void mqttSubscribe(const char * topic);
|
||||
void mqttUnsubscribe(const char * topic);
|
||||
void mqttPublish(const char * topic, const char * payload);
|
||||
void setMQTT(const char * mqtt_host,
|
||||
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);
|
||||
void setMQTT(mqtt_callback_f callback);
|
||||
|
||||
// OTA
|
||||
void setOTA(ota_callback_f OTACallback_pre, ota_callback_f OTACallback_post);
|
||||
@@ -274,8 +281,9 @@ class MyESP {
|
||||
void setUseSerial(bool toggle);
|
||||
|
||||
// FS
|
||||
void setSettings(fs_callback_f callback, fs_settings_callback_f fs_settings_callback);
|
||||
bool fs_saveConfig();
|
||||
void setSettings(fs_loadsave_callback_f loadsave, fs_setlist_callback_f setlist, bool useSerial = true);
|
||||
bool fs_saveConfig(JsonObject root);
|
||||
bool fs_saveCustomConfig(JsonObject root);
|
||||
|
||||
// Web
|
||||
void setWeb(web_callback_f callback_web);
|
||||
@@ -289,8 +297,7 @@ class MyESP {
|
||||
// general
|
||||
void end();
|
||||
void loop();
|
||||
void begin(const char * app_hostname, const char * app_name, const char * app_version);
|
||||
void setBoottime(const char * boottime);
|
||||
void begin(const char * app_hostname, const char * app_name, const char * app_version, const char * app_url, const char * app_updateurl);
|
||||
void resetESP();
|
||||
int getWifiQuality();
|
||||
void showSystemStats();
|
||||
@@ -298,48 +305,56 @@ class MyESP {
|
||||
uint32_t getSystemLoadAverage();
|
||||
uint32_t getSystemResetReason();
|
||||
uint8_t getSystemBootStatus();
|
||||
bool _have_ntp_time;
|
||||
|
||||
private:
|
||||
// mqtt
|
||||
AsyncMqttClient mqttClient;
|
||||
unsigned long _mqtt_reconnect_delay;
|
||||
void _mqttOnMessage(char * topic, char * payload, size_t len);
|
||||
void _mqttConnect();
|
||||
void _mqtt_setup();
|
||||
mqtt_callback_f _mqtt_callback;
|
||||
void _mqttOnConnect();
|
||||
void _sendStart();
|
||||
char * _mqttTopic(const char * topic);
|
||||
char * _mqtt_host;
|
||||
char * _mqtt_username;
|
||||
|
||||
// mqtt log
|
||||
_MQTT_Log MQTT_log[MYESP_MQTTLOG_MAX]; // log for publish messages
|
||||
void _printMQTTLog();
|
||||
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;
|
||||
int _mqtt_port;
|
||||
char * _mqtt_base;
|
||||
unsigned long _mqtt_keepalive;
|
||||
unsigned char _mqtt_qos;
|
||||
bool _mqtt_enabled;
|
||||
uint32_t _mqtt_keepalive;
|
||||
uint8_t _mqtt_qos;
|
||||
bool _mqtt_retain;
|
||||
char * _mqtt_will_topic;
|
||||
char * _mqtt_will_online_payload;
|
||||
char * _mqtt_will_offline_payload;
|
||||
char * _mqtt_topic;
|
||||
unsigned long _mqtt_last_connection;
|
||||
uint32_t _mqtt_last_connection;
|
||||
bool _mqtt_connecting;
|
||||
bool _rtcmem_status;
|
||||
bool _mqtt_heartbeat;
|
||||
|
||||
// wifi
|
||||
void _wifiCallback(justwifi_messages_t code, char * parameter);
|
||||
void _wifi_setup();
|
||||
wifi_callback_f _wifi_callback;
|
||||
char * _wifi_ssid;
|
||||
char * _wifi_password;
|
||||
wifi_callback_f _wifi_callback_f;
|
||||
char * _network_ssid;
|
||||
char * _network_password;
|
||||
uint8_t _network_wmode;
|
||||
bool _wifi_connected;
|
||||
String _getESPhostname();
|
||||
|
||||
// ota
|
||||
ota_callback_f _ota_pre_callback;
|
||||
ota_callback_f _ota_post_callback;
|
||||
ota_callback_f _ota_pre_callback_f;
|
||||
ota_callback_f _ota_post_callback_f;
|
||||
void _ota_setup();
|
||||
void _OTACallback();
|
||||
bool _ota_doing_update;
|
||||
|
||||
// crash
|
||||
void _eeprom_setup();
|
||||
@@ -354,37 +369,42 @@ class MyESP {
|
||||
void _telnet_setup();
|
||||
char _command[TELNET_MAX_COMMAND_LENGTH]; // the input command from either Serial or Telnet
|
||||
void _consoleShowHelp();
|
||||
telnetcommand_callback_f _telnetcommand_callback; // Callable for projects commands
|
||||
telnet_callback_f _telnet_callback; // callback for connect/disconnect
|
||||
telnetcommand_callback_f _telnetcommand_callback_f; // Callable for projects commands
|
||||
telnet_callback_f _telnet_callback_f; // callback for connect/disconnect
|
||||
bool _changeSetting(uint8_t wc, const char * setting, const char * value);
|
||||
|
||||
// fs
|
||||
// fs and settings
|
||||
void _fs_setup();
|
||||
bool _fs_loadConfig();
|
||||
void _fs_printConfig();
|
||||
bool _fs_loadCustomConfig();
|
||||
void _fs_printFile(const char * file);
|
||||
void _fs_eraseConfig();
|
||||
bool _fs_writeConfig();
|
||||
bool _fs_createCustomConfig();
|
||||
bool _fs_sendConfig();
|
||||
fs_loadsave_callback_f _fs_loadsave_callback_f;
|
||||
fs_setlist_callback_f _fs_setlist_callback_f;
|
||||
|
||||
// settings
|
||||
fs_callback_f _fs_callback;
|
||||
fs_settings_callback_f _fs_settings_callback;
|
||||
void _printSetCommands();
|
||||
|
||||
// web
|
||||
web_callback_f _web_callback;
|
||||
|
||||
// general
|
||||
char * _app_hostname;
|
||||
char * _general_hostname;
|
||||
char * _app_name;
|
||||
char * _app_version;
|
||||
char * _boottime;
|
||||
char * _app_url;
|
||||
char * _app_updateurl;
|
||||
bool _suspendOutput;
|
||||
bool _serial;
|
||||
bool _heartbeat;
|
||||
bool _general_serial;
|
||||
unsigned long _getUptime();
|
||||
String _buildTime();
|
||||
bool _firstInstall;
|
||||
char * _getBuildTime();
|
||||
char * _buildTime;
|
||||
bool _timerequest;
|
||||
bool _formatreq;
|
||||
bool _hasValue(char * s);
|
||||
void _printHeap(const char * s);
|
||||
|
||||
// reset reason and rtcmem
|
||||
bool _rtcmem_status;
|
||||
bool _rtcmemStatus();
|
||||
bool _getRtcmemStatus();
|
||||
|
||||
@@ -418,11 +438,29 @@ class MyESP {
|
||||
// heartbeat
|
||||
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 _webRootPage();
|
||||
void _webResetPage();
|
||||
void _webResetAllPage();
|
||||
|
||||
// ntp
|
||||
char * _ntp_server;
|
||||
uint8_t _ntp_interval;
|
||||
bool _ntp_enabled;
|
||||
};
|
||||
|
||||
extern MyESP myESP;
|
||||
46
src/Ntp.cpp
Normal 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
@@ -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
|
||||
@@ -303,7 +303,7 @@ int TelnetSpy::available(void) {
|
||||
}
|
||||
|
||||
int TelnetSpy::read(void) {
|
||||
int val;
|
||||
int val = 0;
|
||||
if (usedSer) {
|
||||
val = usedSer->read();
|
||||
if (val != -1) {
|
||||
@@ -319,7 +319,7 @@ int TelnetSpy::read(void) {
|
||||
}
|
||||
|
||||
int TelnetSpy::peek(void) {
|
||||
int val;
|
||||
int val = 0;
|
||||
if (usedSer) {
|
||||
val = usedSer->peek();
|
||||
if (val != -1) {
|
||||
143
src/TimeLib.cpp
Normal 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
@@ -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
@@ -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>: <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>: <span id="tm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Setpoint Temperature:</th>
|
||||
<td id="ts"></td>
|
||||
<th>Current Temperature:</th>
|
||||
<td id="tc"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Mode:</th>
|
||||
<td colspan="3" id="tmode"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-warning table-responsive" id="sm_show">
|
||||
<div class="panel-heading"><b>Solar Module</b>: <span id="sm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Colector Temperature:</th>
|
||||
<td id="sm1"></td>
|
||||
<th>Bottom Temperature:</th>
|
||||
<td id="sm2"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Pump Modulation:</th>
|
||||
<td id="sm3"></td>
|
||||
<th>Pump Active:</th>
|
||||
<td id="sm4"></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Energy Last Hour:</th>
|
||||
<td id="sm5"></td>
|
||||
<th>Energy Today:</th>
|
||||
<td id="sm6"></td>
|
||||
<th>Energy Total:</th>
|
||||
<td id="sm7"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="panel panel-success table-responsive" id="hp_show">
|
||||
<div class="panel-heading"><b>Heat Pump</b>: <span id="hm"></span></div>
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<tr>
|
||||
<th>Pump Modulation:</th>
|
||||
<td id="hp1"></td>
|
||||
<th>Pump Speed:</th>
|
||||
<td id="hp2"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row form-group" style="text-align: center;">
|
||||
<button onclick="refreshEMS()" class="btn btn-info">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
169
src/custom.js
Normal 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 + " ℃";
|
||||
document.getElementById("b4").innerHTML = ajaxobj.boiler.b4 + " ℃";
|
||||
document.getElementById("b5").innerHTML = ajaxobj.boiler.b5 + " ℃";
|
||||
document.getElementById("b6").innerHTML = ajaxobj.boiler.b6 + " ℃";
|
||||
} else {
|
||||
document.getElementById("boiler_show").style.display = "none";
|
||||
}
|
||||
|
||||
if (ajaxobj.thermostat.ok) {
|
||||
document.getElementById("thermostat_show").style.display = "block";
|
||||
|
||||
document.getElementById("tm").innerHTML = ajaxobj.thermostat.tm;
|
||||
document.getElementById("ts").innerHTML = ajaxobj.thermostat.ts + " ℃";
|
||||
document.getElementById("tc").innerHTML = ajaxobj.thermostat.tc + " ℃";
|
||||
document.getElementById("tmode").innerHTML = ajaxobj.thermostat.tmode;
|
||||
} else {
|
||||
document.getElementById("thermostat_show").style.display = "none";
|
||||
}
|
||||
|
||||
if (ajaxobj.sm.ok) {
|
||||
document.getElementById("sm_show").style.display = "block";
|
||||
|
||||
document.getElementById("sm").innerHTML = ajaxobj.sm.sm;
|
||||
document.getElementById("sm1").innerHTML = ajaxobj.sm.sm1 + " ℃";
|
||||
document.getElementById("sm2").innerHTML = ajaxobj.sm.sm2 + " ℃";
|
||||
document.getElementById("sm3").innerHTML = ajaxobj.sm.sm3 + " %";
|
||||
document.getElementById("sm4").innerHTML = ajaxobj.sm.sm4;
|
||||
document.getElementById("sm5").innerHTML = ajaxobj.sm.sm5 + " Wh";
|
||||
document.getElementById("sm6").innerHTML = ajaxobj.sm.sm6 + " Wh";
|
||||
document.getElementById("sm7").innerHTML = ajaxobj.sm.sm7 + " KWh";
|
||||
} else {
|
||||
document.getElementById("sm_show").style.display = "none";
|
||||
}
|
||||
|
||||
if (ajaxobj.hp.ok) {
|
||||
document.getElementById("hp_show").style.display = "block";
|
||||
|
||||
document.getElementById("hm").innerHTML = ajaxobj.hp.hm;
|
||||
document.getElementById("hp1").innerHTML = ajaxobj.hp.hp1 + " %";
|
||||
document.getElementById("hp2").innerHTML = ajaxobj.hp.hp2 + " %";
|
||||
} else {
|
||||
document.getElementById("hp_show").style.display = "none";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
435
src/ems-esp.cpp
@@ -4,11 +4,11 @@
|
||||
* Paul Derbyshire - https://github.com/proddy/EMS-ESP
|
||||
*
|
||||
* See ChangeLog.md for history
|
||||
* See README.md for Acknowledgments
|
||||
* See wiki at https://github.com/proddy/EMS-ESP/Wiki for Acknowledgments
|
||||
*/
|
||||
|
||||
// local libraries
|
||||
#include "ds18.h"
|
||||
#include "MyESP.h"
|
||||
#include "ems.h"
|
||||
#include "ems_devices.h"
|
||||
#include "emsuart.h"
|
||||
@@ -16,11 +16,9 @@
|
||||
#include "version.h"
|
||||
|
||||
// Dallas external temp sensors
|
||||
#include "ds18.h"
|
||||
DS18 ds18;
|
||||
|
||||
// shared libraries
|
||||
#include <MyESP.h>
|
||||
|
||||
// public libraries
|
||||
#include <ArduinoJson.h> // https://github.com/bblanchon/ArduinoJson
|
||||
#include <CRC32.h> // https://github.com/bakercp/CRC32
|
||||
@@ -28,11 +26,18 @@ DS18 ds18;
|
||||
// standard arduino libs
|
||||
#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_P(...) myESP.myDebug_P(__VA_ARGS__)
|
||||
|
||||
// set to value >0 if the ESP is overheating or there are timing issues. Recommend a value of 1.
|
||||
#define EMSESP_DELAY 1 // initially set to 0 for no delay
|
||||
#define EMSESP_DELAY 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
|
||||
|
||||
@@ -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_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 {
|
||||
uint32_t timestamp; // for internal timings, via millis()
|
||||
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
|
||||
bool dallas_parasite; // on/off is using parasite
|
||||
uint8_t heating_circuit; // number of heating circuit, 1 or 2
|
||||
uint8_t tx_mode; // TX mode 1,2 or 3
|
||||
} _EMSESP_Status;
|
||||
|
||||
typedef struct {
|
||||
@@ -98,15 +119,13 @@ static const command_t project_cmds[] PROGMEM = {
|
||||
{true, "led <on | off>", "toggle status LED on/off"},
|
||||
{true, "led_gpio <gpio>", "set the LED pin. Default is the onboard LED 2. For external D1 use 5"},
|
||||
{true, "dallas_gpio <gpio>", "set the external Dallas temperature sensors pin. Default is 14 for D5"},
|
||||
{true, "dallas_parasite <on | off>", "set to on if powering Dallas sesnsors via parasite power"},
|
||||
{true, "thermostat_type <device ID>", "set the thermostat type ID (e.g. 10 for 0x10)"},
|
||||
{true, "boiler_type <device ID>", "set the boiler type ID (e.g. 8 for 0x08)"},
|
||||
{true, "dallas_parasite <on | off>", "set to on if powering Dallas sensors via parasite power"},
|
||||
{true, "listen_mode <on | off>", "when set to on all automatic Tx are disabled"},
|
||||
{true, "shower_timer <on | off>", "send MQTT notification on all shower durations"},
|
||||
{true, "shower_alert <on | off>", "stop hot water to send 3 cold burst warnings after max shower time is exceeded"},
|
||||
{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, "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, "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"));
|
||||
} else if (sysLog == EMS_SYS_LOGGING_SOLARMODULE) {
|
||||
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 {
|
||||
myDebug_P(PSTR(" System logging set to None"));
|
||||
}
|
||||
@@ -509,7 +530,7 @@ void showInfo() {
|
||||
_renderIntValue("Burner current power", "%", EMS_Boiler.curBurnPow);
|
||||
_renderShortValue("Flame current", "uA", EMS_Boiler.flameCurr);
|
||||
_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);
|
||||
} else {
|
||||
myDebug_P(PSTR(" System service code: %s (%d)"), EMS_Boiler.serviceCodeChar, EMS_Boiler.serviceCode);
|
||||
@@ -550,7 +571,7 @@ void showInfo() {
|
||||
if (ems_getSolarModuleEnabled()) {
|
||||
myDebug_P(PSTR("")); // newline
|
||||
myDebug_P(PSTR("%sSolar Module stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug_P(PSTR(" Solar Module: %s"), ems_getSolarModuleDescription(buffer_type));
|
||||
myDebug_P(PSTR(" Solar module: %s"), ems_getSolarModuleDescription(buffer_type));
|
||||
_renderShortValue("Collector temperature", "C", EMS_SolarModule.collectorTemp);
|
||||
_renderShortValue("Bottom temperature", "C", EMS_SolarModule.bottomTemp);
|
||||
_renderIntValue("Pump modulation", "%", EMS_SolarModule.pumpModulation);
|
||||
@@ -561,16 +582,16 @@ void showInfo() {
|
||||
(EMS_SolarModule.pumpWorkMin % 1440) / 60,
|
||||
EMS_SolarModule.pumpWorkMin % 60);
|
||||
}
|
||||
_renderUShortValue("Energy Last Hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10
|
||||
_renderUShortValue("Energy Today", "Wh", EMS_SolarModule.EnergyToday, 0);
|
||||
_renderUShortValue("Energy Total", "kWH", EMS_SolarModule.EnergyTotal, 1); // *10
|
||||
_renderUShortValue("Energy last hour", "Wh", EMS_SolarModule.EnergyLastHour, 1); // *10
|
||||
_renderUShortValue("Energy today", "Wh", EMS_SolarModule.EnergyToday, 0);
|
||||
_renderUShortValue("Energy total", "kWh", EMS_SolarModule.EnergyTotal, 1); // *10
|
||||
}
|
||||
|
||||
// For HeatPumps
|
||||
if (ems_getHeatPumpEnabled()) {
|
||||
myDebug_P(PSTR("")); // newline
|
||||
myDebug_P(PSTR("%sHeat Pump stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug_P(PSTR(" Solar Module: %s"), ems_getHeatPumpDescription(buffer_type));
|
||||
myDebug_P(PSTR(" Heat Pump module: %s"), ems_getHeatPumpDescription(buffer_type));
|
||||
_renderIntValue("Pump modulation", "%", EMS_HeatPump.HPModulation);
|
||||
_renderIntValue("Pump speed", "%", EMS_HeatPump.HPSpeed);
|
||||
}
|
||||
@@ -579,7 +600,7 @@ void showInfo() {
|
||||
if (ems_getThermostatEnabled()) {
|
||||
myDebug_P(PSTR("")); // newline
|
||||
myDebug_P(PSTR("%sThermostat stats:%s"), COLOR_BOLD_ON, COLOR_BOLD_OFF);
|
||||
myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type));
|
||||
myDebug_P(PSTR(" Thermostat: %s"), ems_getThermostatDescription(buffer_type, false));
|
||||
|
||||
// Render Current & Setpoint Room Temperature
|
||||
if (ems_getThermostatModel() == EMS_MODEL_EASY) {
|
||||
@@ -684,23 +705,22 @@ void publishSensorValues() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// send values via MQTT
|
||||
// a json object is created for the boiler and one for the thermostat
|
||||
// CRC check is done to see if there are changes in the values since the last send to avoid too much wifi traffic
|
||||
// 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) {
|
||||
// don't send if MQTT is connected
|
||||
// don't send if MQTT is not connected
|
||||
if (!myESP.isMQTTConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
char s[20] = {0}; // for formatting strings
|
||||
StaticJsonDocument<MQTT_MAX_SIZE> doc;
|
||||
char data[MQTT_MAX_SIZE] = {0};
|
||||
StaticJsonDocument<MQTT_MAX_PAYLOAD_SIZE> doc;
|
||||
char data[MQTT_MAX_PAYLOAD_SIZE] = {0};
|
||||
CRC32 crc;
|
||||
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 uint32_t previousBoilerPublishCRC = 0; // CRC check for boiler values
|
||||
@@ -772,13 +792,18 @@ void publishValues(bool force) {
|
||||
if (abs(EMS_Boiler.heatWorkMin) != EMS_VALUE_LONG_NOTSET)
|
||||
rootBoiler["heatWorkMin"] = (double)EMS_Boiler.heatWorkMin;
|
||||
|
||||
if (EMS_Boiler.serviceCode != EMS_VALUE_USHORT_NOTSET) {
|
||||
rootBoiler["ServiceCode"] = EMS_Boiler.serviceCodeChar;
|
||||
rootBoiler["ServiceCodeNumber"] = EMS_Boiler.serviceCode;
|
||||
}
|
||||
|
||||
serializeJson(doc, data, sizeof(data));
|
||||
|
||||
// check for empty json
|
||||
jsonSize = measureJson(doc);
|
||||
if (jsonSize > 2) {
|
||||
// calculate hash and send values if something has changed, to save unnecessary wifi traffic
|
||||
for (size_t i = 0; i < measureJson(doc) - 1; i++) {
|
||||
for (uint8_t i = 0; i < (jsonSize - 1); i++) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
fchecksum = crc.finalize();
|
||||
@@ -789,6 +814,7 @@ void publishValues(bool force) {
|
||||
// send values via MQTT
|
||||
myESP.mqttPublish(TOPIC_BOILER_DATA, data);
|
||||
}
|
||||
}
|
||||
|
||||
// see if the heating or hot tap water has changed, if so send
|
||||
// last_boilerActive stores heating in bit 1 and tap water in bit 2
|
||||
@@ -859,9 +885,12 @@ void publishValues(bool force) {
|
||||
data[0] = '\0'; // reset data for next package
|
||||
serializeJson(doc, data, sizeof(data));
|
||||
|
||||
// check for empty json
|
||||
jsonSize = measureJson(doc);
|
||||
if (jsonSize > 2) {
|
||||
// calculate new CRC
|
||||
crc.reset();
|
||||
for (size_t i = 0; i < measureJson(doc) - 1; i++) {
|
||||
for (uint8_t i = 0; i < (jsonSize - 1); i++) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
fchecksum = crc.finalize();
|
||||
@@ -873,6 +902,7 @@ void publishValues(bool force) {
|
||||
myESP.mqttPublish(TOPIC_THERMOSTAT_DATA, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For SM10 and SM100 Solar Modules
|
||||
if (ems_getSolarModuleEnabled()) {
|
||||
@@ -909,9 +939,12 @@ void publishValues(bool force) {
|
||||
data[0] = '\0'; // reset data for next package
|
||||
serializeJson(doc, data, sizeof(data));
|
||||
|
||||
// check for empty json
|
||||
jsonSize = measureJson(doc);
|
||||
if (jsonSize > 2) {
|
||||
// calculate new CRC
|
||||
crc.reset();
|
||||
for (size_t i = 0; i < measureJson(doc) - 1; i++) {
|
||||
for (uint8_t i = 0; i < (jsonSize - 1); i++) {
|
||||
crc.update(data[i]);
|
||||
}
|
||||
fchecksum = crc.finalize();
|
||||
@@ -923,6 +956,7 @@ void publishValues(bool force) {
|
||||
myESP.mqttPublish(TOPIC_SM_DATA, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// handle HeatPump
|
||||
if (ems_getHeatPumpEnabled()) {
|
||||
@@ -1045,8 +1079,6 @@ void do_regularUpdates() {
|
||||
ems_getThermostatValues();
|
||||
ems_getBoilerValues();
|
||||
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)
|
||||
bool FSCallback(MYESP_FSACTION action, const JsonObject json) {
|
||||
bool LoadSaveCallback(MYESP_FSACTION action, JsonObject json) {
|
||||
if (action == MYESP_FSACTION_LOAD) {
|
||||
EMSESP_Status.led = json["led"];
|
||||
EMSESP_Status.led_gpio = json["led_gpio"] | EMSESP_LED_GPIO;
|
||||
EMSESP_Status.dallas_gpio = json["dallas_gpio"] | EMSESP_DALLAS_GPIO;
|
||||
EMSESP_Status.dallas_parasite = json["dallas_parasite"] | EMSESP_DALLAS_PARASITE;
|
||||
const JsonObject & settings = json["settings"];
|
||||
|
||||
EMS_Thermostat.device_id = json["thermostat_type"] | EMSESP_THERMOSTAT_TYPE;
|
||||
EMS_Boiler.device_id = json["boiler_type"] | EMSESP_BOILER_TYPE;
|
||||
EMSESP_Status.led = settings["led"];
|
||||
EMSESP_Status.led_gpio = settings["led_gpio"] | EMSESP_LED_GPIO;
|
||||
EMSESP_Status.dallas_gpio = settings["dallas_gpio"] | EMSESP_DALLAS_GPIO;
|
||||
EMSESP_Status.dallas_parasite = settings["dallas_parasite"] | EMSESP_DALLAS_PARASITE;
|
||||
EMSESP_Status.shower_timer = settings["shower_timer"];
|
||||
EMSESP_Status.shower_alert = settings["shower_alert"];
|
||||
EMSESP_Status.publish_time = settings["publish_time"] | DEFAULT_PUBLISHTIME;
|
||||
|
||||
EMSESP_Status.shower_timer = json["shower_timer"];
|
||||
EMSESP_Status.shower_alert = json["shower_alert"];
|
||||
EMSESP_Status.publish_time = json["publish_time"] | DEFAULT_PUBLISHTIME;
|
||||
|
||||
ems_setTxMode(json["tx_mode"]);
|
||||
|
||||
EMSESP_Status.listen_mode = json["listen_mode"];
|
||||
EMSESP_Status.listen_mode = settings["listen_mode"];
|
||||
ems_setTxDisabled(EMSESP_Status.listen_mode);
|
||||
|
||||
EMSESP_Status.heating_circuit = json["heating_circuit"] | DEFAULT_HEATINGCIRCUIT;
|
||||
EMSESP_Status.heating_circuit = settings["heating_circuit"] | DEFAULT_HEATINGCIRCUIT;
|
||||
ems_setThermostatHC(EMSESP_Status.heating_circuit);
|
||||
|
||||
return true; // return false if some settings are missing and we need to rebuild the file
|
||||
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) {
|
||||
json["thermostat_type"] = EMS_Thermostat.device_id;
|
||||
json["boiler_type"] = EMS_Boiler.device_id;
|
||||
json["led"] = EMSESP_Status.led;
|
||||
json["led_gpio"] = EMSESP_Status.led_gpio;
|
||||
json["dallas_gpio"] = EMSESP_Status.dallas_gpio;
|
||||
json["dallas_parasite"] = EMSESP_Status.dallas_parasite;
|
||||
json["listen_mode"] = EMSESP_Status.listen_mode;
|
||||
json["shower_timer"] = EMSESP_Status.shower_timer;
|
||||
json["shower_alert"] = EMSESP_Status.shower_alert;
|
||||
json["publish_time"] = EMSESP_Status.publish_time;
|
||||
json["heating_circuit"] = EMSESP_Status.heating_circuit;
|
||||
json["tx_mode"] = ems_getTxMode();
|
||||
JsonObject settings = json.createNestedObject("settings");
|
||||
|
||||
settings["led"] = EMSESP_Status.led;
|
||||
settings["led_gpio"] = EMSESP_Status.led_gpio;
|
||||
settings["dallas_gpio"] = EMSESP_Status.dallas_gpio;
|
||||
settings["dallas_parasite"] = EMSESP_Status.dallas_parasite;
|
||||
settings["listen_mode"] = EMSESP_Status.listen_mode;
|
||||
settings["shower_timer"] = EMSESP_Status.shower_timer;
|
||||
settings["shower_alert"] = EMSESP_Status.shower_alert;
|
||||
settings["publish_time"] = EMSESP_Status.publish_time;
|
||||
settings["heating_circuit"] = EMSESP_Status.heating_circuit;
|
||||
settings["tx_mode"] = EMSESP_Status.tx_mode;
|
||||
|
||||
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
|
||||
// wc is number of arguments after the 'set' command
|
||||
// returns true if the setting was recognized and changed and should be saved back to SPIFFs
|
||||
bool SettingsCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) {
|
||||
bool SetListCallback(MYESP_FSACTION action, uint8_t wc, const char * setting, const char * value) {
|
||||
bool ok = false;
|
||||
|
||||
if (action == MYESP_FSACTION_SET) {
|
||||
@@ -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
|
||||
if ((strcmp(setting, "shower_timer") == 0) && (wc == 2)) {
|
||||
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
|
||||
if (((strcmp(setting, "tx_mode") == 0) || (strcmp(setting, "tx_delay") == 0)) && (wc == 2)) {
|
||||
ems_setTxMode(atoi(value));
|
||||
// tx_mode
|
||||
if ((strcmp(setting, "tx_mode") == 0) && (wc == 2)) {
|
||||
uint8_t mode = atoi(value);
|
||||
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(" dallas_gpio=%d"), EMSESP_Status.dallas_gpio);
|
||||
myDebug_P(PSTR(" dallas_parasite=%s"), EMSESP_Status.dallas_parasite ? "on" : "off");
|
||||
|
||||
if (EMS_Thermostat.device_id == EMS_ID_NONE) {
|
||||
myDebug_P(PSTR(" thermostat_type=<not set>"));
|
||||
} else {
|
||||
myDebug_P(PSTR(" thermostat_type=%02X"), EMS_Thermostat.device_id);
|
||||
}
|
||||
|
||||
myDebug_P(PSTR(" heating_circuit=%d"), EMSESP_Status.heating_circuit);
|
||||
|
||||
if (EMS_Boiler.device_id == EMS_ID_NONE) {
|
||||
myDebug_P(PSTR(" boiler_type=<not set>"));
|
||||
} else {
|
||||
myDebug_P(PSTR(" boiler_type=%02X"), EMS_Boiler.device_id);
|
||||
}
|
||||
|
||||
myDebug_P(PSTR(" tx_mode=%d"), EMSESP_Status.tx_mode);
|
||||
myDebug_P(PSTR(" listen_mode=%s"), EMSESP_Status.listen_mode ? "on" : "off");
|
||||
myDebug_P(PSTR(" shower_timer=%s"), EMSESP_Status.shower_timer ? "on" : "off");
|
||||
myDebug_P(PSTR(" shower_alert=%s"), EMSESP_Status.shower_alert ? "on" : "off");
|
||||
myDebug_P(PSTR(" publish_time=%d"), EMSESP_Status.publish_time);
|
||||
myDebug_P(PSTR(" tx_mode=%d"), ems_getTxMode());
|
||||
}
|
||||
|
||||
return ok;
|
||||
@@ -1469,6 +1480,9 @@ void TelnetCommandCallback(uint8_t wc, const char * commandLine) {
|
||||
} else if (strcmp(second_cmd, "n") == 0) {
|
||||
ems_setLogging(EMS_SYS_LOGGING_NONE);
|
||||
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) {
|
||||
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_TEMP);
|
||||
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_DAYTEMP);
|
||||
myESP.mqttSubscribe(TOPIC_THERMOSTAT_CMD_NIGHTTEMP);
|
||||
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
|
||||
myESP.mqttPublish(TOPIC_SHOWER_TIMER, EMSESP_Status.shower_timer ? "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
|
||||
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)) {
|
||||
ems_setWarmWaterActivated(true);
|
||||
} 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
|
||||
void WIFICallback() {
|
||||
// This is where we enable the UART service to scan the incoming serial Tx/Rx bus signals
|
||||
// This is done after we have a WiFi signal to avoid any resource conflicts
|
||||
system_uart_swap(); // TODO check
|
||||
// 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()) {
|
||||
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 {
|
||||
emsuart_init();
|
||||
myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
|
||||
if (!EMSESP_Status.listen_mode) {
|
||||
// go and find the boiler and thermostat types, if not in listen mode
|
||||
ems_discoverModels();
|
||||
if (ems_getBusConnected()) {
|
||||
if (ems_getTxDisabled()) {
|
||||
emsbus["ok"] = false;
|
||||
emsbus["msg"] = "EMS Bus Connected with Rx active but Tx has been disabled (in listen only mode).";
|
||||
} 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
|
||||
@@ -1864,25 +1997,14 @@ void setup() {
|
||||
|
||||
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.setWIFI(NULL, NULL, WIFICallback); // empty ssid and password as we take this from the config file
|
||||
|
||||
// MQTT host, username and password taken from the SPIFFS settings
|
||||
myESP.setMQTT(
|
||||
NULL, NULL, NULL, MQTT_BASE, MQTT_KEEPALIVE, MQTT_QOS, MQTT_RETAIN, MQTT_WILL_TOPIC, MQTT_WILL_ONLINE_PAYLOAD, MQTT_WILL_OFFLINE_PAYLOAD, MQTTCallback);
|
||||
|
||||
// OTA callback which is called when OTA is starting and stopping
|
||||
myESP.setOTA(OTACallback_pre, OTACallback_post);
|
||||
|
||||
// custom settings in SPIFFS
|
||||
myESP.setSettings(FSCallback, SettingsCallback);
|
||||
|
||||
// web custom settings
|
||||
myESP.setWeb(WebCallback);
|
||||
|
||||
// start up all the services
|
||||
myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION);
|
||||
myESP.setWIFI(WIFICallback); // wifi callback
|
||||
myESP.setMQTT(MQTTCallback); // MQTT ip, username and password taken from the SPIFFS settings
|
||||
myESP.setSettings(LoadSaveCallback, SetListCallback, false); // default is Serial off
|
||||
myESP.setWeb(WebCallback); // web custom settings
|
||||
myESP.setOTA(OTACallback_pre, OTACallback_post); // OTA callback which is called when OTA is starting and stopping
|
||||
myESP.begin(APP_HOSTNAME, APP_NAME, APP_VERSION, APP_URL, APP_UPDATEURL);
|
||||
|
||||
// at this point we have all the settings from our internall SPIFFS config file
|
||||
// fire up the UART now
|
||||
@@ -1891,7 +2013,8 @@ void setup() {
|
||||
} else {
|
||||
Serial.println("Note: Serial output will now be disabled. Please use Telnet.");
|
||||
Serial.flush();
|
||||
emsuart_init();
|
||||
myESP.setUseSerial(false);
|
||||
emsuart_init(); // start EMS bus transmissions
|
||||
myDebug_P(PSTR("[UART] Opened Rx/Tx connection"));
|
||||
if (!EMSESP_Status.listen_mode) {
|
||||
// go and find the boiler and thermostat types, if not in listen mode
|
||||
|
||||
688
src/ems.cpp
62
src/ems.h
@@ -11,12 +11,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <list> // std::list
|
||||
|
||||
/* debug helper for logic analyzer
|
||||
* create marker puls on GPIOx
|
||||
* ° for Rx, we use GPIO14
|
||||
* ° for Tx, we use GPIO12
|
||||
*/
|
||||
|
||||
// clang-format off
|
||||
#ifdef LOGICANALYZER
|
||||
#define RX_MARK_PIN 14
|
||||
@@ -113,6 +115,13 @@
|
||||
//#define EMS_SYS_LOGGING_DEFAULT EMS_SYS_LOGGING_VERBOSE
|
||||
#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 */
|
||||
typedef enum {
|
||||
EMS_RX_STATUS_IDLE,
|
||||
@@ -124,7 +133,8 @@ typedef enum {
|
||||
EMS_TX_STATUS_IDLE, // ready
|
||||
EMS_TX_STATUS_WAIT, // waiting for response from last Tx
|
||||
EMS_TX_WTD_TIMEOUT, // watchdog timeout during send
|
||||
EMS_TX_BRK_DETECT // incoming BRK during Tx
|
||||
EMS_TX_BRK_DETECT, // incoming BRK during Tx
|
||||
EMS_TX_REV_DETECT // waiting to detect reverse bit
|
||||
} _EMS_TX_STATUS;
|
||||
|
||||
#define EMS_TX_SUCCESS 0x01 // EMS single byte after a Tx Write indicating a success
|
||||
@@ -145,15 +155,16 @@ typedef enum {
|
||||
EMS_SYS_LOGGING_BASIC, // only basic read/write messages
|
||||
EMS_SYS_LOGGING_THERMOSTAT, // only telegrams sent from thermostat
|
||||
EMS_SYS_LOGGING_SOLARMODULE, // only telegrams sent from thermostat
|
||||
EMS_SYS_LOGGING_VERBOSE // everything
|
||||
EMS_SYS_LOGGING_VERBOSE, // everything
|
||||
EMS_SYS_LOGGING_JABBER // lots of debug output...
|
||||
} _EMS_SYS_LOGGING;
|
||||
|
||||
// status/counters since last power on
|
||||
typedef struct {
|
||||
_EMS_RX_STATUS emsRxStatus;
|
||||
_EMS_TX_STATUS emsTxStatus;
|
||||
uint16_t emsRxPgks; // received
|
||||
uint16_t emsTxPkgs; // sent
|
||||
uint16_t emsRxPgks; // # successfull received
|
||||
uint16_t emsTxPkgs; // # successfull sent
|
||||
uint16_t emxCrcErr; // CRC errors
|
||||
bool emsPollEnabled; // flag enable the response to poll messages
|
||||
_EMS_SYS_LOGGING emsLogging; // logging
|
||||
@@ -164,8 +175,9 @@ typedef struct {
|
||||
bool emsTxCapable; // able to send via Tx
|
||||
bool emsTxDisabled; // true to prevent all Tx
|
||||
uint8_t txRetryCount; // # times the last Tx was re-sent
|
||||
bool emsReverse; // if true, poll logic is reversed
|
||||
uint8_t emsTxMode; // handles Tx logic
|
||||
uint8_t emsIDMask; // Buderus: 0x00, Junkers: 0x80
|
||||
uint8_t emsPollAck[1]; // acknowledge buffer for Poll
|
||||
uint8_t emsTxMode; // Tx mode 1, 2 or 3
|
||||
} _EMS_Sys_Status;
|
||||
|
||||
// The Tx send package
|
||||
@@ -174,12 +186,12 @@ typedef struct {
|
||||
uint8_t dest;
|
||||
uint16_t type;
|
||||
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
|
||||
uint16_t type_validate; // type to call after a successful Write command
|
||||
uint8_t comparisonValue; // value to compare against during a validate
|
||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too later
|
||||
uint16_t comparisonPostRead; // after a successful write call this to read from this type ID
|
||||
uint8_t comparisonValue; // value to compare against during a validate command
|
||||
uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation
|
||||
uint16_t comparisonPostRead; // after a successful write, do a read from this type ID
|
||||
bool forceRefresh; // should we send to MQTT after a successful Tx?
|
||||
uint32_t timestamp; // when created
|
||||
uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
|
||||
@@ -197,9 +209,10 @@ typedef struct {
|
||||
uint8_t offset; // offset
|
||||
uint8_t * data; // pointer to where telegram data starts
|
||||
bool emsplus; // true if ems+/ems 2.0
|
||||
uint8_t emsplus_type; // FF, F7 or F9
|
||||
} _EMS_RxTelegram;
|
||||
|
||||
// default empty Tx
|
||||
// default empty Tx, must match struct
|
||||
const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||
EMS_TX_TELEGRAM_INIT, // action
|
||||
EMS_ID_NONE, // dest
|
||||
@@ -220,25 +233,25 @@ const _EMS_TxTelegram EMS_TX_TELEGRAM_NEW = {
|
||||
typedef struct {
|
||||
uint8_t product_id;
|
||||
char model_string[50];
|
||||
} _Boiler_Type;
|
||||
} _Boiler_Device;
|
||||
|
||||
typedef struct {
|
||||
uint8_t product_id;
|
||||
uint8_t device_id;
|
||||
char model_string[50];
|
||||
} _SolarModule_Type;
|
||||
} _SolarModule_Device;
|
||||
|
||||
typedef struct {
|
||||
uint8_t product_id;
|
||||
uint8_t device_id;
|
||||
char model_string[50];
|
||||
} _Other_Type;
|
||||
} _Other_Device;
|
||||
|
||||
typedef struct {
|
||||
uint8_t product_id;
|
||||
uint8_t device_id;
|
||||
char model_string[50];
|
||||
} _HeatPump_Type;
|
||||
} _HeatPump_Device;
|
||||
|
||||
typedef struct {
|
||||
uint8_t model_id;
|
||||
@@ -246,15 +259,16 @@ typedef struct {
|
||||
uint8_t device_id;
|
||||
char model_string[50];
|
||||
bool write_supported;
|
||||
} _Thermostat_Type;
|
||||
} _Thermostat_Device;
|
||||
|
||||
// for consolidating all types
|
||||
typedef struct {
|
||||
uint8_t model_type; // 1=boiler, 2=thermostat, 3=sm, 4=other, 5=unknown
|
||||
uint8_t product_id;
|
||||
uint8_t device_id;
|
||||
char version[10];
|
||||
char model_string[50];
|
||||
} _Generic_Type;
|
||||
} _Generic_Device;
|
||||
|
||||
/*
|
||||
* Telegram package defintions
|
||||
@@ -390,6 +404,7 @@ typedef struct {
|
||||
} _EMS_Type;
|
||||
|
||||
// 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);
|
||||
void ems_init();
|
||||
void ems_doReadCommand(uint16_t type, uint8_t dest, bool forceRefresh = false);
|
||||
@@ -403,7 +418,6 @@ void ems_testTelegram(uint8_t test_num);
|
||||
void ems_startupTelegrams();
|
||||
bool ems_checkEMSBUSAlive();
|
||||
void ems_clearDeviceList();
|
||||
void ems_setTxMode(uint8_t mode);
|
||||
|
||||
void ems_setThermostatTemp(float temperature, uint8_t temptype = 0);
|
||||
void ems_setThermostatMode(uint8_t mode);
|
||||
@@ -419,12 +433,12 @@ void ems_setWarmWaterModeComfort(uint8_t comfort);
|
||||
void ems_setModels();
|
||||
void ems_setTxDisabled(bool b);
|
||||
bool ems_getTxDisabled();
|
||||
uint8_t ems_getTxMode();
|
||||
void ems_setTxMode(uint8_t mode);
|
||||
|
||||
char * ems_getThermostatDescription(char * buffer);
|
||||
char * ems_getBoilerDescription(char * buffer);
|
||||
char * ems_getSolarModuleDescription(char * buffer);
|
||||
char * ems_getHeatPumpDescription(char * buffer);
|
||||
char * ems_getThermostatDescription(char * buffer, bool name_only = false);
|
||||
char * ems_getBoilerDescription(char * buffer, bool name_only = false);
|
||||
char * ems_getSolarModuleDescription(char * buffer, bool name_only = false);
|
||||
char * ems_getHeatPumpDescription(char * buffer, bool name_only = false);
|
||||
void ems_getThermostatValues();
|
||||
void ems_getBoilerValues();
|
||||
void ems_getSolarModuleValues();
|
||||
@@ -457,3 +471,5 @@ extern _EMS_Thermostat EMS_Thermostat;
|
||||
extern _EMS_SolarModule EMS_SolarModule;
|
||||
extern _EMS_HeatPump EMS_HeatPump;
|
||||
extern _EMS_Other EMS_Other;
|
||||
|
||||
extern std::list<_Generic_Device> Devices;
|
||||
|
||||
@@ -13,12 +13,12 @@
|
||||
#include "ems.h"
|
||||
|
||||
/*
|
||||
* Common
|
||||
* Common Type
|
||||
*/
|
||||
#define EMS_TYPE_Version 0x02
|
||||
|
||||
/*
|
||||
* Boiler...
|
||||
* Boiler Telegram Types...
|
||||
*/
|
||||
#define EMS_TYPE_UBAMonitorFast 0x18 // 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
|
||||
|
||||
// Other
|
||||
// SM and HP Types
|
||||
#define EMS_TYPE_SM10Monitor 0x97 // SM10Monitor
|
||||
#define EMS_TYPE_SM100Monitor 0x0262 // SM100Monitor
|
||||
#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)
|
||||
|
||||
/*
|
||||
* Thermostats...
|
||||
* Thermostat Types
|
||||
*/
|
||||
|
||||
// Common for all thermostats
|
||||
@@ -63,38 +63,43 @@
|
||||
|
||||
// RC10 specific
|
||||
#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_curr 2 // current temp
|
||||
|
||||
#define EMS_TYPE_RC10Set 0xB0 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC10Set_temp 4 // position of thermostat setpoint temperature
|
||||
|
||||
// RC20 specific
|
||||
#define EMS_TYPE_RC20StatusMessage 0x91 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_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_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
|
||||
#define EMS_TYPE_RC30StatusMessage 0x41 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_OFFSET_RC30StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_OFFSET_RC30StatusMessage_curr 2 // current temp
|
||||
|
||||
#define EMS_TYPE_RC30Set 0xA7 // for setting values like temp and mode
|
||||
#define EMS_OFFSET_RC30Set_mode 23 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC30Set_temp 28 // position of thermostat setpoint temperature
|
||||
#define EMS_OFFSET_RC30StatusMessage_setpoint 1 // setpoint temp
|
||||
#define EMS_OFFSET_RC30StatusMessage_curr 2 // current temp
|
||||
|
||||
|
||||
// RC35 specific
|
||||
#define EMS_TYPE_RC35StatusMessage_HC1 0x3E // is an automatic thermostat broadcast giving us temps on HC1
|
||||
#define EMS_TYPE_RC35StatusMessage_HC2 0x48 // is an automatic thermostat broadcast giving us temps on HC2
|
||||
#define EMS_TYPE_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_curr 3 // current temp
|
||||
#define EMS_OFFSET_RC35StatusMessage_mode 1 //day mode
|
||||
|
||||
#define EMS_TYPE_RC35Set_HC1 0x3D // for setting values like temp and mode (Working mode HC1)
|
||||
#define EMS_TYPE_RC35Set_HC2 0x47 // for setting values like temp and mode (Working mode HC2)
|
||||
#define EMS_OFFSET_RC35Set_mode 7 // position of thermostat mode
|
||||
#define EMS_OFFSET_RC35Set_temp_day 2 // position of thermostat setpoint temperature for day time
|
||||
#define EMS_OFFSET_RC35Set_temp_night 1 // position of thermostat setpoint temperature for night time
|
||||
#define EMS_OFFSET_RC35Get_mode_day 1 // position of thermostat day mode
|
||||
#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
|
||||
@@ -105,14 +110,21 @@
|
||||
#define EMS_OFFSET_EasyStatusMessage_curr 8 // current temp
|
||||
|
||||
// RC1010, RC310 and RC300 specific (EMS Plus)
|
||||
#define EMS_TYPE_RCPLUSStatusMessage 0x01A5 // is an automatic thermostat broadcast giving us temps
|
||||
#define EMS_TYPE_RCPLUSStatusHeating 0x01B9 // heating mode
|
||||
#define EMS_TYPE_RCPLUSStatusMessage 0x01A5 // is an automatic thermostat broadcast giving us temps, also reading
|
||||
#define EMS_TYPE_RCPLUSStatusMode 0x1AF // summer/winter mode
|
||||
#define EMS_TYPE_RCPLUSSet 0x03 // setpoint temp message
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_mode 10 // thermostat mode (auto, manual)
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_setpoint 3 // setpoint temp
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_curr 0 // current temp
|
||||
#define EMS_OFFSET_RCPLUSGet_mode_day 8 // day/night mode
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_mode 0x0A // thermostat mode (auto, manual)
|
||||
#define EMS_OFFSET_RCPLUSStatusMessage_currsetpoint 6 // target setpoint temp
|
||||
|
||||
#define EMS_TYPE_RCPLUSSet 0x01B9 // setpoint temp message and mode
|
||||
#define EMS_OFFSET_RCPLUSSet_mode 0 // operation mode(Auto=0xFF, Manual=0x00)
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_comfort3 1 // comfort3 level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_comfort2 2 // comfort2 level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_comfort1 3 // comfort1 level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_eco 4 // eco level
|
||||
#define EMS_OFFSET_RCPLUSSet_temp_setpoint 8 // temp setpoint, when changing of templevel (in auto) value is reset and set to FF
|
||||
#define EMS_OFFSET_RCPLUSSet_manual_setpoint 10 // manual setpoint
|
||||
|
||||
// Junkers FR10, FW100 (EMS Plus)
|
||||
#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
|
||||
|
||||
|
||||
// Known EMS types
|
||||
// Known EMS devices
|
||||
typedef enum {
|
||||
EMS_MODEL_NONE, // unset
|
||||
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.
|
||||
// The device_id is always 0x08
|
||||
// format is PRODUCT ID, DESCRIPTION
|
||||
const _Boiler_Type Boiler_Types[] = {
|
||||
const _Boiler_Device Boiler_Devices[] = {
|
||||
|
||||
{72, "MC10 Module"},
|
||||
{123, "Buderus GB172/Nefit Trendline/Junkers Cerapur"},
|
||||
@@ -175,7 +187,7 @@ const _Boiler_Type Boiler_Types[] = {
|
||||
* Known Solar Module types
|
||||
* 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_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
|
||||
// format is PRODUCT ID, DEVICE ID, DESCRIPTION
|
||||
const _Other_Type Other_Types[] = {
|
||||
const _Other_Device Other_Devices[] = {
|
||||
|
||||
{69, 0x21, "MM10 Mixer Module"},
|
||||
{71, 0x11, "WM10 Switch Module"},
|
||||
@@ -207,13 +219,13 @@ const _Other_Type Other_Types[] = {
|
||||
|
||||
// heatpump
|
||||
// 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
|
||||
* 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
|
||||
{EMS_MODEL_EASY, 202, 0x18, "Logamatic TC100/Nefit Moduline Easy", EMS_THERMOSTAT_WRITE_NO},
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
|
||||
#include "emsuart.h"
|
||||
#include "ems.h"
|
||||
#include <Arduino.h>
|
||||
#include <user_interface.h>
|
||||
|
||||
_EMSRxBuf * pEMSRxBuf;
|
||||
_EMSRxBuf * paEMSRxBuf[EMS_MAXBUFFERS];
|
||||
uint8_t emsRxBufIdx = 0;
|
||||
uint8_t phantomBreak = 0;
|
||||
|
||||
os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
|
||||
|
||||
@@ -22,7 +22,7 @@ os_event_t recvTaskQueue[EMSUART_recvTaskQueueLen]; // our Rx queue
|
||||
//
|
||||
static void emsuart_rx_intr_handler(void * para) {
|
||||
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
|
||||
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
|
||||
if (USIS(EMSUART_UART) & ((1 << UIFF) | (1 << UITO) | (1 << UIBD))) {
|
||||
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
|
||||
@@ -46,8 +48,9 @@ static void emsuart_rx_intr_handler(void * para) {
|
||||
ETS_UART_INTR_DISABLE(); // disable all interrupts and clear them
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // INT clear the BREAK detect interrupt
|
||||
|
||||
pEMSRxBuf->length = length;
|
||||
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, length); // copy data into transfer buffer, including the BRK 0x00 at the end
|
||||
pEMSRxBuf->length = (length > EMS_MAXBUFFERSIZE) ? EMS_MAXBUFFERSIZE : length;
|
||||
os_memcpy((void *)pEMSRxBuf->buffer, (void *)&uart_buffer, pEMSRxBuf->length); // copy data into transfer buffer, including the BRK 0x00 at the end
|
||||
length = 0;
|
||||
EMS_Sys_Status.emsRxStatus = EMS_RX_STATUS_IDLE; // set the status flag stating BRK has been received and we can start a new package
|
||||
ETS_UART_INTR_ENABLE(); // re-enable UART interrupts
|
||||
|
||||
@@ -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
|
||||
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) {
|
||||
RX_PULSE(20);
|
||||
// it's a poll or status code, single byte and ok to send on
|
||||
ems_parseTelegram((uint8_t *)pCurrent->buffer, 1);
|
||||
} else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1) && (pCurrent->buffer[length - 2] != 0x00)) {
|
||||
} else if ((length > 4) && (length <= EMS_MAXBUFFERSIZE + 1)) {
|
||||
// ignore double BRK at the end, possibly from the Tx loopback
|
||||
// also telegrams with no data value
|
||||
RX_PULSE(40);
|
||||
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)
|
||||
// see https://www.espressif.com/sites/default/files/documentation/esp8266-technical_reference_en.pdf
|
||||
//
|
||||
// change: we set UCFFT to 1 to get an immediate indicator about incoming trafffic.
|
||||
// change: we set UCFFT to 1 to get an immediate indicator about incoming traffic.
|
||||
// Otherwise, we're only noticed by UCTOT or RxBRK!
|
||||
USC1(EMSUART_UART) = 0; // reset config first
|
||||
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (1 << UCTOE); // enable interupts
|
||||
USC1(EMSUART_UART) = (0x01 << UCFFT) | (0x01 << UCTOT) | (0 << UCTOE); // enable interupts
|
||||
|
||||
// set interrupts for triggers
|
||||
USIC(EMSUART_UART) = 0xFFFF; // clear all interupts
|
||||
@@ -133,7 +139,8 @@ void ICACHE_FLASH_ATTR emsuart_init() {
|
||||
|
||||
// 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
|
||||
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
|
||||
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);
|
||||
|
||||
// swap Rx and Tx pins to use GPIO13 (D7) and GPIO15 (D8) respectively
|
||||
//system_uart_swap();
|
||||
system_uart_swap();
|
||||
|
||||
ETS_UART_INTR_ATTACH(emsuart_rx_intr_handler, nullptr);
|
||||
ETS_UART_INTR_ENABLE();
|
||||
@@ -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 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) {
|
||||
LA_PULSE(50);
|
||||
// temp code until we get mode 2 working without resets
|
||||
if (EMS_Sys_Status.emsTxMode == 0) { // classic mode logic
|
||||
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+
|
||||
|
||||
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];
|
||||
@@ -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);
|
||||
}
|
||||
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
|
||||
* 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
|
||||
@@ -254,9 +259,9 @@ _EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len) {
|
||||
|
||||
// shorter busy poll...
|
||||
#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;
|
||||
|
||||
ETS_UART_INTR_DISABLE(); // disable rx interrupt
|
||||
|
||||
// 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
|
||||
// wait for echo from busmaster
|
||||
GPIO_L(TX_MARK_MASK);
|
||||
|
||||
while (((USS(EMSUART_UART) >> USRXC) & 0xFF) == _usrxc) {
|
||||
delayMicroseconds(EMSUART_BUSY_WAIT); // burn CPU cycles...
|
||||
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...
|
||||
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>
|
||||
USIC(EMSUART_UART) = (1 << UIBD); // clear BRK detect IRQ
|
||||
phantomBreak = 1;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <ems.h>
|
||||
|
||||
#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_start();
|
||||
_EMS_TX_STATUS ICACHE_FLASH_ATTR emsuart_tx_buffer(uint8_t * buf, uint8_t len);
|
||||
void ICACHE_FLASH_ATTR emsuart_tx_poll();
|
||||
|
||||
@@ -10,26 +10,14 @@
|
||||
|
||||
#include "ems.h"
|
||||
|
||||
// MQTT base name
|
||||
#define MQTT_BASE "home" // all MQTT topics are prefix with this string, in the format <MQTT_BASE>/<app name>/<topic>
|
||||
|
||||
// MQTT general settings
|
||||
#define MQTT_WILL_TOPIC "status" // for last will & testament topic name
|
||||
#define MQTT_WILL_ONLINE_PAYLOAD "online" // for last will & testament payload
|
||||
#define MQTT_WILL_OFFLINE_PAYLOAD "offline" // for last will & testament payload
|
||||
#define MQTT_RETAIN false
|
||||
#define MQTT_KEEPALIVE 120 // 2 minutes
|
||||
#define MQTT_QOS 1
|
||||
#define MQTT_MAX_SIZE 700 // max size of a JSON object. See https://arduinojson.org/v6/assistant/
|
||||
|
||||
// MQTT for thermostat
|
||||
#define TOPIC_THERMOSTAT_DATA "thermostat_data" // for sending thermostat values to MQTT
|
||||
#define TOPIC_THERMOSTAT_CMD_TEMP "thermostat_cmd_temp" // for received thermostat temp changes via MQTT
|
||||
#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_DAYTEMP "thermostat_daytemp" // RC35 specific
|
||||
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // RC35 specific
|
||||
#define TOPIC_THERMOSTAT_CMD_HOLIDAYTEMP "thermostat_holidayttemp" // RC35 specific
|
||||
#define TOPIC_THERMOSTAT_CMD_DAYTEMP "thermostat_daytemp" // for received thermostat day temp (RC35 specific)
|
||||
#define TOPIC_THERMOSTAT_CMD_NIGHTTEMP "thermostat_nighttemp" // for received thermostat night temp (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_SELTEMP "thermostat_seltemp" // selected temperature
|
||||
#define THERMOSTAT_HC "thermostat_hc" // which heating circuit number
|
||||
@@ -44,7 +32,7 @@
|
||||
#define TOPIC_BOILER_DATA "boiler_data" // for sending boiler values to MQTT
|
||||
#define TOPIC_BOILER_TAPWATER_ACTIVE "tapwater_active" // if hot tap water is running
|
||||
#define TOPIC_BOILER_HEATING_ACTIVE "heating_active" // if heating is on
|
||||
#define TOPIC_BOILER_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_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
|
||||
@@ -74,28 +62,3 @@
|
||||
// MQTT for EXTERNAL SENSORS
|
||||
#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
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// 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
|
||||
|
||||
@@ -52,7 +52,9 @@ static const char * TEST_DATA[] = {
|
||||
"38 10 FF 00 03 2B 00 C7 07 C3 01", // test 47 - heatpump Enviline
|
||||
"08 0B 19 00 00 F7 80 00 80 00 00 00 00 00 03 58 97 0C 7B 1F 00 00 00 06 C4 DF 02 64 48 80 00", // test 48 - outdoor temp check
|
||||
"88 00 19 00 00 DC 80 00 80 00 FF FF 00 00 00 21 9A 06 E1 7C 00 00 00 06 C2 13 00 1E 90 80 00", // test 49 - check max length
|
||||
"30 00 FF 00 02 8E 00 00 41 82 00 00 28 36 00 00 82 21" // test 50 - SM100
|
||||
"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
|
||||
|
||||
|
||||
};
|
||||
|
||||
@@ -1,10 +1 @@
|
||||
/**
|
||||
*
|
||||
* 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"
|
||||
#define APP_VERSION "1.9.0"
|
||||
|
||||
0
src/webh/.gitkeep
Normal file
6
src/websrc/3rdparty/css/bootstrap-3.3.7.min.css
vendored
Normal file
1
src/websrc/3rdparty/css/footable.bootstrap-3.1.6.min.css
vendored
Normal file
1
src/websrc/3rdparty/css/sidebar.css
vendored
Normal 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 }
|
||||
BIN
src/websrc/3rdparty/fonts/glyphicons-halflings-regular.woff
vendored
Normal file
7
src/websrc/3rdparty/js/bootstrap-3.3.7.min.js
vendored
Normal file
10
src/websrc/3rdparty/js/footable-3.1.6.min.js
vendored
Normal file
5
src/websrc/3rdparty/js/jquery-1.12.4.min.js
vendored
Normal file
293
src/websrc/index.html
Normal 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">×</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">×</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">×</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">×</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">×</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">×</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
@@ -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">×</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 (<mqtt base>/<host name>/)"></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
19
tools/webfilesbuilder/gulp.js
Normal 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');
|
||||
9
tools/webfilesbuilder/gulp.meta.js
Normal 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'
|
||||
};
|
||||
};
|
||||
232
tools/webfilesbuilder/gulpfile.js
Normal 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
24
tools/webfilesbuilder/package.json
Normal 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
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
14
tools/wsemulator/package.json
Normal 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
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
});
|
||||
});
|
||||