Browse Source

Merge remote-tracking branch 'xoseperez/dev' into lightfox-dual

rules-rpn
Andrey F. Kupreychik 5 years ago
parent
commit
609e047f95
54 changed files with 17688 additions and 17440 deletions
  1. +2
    -0
      .gitattributes
  2. +26
    -0
      CHANGELOG.md
  3. +3
    -3
      README.md
  4. +1
    -1
      code/espurna/alexa.ino
  5. +1
    -1
      code/espurna/api.ino
  6. +1
    -1
      code/espurna/button.ino
  7. +1
    -0
      code/espurna/config/arduino.h
  8. +69
    -0
      code/espurna/config/hardware.h
  9. +19
    -1
      code/espurna/config/sensors.h
  10. +4
    -0
      code/espurna/config/types.h
  11. +1
    -1
      code/espurna/config/version.h
  12. BIN
      code/espurna/data/index.all.html.gz
  13. BIN
      code/espurna/data/index.light.html.gz
  14. BIN
      code/espurna/data/index.lightfox.html.gz
  15. BIN
      code/espurna/data/index.rfbridge.html.gz
  16. BIN
      code/espurna/data/index.rfm69.html.gz
  17. BIN
      code/espurna/data/index.sensor.html.gz
  18. BIN
      code/espurna/data/index.small.html.gz
  19. +3
    -3
      code/espurna/debug.ino
  20. +64
    -75
      code/espurna/domoticz.ino
  21. +1
    -1
      code/espurna/espurna.ino
  22. +26
    -6
      code/espurna/homeassistant.ino
  23. +1
    -1
      code/espurna/ir.ino
  24. +1
    -1
      code/espurna/led.ino
  25. +1
    -1
      code/espurna/libs/RFM69Wrap.h
  26. +1
    -1
      code/espurna/libs/StreamInjector.h
  27. +2
    -1
      code/espurna/light.ino
  28. +24
    -2
      code/espurna/migrate.ino
  29. +1
    -1
      code/espurna/mqtt.ino
  30. +1
    -1
      code/espurna/nofuss.ino
  31. +1
    -1
      code/espurna/ntp.ino
  32. +1
    -1
      code/espurna/ota.ino
  33. +23
    -10
      code/espurna/relay.ino
  34. +4
    -2
      code/espurna/rfbridge.ino
  35. +5
    -4
      code/espurna/sensor.ino
  36. +9
    -1
      code/espurna/sensors/MHZ19Sensor.h
  37. +5
    -1
      code/espurna/sensors/PZEM004TSensor.h
  38. +2228
    -2227
      code/espurna/static/index.all.html.gz.h
  39. +2180
    -2179
      code/espurna/static/index.light.html.gz.h
  40. +2592
    -2591
      code/espurna/static/index.lightfox.html.gz.h
  41. +2218
    -2217
      code/espurna/static/index.rfbridge.html.gz.h
  42. +3196
    -3195
      code/espurna/static/index.rfm69.html.gz.h
  43. +2282
    -2282
      code/espurna/static/index.sensor.html.gz.h
  44. +2592
    -2591
      code/espurna/static/index.small.html.gz.h
  45. +3
    -12
      code/espurna/system.ino
  46. +1
    -1
      code/espurna/terminal.ino
  47. +18
    -3
      code/espurna/utils.ino
  48. +3
    -2
      code/espurna/web.ino
  49. +6
    -4
      code/espurna/wifi.ino
  50. +1
    -1
      code/espurna/ws.ino
  51. +1
    -1
      code/gulpfile.js
  52. +12
    -10
      code/html/custom.js
  53. +3
    -2
      code/html/index.html
  54. +50
    -0
      code/platformio.ini

+ 2
- 0
.gitattributes View File

@ -0,0 +1,2 @@
*.gz.h -diff -merge
*.gz.h linguist-generated=true

+ 26
- 0
CHANGELOG.md View File

@ -3,6 +3,32 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.13.6] Unreleased
### Fixed
### Added
### Changed
## [1.13.5] 2019-02-27
### Fixed
- Revert loopDelay dependency on wifi sleep mode (#1574)
- Fix hardcoded serial objects in _debugSendSerial, terminalLoop and PZEM sensor (#1573)
- Fix RFBridge not showing codes in web UI as per @mcspr suggested change (#1571)
- Fix BSSIDs in scan output (#1567)
- Fix PZEM004TSensor pointer use
- RFBridge: fix webui codes parsing
- Avoid websocket ping back on fw upgrade via web UI form (#1574)
- Revert loopDelay dependency on wifi sleep mode
- Removing line break before templated variable to fix issue with Windows Arduino IDE (#1579, thanks to @AlbertWeterings)
- Send brightness to websocket
### Added
- Relay MQTT group receive-only sync mode setting
- Set wifi sleep mode from settings
- Add unique id and device support for better HA UI integration (#1547, thanks to @abmantis)
- Improved inline documentation of BMX280 settings (#1585, thanks to CraigMarkwardt)
## [1.13.4] 2019-02-21
### Fixed
- Travis fixes


+ 3
- 3
README.md View File

@ -3,10 +3,10 @@
ESPurna ("spark" in Catalan) is a custom firmware for ESP8285/ESP8266 based smart switches, lights and sensors.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.13.4-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-master-orange.svg)](https://github.com/xoseperez/espurna/tree/master/)
[![version](https://img.shields.io/badge/version-1.13.6--dev-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.com/xoseperez/espurna/tree/dev/)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=master)](https://travis-ci.org/xoseperez/espurna)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://api.codacy.com/project/badge/Grade/c9496e25cf07434cba786b462cb15f49)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![downloads](https://img.shields.io/github/downloads/xoseperez/espurna/total.svg)](https://github.com/xoseperez/espurna/releases)
<br />


+ 1
- 1
code/espurna/alexa.ino View File

@ -2,7 +2,7 @@
ALEXA MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/espurna/api.ino View File

@ -2,7 +2,7 @@
API MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/espurna/button.ino View File

@ -2,7 +2,7 @@
BUTTON MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 0
code/espurna/config/arduino.h View File

@ -116,6 +116,7 @@
//#define GOSUND_WS1
//#define ARILUX_AL_LC02_V14
//#define BLITZWOLF_BWSHPX_V23
//#define DIGOO_NX_SP202
//#define FOXEL_LIGHTFOX_DUAL
//--------------------------------------------------------------------------------


+ 69
- 0
code/espurna/config/hardware.h View File

@ -3369,6 +3369,75 @@
#define HLW8012_POWER_RATIO 3414290
#define HLW8012_INTERRUPT_ON FALLING
// ----------------------------------------------------------------------------------------
// Hama WiFi Steckdose (00176533)
// https://at.hama.com/00176533/hama-wifi-steckdose-3500w-16a
// ----------------------------------------------------------------------------------------
#elif defined(HAMA_WIFI_STECKDOSE_00176533)
// Info
#define MANUFACTURER "HAMA"
#define DEVICE "WIFI_STECKDOSE_00176533"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 4
#define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// Oxaoxe NX-SP202
// Digoo NX-SP202 (not tested)
// Digoo DG-SP202 (not tested)
// https://github.com/xoseperez/espurna/issues/1502
// -----------------------------------------------------------------------------
#elif defined(DIGOO_NX_SP202)
// Info
#define MANUFACTURER "DIGOO"
#define DEVICE "NX_SP202"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
#define BUTTON2_PIN 16
#define BUTTON2_MODE BUTTON_PUSHBUTTON | BUTTON_SET_PULLUP | BUTTON_DEFAULT_HIGH
#define BUTTON2_RELAY 2
// Relays
#define RELAY1_PIN 15
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 14
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// HJL01 / BL0937
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 1
#endif
#define HLW8012_SEL_PIN 12
#define HLW8012_CF1_PIN 5
#define HLW8012_CF_PIN 4
#define HLW8012_SEL_CURRENT LOW
#define HLW8012_CURRENT_RATIO 23296
#define HLW8012_VOLTAGE_RATIO 310085
#define HLW8012_POWER_RATIO 3368471
#define HLW8012_INTERRUPT_ON FALLING
// -----------------------------------------------------------------------------
// Foxel's LightFox dual
// https://github.com/foxel/esp-dual-rf-switch


+ 19
- 1
code/espurna/config/sensors.h View File

@ -168,9 +168,27 @@
// 6 for 10ms, 7 for 20ms
#define BMX280_FILTER 0 // 0 for OFF, 1 for 2 values, 2 for 4 values, 3 for 8 values and 4 for 16 values
#define BMX280_TEMPERATURE 1 // Oversampling for temperature (set to 0 to disable magnitude)
// 0b000 = 0 = Skip measurement
// 0b001 = 1 = 1x 16bit/0.0050C resolution
// 0b010 = 2 = 2x 17bit/0.0025C
// 0b011 = 3 = 4x 18bit/0.0012C
// 0b100 = 4 = 8x 19bit/0.0006C
// 0b101 = 5 = 16x 20bit/0.0003C
#define BMX280_HUMIDITY 1 // Oversampling for humidity (set to 0 to disable magnitude, only for BME280)
// 0b000 = 0 = Skip measurement
// 0b001 = 1 = 1x 0.07% resolution
// 0b010 = 2 = 2x 0.05%
// 0b011 = 3 = 4x 0.04%
// 0b100 = 4 = 8x 0.03%
// 0b101 = 5 = 16x 0.02%
#define BMX280_PRESSURE 1 // Oversampling for pressure (set to 0 to disable magnitude)
// 0b000 = 0 = Skipped
// 0b001 = 1 = 1x 16bit/2.62 Pa resolution
// 0b010 = 2 = 2x 17bit/1.31 Pa
// 0b011 = 3 = 4x 18bit/0.66 Pa
// 0b100 = 4 = 8x 19bit/0.33 Pa
// 0b101 = 5 = 16x 20bit/0.16 Pa
//------------------------------------------------------------------------------
// Dallas OneWire temperature sensors
// Enable support by passing DALLAS_SUPPORT=1 build flag


+ 4
- 0
code/espurna/config/types.h View File

@ -96,6 +96,10 @@
#define RELAY_PROVIDER_RFBRIDGE 3
#define RELAY_PROVIDER_STM 4
#define RELAY_GROUP_SYNC_NORMAL 0
#define RELAY_GROUP_SYNC_INVERSE 1
#define RELAY_GROUP_SYNC_RECEIVEONLY 2
//------------------------------------------------------------------------------
// UDP SYSLOG
//------------------------------------------------------------------------------


+ 1
- 1
code/espurna/config/version.h View File

@ -1,5 +1,5 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.13.4"
#define APP_VERSION "1.13.6-dev"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"
#define CFG_VERSION 3

BIN
code/espurna/data/index.all.html.gz View File


BIN
code/espurna/data/index.light.html.gz View File


BIN
code/espurna/data/index.lightfox.html.gz View File


BIN
code/espurna/data/index.rfbridge.html.gz View File


BIN
code/espurna/data/index.rfm69.html.gz View File


BIN
code/espurna/data/index.sensor.html.gz View File


BIN
code/espurna/data/index.small.html.gz View File


+ 3
- 3
code/espurna/debug.ino View File

@ -2,7 +2,7 @@
DEBUG MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -19,9 +19,9 @@ char _udp_syslog_header[40] = {0};
#if DEBUG_SERIAL_SUPPORT
void _debugSendSerial(const char* prefix, const char* data) {
if (prefix && (prefix[0] != '\0')) {
Serial.print(prefix);
DEBUG_PORT.print(prefix);
}
Serial.print(data);
DEBUG_PORT.print(data);
}
#endif


+ 64
- 75
code/espurna/domoticz.ino View File

@ -2,7 +2,7 @@
DOMOTICZ MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -17,7 +17,7 @@ std::vector<bool> _dcz_relay_state;
// Private methods
//------------------------------------------------------------------------------
unsigned char _domoticzRelay(unsigned int idx) {
int _domoticzRelay(unsigned int idx) {
for (unsigned char relayID=0; relayID<relayCount(); relayID++) {
if (domoticzIdx(relayID) == idx) {
return relayID;
@ -46,6 +46,56 @@ void _domoticzStatus(unsigned char id, bool status) {
relayStatus(id, status);
}
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
void _domoticzLight(unsigned int idx, const JsonObject& root) {
if (!lightHasColor()) return;
JsonObject& color = root["Color"];
if (!color.success()) return;
// m field contains information about color mode (enum ColorMode from domoticz ColorSwitch.h):
unsigned int cmode = color["m"];
if (cmode == 3 || cmode == 4) { // ColorModeRGB or ColorModeCustom - see domoticz ColorSwitch.h
lightChannel(0, color["r"]);
lightChannel(1, color["g"]);
lightChannel(2, color["b"]);
// WARM WHITE (or MONOCHROME WHITE) and COLD WHITE are always sent.
// Apply only when supported.
if (lightChannels() > 3) {
lightChannel(3, color["ww"]);
}
if (lightChannels() > 4) {
lightChannel(4, color["cw"]);
}
// domoticz uses 100 as maximum value while we're using LIGHT_MAX_BRIGHTNESS
unsigned int brightness = (root["Level"].as<uint8_t>() / 100.0) * LIGHT_MAX_BRIGHTNESS;
lightBrightness(brightness);
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received rgb:%u,%u,%u ww:%u,cw:%u brightness:%u for IDX %u\n"),
color["r"].as<uint8_t>(),
color["g"].as<uint8_t>(),
color["b"].as<uint8_t>(),
color["ww"].as<uint8_t>(),
color["cw"].as<uint8_t>(),
brightness,
idx
);
lightUpdate(true, mqttForward());
}
}
#endif
void _domoticzMqtt(unsigned int type, const char * topic, const char * payload) {
if (!_dcz_enabled) return;
@ -78,79 +128,18 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
// IDX
unsigned int idx = root["idx"];
String stype = root["stype"];
if (
(stype.equals("RGB") || stype.equals("RGBW") || stype.equals("RGBWW"))
&& domoticzIdx(0) == idx
) {
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (lightHasColor()) {
// m field contains information about color mode (enum ColorMode from domoticz ColorSwitch.h):
unsigned int cmode = root["Color"]["m"];
unsigned int cval;
if (cmode == 3 || cmode == 4) { // ColorModeRGB or ColorModeCustom - see domoticz ColorSwitch.h
// RED
cval = root["Color"]["r"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received RED value %u for IDX %u\n"), cval, idx);
lightChannel(0, cval);
// GREEN
cval = root["Color"]["g"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received GREEN value %u for IDX %u\n"), cval, idx);
lightChannel(1, cval);
// BLUE
cval = root["Color"]["b"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received BLUE value %u for IDX %u\n"), cval, idx);
lightChannel(2, cval);
// WARM WHITE or MONOCHROME WHITE if supported
if (lightChannels() > 3) {
cval = root["Color"]["ww"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received WARM WHITE value %u for IDX %u\n"), cval, idx);
lightChannel(3, cval);
}
// COLD WHITE if supported
if (lightChannels() > 4) {
cval = root["Color"]["cw"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received COLD WHITE value %u for IDX %u\n"), cval, idx);
lightChannel(4, cval);
}
unsigned int brightness = root["Level"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received BRIGHTNESS value %u for IDX %u\n"), brightness, idx);
unsigned int br = (brightness / 100.0) * LIGHT_MAX_BRIGHTNESS;
DEBUG_MSG_P(PSTR("[DOMOTICZ] Calculated BRIGHTNESS value %u for IDX %u\n"), br, idx);
lightBrightness(br); // domoticz uses 100 as maximum value while we're using LIGHT_MAX_BRIGHTNESS
// update lights
lightUpdate(true, mqttForward());
}
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
_domoticzStatus(relayID, value > 0);
}
}
#else
DEBUG_MSG_P(PSTR("[DOMOTICZ] ESPurna compiled without LIGHT_PROVIDER"));
#endif
} else {
unsigned char relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
_domoticzStatus(relayID, value == 1);
#if LIGHT_PROVIDER != LIGHT_PROVIDER_NONE
if (stype.startsWith("RGB") && (domoticzIdx(0) == idx)) {
_domoticzLight(idx, root);
}
#endif
int relayID = _domoticzRelay(idx);
if (relayID >= 0) {
unsigned char value = root["nvalue"];
DEBUG_MSG_P(PSTR("[DOMOTICZ] Received value %u for IDX %u\n"), value, idx);
_domoticzStatus(relayID, value > 1);
}
}
@ -171,7 +160,7 @@ void _domoticzBrokerCallback(const unsigned char type, const char * topic, unsig
_dcz_relay_state[id] = status;
domoticzSendRelay(id, status);
}
}
#endif // BROKER_SUPPORT


+ 1
- 1
code/espurna/espurna.ino View File

@ -2,7 +2,7 @@
ESPurna
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


+ 26
- 6
code/espurna/homeassistant.ino View File

@ -38,10 +38,9 @@ void _haSendMagnitude(unsigned char i, JsonObject& config) {
config.set("platform", "mqtt");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = magnitudeUnits(type);
}
void _haSendMagnitudes() {
void _haSendMagnitudes(const JsonObject& deviceConfig) {
for (unsigned char i=0; i<magnitudeCount(); i++) {
@ -55,6 +54,9 @@ void _haSendMagnitudes() {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config);
config["uniq_id"] = getIdentifier() + "_" + magnitudeTopic(magnitudeType(i)) + "_" + String(i);
config["device"] = deviceConfig;
config.printTo(output);
jsonBuffer.clear();
}
@ -115,7 +117,7 @@ void _haSendSwitch(unsigned char i, JsonObject& config) {
}
void _haSendSwitches() {
void _haSendSwitches(const JsonObject& deviceConfig) {
#if (LIGHT_PROVIDER != LIGHT_PROVIDER_NONE) || (defined(ITEAD_SLAMPHER))
String type = String("light");
@ -135,6 +137,9 @@ void _haSendSwitches() {
DynamicJsonBuffer jsonBuffer;
JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config);
config["uniq_id"] = getIdentifier() + "_" + type + "_" + String(i);
config["device"] = deviceConfig;
config.printTo(output);
jsonBuffer.clear();
}
@ -241,7 +246,16 @@ void _haDumpConfig(std::function<void(String&)> printer, bool wrapJson = false)
}
#endif
}
void _haGetDeviceConfig(JsonObject& config) {
String identifier = getIdentifier();
config.createNestedArray("identifiers").add(identifier);
config["name"] = getSetting("desc", getSetting("hostname"));
config["manufacturer"] = String(MANUFACTURER);
config["model"] = String(DEVICE);
config["sw_version"] = String(APP_NAME) + " " + String(APP_VERSION) + " (" + getCoreVersion() + ")";
}
void _haSend() {
@ -254,12 +268,18 @@ void _haSend() {
DEBUG_MSG_P(PSTR("[HA] Sending autodiscovery MQTT message\n"));
// Get common device config
DynamicJsonBuffer jsonBuffer;
JsonObject& deviceConfig = jsonBuffer.createObject();
_haGetDeviceConfig(deviceConfig);
// Send messages
_haSendSwitches();
_haSendSwitches(deviceConfig);
#if SENSOR_SUPPORT
_haSendMagnitudes();
_haSendMagnitudes(deviceConfig);
#endif
jsonBuffer.clear();
_haSendFlag = false;
}


+ 1
- 1
code/espurna/ir.ino View File

@ -4,7 +4,7 @@ IR MODULE
Copyright (C) 2018 by Alexander Kolesnikov (raw and MQTT implementation)
Copyright (C) 2017-2018 by François Déchery
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
-----------------------------------------------------------------------------
Configuration


+ 1
- 1
code/espurna/led.ino View File

@ -2,7 +2,7 @@
LED MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/espurna/libs/RFM69Wrap.h View File

@ -3,7 +3,7 @@
RFM69Wrap
RFM69 by Felix Ruso (http://LowPowerLab.com/contact) wrapper for ESP8266
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


+ 1
- 1
code/espurna/libs/StreamInjector.h View File

@ -2,7 +2,7 @@
StreamInjector
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


+ 2
- 1
code/espurna/light.ino View File

@ -2,7 +2,7 @@
LIGHT MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -840,6 +840,7 @@ void _lightWebSocketStatus(JsonObject& root) {
for (unsigned char id=0; id < _light_channel.size(); id++) {
channels.add(lightChannel(id));
}
root["brightness"] = lightBrightness();
}
void _lightWebSocketOnSend(JsonObject& root) {


+ 24
- 2
code/espurna/migrate.ino View File

@ -2,7 +2,7 @@
MIGRATE MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -1268,9 +1268,31 @@ void migrate() {
setSetting("relayGPIO", 1, 15); // Left outlet
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(FOXEL_LIGHTFOX_DUAL)
#elif defined(DIGOO_NX_SP202)
setSetting("board", 95);
setSetting("ledGPIO", 0, 13);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("btnGPIO", 1, 16);
setSetting("btnRelay", 1, 1);
setSetting("relayGPIO", 0, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayGPIO", 1, 14);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("selGPIO", 12);
setSetting("cf1GPIO", 5);
setSetting("cfGPIO", 4);
setSetting("pwrRatioC", 23296);
setSetting("pwrRatioV", 310085);
setSetting("pwrRatioP", 3368471);
setSetting("hlwSelC", LOW);
setSetting("hlwIntM", FALLING);
#elif defined(FOXEL_LIGHTFOX_DUAL)
setSetting("board", 96);
setSetting("btnRelay", 0, 0);
setSetting("btnRelay", 1, 1);
setSetting("btnRelay", 2, 1);


+ 1
- 1
code/espurna/mqtt.ino View File

@ -2,7 +2,7 @@
MQTT MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/espurna/nofuss.ino View File

@ -2,7 +2,7 @@
NOFUSS MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/espurna/ntp.ino View File

@ -2,7 +2,7 @@
NTP MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/espurna/ota.ino View File

@ -2,7 +2,7 @@
OTA MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 23
- 10
code/espurna/relay.ino View File

@ -2,7 +2,7 @@
RELAY MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -480,6 +480,12 @@ unsigned char relayParsePayload(const char * payload) {
// BACKWARDS COMPATIBILITY
void _relayBackwards() {
for (unsigned int i=0; i<_relays.size(); i++) {
if (!hasSetting("mqttGroupInv", i)) continue;
setSetting("mqttGroupSync", i, getSetting("mqttGroupInv", i));
delSetting("mqttGroupInv", i);
}
byte relayMode = getSetting("relayMode", RELAY_BOOT_MODE).toInt();
byte relayPulseMode = getSetting("relayPulseMode", RELAY_PULSE_MODE).toInt();
float relayPulseTime = getSetting("relayPulseTime", RELAY_PULSE_TIME).toFloat();
@ -638,7 +644,7 @@ void _relayWebSocketSendRelays() {
#if MQTT_SUPPORT
JsonArray& group = relays.createNestedArray("group");
JsonArray& group_inverse = relays.createNestedArray("group_inv");
JsonArray& group_sync = relays.createNestedArray("group_sync");
JsonArray& on_disconnect = relays.createNestedArray("on_disc");
#endif
@ -654,7 +660,7 @@ void _relayWebSocketSendRelays() {
#if MQTT_SUPPORT
group.add(getSetting("mqttGroup", i, ""));
group_inverse.add(getSetting("mqttGroupInv", i, 0).toInt() == 1);
group_sync.add(getSetting("mqttGroupSync", i, 0).toInt() == 1);
on_disconnect.add(getSetting("relayOnDisc", i, 0).toInt());
#endif
}
@ -809,6 +815,18 @@ void relaySetupAPI() {
#if MQTT_SUPPORT
void _relayMQTTGroup(unsigned char id) {
String topic = getSetting("mqttGroup", id, "");
if (!topic.length()) return;
unsigned char mode = getSetting("mqttGroupSync", id, RELAY_GROUP_SYNC_NORMAL).toInt();
if (mode == RELAY_GROUP_SYNC_RECEIVEONLY) return;
bool status = relayStatus(id);
if (mode == RELAY_GROUP_SYNC_INVERSE) status = !status;
mqttSendRaw(topic.c_str(), status ? RELAY_MQTT_ON : RELAY_MQTT_OFF);
}
void relayMQTT(unsigned char id) {
if (id >= _relays.size()) return;
@ -822,12 +840,7 @@ void relayMQTT(unsigned char id) {
// Check group topic
if (_relays[id].group_report) {
_relays[id].group_report = false;
String t = getSetting("mqttGroup", id, "");
if (t.length() > 0) {
bool status = relayStatus(id);
if (getSetting("mqttGroupInv", id, 0).toInt() == 1) status = !status;
mqttSendRaw(t.c_str(), status ? RELAY_MQTT_ON : RELAY_MQTT_OFF);
}
_relayMQTTGroup(id);
}
// Send speed for IFAN02
@ -954,7 +967,7 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (value == 0xFF) return;
if (value < 2) {
if (getSetting("mqttGroupInv", i, 0).toInt() == 1) {
if (getSetting("mqttGroupSync", i, RELAY_GROUP_SYNC_NORMAL).toInt() == RELAY_GROUP_SYNC_INVERSE) {
value = 1 - value;
}
}


+ 4
- 2
code/espurna/rfbridge.ino View File

@ -2,7 +2,7 @@
RF MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -89,6 +89,7 @@ static bool _rfbToChar(byte * in, char * out, int n = RF_MESSAGE_SIZE) {
#if WEB_SUPPORT
void _rfbWebSocketSendCodeArray(unsigned char start, unsigned char size) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
@ -104,7 +105,8 @@ void _rfbWebSocketSendCodeArray(unsigned char start, unsigned char size) {
off.add(rfbRetrieve(id, false));
}
wsSend(rfb);
wsSend(root);
}
void _rfbWebSocketSendCode(unsigned char id) {


+ 5
- 4
code/espurna/sensor.ino View File

@ -2,7 +2,7 @@
SENSOR MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -102,8 +102,7 @@ double _magnitudeProcess(unsigned char type, double value) {
#if WEB_SUPPORT
template<typename T>
void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
template<typename T> void _sensorWebSocketMagnitudes(JsonObject& root, T prefix) {
// ws produces flat list <prefix>Magnitudes
String ws_name = String(prefix);
@ -639,6 +638,8 @@ void _sensorLoad() {
MHZ19Sensor * sensor = new MHZ19Sensor();
sensor->setRX(MHZ19_RX_PIN);
sensor->setTX(MHZ19_TX_PIN);
if (getSetting("mhz19CalibrateAuto", 0).toInt() == 1)
sensor->setCalibrateAuto(true);
_sensors.push_back(sensor);
}
#endif
@ -1403,7 +1404,7 @@ void sensorLoop() {
// Get the first relay state
#if SENSOR_POWER_CHECK_STATUS
bool relay_off = (relayCount() > 0) && (relayStatus(0) == 0);
bool relay_off = (relayCount() == 1) && (relayStatus(0) == 0);
#endif
// Get readings


+ 9
- 1
code/espurna/sensors/MHZ19Sensor.h View File

@ -78,7 +78,7 @@ class MHZ19Sensor : public BaseSensor {
_serial = new SoftwareSerial(_pin_rx, _pin_tx, false, 32);
_serial->enableIntTx(false);
_serial->begin(9600);
calibrateAuto(false);
calibrateAuto(_calibrateAuto);
_ready = true;
_dirty = false;
@ -139,6 +139,13 @@ class MHZ19Sensor : public BaseSensor {
_write(buffer);
}
void setCalibrateAuto(boolean value) {
_calibrateAuto = value;
if (_ready) {
calibrateAuto(value);
}
}
protected:
// ---------------------------------------------------------------------
@ -214,6 +221,7 @@ class MHZ19Sensor : public BaseSensor {
double _co2 = 0;
unsigned int _pin_rx;
unsigned int _pin_tx;
bool _calibrateAuto = false;
SoftwareSerial * _serial = NULL;
};


+ 5
- 1
code/espurna/sensors/PZEM004TSensor.h View File

@ -247,7 +247,11 @@ class PZEM004TSensor : public BaseSensor {
_busy = true;
// Clear buffer in case of late response(Timeout)
while(Serial.available() > 0) Serial.read();
if (_serial) {
while(_serial->available() > 0) _serial->read();
} else {
// This we cannot do it from outside the library
}
float read;
float* readings_p;


+ 2228
- 2227
code/espurna/static/index.all.html.gz.h
File diff suppressed because it is too large
View File


+ 2180
- 2179
code/espurna/static/index.light.html.gz.h
File diff suppressed because it is too large
View File


+ 2592
- 2591
code/espurna/static/index.lightfox.html.gz.h
File diff suppressed because it is too large
View File


+ 2218
- 2217
code/espurna/static/index.rfbridge.html.gz.h
File diff suppressed because it is too large
View File


+ 3196
- 3195
code/espurna/static/index.rfm69.html.gz.h
File diff suppressed because it is too large
View File


+ 2282
- 2282
code/espurna/static/index.sensor.html.gz.h
File diff suppressed because it is too large
View File


+ 2592
- 2591
code/espurna/static/index.small.html.gz.h
File diff suppressed because it is too large
View File


+ 3
- 12
code/espurna/system.ino View File

@ -10,9 +10,7 @@ Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
// -----------------------------------------------------------------------------
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
unsigned long _loop_delay = 0;
#endif
bool _system_send_heartbeat = false;
unsigned char _heartbeat_mode = HEARTBEAT_MODE;
@ -75,11 +73,9 @@ bool systemGetHeartbeat() {
return _system_send_heartbeat;
}
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
unsigned long systemLoopDelay() {
return _loop_delay;
}
#endif
unsigned long systemLoadAverage() {
return _load_average;
@ -160,10 +156,7 @@ void systemLoop() {
// -------------------------------------------------------------------------
// Power saving delay
// -------------------------------------------------------------------------
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
delay(_loop_delay);
#endif
if (_loop_delay) delay(_loop_delay);
}
@ -202,10 +195,8 @@ void systemSetup() {
_systemSetupSpecificHardware();
// Cache loop delay value to speed things (recommended max 250ms)
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
_loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = constrain(_loop_delay, 0, 300);
#endif
_loop_delay = atol(getSetting("loopDelay", LOOP_DELAY_TIME).c_str());
_loop_delay = constrain(_loop_delay, 0, 300);
// Register Loop
espurnaRegisterLoop(systemLoop);


+ 1
- 1
code/espurna/terminal.ino View File

@ -221,7 +221,7 @@ void _terminalLoop() {
#if SERIAL_RX_ENABLED
while (SERIAL_RX_PORT.available() > 0) {
char rc = Serial.read();
char rc = SERIAL_RX_PORT.read();
_serial_rx_buffer[_serial_rx_pointer++] = rc;
if ((_serial_rx_pointer == TERMINAL_BUFFER_SIZE) || (rc == 10)) {
terminalInject(_serial_rx_buffer, (size_t) _serial_rx_pointer);


+ 18
- 3
code/espurna/utils.ino View File

@ -368,6 +368,16 @@ void infoMemory(const char * name, unsigned int total_memory, unsigned int free_
}
const char* _info_wifi_sleep_mode(WiFiSleepType_t type) {
switch (type) {
case WIFI_NONE_SLEEP: return "NONE";
case WIFI_LIGHT_SLEEP: return "LIGHT";
case WIFI_MODEM_SLEEP: return "MODEM";
default: return "UNKNOWN";
}
}
void info() {
DEBUG_MSG_P(PSTR("\n\n---8<-------\n\n"));
@ -469,9 +479,14 @@ void info() {
#if ADC_MODE_VALUE == ADC_VCC
DEBUG_MSG_P(PSTR("[MAIN] Power: %u mV\n"), ESP.getVcc());
#endif
#if WIFI_SLEEP_MODE != WIFI_NONE_SLEEP
if (systemLoopDelay()) {
DEBUG_MSG_P(PSTR("[MAIN] Power saving delay value: %lu ms\n"), systemLoopDelay());
#endif
}
const WiFiSleepType_t sleep_mode = WiFi.getSleepMode();
if (sleep_mode != WIFI_NONE_SLEEP) {
DEBUG_MSG_P(PSTR("[MAIN] WiFi Sleep Mode: %s\n"), _info_wifi_sleep_mode(sleep_mode));
}
// -------------------------------------------------------------------------
@ -606,4 +621,4 @@ bool isNumber(const char * s) {
}
}
return digit;
}
}

+ 3
- 2
code/espurna/web.ino View File

@ -2,7 +2,7 @@
WEBSERVER MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -329,7 +329,8 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
#endif
}
} else {
DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len);
//Removed to avoid websocket ping back during upgrade (see #1574)
//DEBUG_MSG_P(PSTR("[UPGRADE] Progress: %u bytes\r"), index + len);
}
}


+ 6
- 4
code/espurna/wifi.ino View File

@ -2,7 +2,7 @@
WIFI MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/
@ -75,6 +75,10 @@ void _wifiConfigure() {
jw.enableScan(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1);
unsigned char sleep_mode = getSetting("wifiSleep", WIFI_SLEEP_MODE).toInt();
sleep_mode = constrain(sleep_mode, 0, 2);
WiFi.setSleepMode(static_cast<WiFiSleepType_t>(sleep_mode));
}
void _wifiScan(uint32_t client_id = 0) {
@ -116,7 +120,7 @@ void _wifiScan(uint32_t client_id = 0) {
snprintf_P(buffer, sizeof(buffer),
PSTR("BSSID: %02X:%02X:%02X:%02X:%02X:%02X SEC: %s RSSI: %3d CH: %2d SSID: %s"),
BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5], BSSID_scan[6],
BSSID_scan[0], BSSID_scan[1], BSSID_scan[2], BSSID_scan[3], BSSID_scan[4], BSSID_scan[5],
(sec_scan != ENC_TYPE_NONE ? "YES" : "NO "),
rssi_scan,
chan_scan,
@ -603,8 +607,6 @@ void wifiRegister(wifi_callback_f callback) {
void wifiSetup() {
WiFi.setSleepMode(WIFI_SLEEP_MODE);
_wifiInject();
_wifiConfigure();


+ 1
- 1
code/espurna/ws.ino View File

@ -2,7 +2,7 @@
WEBSOCKET MODULE
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
*/


+ 1
- 1
code/gulpfile.js View File

@ -2,7 +2,7 @@
ESP8266 file system builder
Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
Copyright (C) 2016-2019 by Xose Pérez <xose dot perez at gmail dot com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by


+ 12
- 10
code/html/custom.js View File

@ -246,7 +246,7 @@ function addValue(data, name, value) {
"ssid", "pass", "gw", "mask", "ip", "dns",
"schEnabled", "schSwitch","schAction","schType","schHour","schMinute","schWDs","schUTC",
"relayBoot", "relayPulse", "relayTime",
"mqttGroup", "mqttGroupInv", "relayOnDisc",
"mqttGroup", "mqttGroupSync", "relayOnDisc",
"dczRelayIdx", "dczMagnitude",
"tspkRelay", "tspkMagnitude",
"ledMode",
@ -958,8 +958,8 @@ function initRelayConfig(data) {
if ("group" in data) {
$("input[name='mqttGroup']", line).val(data.group[i]);
}
if ("group_inv" in data) {
$("input[name='mqttGroupInv']", line).val(data.group_inv[i]);
if ("group_sync" in data) {
$("input[name='mqttGroupSync']", line).val(data.group_sync[i]);
}
if ("on_disc" in data) {
$("input[name='relayOnDisc']", line).val(data.on_disc[i]);
@ -1261,19 +1261,21 @@ function processData(data) {
if ("rfb" === key) {
var rfb = data.rfb;
var size = data.size;
var start = data.start;
var on = rfb["on"];
var off = rfb["off"];
var size = rfb.size;
var start = rfb.start;
for (var i=start; i<start+size; ++i) {
$("input[name='rfbcode'][data-id='" + i + "'][data-status='1']").val(on[i]);
$("input[name='rfbcode'][data-id='" + i + "'][data-status='0']").val(off[i]);
var processOn = ((rfb.on !== undefined) && (rfb.on.length > 0));
var processOff = ((rfb.off !== undefined) && (rfb.off.length > 0));
for (var i=0; i<size; ++i) {
if (processOn) $("input[name='rfbcode'][data-id='" + (i + start) + "'][data-status='1']").val(rfb.on[i]);
if (processOff) $("input[name='rfbcode'][data-id='" + (i + start) + "'][data-status='0']").val(rfb.off[i]);
}
return;
}
<!-- endRemoveIf(!rfbridge)-->
// ---------------------------------------------------------------------


+ 3
- 2
code/html/index.html View File

@ -187,7 +187,7 @@
</div>
<div class="footer">
&copy; 2016-2018<br />
&copy; 2016-2019<br />
Xose Pérez<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/>
@ -1682,9 +1682,10 @@
</div>
<div class="pure-g module module-mqtt">
<div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group sync</label></div>
<select class="pure-u-1 pure-u-lg-3-4" name="mqttGroupInv">
<select class="pure-u-1 pure-u-lg-3-4" name="mqttGroupSync">
<option value="0">Same</option>
<option value="1">Inverse</option>
<option value="2">Receive Only</option>
</select>
</div>
<div class="pure-g module module-mqtt">


+ 50
- 0
code/platformio.ini View File

@ -2925,6 +2925,32 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:digoo-nx-sp202]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DDIGOO_NX_SP202
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:digoo-nx-sp202-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DDIGOO_NX_SP202
upload_speed = ${common.upload_speed}
monitor_speed = ${common.monitor_speed}
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
extra_scripts = ${common.extra_scripts}
[env:homecube-16a]
platform = ${common.platform}
framework = ${common.framework}
@ -3201,3 +3227,27 @@ upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:hama-wifi-steckdose-00176533]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DHAMA_WIFI_STECKDOSE_00176533
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}
[env:hama-wifi-steckdose-00176533-ota]
platform = ${common.platform}
framework = ${common.framework}
board = ${common.board_1m}
board_build.flash_mode = ${common.flash_mode}
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m0m} -DHAMA_WIFI_STECKDOSE_00176533
upload_port = ${common.upload_port}
upload_flags = ${common.upload_flags}
monitor_speed = ${common.monitor_speed}
extra_scripts = ${common.extra_scripts}

Loading…
Cancel
Save