Browse Source

Merge branch 'dev' into sensors

rfm69
Xose Pérez 6 years ago
parent
commit
56076dc8e1
55 changed files with 2783 additions and 953 deletions
  1. +0
    -0
      .github/stale.yml
  2. +1
    -0
      .gitignore
  3. +1
    -1
      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. +1
    -0
      code/espurna/config/arduino.h
  13. +25
    -0
      code/espurna/config/defaults.h
  14. +7
    -4
      code/espurna/config/general.h
  15. +6
    -0
      code/espurna/config/hardware.h
  16. +178
    -4
      code/espurna/config/progmem.h
  17. +7
    -0
      code/espurna/config/prototypes.h
  18. +36
    -2
      code/espurna/config/sensors.h
  19. +20
    -1
      code/espurna/config/types.h
  20. +1
    -1
      code/espurna/config/version.h
  21. +44
    -34
      code/espurna/debug.ino
  22. +86
    -0
      code/espurna/eeprom.ino
  23. +4
    -1
      code/espurna/espurna.ino
  24. +12
    -2
      code/espurna/homeassistant.ino
  25. +30
    -28
      code/espurna/led.ino
  26. +636
    -0
      code/espurna/libs/fs_math.c
  27. +116
    -0
      code/espurna/libs/fs_math.h
  28. +0
    -0
      code/espurna/libs/pwm.c
  29. +13
    -23
      code/espurna/light.ino
  30. +12
    -10
      code/espurna/mqtt.ino
  31. +11
    -1
      code/espurna/ota.ino
  32. +97
    -41
      code/espurna/relay.ino
  33. +33
    -9
      code/espurna/rfbridge.ino
  34. +13
    -1
      code/espurna/sensor.ino
  35. +15
    -8
      code/espurna/sensors/EmonSensor.h
  36. +298
    -0
      code/espurna/sensors/GeigerSensor.h
  37. +4
    -1
      code/espurna/sensors/V9261FSensor.h
  38. +46
    -38
      code/espurna/settings.ino
  39. +4
    -6
      code/espurna/system.ino
  40. +1
    -1
      code/espurna/telnet.ino
  41. +77
    -174
      code/espurna/utils.ino
  42. +33
    -6
      code/espurna/web.ino
  43. +214
    -52
      code/espurna/wifi.ino
  44. +2
    -0
      code/espurna/ws.ino
  45. +11
    -5
      code/html/custom.js
  46. +1
    -1
      code/html/index.html
  47. +36
    -10
      code/ota.py
  48. +483
    -469
      code/platformio.ini
  49. +57
    -0
      code/symbols.sh
  50. BIN
      images/devices/geiger_espurna_configuration.png
  51. BIN
      images/devices/geiger_espurna_status.png
  52. BIN
      images/devices/geiger_grafana_dashboard.png
  53. BIN
      images/devices/geiger_scope_following_pulses.png
  54. BIN
      images/devices/geiger_scope_single_pulse.png
  55. BIN
      images/devices/geiger_wiring_diagram.png

code/.github/stale.yml → .github/stale.yml View File


+ 1
- 0
.gitignore View File

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

+ 1
- 1
README.md View File

@ -3,7 +3,7 @@
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.7a-brightgreen.svg)](CHANGELOG.md)
[![version](https://img.shields.io/badge/version-1.13.0c-brightgreen.svg)](CHANGELOG.md)
[![branch](https://img.shields.io/badge/branch-sensors-orange.svg)](https://github.org/xoseperez/espurna/tree/sensors/) [![branch](https://img.shields.io/badge/branch-sensors-orange.svg)](https://github.org/xoseperez/espurna/tree/sensors/)
[![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=sensors)](https://travis-ci.org/xoseperez/espurna) [![travis](https://travis-ci.org/xoseperez/espurna.svg?branch=sensors)](https://travis-ci.org/xoseperez/espurna)
[![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/sensors.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard) [![codacy](https://img.shields.io/codacy/grade/c9496e25cf07434cba786b462cb15f49/sensors.svg)](https://www.codacy.com/app/xoseperez/espurna/dashboard)


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


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

@ -145,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

+ 25
- 0
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


+ 7
- 4
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
@ -682,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"


+ 6
- 0
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


+ 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


+ 36
- 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 0 // 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 || \
@ -639,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.7a"
#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"


+ 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;
} }


+ 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


+ 12
- 2
code/espurna/homeassistant.ino View File

@ -44,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());
@ -123,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());
@ -163,6 +165,8 @@ String _haGetConfig() {
} }
output += "\n"; output += "\n";
jsonBuffer.clear();
} }
#if SENSOR_SUPPORT #if SENSOR_SUPPORT
@ -186,6 +190,8 @@ String _haGetConfig() {
} }
output += "\n"; output += "\n";
jsonBuffer.clear();
} }
#endif #endif
@ -255,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"));
}); });
} }


+ 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


+ 13
- 23
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" {
@ -275,53 +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) {
if (!_light_has_color) return; if (!_light_has_color) return;
if (setMireds) {
_light_mireds = constrain(round(1000000UL / kelvin), LIGHT_MIN_MIREDS, LIGHT_MAX_MIREDS);
}
if (_light_use_cct) { if (_light_use_cct) {
_setRGBInputValue(LIGHT_MAX_VALUE, LIGHT_MAX_VALUE, LIGHT_MAX_VALUE); _setRGBInputValue(LIGHT_MAX_VALUE, LIGHT_MAX_VALUE, LIGHT_MAX_VALUE);
return; 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(red, green, blue); _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 (!_light_has_color) return;
_light_mireds = mireds = constrain(mireds, LIGHT_MIN_MIREDS, LIGHT_MAX_MIREDS);
if (_light_use_cct) {
_setRGBInputValue(LIGHT_MAX_VALUE, LIGHT_MAX_VALUE, LIGHT_MAX_VALUE);
return;
}
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000) / 100;
_fromKelvin(kelvin, false);
unsigned long kelvin = constrain(1000000UL / mireds, 1000, 40000);
_fromKelvin(kelvin);
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------


+ 12
- 10
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>
@ -265,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,
@ -274,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));
@ -287,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++;
@ -613,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


+ 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();


+ 97
- 41
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>
@ -23,7 +23,6 @@ typedef struct {
unsigned long delay_off; // Delay to turn relay OFF unsigned long delay_off; // Delay to turn relay OFF
unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON unsigned char pulse; // RELAY_PULSE_NONE, RELAY_PULSE_OFF or RELAY_PULSE_ON
unsigned long pulse_ms; // Pulse length in millis unsigned long pulse_ms; // Pulse length in millis
unsigned long pulse_start; // Current pulse start (millis), 0 means no pulse
// Status variables // Status variables
@ -35,6 +34,10 @@ typedef struct {
bool report; // Whether to report to own topic bool report; // Whether to report to own topic
bool group_report; // Whether to report to group topic bool group_report; // Whether to report to group topic
// Helping objects
Ticker pulseTicker; // Holds the pulse back timer
} relay_t; } relay_t;
std::vector<relay_t> _relays; std::vector<relay_t> _relays;
bool _relayRecursive = false; bool _relayRecursive = false;
@ -200,27 +203,14 @@ void _relayProcess(bool mode) {
} }
/**
* Walks the relay vector check if any relay has to pulse back
*/
void _relayPulseCheck() {
unsigned long current_time = millis();
for (unsigned char id = 0; id < _relays.size(); id++) {
if (_relays[id].pulse_start > 0) {
if (current_time - _relays[id].pulse_start > _relays[id].pulse_ms) {
_relays[id].pulse_start = 0;
relayToggle(id);
}
}
}
}
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// RELAY // RELAY
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
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;
@ -229,11 +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].pulse_start = 0;
} else {
if (pulseStatus != status) {
DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms); DEBUG_MSG_P(PSTR("[RELAY] Scheduling relay #%d back in %lums (pulse)\n"), id, ms);
_relays[id].pulse_start = millis();
_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();
} }
} }
@ -369,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) {
@ -449,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
@ -476,7 +467,6 @@ void _relayBoot() {
} }
_relays[i].current_status = !status; _relays[i].current_status = !status;
_relays[i].target_status = status; _relays[i].target_status = status;
_relays[i].pulse_start = 0;
#if RELAY_PROVIDER == RELAY_PROVIDER_STM #if RELAY_PROVIDER == RELAY_PROVIDER_STM
_relays[i].change_time = millis() + 3000 + 1000 * i; _relays[i].change_time = millis() + 3000 + 1000 * i;
#else #else
@ -487,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;
@ -611,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);
@ -638,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;
}
);
} }
} }
@ -706,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++) {
@ -720,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++) {
@ -811,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) {
@ -822,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"));
}); });
@ -834,7 +891,6 @@ void _relayInitCommands() {
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
void _relayLoop() { void _relayLoop() {
_relayPulseCheck();
_relayProcess(false); _relayProcess(false);
_relayProcess(true); _relayProcess(true);
} }


+ 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

@ -451,6 +451,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();
@ -593,7 +605,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();


+ 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

+ 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);
} }


+ 46
- 38
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,18 +251,40 @@ void _settingsInitCommands() {
DEBUG_MSG_P(PSTR("+OK\n")); DEBUG_MSG_P(PSTR("+OK\n"));
}); });
settingsRegisterCommand(F("RELOAD"), [](Embedis* e) {
wsReload();
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")); 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);
}); });
@ -358,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;
@ -371,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) {
@ -410,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
@ -423,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
@ -448,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;
} }


+ 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


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

@ -138,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


+ 77
- 174
code/espurna/utils.ino View File

@ -64,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
@ -211,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"));
@ -235,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] Max OTA size: %8u bytes / %4d sectors\n"), maxSketchSpace(), sectors(maxSketchSpace()));
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"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -265,171 +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 BH1750_SUPPORT
DEBUG_MSG_P(PSTR(" BH1750"));
#endif
#if BMX280_SUPPORT
DEBUG_MSG_P(PSTR(" BMX280"));
#endif
#if CSE7766_SUPPORT
DEBUG_MSG_P(PSTR(" CSE7766"));
#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 SENSEAIR_SUPPORT
DEBUG_MSG_P(PSTR(" SENSEAIR"));
#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"));
// ------------------------------------------------------------------------- // -------------------------------------------------------------------------
@ -508,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;
} }
@ -516,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) {
@ -552,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;
}

+ 33
- 6
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 (!webAuthenticate(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,18 +55,23 @@ 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());
response->addHeader("Content-Disposition", buffer); response->addHeader("Content-Disposition", buffer);
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
request->send(response); request->send(response);
} }
void _onPostConfig(AsyncWebServerRequest *request) { void _onPostConfig(AsyncWebServerRequest *request) {
webLog(request); webLog(request);
if (!webAuthenticate(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);
} }
@ -112,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 (!webAuthenticate(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)) {
@ -151,6 +160,9 @@ void _onHome(AsyncWebServerRequest *request) {
response->addHeader("Content-Encoding", "gzip"); response->addHeader("Content-Encoding", "gzip");
response->addHeader("Last-Modified", _last_modified); response->addHeader("Last-Modified", _last_modified);
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
request->send(response); request->send(response);
} }
@ -212,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 (!webAuthenticate(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()) {
@ -223,7 +237,12 @@ void _onUpgrade(AsyncWebServerRequest *request) {
AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer); AsyncWebServerResponse *response = request->beginResponse(200, "text/plain", buffer);
response->addHeader("Connection", "close"); response->addHeader("Connection", "close");
if (!Update.hasError()) {
response->addHeader("X-XSS-Protection", "1; mode=block");
response->addHeader("X-Content-Type-Options", "nosniff");
response->addHeader("X-Frame-Options", "deny");
if (Update.hasError()) {
eepromRotate(true);
} else {
deferredReset(100, CUSTOM_RESET_UPGRADE); deferredReset(100, CUSTOM_RESET_UPGRADE);
} }
request->send(response); request->send(response);
@ -231,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)) {
@ -239,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
@ -247,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);


+ 214
- 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,6 +213,47 @@ 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" #include "DNSServer.h"
@ -221,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"));
@ -243,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);
} }
@ -256,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
@ -294,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"));
@ -346,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
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@ -372,11 +535,30 @@ 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;
@ -389,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 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());
}
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) {
@ -445,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
@ -470,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();
}
} }

+ 2
- 0
code/espurna/ws.ino View File

@ -440,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());
} }
} }
@ -464,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());
} }


+ 11
- 5
code/html/custom.js View File

@ -40,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];
@ -54,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];
@ -510,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);
@ -1216,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;
} }
@ -1356,9 +1357,14 @@ function initUrls(root) {
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:";
}
} }


+ 1
- 1
code/html/index.html View File

@ -1336,7 +1336,7 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-4"><label>Pulse time (s)</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>Pulse time (s)</label></div>
<div class="pure-u-1 pure-u-lg-1-4"><input name="relayTime" class="pure-u-1" type="number" min="0" step="0.1" max="86400" /></div>
<div class="pure-u-1 pure-u-lg-1-4"><input name="relayTime" class="pure-u-1" type="number" min="0" step="0.1" max="3600" /></div>
</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>MQTT group</label></div> <div class="pure-u-1 pure-u-lg-1-4"><label>MQTT group</label></div>


+ 36
- 10
code/ota.py View File

@ -8,6 +8,7 @@
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
from __future__ import print_function from __future__ import print_function
import shutil
import argparse import argparse
import re import re
import socket import socket
@ -27,7 +28,7 @@ except NameError:
DISCOVER_TIMEOUT = 2 DISCOVER_TIMEOUT = 2
description = "ESPurna OTA Manager v0.2"
description = "ESPurna OTA Manager v0.3"
devices = [] devices = []
discover_last = 0 discover_last = 0
@ -111,6 +112,11 @@ def get_boards():
boards.append(m.group(1)) boards.append(m.group(1))
return sorted(boards) return sorted(boards)
def get_device_size(device):
if device.get('mem_size', 0) == device.get('sdk_size', 0):
return int(device.get('mem_size', 0)) / 1024
return 0
def get_empty_board(): def get_empty_board():
""" """
Returns the empty structure of a board to flash Returns the empty structure of a board to flash
@ -128,9 +134,26 @@ def get_board_by_index(index):
board['hostname'] = device.get('hostname') board['hostname'] = device.get('hostname')
board['board'] = device.get('target_board', '') 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'] = get_device_size(device)
return board return board
def get_board_by_mac(mac):
"""
Returns the required data to flash a given board
"""
hostname = hostname.lower()
for device in devices:
if device.get('mac', '').lower() == mac:
board = {}
board['hostname'] = device.get('hostname')
board['board'] = device.get('device')
board['ip'] = device.get('ip')
board['size'] = get_device_size(device)
if not board['board'] or not board['ip'] or board['size'] == 0:
return None
return board
return None
def get_board_by_hostname(hostname): def get_board_by_hostname(hostname):
""" """
Returns the required data to flash a given board Returns the required data to flash a given board
@ -141,13 +164,9 @@ def get_board_by_hostname(hostname):
board = {} board = {}
board['hostname'] = device.get('hostname') board['hostname'] = device.get('hostname')
board['board'] = device.get('target_board') board['board'] = device.get('target_board')
if not board['board']:
return None
board['ip'] = device.get('ip') board['ip'] = device.get('ip')
if not board['ip']:
return None
board['size'] = int(device.get('sdk_size', 0)) / 1024
if board['size'] == 0:
board['size'] = get_device_size(device)
if not board['board'] or not board['ip'] or board['size'] == 0:
return None return None
return board return board
return None return None
@ -201,13 +220,20 @@ def input_board():
return board return board
def boardname(board):
return board.get('hostname', board['ip'])
def store(device, env):
source = ".pioenvs/%s/firmware.elf" % env
destination = ".pioenvs/elfs/%s.elf" % boardname(device).lower()
shutil.move(source, destination)
def run(device, env): def run(device, env):
print("Building and flashing image over-the-air...") print("Building and flashing image over-the-air...")
command = "export ESPURNA_IP=\"%s\"; export ESPURNA_BOARD=\"%s\"; export ESPURNA_AUTH=\"%s\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s -t upload" command = "export ESPURNA_IP=\"%s\"; export ESPURNA_BOARD=\"%s\"; export ESPURNA_AUTH=\"%s\"; export ESPURNA_FLAGS=\"%s\"; platformio run --silent --environment %s -t upload"
command = command % (device['ip'], device['board'], device['auth'], device['flags'], env) command = command % (device['ip'], device['board'], device['auth'], device['flags'], env)
subprocess.check_call(command, shell=True) subprocess.check_call(command, shell=True)
store(device, env)
# ------------------------------------------------------------------------------- # -------------------------------------------------------------------------------
@ -287,7 +313,7 @@ if __name__ == '__main__':
# Summary # Summary
print() print()
print("HOST = %s" % board.get('hostname', board['ip']))
print("HOST = %s" % boardname(board))
print("IP = %s" % board['ip']) print("IP = %s" % board['ip'])
print("BOARD = %s" % board['board']) print("BOARD = %s" % board['board'])
print("AUTH = %s" % board['auth']) print("AUTH = %s" % board['auth'])


+ 483
- 469
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

Loading…
Cancel
Save