Browse Source

Merge branch 'dev' into master

fastled^2
Xose Pérez 6 years ago
committed by GitHub
parent
commit
c567dd220d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
70 changed files with 6897 additions and 4275 deletions
  1. +41
    -0
      .github/stale.yml
  2. +1
    -0
      .gitignore
  3. +9
    -4
      README.md
  4. +4
    -4
      code/eagle.flash.1m0m1s.ld
  5. +19
    -0
      code/eagle.flash.1m0m2s.ld
  6. +21
    -0
      code/eagle.flash.4m1m4s.ld
  7. +20
    -0
      code/eagle.flash.4m3m4e.ld
  8. +18
    -0
      code/eagle.flash.512k0m1s.ld
  9. +6
    -1
      code/espurna/api.ino
  10. +22
    -13
      code/espurna/button.ino
  11. +1
    -1
      code/espurna/config/all.h
  12. +6
    -0
      code/espurna/config/arduino.h
  13. +26
    -1
      code/espurna/config/defaults.h
  14. +30
    -17
      code/espurna/config/general.h
  15. +212
    -2
      code/espurna/config/hardware.h
  16. +178
    -4
      code/espurna/config/progmem.h
  17. +7
    -0
      code/espurna/config/prototypes.h
  18. +40
    -2
      code/espurna/config/sensors.h
  19. +20
    -1
      code/espurna/config/types.h
  20. +1
    -1
      code/espurna/config/version.h
  21. BIN
      code/espurna/data/index.html.gz
  22. +44
    -34
      code/espurna/debug.ino
  23. +12
    -0
      code/espurna/domoticz.ino
  24. +86
    -0
      code/espurna/eeprom.ino
  25. +4
    -1
      code/espurna/espurna.ino
  26. +13
    -4
      code/espurna/homeassistant.ino
  27. +1
    -1
      code/espurna/influxdb.ino
  28. +30
    -28
      code/espurna/led.ino
  29. +636
    -0
      code/espurna/libs/fs_math.c
  30. +116
    -0
      code/espurna/libs/fs_math.h
  31. +0
    -0
      code/espurna/libs/pwm.c
  32. +87
    -45
      code/espurna/light.ino
  33. +1
    -1
      code/espurna/mdns.ino
  34. +74
    -0
      code/espurna/migrate.ino
  35. +22
    -11
      code/espurna/mqtt.ino
  36. +11
    -1
      code/espurna/ota.ino
  37. +96
    -24
      code/espurna/relay.ino
  38. +33
    -9
      code/espurna/rfbridge.ino
  39. +13
    -1
      code/espurna/sensor.ino
  40. +9
    -3
      code/espurna/sensors/CSE7766Sensor.h
  41. +15
    -8
      code/espurna/sensors/EmonSensor.h
  42. +298
    -0
      code/espurna/sensors/GeigerSensor.h
  43. +8
    -11
      code/espurna/sensors/PZEM004TSensor.h
  44. +4
    -1
      code/espurna/sensors/V9261FSensor.h
  45. +49
    -36
      code/espurna/settings.ino
  46. +3251
    -3268
      code/espurna/static/index.html.gz.h
  47. +4
    -6
      code/espurna/system.ino
  48. +10
    -1
      code/espurna/telnet.ino
  49. +3
    -1
      code/espurna/uartmqtt.ino
  50. +82
    -166
      code/espurna/utils.ino
  51. +25
    -7
      code/espurna/web.ino
  52. +216
    -52
      code/espurna/wifi.ino
  53. +67
    -14
      code/espurna/ws.ino
  54. +2
    -0
      code/gulpfile.js
  55. +94
    -25
      code/html/custom.js
  56. +29
    -9
      code/html/index.html
  57. +2
    -2
      code/html/vendor/pure-1.0.0.min.css
  58. +2
    -2
      code/html/vendor/pure-grids-responsive-1.0.0.min.css
  59. +28
    -21
      code/ota.py
  60. +115
    -0
      code/package-lock.json
  61. +1
    -0
      code/package.json
  62. +565
    -431
      code/platformio.ini
  63. +57
    -0
      code/symbols.sh
  64. BIN
      images/devices/geiger_espurna_configuration.png
  65. BIN
      images/devices/geiger_espurna_status.png
  66. BIN
      images/devices/geiger_grafana_dashboard.png
  67. BIN
      images/devices/geiger_scope_following_pulses.png
  68. BIN
      images/devices/geiger_scope_single_pulse.png
  69. BIN
      images/devices/geiger_wiring_diagram.png
  70. BIN
      images/devices/tonbux-mosquito-killer.jpg

+ 41
- 0
.github/stale.yml View File

@ -0,0 +1,41 @@
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 120
# Number of days of inactivity before a stale Issue or Pull Request is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 30
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- enhancement
- bug
- staged for release
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: false
# Label to use when marking as stale
staleLabel: stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in 30 days if no further activity occurs.
Thank you for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here.
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue will be auto-closed because there hasn't been any activity for a few months. Feel free to open a new one if you still experience this problem.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
only: issues

+ 1
- 0
.gitignore View File

@ -14,3 +14,4 @@ custom.h
.python .python
.env .env
.DS_Store .DS_Store
.vscode

+ 9
- 4
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. 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. It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
[![version](https://img.shields.io/badge/version-1.12.6-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-master-orange.svg)](https://github.org/xoseperez/espurna/tree/master/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=master)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/master.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![version](https://img.shields.io/badge/version-1.13.0c-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-dev-orange.svg)](https://github.org/xoseperez/espurna/tree/dev/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=dev)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/dev.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)
[![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE) [![license](https://img.shields.io/github/license/xoseperez/espurna.svg)](LICENSE)
<br /> <br />
[![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest) [![donate](https://img.shields.io/badge/donate-PayPal-blue.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=xose%2eperez%40gmail%2ecom&lc=US&no_note=0&currency_code=EUR&bn=PP%2dDonationsBF%3abtn_donate_LG%2egif%3aNonHostedGuest)
@ -243,10 +243,15 @@ Here is the list of supported hardware. For more information please refer to the
|**Heltec Touch Relay**|**Generic Relay v4.0**|**Generic RGBLed v1.0**| |**Heltec Touch Relay**|**Generic Relay v4.0**|**Generic RGBLed v1.0**|
|![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)|| |![Generic DHT11 v1.0](images/devices/generic-dht11-10.jpg)|![Generic DS18B20 v1.0](images/devices/generic-ds18b20-10.jpg)||
|**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**|| |**Generic DHT11 v1.0**|**Generic DS18B20 v1.0**||
|![Tonbux Mosquito Killer](images/devices/tonbux-mosquito-killer.jpg)|||
|**Tonbux Mosquito Killer**|||
**Other supported boards:** **Other supported boards:**
IteadStudio Sonoff S31, IteadStudio Sonoff POW R2, Zhilde ZLD-EU55-W, Luani HVIO IteadStudio Sonoff S31, IteadStudio Sonoff POW R2, Zhilde ZLD-EU55-W, Luani HVIO
**Other supported boards (beta):**
KMC 4 Outlet, Gosund WS1, Smart Dual Plug, MakerFocus Intelligent Module LM33 for Lamps
## License ## License
Copyright (C) 2016-2018 by Xose Pérez (@xoseperez) Copyright (C) 2016-2018 by Xose Pérez (@xoseperez)


code/esp8266.flash.1m0.ld → code/eagle.flash.1m0m1s.ld View File

@ -1,4 +1,4 @@
/* Flash Split for 1M chips, no SPIFFS */
/* Flash Split for 1M chips */
/* sketch 999KB */ /* sketch 999KB */
/* eeprom 20KB */ /* eeprom 20KB */
@ -12,7 +12,7 @@ MEMORY
PROVIDE ( _SPIFFS_start = 0x402FB000 ); PROVIDE ( _SPIFFS_start = 0x402FB000 );
PROVIDE ( _SPIFFS_end = 0x402FB000 ); PROVIDE ( _SPIFFS_end = 0x402FB000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
INCLUDE "esp8266.flash.common.ld"
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 19
- 0
code/eagle.flash.1m0m2s.ld View File

@ -0,0 +1,19 @@
/* Flash Split for 1M chips, no SPIFFS */
/* sketch 995KB */
/* eeprom 8KB */
/* reserved 16KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xf8ff0
}
PROVIDE ( _SPIFFS_start = 0x402FA000 );
PROVIDE ( _SPIFFS_end = 0x402FA000 );
PROVIDE ( _SPIFFS_page = 0 );
PROVIDE ( _SPIFFS_block = 0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 21
- 0
code/eagle.flash.4m1m4s.ld View File

@ -0,0 +1,21 @@
/* Flash Split for 4M chips */
/* sketch 1019KB */
/* empty/ota? 2048KB */
/* spiffs 992KB */
/* eeprom 16KB */
/* reserved 16KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xfeff0
}
PROVIDE ( _SPIFFS_start = 0x40500000 );
PROVIDE ( _SPIFFS_end = 0x405F8000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 20
- 0
code/eagle.flash.4m3m4e.ld View File

@ -0,0 +1,20 @@
/* Flash Split for 4M chips */
/* sketch 1019KB */
/* spiffs 3040KB */
/* eeprom 16KB */
/* reserved 16KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0xfeff0
}
PROVIDE ( _SPIFFS_start = 0x40300000 );
PROVIDE ( _SPIFFS_end = 0x405F8000 );
PROVIDE ( _SPIFFS_page = 0x100 );
PROVIDE ( _SPIFFS_block = 0x2000 );
INCLUDE "../ld/eagle.app.v6.common.ld"

+ 18
- 0
code/eagle.flash.512k0m1s.ld View File

@ -0,0 +1,18 @@
/* Flash Split for 512K chips */
/* sketch 487KB */
/* eeprom 20KB */
MEMORY
{
dport0_0_seg : org = 0x3FF00000, len = 0x10
dram0_0_seg : org = 0x3FFE8000, len = 0x14000
iram1_0_seg : org = 0x40100000, len = 0x8000
irom0_0_seg : org = 0x40201010, len = 0x79ff0
}
PROVIDE ( _SPIFFS_start = 0x4027B000 );
PROVIDE ( _SPIFFS_end = 0x4027B000 );
PROVIDE ( _SPIFFS_page = 0x0 );
PROVIDE ( _SPIFFS_block = 0x0 );
INCLUDE "../ld/eagle.app.v6.common.ld"

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

@ -102,7 +102,11 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
// Format response according to the Accept header // Format response according to the Accept header
if (_asJson(request)) { if (_asJson(request)) {
char buffer[64]; char buffer[64];
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
if (isNumber(value)) {
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
} else {
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": \"%s\" }"), api.key, value);
}
request->send(200, "application/json", buffer); request->send(200, "application/json", buffer);
} else { } else {
request->send(200, "text/plain", value); request->send(200, "text/plain", value);
@ -130,6 +134,7 @@ void _onAPIs(AsyncWebServerRequest *request) {
root[_apis[i].key] = String(buffer); root[_apis[i].key] = String(buffer);
} }
root.printTo(output); root.printTo(output);
jsonBuffer.clear();
request->send(200, "application/json", output); request->send(200, "application/json", output);
} else { } else {


+ 22
- 13
code/espurna/button.ino View File

@ -60,16 +60,18 @@ unsigned char buttonAction(unsigned char id, unsigned char event) {
if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F; if (event == BUTTON_EVENT_DBLCLICK) return (actions >> 8) & 0x0F;
if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F; if (event == BUTTON_EVENT_LNGCLICK) return (actions >> 12) & 0x0F;
if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F; if (event == BUTTON_EVENT_LNGLNGCLICK) return (actions >> 16) & 0x0F;
if (event == BUTTON_EVENT_TRIPLECLICK) return (actions >> 20) & 0x0F;
return BUTTON_MODE_NONE; return BUTTON_MODE_NONE;
} }
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick) {
unsigned long buttonStore(unsigned long pressed, unsigned long click, unsigned long dblclick, unsigned long lngclick, unsigned long lnglngclick, unsigned long tripleclick) {
unsigned int value; unsigned int value;
value = pressed; value = pressed;
value += click << 4; value += click << 4;
value += dblclick << 8; value += dblclick << 8;
value += lngclick << 12; value += lngclick << 12;
value += lnglngclick << 16; value += lnglngclick << 16;
value += tripleclick << 20;
return value; return value;
} }
@ -77,12 +79,13 @@ uint8_t mapEvent(uint8_t event, uint8_t count, uint16_t length) {
if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED; if (event == EVENT_PRESSED) return BUTTON_EVENT_PRESSED;
if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK; if (event == EVENT_CHANGED) return BUTTON_EVENT_CLICK;
if (event == EVENT_RELEASED) { if (event == EVENT_RELEASED) {
if (count == 1) {
if (1 == count) {
if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK; if (length > BUTTON_LNGLNGCLICK_DELAY) return BUTTON_EVENT_LNGLNGCLICK;
if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK; if (length > BUTTON_LNGCLICK_DELAY) return BUTTON_EVENT_LNGCLICK;
return BUTTON_EVENT_CLICK; return BUTTON_EVENT_CLICK;
} }
if (count == 2) return BUTTON_EVENT_DBLCLICK;
if (2 == count) return BUTTON_EVENT_DBLCLICK;
if (3 == count) return BUTTON_EVENT_TRIPLECLICK;
} }
return BUTTON_EVENT_NONE; return BUTTON_EVENT_NONE;
} }
@ -113,7 +116,13 @@ void buttonEvent(unsigned int id, unsigned char event) {
relayStatus(_buttons[id].relayID - 1, false); relayStatus(_buttons[id].relayID - 1, false);
} }
} }
if (action == BUTTON_MODE_AP) createAP();
if (action == BUTTON_MODE_AP) wifiStartAP();
#if defined(JUSTWIFI_ENABLE_WPS)
if (action == BUTTON_MODE_WPS) wifiStartWPS();
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_SMART_CONFIG) wifiStartSmartConfig();
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
if (action == BUTTON_MODE_RESET) { if (action == BUTTON_MODE_RESET) {
deferredReset(100, CUSTOM_RESET_HARDWARE); deferredReset(100, CUSTOM_RESET_HARDWARE);
} }
@ -129,7 +138,7 @@ void buttonSetup() {
#ifdef ITEAD_SONOFF_DUAL #ifdef ITEAD_SONOFF_DUAL
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
unsigned int actions = buttonStore(BUTTON_MODE_NONE, BUTTON_MODE_TOGGLE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE, BUTTON_MODE_NONE);
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1}); _buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 1});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2}); _buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, 2});
_buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY}); _buttons.push_back({new DebounceEvent(0, BUTTON_PUSHBUTTON), actions, BUTTON3_RELAY});
@ -140,49 +149,49 @@ void buttonSetup() {
#if BUTTON1_PIN != GPIO_NONE #if BUTTON1_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON1_PRESS, BUTTON1_CLICK, BUTTON1_DBLCLICK, BUTTON1_LNGCLICK, BUTTON1_LNGLNGCLICK, BUTTON1_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON1_PIN, BUTTON1_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON1_RELAY});
} }
#endif #endif
#if BUTTON2_PIN != GPIO_NONE #if BUTTON2_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON2_PRESS, BUTTON2_CLICK, BUTTON2_DBLCLICK, BUTTON2_LNGCLICK, BUTTON2_LNGLNGCLICK, BUTTON2_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON2_PIN, BUTTON2_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON2_RELAY});
} }
#endif #endif
#if BUTTON3_PIN != GPIO_NONE #if BUTTON3_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON3_PRESS, BUTTON3_CLICK, BUTTON3_DBLCLICK, BUTTON3_LNGCLICK, BUTTON3_LNGLNGCLICK, BUTTON3_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON3_PIN, BUTTON3_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON3_RELAY});
} }
#endif #endif
#if BUTTON4_PIN != GPIO_NONE #if BUTTON4_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON4_PRESS, BUTTON4_CLICK, BUTTON4_DBLCLICK, BUTTON4_LNGCLICK, BUTTON4_LNGLNGCLICK, BUTTON4_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON4_PIN, BUTTON4_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON4_RELAY});
} }
#endif #endif
#if BUTTON5_PIN != GPIO_NONE #if BUTTON5_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON5_PRESS, BUTTON5_CLICK, BUTTON5_DBLCLICK, BUTTON5_LNGCLICK, BUTTON5_LNGLNGCLICK, BUTTON5_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON5_PIN, BUTTON5_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON5_RELAY});
} }
#endif #endif
#if BUTTON6_PIN != GPIO_NONE #if BUTTON6_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON6_PRESS, BUTTON6_CLICK, BUTTON6_DBLCLICK, BUTTON6_LNGCLICK, BUTTON6_LNGLNGCLICK, BUTTON6_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON6_PIN, BUTTON6_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON6_RELAY});
} }
#endif #endif
#if BUTTON7_PIN != GPIO_NONE #if BUTTON7_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON7_PRESS, BUTTON7_CLICK, BUTTON7_DBLCLICK, BUTTON7_LNGCLICK, BUTTON7_LNGLNGCLICK, BUTTON7_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON7_PIN, BUTTON7_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON7_RELAY});
} }
#endif #endif
#if BUTTON8_PIN != GPIO_NONE #if BUTTON8_PIN != GPIO_NONE
{ {
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK);
unsigned int actions = buttonStore(BUTTON8_PRESS, BUTTON8_CLICK, BUTTON8_DBLCLICK, BUTTON8_LNGCLICK, BUTTON8_LNGLNGCLICK, BUTTON8_TRIPLECLICK);
_buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY}); _buttons.push_back({new DebounceEvent(BUTTON8_PIN, BUTTON8_MODE, BUTTON_DEBOUNCE_DELAY, btnDelay), actions, BUTTON8_RELAY});
} }
#endif #endif


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

@ -31,8 +31,8 @@
#include "general.h" #include "general.h"
#include "prototypes.h" #include "prototypes.h"
#include "sensors.h" #include "sensors.h"
#include "progmem.h"
#include "dependencies.h" #include "dependencies.h"
#include "progmem.h"
#include "debug.h" #include "debug.h"
#ifdef USE_CORE_VERSION_H #ifdef USE_CORE_VERSION_H


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

@ -80,6 +80,11 @@
//#define HELTEC_TOUCHRELAY //#define HELTEC_TOUCHRELAY
//#define ZHILDE_EU44_W //#define ZHILDE_EU44_W
//#define LUANI_HVIO //#define LUANI_HVIO
//#define ALLNET_4DUINO_IOT_WLAN_RELAIS
//#define TONBUX_MOSQUITO_KILLER
//#define NEO_COOLCAM_POWER_PLUG_WIFI
//#define ESTINK_WIFI_POWER_STRIP
//#define PILOTAK_ESP_DIN_V1
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Features (values below are non-default values) // Features (values below are non-default values)
@ -140,3 +145,4 @@
//#define SI7021_SUPPORT 1 //#define SI7021_SUPPORT 1
//#define TMP3X_SUPPORT 1 //#define TMP3X_SUPPORT 1
//#define V9261F_SUPPORT 1 //#define V9261F_SUPPORT 1
//#define GEIGER_SUPPORT 1

+ 26
- 1
code/espurna/config/defaults.h View File

@ -108,6 +108,31 @@
#define BUTTON8_DBLCLICK BUTTON_MODE_NONE #define BUTTON8_DBLCLICK BUTTON_MODE_NONE
#endif #endif
#ifndef BUTTON1_TRIPLECLICK
#define BUTTON1_TRIPLECLICK BUTTON_MODE_SMART_CONFIG
#endif
#ifndef BUTTON2_TRIPLECLICK
#define BUTTON2_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON3_TRIPLECLICK
#define BUTTON3_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON4_TRIPLECLICK
#define BUTTON4_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON5_TRIPLECLICK
#define BUTTON5_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON6_TRIPLECLICK
#define BUTTON6_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON7_TRIPLECLICK
#define BUTTON7_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON8_TRIPLECLICK
#define BUTTON8_TRIPLECLICK BUTTON_MODE_NONE
#endif
#ifndef BUTTON1_LNGCLICK #ifndef BUTTON1_LNGCLICK
#define BUTTON1_LNGCLICK BUTTON_MODE_RESET #define BUTTON1_LNGCLICK BUTTON_MODE_RESET
#endif #endif
@ -399,7 +424,7 @@
// General // General
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Default hostname will be ESPURNA_XXXXXX, where XXXXXX is last 3 octets of chipID
// Default hostname will be ESPURNA-XXXXXX, where XXXXXX is last 3 octets of chipID
#ifndef HOSTNAME #ifndef HOSTNAME
#define HOSTNAME "" #define HOSTNAME ""
#endif #endif


+ 30
- 17
code/espurna/config/general.h View File

@ -142,12 +142,17 @@
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
#define EEPROM_SIZE 4096 // EEPROM size in bytes #define EEPROM_SIZE 4096 // EEPROM size in bytes
//#define EEPROM_RORATE_SECTORS 2 // Number of sectors to use for EEPROM rotation
// If not defined the firmware will use a number based
// on the number of available sectors
#define EEPROM_RELAY_STATUS 0 // Address for the relay status (1 byte) #define EEPROM_RELAY_STATUS 0 // Address for the relay status (1 byte)
#define EEPROM_ENERGY_COUNT 1 // Address for the energy counter (4 bytes) #define EEPROM_ENERGY_COUNT 1 // Address for the energy counter (4 bytes)
#define EEPROM_CUSTOM_RESET 5 // Address for the reset reason (1 byte) #define EEPROM_CUSTOM_RESET 5 // Address for the reset reason (1 byte)
#define EEPROM_CRASH_COUNTER 6 // Address for the crash counter (1 byte) #define EEPROM_CRASH_COUNTER 6 // Address for the crash counter (1 byte)
#define EEPROM_MESSAGE_ID 7 // Address for the MQTT message id (4 bytes) #define EEPROM_MESSAGE_ID 7 // Address for the MQTT message id (4 bytes)
#define EEPROM_DATA_END 11 // End of custom EEPROM data block
#define EEPROM_ROTATE_DATA 11 // Reserved for the EEPROM_ROTATE library (3 bytes)
#define EEPROM_DATA_END 14 // End of custom EEPROM data block
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// HEARTBEAT // HEARTBEAT
@ -276,9 +281,6 @@
#define WIFI_AP_CAPTIVE 1 // Captive portal enabled when in AP mode #define WIFI_AP_CAPTIVE 1 // Captive portal enabled when in AP mode
#endif #endif
#ifndef WIFI_AP_MODE
#define WIFI_AP_MODE AP_MODE_ALONE
#endif
#ifndef WIFI_SLEEP_MODE #ifndef WIFI_SLEEP_MODE
#define WIFI_SLEEP_MODE WIFI_NONE_SLEEP // WIFI_NONE_SLEEP, WIFI_LIGHT_SLEEP or WIFI_MODEM_SLEEP #define WIFI_SLEEP_MODE WIFI_NONE_SLEEP // WIFI_NONE_SLEEP, WIFI_LIGHT_SLEEP or WIFI_MODEM_SLEEP
@ -511,6 +513,10 @@
#define UART_MQTT_BAUDRATE 115200 // Serial speed #define UART_MQTT_BAUDRATE 115200 // Serial speed
#endif #endif
#ifndef UART_MQTT_TERMINATION
#define UART_MQTT_TERMINATION '\n' // Termination character
#endif
#define UART_MQTT_BUFFER_SIZE 100 // UART buffer size #define UART_MQTT_BUFFER_SIZE 100 // UART buffer size
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -558,9 +564,9 @@
#ifndef MQTT_AUTOCONNECT #ifndef MQTT_AUTOCONNECT
#define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SERVER_SUPPORT=1 will perform an autodiscover and #define MQTT_AUTOCONNECT 1 // If enabled and MDNS_SERVER_SUPPORT=1 will perform an autodiscover and
// autoconnect to the first MQTT broker found if none defined
#endif #endif
// autoconnect to the first MQTT broker found if none defined
#ifndef MQTT_SERVER #ifndef MQTT_SERVER
#define MQTT_SERVER "" // Default MQTT broker address #define MQTT_SERVER "" // Default MQTT broker address
#endif #endif
@ -590,7 +596,7 @@
#endif #endif
#ifndef MQTT_KEEPALIVE #ifndef MQTT_KEEPALIVE
#define MQTT_KEEPALIVE 30 // MQTT keepalive value
#define MQTT_KEEPALIVE 300 // MQTT keepalive value
#endif #endif
@ -678,6 +684,7 @@
#define MQTT_TOPIC_UARTOUT "uartout" #define MQTT_TOPIC_UARTOUT "uartout"
#define MQTT_TOPIC_LOADAVG "loadavg" #define MQTT_TOPIC_LOADAVG "loadavg"
#define MQTT_TOPIC_BOARD "board" #define MQTT_TOPIC_BOARD "board"
#define MQTT_TOPIC_PULSE "pulse"
// Light module // Light module
#define MQTT_TOPIC_CHANNEL "channel" #define MQTT_TOPIC_CHANNEL "channel"
@ -772,11 +779,8 @@
#define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value #define LIGHT_MAX_BRIGHTNESS 255 // Maximun brightness value
#endif #endif
//#define LIGHT_MIN_MIREDS 153 // NOT USED (yet)! // Default to the Philips Hue value that HA has always assumed
//#define LIGHT_MAX_MIREDS 500 // NOT USED (yet)! // https://developers.meethue.com/documentation/core-concepts
#ifndef LIGHT_DEFAULT_MIREDS
#define LIGHT_DEFAULT_MIREDS 153 // Default value used by MQTT. This value is __NEVRER__ applied!
#endif
#define LIGHT_MIN_MIREDS 153 // Default to the Philips Hue value that HA also use.
#define LIGHT_MAX_MIREDS 500 // https://developers.meethue.com/documentation/core-concepts
#ifndef LIGHT_STEP #ifndef LIGHT_STEP
#define LIGHT_STEP 32 // Step size #define LIGHT_STEP 32 // Step size
@ -787,9 +791,18 @@
#endif #endif
#ifndef LIGHT_USE_WHITE #ifndef LIGHT_USE_WHITE
#define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value
#define LIGHT_USE_WHITE 0 // Use the 4th channel as (Warm-)White LEDs
#endif
#ifndef LIGHT_USE_CCT
#define LIGHT_USE_CCT 0 // Use the 5th channel as Coldwhite LEDs, LIGHT_USE_WHITE must be 1.
#endif #endif
// Used when LIGHT_USE_WHITE AND LIGHT_USE_CCT is 1 - (1000000/Kelvin = MiReds)
// Warning! Don't change this yet, NOT FULLY IMPLEMENTED!
#define LIGHT_COLDWHITE_MIRED 153 // Coldwhite Strip, Value must be __BELOW__ W2!! (Default: 6535 Kelvin/153 MiRed)
#define LIGHT_WARMWHITE_MIRED 500 // Warmwhite Strip, Value must be __ABOVE__ W1!! (Default: 2000 Kelvin/500 MiRed)
#ifndef LIGHT_USE_GAMMA #ifndef LIGHT_USE_GAMMA
#define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels #define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels
#endif #endif
@ -1142,13 +1155,13 @@
#if IR_BUTTON_SET == 3 #if IR_BUTTON_SET == 3
/* /*
+------+------+------+ +------+------+------+
| 1 | 2 | 3 |
| 1 | 2 | 3 |
+------+------+------+ +------+------+------+
| 4 | 5 | 6 |
| 4 | 5 | 6 |
+------+------+------+ +------+------+------+
| 7 | 8 | 9 |
| 7 | 8 | 9 |
+------+------+------+ +------+------+------+
| | 0 | |
| | 0 | |
+------+------+------+ +------+------+------+
*/ */
#define IR_BUTTON_COUNT 10 #define IR_BUTTON_COUNT 10
@ -1158,7 +1171,7 @@
{ 0xE0E020DF, IR_BUTTON_MODE_TOGGLE, 0 }, // Toggle Relay #0 { 0xE0E020DF, IR_BUTTON_MODE_TOGGLE, 0 }, // Toggle Relay #0
{ 0xE0E0A05F, IR_BUTTON_MODE_TOGGLE, 1 }, // Toggle Relay #1 { 0xE0E0A05F, IR_BUTTON_MODE_TOGGLE, 1 }, // Toggle Relay #1
{ 0xE0E0609F, IR_BUTTON_MODE_TOGGLE, 2 }, // Toggle Relay #2 { 0xE0E0609F, IR_BUTTON_MODE_TOGGLE, 2 }, // Toggle Relay #2
{ 0xE0E010EF, IR_BUTTON_MODE_TOGGLE, 3 }, // Toggle Relay #3 { 0xE0E010EF, IR_BUTTON_MODE_TOGGLE, 3 }, // Toggle Relay #3
{ 0xE0E0906F, IR_BUTTON_MODE_TOGGLE, 4 }, // Toggle Relay #4 { 0xE0E0906F, IR_BUTTON_MODE_TOGGLE, 4 }, // Toggle Relay #4
{ 0xE0E050AF, IR_BUTTON_MODE_TOGGLE, 5 }, // Toggle Relay #5 { 0xE0E050AF, IR_BUTTON_MODE_TOGGLE, 5 }, // Toggle Relay #5


+ 212
- 2
code/espurna/config/hardware.h View File

@ -70,6 +70,12 @@
#define LED1_PIN 2 #define LED1_PIN 2
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
#elif defined(NODEMCU_BASIC)
// Info
// Generic NodeMCU Board without any buttons or relays connected.
#define MANUFACTURER "NODEMCU"
#define DEVICE "BASIC"
#elif defined(WEMOS_D1_MINI_RELAYSHIELD) #elif defined(WEMOS_D1_MINI_RELAYSHIELD)
// Info // Info
@ -395,8 +401,11 @@
#define RELAY1_TYPE RELAY_TYPE_NORMAL #define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs // LEDs
#define LED1_PIN 15
#define LED1_PIN_INVERSE 0
#define LED1_PIN 13
#define LED1_PIN_INVERSE 1
// Disable UART noise
#define DEBUG_SERIAL_SUPPORT 0
// CSE7766 // CSE7766
#ifndef CSE7766_SUPPORT #ifndef CSE7766_SUPPORT
@ -1923,6 +1932,7 @@
#define DHT_SUPPORT 1 #define DHT_SUPPORT 1
#endif #endif
#define DHT_PIN 2 #define DHT_PIN 2
#define DHT_TYPE DHT_CHIP_DHT11
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// ESP-01S DS18B20 v1.0 // ESP-01S DS18B20 v1.0
@ -1941,6 +1951,52 @@
#endif #endif
#define DALLAS_PIN 2 #define DALLAS_PIN 2
// -----------------------------------------------------------------------------
// ESP-DIN relay board V1
// https://github.com/pilotak/esp_din
// -----------------------------------------------------------------------------
#elif defined(PILOTAK_ESP_DIN_V1)
// Info
#define MANUFACTURER "PILOTAK"
#define DEVICE "ESP_DIN_V1"
// Buttons
#define BUTTON1_PIN 0
#define BUTTON1_RELAY 1
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Relays
#define RELAY1_PIN 4
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_PIN 5
#define RELAY2_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 15
#define LED1_PIN_INVERSE 0
#define I2C_SDA_PIN 12
#define I2C_SCL_PIN 13
#ifndef DALLAS_SUPPORT
#define DALLAS_SUPPORT 1
#endif
#define DALLAS_PIN 2
#ifndef RF_SUPPORT
#define RF_SUPPORT 1
#endif
#define RF_PIN 14
#ifndef DIGITAL_SUPPORT
#define DIGITAL_SUPPORT 1
#endif
#define DIGITAL_PIN 16
#define DIGITAL_PIN_MODE INPUT
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Heltec Touch Relay // Heltec Touch Relay
// https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180408043114&SearchText=esp8266+touch+relay // https://www.aliexpress.com/wholesale?catId=0&initiative_id=SB_20180408043114&SearchText=esp8266+touch+relay
@ -1997,6 +2053,48 @@
#define LED1_PIN 1 #define LED1_PIN 1
#define LED1_PIN_INVERSE 1 #define LED1_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// Allnet 4duino ESP8266-UP-Relais
// http://www.allnet.de/de/allnet-brand/produkte/neuheiten/p/allnet-4duino-iot-wlan-relais-unterputz-esp8266-up-relais/
// https://shop.allnet.de/fileadmin/transfer/products/148814.pdf
// -----------------------------------------------------------------------------
#elif defined(ALLNET_4DUINO_IOT_WLAN_RELAIS)
// Info
#define MANUFACTURER "ALLNET"
#define DEVICE "4DUINO_IOT_WLAN_RELAIS"
// Relays
#define RELAY1_PIN 14
#define RELAY1_RESET_PIN 12
#define RELAY1_TYPE RELAY_TYPE_LATCHED
// LEDs
#define LED1_PIN 0
#define LED1_PIN_INVERSE 1
// Buttons
//#define BUTTON1_PIN 0
//#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Using pins labelled as SDA & SCL as buttons
#define BUTTON2_PIN 4
#define BUTTON2_MODE BUTTON_PUSHBUTTON
#define BUTTON2_PRESS BUTTON_MODE_TOGGLE
#define BUTTON2_CLICK BUTTON_MODE_NONE
#define BUTTON2_DBLCLICK BUTTON_MODE_NONE
#define BUTTON2_LNGCLICK BUTTON_MODE_NONE
#define BUTTON2_LNGLNGCLICK BUTTON_MODE_NONE
#define BUTTON3_PIN 5
#define BUTTON3_MODE BUTTON_PUSHBUTTON
// Using pins labelled as SDA & SCL for I2C
//#define I2C_SDA_PIN 4
//#define I2C_SCL_PIN 5
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Luani HVIO // Luani HVIO
// https://luani.de/projekte/esp8266-hvio/ // https://luani.de/projekte/esp8266-hvio/
@ -2030,6 +2128,118 @@
#define LED1_PIN 15 #define LED1_PIN 15
#define LED1_PIN_INVERSE 0 #define LED1_PIN_INVERSE 0
// -----------------------------------------------------------------------------
// Tonbux 50-100M Smart Mosquito Killer USB
// https://www.aliexpress.com/item/Original-Tonbux-50-100M-Smart-Mosquito-Killer-USB-Plug-No-Noise-Repellent-App-Smart-Module/32859330820.html
// -----------------------------------------------------------------------------
#elif defined(TONBUX_MOSQUITO_KILLER)
// Info
#define MANUFACTURER "TONBUX"
#define DEVICE "MOSQUITO_KILLER"
// Buttons
#define BUTTON1_PIN 2
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Relays
#define RELAY1_PIN 5 // not a relay, fan
#define RELAY1_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 15 // blue led
#define LED1_PIN_INVERSE 1
#define LED1_MODE LED_MODE_WIFI
#define LED2_PIN 14 // red led
#define LED2_PIN_INVERSE 1
#define LED2_MODE LED_MODE_RELAY
#define LED3_PIN 12 // UV leds (1-2-3-4-5-6-7-8)
#define LED3_PIN_INVERSE 0
#define LED3_RELAY 1
#define LED4_PIN 16 // UV leds (9-10-11)
#define LED4_PIN_INVERSE 0
#define LED4_RELAY 1
// -----------------------------------------------------------------------------
// NEO Coolcam Power Plug
// https://es.aliexpress.com/item/-/32854589733.html?spm=a219c.12010608.0.0.6d084e68xX0y5N
// -----------------------------------------------------------------------------
#elif defined(NEO_COOLCAM_POWER_PLUG_WIFI)
// Info
#define MANUFACTURER "NEO_COOLCAM"
#define DEVICE "POWER_PLUG_WIFI"
// Buttons
#define BUTTON1_PIN 13
#define BUTTON1_MODE BUTTON_PUSHBUTTON | 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
// ------------------------------------------------------------------------------
// Estink Wifi Power Strip
// https://www.amazon.de/Steckdosenleiste-Ladeger%C3%A4t-Sprachsteuerung-SmartphonesTablets-Android/dp/B0796W5FZY
// Fornorm Wi-Fi USB Extension Socket (ZLD-34EU)
// https://www.aliexpress.com/item/Fornorm-WiFi-Extension-Socket-with-Surge-Protector-Smart-Power-Strip-3-Outlets-and-4-USB-Charging/32849743948.html
// -----------------------------------------------------------------------------
#elif defined(ESTINK_WIFI_POWER_STRIP)
// Info
#define MANUFACTURER "ESTINK"
#define DEVICE "WIFI_POWER_STRIP"
// Disable UART noise since this board uses GPIO3
#define DEBUG_SERIAL_SUPPORT 0
// Buttons
#define BUTTON1_PIN 16
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 4
// Relays
#define RELAY1_PIN 14 // USB power
#define RELAY2_PIN 13 // power plug 1
#define RELAY3_PIN 4 // power plug 2
#define RELAY4_PIN 15 // power plug 3
#define RELAY1_TYPE RELAY_TYPE_NORMAL
#define RELAY2_TYPE RELAY_TYPE_NORMAL
#define RELAY3_TYPE RELAY_TYPE_NORMAL
#define RELAY4_TYPE RELAY_TYPE_NORMAL
// LEDs
#define LED1_PIN 0 // power led
#define LED2_PIN 12 // power plug 1
#define LED3_PIN 3 // power plug 2
#define LED4_PIN 5 // power plug 3
#define LED1_PIN_INVERSE 1
#define LED2_PIN_INVERSE 1
#define LED3_PIN_INVERSE 1
#define LED4_PIN_INVERSE 1
#define LED1_MODE LED_MODE_FINDME
#define LED2_MODE LED_MODE_FOLLOW
#define LED3_MODE LED_MODE_FOLLOW
#define LED4_MODE LED_MODE_FOLLOW
#define LED2_RELAY 2
#define LED3_RELAY 3
#define LED4_RELAY 4
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// TEST boards (do not use!!) // TEST boards (do not use!!)
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 178
- 4
code/espurna/config/progmem.h View File

@ -23,19 +23,187 @@ PROGMEM const char* const custom_reset_string[] = {
custom_reset_factory custom_reset_factory
}; };
//--------------------------------------------------------------------------------
// Capabilities
//--------------------------------------------------------------------------------
PROGMEM const char espurna_modules[] =
#if ALEXA_SUPPORT
"ALEXA "
#endif
#if BROKER_SUPPORT
"BROKER "
#endif
#if DEBUG_SERIAL_SUPPORT
"DEBUG_SERIAL "
#endif
#if DEBUG_TELNET_SUPPORT
"DEBUG_TELNET "
#endif
#if DEBUG_UDP_SUPPORT
"DEBUG_UDP "
#endif
#if DEBUG_WEB_SUPPORT
"DEBUG_WEB "
#endif
#if DOMOTICZ_SUPPORT
"DOMOTICZ "
#endif
#if HOMEASSISTANT_SUPPORT
"HOMEASSISTANT "
#endif
#if I2C_SUPPORT
"I2C "
#endif
#if INFLUXDB_SUPPORT
"INFLUXDB "
#endif
#if LLMNR_SUPPORT
"LLMNR "
#endif
#if MDNS_SERVER_SUPPORT
"MDNS_SERVER "
#endif
#if MDNS_CLIENT_SUPPORT
"MDNS_CLIENT "
#endif
#if MQTT_SUPPORT
"MQTT "
#endif
#if NETBIOS_SUPPORT
"NETBIOS "
#endif
#if NOFUSS_SUPPORT
"NOFUSS "
#endif
#if NTP_SUPPORT
"NTP "
#endif
#if RF_SUPPORT
"RF "
#endif
#if SCHEDULER_SUPPORT
"SCHEDULER "
#endif
#if SENSOR_SUPPORT
"SENSOR "
#endif
#if SPIFFS_SUPPORT
"SPIFFS "
#endif
#if SSDP_SUPPORT
"SSDP "
#endif
#if TELNET_SUPPORT
"TELNET "
#endif
#if TERMINAL_SUPPORT
"TERMINAL "
#endif
#if THINGSPEAK_SUPPORT
"THINGSPEAK "
#endif
#if UART_MQTT_SUPPORT
"UART_MQTT "
#endif
#if WEB_SUPPORT
"WEB "
#endif
"";
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Sensors // Sensors
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
PROGMEM const char espurna_sensors[] =
#if AM2320_SUPPORT
"AM2320_I2C "
#endif
#if ANALOG_SUPPORT
"ANALOG "
#endif
#if BH1750_SUPPORT
"BH1750 "
#endif
#if BMX280_SUPPORT
"BMX280 "
#endif
#if CSE7766_SUPPORT
"CSE7766 "
#endif
#if DALLAS_SUPPORT
"DALLAS "
#endif
#if DHT_SUPPORT
"DHTXX "
#endif
#if DIGITAL_SUPPORT
"DIGITAL "
#endif
#if ECH1560_SUPPORT
"ECH1560 "
#endif
#if EMON_ADC121_SUPPORT
"EMON_ADC121 "
#endif
#if EMON_ADS1X15_SUPPORT
"EMON_ADX1X15 "
#endif
#if EMON_ANALOG_SUPPORT
"EMON_ANALOG "
#endif
#if EVENTS_SUPPORT
"EVENTS "
#endif
#if GEIGER_SUPPORT
"GEIGER "
#endif
#if GUVAS12SD_SUPPORT
"GUVAS12SD "
#endif
#if HCSR04_SUPPORT
"HCSR04 "
#endif
#if HLW8012_SUPPORT
"HLW8012 "
#endif
#if MHZ19_SUPPORT
"MHZ19 "
#endif
#if PMSX003_SUPPORT
"PMSX003 "
#endif
#if PZEM004T_SUPPORT
"PZEM004T "
#endif
#if SENSEAIR_SUPPORT
"SENSEAIR "
#endif
#if SHT3X_I2C_SUPPORT
"SHT3X_I2C "
#endif
#if SI7021_SUPPORT
"SI7021 "
#endif
#if TMP3X_SUPPORT
"TMP3X "
#endif
#if V9261F_SUPPORT
"V9261F "
#endif
"";
PROGMEM const unsigned char magnitude_decimals[] = { PROGMEM const unsigned char magnitude_decimals[] = {
0, 0,
1, 0, 2, 1, 0, 2,
3, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0,
0, 0, 3, 3
0, 0, 3, 3,
4, 4 // Geiger Counter decimals
}; };
PROGMEM const char magnitude_unknown_topic[] = "unknown"; PROGMEM const char magnitude_unknown_topic[] = "unknown";
@ -61,6 +229,8 @@ PROGMEM const char magnitude_lux_topic[] = "lux";
PROGMEM const char magnitude_uv_topic[] = "uv"; PROGMEM const char magnitude_uv_topic[] = "uv";
PROGMEM const char magnitude_distance_topic[] = "distance"; PROGMEM const char magnitude_distance_topic[] = "distance";
PROGMEM const char magnitude_hcho_topic[] = "hcho"; PROGMEM const char magnitude_hcho_topic[] = "hcho";
PROGMEM const char magnitude_geiger_cpm_topic[] = "ldr_cpm"; // local dose rate [Counts per minute]
PROGMEM const char magnitude_geiger_sv_topic[] = "ldr_uSvh"; // local dose rate [µSievert per hour]
PROGMEM const char* const magnitude_topics[] = { PROGMEM const char* const magnitude_topics[] = {
magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic, magnitude_unknown_topic, magnitude_temperature_topic, magnitude_humidity_topic,
@ -70,7 +240,8 @@ PROGMEM const char* const magnitude_topics[] = {
magnitude_analog_topic, magnitude_digital_topic, magnitude_events_topic, magnitude_analog_topic, magnitude_digital_topic, magnitude_events_topic,
magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic, magnitude_pm1dot0_topic, magnitude_pm2dot5_topic, magnitude_pm10_topic,
magnitude_co2_topic, magnitude_lux_topic, magnitude_uv_topic, magnitude_co2_topic, magnitude_lux_topic, magnitude_uv_topic,
magnitude_distance_topic, magnitude_hcho_topic
magnitude_distance_topic, magnitude_hcho_topic,
magnitude_geiger_cpm_topic, magnitude_geiger_sv_topic // Geiger Counter topics
}; };
PROGMEM const char magnitude_empty[] = ""; PROGMEM const char magnitude_empty[] = "";
@ -90,6 +261,9 @@ PROGMEM const char magnitude_lux[] = "lux";
PROGMEM const char magnitude_uv[] = "uv"; PROGMEM const char magnitude_uv[] = "uv";
PROGMEM const char magnitude_distance[] = "m"; PROGMEM const char magnitude_distance[] = "m";
PROGMEM const char magnitude_mgm3[] = "mg/m³"; PROGMEM const char magnitude_mgm3[] = "mg/m³";
PROGMEM const char magnitude_geiger_cpm[] = "cpm"; // Counts per Minute: Unit of local dose rate (Geiger counting)
PROGMEM const char magnitude_geiger_sv[] = "µSv/h"; // µSievert per hour: 2nd unit of local dose rate (Geiger counting)
PROGMEM const char* const magnitude_units[] = { PROGMEM const char* const magnitude_units[] = {
magnitude_empty, magnitude_celsius, magnitude_percentage, magnitude_empty, magnitude_celsius, magnitude_percentage,
@ -99,8 +273,8 @@ PROGMEM const char* const magnitude_units[] = {
magnitude_empty, magnitude_empty, magnitude_empty, magnitude_empty, magnitude_empty, magnitude_empty,
magnitude_ugm3, magnitude_ugm3, magnitude_ugm3, magnitude_ugm3, magnitude_ugm3, magnitude_ugm3,
magnitude_ppm, magnitude_lux, magnitude_uv, magnitude_ppm, magnitude_lux, magnitude_uv,
magnitude_distance, magnitude_mgm3
magnitude_distance, magnitude_mgm3,
magnitude_geiger_cpm, magnitude_geiger_sv // Geiger counter units
}; };
#endif #endif

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

@ -7,6 +7,12 @@ extern "C" {
#include "user_interface.h" #include "user_interface.h"
} }
// -----------------------------------------------------------------------------
// EEPROM_ROTATE
// -----------------------------------------------------------------------------
#include <EEPROM_Rotate.h>
EEPROM_Rotate EEPROMr;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// WebServer // WebServer
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -67,6 +73,7 @@ void settingsGetJson(JsonObject& data);
bool settingsRestoreJson(JsonObject& data); bool settingsRestoreJson(JsonObject& data);
void settingsRegisterCommand(const String& name, void (*call)(Embedis*)); void settingsRegisterCommand(const String& name, void (*call)(Embedis*));
void settingsInject(void *data, size_t len); void settingsInject(void *data, size_t len);
Stream & settingsSerial();
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// I2C // I2C


+ 40
- 2
code/espurna/config/sensors.h View File

@ -285,7 +285,36 @@
#define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, BOTH #define EVENTS_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#endif #endif
#define EVENTS_DEBOUNCE 50 // Do not register events within less than 10 millis
#define EVENTS_DEBOUNCE 50 // Do not register events within less than 50 millis
//------------------------------------------------------------------------------
// Geiger sensor
// Enable support by passing GEIGER_SUPPORT=1 build flag
//------------------------------------------------------------------------------
#ifndef GEIGER_SUPPORT
#define GEIGER_SUPPORT 0 // Do not build with geiger support by default
#endif
#ifndef GEIGER_PIN
#define GEIGER_PIN D1 // GPIO to monitor "D1" => "GPIO5"
#endif
#ifndef GEIGER_PIN_MODE
#define GEIGER_PIN_MODE INPUT // INPUT, INPUT_PULLUP
#endif
#ifndef GEIGER_INTERRUPT_MODE
#define GEIGER_INTERRUPT_MODE RISING // RISING, FALLING, BOTH
#endif
#define GEIGER_DEBOUNCE 25 // Do not register events within less than 25 millis.
// Value derived here: Debounce time 25ms, because https://github.com/Trickx/espurna/wiki/Geiger-counter
#define GEIGER_CPM2SIEVERT 240 // CPM to µSievert per hour conversion factor
// Typically the literature uses the invers, but I find an integer type more convienient.
#define GEIGER_REPORT_SIEVERTS 1 // Enabler for local dose rate reports in µSv/h
#define GEIGER_REPORT_CPM 1 // Enabler for local dose rate reports in counts per minute
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// GUVAS12SD UV Sensor (analog) // GUVAS12SD UV Sensor (analog)
@ -428,7 +457,7 @@
#endif #endif
#ifndef PZEM004T_USE_SOFT #ifndef PZEM004T_USE_SOFT
#define PZEM004T_USE_SOFT 1 // Use software serial
#define PZEM004T_USE_SOFT 0 // Software serial is not working atm, use hardware serial
#endif #endif
#ifndef PZEM004T_RX_PIN #ifndef PZEM004T_RX_PIN
@ -527,6 +556,7 @@
EMON_ADS1X15_SUPPORT || \ EMON_ADS1X15_SUPPORT || \
EMON_ANALOG_SUPPORT || \ EMON_ANALOG_SUPPORT || \
EVENTS_SUPPORT || \ EVENTS_SUPPORT || \
GEIGER_SUPPORT || \
GUVAS12SD_SUPPORT || \ GUVAS12SD_SUPPORT || \
HCSR04_SUPPORT || \ HCSR04_SUPPORT || \
HLW8012_SUPPORT || \ HLW8012_SUPPORT || \
@ -579,6 +609,10 @@
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
#if SENSOR_DEBUG
#include "../config/debug.h"
#endif
#include "../sensors/BaseSensor.h" #include "../sensors/BaseSensor.h"
#if AM2320_SUPPORT #if AM2320_SUPPORT
@ -635,6 +669,10 @@
#include "../sensors/EventSensor.h" #include "../sensors/EventSensor.h"
#endif #endif
#if GEIGER_SUPPORT
#include "../sensors/GeigerSensor.h" // The main file for geiger counting module
#endif
#if GUVAS12SD_SUPPORT #if GUVAS12SD_SUPPORT
#include "../sensors/GUVAS12SDSensor.h" #include "../sensors/GUVAS12SDSensor.h"
#endif #endif


+ 20
- 1
code/espurna/config/types.h View File

@ -3,6 +3,19 @@
// Do not touch this definitions // Do not touch this definitions
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// -----------------------------------------------------------------------------
// WIFI
// -----------------------------------------------------------------------------
#define WIFI_STATE_AP 1
#define WIFI_STATE_STA 2
#define WIFI_STATE_AP_STA 3
#define WIFI_STATE_WPS 4
#define WIFI_STATE_SMARTCONFIG 8
#define WIFI_AP_ALLWAYS 1
#define WIFI_AP_FALLBACK 2
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// BUTTONS // BUTTONS
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
@ -14,6 +27,7 @@
#define BUTTON_EVENT_DBLCLICK 3 #define BUTTON_EVENT_DBLCLICK 3
#define BUTTON_EVENT_LNGCLICK 4 #define BUTTON_EVENT_LNGCLICK 4
#define BUTTON_EVENT_LNGLNGCLICK 5 #define BUTTON_EVENT_LNGLNGCLICK 5
#define BUTTON_EVENT_TRIPLECLICK 6
#define BUTTON_MODE_NONE 0 #define BUTTON_MODE_NONE 0
#define BUTTON_MODE_TOGGLE 1 #define BUTTON_MODE_TOGGLE 1
@ -23,6 +37,8 @@
#define BUTTON_MODE_RESET 5 #define BUTTON_MODE_RESET 5
#define BUTTON_MODE_PULSE 6 #define BUTTON_MODE_PULSE 6
#define BUTTON_MODE_FACTORY 7 #define BUTTON_MODE_FACTORY 7
#define BUTTON_MODE_WPS 8
#define BUTTON_MODE_SMART_CONFIG 9
// Needed for ESP8285 boards under Windows using PlatformIO (?) // Needed for ESP8285 boards under Windows using PlatformIO (?)
#ifndef BUTTON_PUSHBUTTON #ifndef BUTTON_PUSHBUTTON
@ -251,6 +267,7 @@
#define SENSOR_TMP3X_ID 0x22 #define SENSOR_TMP3X_ID 0x22
#define SENSOR_HCSR04_ID 0x23 #define SENSOR_HCSR04_ID 0x23
#define SENSOR_SENSEAIR_ID 0x24 #define SENSOR_SENSEAIR_ID 0x24
#define SENSOR_GEIGER_ID 0x25
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// Magnitudes // Magnitudes
@ -279,5 +296,7 @@
#define MAGNITUDE_UV 20 #define MAGNITUDE_UV 20
#define MAGNITUDE_DISTANCE 21 #define MAGNITUDE_DISTANCE 21
#define MAGNITUDE_HCHO 22 #define MAGNITUDE_HCHO 22
#define MAGNITUDE_GEIGER_CPM 23
#define MAGNITUDE_GEIGER_SIEVERT 24
#define MAGNITUDE_MAX 23
#define MAGNITUDE_MAX 25

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

@ -1,5 +1,5 @@
#define APP_NAME "ESPURNA" #define APP_NAME "ESPURNA"
#define APP_VERSION "1.12.6"
#define APP_VERSION "1.13.0c"
#define APP_REVISION "db84006" #define APP_REVISION "db84006"
#define APP_AUTHOR "xose.perez@gmail.com" #define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat" #define APP_WEBSITE "http://tinkerman.cat"


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


+ 44
- 34
code/espurna/debug.ino View File

@ -10,7 +10,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <stdio.h> #include <stdio.h>
#include <stdarg.h> #include <stdarg.h>
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
extern "C" { extern "C" {
#include "user_interface.h" #include "user_interface.h"
@ -26,6 +26,8 @@ char _udp_syslog_header[40] = {0};
void _debugSend(char * message) { void _debugSend(char * message) {
bool pause = false;
#if DEBUG_ADD_TIMESTAMP #if DEBUG_ADD_TIMESTAMP
static bool add_timestamp = true; static bool add_timestamp = true;
char timestamp[10] = {0}; char timestamp[10] = {0};
@ -50,6 +52,7 @@ void _debugSend(char * message) {
#endif #endif
_udp_debug.write(message); _udp_debug.write(message);
_udp_debug.endPacket(); _udp_debug.endPacket();
pause = true;
#if SYSTEM_CHECK_ENABLED #if SYSTEM_CHECK_ENABLED
} }
#endif #endif
@ -60,24 +63,31 @@ void _debugSend(char * message) {
_telnetWrite(timestamp, strlen(timestamp)); _telnetWrite(timestamp, strlen(timestamp));
#endif #endif
_telnetWrite(message, strlen(message)); _telnetWrite(message, strlen(message));
pause = true;
#endif #endif
#if DEBUG_WEB_SUPPORT #if DEBUG_WEB_SUPPORT
if (wsConnected() && (getFreeHeap() > 10000)) { if (wsConnected() && (getFreeHeap() > 10000)) {
String m = String(message);
m.replace("\"", "&quot;");
m.replace("{", "&#123");
m.replace("}", "&#125");
char buffer[m.length() + 24];
DynamicJsonBuffer jsonBuffer(JSON_OBJECT_SIZE(1) + strlen(message) + 17);
JsonObject &root = jsonBuffer.createObject();
#if DEBUG_ADD_TIMESTAMP #if DEBUG_ADD_TIMESTAMP
snprintf_P(buffer, sizeof(buffer), PSTR("{\"weblog\": \"%s%s\"}"), timestamp, m.c_str());
char buffer[strlen(timestamp) + strlen(message) + 1];
snprintf_P(buffer, sizeof(buffer), "%s%s", timestamp, message);
root.set("weblog", buffer);
#else #else
snprintf_P(buffer, sizeof(buffer), PSTR("{\"weblog\": \"%s\"}"), m.c_str());
root.set("weblog", message);
#endif #endif
wsSend(buffer);
String out;
root.printTo(out);
jsonBuffer.clear();
wsSend(out.c_str());
pause = true;
} }
#endif #endif
if (pause) optimistic_yield(100);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -204,31 +214,31 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
// write crash time to EEPROM // write crash time to EEPROM
uint32_t crash_time = millis(); uint32_t crash_time = millis();
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// write reset info to EEPROM // write reset info to EEPROM
EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
EEPROM.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON, rst_info->reason);
EEPROMr.write(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE, rst_info->exccause);
// write epc1, epc2, epc3, excvaddr and depc to EEPROM // write epc1, epc2, epc3, excvaddr and depc to EEPROM
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, rst_info->epc1);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, rst_info->epc2);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, rst_info->epc3);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, rst_info->excvaddr);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, rst_info->depc);
// write stack start and end address to EEPROM // write stack start and end address to EEPROM
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
// write stack trace to EEPROM // write stack trace to EEPROM
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
for (uint32_t i = stack_start; i < stack_end; i++) { for (uint32_t i = stack_start; i < stack_end; i++) {
byte* byteValue = (byte*) i; byte* byteValue = (byte*) i;
EEPROM.write(current_address++, *byteValue);
EEPROMr.write(current_address++, *byteValue);
} }
EEPROM.commit();
EEPROMr.commit();
} }
@ -237,8 +247,8 @@ extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack
*/ */
void debugClearCrashInfo() { void debugClearCrashInfo() {
uint32_t crash_time = 0xFFFFFFFF; uint32_t crash_time = 0xFFFFFFFF;
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROM.commit();
EEPROMr.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.commit();
} }
/** /**
@ -247,28 +257,28 @@ void debugClearCrashInfo() {
void debugDumpCrashInfo() { void debugDumpCrashInfo() {
uint32_t crash_time; uint32_t crash_time;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) { if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n")); DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n"));
return; return;
} }
DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time); DEBUG_MSG_P(PSTR("[DEBUG] Latest crash was at %lu ms after boot\n"), crash_time);
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %u\n"), EEPROMr.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
uint32_t epc1, epc2, epc3, excvaddr, depc; uint32_t epc1, epc2, epc3, excvaddr, depc;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC1, epc1);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC2, epc2);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EPC3, epc3);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCVADDR, excvaddr);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_DEPC, depc);
DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3); DEBUG_MSG_P(PSTR("[DEBUG] epc1=0x%08x epc2=0x%08x epc3=0x%08x\n"), epc1, epc2, epc3);
DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc); DEBUG_MSG_P(PSTR("[DEBUG] excvaddr=0x%08x depc=0x%08x\n"), excvaddr, depc);
uint32_t stack_start, stack_end; uint32_t stack_start, stack_end;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_START, stack_start);
EEPROMr.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_END, stack_end);
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] ")); DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE; int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
int16_t stack_len = stack_end - stack_start; int16_t stack_len = stack_end - stack_start;
@ -276,7 +286,7 @@ void debugDumpCrashInfo() {
for (int16_t i = 0; i < stack_len; i += 0x10) { for (int16_t i = 0; i < stack_len; i += 0x10) {
DEBUG_MSG_P(PSTR("%08x: "), stack_start + i); DEBUG_MSG_P(PSTR("%08x: "), stack_start + i);
for (byte j = 0; j < 4; j++) { for (byte j = 0; j < 4; j++) {
EEPROM.get(current_address, stack_trace);
EEPROMr.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace); DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4; current_address += 4;
} }


+ 12
- 0
code/espurna/domoticz.ino View File

@ -43,7 +43,13 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC); String dczTopicOut = getSetting("dczTopicOut", DOMOTICZ_OUT_TOPIC);
if (type == MQTT_CONNECT_EVENT) { if (type == MQTT_CONNECT_EVENT) {
// Subscribe to domoticz action topics
mqttSubscribeRaw(dczTopicOut.c_str()); mqttSubscribeRaw(dczTopicOut.c_str());
// Send relays state on connection
domoticzSendRelays();
} }
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
@ -138,6 +144,12 @@ void domoticzSendRelay(unsigned char relayID) {
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0"); domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
} }
void domoticzSendRelays() {
for (uint8_t relayID=0; relayID < relayCount(); relayID++) {
domoticzSendRelay(relayID);
}
}
unsigned int domoticzIdx(unsigned char relayID) { unsigned int domoticzIdx(unsigned char relayID) {
char buffer[15]; char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID); snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%u"), relayID);


+ 86
- 0
code/espurna/eeprom.ino View File

@ -0,0 +1,86 @@
/*
EEPROM MODULE
*/
#include <EEPROM_Rotate.h>
// -----------------------------------------------------------------------------
bool eepromRotate(bool value) {
// Enable/disable EEPROM rotation only if we are using more sectors than the
// reserved by the memory layout
if (EEPROMr.size() > EEPROMr.reserved()) {
if (value) {
DEBUG_MSG_P(PSTR("[EEPROM] Reenabling EEPROM rotation\n"));
} else {
DEBUG_MSG_P(PSTR("[EEPROM] Disabling EEPROM rotation\n"));
}
EEPROMr.rotate(value);
}
}
String eepromSectors() {
String response;
for (uint32_t i = 0; i < EEPROMr.size(); i++) {
if (i > 0) response = response + String(", ");
response = response + String(EEPROMr.base() - i);
}
return response;
}
#if TERMINAL_SUPPORT
void _eepromInitCommands() {
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
EEPROMr.dump(settingsSerial());
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
settingsRegisterCommand(F("FLASH.DUMP"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
uint32_t sector = String(e->argv[1]).toInt();
uint32_t max = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE;
if (sector >= max) {
DEBUG_MSG_P(PSTR("-ERROR: Sector out of range\n"));
return;
}
EEPROMr.dump(settingsSerial(), sector);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
}
#endif
// -----------------------------------------------------------------------------
void eepromSetup() {
#ifdef EEPROM_ROTATE_SECTORS
EEPROMr.size(EEPROM_ROTATE_SECTORS);
#else
// If the memory layout has more than one sector reserved use those,
// otherwise calculate pool size based on memory size.
if (EEPROMr.size() == 1) {
if (EEPROMr.last() > 1000) { // 4Mb boards
EEPROMr.size(4);
} else if (EEPROMr.last() > 250) { // 1Mb boards
EEPROMr.size(2);
}
}
#endif
EEPROMr.offset(EEPROM_ROTATE_DATA);
EEPROMr.begin(EEPROM_SIZE);
#if TERMINAL_SUPPORT
_eepromInitCommands();
#endif
}

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

@ -47,7 +47,10 @@ void setup() {
debugSetup(); debugSetup();
#endif #endif
// Init EEPROM, Serial, SPIFFS and system check
// Init EEPROM
eepromSetup();
// Init Serial, SPIFFS and system check
systemSetup(); systemSetup();
// Init persistance and terminal features // Init persistance and terminal features


+ 13
- 4
code/espurna/homeassistant.ino View File

@ -24,9 +24,8 @@ void _haSendMagnitude(unsigned char i, JsonObject& config) {
unsigned char type = magnitudeType(i); unsigned char type = magnitudeType(i);
config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type); config["name"] = getSetting("hostname") + String(" ") + magnitudeTopic(type);
config.set("platform", "mqtt"); config.set("platform", "mqtt");
config.set("device_class", "sensor");
config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false); config["state_topic"] = mqttTopic(magnitudeTopicIndex(i).c_str(), false);
config["unit_of_measurement"] = String("\"") + magnitudeUnits(type) + String("\"");
config["unit_of_measurement"] = magnitudeUnits(type);
} }
@ -45,6 +44,7 @@ void _haSendMagnitudes() {
JsonObject& config = jsonBuffer.createObject(); JsonObject& config = jsonBuffer.createObject();
_haSendMagnitude(i, config); _haSendMagnitude(i, config);
config.printTo(output); config.printTo(output);
jsonBuffer.clear();
} }
mqttSendRaw(topic.c_str(), output.c_str()); mqttSendRaw(topic.c_str(), output.c_str());
@ -124,6 +124,7 @@ void _haSendSwitches() {
JsonObject& config = jsonBuffer.createObject(); JsonObject& config = jsonBuffer.createObject();
_haSendSwitch(i, config); _haSendSwitch(i, config);
config.printTo(output); config.printTo(output);
jsonBuffer.clear();
} }
mqttSendRaw(topic.c_str(), output.c_str()); mqttSendRaw(topic.c_str(), output.c_str());
@ -164,6 +165,8 @@ String _haGetConfig() {
} }
output += "\n"; output += "\n";
jsonBuffer.clear();
} }
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
@ -187,6 +190,8 @@ String _haGetConfig() {
} }
output += "\n"; output += "\n";
jsonBuffer.clear();
} }
#endif #endif
@ -256,13 +261,17 @@ void _haInitCommands() {
settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) { settingsRegisterCommand(F("HA.SEND"), [](Embedis* e) {
setSetting("haEnabled", "1"); setSetting("haEnabled", "1");
_haConfigure(); _haConfigure();
wsSend(_haWebSocketOnSend);
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) { settingsRegisterCommand(F("HA.CLEAR"), [](Embedis* e) {
setSetting("haEnabled", "0"); setSetting("haEnabled", "0");
_haConfigure(); _haConfigure();
wsSend(_haWebSocketOnSend);
#if WEB_SUPPORT
wsSend(_haWebSocketOnSend);
#endif
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
} }


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

@ -92,7 +92,7 @@ bool idbSend(const char * topic, const char * payload) {
bool idbSend(const char * topic, unsigned char id, const char * payload) { bool idbSend(const char * topic, unsigned char id, const char * payload) {
char measurement[64]; char measurement[64];
snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id); snprintf(measurement, sizeof(measurement), "%s,id=%d", topic, id);
return idbSend(topic, payload);
return idbSend(measurement, payload);
} }
bool idbEnabled() { bool idbEnabled() {


+ 30
- 28
code/espurna/led.ino View File

@ -182,16 +182,18 @@ void ledSetup() {
void ledLoop() { void ledLoop() {
uint8_t wifi_state = wifiState();
for (unsigned char i=0; i<_leds.size(); i++) { for (unsigned char i=0; i<_leds.size(); i++) {
if (_ledMode(i) == LED_MODE_WIFI) { if (_ledMode(i) == LED_MODE_WIFI) {
if (wifiConnected()) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
_ledBlink(i, 4900, 100);
} else if (wifi_state & WIFI_STATE_AP) {
_ledBlink(i, 900, 100);
} else { } else {
_ledBlink(i, 500, 500); _ledBlink(i, 500, 500);
} }
@ -200,19 +202,19 @@ void ledLoop() {
if (_ledMode(i) == LED_MODE_FINDME_WIFI) { if (_ledMode(i) == LED_MODE_FINDME_WIFI) {
if (wifiConnected()) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) { if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
_ledBlink(i, 4900, 100);
} else { } else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
_ledBlink(i, 100, 4900);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 100, 900);
} }
} else { } else {
_ledBlink(i, 500, 500); _ledBlink(i, 500, 500);
@ -222,19 +224,19 @@ void ledLoop() {
if (_ledMode(i) == LED_MODE_RELAY_WIFI) { if (_ledMode(i) == LED_MODE_RELAY_WIFI) {
if (wifiConnected()) {
if (wifi_state & WIFI_STATE_WPS || wifi_state & WIFI_STATE_SMARTCONFIG) {
_ledBlink(i, 100, 100);
} else if (wifi_state & WIFI_STATE_STA) {
if (relayStatus(_leds[i].relay-1)) { if (relayStatus(_leds[i].relay-1)) {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 100, 4900);
}
_ledBlink(i, 100, 4900);
} else { } else {
if (WiFi.getMode() == WIFI_AP) {
_ledBlink(i, 900, 100);
} else {
_ledBlink(i, 4900, 100);
}
_ledBlink(i, 4900, 100);
}
} else if (wifi_state & WIFI_STATE_AP) {
if (relayStatus(_leds[i].relay-1)) {
_ledBlink(i, 100, 900);
} else {
_ledBlink(i, 900, 100);
} }
} else { } else {
_ledBlink(i, 500, 500); _ledBlink(i, 500, 500);


+ 636
- 0
code/espurna/libs/fs_math.c View File

@ -0,0 +1,636 @@
/**
* This code is available at
* http://www.mindspring.com/~pfilandr/C/fs_math/
* and it is believed to be public domain.
*/
/* BEGIN fs_math.c */
#include "fs_math.h"
#include <float.h>
/*
** pi == (atan(1.0 / 3) + atan(1.0 / 2)) * 4
*/
static double fs_pi(void);
static long double fs_pil(void);
double fs_sqrt(double x)
{
int n;
double a, b;
if (x > 0 && DBL_MAX >= x) {
for (n = 0; x > 2; x /= 4) {
++n;
}
while (0.5 > x) {
--n;
x *= 4;
}
a = x;
b = (1 + x) / 2;
do {
x = b;
b = (a / x + x) / 2;
} while (x > b);
while (n > 0) {
x *= 2;
--n;
}
while (0 > n) {
x /= 2;
++n;
}
} else {
if (x != 0) {
x = DBL_MAX;
}
}
return x;
}
double fs_log(double x)
{
int n;
double a, b, c, epsilon;
static double A, B, C;
static int initialized;
if (x > 0 && DBL_MAX >= x) {
if (!initialized) {
initialized = 1;
A = fs_sqrt(2);
B = A / 2;
C = fs_log(A);
}
for (n = 0; x > A; x /= 2) {
++n;
}
while (B > x) {
--n;
x *= 2;
}
a = (x - 1) / (x + 1);
x = C * n + a;
c = a * a;
n = 1;
epsilon = DBL_EPSILON * x;
if (0 > a) {
if (epsilon > 0) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (epsilon > b);
} else {
if (0 > epsilon) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (b > epsilon);
}
x *= 2;
} else {
x = -DBL_MAX;
}
return x;
}
double fs_log10(double x)
{
static double log_10;
static int initialized;
if (!initialized) {
initialized = 1;
log_10 = fs_log(10);
}
return x > 0 && DBL_MAX >= x ? fs_log(x) / log_10 : fs_log(x);
}
double fs_exp(double x)
{
unsigned n, square;
double b, e;
static double x_max, x_min, epsilon;
static int initialized;
if (!initialized) {
initialized = 1;
x_max = fs_log(DBL_MAX);
x_min = fs_log(DBL_MIN);
epsilon = DBL_EPSILON / 4;
}
if (x_max >= x && x >= x_min) {
for (square = 0; x > 1; x /= 2) {
++square;
}
while (-1 > x) {
++square;
x /= 2;
}
e = b = n = 1;
do {
b /= n++;
b *= x;
e += b;
b /= n++;
b *= x;
e += b;
} while (b > epsilon);
while (square-- != 0) {
e *= e;
}
} else {
e = x > 0 ? DBL_MAX : 0;
}
return e;
}
double fs_modf(double value, double *iptr)
{
double a, b;
const double c = value;
if (0 > c) {
value = -value;
}
if (DBL_MAX >= value) {
for (*iptr = 0; value >= 1; value -= b) {
a = value / 2;
b = 1;
while (a >= b) {
b *= 2;
}
*iptr += b;
}
} else {
*iptr = value;
value = 0;
}
if (0 > c) {
*iptr = -*iptr;
value = -value;
}
return value;
}
double fs_fmod(double x, double y)
{
double a, b;
const double c = x;
if (0 > c) {
x = -x;
}
if (0 > y) {
y = -y;
}
if (y != 0 && DBL_MAX >= y && DBL_MAX >= x) {
while (x >= y) {
a = x / 2;
b = y;
while (a >= b) {
b *= 2;
}
x -= b;
}
} else {
x = 0;
}
return 0 > c ? -x : x;
}
double fs_pow(double x, double y)
{
double p = 0;
if (0 > x && fs_fmod(y, 1) == 0) {
if (fs_fmod(y, 2) == 0) {
p = fs_exp(fs_log(-x) * y);
} else {
p = -fs_exp(fs_log(-x) * y);
}
} else {
if (x != 0 || 0 >= y) {
p = fs_exp(fs_log( x) * y);
}
}
return p;
}
static double fs_pi(void)
{
unsigned n;
double a, b, epsilon;
static double p;
static int initialized;
if (!initialized) {
initialized = 1;
epsilon = DBL_EPSILON / 4;
n = 1;
a = 3;
do {
a /= 9;
b = a / n;
n += 2;
a /= 9;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
epsilon = DBL_EPSILON / 2;
n = 1;
a = 2;
do {
a /= 4;
b = a / n;
n += 2;
a /= 4;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
p *= 4;
}
return p;
}
double fs_cos(double x)
{
unsigned n;
int negative, sine;
double a, b, c;
static double pi, two_pi, half_pi, third_pi, epsilon;
static int initialized;
if (0 > x) {
x = -x;
}
if (DBL_MAX >= x) {
if (!initialized) {
initialized = 1;
pi = fs_pi();
two_pi = 2 * pi;
half_pi = pi / 2;
third_pi = pi / 3;
epsilon = DBL_EPSILON / 2;
}
if (x > two_pi) {
x = fs_fmod(x, two_pi);
}
if (x > pi) {
x = two_pi - x;
}
if (x > half_pi) {
x = pi - x;
negative = 1;
} else {
negative = 0;
}
if (x > third_pi) {
x = half_pi - x;
sine = 1;
} else {
sine = 0;
}
c = x * x;
x = n = 0;
a = 1;
do {
b = a;
a *= c;
a /= ++n;
a /= ++n;
b -= a;
a *= c;
a /= ++n;
a /= ++n;
x += b;
} while (b > epsilon);
if (sine) {
x = fs_sqrt((1 - x) * (1 + x));
}
if (negative) {
x = -x;
}
} else {
x = -DBL_MAX;
}
return x;
}
double fs_log2(double x)
{
static double log_2;
static int initialized;
if (!initialized) {
initialized = 1;
log_2 = fs_log(2);
}
return x > 0 && DBL_MAX >= x ? fs_log(x) / log_2 : fs_log(x);
}
double fs_exp2(double x)
{
static double log_2;
static int initialized;
if (!initialized) {
initialized = 1;
log_2 = fs_log(2);
}
return fs_exp(x * log_2);
}
long double fs_powl(long double x, long double y)
{
long double p;
if (0 > x && fs_fmodl(y, 1) == 0) {
if (fs_fmodl(y, 2) == 0) {
p = fs_expl(fs_logl(-x) * y);
} else {
p = -fs_expl(fs_logl(-x) * y);
}
} else {
if (x != 0 || 0 >= y) {
p = fs_expl(fs_logl( x) * y);
} else {
p = 0;
}
}
return p;
}
long double fs_sqrtl(long double x)
{
long int n;
long double a, b;
if (x > 0 && LDBL_MAX >= x) {
for (n = 0; x > 2; x /= 4) {
++n;
}
while (0.5 > x) {
--n;
x *= 4;
}
a = x;
b = (1 + x) / 2;
do {
x = b;
b = (a / x + x) / 2;
} while (x > b);
while (n > 0) {
x *= 2;
--n;
}
while (0 > n) {
x /= 2;
++n;
}
} else {
if (x != 0) {
x = LDBL_MAX;
}
}
return x;
}
long double fs_logl(long double x)
{
long int n;
long double a, b, c, epsilon;
static long double A, B, C;
static int initialized;
if (x > 0 && LDBL_MAX >= x) {
if (!initialized) {
initialized = 1;
B = 1.5;
do {
A = B;
B = 1 / A + A / 2;
} while (A > B);
B /= 2;
C = fs_logl(A);
}
for (n = 0; x > A; x /= 2) {
++n;
}
while (B > x) {
--n;
x *= 2;
}
a = (x - 1) / (x + 1);
x = C * n + a;
c = a * a;
n = 1;
epsilon = LDBL_EPSILON * x;
if (0 > a) {
if (epsilon > 0) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (epsilon > b);
} else {
if (0 > epsilon) {
epsilon = -epsilon;
}
do {
n += 2;
a *= c;
b = a / n;
x += b;
} while (b > epsilon);
}
x *= 2;
} else {
x = -LDBL_MAX;
}
return x;
}
long double fs_expl(long double x)
{
long unsigned n, square;
long double b, e;
static long double x_max, x_min, epsilon;
static int initialized;
if (!initialized) {
initialized = 1;
x_max = fs_logl(LDBL_MAX);
x_min = fs_logl(LDBL_MIN);
epsilon = LDBL_EPSILON / 4;
}
if (x_max >= x && x >= x_min) {
for (square = 0; x > 1; x /= 2) {
++square;
}
while (-1 > x) {
++square;
x /= 2;
}
e = b = n = 1;
do {
b /= n++;
b *= x;
e += b;
b /= n++;
b *= x;
e += b;
} while (b > epsilon);
while (square-- != 0) {
e *= e;
}
} else {
e = x > 0 ? LDBL_MAX : 0;
}
return e;
}
static long double fs_pil(void)
{
long unsigned n;
long double a, b, epsilon;
static long double p;
static int initialized;
if (!initialized) {
initialized = 1;
epsilon = LDBL_EPSILON / 4;
n = 1;
a = 3;
do {
a /= 9;
b = a / n;
n += 2;
a /= 9;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
epsilon = LDBL_EPSILON / 2;
n = 1;
a = 2;
do {
a /= 4;
b = a / n;
n += 2;
a /= 4;
b -= a / n;
n += 2;
p += b;
} while (b > epsilon);
p *= 4;
}
return p;
}
long double fs_cosl(long double x)
{
long unsigned n;
int negative, sine;
long double a, b, c;
static long double pi, two_pi, half_pi, third_pi, epsilon;
static int initialized;
if (0 > x) {
x = -x;
}
if (LDBL_MAX >= x) {
if (!initialized) {
initialized = 1;
pi = fs_pil();
two_pi = 2 * pi;
half_pi = pi / 2;
third_pi = pi / 3;
epsilon = LDBL_EPSILON / 2;
}
if (x > two_pi) {
x = fs_fmodl(x, two_pi);
}
if (x > pi) {
x = two_pi - x;
}
if (x > half_pi) {
x = pi - x;
negative = 1;
} else {
negative = 0;
}
if (x > third_pi) {
x = half_pi - x;
sine = 1;
} else {
sine = 0;
}
c = x * x;
x = n = 0;
a = 1;
do {
b = a;
a *= c;
a /= ++n;
a /= ++n;
b -= a;
a *= c;
a /= ++n;
a /= ++n;
x += b;
} while (b > epsilon);
if (sine) {
x = fs_sqrtl((1 - x) * (1 + x));
}
if (negative) {
x = -x;
}
} else {
x = -LDBL_MAX;
}
return x;
}
long double fs_fmodl(long double x, long double y)
{
long double a, b;
const long double c = x;
if (0 > c) {
x = -x;
}
if (0 > y) {
y = -y;
}
if (y != 0 && LDBL_MAX >= y && LDBL_MAX >= x) {
while (x >= y) {
a = x / 2;
b = y;
while (a >= b) {
b *= 2;
}
x -= b;
}
} else {
x = 0;
}
return 0 > c ? -x : x;
}
/* END fs_math.c */

+ 116
- 0
code/espurna/libs/fs_math.h View File

@ -0,0 +1,116 @@
/**
* This code is available at
* http://www.mindspring.com/~pfilandr/C/fs_math/
* and it is believed to be public domain.
*/
/* BEGIN fs_math.h */
/*
** Portable freestanding code.
*/
#ifndef H_FS_MATH_H
#define H_FS_MATH_H
double fs_sqrt(double x);
double fs_log(double x);
double fs_log10(double x);
/*
** exp(x) = 1 + x + x^2/2! + x^3/3! + ...
*/
double fs_exp(double x);
double fs_modf(double value, double *iptr);
double fs_fmod(double x, double y);
double fs_pow(double x, double y);
double fs_cos(double x);
/*
** C99
*/
double fs_log2(double x);
double fs_exp2(double x);
long double fs_powl(long double x, long double y);
long double fs_sqrtl(long double x);
long double fs_logl(long double x);
long double fs_expl(long double x);
long double fs_cosl(long double x);
long double fs_fmodl(long double x, long double y);
#endif
/* END fs_math.h */
#if 0
/*
> > Anybody know where I can get some source code for a
> > reasonably fast double
> > precision square root algorithm in C.
> > I'm looking for one that is not IEEE
> > compliant as I am running on a Z/OS mainframe.
> >
> > I would love to use the standard library but
> > unfortunatly I'm using a
> > stripped down version of C that looses the the runtime library
> > (we have to write our own).
>
> long double Ssqrt(long double x)
> {
> long double a, b;
> size_t c;
size_t is a bug here.
c needs to be a signed type:
long c;
> if (x > 0) {
> c = 0;
> while (x > 4) {
> x /= 4;
> ++c;
> }
> while (1.0 / 4 > x) {
> x *= 4;
> --c;
> }
> a = x;
> b = ((4 > a) + a) / 2;
Not a bug, but should be:
b = (1 + a) / 2;
> do {
> x = b;
> b = (a / x + x) / 2;
> } while (x > b);
> if (c > 0) {
The above line is why c needs to be a signed type,
otherwise the decremented values of c, are greater than zero,
and the function won't work if the initial value of x
is less than 0.25
> while (c--) {
> x *= 2;
> }
> } else {
> while (c++) {
> x /= 2;
> }
> }
> }
> return x;
> }
>
> >
> > That algorithm was actually 4 times slower
> > then the one below, and more
> > code. It was accurate though.
> >
>
> Sorry Pete, I wasn't looking very carefully.
> When I converted your function
> to double precision it's was much quicker, the best I've seen yet.
*/
#endif

code/espurna/pwm.c → code/espurna/libs/pwm.c View File


+ 87
- 45
code/espurna/light.ino View File

@ -12,6 +12,10 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <vector> #include <vector>
extern "C" {
#include "libs/fs_math.h"
}
#if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER #if LIGHT_PROVIDER == LIGHT_PROVIDER_DIMMER
#define PWM_CHANNEL_NUM_MAX LIGHT_CHANNELS #define PWM_CHANNEL_NUM_MAX LIGHT_CHANNELS
extern "C" { extern "C" {
@ -40,10 +44,11 @@ bool _light_use_transitions = false;
unsigned int _light_transition_time = LIGHT_TRANSITION_TIME; unsigned int _light_transition_time = LIGHT_TRANSITION_TIME;
bool _light_has_color = false; bool _light_has_color = false;
bool _light_use_white = false; bool _light_use_white = false;
bool _light_use_cct = false;
bool _light_use_gamma = false; bool _light_use_gamma = false;
unsigned long _light_steps_left = 1; unsigned long _light_steps_left = 1;
unsigned char _light_brightness = LIGHT_MAX_BRIGHTNESS; unsigned char _light_brightness = LIGHT_MAX_BRIGHTNESS;
unsigned int _light_mireds = LIGHT_DEFAULT_MIREDS;
unsigned int _light_mireds = round((LIGHT_COLDWHITE_MIRED+LIGHT_WARMWHITE_MIRED)/2);
#if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX #if LIGHT_PROVIDER == LIGHT_PROVIDER_MY92XX
#include <my92xx.h> #include <my92xx.h>
@ -77,45 +82,70 @@ const unsigned char _light_gamma_table[] = {
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _setRGBInputValue(unsigned char red, unsigned char green, unsigned char blue) { void _setRGBInputValue(unsigned char red, unsigned char green, unsigned char blue) {
_light_channel[0].inputValue = red;
_light_channel[1].inputValue = green;
_light_channel[2].inputValue = blue;
_light_channel[0].inputValue = constrain(red, 0, LIGHT_MAX_VALUE);
_light_channel[1].inputValue = constrain(green, 0, LIGHT_MAX_VALUE);;
_light_channel[2].inputValue = constrain(blue, 0, LIGHT_MAX_VALUE);;
} }
void _generateBrightness() { void _generateBrightness() {
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
// Convert RGB to RGBW
// Convert RGB to RGBW(W)
if (_light_has_color && _light_use_white) { if (_light_has_color && _light_use_white) {
// Substract the common part from RGB channels and add it to white channel. So [250,150,50] -> [200,100,0,50
// Substract the common part from RGB channels and add it to white channel. So [250,150,50] -> [200,100,0,50]
unsigned char white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue)); unsigned char white = std::min(_light_channel[0].inputValue, std::min(_light_channel[1].inputValue, _light_channel[2].inputValue));
for (unsigned int i=0; i < 3; i++) { for (unsigned int i=0; i < 3; i++) {
_light_channel[i].value = _light_channel[i].inputValue - white; _light_channel[i].value = _light_channel[i].inputValue - white;
} }
_light_channel[3].value = white;
_light_channel[3].inputValue = 0;
// Split the White Value across 2 White LED Strips.
if (_light_use_cct) {
// This change the range from 153-500 to 0-347 so we get a value between 0 and 1 in the end.
double miredFactor = ((double) _light_mireds - (double) LIGHT_COLDWHITE_MIRED)/((double) LIGHT_WARMWHITE_MIRED - (double) LIGHT_COLDWHITE_MIRED);
// set cold white
_light_channel[3].inputValue = 0;
_light_channel[3].value = round(((double) 1.0 - miredFactor) * white);
// set warm white
_light_channel[4].inputValue = 0;
_light_channel[4].value = round(miredFactor * white);
} else {
_light_channel[3].inputValue = 0;
_light_channel[3].value = white;
}
// Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63] // Scale up to equal input values. So [250,150,50] -> [200,100,0,50] -> [250, 125, 0, 63]
unsigned char max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue)); unsigned char max_in = std::max(_light_channel[0].inputValue, std::max(_light_channel[1].inputValue, _light_channel[2].inputValue));
unsigned char max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value)); unsigned char max_out = std::max(std::max(_light_channel[0].value, _light_channel[1].value), std::max(_light_channel[2].value, _light_channel[3].value));
unsigned char channelSize = _light_use_cct ? 5 : 4;
if (_light_use_cct) {
max_out = std::max(max_out, _light_channel[4].value);
}
double factor = (max_out > 0) ? (double) (max_in / max_out) : 0; double factor = (max_out > 0) ? (double) (max_in / max_out) : 0;
for (unsigned int i=0; i < 4; i++) {
for (unsigned char i=0; i < channelSize; i++) {
_light_channel[i].value = round((double) _light_channel[i].value * factor * brightness); _light_channel[i].value = round((double) _light_channel[i].value * factor * brightness);
} }
// Scale white channel to match brightness // Scale white channel to match brightness
_light_channel[3].value = constrain(_light_channel[3].value * LIGHT_WHITE_FACTOR, 0, 255);
for (unsigned char i=3; i < channelSize; i++) {
_light_channel[i].value = constrain(_light_channel[i].value * LIGHT_WHITE_FACTOR, 0, LIGHT_MAX_BRIGHTNESS);
}
// For the rest of channels, don't apply brightness, it is already in the inputValue:
for (unsigned char i=4; i < _light_channel.size(); i++) {
// For the rest of channels, don't apply brightness, it is already in the inputValue
// i should be 4 when RGBW and 5 when RGBWW
for (unsigned char i=channelSize; i < _light_channel.size(); i++) {
_light_channel[i].value = _light_channel[i].inputValue; _light_channel[i].value = _light_channel[i].inputValue;
} }
} else { } else {
// Don't apply brightness, it is already in the inputValue:
// Don't apply brightness, it is already in the target:
for (unsigned char i=0; i < _light_channel.size(); i++) { for (unsigned char i=0; i < _light_channel.size(); i++) {
if (_light_has_color & (i<3)) { if (_light_has_color & (i<3)) {
_light_channel[i].value = _light_channel[i].inputValue * brightness; _light_channel[i].value = _light_channel[i].inputValue * brightness;
@ -155,16 +185,10 @@ void _fromRGB(const char * rgb) {
} }
break; break;
case 'M': // Mired Value case 'M': // Mired Value
if (_light_has_color) {
unsigned long mireds = atol(p + 1);
_fromMireds(mireds);
}
_fromMireds(atol(p + 1));
break; break;
case 'K': // Kelvin Value case 'K': // Kelvin Value
if (_light_has_color) {
unsigned long kelvin = atol(p + 1);
_fromKelvin(kelvin);
}
_fromKelvin(atol(p + 1));
break; break;
default: // assume decimal values separated by commas default: // assume decimal values separated by commas
char * tok; char * tok;
@ -255,45 +279,39 @@ void _fromHSV(const char * hsv) {
// Thanks to Sacha Telgenhof for sharing this code in his AiLight library // Thanks to Sacha Telgenhof for sharing this code in his AiLight library
// https://github.com/stelgenhof/AiLight // https://github.com/stelgenhof/AiLight
void _fromKelvin(unsigned long kelvin, bool setMireds) {
void _fromKelvin(unsigned long kelvin) {
// Check we have RGB channels
if (!_light_has_color) return; if (!_light_has_color) return;
if (setMireds) {
_light_mireds = round(1000000UL / kelvin);
if (_light_use_cct) {
_setRGBInputValue(LIGHT_MAX_VALUE, LIGHT_MAX_VALUE, LIGHT_MAX_VALUE);
return;
} }
_light_mireds = constrain(round(1000000UL / kelvin), LIGHT_MIN_MIREDS, LIGHT_MAX_MIREDS);
// Calculate colors // Calculate colors
kelvin /= 100;
unsigned int red = (kelvin <= 66) unsigned int red = (kelvin <= 66)
? LIGHT_MAX_VALUE ? LIGHT_MAX_VALUE
: 329.698727446 * pow((kelvin - 60), -0.1332047592);
: 329.698727446 * fs_pow((double) (kelvin - 60), -0.1332047592);
unsigned int green = (kelvin <= 66) unsigned int green = (kelvin <= 66)
? 99.4708025861 * log(kelvin) - 161.1195681661
: 288.1221695283 * pow(kelvin, -0.0755148492);
? 99.4708025861 * fs_log(kelvin) - 161.1195681661
: 288.1221695283 * fs_pow((double) kelvin, -0.0755148492);
unsigned int blue = (kelvin >= 66) unsigned int blue = (kelvin >= 66)
? LIGHT_MAX_VALUE ? LIGHT_MAX_VALUE
: ((kelvin <= 19) : ((kelvin <= 19)
? 0 ? 0
: 138.5177312231 * log(kelvin - 10) - 305.0447927307);
: 138.5177312231 * fs_log(kelvin - 10) - 305.0447927307);
_setRGBInputValue(
constrain(red, 0, LIGHT_MAX_VALUE),
constrain(green, 0, LIGHT_MAX_VALUE),
constrain(blue, 0, LIGHT_MAX_VALUE)
);
}
_setRGBInputValue(red, green, blue);
void _fromKelvin(unsigned long kelvin) {
_fromKelvin(kelvin, true);
} }
// Color temperature is measured in mireds (kelvin = 1e6/mired) // Color temperature is measured in mireds (kelvin = 1e6/mired)
void _fromMireds(unsigned long mireds) { void _fromMireds(unsigned long mireds) {
if (mireds == 0) mireds = 1;
_light_mireds = mireds;
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100;
_fromKelvin(kelvin, false);
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000);
_fromKelvin(kelvin);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -313,15 +331,15 @@ void _toRGB(char * rgb, size_t len) {
} }
void _toHSV(char * hsv, size_t len) { void _toHSV(char * hsv, size_t len) {
double min, max, h, s, v;
double h, s, v;
double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS; double brightness = (double) _light_brightness / LIGHT_MAX_BRIGHTNESS;
double r = (double) (_light_channel[0].inputValue * brightness) / 255.0; double r = (double) (_light_channel[0].inputValue * brightness) / 255.0;
double g = (double) (_light_channel[1].inputValue * brightness) / 255.0; double g = (double) (_light_channel[1].inputValue * brightness) / 255.0;
double b = (double) (_light_channel[2].inputValue * brightness) / 255.0; double b = (double) (_light_channel[2].inputValue * brightness) / 255.0;
min = std::min(r, std::min(g, b));
max = std::max(r, std::max(g, b));
double min = std::min(r, std::min(g, b));
double max = std::max(r, std::max(g, b));
v = 100.0 * max; v = 100.0 * max;
if (v == 0) { if (v == 0) {
@ -389,6 +407,7 @@ unsigned int _toPWM(unsigned char id) {
} }
void _shadow() { void _shadow() {
// Update transition ticker // Update transition ticker
_light_steps_left--; _light_steps_left--;
if (_light_steps_left == 0) _light_transition_ticker.detach(); if (_light_steps_left == 0) _light_transition_ticker.detach();
@ -396,15 +415,20 @@ void _shadow() {
// Transitions // Transitions
unsigned char target; unsigned char target;
for (unsigned int i=0; i < _light_channel.size(); i++) { for (unsigned int i=0; i < _light_channel.size(); i++) {
target = _light_state ? _light_channel[i].value : 0;
target = _light_state && _light_channel[i].state ? _light_channel[i].value : 0;
if (_light_steps_left == 0) { if (_light_steps_left == 0) {
_light_channel[i].current = target; _light_channel[i].current = target;
} else { } else {
double difference = (double) (target - _light_channel[i].current) / (_light_steps_left + 1); double difference = (double) (target - _light_channel[i].current) / (_light_steps_left + 1);
_light_channel[i].current = _light_channel[i].current + difference; _light_channel[i].current = _light_channel[i].current + difference;
} }
_light_channel[i].shadow = _light_channel[i].current; _light_channel[i].shadow = _light_channel[i].current;
} }
} }
void _lightProviderUpdate() { void _lightProviderUpdate() {
@ -441,6 +465,7 @@ void _lightColorSave() {
setSetting("ch", i, _light_channel[i].inputValue); setSetting("ch", i, _light_channel[i].inputValue);
} }
setSetting("brightness", _light_brightness); setSetting("brightness", _light_brightness);
setSetting("mireds", _light_mireds);
saveSettings(); saveSettings();
} }
@ -449,6 +474,7 @@ void _lightColorRestore() {
_light_channel[i].inputValue = getSetting("ch", i, i==0 ? 255 : 0).toInt(); _light_channel[i].inputValue = getSetting("ch", i, i==0 ? 255 : 0).toInt();
} }
_light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt(); _light_brightness = getSetting("brightness", LIGHT_MAX_BRIGHTNESS).toInt();
_light_mireds = getSetting("mireds", _light_mireds).toInt();
lightUpdate(false, false); lightUpdate(false, false);
} }
@ -751,6 +777,10 @@ void _lightWebSocketOnSend(JsonObject& root) {
bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1; bool useRGB = getSetting("useRGB", LIGHT_USE_RGB).toInt() == 1;
root["useRGB"] = useRGB; root["useRGB"] = useRGB;
if (_light_has_color) { if (_light_has_color) {
if (_light_use_cct) {
root["useCCT"] = _light_use_cct;
root["mireds"] = _light_mireds;
}
if (useRGB) { if (useRGB) {
root["rgb"] = lightColor(true); root["rgb"] = lightColor(true);
root["brightness"] = lightBrightness(); root["brightness"] = lightBrightness();
@ -780,6 +810,12 @@ void _lightWebSocketOnAction(uint32_t client_id, const char * action, JsonObject
lightUpdate(true, true); lightUpdate(true, true);
} }
} }
if (_light_use_cct) {
if (strcmp(action, "mireds") == 0) {
_fromMireds(data["mireds"]);
lightUpdate(true, true);
}
}
} }
if (strcmp(action, "channel") == 0) { if (strcmp(action, "channel") == 0) {
@ -961,6 +997,12 @@ void _lightConfigure() {
setSetting("useWhite", _light_use_white); setSetting("useWhite", _light_use_white);
} }
_light_use_cct = getSetting("useCCT", LIGHT_USE_CCT).toInt() == 1;
if (_light_use_cct && ((_light_channel.size() < 5) || !_light_use_white)) {
_light_use_cct = false;
setSetting("useCCT", _light_use_cct);
}
_light_use_gamma = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1; _light_use_gamma = getSetting("useGamma", LIGHT_USE_GAMMA).toInt() == 1;
_light_use_transitions = getSetting("useTransitions", LIGHT_USE_TRANSITIONS).toInt() == 1; _light_use_transitions = getSetting("useTransitions", LIGHT_USE_TRANSITIONS).toInt() == 1;
_light_transition_time = getSetting("lightTime", LIGHT_TRANSITION_TIME).toInt(); _light_transition_time = getSetting("lightTime", LIGHT_TRANSITION_TIME).toInt();


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

@ -28,7 +28,7 @@ void _mdnsFindMQTT() {
#endif #endif
void _mdnsServerStart() { void _mdnsServerStart() {
if (MDNS.begin(WiFi.getMode() == WIFI_AP ? APP_NAME : (char *) WiFi.hostname().c_str())) {
if (MDNS.begin((char *) getSetting("hostname").c_str())) {
DEBUG_MSG_P(PSTR("[MDNS] OK\n")); DEBUG_MSG_P(PSTR("[MDNS] OK\n"));
} else { } else {
DEBUG_MSG_P(PSTR("[MDNS] FAIL\n")); DEBUG_MSG_P(PSTR("[MDNS] FAIL\n"));


+ 74
- 0
code/espurna/migrate.ino View File

@ -930,6 +930,80 @@ void migrate() {
setSetting("relayGPIO", 1, 5); setSetting("relayGPIO", 1, 5);
setSetting("relayType", 1, RELAY_TYPE_NORMAL); setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ALLNET_4DUINO_IOT_WLAN_RELAIS)
setSetting("board", 73);
setSetting("relayGPIO", 0, 14);
setSetting("relayResetGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_LATCHED);
#elif defined(TONBUX_MOSQUITO_KILLER)
setSetting("board", 74);
setSetting("ledGPIO", 0, 15);
setSetting("ledLogic", 0, 1);
setSetting("ledGPIO", 1, 14);
setSetting("ledLogic", 1, 1);
setSetting("ledGPIO", 2, 12);
setSetting("ledLogic", 2, 0);
setSetting("ledGPIO", 3, 16);
setSetting("ledLogic", 3, 0);
setSetting("btnGPIO", 0, 2);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(NEO_COOLCAM_POWER_PLUG_WIFI)
setSetting("board", 75);
setSetting("ledGPIO", 0, 4);
setSetting("ledLogic", 0, 1);
setSetting("btnGPIO", 0, 13);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 12);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
#elif defined(PILOTAK_ESP_DIN_V1)
setSetting("board", 76);
setSetting("ledGPIO", 0, 16);
setSetting("ledLogic", 0, 0);
setSetting("btnGPIO", 0, 0);
setSetting("btnRelay", 0, 0);
setSetting("relayGPIO", 0, 4);
setSetting("relayGPIO", 1, 5);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
#elif defined(ESTINK_WIFI_POWER_STRIP)
setSetting("board", 76);
setSetting("btnGPIO", 0, 16);
setSetting("btnRelay", 0, 3);
setSetting("ledGPIO", 0, 0);
setSetting("ledGPIO", 1, 12);
setSetting("ledGPIO", 2, 3);
setSetting("ledGPIO", 3, 5);
setSetting("ledLogic", 0, 1);
setSetting("ledLogic", 1, 1);
setSetting("ledLogic", 2, 1);
setSetting("ledLogic", 3, 1);
setSetting("ledMode", 0, LED_MODE_FINDME);
setSetting("ledMode", 1, LED_MODE_FOLLOW);
setSetting("ledMode", 2, LED_MODE_FOLLOW);
setSetting("ledMode", 3, LED_MODE_FOLLOW);
setSetting("ledRelay", 1, 1);
setSetting("ledRelay", 2, 2);
setSetting("ledRelay", 3, 3);
setSetting("relayGPIO", 0, 14);
setSetting("relayGPIO", 1, 13);
setSetting("relayGPIO", 2, 4);
setSetting("relayGPIO", 3, 15);
setSetting("relayType", 0, RELAY_TYPE_NORMAL);
setSetting("relayType", 1, RELAY_TYPE_NORMAL);
setSetting("relayType", 2, RELAY_TYPE_NORMAL);
setSetting("relayType", 3, RELAY_TYPE_NORMAL);
#else #else
// Allow users to define new settings without migration config // Allow users to define new settings without migration config


+ 22
- 11
code/espurna/mqtt.ino View File

@ -8,7 +8,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#if MQTT_SUPPORT #if MQTT_SUPPORT
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESP8266mDNS.h> #include <ESP8266mDNS.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
@ -218,7 +218,6 @@ void _mqttConfigure() {
if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1); if (_mqtt_topic.endsWith("/")) _mqtt_topic.remove(_mqtt_topic.length()-1);
// Placeholders // Placeholders
_mqtt_topic.replace("{identifier}", getSetting("hostname"));
_mqtt_topic.replace("{hostname}", getSetting("hostname")); _mqtt_topic.replace("{hostname}", getSetting("hostname"));
_mqtt_topic.replace("{magnitude}", "#"); _mqtt_topic.replace("{magnitude}", "#");
if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#"; if (_mqtt_topic.indexOf("#") == -1) _mqtt_topic = _mqtt_topic + "/#";
@ -250,6 +249,14 @@ void _mqttConfigure() {
} }
void _mqttBackwards() {
String mqttTopic = getSetting("mqttTopic", MQTT_TOPIC);
if (mqttTopic.indexOf("{identifier}") > 0) {
mqttTopic.replace("{identifier}", "{hostname}");
setSetting("mqttTopic", mqttTopic);
}
}
unsigned long _mqttNextMessageId() { unsigned long _mqttNextMessageId() {
static unsigned long id = 0; static unsigned long id = 0;
@ -258,7 +265,7 @@ unsigned long _mqttNextMessageId() {
if (id == 0) { if (id == 0) {
// read id from EEPROM and shift it // read id from EEPROM and shift it
id = EEPROM.read(EEPROM_MESSAGE_ID);
id = EEPROMr.read(EEPROM_MESSAGE_ID);
if (id == 0xFF) { if (id == 0xFF) {
// There was nothing in EEPROM, // There was nothing in EEPROM,
@ -267,9 +274,9 @@ unsigned long _mqttNextMessageId() {
} else { } else {
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROM.read(EEPROM_MESSAGE_ID + 3);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 1);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 2);
id = (id << 8) + EEPROMr.read(EEPROM_MESSAGE_ID + 3);
// Calculate next block and start from there // Calculate next block and start from there
id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT)); id = MQTT_MESSAGE_ID_SHIFT * (1 + (id / MQTT_MESSAGE_ID_SHIFT));
@ -280,11 +287,11 @@ unsigned long _mqttNextMessageId() {
// Save to EEPROM every MQTT_MESSAGE_ID_SHIFT // Save to EEPROM every MQTT_MESSAGE_ID_SHIFT
if (id % MQTT_MESSAGE_ID_SHIFT == 0) { if (id % MQTT_MESSAGE_ID_SHIFT == 0) {
EEPROM.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROM.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROM.commit();
EEPROMr.write(EEPROM_MESSAGE_ID + 0, (id >> 24) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 1, (id >> 16) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 2, (id >> 8) & 0xFF);
EEPROMr.write(EEPROM_MESSAGE_ID + 3, (id >> 0) & 0xFF);
EEPROMr.commit();
} }
id++; id++;
@ -606,6 +613,8 @@ void mqttFlush() {
// Send // Send
String output; String output;
root.printTo(output); root.printTo(output);
jsonBuffer.clear();
mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false); mqttSendRaw(_mqtt_topic_json.c_str(), output.c_str(), false);
// Clear queue // Clear queue
@ -741,6 +750,8 @@ void mqttReset() {
void mqttSetup() { void mqttSetup() {
_mqttBackwards();
DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"), DEBUG_MSG_P(PSTR("[MQTT] Async %s, SSL %s, Autoconnect %s\n"),
MQTT_USE_ASYNC ? "ENABLED" : "DISABLED", MQTT_USE_ASYNC ? "ENABLED" : "DISABLED",
ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED", ASYNC_TCP_SSL_ENABLED ? "ENABLED" : "DISABLED",


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

@ -70,6 +70,7 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
#ifdef DEBUG_PORT #ifdef DEBUG_PORT
Update.printError(DEBUG_PORT); Update.printError(DEBUG_PORT);
#endif #endif
eepromRotate(true);
} }
DEBUG_MSG_P(PSTR("[OTA] Disconnected\n")); DEBUG_MSG_P(PSTR("[OTA] Disconnected\n"));
@ -133,6 +134,9 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
} }
#endif #endif
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url); DEBUG_MSG_P(PSTR("[OTA] Downloading %s\n"), _ota_url);
char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)]; char buffer[strlen_P(OTA_REQUEST_TEMPLATE) + strlen(_ota_url) + strlen(_ota_host)];
snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host); snprintf_P(buffer, sizeof(buffer), OTA_REQUEST_TEMPLATE, _ota_url, _ota_host);
@ -140,7 +144,6 @@ void _otaFrom(const char * host, unsigned int port, const char * url) {
}, NULL); }, NULL);
#if ASYNC_TCP_SSL_ENABLED #if ASYNC_TCP_SSL_ENABLED
bool connected = _ota_client->connect(host, port, 443 == port); bool connected = _ota_client->connect(host, port, 443 == port);
#else #else
@ -214,10 +217,16 @@ void otaSetup() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
ArduinoOTA.onStart([]() { ArduinoOTA.onStart([]() {
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[OTA] Start\n")); DEBUG_MSG_P(PSTR("[OTA] Start\n"));
#if WEB_SUPPORT #if WEB_SUPPORT
wsSend_P(PSTR("{\"message\": 2}")); wsSend_P(PSTR("{\"message\": 2}"));
#endif #endif
}); });
ArduinoOTA.onEnd([]() { ArduinoOTA.onEnd([]() {
@ -242,6 +251,7 @@ void otaSetup() {
else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n")); else if (error == OTA_RECEIVE_ERROR) DEBUG_MSG_P(PSTR("Receive Failed\n"));
else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n")); else if (error == OTA_END_ERROR) DEBUG_MSG_P(PSTR("End Failed\n"));
#endif #endif
eepromRotate(true);
}); });
ArduinoOTA.begin(); ArduinoOTA.begin();


+ 96
- 24
code/espurna/relay.ino View File

@ -6,7 +6,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <Ticker.h> #include <Ticker.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <vector> #include <vector>
@ -209,6 +209,8 @@ void _relayProcess(bool mode) {
void relayPulse(unsigned char id) { void relayPulse(unsigned char id) {
_relays[id].pulseTicker.detach();
byte mode = _relays[id].pulse; byte mode = _relays[id].pulse;
if (mode == RELAY_PULSE_NONE) return; if (mode == RELAY_PULSE_NONE) return;
unsigned long ms = _relays[id].pulse_ms; unsigned long ms = _relays[id].pulse_ms;
@ -217,10 +219,12 @@ void relayPulse(unsigned char id) {
bool status = relayStatus(id); bool status = relayStatus(id);
bool pulseStatus = (mode == RELAY_PULSE_ON); bool pulseStatus = (mode == RELAY_PULSE_ON);
if (pulseStatus == status) {
_relays[id].pulseTicker.detach();
} else {
if (pulseStatus != status) {
DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms);
_relays[id].pulseTicker.once_ms(ms, relayToggle, id); _relays[id].pulseTicker.once_ms(ms, relayToggle, id);
// Reconfigure after dynamic pulse
_relays[id].pulse = getSetting("relayPulse", id, RELAY_PULSE_MODE).toInt();
_relays[id].pulse_ms = 1000 * getSetting("relayTime", id, RELAY_PULSE_MODE).toFloat();
} }
} }
@ -356,9 +360,9 @@ void relaySave() {
if (relayStatus(i)) mask += bit; if (relayStatus(i)) mask += bit;
bit += bit; bit += bit;
} }
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask); DEBUG_MSG_P(PSTR("[RELAY] Saving mask: %d\n"), mask);
EEPROM.commit();
EEPROMr.commit();
} }
void relayToggle(unsigned char id, bool report, bool group_report) { void relayToggle(unsigned char id, bool report, bool group_report) {
@ -436,7 +440,7 @@ void _relayBoot() {
bool trigger_save = false; bool trigger_save = false;
// Get last statuses from EEPROM // Get last statuses from EEPROM
unsigned char mask = EEPROM.read(EEPROM_RELAY_STATUS);
unsigned char mask = EEPROMr.read(EEPROM_RELAY_STATUS);
DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask); DEBUG_MSG_P(PSTR("[RELAY] Retrieving mask: %d\n"), mask);
// Walk the relays // Walk the relays
@ -473,8 +477,8 @@ void _relayBoot() {
// Save if there is any relay in the RELAY_BOOT_TOGGLE mode // Save if there is any relay in the RELAY_BOOT_TOGGLE mode
if (trigger_save) { if (trigger_save) {
EEPROM.write(EEPROM_RELAY_STATUS, mask);
EEPROM.commit();
EEPROMr.write(EEPROM_RELAY_STATUS, mask);
EEPROMr.commit();
} }
_relayRecursive = false; _relayRecursive = false;
@ -597,9 +601,9 @@ void relaySetupAPI() {
// API entry points (protected with apikey) // API entry points (protected with apikey)
for (unsigned int relayID=0; relayID<relayCount(); relayID++) { for (unsigned int relayID=0; relayID<relayCount(); relayID++) {
char key[15];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
char key[20];
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_RELAY, relayID);
apiRegister(key, apiRegister(key,
[relayID](char * buffer, size_t len) { [relayID](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0); snprintf_P(buffer, len, PSTR("%d"), _relays[relayID].target_status ? 1 : 0);
@ -624,6 +628,30 @@ void relaySetupAPI() {
} }
); );
snprintf_P(key, sizeof(key), PSTR("%s/%d"), MQTT_TOPIC_PULSE, relayID);
apiRegister(key,
[relayID](char * buffer, size_t len) {
dtostrf((double) _relays[relayID].pulse_ms / 1000, 1-len, 3, buffer);
},
[relayID](const char * payload) {
unsigned long pulse = 1000 * String(payload).toFloat();
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[relayID].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), relayID);
}
_relays[relayID].pulse_ms = pulse;
_relays[relayID].pulse = relayStatus(relayID) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(relayID, true, false);
return;
}
);
} }
} }
@ -692,9 +720,14 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
#endif #endif
// Subscribe to own /set topic // Subscribe to own /set topic
char buffer[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(buffer, sizeof(buffer), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(buffer);
char relay_topic[strlen(MQTT_TOPIC_RELAY) + 3];
snprintf_P(relay_topic, sizeof(relay_topic), PSTR("%s/+"), MQTT_TOPIC_RELAY);
mqttSubscribe(relay_topic);
// Subscribe to pulse topic
char pulse_topic[strlen(MQTT_TOPIC_PULSE) + 3];
snprintf_P(pulse_topic, sizeof(pulse_topic), PSTR("%s/+"), MQTT_TOPIC_PULSE);
mqttSubscribe(pulse_topic);
// Subscribe to group topics // Subscribe to group topics
for (unsigned int i=0; i < _relays.size(); i++) { for (unsigned int i=0; i < _relays.size(); i++) {
@ -706,26 +739,53 @@ void relayMQTTCallback(unsigned int type, const char * topic, const char * paylo
if (type == MQTT_MESSAGE_EVENT) { if (type == MQTT_MESSAGE_EVENT) {
// Check relay topic
String t = mqttMagnitude((char *) topic); String t = mqttMagnitude((char *) topic);
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
// magnitude is relay/#/pulse
if (t.startsWith(MQTT_TOPIC_PULSE)) {
unsigned int id = t.substring(strlen(MQTT_TOPIC_PULSE)+1).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
return;
}
unsigned long pulse = 1000 * String(payload).toFloat();
if (0 == pulse) return;
if (RELAY_PULSE_NONE != _relays[id].pulse) {
DEBUG_MSG_P(PSTR("[RELAY] Overriding relay #%d pulse settings\n"), id);
}
_relays[id].pulse_ms = pulse;
_relays[id].pulse = relayStatus(id) ? RELAY_PULSE_ON : RELAY_PULSE_OFF;
relayToggle(id, true, false);
return;
}
// magnitude is relay/#
if (t.startsWith(MQTT_TOPIC_RELAY)) {
// Get relay ID // Get relay ID
unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt(); unsigned int id = t.substring(strlen(MQTT_TOPIC_RELAY)+1).toInt();
if (id >= relayCount()) { if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id); DEBUG_MSG_P(PSTR("[RELAY] Wrong relayID (%d)\n"), id);
} else {
relayStatusWrap(id, value, false);
return;
} }
return;
// Get value
unsigned char value = relayParsePayload(payload);
if (value == 0xFF) return;
relayStatusWrap(id, value, false);
return;
} }
// Check group topics // Check group topics
for (unsigned int i=0; i < _relays.size(); i++) { for (unsigned int i=0; i < _relays.size(); i++) {
@ -797,8 +857,14 @@ void _relayInitCommands() {
settingsRegisterCommand(F("RELAY"), [](Embedis* e) { settingsRegisterCommand(F("RELAY"), [](Embedis* e) {
if (e->argc < 2) { if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n")); DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
} }
int id = String(e->argv[1]).toInt(); int id = String(e->argv[1]).toInt();
if (id >= relayCount()) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong relayID (%d)\n"), id);
return;
}
if (e->argc > 2) { if (e->argc > 2) {
int value = String(e->argv[2]).toInt(); int value = String(e->argv[2]).toInt();
if (value == 2) { if (value == 2) {
@ -808,6 +874,11 @@ void _relayInitCommands() {
} }
} }
DEBUG_MSG_P(PSTR("Status: %s\n"), _relays[id].target_status ? "true" : "false"); DEBUG_MSG_P(PSTR("Status: %s\n"), _relays[id].target_status ? "true" : "false");
if (_relays[id].pulse != RELAY_PULSE_NONE) {
DEBUG_MSG_P(PSTR("Pulse: %s\n"), (_relays[id].pulse == RELAY_PULSE_ON) ? "ON" : "OFF");
DEBUG_MSG_P(PSTR("Pulse time: %d\n"), _relays[id].pulse_ms);
}
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
@ -829,9 +900,10 @@ void relaySetup() {
// Dummy relays for AI Light, Magic Home LED Controller, H801, // Dummy relays for AI Light, Magic Home LED Controller, H801,
// Sonoff Dual and Sonoff RF Bridge // Sonoff Dual and Sonoff RF Bridge
#if DUMMY_RELAY_COUNT > 0 #if DUMMY_RELAY_COUNT > 0
unsigned int _delay_on[8] = {RELAY1_DELAY_ON, RELAY2_DELAY_ON, RELAY3_DELAY_ON, RELAY4_DELAY_ON, RELAY5_DELAY_ON, RELAY6_DELAY_ON, RELAY7_DELAY_ON, RELAY8_DELAY_ON};
unsigned int _delay_off[8] = {RELAY1_DELAY_OFF, RELAY2_DELAY_OFF, RELAY3_DELAY_OFF, RELAY4_DELAY_OFF, RELAY5_DELAY_OFF, RELAY6_DELAY_OFF, RELAY7_DELAY_OFF, RELAY8_DELAY_OFF};
for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) { for (unsigned char i=0; i < DUMMY_RELAY_COUNT; i++) {
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL});
_relays.push_back((relay_t) {0, RELAY_TYPE_NORMAL,0,_delay_on[i], _delay_off[i]});
} }
#else #else


+ 33
- 9
code/espurna/rfbridge.ino View File

@ -156,6 +156,9 @@ void _rfbSendRaw(const byte *message, const unsigned char n = RF_MESSAGE_SIZE) {
void _rfbSend(byte * message) { void _rfbSend(byte * message) {
#if RFB_DIRECT #if RFB_DIRECT
unsigned int protocol = message[1]; unsigned int protocol = message[1];
unsigned int timing =
(message[2] << 8) |
(message[3] << 0) ;
unsigned int bitlength = message[4]; unsigned int bitlength = message[4];
unsigned long rf_code = unsigned long rf_code =
(message[5] << 24) | (message[5] << 24) |
@ -163,7 +166,11 @@ void _rfbSend(byte * message) {
(message[7] << 8) | (message[7] << 8) |
(message[8] << 0) ; (message[8] << 0) ;
_rfModem->setProtocol(protocol); _rfModem->setProtocol(protocol);
if (timing > 0) {
_rfModem->setPulseLength(timing);
}
_rfModem->send(rf_code, bitlength); _rfModem->send(rf_code, bitlength);
_rfModem->resetAvailable();
#else #else
Serial.println(); Serial.println();
Serial.write(RF_CODE_START); Serial.write(RF_CODE_START);
@ -202,6 +209,9 @@ void _rfbSend() {
} }
void _rfbSend(byte * code, unsigned char times) { void _rfbSend(byte * code, unsigned char times) {
#if RFB_DIRECT
times = 1;
#endif
char buffer[RF_MESSAGE_SIZE]; char buffer[RF_MESSAGE_SIZE];
_rfbToChar(code, buffer); _rfbToChar(code, buffer);
@ -231,7 +241,7 @@ void _rfbSendRawOnce(byte *code, unsigned char length) {
#endif // RF_RAW_SUPPORT #endif // RF_RAW_SUPPORT
bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
bool _rfbMatch(char* code, unsigned char& relayID, unsigned char& value, char* buffer = NULL) {
if (strlen(code) != 18) return false; if (strlen(code) != 18) return false;
@ -247,6 +257,7 @@ bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i); DEBUG_MSG_P(PSTR("[RFBRIDGE] Match ON code for relay %d\n"), i);
value = 1; value = 1;
found = true; found = true;
if (buffer) strcpy(buffer, code_on.c_str());
} }
String code_off = rfbRetrieve(i, false); String code_off = rfbRetrieve(i, false);
@ -254,6 +265,7 @@ bool _rfbMatch(char * code, unsigned char& relayID, unsigned char& value) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i); DEBUG_MSG_P(PSTR("[RFBRIDGE] Match OFF code for relay %d\n"), i);
if (found) value = 2; if (found) value = 2;
found = true; found = true;
if (buffer) strcpy(buffer, code_off.c_str());
} }
if (found) { if (found) {
@ -285,12 +297,25 @@ void _rfbDecode() {
#endif #endif
} }
unsigned char id;
unsigned char status;
bool matched = false;
if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) { if (action == RF_CODE_LEARN_OK || action == RF_CODE_RFIN) {
_rfbAck();
_rfbToChar(&_uartbuf[1], buffer);
/* Look for the code, possibly replacing the code with the exact learned one on match
* we want to do this on learn too to be sure that the learned code is the same if it
* is equivalent
*/
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received message '%s'\n"), buffer);
matched = _rfbMatch(buffer, id, status, buffer);
DEBUG_MSG_P(PSTR("[RFBRIDGE] Matched message '%s'\n"), buffer);
#if MQTT_SUPPORT #if MQTT_SUPPORT
_rfbToChar(&_uartbuf[1], buffer);
mqttSend(MQTT_TOPIC_RFIN, buffer); mqttSend(MQTT_TOPIC_RFIN, buffer);
#endif #endif
_rfbAck();
} }
if (action == RF_CODE_LEARN_OK) { if (action == RF_CODE_LEARN_OK) {
@ -308,13 +333,8 @@ void _rfbDecode() {
} }
if (action == RF_CODE_RFIN) { if (action == RF_CODE_RFIN) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer); DEBUG_MSG_P(PSTR("[RFBRIDGE] Forward message '%s'\n"), buffer);
// Look for the code
unsigned char id;
unsigned char status = 0;
if (_rfbMatch(buffer, id, status)) {
if (matched) {
_rfbin = true; _rfbin = true;
if (status == 2) { if (status == 2) {
relayToggle(id); relayToggle(id);
@ -354,11 +374,14 @@ void _rfbReceive() {
unsigned long rf_code = _rfModem->getReceivedValue(); unsigned long rf_code = _rfModem->getReceivedValue();
if ( rf_code > 0) { if ( rf_code > 0) {
DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code); DEBUG_MSG_P(PSTR("[RFBRIDGE] Received code: %08X\n"), rf_code);
unsigned int timing = _rfModem->getReceivedDelay();
memset(_uartbuf, 0, sizeof(_uartbuf)); memset(_uartbuf, 0, sizeof(_uartbuf));
unsigned char *msgbuf = _uartbuf + 1; unsigned char *msgbuf = _uartbuf + 1;
_uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN; _uartbuf[0] = _learning ? RF_CODE_LEARN_OK: RF_CODE_RFIN;
msgbuf[0] = 0xC0; msgbuf[0] = 0xC0;
msgbuf[1] = _rfModem->getReceivedProtocol(); msgbuf[1] = _rfModem->getReceivedProtocol();
msgbuf[2] = timing >> 8;
msgbuf[3] = timing >> 0;
msgbuf[4] = _rfModem->getReceivedBitlength(); msgbuf[4] = _rfModem->getReceivedBitlength();
msgbuf[5] = rf_code >> 24; msgbuf[5] = rf_code >> 24;
msgbuf[6] = rf_code >> 16; msgbuf[6] = rf_code >> 16;
@ -594,6 +617,7 @@ void rfbSetup() {
_rfModem = new RCSwitch(); _rfModem = new RCSwitch();
_rfModem->enableReceive(RFB_RX_PIN); _rfModem->enableReceive(RFB_RX_PIN);
_rfModem->enableTransmit(RFB_TX_PIN); _rfModem->enableTransmit(RFB_TX_PIN);
_rfModem->setRepeatTransmit(6);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF receiver on GPIO %u\n"), RFB_RX_PIN); DEBUG_MSG_P(PSTR("[RFBRIDGE] RF receiver on GPIO %u\n"), RFB_RX_PIN);
DEBUG_MSG_P(PSTR("[RFBRIDGE] RF transmitter on GPIO %u\n"), RFB_TX_PIN); DEBUG_MSG_P(PSTR("[RFBRIDGE] RF transmitter on GPIO %u\n"), RFB_TX_PIN);
#endif #endif


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

@ -450,6 +450,18 @@ void _sensorLoad() {
} }
#endif #endif
#if GEIGER_SUPPORT
{
GeigerSensor * sensor = new GeigerSensor(); // Create instance of thr Geiger module.
sensor->setGPIO(GEIGER_PIN); // Interrupt pin of the attached geiger counter board.
sensor->setMode(GEIGER_PIN_MODE); // This pin is an input.
sensor->setDebounceTime(GEIGER_DEBOUNCE); // Debounce time 25ms, because https://github.com/Trickx/espurna/wiki/Geiger-counter
sensor->setInterruptMode(GEIGER_INTERRUPT_MODE); // Interrupt triggering: edge detection rising.
sensor->setCPM2SievertFactor(GEIGER_CPM2SIEVERT); // Conversion factor from counts per minute to µSv/h
_sensors.push_back(sensor);
}
#endif
#if GUVAS12SD_SUPPORT #if GUVAS12SD_SUPPORT
{ {
GUVAS12SDSensor * sensor = new GUVAS12SDSensor(); GUVAS12SDSensor * sensor = new GUVAS12SDSensor();
@ -592,7 +604,7 @@ void _sensorInit() {
new_magnitude.min_change = 0; new_magnitude.min_change = 0;
if (type == MAGNITUDE_DIGITAL) { if (type == MAGNITUDE_DIGITAL) {
new_magnitude.filter = new MaxFilter(); new_magnitude.filter = new MaxFilter();
} else if (type == MAGNITUDE_EVENTS) {
} else if (type == MAGNITUDE_EVENTS || type == MAGNITUDE_GEIGER_CPM|| type == MAGNITUDE_GEIGER_SIEVERT) { // For geiger counting moving average filter is the most appropriate if needed at all.
new_magnitude.filter = new MovingAverageFilter(); new_magnitude.filter = new MovingAverageFilter();
} else { } else {
new_magnitude.filter = new MedianFilter(); new_magnitude.filter = new MedianFilter();


+ 9
- 3
code/espurna/sensors/CSE7766Sensor.h View File

@ -270,10 +270,16 @@ class CSE7766Sensor : public BaseSensor {
} }
// Calculate energy // Calculate energy
static unsigned long cf_pulses_last = 0;
unsigned long cf_pulses = _data[21] << 8 | _data[22];
unsigned int difference;
static unsigned int cf_pulses_last = 0;
unsigned int cf_pulses = _data[21] << 8 | _data[22];
if (0 == cf_pulses_last) cf_pulses_last = cf_pulses; if (0 == cf_pulses_last) cf_pulses_last = cf_pulses;
_energy += (cf_pulses - cf_pulses_last) * (float) _coefP / 1000000.0;
if (cf_pulses < cf_pulses_last) {
difference = cf_pulses + (0xFFFF - cf_pulses_last) + 1;
} else {
difference = cf_pulses - cf_pulses_last;
}
_energy += difference * (float) _coefP / 1000000.0;
cf_pulses_last = cf_pulses; cf_pulses_last = cf_pulses;
} }


+ 15
- 8
code/espurna/sensors/EmonSensor.h View File

@ -10,9 +10,11 @@
#undef I2C_SUPPORT #undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Explicitly request I2C support. #define I2C_SUPPORT 1 // Explicitly request I2C support.
#include "Arduino.h" #include "Arduino.h"
#include "I2CSensor.h" #include "I2CSensor.h"
extern "C" {
#include "libs/fs_math.h"
}
class EmonSensor : public I2CSensor { class EmonSensor : public I2CSensor {
@ -43,6 +45,7 @@ class EmonSensor : public I2CSensor {
if (actual == 0) return; if (actual == 0) return;
if (expected == actual) return; if (expected == actual) return;
_current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual); _current_ratio[channel] = _current_ratio[channel] * ((double) expected / (double) actual);
calculateFactors(channel);
_dirty = true; _dirty = true;
} }
@ -70,6 +73,7 @@ class EmonSensor : public I2CSensor {
if (channel >= _channels) return; if (channel >= _channels) return;
if (_current_ratio[channel] == current_ratio) return; if (_current_ratio[channel] == current_ratio) return;
_current_ratio[channel] = current_ratio; _current_ratio[channel] = current_ratio;
calculateFactors(channel);
_dirty = true; _dirty = true;
} }
@ -105,8 +109,7 @@ class EmonSensor : public I2CSensor {
for (unsigned char i=0; i<_channels; i++) { for (unsigned char i=0; i<_channels; i++) {
_energy[i] = _current[i] = 0; _energy[i] = _current[i] = 0;
_pivot[i] = _adc_counts >> 1; _pivot[i] = _adc_counts >> 1;
_current_factor[i] = _current_ratio[i] * _reference / _adc_counts;
_multiplier[i] = calculateMultiplier(_current_factor[i]);
calculateFactors(i);
} }
#if SENSOR_DEBUG #if SENSOR_DEBUG
@ -145,18 +148,22 @@ class EmonSensor : public I2CSensor {
virtual unsigned int readADC(unsigned char channel) {} virtual unsigned int readADC(unsigned char channel) {}
unsigned int calculateMultiplier(double current_factor) {
void calculateFactors(unsigned char channel) {
_current_factor[channel] = _current_ratio[channel] * _reference / _adc_counts;
unsigned int s = 1; unsigned int s = 1;
unsigned int i = 1; unsigned int i = 1;
unsigned int m = s * i; unsigned int m = s * i;
unsigned int multiplier; unsigned int multiplier;
while (m * current_factor < 1) {
while (m * _current_factor[channel] < 1) {
multiplier = m; multiplier = m;
i = (i == 1) ? 2 : (i == 2) ? 5 : 1; i = (i == 1) ? 2 : (i == 2) ? 5 : 1;
if (i == 1) s *= 10; if (i == 1) s *= 10;
m = s * i; m = s * i;
} }
return multiplier;
_multiplier[channel] = multiplier;
} }
double read(unsigned char channel) { double read(unsigned char channel) {
@ -192,7 +199,7 @@ class EmonSensor : public I2CSensor {
} }
// Calculate current // Calculate current
double rms = _samples > 0 ? sqrt(sum / _samples) : 0;
double rms = _samples > 0 ? fs_sqrt(sum / _samples) : 0;
double current = _current_factor[channel] * rms; double current = _current_factor[channel] * rms;
current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel]; current = (double) (int(current * _multiplier[channel]) - 1) / _multiplier[channel];
if (current < 0) current = 0; if (current < 0) current = 0;
@ -206,7 +213,7 @@ class EmonSensor : public I2CSensor {
DEBUG_MSG("[EMON] Min value: %d\n", min); DEBUG_MSG("[EMON] Min value: %d\n", min);
DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel])); DEBUG_MSG("[EMON] Midpoint value: %d\n", int(_pivot[channel]));
DEBUG_MSG("[EMON] RMS value: %d\n", int(rms)); DEBUG_MSG("[EMON] RMS value: %d\n", int(rms));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(current));
DEBUG_MSG("[EMON] Current (mA): %d\n", int(1000 * current));
#endif #endif
// Check timing // Check timing


+ 298
- 0
code/espurna/sensors/GeigerSensor.h View File

@ -0,0 +1,298 @@
// -----------------------------------------------------------------------------
// Geiger Sensor based on Event Counter Sensor
// Copyright (C) 2018 by Sven Kopetzki <skopetzki at web dot de>
// Documentation: https://github.com/Trickx/espurna/wiki/Geiger-counter
// -----------------------------------------------------------------------------
#if SENSOR_SUPPORT && GEIGER_SUPPORT
#pragma once
#include "Arduino.h"
#include "BaseSensor.h"
class GeigerSensor : public BaseSensor {
public:
// ---------------------------------------------------------------------
// Public
// ---------------------------------------------------------------------
GeigerSensor() : BaseSensor() {
_count = 2;
_sensor_id = SENSOR_GEIGER_ID;
}
~GeigerSensor() {
_enableInterrupts(false);
}
// ---------------------------------------------------------------------
void setGPIO(unsigned char gpio) {
_gpio = gpio;
}
void setMode(unsigned char mode) {
_mode = mode;
}
void setInterruptMode(unsigned char mode) {
_interrupt_mode = mode;
}
void setDebounceTime(unsigned long debounce) {
_debounce = debounce;
}
void setCPM2SievertFactor(unsigned int cpm2sievert) {
_cpm2sievert = cpm2sievert;
}
// ---------------------------------------------------------------------
unsigned char getGPIO() {
return _gpio;
}
unsigned char getMode() {
return _mode;
}
unsigned char getInterruptMode() {
return _interrupt_mode;
}
unsigned long getDebounceTime() {
return _debounce;
}
unsigned long getCPM2SievertFactor() {
return _cpm2sievert;
}
// ---------------------------------------------------------------------
// Sensors API
// ---------------------------------------------------------------------
// Initialization method, must be idempotent
// Defined outside the class body
void begin() {
pinMode(_gpio, _mode);
_enableInterrupts(true);
_ready = true;
}
// Descriptive name of the sensor
String description() {
char buffer[20];
snprintf(buffer, sizeof(buffer), "µSv/h @ GPIO%d", _gpio);
return String(buffer);
}
// Descriptive name of the slot # index
String slot(unsigned char index) {
char buffer[30];
unsigned char i=0;
#if GEIGER_REPORT_CPM
if (index == i++) {
snprintf(buffer, sizeof(buffer), "Counts per Minute @ GPIO%d", _gpio);
return String(buffer);
}
#endif
#if GEIGER_REPORT_SIEVERTS
if (index == i++) {
snprintf(buffer, sizeof(buffer), "CPM / %d = µSv/h", _cpm2sievert);
return String(buffer);
}
#endif
snprintf(buffer, sizeof(buffer), "Events @ GPIO%d", _gpio);
return String(buffer);
};
// Address of the sensor (it could be the GPIO or I2C address)
String address(unsigned char index) {
return String(_gpio);
}
// Type for slot # index
unsigned char type(unsigned char index) {
unsigned char i=0;
#if GEIGER_REPORT_CPM
if (index == i++) return MAGNITUDE_GEIGER_CPM;
#endif
#if GEIGER_REPORT_SIEVERTS
if (index == i++) return MAGNITUDE_GEIGER_SIEVERT;
#endif
return MAGNITUDE_NONE;
}
// Current value for slot # index
double value(unsigned char index) {
unsigned char i=0;
#if GEIGER_REPORT_CPM
if (index == i++) {
unsigned long _period_begin = _lastreport_cpm;
_lastreport_cpm = millis();
double value = _events * 60000;
value = value / (_lastreport_cpm-_period_begin);
#if SENSOR_DEBUG
char data[128]; char buffer[10];
dtostrf(value, 1-sizeof(buffer), 4, buffer);
snprintf(data, sizeof(data), "Ticks: %u | Interval: %u | CPM: %s", _ticks, (_lastreport_cpm-_period_begin), buffer);
DEBUG_MSG("[GEIGER] %s\n", data);
#endif
_events = 0;
return value;
}
#endif
#if GEIGER_REPORT_SIEVERTS
if (index == i++) {
unsigned long _period_begin = _lastreport_sv;
_lastreport_sv = millis();
double value = _ticks * 60000 / _cpm2sievert;
value = value / (_lastreport_sv-_period_begin);
#if SENSOR_DEBUG
char data[128]; char buffer[10];
dtostrf(value, 1-sizeof(buffer), 4, buffer);
snprintf(data, sizeof(data), "Ticks: %u | Interval: %u | µSievert: %s", _ticks, (_lastreport_sv-_period_begin), buffer);
DEBUG_MSG("[GEIGER] %s\n", data);
#endif
_ticks = 0;
return value;
}
#endif
return 0;
}
// Handle interrupt calls
void handleInterrupt(unsigned char gpio) {
(void) gpio;
static unsigned long last = 0;
if (millis() - last > _debounce) {
_events = _events + 1;
_ticks = _ticks + 1;
last = millis();
}
}
protected:
// ---------------------------------------------------------------------
// Interrupt management
// ---------------------------------------------------------------------
void _attach(GeigerSensor * instance, unsigned char gpio, unsigned char mode);
void _detach(unsigned char gpio);
void _enableInterrupts(bool value) {
static unsigned char _interrupt_gpio = GPIO_NONE;
if (value) {
if (_interrupt_gpio != GPIO_NONE) _detach(_interrupt_gpio);
_attach(this, _gpio, _interrupt_mode);
_interrupt_gpio = _gpio;
} else if (_interrupt_gpio != GPIO_NONE) {
_detach(_interrupt_gpio);
_interrupt_gpio = GPIO_NONE;
}
}
// ---------------------------------------------------------------------
// Protected
// ---------------------------------------------------------------------
volatile unsigned long _events = 0;
volatile unsigned long _ticks = 0;
unsigned long _debounce = GEIGER_DEBOUNCE;
unsigned int _cpm2sievert = GEIGER_CPM2SIEVERT;
unsigned char _gpio;
unsigned char _mode;
unsigned char _interrupt_mode;
// Added for µSievert calculations
unsigned long _lastreport_cpm = millis();
unsigned long _lastreport_sv = _lastreport_cpm;
};
// -----------------------------------------------------------------------------
// Interrupt helpers
// -----------------------------------------------------------------------------
GeigerSensor * _geiger_sensor_instance[10] = {NULL};
void ICACHE_RAM_ATTR _geiger_sensor_isr(unsigned char gpio) {
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_geiger_sensor_instance[index]) {
_geiger_sensor_instance[index]->handleInterrupt(gpio);
}
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_0() {
_geiger_sensor_isr(0);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_1() {
_geiger_sensor_isr(1);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_2() {
_geiger_sensor_isr(2);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_3() {
_geiger_sensor_isr(3);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_4() {
_geiger_sensor_isr(4);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_5() {
_geiger_sensor_isr(5);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_12() {
_geiger_sensor_isr(12);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_13() {
_geiger_sensor_isr(13);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_14() {
_geiger_sensor_isr(14);
}
void ICACHE_RAM_ATTR _geiger_sensor_isr_15() {
_geiger_sensor_isr(15);
}
static void (*_geiger_sensor_isr_list[10])() = {
_geiger_sensor_isr_0, _geiger_sensor_isr_1, _geiger_sensor_isr_2,
_geiger_sensor_isr_3, _geiger_sensor_isr_4, _geiger_sensor_isr_5,
_geiger_sensor_isr_12, _geiger_sensor_isr_13, _geiger_sensor_isr_14,
_geiger_sensor_isr_15
};
void GeigerSensor::_attach(GeigerSensor * instance, unsigned char gpio, unsigned char mode) {
if (!gpioValid(gpio)) return;
_detach(gpio);
unsigned char index = gpio > 5 ? gpio-6 : gpio;
_geiger_sensor_instance[index] = instance;
attachInterrupt(gpio, _geiger_sensor_isr_list[index], mode);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[GEIGER] GPIO%d interrupt attached to %s\n"), gpio, instance->description().c_str());
#endif
}
void GeigerSensor::_detach(unsigned char gpio) {
if (!gpioValid(gpio)) return;
unsigned char index = gpio > 5 ? gpio-6 : gpio;
if (_geiger_sensor_instance[index]) {
detachInterrupt(gpio);
#if SENSOR_DEBUG
DEBUG_MSG_P(PSTR("[GEIGER] GPIO%d interrupt detached from %s\n"), gpio, _geiger_sensor_instance[index]->description().c_str());
#endif
_geiger_sensor_instance[index] = NULL;
}
}
#endif // SENSOR_SUPPORT && GEIGER_SUPPORT

+ 8
- 11
code/espurna/sensors/PZEM004TSensor.h View File

@ -20,9 +20,10 @@ class PZEM004TSensor : public BaseSensor {
// Public // Public
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
PZEM004TSensor(): BaseSensor(), _data() {
PZEM004TSensor(): BaseSensor() {
_count = 4; _count = 4;
_sensor_id = SENSOR_PZEM004T_ID; _sensor_id = SENSOR_PZEM004T_ID;
_ip = IPAddress(192,168,1,1);
} }
~PZEM004TSensor() { ~PZEM004TSensor() {
@ -43,7 +44,7 @@ class PZEM004TSensor : public BaseSensor {
_dirty = true; _dirty = true;
} }
void setSerial(Stream & serial) {
void setSerial(HardwareSerial * serial) {
_serial = serial; _serial = serial;
_dirty = true; _dirty = true;
} }
@ -58,10 +59,6 @@ class PZEM004TSensor : public BaseSensor {
return _pin_tx; return _pin_tx;
} }
Stream & getSerial() {
return _serial;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensor API // Sensor API
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -72,10 +69,10 @@ class PZEM004TSensor : public BaseSensor {
if (!_dirty) return; if (!_dirty) return;
if (_pzem) delete _pzem; if (_pzem) delete _pzem;
if (_serial == NULL) {
_pzem = PZEM004T(_pin_rx, _pin_tx);
if (_serial) {
_pzem = new PZEM004T(_serial);
} else { } else {
_pzem = PZEM004T(_serial);
_pzem = new PZEM004T(_pin_rx, _pin_tx);
} }
_pzem->setAddress(_ip); _pzem->setAddress(_ip);
@ -127,8 +124,8 @@ class PZEM004TSensor : public BaseSensor {
unsigned int _pin_rx = PZEM004T_RX_PIN; unsigned int _pin_rx = PZEM004T_RX_PIN;
unsigned int _pin_tx = PZEM004T_TX_PIN; unsigned int _pin_tx = PZEM004T_TX_PIN;
Stream & _serial = NULL;
IPAddress _ip(192,168,1,1);
IPAddress _ip;
HardwareSerial * _serial = NULL;
PZEM004T * _pzem = NULL; PZEM004T * _pzem = NULL;
}; };


+ 4
- 1
code/espurna/sensors/V9261FSensor.h View File

@ -9,6 +9,9 @@
#include "Arduino.h" #include "Arduino.h"
#include "BaseSensor.h" #include "BaseSensor.h"
extern "C" {
#include "libs/fs_math.h"
}
#include <SoftwareSerial.h> #include <SoftwareSerial.h>
@ -203,7 +206,7 @@ class V9261FSensor : public BaseSensor {
if (_voltage < 0) _voltage = 0; if (_voltage < 0) _voltage = 0;
if (_current < 0) _current = 0; if (_current < 0) _current = 0;
_apparent = sqrt(_reactive * _reactive + _active * _active);
_apparent = fs_sqrt(_reactive * _reactive + _active * _active);
} }


+ 49
- 36
code/espurna/settings.ino View File

@ -6,7 +6,7 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
#include <vector> #include <vector>
#include "libs/EmbedisWrap.h" #include "libs/EmbedisWrap.h"
#include <Stream.h> #include <Stream.h>
@ -30,7 +30,7 @@ bool _settings_save = false;
unsigned long settingsSize() { unsigned long settingsSize() {
unsigned pos = SPI_FLASH_SEC_SIZE - 1; unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2; pos = pos - len - 2;
} }
return SPI_FLASH_SEC_SIZE - pos; return SPI_FLASH_SEC_SIZE - pos;
@ -41,9 +41,9 @@ unsigned long settingsSize() {
unsigned int _settingsKeyCount() { unsigned int _settingsKeyCount() {
unsigned count = 0; unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1; unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2; pos = pos - len - 2;
len = EEPROM.read(pos);
len = EEPROMr.read(pos);
pos = pos - len - 2; pos = pos - len - 2;
count ++; count ++;
} }
@ -56,17 +56,17 @@ String _settingsKeyName(unsigned int index) {
unsigned count = 0; unsigned count = 0;
unsigned pos = SPI_FLASH_SEC_SIZE - 1; unsigned pos = SPI_FLASH_SEC_SIZE - 1;
while (size_t len = EEPROM.read(pos)) {
while (size_t len = EEPROMr.read(pos)) {
pos = pos - len - 2; pos = pos - len - 2;
if (count == index) { if (count == index) {
s.reserve(len); s.reserve(len);
for (unsigned char i = 0 ; i < len; i++) { for (unsigned char i = 0 ; i < len; i++) {
s += (char) EEPROM.read(pos + i + 1);
s += (char) EEPROMr.read(pos + i + 1);
} }
break; break;
} }
count++; count++;
len = EEPROM.read(pos);
len = EEPROMr.read(pos);
pos = pos - len - 2; pos = pos - len - 2;
} }
@ -151,32 +151,21 @@ void _settingsKeysCommand() {
DEBUG_MSG_P(PSTR("Current settings:\n")); DEBUG_MSG_P(PSTR("Current settings:\n"));
for (unsigned int i=0; i<keys.size(); i++) { for (unsigned int i=0; i<keys.size(); i++) {
String value = getSetting(keys[i]); String value = getSetting(keys[i]);
DEBUG_MSG_P(PSTR("> %s => %s\n"), (keys[i]).c_str(), value.c_str());
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), (keys[i]).c_str(), value.c_str());
} }
unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize(); unsigned long freeEEPROM = SPI_FLASH_SEC_SIZE - settingsSize();
DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size()); DEBUG_MSG_P(PSTR("Number of keys: %d\n"), keys.size());
DEBUG_MSG_P(PSTR("Current EEPROM sector: %u\n"), EEPROMr.current());
DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE); DEBUG_MSG_P(PSTR("Free EEPROM: %d bytes (%d%%)\n"), freeEEPROM, 100 * freeEEPROM / SPI_FLASH_SEC_SIZE);
} }
void _settingsFactoryResetCommand() { void _settingsFactoryResetCommand() {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) { for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
}
EEPROM.commit();
}
void _settingsDumpCommand(bool ascii) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) DEBUG_MSG_P(PSTR("\n[%04X] "), i);
byte c = EEPROM.read(i);
if (ascii && 32 <= c && c <= 126) {
DEBUG_MSG_P(PSTR(" %c "), c);
} else {
DEBUG_MSG_P(PSTR("%02X "), c);
}
EEPROMr.write(i, 0xFF);
} }
EEPROMr.commit();
} }
void _settingsInitCommands() { void _settingsInitCommands() {
@ -194,13 +183,6 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("EEPROM.DUMP"), [](Embedis* e) {
bool ascii = false;
if (e->argc == 2) ascii = String(e->argv[1]).toInt() == 1;
_settingsDumpCommand(ascii);
DEBUG_MSG_P(PSTR("\n+OK\n"));
});
settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) { settingsRegisterCommand(F("ERASE.CONFIG"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
resetReason(CUSTOM_RESET_TERMINAL); resetReason(CUSTOM_RESET_TERMINAL);
@ -257,7 +239,7 @@ void _settingsInitCommands() {
settingsRegisterCommand(F("INFO"), [](Embedis* e) { settingsRegisterCommand(F("INFO"), [](Embedis* e) {
info(); info();
wifiStatus();
wifiDebug();
//StreamString s; //StreamString s;
//WiFi.printDiag(s); //WiFi.printDiag(s);
//DEBUG_MSG(s.c_str()); //DEBUG_MSG(s.c_str());
@ -269,13 +251,40 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("GET"), [](Embedis* e) {
if (e->argc < 2) {
DEBUG_MSG_P(PSTR("-ERROR: Wrong arguments\n"));
return;
}
for (unsigned char i = 1; i < e->argc; i++) {
String key = String(e->argv[i]);
String value;
if (!Embedis::get(key, value)) {
DEBUG_MSG_P(PSTR("> %s =>\n"), key.c_str());
continue;
}
DEBUG_MSG_P(PSTR("> %s => \"%s\"\n"), key.c_str(), value.c_str());
}
DEBUG_MSG_P(PSTR("+OK\n"));
});
#if WEB_SUPPORT
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif
settingsRegisterCommand(F("RESET"), [](Embedis* e) { settingsRegisterCommand(F("RESET"), [](Embedis* e) {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL); deferredReset(100, CUSTOM_RESET_TERMINAL);
}); });
settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) { settingsRegisterCommand(F("RESET.SAFE"), [](Embedis* e) {
EEPROM.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
EEPROMr.write(EEPROM_CRASH_COUNTER, SYSTEM_CHECK_MAX);
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
deferredReset(100, CUSTOM_RESET_TERMINAL); deferredReset(100, CUSTOM_RESET_TERMINAL);
}); });
@ -353,6 +362,10 @@ void settingsInject(void *data, size_t len) {
_serial.inject((char *) data, len); _serial.inject((char *) data, len);
} }
Stream & settingsSerial() {
return (Stream &) _serial;
}
size_t settingsMaxSize() { size_t settingsMaxSize() {
size_t size = EEPROM_SIZE; size_t size = EEPROM_SIZE;
if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE; if (size > SPI_FLASH_SEC_SIZE) size = SPI_FLASH_SEC_SIZE;
@ -366,7 +379,7 @@ bool settingsRestoreJson(JsonObject& data) {
if (strcmp(app, APP_NAME) != 0) return false; if (strcmp(app, APP_NAME) != 0) return false;
for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) { for (unsigned int i = EEPROM_DATA_END; i < SPI_FLASH_SEC_SIZE; i++) {
EEPROM.write(i, 0xFF);
EEPROMr.write(i, 0xFF);
} }
for (auto element : data) { for (auto element : data) {
@ -405,7 +418,7 @@ void settingsRegisterCommand(const String& name, void (*call)(Embedis*)) {
void settingsSetup() { void settingsSetup() {
EEPROM.begin(SPI_FLASH_SEC_SIZE);
EEPROMr.begin(SPI_FLASH_SEC_SIZE);
_serial.callback([](uint8_t ch) { _serial.callback([](uint8_t ch) {
#if TELNET_SUPPORT #if TELNET_SUPPORT
@ -418,8 +431,8 @@ void settingsSetup() {
Embedis::dictionary( F("EEPROM"), Embedis::dictionary( F("EEPROM"),
SPI_FLASH_SEC_SIZE, SPI_FLASH_SEC_SIZE,
[](size_t pos) -> char { return EEPROM.read(pos); },
[](size_t pos, char value) { EEPROM.write(pos, value); },
[](size_t pos) -> char { return EEPROMr.read(pos); },
[](size_t pos, char value) { EEPROMr.write(pos, value); },
#if SETTINGS_AUTOSAVE #if SETTINGS_AUTOSAVE
[]() { _settings_save = true; } []() { _settings_save = true; }
#else #else
@ -443,7 +456,7 @@ void settingsSetup() {
void settingsLoop() { void settingsLoop() {
if (_settings_save) { if (_settings_save) {
EEPROM.commit();
EEPROMr.commit();
_settings_save = false; _settings_save = false;
} }


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


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

@ -6,7 +6,7 @@ Copyright (C) 2018 by Xose Pérez <xose dot perez at gmail dot com>
*/ */
#include <EEPROM.h>
#include <EEPROM_Rotate.h>
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -30,7 +30,7 @@ unsigned short int _load_average = 100;
bool _systemStable = true; bool _systemStable = true;
void systemCheck(bool stable) { void systemCheck(bool stable) {
unsigned char value = EEPROM.read(EEPROM_CRASH_COUNTER);
unsigned char value = EEPROMr.read(EEPROM_CRASH_COUNTER);
if (stable) { if (stable) {
value = 0; value = 0;
DEBUG_MSG_P(PSTR("[MAIN] System OK\n")); DEBUG_MSG_P(PSTR("[MAIN] System OK\n"));
@ -41,8 +41,8 @@ void systemCheck(bool stable) {
DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n")); DEBUG_MSG_P(PSTR("[MAIN] System UNSTABLE\n"));
} }
} }
EEPROM.write(EEPROM_CRASH_COUNTER, value);
EEPROM.commit();
EEPROMr.write(EEPROM_CRASH_COUNTER, value);
EEPROMr.commit();
} }
bool systemCheck() { bool systemCheck() {
@ -148,8 +148,6 @@ void _systemSetupSpecificHardware() {
void systemSetup() { void systemSetup() {
EEPROM.begin(EEPROM_SIZE);
#if SPIFFS_SUPPORT #if SPIFFS_SUPPORT
SPIFFS.begin(); SPIFFS.begin();
#endif #endif


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

@ -66,6 +66,15 @@ void _telnetData(unsigned char clientId, void *data, size_t len) {
// Capture close connection // Capture close connection
char * p = (char *) data; char * p = (char *) data;
// C-d is sent as two bytes (sometimes repeating)
if (len >= 2) {
if ((p[0] == 0xFF) && (p[1] == 0xEC)) {
_telnetClients[clientId]->close(true);
return;
}
}
if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) { if ((strncmp(p, "close", 5) == 0) || (strncmp(p, "quit", 4) == 0)) {
_telnetClients[clientId]->close(); _telnetClients[clientId]->close();
return; return;
@ -129,7 +138,7 @@ void _telnetNewClient(AsyncClient *client) {
// If there is no terminal support automatically dump info and crash data // If there is no terminal support automatically dump info and crash data
#if TERMINAL_SUPPORT == 0 #if TERMINAL_SUPPORT == 0
info(); info();
wifiStatus();
wifiDebug();
debugDumpCrashInfo(); debugDumpCrashInfo();
debugClearCrashInfo(); debugClearCrashInfo();
#endif #endif


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

@ -32,15 +32,17 @@ void _uartmqttReceiveUART() {
char rc = UART_MQTT_PORT.read(); char rc = UART_MQTT_PORT.read();
if (rc != '\n') {
if (rc != UART_MQTT_TERMINATION) {
_uartmqttBuffer[ndx] = rc; _uartmqttBuffer[ndx] = rc;
if (ndx < UART_MQTT_BUFFER_SIZE - 1) ndx++; if (ndx < UART_MQTT_BUFFER_SIZE - 1) ndx++;
} else { } else {
_uartmqttBuffer[ndx] = '\0'; _uartmqttBuffer[ndx] = '\0';
_uartmqttNewData = true; _uartmqttNewData = true;
ndx = 0; ndx = 0;
} }
} }


+ 82
- 166
code/espurna/utils.ino View File

@ -11,7 +11,7 @@ Ticker _defer_reset;
String getIdentifier() { String getIdentifier() {
char buffer[20]; char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%s_%06X"), APP_NAME, ESP.getChipId());
snprintf_P(buffer, sizeof(buffer), PSTR("%s-%06X"), APP_NAME, ESP.getChipId());
return String(buffer); return String(buffer);
} }
@ -52,6 +52,10 @@ String getCoreRevision() {
#endif #endif
} }
unsigned long maxSketchSpace() {
return (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
}
// WTF // WTF
// Calling ESP.getFreeHeap() is making the system crash on a specific // Calling ESP.getFreeHeap() is making the system crash on a specific
// AiLight bulb, but anywhere else... // AiLight bulb, but anywhere else...
@ -60,6 +64,16 @@ unsigned int getFreeHeap() {
return ESP.getFreeHeap(); return ESP.getFreeHeap();
} }
String getEspurnaModules() {
return FPSTR(espurna_modules);
}
#if SENSOR_SUPPORT
String getEspurnaSensors() {
return FPSTR(espurna_sensors);
}
#endif
String buildTime() { String buildTime() {
const char time_now[] = __TIME__; // hh:mm:ss const char time_now[] = __TIME__; // hh:mm:ss
@ -207,10 +221,42 @@ void heartbeat() {
#endif /// HEARTBEAT_ENABLED #endif /// HEARTBEAT_ENABLED
unsigned int sectors(size_t size) {
// -----------------------------------------------------------------------------
// INFO
// -----------------------------------------------------------------------------
extern "C" uint32_t _SPIFFS_start;
extern "C" uint32_t _SPIFFS_end;
unsigned int info_bytes2sectors(size_t size) {
return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE; return (int) (size + SPI_FLASH_SEC_SIZE - 1) / SPI_FLASH_SEC_SIZE;
} }
unsigned long info_ota_space() {
return (ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000;
}
unsigned long info_filesystem_space() {
return ((uint32_t)&_SPIFFS_end - (uint32_t)&_SPIFFS_start);
}
unsigned long info_eeprom_space() {
return EEPROMr.reserved() * SPI_FLASH_SEC_SIZE;
}
void _info_print_memory_layout_line(const char * name, unsigned long bytes, bool reset) {
static unsigned long index = 0;
if (reset) index = 0;
if (0 == bytes) return;
unsigned int _sectors = info_bytes2sectors(bytes);
DEBUG_MSG_P(PSTR("[INIT] %-20s: %8lu bytes / %4d sectors (%4d to %4d)\n"), name, bytes, _sectors, index, index + _sectors - 1);
index += _sectors;
}
void _info_print_memory_layout_line(const char * name, unsigned long bytes) {
_info_print_memory_layout_line(name, bytes, false);
}
void info() { void info() {
DEBUG_MSG_P(PSTR("\n\n")); DEBUG_MSG_P(PSTR("\n\n"));
@ -231,13 +277,18 @@ void info() {
DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed()); DEBUG_MSG_P(PSTR("[INIT] Flash speed: %u Hz\n"), ESP.getFlashChipSpeed());
DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN"); DEBUG_MSG_P(PSTR("[INIT] Flash mode: %s\n"), mode == FM_QIO ? "QIO" : mode == FM_QOUT ? "QOUT" : mode == FM_DIO ? "DIO" : mode == FM_DOUT ? "DOUT" : "UNKNOWN");
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] Flash sector size: %8u bytes\n"), SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("[INIT] Flash size (CHIP): %8u bytes\n"), ESP.getFlashChipRealSize());
DEBUG_MSG_P(PSTR("[INIT] Flash size (SDK): %8u bytes / %4d sectors\n"), ESP.getFlashChipSize(), sectors(ESP.getFlashChipSize()));
DEBUG_MSG_P(PSTR("[INIT] Firmware size: %8u bytes / %4d sectors\n"), ESP.getSketchSize(), sectors(ESP.getSketchSize()));
DEBUG_MSG_P(PSTR("[INIT] OTA size: %8u bytes / %4d sectors\n"), ESP.getFreeSketchSpace(), sectors(ESP.getFreeSketchSpace()));
DEBUG_MSG_P(PSTR("[INIT] EEPROM size: %8u bytes / %4d sectors\n"), settingsMaxSize(), sectors(settingsMaxSize()));
DEBUG_MSG_P(PSTR("[INIT] Empty space: %8u bytes / 4 sectors\n"), 4 * SPI_FLASH_SEC_SIZE);
_info_print_memory_layout_line("Flash size (CHIP)", ESP.getFlashChipRealSize(), true);
_info_print_memory_layout_line("Flash size (SDK)", ESP.getFlashChipSize(), true);
_info_print_memory_layout_line("Reserved", 1 * SPI_FLASH_SEC_SIZE, true);
_info_print_memory_layout_line("Firmware size", ESP.getSketchSize());
_info_print_memory_layout_line("Max OTA size", info_ota_space());
_info_print_memory_layout_line("SPIFFS size", info_filesystem_space());
_info_print_memory_layout_line("EEPROM size", info_eeprom_space());
_info_print_memory_layout_line("Reserved", 4 * SPI_FLASH_SEC_SIZE);
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] EEPROM sectors: %s\n"), (char *) eepromSectors().c_str());
DEBUG_MSG_P(PSTR("\n")); DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -261,162 +312,11 @@ void info() {
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str()); DEBUG_MSG_P(PSTR("[INIT] BOARD: %s\n"), getBoardName().c_str());
DEBUG_MSG_P(PSTR("[INIT] SUPPORT:"));
#if ALEXA_SUPPORT
DEBUG_MSG_P(PSTR(" ALEXA"));
#endif
#if BROKER_SUPPORT
DEBUG_MSG_P(PSTR(" BROKER"));
#endif
#if DEBUG_SERIAL_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_SERIAL"));
#endif
#if DEBUG_TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_TELNET"));
#endif
#if DEBUG_UDP_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_UDP"));
#endif
#if DEBUG_WEB_SUPPORT
DEBUG_MSG_P(PSTR(" DEBUG_WEB"));
#endif
#if DOMOTICZ_SUPPORT
DEBUG_MSG_P(PSTR(" DOMOTICZ"));
#endif
#if HOMEASSISTANT_SUPPORT
DEBUG_MSG_P(PSTR(" HOMEASSISTANT"));
#endif
#if I2C_SUPPORT
DEBUG_MSG_P(PSTR(" I2C"));
#endif
#if INFLUXDB_SUPPORT
DEBUG_MSG_P(PSTR(" INFLUXDB"));
#endif
#if LLMNR_SUPPORT
DEBUG_MSG_P(PSTR(" LLMNR"));
#endif
#if MDNS_SERVER_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_SERVER"));
#endif
#if MDNS_CLIENT_SUPPORT
DEBUG_MSG_P(PSTR(" MDNS_CLIENT"));
#endif
#if MQTT_SUPPORT
DEBUG_MSG_P(PSTR(" MQTT"));
#endif
#if NETBIOS_SUPPORT
DEBUG_MSG_P(PSTR(" NETBIOS"));
#endif
#if NOFUSS_SUPPORT
DEBUG_MSG_P(PSTR(" NOFUSS"));
#endif
#if NTP_SUPPORT
DEBUG_MSG_P(PSTR(" NTP"));
#endif
#if RF_SUPPORT
DEBUG_MSG_P(PSTR(" RF"));
#endif
#if SCHEDULER_SUPPORT
DEBUG_MSG_P(PSTR(" SCHEDULER"));
#endif
#if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSOR"));
#endif
#if SPIFFS_SUPPORT
DEBUG_MSG_P(PSTR(" SPIFFS"));
#endif
#if SSDP_SUPPORT
DEBUG_MSG_P(PSTR(" SSDP"));
#endif
#if TELNET_SUPPORT
DEBUG_MSG_P(PSTR(" TELNET"));
#endif
#if TERMINAL_SUPPORT
DEBUG_MSG_P(PSTR(" TERMINAL"));
#endif
#if THINGSPEAK_SUPPORT
DEBUG_MSG_P(PSTR(" THINGSPEAK"));
#endif
#if UART_MQTT_SUPPORT
DEBUG_MSG_P(PSTR(" UART_MQTT"));
#endif
#if WEB_SUPPORT
DEBUG_MSG_P(PSTR(" WEB"));
#endif
DEBUG_MSG_P(PSTR("[INIT] SUPPORT: %s\n"), getEspurnaModules().c_str());
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n"));
DEBUG_MSG_P(PSTR("[INIT] SENSORS:"));
#if AM2320_SUPPORT
DEBUG_MSG_P(PSTR(" AM2320_I2C"));
#endif
#if ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" ANALOG"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if DALLAS_SUPPORT
DEBUG_MSG_P(PSTR(" DALLAS"));
#endif
#if DHT_SUPPORT
DEBUG_MSG_P(PSTR(" DHTXX"));
#endif
#if DIGITAL_SUPPORT
DEBUG_MSG_P(PSTR(" DIGITAL"));
#endif
#if ECH1560_SUPPORT
DEBUG_MSG_P(PSTR(" ECH1560"));
#endif
#if EMON_ADC121_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADC121"));
#endif
#if EMON_ADS1X15_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ADX1X15"));
#endif
#if EMON_ANALOG_SUPPORT
DEBUG_MSG_P(PSTR(" EMON_ANALOG"));
#endif
#if EVENTS_SUPPORT
DEBUG_MSG_P(PSTR(" EVENTS"));
#endif
#if GUVAS12SD_SUPPORT
DEBUG_MSG_P(PSTR(" GUVAS12SD"));
#endif
#if HCSR04_SUPPORT
DEBUG_MSG_P(PSTR(" HCSR04"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if MHZ19_SUPPORT
DEBUG_MSG_P(PSTR(" MHZ19"));
#endif
#if PMSX003_SUPPORT
DEBUG_MSG_P(PSTR(" PMSX003"));
#endif
#if PZEM004T_SUPPORT
DEBUG_MSG_P(PSTR(" PZEM004T"));
#endif
#if SHT3X_I2C_SUPPORT
DEBUG_MSG_P(PSTR(" SHT3X_I2C"));
#endif
#if SI7021_SUPPORT
DEBUG_MSG_P(PSTR(" SI7021"));
#endif
#if TMP3X_SUPPORT
DEBUG_MSG_P(PSTR(" TMP3X"));
#endif
#if V9261F_SUPPORT
DEBUG_MSG_P(PSTR(" V9261F"));
#endif
DEBUG_MSG_P(PSTR("[INIT] SENSORS: %s\n"), getEspurnaSensors().c_str());
#endif // SENSOR_SUPPORT #endif // SENSOR_SUPPORT
DEBUG_MSG_P(PSTR("\n\n"));
DEBUG_MSG_P(PSTR("\n"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -495,7 +395,7 @@ bool sslFingerPrintChar(const char * fingerprint, char * destination) {
unsigned char resetReason() { unsigned char resetReason() {
static unsigned char status = 255; static unsigned char status = 255;
if (status == 255) { if (status == 255) {
status = EEPROM.read(EEPROM_CUSTOM_RESET);
status = EEPROMr.read(EEPROM_CUSTOM_RESET);
if (status > 0) resetReason(0); if (status > 0) resetReason(0);
if (status > CUSTOM_RESET_MAX) status = 0; if (status > CUSTOM_RESET_MAX) status = 0;
} }
@ -503,8 +403,8 @@ unsigned char resetReason() {
} }
void resetReason(unsigned char reason) { void resetReason(unsigned char reason) {
EEPROM.write(EEPROM_CUSTOM_RESET, reason);
EEPROM.commit();
EEPROMr.write(EEPROM_CUSTOM_RESET, reason);
EEPROMr.commit();
} }
void reset(unsigned char reason) { void reset(unsigned char reason) {
@ -539,3 +439,19 @@ void nice_delay(unsigned long ms) {
int __get_adc_mode() { int __get_adc_mode() {
return (int) (ADC_MODE_VALUE); return (int) (ADC_MODE_VALUE);
} }
bool isNumber(const char * s) {
unsigned char len = strlen(s);
bool decimal = false;
for (unsigned char i=0; i<len; i++) {
if (s[i] == '-') {
if (i>0) return false;
} else if (s[i] == '.') {
if (decimal) return false;
decimal = true;
} else if (!isdigit(s[i])) {
return false;
}
}
return true;
}

+ 25
- 7
code/espurna/web.ino View File

@ -43,7 +43,9 @@ void _onReset(AsyncWebServerRequest *request) {
void _onGetConfig(AsyncWebServerRequest *request) { void _onGetConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
AsyncResponseStream *response = request->beginResponseStream("text/json"); AsyncResponseStream *response = request->beginResponseStream("text/json");
@ -53,6 +55,7 @@ void _onGetConfig(AsyncWebServerRequest *request) {
root["version"] = APP_VERSION; root["version"] = APP_VERSION;
settingsGetJson(root); settingsGetJson(root);
root.prettyPrintTo(*response); root.prettyPrintTo(*response);
jsonBuffer.clear();
char buffer[100]; char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str()); snprintf_P(buffer, sizeof(buffer), PSTR("attachment; filename=\"%s-backup.json\""), (char *) getSetting("hostname").c_str());
@ -66,7 +69,9 @@ void _onGetConfig(AsyncWebServerRequest *request) {
void _onPostConfig(AsyncWebServerRequest *request) { void _onPostConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
request->send(_webConfigSuccess ? 200 : 400); request->send(_webConfigSuccess ? 200 : 400);
} }
@ -114,7 +119,9 @@ void _onPostConfigData(AsyncWebServerRequest *request, String filename, size_t i
void _onHome(AsyncWebServerRequest *request) { void _onHome(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
if (request->header("If-Modified-Since").equals(_last_modified)) { if (request->header("If-Modified-Since").equals(_last_modified)) {
@ -217,7 +224,9 @@ int _onCertificate(void * arg, const char *filename, uint8_t **buf) {
void _onUpgrade(AsyncWebServerRequest *request) { void _onUpgrade(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!_authenticate(request)) return request->requestAuthentication(getSetting("hostname").c_str());
if (!webAuthenticate(request)) {
return request->requestAuthentication(getSetting("hostname").c_str());
}
char buffer[10]; char buffer[10];
if (!Update.hasError()) { if (!Update.hasError()) {
@ -231,8 +240,9 @@ void _onUpgrade(AsyncWebServerRequest *request) {
response->addHeader("X-XSS-Protection", "1; mode=block"); response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff"); response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny"); response->addHeader("X-Frame-Options", "deny");
if (!Update.hasError()) {
if (Update.hasError()) {
eepromRotate(true);
} else {
deferredReset(100, CUSTOM_RESET_UPGRADE); deferredReset(100, CUSTOM_RESET_UPGRADE);
} }
request->send(response); request->send(response);
@ -240,7 +250,12 @@ void _onUpgrade(AsyncWebServerRequest *request) {
} }
void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) { void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, bool final) {
if (!index) { if (!index) {
// Disabling EEPROM rotation to prevent writing to EEPROM after the upgrade
eepromRotate(false);
DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str()); DEBUG_MSG_P(PSTR("[UPGRADE] Start: %s\n"), filename.c_str());
Update.runAsync(true); Update.runAsync(true);
if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) {
@ -248,7 +263,9 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
Update.printError(DEBUG_PORT); Update.printError(DEBUG_PORT);
#endif #endif
} }
} }
if (!Update.hasError()) { if (!Update.hasError()) {
if (Update.write(data, len) != len) { if (Update.write(data, len) != len) {
#ifdef DEBUG_PORT #ifdef DEBUG_PORT
@ -256,6 +273,7 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
#endif #endif
} }
} }
if (final) { if (final) {
if (Update.end(true)){ if (Update.end(true)){
DEBUG_MSG_P(PSTR("[UPGRADE] Success: %u bytes\n"), index + len); DEBUG_MSG_P(PSTR("[UPGRADE] Success: %u bytes\n"), index + len);
@ -271,7 +289,7 @@ void _onUpgradeData(AsyncWebServerRequest *request, String filename, size_t inde
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
bool _authenticate(AsyncWebServerRequest *request) {
bool webAuthenticate(AsyncWebServerRequest *request) {
#if USE_PASSWORD #if USE_PASSWORD
String password = getSetting("adminPass", ADMIN_PASS); String password = getSetting("adminPass", ADMIN_PASS);
char httpPassword[password.length() + 1]; char httpPassword[password.length() + 1];


+ 216
- 52
code/espurna/wifi.ino View File

@ -10,11 +10,26 @@ Copyright (C) 2016-2018 by Xose Pérez <xose dot perez at gmail dot com>
#include <Ticker.h> #include <Ticker.h>
uint32_t _wifi_scan_client_id = 0; uint32_t _wifi_scan_client_id = 0;
bool _wifi_wps_running = false;
bool _wifi_smartconfig_running = false;
uint8_t _wifi_ap_mode = WIFI_AP_FALLBACK;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// PRIVATE // PRIVATE
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
void _wifiCheckAP() {
if ((WIFI_AP_FALLBACK == _wifi_ap_mode) &&
(jw.connected()) &&
((WiFi.getMode() & WIFI_AP) > 0) &&
(WiFi.softAPgetStationNum() == 0)
) {
jw.enableAP(false);
}
}
void _wifiConfigure() { void _wifiConfigure() {
jw.setHostname(getSetting("hostname").c_str()); jw.setHostname(getSetting("hostname").c_str());
@ -25,9 +40,11 @@ void _wifiConfigure() {
#endif #endif
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT); jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
wifiReconnectCheck(); wifiReconnectCheck();
jw.setAPMode(WIFI_AP_MODE);
jw.enableAPFallback(true);
jw.cleanNetworks(); jw.cleanNetworks();
_wifi_ap_mode = getSetting("apmode", WIFI_AP_FALLBACK).toInt();
// If system is flagged unstable we do not init wifi networks // If system is flagged unstable we do not init wifi networks
#if SYSTEM_CHECK_ENABLED #if SYSTEM_CHECK_ENABLED
if (!systemCheck()) return; if (!systemCheck()) return;
@ -56,7 +73,7 @@ void _wifiConfigure() {
} }
} }
jw.scanNetworks(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1);
jw.enableScan(getSetting("wifiScan", WIFI_SCAN_NETWORKS).toInt() == 1);
} }
@ -196,8 +213,51 @@ void _wifiInject() {
} }
} }
void _wifiCallback(justwifi_messages_t code, char * parameter) {
if (MESSAGE_WPS_START == code) {
_wifi_wps_running = true;
}
if (MESSAGE_SMARTCONFIG_START == code) {
_wifi_smartconfig_running = true;
}
if (MESSAGE_WPS_ERROR == code || MESSAGE_SMARTCONFIG_ERROR == code) {
_wifi_wps_running = false;
_wifi_smartconfig_running = false;
}
if (MESSAGE_WPS_SUCCESS == code || MESSAGE_SMARTCONFIG_SUCCESS == code) {
String ssid = WiFi.SSID();
String pass = WiFi.psk();
// Look for the same SSID
uint8_t count = 0;
while (count < WIFI_MAX_NETWORKS) {
if (!hasSetting("ssid", count)) break;
if (ssid.equals(getSetting("ssid", count, ""))) break;
count++;
}
// If we have reached the max we overwrite the first one
if (WIFI_MAX_NETWORKS == count) count = 0;
setSetting("ssid", count, ssid);
setSetting("pass", count, pass);
_wifi_wps_running = false;
_wifi_smartconfig_running = false;
}
}
#if WIFI_AP_CAPTIVE #if WIFI_AP_CAPTIVE
#include "DNSServer.h"
DNSServer _wifi_dnsServer; DNSServer _wifi_dnsServer;
void _wifiCaptivePortal(justwifi_messages_t code, char * parameter) { void _wifiCaptivePortal(justwifi_messages_t code, char * parameter) {
@ -219,7 +279,9 @@ void _wifiCaptivePortal(justwifi_messages_t code, char * parameter) {
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
void _wifiDebug(justwifi_messages_t code, char * parameter) {
void _wifiDebugCallback(justwifi_messages_t code, char * parameter) {
// -------------------------------------------------------------------------
if (code == MESSAGE_SCANNING) { if (code == MESSAGE_SCANNING) {
DEBUG_MSG_P(PSTR("[WIFI] Scanning\n")); DEBUG_MSG_P(PSTR("[WIFI] Scanning\n"));
@ -241,6 +303,8 @@ void _wifiDebug(justwifi_messages_t code, char * parameter) {
DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter); DEBUG_MSG_P(PSTR("[WIFI] %s\n"), parameter);
} }
// -------------------------------------------------------------------------
if (code == MESSAGE_CONNECTING) { if (code == MESSAGE_CONNECTING) {
DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter); DEBUG_MSG_P(PSTR("[WIFI] Connecting to %s\n"), parameter);
} }
@ -254,25 +318,59 @@ void _wifiDebug(justwifi_messages_t code, char * parameter) {
} }
if (code == MESSAGE_CONNECTED) { if (code == MESSAGE_CONNECTED) {
wifiStatus();
}
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiStatus();
wifiDebug(WIFI_STA);
} }
if (code == MESSAGE_DISCONNECTED) { if (code == MESSAGE_DISCONNECTED) {
DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n")); DEBUG_MSG_P(PSTR("[WIFI] Disconnected\n"));
} }
// -------------------------------------------------------------------------
if (code == MESSAGE_ACCESSPOINT_CREATING) { if (code == MESSAGE_ACCESSPOINT_CREATING) {
DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n")); DEBUG_MSG_P(PSTR("[WIFI] Creating access point\n"));
} }
if (code == MESSAGE_ACCESSPOINT_CREATED) {
wifiDebug(WIFI_AP);
}
if (code == MESSAGE_ACCESSPOINT_FAILED) { if (code == MESSAGE_ACCESSPOINT_FAILED) {
DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n")); DEBUG_MSG_P(PSTR("[WIFI] Could not create access point\n"));
} }
if (code == MESSAGE_ACCESSPOINT_DESTROYED) {
DEBUG_MSG_P(PSTR("[WIFI] Access point destroyed\n"));
}
// -------------------------------------------------------------------------
if (code == MESSAGE_WPS_START) {
DEBUG_MSG_P(PSTR("[WIFI] WPS started\n"));
}
if (code == MESSAGE_WPS_SUCCESS) {
DEBUG_MSG_P(PSTR("[WIFI] WPS succeded!\n"));
}
if (code == MESSAGE_WPS_ERROR) {
DEBUG_MSG_P(PSTR("[WIFI] WPS failed\n"));
}
// ------------------------------------------------------------------------
if (code == MESSAGE_SMARTCONFIG_START) {
DEBUG_MSG_P(PSTR("[WIFI] Smart Config started\n"));
}
if (code == MESSAGE_SMARTCONFIG_SUCCESS) {
DEBUG_MSG_P(PSTR("[WIFI] Smart Config succeded!\n"));
}
if (code == MESSAGE_SMARTCONFIG_ERROR) {
DEBUG_MSG_P(PSTR("[WIFI] Smart Config failed\n"));
}
} }
#endif // DEBUG_SUPPORT #endif // DEBUG_SUPPORT
@ -292,10 +390,24 @@ void _wifiInitCommands() {
}); });
settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) { settingsRegisterCommand(F("WIFI.AP"), [](Embedis* e) {
createAP();
wifiStartAP();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
#if defined(JUSTWIFI_ENABLE_WPS)
settingsRegisterCommand(F("WIFI.WPS"), [](Embedis* e) {
wifiStartWPS();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SMARTCONFIG"), [](Embedis* e) {
wifiStartSmartConfig();
DEBUG_MSG_P(PSTR("+OK\n"));
});
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) { settingsRegisterCommand(F("WIFI.SCAN"), [](Embedis* e) {
_wifiScan(); _wifiScan();
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
@ -344,6 +456,59 @@ void _wifiWebSocketOnAction(uint32_t client_id, const char * action, JsonObject&
#endif #endif
// -----------------------------------------------------------------------------
// INFO
// -----------------------------------------------------------------------------
void wifiDebug(WiFiMode_t modes) {
bool footer = false;
if (((modes & WIFI_STA) > 0) && ((WiFi.getMode() & WIFI_STA) > 0)) {
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] ------------------------------------- MODE STA\n"));
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST http://%s.local\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
footer = true;
}
if (((modes & WIFI_AP) > 0) && ((WiFi.getMode() & WIFI_AP) > 0)) {
DEBUG_MSG_P(PSTR("[WIFI] -------------------------------------- MODE AP\n"));
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), getSetting("hostname").c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
footer = true;
}
if (WiFi.getMode() == 0) {
DEBUG_MSG_P(PSTR("[WIFI] ------------------------------------- MODE OFF\n"));
DEBUG_MSG_P(PSTR("[WIFI] No connection\n"));
footer = true;
}
if (footer) {
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
}
}
void wifiDebug() {
wifiDebug(WIFI_AP_STA);
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// API // API
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -370,12 +535,31 @@ void wifiDisconnect() {
jw.disconnect(); jw.disconnect();
} }
bool createAP() {
jw.disconnect();
jw.resetReconnectTimeout();
return jw.createAP();
void wifiStartAP(bool only) {
if (only) {
jw.enableSTA(false);
jw.disconnect();
jw.resetReconnectTimeout();
}
jw.enableAP(true);
} }
void wifiStartAP() {
wifiStartAP(true);
}
#if defined(JUSTWIFI_ENABLE_WPS)
void wifiStartWPS() {
jw.startWPS();
}
#endif // defined(JUSTWIFI_ENABLE_WPS)
#if defined(JUSTWIFI_ENABLE_SMARTCONFIG)
void wifiStartSmartConfig() {
jw.startSmartConfig();
}
#endif // defined(JUSTWIFI_ENABLE_SMARTCONFIG)
void wifiReconnectCheck() { void wifiReconnectCheck() {
bool connected = false; bool connected = false;
#if WEB_SUPPORT #if WEB_SUPPORT
@ -387,44 +571,13 @@ void wifiReconnectCheck() {
jw.setReconnectTimeout(connected ? 0 : WIFI_RECONNECT_INTERVAL); jw.setReconnectTimeout(connected ? 0 : WIFI_RECONNECT_INTERVAL);
} }
void wifiStatus() {
if (WiFi.getMode() == WIFI_AP_STA) {
DEBUG_MSG_P(PSTR("[WIFI] MODE AP + STA --------------------------------\n"));
} else if (WiFi.getMode() == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] MODE AP --------------------------------------\n"));
} else if (WiFi.getMode() == WIFI_STA) {
DEBUG_MSG_P(PSTR("[WIFI] MODE STA -------------------------------------\n"));
} else {
DEBUG_MSG_P(PSTR("[WIFI] MODE OFF -------------------------------------\n"));
DEBUG_MSG_P(PSTR("[WIFI] No connection\n"));
}
if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), jw.getAPSSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] PASS %s\n"), getSetting("adminPass", ADMIN_PASS).c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.softAPIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.softAPmacAddress().c_str());
}
if ((WiFi.getMode() & WIFI_STA) == WIFI_STA) {
uint8_t * bssid = WiFi.BSSID();
DEBUG_MSG_P(PSTR("[WIFI] SSID %s\n"), WiFi.SSID().c_str());
DEBUG_MSG_P(PSTR("[WIFI] IP %s\n"), WiFi.localIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MAC %s\n"), WiFi.macAddress().c_str());
DEBUG_MSG_P(PSTR("[WIFI] GW %s\n"), WiFi.gatewayIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] DNS %s\n"), WiFi.dnsIP().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] MASK %s\n"), WiFi.subnetMask().toString().c_str());
DEBUG_MSG_P(PSTR("[WIFI] HOST %s\n"), WiFi.hostname().c_str());
DEBUG_MSG_P(PSTR("[WIFI] BSSID %02X:%02X:%02X:%02X:%02X:%02X\n"),
bssid[0], bssid[1], bssid[2], bssid[3], bssid[4], bssid[5], bssid[6]
);
DEBUG_MSG_P(PSTR("[WIFI] CH %d\n"), WiFi.channel());
DEBUG_MSG_P(PSTR("[WIFI] RSSI %d\n"), WiFi.RSSI());
}
DEBUG_MSG_P(PSTR("[WIFI] ----------------------------------------------\n"));
uint8_t wifiState() {
uint8_t state = 0;
if (jw.connected()) state += WIFI_STATE_STA;
if (jw.connectable()) state += WIFI_STATE_AP;
if (_wifi_wps_running) state += WIFI_STATE_WPS;
if (_wifi_smartconfig_running) state += WIFI_STATE_SMARTCONFIG;
return state;
} }
void wifiRegister(wifi_callback_f callback) { void wifiRegister(wifi_callback_f callback) {
@ -443,11 +596,12 @@ void wifiSetup() {
_wifiConfigure(); _wifiConfigure();
// Message callbacks // Message callbacks
wifiRegister(_wifiCallback);
#if WIFI_AP_CAPTIVE #if WIFI_AP_CAPTIVE
wifiRegister(_wifiCaptivePortal); wifiRegister(_wifiCaptivePortal);
#endif #endif
#if DEBUG_SUPPORT #if DEBUG_SUPPORT
wifiRegister(_wifiDebug);
wifiRegister(_wifiDebugCallback);
#endif #endif
#if WEB_SUPPORT #if WEB_SUPPORT
@ -468,17 +622,27 @@ void wifiSetup() {
void wifiLoop() { void wifiLoop() {
// Main wifi loop
jw.loop(); jw.loop();
// Process captrive portal DNS queries if in AP mode only
#if WIFI_AP_CAPTIVE #if WIFI_AP_CAPTIVE
if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) { if ((WiFi.getMode() & WIFI_AP) == WIFI_AP) {
_wifi_dnsServer.processNextRequest(); _wifi_dnsServer.processNextRequest();
} }
#endif #endif
// Do we have a pending scan?
if (_wifi_scan_client_id > 0) { if (_wifi_scan_client_id > 0) {
_wifiScan(_wifi_scan_client_id); _wifiScan(_wifi_scan_client_id);
_wifi_scan_client_id = 0; _wifi_scan_client_id = 0;
} }
// Check if we should disable AP
static unsigned long last = 0;
if (millis() - last > 60000) {
last = millis();
_wifiCheckAP();
}
} }

+ 67
- 14
code/espurna/ws.ino View File

@ -27,6 +27,57 @@ std::vector<ws_on_receive_callback_f> _ws_on_receive_callbacks;
// Private methods // Private methods
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
typedef struct {
IPAddress ip;
unsigned long timestamp = 0;
} ws_ticket_t;
ws_ticket_t _ticket[WS_BUFFER_SIZE];
void _onAuth(AsyncWebServerRequest *request) {
webLog(request);
if (!webAuthenticate(request)) return request->requestAuthentication();
IPAddress ip = request->client()->remoteIP();
unsigned long now = millis();
unsigned short index;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if (_ticket[index].ip == ip) break;
if (_ticket[index].timestamp == 0) break;
if (now - _ticket[index].timestamp > WS_TIMEOUT) break;
}
if (index == WS_BUFFER_SIZE) {
request->send(429);
} else {
_ticket[index].ip = ip;
_ticket[index].timestamp = now;
request->send(200, "text/plain", "OK");
}
}
bool _wsAuth(AsyncWebSocketClient * client) {
IPAddress ip = client->remoteIP();
unsigned long now = millis();
unsigned short index = 0;
for (index = 0; index < WS_BUFFER_SIZE; index++) {
if ((_ticket[index].ip == ip) && (now - _ticket[index].timestamp < WS_TIMEOUT)) break;
}
if (index == WS_BUFFER_SIZE) {
DEBUG_MSG_P(PSTR("[WEBSOCKET] Validation check failed\n"));
wsSend_P(client->id(), PSTR("{\"message\": 10}"));
return false;
}
return true;
}
// -----------------------------------------------------------------------------
#if MQTT_SUPPORT #if MQTT_SUPPORT
void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) { void _wsMQTTCallback(unsigned int type, const char * topic, const char * payload) {
if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}")); if (type == MQTT_CONNECT_EVENT) wsSend_P(PSTR("{\"mqttStatus\": true}"));
@ -205,9 +256,7 @@ void _wsParse(AsyncWebSocketClient *client, uint8_t * payload, size_t length) {
if (save) { if (save) {
// Callbacks // Callbacks
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
wsReload();
// This should got to callback as well // This should got to callback as well
// but first change management has to be in place // but first change management has to be in place
@ -317,6 +366,11 @@ void _wsStart(uint32_t client_id) {
void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){ void _wsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t *data, size_t len){
if (type == WS_EVT_CONNECT) { if (type == WS_EVT_CONNECT) {
#ifndef NOWSAUTH
if (!_wsAuth(client)) return;
#endif
IPAddress ip = client->remoteIP(); IPAddress ip = client->remoteIP();
DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url()); DEBUG_MSG_P(PSTR("[WEBSOCKET] #%u connected, ip: %d.%d.%d.%d, url: %s\n"), client->id(), ip[0], ip[1], ip[2], ip[3], server->url());
_wsStart(client->id()); _wsStart(client->id());
@ -386,6 +440,7 @@ void wsSend(ws_on_send_callback_f callback) {
callback(root); callback(root);
String output; String output;
root.printTo(output); root.printTo(output);
jsonBuffer.clear();
_ws.textAll((char *) output.c_str()); _ws.textAll((char *) output.c_str());
} }
} }
@ -410,6 +465,7 @@ void wsSend(uint32_t client_id, ws_on_send_callback_f callback) {
callback(root); callback(root);
String output; String output;
root.printTo(output); root.printTo(output);
jsonBuffer.clear();
_ws.text(client_id, (char *) output.c_str()); _ws.text(client_id, (char *) output.c_str());
} }
@ -423,27 +479,24 @@ void wsSend_P(uint32_t client_id, PGM_P payload) {
_ws.text(client_id, buffer); _ws.text(client_id, buffer);
} }
void wsConfigure() {
#if USE_PASSWORD
bool auth = getSetting("wsAuth", WS_AUTHENTICATION).toInt() == 1;
if (auth) {
_ws.setAuthentication(WEB_USERNAME, (const char *) getSetting("adminPass", ADMIN_PASS).c_str());
} else {
_ws.setAuthentication("", "");
}
#endif
// This method being public makes
// _ws_on_after_parse_callbacks strange here,
// it should belong somewhere else.
void wsReload() {
for (unsigned char i = 0; i < _ws_on_after_parse_callbacks.size(); i++) {
(_ws_on_after_parse_callbacks[i])();
}
} }
void wsSetup() { void wsSetup() {
_ws.onEvent(_wsEvent); _ws.onEvent(_wsEvent);
wsConfigure();
webServer()->addHandler(&_ws); webServer()->addHandler(&_ws);
webServer()->on("/auth", HTTP_GET, _onAuth);
#if MQTT_SUPPORT #if MQTT_SUPPORT
mqttRegister(_wsMQTTCallback); mqttRegister(_wsMQTTCallback);
#endif #endif
wsOnSendRegister(_wsOnStart); wsOnSendRegister(_wsOnStart);
wsOnReceiveRegister(_wsOnReceive); wsOnReceiveRegister(_wsOnReceive);
wsOnAfterParseRegister(wsConfigure);
espurnaRegisterLoop(_wsLoop); espurnaRegisterLoop(_wsLoop);
} }


+ 2
- 0
code/gulpfile.js View File

@ -38,6 +38,7 @@ const htmllint = require('gulp-htmllint');
const log = require('fancy-log'); const log = require('fancy-log');
const csslint = require('gulp-csslint'); const csslint = require('gulp-csslint');
const crass = require('gulp-crass'); const crass = require('gulp-crass');
const replace = require('gulp-replace');
const dataFolder = 'espurna/data/'; const dataFolder = 'espurna/data/';
const staticFolder = 'espurna/static/'; const staticFolder = 'espurna/static/';
@ -126,6 +127,7 @@ gulp.task('buildfs_inline', function() {
minifyCSS: true, minifyCSS: true,
minifyJS: true minifyJS: true
})). })).
pipe(replace('pure-', 'p-')).
pipe(gzip()). pipe(gzip()).
pipe(gulp.dest(dataFolder)); pipe(gulp.dest(dataFolder));
}); });


+ 94
- 25
code/html/custom.js View File

@ -13,6 +13,7 @@ var numReconnect = 0;
var numReload = 0; var numReload = 0;
var useWhite = false; var useWhite = false;
var useCCT = false;
var now = 0; var now = 0;
var ago = 0; var ago = 0;
@ -39,7 +40,7 @@ function sensorName(id) {
"HLW8012", "V9261F", "ECH1560", "Analog", "Digital", "HLW8012", "V9261F", "ECH1560", "Analog", "Digital",
"Events", "PMSX003", "BMX280", "MHZ19", "SI7021", "Events", "PMSX003", "BMX280", "MHZ19", "SI7021",
"SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD", "SHT3X I2C", "BH1750", "PZEM004T", "AM2320 I2C", "GUVAS12SD",
"TMP3X", "HC-SR04", "SenseAir"
"TMP3X", "HC-SR04", "SenseAir", "GeigerTicks", "GeigerCPM"
]; ];
if (1 <= id && id <= names.length) { if (1 <= id && id <= names.length) {
return names[id - 1]; return names[id - 1];
@ -53,7 +54,8 @@ function magnitudeType(type) {
"Current", "Voltage", "Active Power", "Apparent Power", "Current", "Voltage", "Active Power", "Apparent Power",
"Reactive Power", "Power Factor", "Energy", "Energy (delta)", "Reactive Power", "Power Factor", "Energy", "Energy (delta)",
"Analog", "Digital", "Events", "Analog", "Digital", "Events",
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV", "Distance" , "HCHO"
"PM1.0", "PM2.5", "PM10", "CO2", "Lux", "UV", "Distance" , "HCHO",
"Local Dose Rate", "Local Dose Rate"
]; ];
if (1 <= type && type <= types.length) { if (1 <= type && type <= types.length) {
return types[type - 1]; return types[type - 1];
@ -101,14 +103,6 @@ function keepTime() {
} }
// http://www.the-art-of-web.com/javascript/validate-password/
function checkPassword(str) {
// at least one lowercase and one uppercase letter or number
// at least five characters (letters, numbers or special characters)
var re = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
return re.test(str);
}
function zeroPad(number, positions) { function zeroPad(number, positions) {
var zeros = ""; var zeros = "";
for (var i = 0; i < positions; i++) { for (var i = 0; i < positions; i++) {
@ -146,9 +140,14 @@ function loadTimeZones() {
function validateForm(form) { function validateForm(form) {
// http://www.the-art-of-web.com/javascript/validate-password/
// at least one lowercase and one uppercase letter or number
// at least five characters (letters, numbers or special characters)
var re_password = /^(?=.*[A-Z\d])(?=.*[a-z])[\w~!@#$%^&*\(\)<>,.\?;:{}\[\]\\|]{5,}$/;
// password // password
var adminPass1 = $("input[name='adminPass']", form).first().val(); var adminPass1 = $("input[name='adminPass']", form).first().val();
if (adminPass1.length > 0 && !checkPassword(adminPass1)) {
if (adminPass1.length > 0 && !re_password.test(adminPass1)) {
alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!"); alert("The password you have entered is not valid, it must have at least 5 characters, 1 lowercase and 1 uppercase or number!");
return false; return false;
} }
@ -159,6 +158,23 @@ function validateForm(form) {
return false; return false;
} }
// RFCs mandate that a hostname's labels may contain only
// the ASCII letters 'a' through 'z' (case-insensitive),
// the digits '0' through '9', and the hyphen.
// Hostname labels cannot begin or end with a hyphen.
// No other symbols, punctuation characters, or blank spaces are permitted.
// Negative lookbehind does not work in Javascript
// var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{1,32}(?<!-)$');
var re_hostname = new RegExp('^(?!-)[A-Za-z0-9-]{0,31}[A-Za-z0-9]$');
var hostname = $("input[name='hostname']", form).val();
if (!re_hostname.test(hostname)) {
alert("Hostname cannot be empty and may only contain the ASCII letters ('A' through 'Z' and 'a' through 'z'), the digits '0' through '9', and the hyphen ('-')! They can neither start or end with an hyphen.");
return false;
}
return true; return true;
} }
@ -495,7 +511,7 @@ function onFileUpload(event) {
if (data) { if (data) {
sendAction("restore", data); sendAction("restore", data);
} else { } else {
alert(messages[4]);
window.alert(messages[4]);
} }
}; };
reader.readAsText(inputFile); reader.readAsText(inputFile);
@ -804,6 +820,22 @@ function initColorRGB() {
} }
function initCCT() {
// check if already initialized
var done = $("#cct > div").length;
if (done > 0) { return; }
$("#miredsTemplate").children().clone().appendTo("#cct");
$("#mireds").on("change", function() {
var value = $(this).val();
var parent = $(this).parents(".pure-g");
$("span", parent).html(value);
sendAction("mireds", {mireds: value});
});
}
function initColorHSV() { function initColorHSV() {
// check if already initialized // check if already initialized
@ -841,6 +873,9 @@ function initChannels(num) {
max = num % 3; max = num % 3;
if ((max > 0) & useWhite) { if ((max > 0) & useWhite) {
max--; max--;
if (useCCT) {
max--;
}
} }
} }
var start = num - max; var start = num - max;
@ -854,8 +889,9 @@ function initChannels(num) {
}; };
// add templates // add templates
var i = 0;
var template = $("#channelTemplate").children(); var template = $("#channelTemplate").children();
for (var i=0; i<max; i++) {
for (i=0; i<max; i++) {
var channel_id = start + i; var channel_id = start + i;
var line = $(template).clone(); var line = $(template).clone();
@ -867,7 +903,7 @@ function initChannels(num) {
} }
for (var i=0; i<num; i++) {
for (i=0; i<num; i++) {
$("select.islight").append( $("select.islight").append(
$("<option></option>").attr("value",i).text("Channel #" + i)); $("<option></option>").attr("value",i).text("Channel #" + i));
} }
@ -1021,10 +1057,21 @@ function processData(data) {
return; return;
} }
if ("mireds" === key) {
$("#mireds").val(value);
$("span.mireds").html(value);
return;
}
if ("useWhite" === key) { if ("useWhite" === key) {
useWhite = value; useWhite = value;
} }
if ("useCCT" === key) {
initCCT();
useCCT = value;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
// Sensors & Magnitudes // Sensors & Magnitudes
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
@ -1170,7 +1217,7 @@ function processData(data) {
// Web log // Web log
if ("weblog" === key) { if ("weblog" === key) {
$("#weblog").append(value);
$("#weblog").append(new Text(value));
$("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height()); $("#weblog").scrollTop($("#weblog")[0].scrollHeight - $("#weblog").height());
return; return;
} }
@ -1183,6 +1230,12 @@ function processData(data) {
return; return;
} }
if ("deviceip" === key) {
var a_href = $("span[name='" + key + "']").parent();
a_href.attr("href", "http://" + value);
a_href.next().attr("href", "telnet://" + value);
}
if ("now" === key) { if ("now" === key) {
now = value; now = value;
return; return;
@ -1299,27 +1352,43 @@ function hasChanged() {
function initUrls(root) { function initUrls(root) {
var paths = ["ws", "upgrade", "config"];
var paths = ["ws", "upgrade", "config", "auth"];
urls["root"] = root; urls["root"] = root;
paths.forEach(function(path) { paths.forEach(function(path) {
urls[path] = new URL(path, root); urls[path] = new URL(path, root);
urls[path].protocol = root.protocol;
}); });
urls.ws.protocol = "ws";
if (root.protocol == "https:") {
urls.ws.protocol = "wss:";
} else {
urls.ws.protocol = "ws:";
}
} }
function connectToURL(url) { function connectToURL(url) {
initUrls(url); initUrls(url);
if (websock) { websock.close(); }
websock = new WebSocket(urls.ws.href);
websock.onmessage = function(evt) {
var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
if (data) {
processData(data);
}
};
$.ajax({
'method': 'GET',
'url': urls.auth.href,
'xhrFields': { 'withCredentials': true }
}).done(function(data) {
if (websock) { websock.close(); }
websock = new WebSocket(urls.ws.href);
websock.onmessage = function(evt) {
var data = getJson(evt.data.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t"));
if (data) {
processData(data);
}
};
}).fail(function() {
// Nothing to do, reload page and retry
});
} }
function connect(host) { function connect(host) {


+ 29
- 9
code/html/index.html View File

@ -179,6 +179,8 @@
<div id="colors"></div> <div id="colors"></div>
<div id="cct"></div>
<div id="channels"></div> <div id="channels"></div>
<div id="magnitudes"></div> <div id="magnitudes"></div>
@ -240,7 +242,7 @@
<div class="pure-u-11-24"><span class="right" name="rssi"></span></div> <div class="pure-u-11-24"><span class="right" name="rssi"></span></div>
<div class="pure-u-1-2">IP</div> <div class="pure-u-1-2">IP</div>
<div class="pure-u-11-24"><span class="right" name="deviceip"></span></div>
<div class="pure-u-11-24"><a href=""><span class="right" name="deviceip"></span></a> (<a href=""><span class="right">telnet</span></a>)</div>
<div class="pure-u-1-2">Free heap</div> <div class="pure-u-1-2">Free heap</div>
<div class="pure-u-11-24"><span class="right" name="heap" post=" bytes"></span></div> <div class="pure-u-11-24"><span class="right" name="heap" post=" bytes"></span></div>
@ -293,7 +295,9 @@
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></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"> <div class="pure-u-1 pure-u-lg-3-4 hint">
This name will identify this device in your network (http://&lt;hostname&gt;.local). For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button.
This name will identify this device in your network (http://&lt;hostname&gt;.local).<br />
Hostname may contain only the ASCII letters 'a' through 'z' (in a case-insensitive manner), the digits '0' through '9', and the hyphen ('-'). They can neither start or end with an hyphen.<br />
For this setting to take effect you should restart the wifi interface by clicking the "Reconnect" button.
</div> </div>
</div> </div>
@ -410,12 +414,20 @@
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div> <div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useWhite" action="reload" tabindex="9" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></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">Use forth dimmable channel as white when first 3 have the same RGB value.<br />Will only work if the device has at least 4 dimmable channels.<br />Enabling this will render useless the "Channel 4" slider in the status page.<br />Reload the page to update the web interface.</div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use forth dimmable channel as (cold) white light calculated out of the RGB values.<br />Will only work if the device has at least 4 dimmable channels.<br />Enabling this will render useless the "Channel 4" slider in the status page.<br />Reload the page to update the web interface.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use white color temperature</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCCT" action="reload" tabindex="10" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div>
<div class="pure-u-0 pure-u-lg-1-4"></div>
<div class="pure-u-1 pure-u-lg-3-4 hint">Use fifth dimmable channel as warm white light and the forth dimmable channel as cold white.<br />Will only work if the device has at least 5 dimmable channels and "white channel" above is also ON.<br />Enabling this will render useless the "Channel 5" slider in the status page.<br />Reload the page to update the web interface.</div>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use gamma correction</label> <label class="pure-u-1 pure-u-lg-1-4">Use gamma correction</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="10" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useGamma" tabindex="11" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></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">Use gamma correction for RGB channels.<br />Will only work if "use colorpicker" above is also ON.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Use gamma correction for RGB channels.<br />Will only work if "use colorpicker" above is also ON.</div>
@ -423,7 +435,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Use CSS style</label> <label class="pure-u-1 pure-u-lg-1-4">Use CSS style</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="11" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useCSS" tabindex="12" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></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">Use CSS style to report colors to MQTT and REST API. <br />Red will be reported as "#FF0000" if ON, otherwise "255,0,0"</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Use CSS style to report colors to MQTT and REST API. <br />Red will be reported as "#FF0000" if ON, otherwise "255,0,0"</div>
@ -431,7 +443,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Color transitions</label> <label class="pure-u-1 pure-u-lg-1-4">Color transitions</label>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="12" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input type="checkbox" name="useTransitions" tabindex="13" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></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">If enabled color changes will be smoothed.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">If enabled color changes will be smoothed.</div>
@ -439,7 +451,7 @@
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Transition time</label> <label class="pure-u-1 pure-u-lg-1-4">Transition time</label>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1" type="number" name="lightTime" min="10" max="5000" tabindex="13" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input class="pure-u-1" type="number" name="lightTime" min="10" max="5000" tabindex="14" /></div>
<div class="pure-u-0 pure-u-lg-1-2"></div> <div class="pure-u-0 pure-u-lg-1-2"></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">Time in millisecons to transition from one color to another.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Time in millisecons to transition from one color to another.</div>
@ -447,7 +459,7 @@
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div>
<div class="pure-u-1 pure-u-lg-3-4"><input name="mqttGroupColor" class="pure-u-1" tabindex="14" action="reconnect" /></div>
<div class="pure-u-1 pure-u-lg-3-4"><input name="mqttGroupColor" class="pure-u-1" tabindex="15" action="reconnect" /></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">Sync color between different lights.</div> <div class="pure-u-1 pure-u-lg-3-4 hint">Sync color between different lights.</div>
</div> </div>
@ -1339,7 +1351,7 @@
</div> </div>
<div class="pure-g module module-mqtt"> <div class="pure-g module module-mqtt">
<div class="pure-u-1 pure-u-lg-1-4"><label>On MQTT disconnect</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>On MQTT disconnect</label></div>
<select class="pure-u-1 pure-u-lg-3-4" name="mqttOnDisc">
<select class="pure-u-1 pure-u-lg-3-4" name="relayOnDisc">
<option value="0">Don't change</option> <option value="0">Don't change</option>
<option value="1">Turn the switch OFF</option> <option value="1">Turn the switch OFF</option>
<option value="2">Turn the switch ON</option> <option value="2">Turn the switch ON</option>
@ -1405,6 +1417,14 @@
</div> </div>
</div> </div>
<div id="miredsTemplate" class="template">
<div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4">Mireds (Cold &harr; Warm)</label>
<input type="range" min="153" max="500" class="slider pure-u-lg-1-4" id="mireds">
<span class="slider mireds pure-u-lg-1-4"></span>
</div>
</div>
<div id="magnitudeTemplate" class="template"> <div id="magnitudeTemplate" class="template">
<div class="pure-g"> <div class="pure-g">
<label class="pure-u-1 pure-u-lg-1-4"></label> <label class="pure-u-1 pure-u-lg-1-4"></label>


+ 2
- 2
code/html/vendor/pure-1.0.0.min.css
File diff suppressed because it is too large
View File


+ 2
- 2
code/html/vendor/pure-grids-responsive-1.0.0.min.css
File diff suppressed because it is too large
View File


+ 28
- 21
code/ota.py View File

@ -13,7 +13,7 @@ import re
import socket import socket
import subprocess import subprocess
import sys import sys
from time import sleep
import time
from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf from zeroconf import ServiceBrowser, ServiceStateChange, Zeroconf
@ -25,9 +25,11 @@ except NameError:
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
devices = []
description = "ESPurna OTA Manager v0.1"
DISCOVER_TIMEOUT = 2
description = "ESPurna OTA Manager v0.2"
devices = []
discover_last = 0
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
@ -37,23 +39,26 @@ def on_service_state_change(zeroconf, service_type, name, state_change):
""" """
if state_change is ServiceStateChange.Added: if state_change is ServiceStateChange.Added:
discover_last = time.time()
info = zeroconf.get_service_info(service_type, name) info = zeroconf.get_service_info(service_type, name)
if info: if info:
hostname = info.server.split(".")[0] hostname = info.server.split(".")[0]
device = { device = {
'hostname': hostname.upper(), 'hostname': hostname.upper(),
'ip': socket.inet_ntoa(info.address)
'ip': socket.inet_ntoa(info.address),
'mac': '',
'app_name': '',
'app_version': '',
'target_board': '',
'mem_size': '',
'sdk_size': '',
'free_space': '',
} }
device['mac'] = info.properties.get('mac', '')
device['app'] = info.properties.get('app_name', '')
device['version'] = info.properties.get('app_version', '')
device['device'] = info.properties.get('target_board', '')
if 'mem_size' in info.properties:
device['mem_size'] = info.properties.get('mem_size')
if 'sdk_size' in info.properties:
device['sdk_size'] = info.properties.get('sdk_size')
if 'free_space' in info.properties:
device['free_space'] = info.properties.get('free_space')
for key, item in info.properties.items():
device[key.decode('UTF-8')] = item.decode('UTF-8');
devices.append(device) devices.append(device)
@ -84,9 +89,9 @@ def list_devices():
device.get('hostname', ''), device.get('hostname', ''),
device.get('ip', ''), device.get('ip', ''),
device.get('mac', ''), device.get('mac', ''),
device.get('app', ''),
device.get('version', ''),
device.get('device', ''),
device.get('app_name', ''),
device.get('app_version', ''),
device.get('target_board', ''),
device.get('mem_size', ''), device.get('mem_size', ''),
device.get('sdk_size', ''), device.get('sdk_size', ''),
device.get('free_space', ''), device.get('free_space', ''),
@ -121,7 +126,7 @@ def get_board_by_index(index):
if 1 <= index and index <= len(devices): if 1 <= index and index <= len(devices):
device = devices[index - 1] device = devices[index - 1]
board['hostname'] = device.get('hostname') board['hostname'] = device.get('hostname')
board['board'] = device.get('device', '')
board['board'] = device.get('target_board', '')
board['ip'] = device.get('ip', '') board['ip'] = device.get('ip', '')
board['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024 board['size'] = int(device.get('mem_size', 0) if device.get('mem_size', 0) == device.get('sdk_size', 0) else 0) / 1024
return board return board
@ -135,7 +140,7 @@ def get_board_by_hostname(hostname):
if device.get('hostname', '').lower() == hostname: if device.get('hostname', '').lower() == hostname:
board = {} board = {}
board['hostname'] = device.get('hostname') board['hostname'] = device.get('hostname')
board['board'] = device.get('device')
board['board'] = device.get('target_board')
if not board['board']: if not board['board']:
return None return None
board['ip'] = device.get('ip') board['ip'] = device.get('ip')
@ -226,8 +231,10 @@ if __name__ == '__main__':
# Look for sevices # Look for sevices
zeroconf = Zeroconf() zeroconf = Zeroconf()
browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change]) browser = ServiceBrowser(zeroconf, "_arduino._tcp.local.", handlers=[on_service_state_change])
sleep(5)
zeroconf.close()
discover_last = time.time()
while time.time() < discover_last + DISCOVER_TIMEOUT:
None
#zeroconf.close()
if len(devices) == 0: if len(devices) == 0:
print("Nothing found!\n") print("Nothing found!\n")


+ 115
- 0
code/package-lock.json View File

@ -238,6 +238,12 @@
"integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=",
"dev": true "dev": true
}, },
"binaryextensions": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz",
"integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==",
"dev": true
},
"boolbase": { "boolbase": {
"version": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "version": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=",
@ -944,6 +950,12 @@
"jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz" "jsbn": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz"
} }
}, },
"editions": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz",
"integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==",
"dev": true
},
"end-of-stream": { "end-of-stream": {
"version": "0.1.5", "version": "0.1.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz",
@ -1708,6 +1720,49 @@
"through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz" "through2": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz"
} }
}, },
"gulp-replace": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/gulp-replace/-/gulp-replace-1.0.0.tgz",
"integrity": "sha512-lgdmrFSI1SdhNMXZQbrC75MOl1UjYWlOWNbNRnz+F/KHmgxt3l6XstBoAYIdadwETFyG/6i+vWUSCawdC3pqOw==",
"dev": true,
"requires": {
"istextorbinary": "2.2.1",
"readable-stream": "2.3.6",
"replacestream": "4.0.3"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"isarray": "1.0.0",
"process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"string_decoder": "1.1.1",
"util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
}
}
}
},
"gulp-uglify": { "gulp-uglify": {
"version": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-1.5.4.tgz", "version": "https://registry.npmjs.org/gulp-uglify/-/gulp-uglify-1.5.4.tgz",
"integrity": "sha1-UkeI2HZm0J+dDCH7IXf5ADmmWMk=", "integrity": "sha1-UkeI2HZm0J+dDCH7IXf5ADmmWMk=",
@ -2227,6 +2282,17 @@
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true "dev": true
}, },
"istextorbinary": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz",
"integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==",
"dev": true,
"requires": {
"binaryextensions": "2.1.1",
"editions": "1.3.4",
"textextensions": "2.2.0"
}
},
"js-yaml": { "js-yaml": {
"version": "3.7.0", "version": "3.7.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz",
@ -3136,6 +3202,49 @@
"integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=",
"dev": true "dev": true
}, },
"replacestream": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/replacestream/-/replacestream-4.0.3.tgz",
"integrity": "sha512-AC0FiLS352pBBiZhd4VXB1Ab/lh0lEgpP+GGvZqbQh8a5cmXVoTe5EX/YeTFArnp4SRGTHh1qCHu9lGs1qG8sA==",
"dev": true,
"requires": {
"escape-string-regexp": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"object-assign": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"readable-stream": "2.3.6"
},
"dependencies": {
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
"dev": true
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
"core-util-is": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"inherits": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"isarray": "1.0.0",
"process-nextick-args": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz",
"string_decoder": "1.1.1",
"util-deprecate": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"requires": {
"safe-buffer": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz"
}
}
}
},
"request": { "request": {
"version": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", "version": "https://registry.npmjs.org/request/-/request-2.83.0.tgz",
"integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=", "integrity": "sha1-ygtl2gLtYpNYh4COb1EDgQNOM1Y=",
@ -3675,6 +3784,12 @@
"upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz" "upper-case": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz"
} }
}, },
"textextensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz",
"integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==",
"dev": true
},
"through": { "through": {
"version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "version": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",


+ 1
- 0
code/package.json View File

@ -17,6 +17,7 @@
"gulp-htmllint": "0.0.14", "gulp-htmllint": "0.0.14",
"gulp-htmlmin": "^2.0.0", "gulp-htmlmin": "^2.0.0",
"gulp-inline": "^0.1.1", "gulp-inline": "^0.1.1",
"gulp-replace": "^1.0.0",
"gulp-uglify": "^1.5.3" "gulp-uglify": "^1.5.3"
} }
} }

+ 565
- 431
code/platformio.ini
File diff suppressed because it is too large
View File


+ 57
- 0
code/symbols.sh View File

@ -0,0 +1,57 @@
#!/bin/bash
# ------------------------------------------------------------------------------
# CONFIGURATION
# ------------------------------------------------------------------------------
ENVIRONMENT="wemos-d1mini-relayshield"
READELF="xtensa-lx106-elf-readelf"
NUMBER=20
# ------------------------------------------------------------------------------
# END CONFIGURATION - DO NOT EDIT FURTHER
# ------------------------------------------------------------------------------
# remove default trace file
rm -rf $FILE
function help {
echo
echo "Syntax: $0 [-e <environment>] [-n <number>]"
echo
}
# get environment from command line
while [[ $# -gt 1 ]]; do
key="$1"
case $key in
-e)
ENVIRONMENT="$2"
shift
;;
-n)
NUMBER="$2"
shift
;;
esac
shift # past argument or value
done
# check environment folder
if [ $ENVIRONMENT == "" ]; then
echo "No environment defined"
help
exit 1
fi
ELF=.pioenvs/$ENVIRONMENT/firmware.elf
if [ ! -f $ELF ]; then
echo "Could not find ELF file for the selected environment: $ELF"
exit 2
fi
$READELF -s $ELF | head -3 | tail -1
$READELF -s $ELF | sort -r -k3 -n | head -$NUMBER

BIN
images/devices/geiger_espurna_configuration.png View File

Before After
Width: 2076  |  Height: 1564  |  Size: 247 KiB

BIN
images/devices/geiger_espurna_status.png View File

Before After
Width: 1800  |  Height: 1496  |  Size: 325 KiB

BIN
images/devices/geiger_grafana_dashboard.png View File

Before After
Width: 2414  |  Height: 658  |  Size: 457 KiB

BIN
images/devices/geiger_scope_following_pulses.png View File

Before After
Width: 800  |  Height: 480  |  Size: 39 KiB

BIN
images/devices/geiger_scope_single_pulse.png View File

Before After
Width: 800  |  Height: 480  |  Size: 38 KiB

BIN
images/devices/geiger_wiring_diagram.png View File

Before After
Width: 2152  |  Height: 864  |  Size: 1.9 MiB

BIN
images/devices/tonbux-mosquito-killer.jpg View File

Before After
Width: 400  |  Height: 400  |  Size: 16 KiB

Loading…
Cancel
Save