Browse Source

Add thermostat module

rules-rpn
Dmitry Blinov 5 years ago
parent
commit
af07494f00
10 changed files with 1013 additions and 9 deletions
  1. +38
    -1
      code/espurna/config/general.h
  2. +11
    -0
      code/espurna/config/prototypes.h
  3. +6
    -0
      code/espurna/espurna.ino
  4. +9
    -0
      code/espurna/sensor.ino
  5. +766
    -0
      code/espurna/thermostat.ino
  6. +19
    -2
      code/espurna/utils.ino
  7. +4
    -0
      code/html/custom.css
  8. +27
    -0
      code/html/custom.js
  9. +132
    -6
      code/html/index.html
  10. +1
    -0
      code/platformio.ini

+ 38
- 1
code/espurna/config/general.h View File

@ -162,6 +162,21 @@
#define EEPROM_ROTATE_DATA 11 // Reserved for the EEPROM_ROTATE library (3 bytes) #define EEPROM_ROTATE_DATA 11 // Reserved for the EEPROM_ROTATE library (3 bytes)
#define EEPROM_DATA_END 14 // End of custom EEPROM data block #define EEPROM_DATA_END 14 // End of custom EEPROM data block
//------------------------------------------------------------------------------
// THERMOSTAT
//------------------------------------------------------------------------------
#ifndef THERMOSTAT_SUPPORT
#define THERMOSTAT_SUPPORT 0
#endif
#ifndef THERMOSTAT_DISPLAY_SUPPORT
#define THERMOSTAT_DISPLAY_SUPPORT 0
#endif
#define THERMOSTAT_SERVER_LOST_INTERVAL 120000 //server means lost after 2 min from last response
#define THERMOSTAT_REMOTE_TEMP_MAX_WAIT 120 // 2 min
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// HEARTBEAT // HEARTBEAT
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -259,6 +274,18 @@
#define HEARTBEAT_REPORT_INTERVAL 0 #define HEARTBEAT_REPORT_INTERVAL 0
#endif #endif
#if THERMOSTAT_SUPPORT && ! defined HEARTBEAT_REPORT_RANGE
#define HEARTBEAT_REPORT_RANGE 1
#else
#define HEARTBEAT_REPORT_RANGE 0
#endif
#if THERMOSTAT_SUPPORT && ! defined HEARTBEAT_REPORT_REMOTE_TEMP
#define HEARTBEAT_REPORT_REMOTE_TEMP 1
#else
#define HEARTBEAT_REPORT_REMOTE_TEMP 0
#endif
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// Load average // Load average
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -753,7 +780,7 @@
#ifndef MQTT_USE_JSON #ifndef MQTT_USE_JSON
#define MQTT_USE_JSON 0 // Group messages in a JSON body
#define MQTT_USE_JSON 1 // Group messages in a JSON body
#endif #endif
#ifndef MQTT_USE_JSON_DELAY #ifndef MQTT_USE_JSON_DELAY
@ -833,6 +860,16 @@
#define MQTT_TOPIC_KELVIN "kelvin" #define MQTT_TOPIC_KELVIN "kelvin"
#define MQTT_TOPIC_TRANSITION "transition" #define MQTT_TOPIC_TRANSITION "transition"
// Thermostat module
#define MQTT_TOPIC_HOLD_TEMP "hold_temp"
#define MQTT_TOPIC_HOLD_TEMP_MIN "min"
#define MQTT_TOPIC_HOLD_TEMP_MAX "max"
#define MQTT_TOPIC_REMOTE_TEMP "remote_temp"
#define MQTT_TOPIC_ASK_TEMP_RANGE "ask_temp_range"
#define MQTT_TOPIC_NOTIFY_TEMP_RANGE_MIN "notify_temp_range_min"
#define MQTT_TOPIC_NOTIFY_TEMP_RANGE_MAX "notify_temp_range_max"
#define MQTT_STATUS_ONLINE "1" // Value for the device ON message #define MQTT_STATUS_ONLINE "1" // Value for the device ON message
#define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will) #define MQTT_STATUS_OFFLINE "0" // Value for the device OFF message (will)


+ 11
- 0
code/espurna/config/prototypes.h View File

@ -210,3 +210,14 @@ void webRequestRegister(web_request_callback_f callback);
typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f; typedef std::function<void(justwifi_messages_t code, char * parameter)> wifi_callback_f;
void wifiRegister(wifi_callback_f callback); void wifiRegister(wifi_callback_f callback);
bool wifiConnected(); bool wifiConnected();
// -----------------------------------------------------------------------------
// THERMOSTAT
// -----------------------------------------------------------------------------
#if THERMOSTAT_SUPPORT
typedef std::function<void(bool)> thermostat_callback_f;
void thermostatRegister(thermostat_callback_f callback);
#else
#define thermostat_callback_f void *
#endif

+ 6
- 0
code/espurna/espurna.ino View File

@ -189,6 +189,12 @@ void setup() {
#if UART_MQTT_SUPPORT #if UART_MQTT_SUPPORT
uartmqttSetup(); uartmqttSetup();
#endif #endif
#if THERMOSTAT_SUPPORT
thermostatSetup();
#endif
#if THERMOSTAT_DISPLAY_SUPPORT
displaySetup();
#endif
// 3rd party code hook // 3rd party code hook


+ 9
- 0
code/espurna/sensor.ino View File

@ -15,6 +15,8 @@ Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
#include "filters/MovingAverageFilter.h" #include "filters/MovingAverageFilter.h"
#include "sensors/BaseSensor.h" #include "sensors/BaseSensor.h"
#include <float.h>
typedef struct { typedef struct {
BaseSensor * sensor; // Sensor object BaseSensor * sensor; // Sensor object
BaseFilter * filter; // Filter object BaseFilter * filter; // Filter object
@ -1286,6 +1288,13 @@ unsigned char magnitudeType(unsigned char index) {
return MAGNITUDE_NONE; return MAGNITUDE_NONE;
} }
double magnitudeValue(unsigned char index) {
if (index < _magnitudes.size()) {
return _sensor_realtime ? _magnitudes[index].current : _magnitudes[index].reported;
}
return DBL_MIN;
}
unsigned char magnitudeIndex(unsigned char index) { unsigned char magnitudeIndex(unsigned char index) {
if (index < _magnitudes.size()) { if (index < _magnitudes.size()) {
return int(_magnitudes[index].global); return int(_magnitudes[index].global);


+ 766
- 0
code/espurna/thermostat.ino View File

@ -0,0 +1,766 @@
/*
THERMOSTAT MODULE
Copyright (C) 2017 by Dmitry Blinov <dblinov76 at gmail dot com>
*/
#if THERMOSTAT_SUPPORT
#include <ArduinoJson.h>
#include <float.h>
const char* NAME_TEMP_RANGE_MIN = "tempRangeMin";
const char* NAME_TEMP_RANGE_MAX = "tempRangeMax";
const char* NAME_REMOTE_SENSOR_NAME = "remoteSensorName";
const char* NAME_REMOTE_TEMP_MAX_WAIT = "remoteTempMaxWait";
const char* NAME_ALONE_ON_TIME = "aloneOnTime";
const char* NAME_ALONE_OFF_TIME = "aloneOffTime";
const char* NAME_MAX_ON_TIME = "maxOnTime";
const char* NAME_MIN_OFF_TIME = "minOffTime";
const char* NAME_BURN_TOTAL = "burnTotal";
const char* NAME_BURN_TODAY = "burnToday";
const char* NAME_BURN_YESTERDAY = "burnYesterday";
const char* NAME_BURN_THIS_MONTH = "burnThisMonth";
const char* NAME_BURN_PREV_MONTH = "burnPrevMonth";
const char* NAME_BURN_DAY = "burnDay";
const char* NAME_BURN_MONTH = "burnMonth";
const char* NAME_OPERATION_MODE = "thermostatOperationMode";
#define ASK_TEMP_RANGE_INTERVAL_INITIAL 15000 // ask initially once per every 15 seconds
#define ASK_TEMP_RANGE_INTERVAL_REGULAR 60000 // ask every minute to be sure
#define MILLIS_IN_SEC 1000
#define MILLIS_IN_MIN 60000
#define THERMOSTAT_STATE_UPDATE_INTERVAL 60000 // 1 min
#define THERMOSTAT_RELAY 0 // use relay 0
#define THERMOSTAT_TEMP_RANGE_MIN 10 // grad. Celsius
#define THERMOSTAT_TEMP_RANGE_MIN_MIN 3 // grad. Celsius
#define THERMOSTAT_TEMP_RANGE_MIN_MAX 30 // grad. Celsius
#define THERMOSTAT_TEMP_RANGE_MAX 20 // grad. Celsius
#define THERMOSTAT_TEMP_RANGE_MAX_MIN 8 // grad. Celsius
#define THERMOSTAT_TEMP_RANGE_MAX_MAX 35 // grad. Celsius
#define THERMOSTAT_ALONE_ON_TIME 5 // 5 min
#define THERMOSTAT_ALONE_OFF_TIME 55 // 55 min
#define THERMOSTAT_MAX_ON_TIME 30 // 30 min
#define THERMOSTAT_MIN_OFF_TIME 10 // 10 min
unsigned long _thermostat_remote_temp_max_wait = THERMOSTAT_REMOTE_TEMP_MAX_WAIT * MILLIS_IN_SEC;
unsigned long _thermostat_alone_on_time = THERMOSTAT_ALONE_ON_TIME * MILLIS_IN_MIN;
unsigned long _thermostat_alone_off_time = THERMOSTAT_ALONE_OFF_TIME * MILLIS_IN_MIN;
unsigned long _thermostat_max_on_time = THERMOSTAT_MAX_ON_TIME * MILLIS_IN_MIN;
unsigned long _thermostat_min_off_time = THERMOSTAT_MIN_OFF_TIME * MILLIS_IN_MIN;
unsigned int _thermostat_on_time_for_day = 0;
unsigned int _thermostat_burn_total = 0;
unsigned int _thermostat_burn_today = 0;
unsigned int _thermostat_burn_yesterday = 0;
unsigned int _thermostat_burn_this_month = 0;
unsigned int _thermostat_burn_prev_month = 0;
unsigned int _thermostat_burn_day = 0;
unsigned int _thermostat_burn_month = 0;
struct temp_t {
float temp;
unsigned long last_update = 0;
bool need_display_update = false;
};
temp_t _remote_temp;
struct temp_range_t {
int min = THERMOSTAT_TEMP_RANGE_MIN;
int max = THERMOSTAT_TEMP_RANGE_MAX;
unsigned long last_update = 0;
unsigned long ask_time = 0;
unsigned int ask_interval = 0;
bool need_display_update = true;
};
temp_range_t _temp_range;
enum temperature_source_t {temp_none, temp_local, temp_remote};
struct thermostat_t {
unsigned long last_update = 0;
unsigned long last_switch = 0;
String remote_sensor_name;
unsigned int temperature_source = temp_none;
};
thermostat_t _thermostat;
enum thermostat_cycle_type {cooling, heating};
unsigned int _thermostat_cycle = heating;
String thermostat_remote_sensor_topic;
//------------------------------------------------------------------------------
std::vector<thermostat_callback_f> _thermostat_callbacks;
void thermostatRegister(thermostat_callback_f callback) {
_thermostat_callbacks.push_back(callback);
}
//------------------------------------------------------------------------------
void updateOperationMode() {
#if WEB_SUPPORT
String message;
if (_thermostat.temperature_source == temp_remote) {
message = "{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"remote temperature\"}";
updateRemoteTemp(true);
} else if (_thermostat.temperature_source == temp_local) {
message = "{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"local temperature\"}";
updateRemoteTemp(false);
} else {
message = "{\"thermostatVisible\": 1, \"thermostatOperationMode\": \"autonomous\"}";
updateRemoteTemp(false);
}
wsSend(message.c_str());
#endif
}
//------------------------------------------------------------------------------
void updateRemoteTemp(bool remote_temp_actual) {
#if WEB_SUPPORT
char tmp_str[6];
if (remote_temp_actual) {
dtostrf(_remote_temp.temp, 1-sizeof(tmp_str), 1, tmp_str);
} else {
strcpy(tmp_str, "\"?\"");
}
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"thermostatVisible\": 1, \"remoteTmp\": %s}"), tmp_str);
wsSend(buffer);
#endif
}
//------------------------------------------------------------------------------
// MQTT
//------------------------------------------------------------------------------
void thermostatMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) {
mqttSubscribeRaw(thermostat_remote_sensor_topic.c_str());
mqttSubscribe(MQTT_TOPIC_HOLD_TEMP);
_temp_range.ask_interval = ASK_TEMP_RANGE_INTERVAL_INITIAL;
_temp_range.ask_time = millis();
}
if (type == MQTT_MESSAGE_EVENT) {
// Match topic
String t = mqttMagnitude((char *) topic);
if (strcmp(topic, thermostat_remote_sensor_topic.c_str()) != 0
&& !t.equals(MQTT_TOPIC_HOLD_TEMP))
return;
// Parse JSON input
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(payload);
if (!root.success()) {
DEBUG_MSG_P(PSTR("[THERMOSTAT] Error parsing data\n"));
return;
}
// Check rempte sensor temperature
if (strcmp(topic, thermostat_remote_sensor_topic.c_str()) == 0) {
if (root.containsKey(magnitudeTopic(MAGNITUDE_TEMPERATURE))) {
String remote_temp = root[magnitudeTopic(MAGNITUDE_TEMPERATURE)];
_remote_temp.temp = remote_temp.toFloat();
_remote_temp.last_update = millis();
_remote_temp.need_display_update = true;
DEBUG_MSG_P(PSTR("[THERMOSTAT] Remote sensor temperature: %s\n"), remote_temp.c_str());
updateRemoteTemp(true);
}
}
// Check temperature range change
if (t.equals(MQTT_TOPIC_HOLD_TEMP)) {
if (root.containsKey(MQTT_TOPIC_HOLD_TEMP_MIN)) {
int t_min = root[MQTT_TOPIC_HOLD_TEMP_MIN];
int t_max = root[MQTT_TOPIC_HOLD_TEMP_MAX];
if (t_min < THERMOSTAT_TEMP_RANGE_MIN_MIN || t_min > THERMOSTAT_TEMP_RANGE_MIN_MAX ||
t_max < THERMOSTAT_TEMP_RANGE_MAX_MIN || t_max > THERMOSTAT_TEMP_RANGE_MAX_MAX) {
DEBUG_MSG_P(PSTR("[THERMOSTAT] Hold temperature range error\n"));
return;
}
_temp_range.min = root[MQTT_TOPIC_HOLD_TEMP_MIN];
_temp_range.max = root[MQTT_TOPIC_HOLD_TEMP_MAX];
setSetting(NAME_TEMP_RANGE_MIN, _temp_range.min);
setSetting(NAME_TEMP_RANGE_MAX, _temp_range.max);
saveSettings();
_temp_range.ask_interval = ASK_TEMP_RANGE_INTERVAL_REGULAR;
_temp_range.last_update = millis();
_temp_range.need_display_update = true;
DEBUG_MSG_P(PSTR("[THERMOSTAT] Hold temperature range: (%d - %d)\n"), _temp_range.min, _temp_range.max);
// Update websocket clients
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"thermostatVisible\": 1, \"tempRangeMin\": %d, \"tempRangeMax\": %d}"), _temp_range.min, _temp_range.max);
wsSend(buffer);
#endif
} else {
DEBUG_MSG_P(PSTR("[THERMOSTAT] Error temperature range data\n"));
}
}
}
}
#if MQTT_SUPPORT
//------------------------------------------------------------------------------
void thermostatSetupMQTT() {
mqttRegister(thermostatMQTTCallback);
}
#endif
//------------------------------------------------------------------------------
void notifyRangeChanged(bool min) {
DEBUG_MSG_P(PSTR("[THERMOSTAT] notifyRangeChanged %s = %d\n"), min ? "MIN" : "MAX", min ? _temp_range.min : _temp_range.max);
char tmp_str[6];
sprintf(tmp_str, "%d", min ? _temp_range.min : _temp_range.max);
mqttSend(min ? MQTT_TOPIC_NOTIFY_TEMP_RANGE_MIN : MQTT_TOPIC_NOTIFY_TEMP_RANGE_MAX, tmp_str, true);
}
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
void commonSetup() {
_temp_range.min = getSetting(NAME_TEMP_RANGE_MIN, THERMOSTAT_TEMP_RANGE_MIN).toInt();
_temp_range.max = getSetting(NAME_TEMP_RANGE_MAX, THERMOSTAT_TEMP_RANGE_MAX).toInt();
DEBUG_MSG_P(PSTR("[THERMOSTAT] _temp_range.min = %d\n"), _temp_range.min);
DEBUG_MSG_P(PSTR("[THERMOSTAT] _temp_range.max = %d\n"), _temp_range.max);
_thermostat.remote_sensor_name = getSetting(NAME_REMOTE_SENSOR_NAME);
thermostat_remote_sensor_topic = _thermostat.remote_sensor_name + String("/") + String(MQTT_TOPIC_JSON);
_thermostat_remote_temp_max_wait = getSetting(NAME_REMOTE_TEMP_MAX_WAIT, THERMOSTAT_REMOTE_TEMP_MAX_WAIT).toInt() * MILLIS_IN_SEC;
_thermostat_alone_on_time = getSetting(NAME_ALONE_ON_TIME, THERMOSTAT_ALONE_ON_TIME).toInt() * MILLIS_IN_MIN;
_thermostat_alone_off_time = getSetting(NAME_ALONE_OFF_TIME, THERMOSTAT_ALONE_OFF_TIME).toInt() * MILLIS_IN_MIN;
_thermostat_max_on_time = getSetting(NAME_MAX_ON_TIME, THERMOSTAT_MAX_ON_TIME).toInt() * MILLIS_IN_MIN;
_thermostat_min_off_time = getSetting(NAME_MIN_OFF_TIME, THERMOSTAT_MIN_OFF_TIME).toInt() * MILLIS_IN_MIN;
}
//------------------------------------------------------------------------------
void thermostatConfigure() {
commonSetup();
_thermostat.temperature_source = temp_none;
_thermostat_burn_total = getSetting(NAME_BURN_TOTAL).toInt();
_thermostat_burn_today = getSetting(NAME_BURN_TODAY).toInt();
_thermostat_burn_yesterday = getSetting(NAME_BURN_YESTERDAY).toInt();
_thermostat_burn_this_month = getSetting(NAME_BURN_THIS_MONTH).toInt();
_thermostat_burn_prev_month = getSetting(NAME_BURN_PREV_MONTH).toInt();
_thermostat_burn_day = getSetting(NAME_BURN_DAY).toInt();
_thermostat_burn_month = getSetting(NAME_BURN_MONTH).toInt();
}
//------------------------------------------------------------------------------
void _thermostatReload() {
int prev_temp_range_min = _temp_range.min;
int prev_temp_range_max = _temp_range.max;
commonSetup();
if (_temp_range.min != prev_temp_range_min)
notifyRangeChanged(true);
if (_temp_range.max != prev_temp_range_max)
notifyRangeChanged(false);
}
#if WEB_SUPPORT
//------------------------------------------------------------------------------
void _thermostatWebSocketOnSend(JsonObject& root) {
root["thermostatVisible"] = 1;
root[NAME_TEMP_RANGE_MIN] = _temp_range.min;
root[NAME_TEMP_RANGE_MAX] = _temp_range.max;
root[NAME_REMOTE_SENSOR_NAME] = _thermostat.remote_sensor_name;
root[NAME_REMOTE_TEMP_MAX_WAIT] = _thermostat_remote_temp_max_wait / MILLIS_IN_SEC;
root[NAME_MAX_ON_TIME] = _thermostat_max_on_time / MILLIS_IN_MIN;
root[NAME_MIN_OFF_TIME] = _thermostat_min_off_time / MILLIS_IN_MIN;
root[NAME_ALONE_ON_TIME] = _thermostat_alone_on_time / MILLIS_IN_MIN;
root[NAME_ALONE_OFF_TIME] = _thermostat_alone_off_time / MILLIS_IN_MIN;
root[NAME_BURN_TODAY] = _thermostat_burn_today;
root[NAME_BURN_YESTERDAY] = _thermostat_burn_yesterday;
root[NAME_BURN_THIS_MONTH] = _thermostat_burn_this_month;
root[NAME_BURN_PREV_MONTH] = _thermostat_burn_prev_month;
root[NAME_BURN_TOTAL] = _thermostat_burn_total;
if (_thermostat.temperature_source == temp_remote) {
root[NAME_OPERATION_MODE] = "remote temperature";
root["remoteTmp"] = _remote_temp.temp;
} else if (_thermostat.temperature_source == temp_local) {
root[NAME_OPERATION_MODE] = "local temperature";
root["remoteTmp"] = "?";
} else {
root[NAME_OPERATION_MODE] = "autonomous";
root["remoteTmp"] = "?";
}
}
//------------------------------------------------------------------------------
bool _thermostatWebSocketOnReceive(const char * key, JsonVariant& value) {
if (strncmp(key, NAME_TEMP_RANGE_MIN, strlen(NAME_TEMP_RANGE_MIN)) == 0) return true;
if (strncmp(key, NAME_TEMP_RANGE_MAX, strlen(NAME_TEMP_RANGE_MAX)) == 0) return true;
if (strncmp(key, NAME_REMOTE_SENSOR_NAME, strlen(NAME_REMOTE_SENSOR_NAME)) == 0) return true;
if (strncmp(key, NAME_REMOTE_TEMP_MAX_WAIT, strlen(NAME_REMOTE_TEMP_MAX_WAIT)) == 0) return true;
if (strncmp(key, NAME_MAX_ON_TIME, strlen(NAME_MAX_ON_TIME)) == 0) return true;
if (strncmp(key, NAME_MIN_OFF_TIME, strlen(NAME_MIN_OFF_TIME)) == 0) return true;
if (strncmp(key, NAME_ALONE_ON_TIME, strlen(NAME_ALONE_ON_TIME)) == 0) return true;
if (strncmp(key, NAME_ALONE_OFF_TIME, strlen(NAME_ALONE_OFF_TIME)) == 0) return true;
return false;
}
//------------------------------------------------------------------------------
void _thermostatWebSocketOnAction(uint32_t client_id, const char * action, JsonObject& data) {
if (strcmp(action, "thermostat_reset_counters") == 0) resetBurnCounters();
}
#endif
//------------------------------------------------------------------------------
void thermostatSetup() {
thermostatConfigure();
#if MQTT_SUPPORT
thermostatSetupMQTT();
#endif
// Websockets
#if WEB_SUPPORT
wsOnSendRegister(_thermostatWebSocketOnSend);
wsOnReceiveRegister(_thermostatWebSocketOnReceive);
wsOnActionRegister(_thermostatWebSocketOnAction);
#endif
espurnaRegisterLoop(thermostatLoop);
espurnaRegisterReload(_thermostatReload);
}
//------------------------------------------------------------------------------
void sendTempRangeRequest() {
DEBUG_MSG_P(PSTR("[THERMOSTAT] sendTempRangeRequest\n"));
mqttSend(MQTT_TOPIC_ASK_TEMP_RANGE, "", true);
}
//------------------------------------------------------------------------------
void setThermostatState(bool state) {
DEBUG_MSG_P(PSTR("[THERMOSTAT] setThermostatState: %s\n"), state ? "ON" : "OFF");
relayStatus(THERMOSTAT_RELAY, state, mqttForward(), false);
_thermostat.last_switch = millis();
// Send thermostat change state event to subscribers
for (unsigned char i = 0; i < _thermostat_callbacks.size(); i++) {
(_thermostat_callbacks[i])(state);
}
}
//------------------------------------------------------------------------------
void debugPrintSwitch(bool state, double temp) {
char tmp_str[6];
dtostrf(temp, 1-sizeof(tmp_str), 1, tmp_str);
DEBUG_MSG_P(PSTR("[THERMOSTAT] switch %s, temp: %s, min: %d, max: %d, relay: %s, last switch %d\n"),
state ? "ON" : "OFF", tmp_str, _temp_range.min, _temp_range.max, relayStatus(THERMOSTAT_RELAY) ? "ON" : "OFF", millis() - _thermostat.last_switch);
}
//------------------------------------------------------------------------------
inline bool lastSwitchEarlierThan(unsigned int comparing_time) {
return millis() - _thermostat.last_switch > comparing_time;
}
//------------------------------------------------------------------------------
inline void switchThermostat(bool state, double temp) {
debugPrintSwitch(state, temp);
setThermostatState(state);
}
//------------------------------------------------------------------------------
//----------- Main function that make decision ---------------------------------
//------------------------------------------------------------------------------
void checkTempAndAdjustRelay(double temp) {
// if thermostat switched ON and t > max - switch it OFF and start cooling
if (relayStatus(THERMOSTAT_RELAY) && temp > _temp_range.max) {
_thermostat_cycle = cooling;
switchThermostat(false, temp);
// if thermostat switched ON for max time - switch it OFF for rest
} else if (relayStatus(THERMOSTAT_RELAY) && lastSwitchEarlierThan(_thermostat_max_on_time)) {
switchThermostat(false, temp);
// if t < min and thermostat switched OFF for at least minimum time - switch it ON and start
} else if (!relayStatus(THERMOSTAT_RELAY) && temp < _temp_range.min
&& (_thermostat.last_switch == 0 || lastSwitchEarlierThan(_thermostat_min_off_time))) {
_thermostat_cycle = heating;
switchThermostat(true, temp);
// if heating cycle and thermostat switchaed OFF for more than min time - switch it ON
// continue heating cycle
} else if (!relayStatus(THERMOSTAT_RELAY) && _thermostat_cycle == heating
&& lastSwitchEarlierThan(_thermostat_min_off_time)) {
switchThermostat(true, temp);
}
}
//------------------------------------------------------------------------------
void updateCounters() {
if (relayStatus(THERMOSTAT_RELAY)) {
setSetting(NAME_BURN_TOTAL, ++_thermostat_burn_total);
setSetting(NAME_BURN_TODAY, ++_thermostat_burn_today);
setSetting(NAME_BURN_THIS_MONTH, ++_thermostat_burn_this_month);
}
if (ntpSynced()) {
String value = NTP.getDateStr();
unsigned int day = value.substring(0, 2).toInt();
unsigned int month = value.substring(3, 5).toInt();
if (day != _thermostat_burn_day) {
_thermostat_burn_yesterday = _thermostat_burn_today;
_thermostat_burn_today = 0;
_thermostat_burn_day = day;
setSetting(NAME_BURN_YESTERDAY, _thermostat_burn_yesterday);
setSetting(NAME_BURN_TODAY, _thermostat_burn_today);
setSetting(NAME_BURN_DAY, _thermostat_burn_day);
}
if (month != _thermostat_burn_month) {
_thermostat_burn_prev_month = _thermostat_burn_this_month;
_thermostat_burn_this_month = 0;
_thermostat_burn_month = month;
setSetting(NAME_BURN_PREV_MONTH, _thermostat_burn_prev_month);
setSetting(NAME_BURN_THIS_MONTH, _thermostat_burn_this_month);
setSetting(NAME_BURN_MONTH, _thermostat_burn_month);
}
}
}
//------------------------------------------------------------------------------
double getLocalTemperature() {
#if SENSOR_SUPPORT
for (byte i=0; i<magnitudeCount(); i++) {
if (magnitudeType(i) == MAGNITUDE_TEMPERATURE) {
double temp = magnitudeValue(i);
char tmp_str[6];
dtostrf(temp, 1-sizeof(tmp_str), 1, tmp_str);
DEBUG_MSG_P(PSTR("[THERMOSTAT] getLocalTemperature temp: %s\n"), tmp_str);
return temp > -0.1 && temp < 0.1 ? DBL_MIN : temp;
}
}
#endif
return DBL_MIN;
}
//------------------------------------------------------------------------------
double getLocalHumidity() {
#if SENSOR_SUPPORT
for (byte i=0; i<magnitudeCount(); i++) {
if (magnitudeType(i) == MAGNITUDE_HUMIDITY) {
double hum = magnitudeValue(i);
char tmp_str[4];
dtostrf(hum, 1-sizeof(tmp_str), 0, tmp_str);
DEBUG_MSG_P(PSTR("[THERMOSTAT] getLocalHumidity hum: %s\%\n"), tmp_str);
return hum > -0.1 && hum < 0.1 ? DBL_MIN : hum;
}
}
#endif
return DBL_MIN;
}
//------------------------------------------------------------------------------
// Loop
//------------------------------------------------------------------------------
void thermostatLoop(void) {
// Update temperature range
if (mqttConnected()) {
if (millis() - _temp_range.ask_time > _temp_range.ask_interval) {
_temp_range.ask_time = millis();
sendTempRangeRequest();
}
}
// Update thermostat state
if (millis() - _thermostat.last_update > THERMOSTAT_STATE_UPDATE_INTERVAL) {
_thermostat.last_update = millis();
updateCounters();
unsigned int last_temp_src = _thermostat.temperature_source;
if (_remote_temp.last_update != 0 && millis() - _remote_temp.last_update < _thermostat_remote_temp_max_wait) {
// we have remote temp
_thermostat.temperature_source = temp_remote;
DEBUG_MSG_P(PSTR("[THERMOSTAT] setup thermostat by remote temperature\n"));
checkTempAndAdjustRelay(_remote_temp.temp);
} else if (getLocalTemperature() != DBL_MIN) {
// we have local temp
_thermostat.temperature_source = temp_local;
DEBUG_MSG_P(PSTR("[THERMOSTAT] setup thermostat by local temperature\n"));
checkTempAndAdjustRelay(getLocalTemperature());
// updateRemoteTemp(false);
} else {
// we don't have any temp - switch thermostat on for N minutes every hour
_thermostat.temperature_source = temp_none;
DEBUG_MSG_P(PSTR("[THERMOSTAT] setup thermostat by timeout\n"));
if (relayStatus(THERMOSTAT_RELAY) && millis() - _thermostat.last_switch > _thermostat_alone_on_time) {
setThermostatState(false);
} else if (!relayStatus(THERMOSTAT_RELAY) && millis() - _thermostat.last_switch > _thermostat_alone_off_time) {
setThermostatState(false);
}
}
if (last_temp_src != _thermostat.temperature_source) {
updateOperationMode();
}
}
}
//------------------------------------------------------------------------------
String getBurnTimeStr(unsigned int burn_time) {
char burnTimeStr[18] = { 0 };
if (burn_time < 60) {
sprintf(burnTimeStr, "%d мин.", burn_time);
} else {
sprintf(burnTimeStr, "%d ч. %d мин.", (int)floor(burn_time / 60), burn_time % 60);
}
return String(burnTimeStr);
}
//------------------------------------------------------------------------------
void resetBurnCounters() {
DEBUG_MSG_P(PSTR("[THERMOSTAT] resetBurnCounters\n"));
setSetting(NAME_BURN_TOTAL, 0);
setSetting(NAME_BURN_TODAY, 0);
setSetting(NAME_BURN_YESTERDAY, 0);
setSetting(NAME_BURN_THIS_MONTH, 0);
setSetting(NAME_BURN_PREV_MONTH, 0);
_thermostat_burn_total = 0;
_thermostat_burn_today = 0;
_thermostat_burn_yesterday = 0;
_thermostat_burn_this_month = 0;
_thermostat_burn_prev_month = 0;
}
#endif // THERMOSTAT_SUPPORT
//#######################################################################
// ___ _ _
// | \ (_) ___ _ __ | | __ _ _ _
// | |) || |(_-<| '_ \| |/ _` || || |
// |___/ |_|/__/| .__/|_|\__,_| \_, |
// |_| |__/
//#######################################################################
#if THERMOSTAT_DISPLAY_SUPPORT
#include "SSD1306.h" // alias for `#include "SSD1306Wire.h"`
#define wifi_on_width 16
#define wifi_on_height 16
const char wifi_on_bits[] PROGMEM = {
0x00, 0x00, 0x0E, 0x00, 0x7E, 0x00, 0xFE, 0x01, 0xE0, 0x03, 0x80, 0x07,
0x02, 0x0F, 0x1E, 0x1E, 0x3E, 0x1C, 0x78, 0x38, 0xE0, 0x38, 0xC0, 0x31,
0xC6, 0x71, 0x8E, 0x71, 0x8E, 0x73, 0x00, 0x00, };
#define mqtt_width 16
#define mqtt_height 16
const char mqtt_bits[] PROGMEM = {
0x00, 0x00, 0x00, 0x08, 0x00, 0x18, 0x00, 0x38, 0xEA, 0x7F, 0xEA, 0x7F,
0x00, 0x38, 0x10, 0x18, 0x18, 0x08, 0x1C, 0x00, 0xFE, 0x57, 0xFE, 0x57,
0x1C, 0x00, 0x18, 0x00, 0x10, 0x00, 0x00, 0x00, };
#define remote_temp_width 16
#define remote_temp_height 16
const char remote_temp_bits[] PROGMEM = {
0x00, 0x00, 0xE0, 0x18, 0x10, 0x25, 0x10, 0x25, 0x90, 0x19, 0x50, 0x01,
0x50, 0x01, 0xD0, 0x01, 0x50, 0x01, 0x50, 0x01, 0xD0, 0x01, 0x50, 0x01,
0xE0, 0x00, 0xE0, 0x00, 0xE0, 0x00, 0x00, 0x00, };
#define server_width 16
#define server_height 16
const char server_bits[] PROGMEM = {
0x00, 0x00, 0xF8, 0x1F, 0xFC, 0x3F, 0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30,
0x0C, 0x30, 0x0C, 0x30, 0x0C, 0x30, 0xF8, 0x1F, 0xFC, 0x3F, 0xFE, 0x7F,
0x1E, 0x78, 0xFE, 0x7F, 0xFC, 0x3F, 0x00, 0x00, };
#define LOCAL_TEMP_UPDATE_INTERVAL 60000
#define LOCAL_HUM_UPDATE_INTERVAL 61000
SSD1306 display(0x3c, 1, 3);
unsigned long _local_temp_last_update = 0xFFFF;
unsigned long _local_hum_last_update = 0xFFFF;
bool _display_wifi_status = true;
bool _display_mqtt_status = true;
bool _display_server_status = true;
bool _display_remote_temp_status = true;
bool _display_need_refresh = false;
bool _temp_range_need_update = true;
//------------------------------------------------------------------------------
void drawIco(int16_t x, int16_t y, const char *ico, bool on = true) {
display.drawIco16x16(x, y, ico, !on);
_display_need_refresh = true;
}
//------------------------------------------------------------------------------
void display_wifi_status(bool on) {
_display_wifi_status = on;
drawIco(0, 0, wifi_on_bits, on);
}
//------------------------------------------------------------------------------
void display_mqtt_status(bool on) {
_display_mqtt_status = on;
drawIco(17, 0, mqtt_bits, on);
}
//------------------------------------------------------------------------------
void display_server_status(bool on) {
_display_server_status = on;
drawIco(34, 0, server_bits, on);
}
//------------------------------------------------------------------------------
void display_remote_temp_status(bool on) {
_display_remote_temp_status = on;
drawIco(51, 0, remote_temp_bits, on);
}
//------------------------------------------------------------------------------
void display_temp_range() {
_temp_range.need_display_update = false;
display.setColor(BLACK);
display.fillRect(68, 0, 60, 16);
display.setColor(WHITE);
display.setTextAlignment(TEXT_ALIGN_RIGHT);
display.setFont(ArialMT_Plain_16);
String temp_range = String(_temp_range.min) + "°- " + String(_temp_range.max) + "°";
display.drawString(128, 0, temp_range);
_display_need_refresh = true;
}
//------------------------------------------------------------------------------
void display_remote_temp() {
_remote_temp.need_display_update = false;
display.setColor(BLACK);
display.fillRect(0, 16, 128, 16);
display.setColor(WHITE);
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_LEFT);
String temp_range_title = String("Remote t");
display.drawString(0, 16, temp_range_title);
String temp_range_vol = String("= ") + (_display_remote_temp_status ? String(_remote_temp.temp, 1) : String("?")) + "°";
display.drawString(75, 16, temp_range_vol);
_display_need_refresh = true;
}
//------------------------------------------------------------------------------
void display_local_temp() {
display.setColor(BLACK);
display.fillRect(0, 32, 128, 16);
display.setColor(WHITE);
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_LEFT);
String local_temp_title = String("Local t");
display.drawString(0, 32, local_temp_title);
String local_temp_vol = String("= ") + (getLocalTemperature() != DBL_MIN ? String(getLocalTemperature(), 1) : String("?")) + "°";
display.drawString(75, 32, local_temp_vol);
_display_need_refresh = true;
}
//------------------------------------------------------------------------------
void display_local_humidity() {
display.setColor(BLACK);
display.fillRect(0, 48, 128, 16);
display.setColor(WHITE);
display.setFont(ArialMT_Plain_16);
display.setTextAlignment(TEXT_ALIGN_LEFT);
String local_hum_title = String("Local h ");
display.drawString(0, 48, local_hum_title);
String local_hum_vol = String("= ") + (getLocalHumidity() != DBL_MIN ? String(getLocalHumidity(), 0) : String("?")) + "%";
display.drawString(75, 48, local_hum_vol);
_display_need_refresh = true;
}
//------------------------------------------------------------------------------
// Setup
//------------------------------------------------------------------------------
void displaySetup() {
display.init();
display.flipScreenVertically();
// display.setFont(ArialMT_Plain_24);
// display.setTextAlignment(TEXT_ALIGN_CENTER);
// display.drawString(64, 17, "Thermostat");
espurnaRegisterLoop(displayLoop);
}
//------------------------------------------------------------------------------
void displayLoop() {
_display_need_refresh = false;
//------------------------------------------------------------------------------
// Indicators
//------------------------------------------------------------------------------
if (!_display_wifi_status) {
if (wifiConnected() && WiFi.getMode() != WIFI_AP)
display_wifi_status(true);
} else if (!wifiConnected() || WiFi.getMode() == WIFI_AP) {
display_wifi_status(false);
}
if (!_display_mqtt_status) {
if (mqttConnected())
display_mqtt_status(true);
} else if (!mqttConnected()) {
display_mqtt_status(false);
}
if (millis() - _temp_range.last_update < THERMOSTAT_SERVER_LOST_INTERVAL) {
if (!_display_server_status)
display_server_status(true);
} else if (_display_server_status) {
display_server_status(false);
}
if (millis() - _remote_temp.last_update < _thermostat_remote_temp_max_wait) {
if (!_display_remote_temp_status)
display_remote_temp_status(true);
} else if (_display_remote_temp_status) {
display_remote_temp_status(false);
display_remote_temp();
}
//------------------------------------------------------------------------------
// Temp range
//------------------------------------------------------------------------------
if (_temp_range.need_display_update) {
display_temp_range();
}
//------------------------------------------------------------------------------
// Remote temp
//------------------------------------------------------------------------------
if (_remote_temp.need_display_update) {
display_remote_temp();
}
//------------------------------------------------------------------------------
// Local temp
//------------------------------------------------------------------------------
if (millis() - _local_temp_last_update > LOCAL_TEMP_UPDATE_INTERVAL) {
_local_temp_last_update = millis();
display_local_temp();
}
//------------------------------------------------------------------------------
// Local temp
//------------------------------------------------------------------------------
if (millis() - _local_hum_last_update > LOCAL_HUM_UPDATE_INTERVAL) {
_local_hum_last_update = millis();
display_local_humidity();
}
//------------------------------------------------------------------------------
// Display update
//------------------------------------------------------------------------------
if (_display_need_refresh) {
yield();
display.display();
}
}
#endif // THERMOSTAT_DISPLAY_SUPPORT

+ 19
- 2
code/espurna/utils.ino View File

@ -165,7 +165,9 @@ namespace Heartbeat {
Board = 1 << 15, Board = 1 << 15,
Loadavg = 1 << 16, Loadavg = 1 << 16,
Interval = 1 << 17, Interval = 1 << 17,
Description = 1 << 18
Description = 1 << 18,
Range = 1 << 19,
Remote_temp = 1 << 20
}; };
constexpr uint32_t defaultValue() { constexpr uint32_t defaultValue() {
@ -186,7 +188,9 @@ namespace Heartbeat {
(Version * (HEARTBEAT_REPORT_VERSION)) | \ (Version * (HEARTBEAT_REPORT_VERSION)) | \
(Board * (HEARTBEAT_REPORT_BOARD)) | \ (Board * (HEARTBEAT_REPORT_BOARD)) | \
(Loadavg * (HEARTBEAT_REPORT_LOADAVG)) | \ (Loadavg * (HEARTBEAT_REPORT_LOADAVG)) | \
(Interval * (HEARTBEAT_REPORT_INTERVAL));
(Interval * (HEARTBEAT_REPORT_INTERVAL)) | \
(Range * (HEARTBEAT_REPORT_RANGE)) | \
(Remote_temp * (HEARTBEAT_REPORT_REMOTE_TEMP));
} }
uint32_t currentValue() { uint32_t currentValue() {
@ -295,6 +299,19 @@ void heartbeat() {
if (hb_cfg & Heartbeat::Loadavg) if (hb_cfg & Heartbeat::Loadavg)
mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str()); mqttSend(MQTT_TOPIC_LOADAVG, String(systemLoadAverage()).c_str());
#if THERMOSTAT_SUPPORT
if (hb_cfg & Heartbeat::Range) {
mqttSend(MQTT_TOPIC_HOLD_TEMP "_" MQTT_TOPIC_HOLD_TEMP_MIN, String(_temp_range.min).c_str());
mqttSend(MQTT_TOPIC_HOLD_TEMP "_" MQTT_TOPIC_HOLD_TEMP_MAX, String(_temp_range.max).c_str());
}
if (hb_cfg & Heartbeat::Remote_temp) {
char remote_temp[6];
dtostrf(_remote_temp.temp, 1-sizeof(remote_temp), 1, remote_temp);
mqttSend(MQTT_TOPIC_REMOTE_TEMP, String(remote_temp).c_str());
}
#endif
} else if (!serial && _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) { } else if (!serial && _heartbeat_mode == HEARTBEAT_REPEAT_STATUS) {
mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true); mqttSend(MQTT_TOPIC_STATUS, MQTT_STATUS_ONLINE, true);
} }


+ 4
- 0
code/html/custom.css View File

@ -213,6 +213,10 @@ div.state {
margin-left: 5px; margin-left: 5px;
} }
.button-thermostat-reset-counters {
background: rgb(204, 139, 41);
}
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
Sliders Sliders
-------------------------------------------------------------------------- */ -------------------------------------------------------------------------- */


+ 27
- 0
code/html/custom.js View File

@ -364,6 +364,29 @@ function getJson(str) {
} }
} }
function checkTempRangeMin() {
var min = parseInt($("#tempRangeMinInput").val(), 10);
var max = parseInt($("#tempRangeMaxInput").val(), 10);
if (min > max - 1) {
$("#tempRangeMinInput").val(max - 1);
}
}
function checkTempRangeMax() {
var min = parseInt($("#tempRangeMinInput").val(), 10);
var max = parseInt($("#tempRangeMaxInput").val(), 10);
if (max < min + 1) {
$("#tempRangeMaxInput").val(min + 1);
}
}
function doResetThermostatCounters(ask) {
var question = (typeof ask === "undefined" || false === ask) ?
null :
"Are you sure you want to reset burning counters?";
return doAction(question, "thermostat_reset_counters");
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Actions // Actions
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -1536,6 +1559,9 @@ function processData(data) {
var days = uptime; var days = uptime;
value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s"; value = days + "d " + zeroPad(hours, 2) + "h " + zeroPad(minutes, 2) + "m " + zeroPad(seconds, 2) + "s";
} }
if ("tmpUnits" == key) {
$("span.tmpUnit").html(data[key] == 1 ? "ºF" : "ºC");
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Matching // Matching
@ -1709,6 +1735,7 @@ $(function() {
$(".button-settings-factory").on("click", doFactoryReset); $(".button-settings-factory").on("click", doFactoryReset);
$("#uploader").on("change", onFileUpload); $("#uploader").on("change", onFileUpload);
$(".button-upgrade").on("click", doUpgrade); $(".button-upgrade").on("click", doUpgrade);
$(".button-thermostat-reset-counters").on('click', doResetThermostatCounters);
$(".button-apikey").on("click", generateAPIKey); $(".button-apikey").on("click", generateAPIKey);
$(".button-upgrade-browse").on("click", function() { $(".button-upgrade-browse").on("click", function() {


+ 132
- 6
code/html/index.html View File

@ -96,6 +96,10 @@
<a href="#" class="pure-menu-link" data="panel-general">GENERAL</a> <a href="#" class="pure-menu-link" data="panel-general">GENERAL</a>
</li> </li>
<li class="pure-menu-item module module-thermostat">
<a href="#" class="pure-menu-link" data="panel-thermostat">THERMOSTAT</a>
</li>
<li class="pure-menu-item module module-dcz"> <li class="pure-menu-item module module-dcz">
<a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a> <a href="#" class="pure-menu-link" data="panel-domoticz">DOMOTICZ</a>
</li> </li>
@ -183,9 +187,9 @@
<div class="footer"> <div class="footer">
&copy; 2016-2019<br /> &copy; 2016-2019<br />
Xose Pérez<br/> Xose Pérez<br/>
<a href="https://twitter.com/xoseperez" target="_blank">@xoseperez</a><br/>
<a href="http://tinkerman.cat" target="_blank">http://tinkerman.cat</a><br/>
<a href="https://github.com/xoseperez/espurna" target="_blank">ESPurna @ GitHub</a><br/>
<a href="https://twitter.com/xoseperez" rel="noopener" target="_blank">@xoseperez</a><br/>
<a href="http://tinkerman.cat" rel="noopener" target="_blank">http://tinkerman.cat</a><br/>
<a href="https://github.com/xoseperez/espurna" rel="noopener" target="_blank">ESPurna @ GitHub</a><br/>
GPLv3 license<br/> GPLv3 license<br/>
</div> </div>
@ -661,7 +665,7 @@
<div class="pure-u-1-4 pure-u-lg-1-8"><button type="button" class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div> <div class="pure-u-1-4 pure-u-lg-1-8"><button type="button" class="pure-button button-upgrade-browse pure-u-23-24">Browse</button></div>
<div class="pure-u-1-4 pure-u-lg-1-8"><button type="button" class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div> <div class="pure-u-1-4 pure-u-lg-1-8"><button type="button" class="pure-button button-upgrade pure-u-23-24">Upgrade</button></div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" target="_blank"><strong>two-step update</strong></a>.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">The device has <span name="free_size"></span> bytes available for OTA updates. If your image is larger than this consider doing a <a href="https://github.com/xoseperez/espurna/wiki/TwoStepUpdates" rel="noopener" target="_blank"><strong>two-step update</strong></a>.</div>
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4"><progress id="upgrade-progress"></progress></div> <div class="pure-u-1 pure-u-lg-3-4"><progress id="upgrade-progress"></progress></div>
<input name="upgrade" type="file" tabindex="17" /> <input name="upgrade" type="file" tabindex="17" />
@ -907,7 +911,7 @@
<div class="pure-u-0 pure-u-lg-1-4"></div> <div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint"> <div class="pure-u-1 pure-u-lg-3-4 hint">
This is the fingerprint for the SSL certificate of the server.<br /> This is the fingerprint for the SSL certificate of the server.<br />
You can get it using <a href="https://www.grc.com/fingerprints.htm" target="_blank">https://www.grc.com/fingerprints.htm</a><br />
You can get it using <a href="https://www.grc.com/fingerprints.htm" rel="noopener" target="_blank">https://www.grc.com/fingerprints.htm</a><br />
or using openssl from a linux box by typing:<br /> or using openssl from a linux box by typing:<br />
<pre>$ openssl s_client -connect &lt;host&gt;:&lt;port&gt; &lt; /dev/null 2&gt;/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin</pre> <pre>$ openssl s_client -connect &lt;host&gt;:&lt;port&gt; &lt; /dev/null 2&gt;/dev/null | openssl x509 -fingerprint -noout -in /dev/stdin</pre>
</div> </div>
@ -1024,6 +1028,128 @@
</div> </div>
</form> </form>
<form id="form-thermostat" class="pure-form form-settings">
<div class="panel" id="panel-thermostat">
<div class="header">
<h1>THERMOSTAT</h1>
<h2>Thermostat configuration</h2>
</div>
<div class="page">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="thermostatOperationMode">Operation mode</label>
<input class="pure-u-1 pure-u-lg-1-4" name="thermostatOperationMode" type="text" readonly />
</div>
<fieldset>
<legend>Temperature range</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Max (<span class="tmpUnit"></span>)</label>
<input class="pure-u-1 pure-u-lg-1-4" id="tempRangeMaxInput" name="tempRangeMax" type="number" min="1" max="100" tabindex="32" data="20" onchange="checkTempRangeMax()" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Min (<span class="tmpUnit"></span>)</label>
<input class="pure-u-1 pure-u-lg-1-4" id="tempRangeMinInput" name="tempRangeMin" type="number" min="0" max="99" tabindex="31" data="10" onchange="checkTempRangeMin()" />
</div>
</fieldset>
<fieldset>
<legend>Remote sensor</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="remoteSensorName">Remote sensor name</label>
<input class="pure-u-1 pure-u-lg-1-4" name="remoteSensorName" type="text" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="remoteTmp">Remote temperature (<span class="tmpUnit"></span>)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="remoteTmp" type="text" readonly />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="remoteTempMaxWait">Remote temperature waiting (s)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="remoteTempMaxWait" type="number" min="0" max="1800" tabindex="33" data="120" />
</div>
</fieldset>
<fieldset>
<legend>Operation mode</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="maxOnTime">Max heating time (m)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="maxOnTime" type="number" min="0" max="180" tabindex="34" data="30" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="minOffTime">Min rest time (m)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="minOffTime" type="number" min="0" max="60" tabindex="35" data="10" />
</div>
</fieldset>
<fieldset>
<legend>Autonomous mode</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="aloneOnTime">Heating time (m)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="aloneOnTime" type="number" min="0" max="180" tabindex="36" data="5" />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="aloneOffTime">Rest time (m)</label>
<input class="pure-u-1 pure-u-lg-1-4" name="aloneOffTime" type="number" min="0" max="180" tabindex="37" data="55" />
</div>
</fieldset>
<fieldset>
<legend>Time worked</legend>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="burnToday">Today</label>
<input class="pure-u-1 pure-u-lg-1-4" type="text" name="burnToday" readonly />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="burnYesterday">Yesterday</label>
<input class="pure-u-1 pure-u-lg-1-4" type="text" name="burnYesterday" readonly />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="burnThisMonth">Current month</label>
<input class="pure-u-1 pure-u-lg-1-4" type="text" name="burnThisMonth" readonly />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="burnPrevMonth">Previous month</label>
<input class="pure-u-1 pure-u-lg-1-4" type="text" name="burnPrevMonth" readonly />
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4" for="burnTotal">Total</label>
<input class="pure-u-1 pure-u-lg-1-4" type="text" name="burnTotal" readonly />
</div>
<div class="pure-g">
<button type="button" class="pure-button button-thermostat-reset-counters">Reset counters</button>
</div>
</fieldset>
</div>
</div>
</form>
<form id="form-domoticz" class="pure-form form-settings"> <form id="form-domoticz" class="pure-form form-settings">
<div class="panel" id="panel-domoticz"> <div class="panel" id="panel-domoticz">
@ -1457,7 +1583,7 @@
To learn a new code click <strong>LEARN</strong> (the Sonoff RFBridge will beep) then press a button on the remote, the new code should show up (and the RFBridge will double beep). If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br /> To learn a new code click <strong>LEARN</strong> (the Sonoff RFBridge will beep) then press a button on the remote, the new code should show up (and the RFBridge will double beep). If the device double beeps but the code does not update it has not been properly learnt. Keep trying.<br /><br />
Modify or create new codes manually and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br /> Modify or create new codes manually and then click <strong>SAVE</strong> to store them in the device memory. If your controlled device uses the same code to switch ON and OFF, learn the code with the ON button and copy paste it to the OFF input box, then click SAVE on the last one to store the value.<br /><br />
Delete any code clicking the <strong>FORGET</strong> button. Delete any code clicking the <strong>FORGET</strong> button.
<span class="module module-rfbraw"><br /><br />You can also specify 116-chars long RAW codes. Raw codes require a <a target="_blank" href="https://github.com/rhx/RF-Bridge-EFM8BB1">specific firmware for for the EFM8BB1</a>.</span>
<span class="module module-rfbraw"><br /><br />You can also specify 116-chars long RAW codes. Raw codes require a <a rel="noopener" target="_blank" href="https://github.com/rhx/RF-Bridge-EFM8BB1">specific firmware for for the EFM8BB1</a>.</span>
</h2> </h2>
</div> </div>


+ 1
- 0
code/platformio.ini View File

@ -102,6 +102,7 @@ lib_deps =
https://github.com/sparkfun/SparkFun_VEML6075_Arduino_Library#V_1.0.3 https://github.com/sparkfun/SparkFun_VEML6075_Arduino_Library#V_1.0.3
https://github.com/pololu/vl53l1x-arduino#1.0.1 https://github.com/pololu/vl53l1x-arduino#1.0.1
https://github.com/mcleng/MAX6675-Library#2.0.1 https://github.com/mcleng/MAX6675-Library#2.0.1
https://github.com/ElderJoy/esp8266-oled-ssd1306#4.0.1
lib_ignore = lib_ignore =
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------


Loading…
Cancel
Save