Browse Source

Merge branch 'dev' of https://bitbucket.org/xoseperez/espurna into dev

fastled
Wesley 7 years ago
parent
commit
a6517bda0a
36 changed files with 5433 additions and 4525 deletions
  1. +22
    -0
      CHANGELOG.md
  2. +6
    -4
      README.md
  3. +0
    -1
      code/espurna/config/arduino.h
  4. +140
    -21
      code/espurna/config/general.h
  5. +46
    -3
      code/espurna/config/hardware.h
  6. +0
    -74
      code/espurna/config/sensors.h
  7. +1
    -1
      code/espurna/config/version.h
  8. BIN
      code/espurna/data/index.html.gz
  9. +129
    -0
      code/espurna/debug.ino
  10. +2
    -2
      code/espurna/dht.ino
  11. +9
    -5
      code/espurna/domoticz.ino
  12. +2
    -2
      code/espurna/ds18b20.ino
  13. +0
    -217
      code/espurna/emon.ino
  14. +12
    -20
      code/espurna/espurna.ino
  15. +9
    -1
      code/espurna/hardware.ino
  16. +0
    -344
      code/espurna/hlw8012.ino
  17. +15
    -12
      code/espurna/influxdb.ino
  18. +103
    -116
      code/espurna/mqtt.ino
  19. +3
    -3
      code/espurna/ota.ino
  20. +70
    -0
      code/espurna/power.h
  21. +334
    -0
      code/espurna/power.ino
  22. +207
    -0
      code/espurna/power_ech1560.ino
  23. +164
    -0
      code/espurna/power_emon.ino
  24. +161
    -0
      code/espurna/power_hlw8012.ino
  25. +234
    -0
      code/espurna/power_v9261f.ino
  26. +18
    -1
      code/espurna/settings.ino
  27. +3544
    -3544
      code/espurna/static/index.html.gz.h
  28. +6
    -0
      code/espurna/utils.ino
  29. +51
    -42
      code/espurna/web.ino
  30. +0
    -16
      code/espurna/wifi.ino
  31. +2
    -2
      code/html/custom.js
  32. +44
    -60
      code/html/index.html
  33. +9
    -6
      code/ota_flash.sh
  34. +9
    -6
      code/ota_list.sh
  35. +81
    -22
      code/platformio.ini
  36. BIN
      images/devices/generic-v9261f.jpg

+ 22
- 0
CHANGELOG.md View File

@ -3,6 +3,28 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [1.9.5] 2017-09-28
### Fixed
- Revert to JustWifi 1.1.4 (#228)
## [1.9.4] 2017-09-22
### Added
- Added ESPurna specific mDNS text registers (app_name, app_version, device_name)
- Crash dump info is stored in EEPROM and retrieved via terminal ("crash" command)
- Support for Huacanxing H802
- Support for powermeters based on V9261F IC
- Support for powermeters based on ECH1560 IC (beta, untested)
### Changed
- Changed behaviour on MQTT connection failure (#215)
- Removed boot delay
- Refactor power modules
- Updated JustWifi library
### Fixed
- Set all esp8285 devices to use esp01_1m (#210, #225)
- Removed wifi gain option since it prevents some devices to connect (#204)
## [1.9.3] 2017-09-04
### Added
- New "erase.config" option in terminal to delete SDK settings


+ 6
- 4
README.md View File

@ -4,9 +4,11 @@ ESPurna ("spark" in Catalan) is a custom firmware for ESP8266 based smart switch
It was originally developed with the **[IteadStudio Sonoff](https://www.itead.cc/sonoff-wifi-wireless-switch.html)** in mind but now it supports a growing number of ESP8266-based boards.
It uses the Arduino Core for ESP8266 framework and a number of 3rd party libraries.
**Current Release Version is 1.9.3**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
> **Current Release Version is 1.9.5**, read the [changelog](https://bitbucket.org/xoseperez/espurna/src/master/CHANGELOG.md).
**NOTE**: since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
> **NOTICE**: Default flash layout changed in 1.8.3, as an unpredicted consequence devices will not be able to persist/retrieve configuration if flashed with 1.8.3 via **OTA** from **PlatformIO**. Please check issue #187.
> **NOTICE**: since version 1.9.0 the default **MQTT topics for commands have changed**. They all now end with "/set". This means you will have to change your controller software (Node-RED or alike) to send messages to -for instance- "/home/living/light/relay/0/set". The device will publish its state in "/home/living/light/relay/0" like before.
## Features
@ -95,8 +97,8 @@ Here is the list of supported hardware. For more information please refer to the
|**IteadStudio Sonoff Dual**|**IteadStudio Sonoff POW**|**IteadStudio Sonoff TH10/TH16**|
|![IteadStudio Sonoff 4CH](images/devices/itead-sonoff-4ch.jpg)|![IteadStudio Sonoff 4CH Pro](images/devices/itead-sonoff-4ch-pro.jpg)|![OpenEnergyMonitor WiFi MQTT Relay / Thermostat](images/devices/openenergymonitor-mqtt-relay.jpg)|
|**IteadStudio Sonoff 4CH**|**IteadStudio Sonoff 4CH Pro**|**OpenEnergyMonitor WiFi MQTT Relay / Thermostat**|
|![IteadStudio S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)||
|**IteadStudio S20**|**WorkChoice EcoPlug**||
|![IteadStudio S20](images/devices/itead-s20.jpg)|![WorkChoice EcoPlug](images/devices/workchoice-ecoplug.jpg)|![Power meters based on V9261F and ECH1560](images/devices/generic-v9261f.jpg)|
|**IteadStudio S20**|**WorkChoice EcoPlug**|**Power meters based on V9261F and ECH1560**|
|![IteadStudio Sonoff Touch](images/devices/itead-sonoff-touch.jpg)|![IteadStudio Sonoff T1](images/devices/itead-sonoff-t1.jpg)||
|**IteadStudio Sonoff Touch**|**IteadStudio Sonoff T1**||
|![IteadStudio Slampher](images/devices/itead-slampher.jpg)|![AI-Thinker Wifi Light / Noduino OpenLight](images/devices/aithinker-ai-light.jpg)|![Itead Sonoff B1](images/devices/itead-sonoff-b1.jpg)|


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

@ -54,7 +54,6 @@
//#define DHT_SUPPORT 1
//#define DOMOTICZ_SUPPORT 0
//#define DS18B20_SUPPORT 1
//#define EMON_SUPPORT 1
//#define HOMEASSISTANT_SUPPORT 0
//#define I2C_SUPPORT 1
//#define INFLUXDB_SUPPORT 0


+ 140
- 21
code/espurna/config/general.h View File

@ -262,9 +262,6 @@ PROGMEM const char* const custom_reset_string[] = {
#define WIFI_RECONNECT_INTERVAL 120000 // If could not connect to WIFI, retry after this time in ms
#define WIFI_MAX_NETWORKS 5 // Max number of WIFI connection configurations
#define WIFI_AP_MODE AP_MODE_ALONE
#ifndef WIFI_GAIN
#define WIFI_GAIN 0 // WiFi gain in dBm from 0 (normal) to 20.5 (max power and consumption)
#endif
// Optional hardcoded configuration (up to 2 different networks)
//#define WIFI1_SSID "..."
@ -390,9 +387,11 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_RETAIN true // MQTT retain flag
#define MQTT_QOS 0 // MQTT QoS value for all messages
#define MQTT_KEEPALIVE 30 // MQTT keepalive value
#define MQTT_RECONNECT_DELAY 10000 // Try to reconnect after 10s
#define MQTT_TRY_INTERVAL 30000 // Timeframe for disconnect retries
#define MQTT_MAX_TRIES 5 // After these many retries during the previous MQTT_TRY_INTERVAL the board will reset
#define MQTT_RECONNECT_DELAY_MIN 5000 // Try to reconnect in 5 seconds upon disconnection
#define MQTT_RECONNECT_DELAY_STEP 5000 // Increase the reconnect delay in 5 seconds after each failed attempt
#define MQTT_RECONNECT_DELAY_MAX 120000 // Set reconnect time to 2 minutes at most
#define MQTT_SKIP_RETAINED 1 // Skip retained messages on connection
#define MQTT_SKIP_TIME 1000 // Skip messages for 1 second anter connection
@ -423,7 +422,16 @@ PROGMEM const char* const custom_reset_string[] = {
#define MQTT_TOPIC_RFIN "rfin"
#define MQTT_TOPIC_RFLEARN "rflearn"
// Lights
// Power module
#define MQTT_TOPIC_POWER_ACTIVE "power"
#define MQTT_TOPIC_CURRENT "current"
#define MQTT_TOPIC_VOLTAGE "voltage"
#define MQTT_TOPIC_POWER_APPARENT "apparent"
#define MQTT_TOPIC_POWER_REACTIVE "reactive"
#define MQTT_TOPIC_POWER_FACTOR "factor"
#define MQTT_TOPIC_ENERGY "energy"
// Light module
#define MQTT_TOPIC_CHANNEL "channel"
#define MQTT_TOPIC_COLOR "color"
#define MQTT_TOPIC_BRIGHTNESS "brightness"
@ -454,26 +462,13 @@ PROGMEM const char* const custom_reset_string[] = {
#define SETTINGS_AUTOSAVE 1 // Autosave settings o force manual commit
#endif
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
#ifndef I2C_SUPPORT
#define I2C_SUPPORT 0 // I2C enabled
#endif
#define I2C_SDA_PIN 4 // SDA GPIO
#define I2C_SCL_PIN 14 // SCL GPIO
#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time
#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency
// -----------------------------------------------------------------------------
// LIGHT
// -----------------------------------------------------------------------------
// Available light providers (do not change)
#define LIGHT_PROVIDER_NONE 0
#define LIGHT_PROVIDER_MY9192 1
#define LIGHT_PROVIDER_MY9192 1 // works with MY9231 also (Sonoff B1)
#define LIGHT_PROVIDER_DIMMER 2
// LIGHT_PROVIDER_DIMMER can have from 1 to 5 different channels.
@ -495,6 +490,130 @@ PROGMEM const char* const custom_reset_string[] = {
#define LIGHT_USE_WHITE 0 // Use white channel whenever RGB have the same value
#define LIGHT_USE_GAMMA 0 // Use gamma correction for color channels
// -----------------------------------------------------------------------------
// POWER METERING
// -----------------------------------------------------------------------------
// Available power-metering providers (do not change this)
#define POWER_PROVIDER_NONE 0x00
#define POWER_PROVIDER_EMON_ANALOG 0x10
#define POWER_PROVIDER_EMON_ADC121 0x11
#define POWER_PROVIDER_HLW8012 0x20
#define POWER_PROVIDER_V9261F 0x30
#define POWER_PROVIDER_ECH1560 0x40
// Available magnitudes (do not change this)
#define POWER_MAGNITUDE_CURRENT 1
#define POWER_MAGNITUDE_VOLTAGE 2
#define POWER_MAGNITUDE_ACTIVE 4
#define POWER_MAGNITUDE_APPARENT 8
#define POWER_MAGNITUDE_REACTIVE 16
#define POWER_MAGNITUDE_POWER_FACTOR 32
#define POWER_MAGNITUDE_ALL 63
// No power provider defined (do not change this)
#ifndef POWER_PROVIDER
#define POWER_PROVIDER POWER_PROVIDER_NONE
#endif
// Identify available magnitudes (do not change this)
#if (POWER_PROVIDER == POWER_PROVIDER_HLW8012) || (POWER_PROVIDER == POWER_PROVIDER_V9261F)
#define POWER_HAS_ACTIVE 1
#else
#define POWER_HAS_ACTIVE 0
#endif
#define POWER_VOLTAGE 230 // Default voltage
#define POWER_READ_INTERVAL 6000 // Default reading interval (6 seconds)
#define POWER_REPORT_INTERVAL 60000 // Default report interval (1 minute)
#define POWER_REPORT_BUFFER 12 // Default buffer size ()
#define POWER_CURRENT_DECIMALS 2 // Decimals for current values
#define POWER_VOLTAGE_DECIMALS 0 // Decimals for voltage values
#define POWER_POWER_DECIMALS 0 // Decimals for power values
#define POWER_ENERGY_FACTOR (POWER_REPORT_INTERVAL / 1000.0 / 3600.0)
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG
#define EMON_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A)
#define EMON_SAMPLES 1000 // Number of samples to get for each reading
#define EMON_ADC_BITS 10 // ADC depth
#define EMON_REFERENCE_VOLTAGE 1.0 // Reference voltage of the ADC
#define EMON_CURRENT_OFFSET 0.25 // Current offset (error)
#undef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 0 // Disable internal battery measurement
#endif
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
#define EMON_CURRENT_RATIO 30 // Current ratio in the clamp (30V/1A)
#define EMON_SAMPLES 1000 // Number of samples to get for each reading
#define EMON_ADC_BITS 12 // ADC depth
#define EMON_REFERENCE_VOLTAGE 3.3 // Reference voltage of the ADC
#define EMON_CURRENT_OFFSET 0.10 // Current offset (error)
#define ADC121_I2C_ADDRESS 0x50 // I2C address of the ADC121
#undef I2C_SUPPORT
#define I2C_SUPPORT 1 // Enabled I2C support
#endif
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
#define HLW8012_USE_INTERRUPTS 1 // Use interrupts to trap HLW8012 signals
#define HLW8012_SEL_CURRENT HIGH // SEL pin to HIGH to measure current
#define HLW8012_CURRENT_R 0.001 // Current resistor
#define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Upstream voltage resistor
#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Downstream voltage resistor
#endif
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
#undef POWER_REPORT_BUFFER
#define POWER_REPORT_BUFFER 60 // Override median buffer size
#ifndef V9261F_PIN
#define V9261F_PIN 2 // TX pin from the V9261F
#endif
#ifndef V9261F_PIN_INVERSE
#define V9261F_PIN_INVERSE 1 // Signal is inverted
#endif
#define V9261F_SYNC_INTERVAL 600 // Sync signal length (ms)
#define V9261F_BAUDRATE 4800 // UART baudrate
// Default ratios
#define V9261F_CURRENT_FACTOR 79371434.0
#define V9261F_VOLTAGE_FACTOR 4160651.0
#define V9261F_POWER_FACTOR 153699.0
#define V9261F_RPOWER_FACTOR V9261F_CURRENT_FACTOR
#endif
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560
#undef POWER_REPORT_BUFFER
#define POWER_REPORT_BUFFER 60 // Override median buffer size
#ifndef ECH1560_CLK_PIN
#define ECH1560_CLK_PIN 4 // Default CLK pin
#endif
#ifndef ECH1560_MISO_PIN
#define ECH1560_MISO_PIN 5 // Default MISO pin
#endif
#ifndef ECH1560_INVERTED
#define ECH1560_INVERTED 0 // Power signal is inverted
#endif
#endif
// -----------------------------------------------------------------------------
// I2C
// -----------------------------------------------------------------------------
#ifndef I2C_SUPPORT
#define I2C_SUPPORT 0 // I2C enabled
#endif
#define I2C_SDA_PIN 4 // SDA GPIO
#define I2C_SCL_PIN 14 // SCL GPIO
#define I2C_CLOCK_STRETCH_TIME 200 // BRZO clock stretch time
#define I2C_SCL_FREQUENCY 1000 // BRZO SCL frequency
// -----------------------------------------------------------------------------
// DOMOTICZ
// -----------------------------------------------------------------------------


+ 46
- 3
code/espurna/config/hardware.h View File

@ -71,9 +71,20 @@
// Buttons
#define BUTTON1_PIN 4
#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
#define BUTTON1_RELAY 1
// Normal pushbutton
//#define BUTTON1_MODE BUTTON_PUSHBUTTON | BUTTON_DEFAULT_HIGH
// Touch button
#define BUTTON1_MODE BUTTON_PUSHBUTTON
#define BUTTON1_PRESS BUTTON_MODE_TOGGLE
#define BUTTON1_CLICK BUTTON_MODE_NONE
#define BUTTON1_DBLCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGCLICK BUTTON_MODE_NONE
#define BUTTON1_LNGLNGCLICK BUTTON_MODE_NONE
// Relays
#define RELAY1_PIN 12
#define RELAY1_TYPE RELAY_TYPE_INVERSE
@ -83,7 +94,7 @@
#define LED1_PIN_INVERSE 0
// HLW8012
#define HLW8012_SUPPORT 1
#define POWER_PROVIDER POWER_PROVIDER_HLW8012
#define HLW8012_SEL_PIN 2
#define HLW8012_CF1_PIN 13
#define HLW8012_CF_PIN 14
@ -245,7 +256,7 @@
#define LED1_PIN_INVERSE 0
// HLW8012
#define HLW8012_SUPPORT 1
#define POWER_PROVIDER POWER_PROVIDER_HLW8012
#define HLW8012_SEL_PIN 5
#define HLW8012_CF1_PIN 13
#define HLW8012_CF_PIN 14
@ -416,6 +427,7 @@
#ifndef DUMMY_RELAY_COUNT
#define DUMMY_RELAY_COUNT 6
#endif
#define TERMINAL_SUPPORT 0
#define TRACK_RELAY_STATUS 0
// Buttons
@ -833,6 +845,37 @@
#define LIGHT_CH3_INVERSE 0
#define LIGHT_CH4_INVERSE 0
// -----------------------------------------------------------------------------
// V9261F
// -----------------------------------------------------------------------------
#elif defined(GENERIC_V9261F)
// Info
#define MANUFACTURER "GENERIC"
#define DEVICE "V9261F"
// V9261F
#define POWER_PROVIDER POWER_PROVIDER_V9261F
#define V9261F_PIN 2
#define V9261F_PIN_INVERSE 1
// -----------------------------------------------------------------------------
// ECH1560
// -----------------------------------------------------------------------------
#elif defined(GENERIC_ECH1560)
// Info
#define MANUFACTURER "GENERIC"
#define DEVICE "ECH1560"
// ECH1560
#define POWER_PROVIDER POWER_PROVIDER_ECH1560
#define ECH1560_CLK_PIN 4
#define ECH1560_MISO_PIN 5
#define ECH1560_INVERTED 0
// -----------------------------------------------------------------------------
// Unknown hardware
// -----------------------------------------------------------------------------


+ 0
- 74
code/espurna/config/sensors.h View File

@ -81,80 +81,6 @@
#define DS18B20_UPDATE_INTERVAL 60000
#define DS18B20_TEMPERATURE_TOPIC "temperature"
//--------------------------------------------------------------------------------
// Custom current sensor
// Check http://tinkerman.cat/your-laundry-is-done/
// Check http://tinkerman.cat/power-monitoring-sonoff-th-adc121/
// Enable support by passing EMON_SUPPORT=1 build flag
//--------------------------------------------------------------------------------
#ifndef EMON_SUPPORT
#define EMON_SUPPORT 0
#endif
#define EMON_ANALOG_PROVIDER 0
#define EMON_ADC121_PROVIDER 1
// If you select EMON_ADC121_PROVIDER you need to enable and configure I2C in general.h
#define EMON_PROVIDER EMON_ANALOG_PROVIDER
#if EMON_PROVIDER == EMON_ANALOG_PROVIDER
#define EMON_CURRENT_PIN 0
#define EMON_ADC_BITS 10
#define EMON_REFERENCE_VOLTAGE 1.0
#define EMON_CURRENT_OFFSET 0.25
#if EMON_SUPPORT
#undef ADC_VCC_ENABLED
#define ADC_VCC_ENABLED 0
#endif
#endif
#if EMON_PROVIDER == EMON_ADC121_PROVIDER
#define EMON_ADC121_ADDRESS 0x50
#define EMON_ADC_BITS 12
#define EMON_REFERENCE_VOLTAGE 3.3
#define EMON_CURRENT_OFFSET 0.10
#endif
#define EMON_CURRENT_RATIO 30
#define EMON_SAMPLES 1000
#define EMON_INTERVAL 10000
#define EMON_MEASUREMENTS 6
#define EMON_MAINS_VOLTAGE 230
#define EMON_APOWER_TOPIC "apower"
#define EMON_ENERGY_TOPIC "energy"
#define EMON_CURRENT_TOPIC "current"
//--------------------------------------------------------------------------------
// HLW8012 power sensor (Sonoff POW, Espurna H)
// Enable support by passing HLW8012_SUPPORT=1 build flag
//--------------------------------------------------------------------------------
#ifndef HLW8012_SUPPORT
#define HLW8012_SUPPORT 0
#endif
// GPIOs defined in the hardware.h file
#define HLW8012_USE_INTERRUPTS 1
#define HLW8012_SEL_CURRENT HIGH
#define HLW8012_CURRENT_R 0.001
#define HLW8012_VOLTAGE_R_UP ( 5 * 470000 ) // Real: 2280k
#define HLW8012_VOLTAGE_R_DOWN ( 1000 ) // Real 1.009k
#define HLW8012_POWER_TOPIC "power"
#define HLW8012_CURRENT_TOPIC "current"
#define HLW8012_VOLTAGE_TOPIC "voltage"
#define HLW8012_APOWER_TOPIC "apower"
#define HLW8012_RPOWER_TOPIC "rpower"
#define HLW8012_PFACTOR_TOPIC "pfactor"
#define HLW8012_ENERGY_TOPIC "energy"
#define HLW8012_UPDATE_INTERVAL 5000
#define HLW8012_REPORT_EVERY 12
#define HLW8012_MIN_POWER 5
#define HLW8012_MAX_POWER 2500
#define HLW8012_MIN_CURRENT 0.05
#define HLW8012_MAX_CURRENT 10
//--------------------------------------------------------------------------------
// Internal power montior
// Enable support by passing ADC_VCC_ENABLED=1 build flag


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

@ -1,4 +1,4 @@
#define APP_NAME "ESPURNA"
#define APP_VERSION "1.9.4b"
#define APP_VERSION "1.9.5"
#define APP_AUTHOR "xose.perez@gmail.com"
#define APP_WEBSITE "http://tinkerman.cat"

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


+ 129
- 0
code/espurna/debug.ino View File

@ -87,4 +87,133 @@ void debugSend_P(PGM_P format, ...) {
}
// -----------------------------------------------------------------------------
// Save crash info
// Taken from krzychb EspSaveCrash
// https://github.com/krzychb/EspSaveCrash
// -----------------------------------------------------------------------------
#define SAVE_CRASH_EEPROM_OFFSET 0x0100 // initial address for crash data
/**
* Structure of the single crash data set
*
* 1. Crash time
* 2. Restart reason
* 3. Exception cause
* 4. epc1
* 5. epc2
* 6. epc3
* 7. excvaddr
* 8. depc
* 9. adress of stack start
* 10. adress of stack end
* 11. stack trace bytes
* ...
*/
#define SAVE_CRASH_CRASH_TIME 0x00 // 4 bytes
#define SAVE_CRASH_RESTART_REASON 0x04 // 1 byte
#define SAVE_CRASH_EXCEPTION_CAUSE 0x05 // 1 byte
#define SAVE_CRASH_EPC1 0x06 // 4 bytes
#define SAVE_CRASH_EPC2 0x0A // 4 bytes
#define SAVE_CRASH_EPC3 0x0E // 4 bytes
#define SAVE_CRASH_EXCVADDR 0x12 // 4 bytes
#define SAVE_CRASH_DEPC 0x16 // 4 bytes
#define SAVE_CRASH_STACK_START 0x1A // 4 bytes
#define SAVE_CRASH_STACK_END 0x1E // 4 bytes
#define SAVE_CRASH_STACK_TRACE 0x22 // variable
/**
* Save crash information in EEPROM
* This function is called automatically if ESP8266 suffers an exception
* It should be kept quick / consise to be able to execute before hardware wdt may kick in
*/
extern "C" void custom_crash_callback(struct rst_info * rst_info, uint32_t stack_start, uint32_t stack_end ) {
// This method assumes EEPROM has already been initialized
// which is the first thing ESPurna does
// write crash time to EEPROM
uint32_t crash_time = millis();
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
// 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);
// 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);
// 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);
// write stack trace to EEPROM
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
for (uint32_t i = stack_start; i < stack_end; i++) {
byte* byteValue = (byte*) i;
EEPROM.write(current_address++, *byteValue);
}
EEPROM.commit();
}
/**
* Clears crash info
*/
void debugClearCrashInfo() {
uint32_t crash_time = 0xFFFFFFFF;
EEPROM.put(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
EEPROM.commit();
}
/**
* Print out crash information that has been previusly saved in EEPROM
*/
void debugDumpCrashInfo() {
uint32_t crash_time;
EEPROM.get(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_CRASH_TIME, crash_time);
if ((crash_time == 0) || (crash_time == 0xFFFFFFFF)) {
DEBUG_MSG_P(PSTR("[DEBUG] No crash info\n"));
return;
}
DEBUG_MSG_P(PSTR("[DEBUG] Crash at %ld ms\n"), crash_time);
DEBUG_MSG_P(PSTR("[DEBUG] Reason of restart: %d\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_RESTART_REASON));
DEBUG_MSG_P(PSTR("[DEBUG] Exception cause: %d\n"), EEPROM.read(SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_EXCEPTION_CAUSE));
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);
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);
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);
DEBUG_MSG_P(PSTR("[DEBUG] >>>stack>>>\n[DEBUG] "));
int16_t current_address = SAVE_CRASH_EEPROM_OFFSET + SAVE_CRASH_STACK_TRACE;
int16_t stack_len = stack_end - stack_start;
uint32_t stack_trace;
for (int16_t i = 0; i < stack_len; i += 0x10) {
DEBUG_MSG_P(PSTR("%08x: "), stack_start + i);
for (byte j = 0; j < 4; j++) {
EEPROM.get(current_address, stack_trace);
DEBUG_MSG_P(PSTR("%08x "), stack_trace);
current_address += 4;
}
DEBUG_MSG_P(PSTR("\n[DEBUG] "));
}
DEBUG_MSG_P(PSTR("<<<stack<<<\n"));
}
#endif // DEBUG_SUPPORT

+ 2
- 2
code/espurna/dht.ino View File

@ -34,7 +34,7 @@ void dhtSetup() {
#if WEB_SUPPORT
apiRegister(DHT_TEMPERATURE_TOPIC, DHT_TEMPERATURE_TOPIC, [](char * buffer, size_t len) {
dtostrf(_dhtTemperature, len-1, 1, buffer);
dtostrf(_dhtTemperature, 1-len, 1, buffer);
});
apiRegister(DHT_HUMIDITY_TOPIC, DHT_HUMIDITY_TOPIC, [](char * buffer, size_t len) {
snprintf_P(buffer, len, PSTR("%d"), _dhtHumidity);
@ -68,7 +68,7 @@ void dhtLoop() {
char temperature[6];
char humidity[6];
dtostrf(t, 4, 1, temperature);
dtostrf(t, 1-sizeof(temperature), 1, temperature);
itoa((unsigned int) h, humidity, 10);
DEBUG_MSG_P(PSTR("[DHT] Temperature: %s%s\n"), temperature, (tmpUnits == TMP_CELSIUS) ? "ºC" : "ºF");


+ 9
- 5
code/espurna/domoticz.ino View File

@ -10,7 +10,7 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ArduinoJson.h>
bool _dczEnabled = false;
bool _dcz_enabled = false;
//------------------------------------------------------------------------------
// Private methods
@ -35,7 +35,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
if (type == MQTT_MESSAGE_EVENT) {
if (!_dczEnabled) return;
if (!_dcz_enabled) return;
// Check topic
if (dczTopicOut.equals(topic)) {
@ -68,7 +68,7 @@ void _domoticzMqtt(unsigned int type, const char * topic, const char * payload)
//------------------------------------------------------------------------------
template<typename T> void domoticzSend(const char * key, T nvalue, const char * svalue) {
if (!_dczEnabled) return;
if (!_dcz_enabled) return;
unsigned int idx = getSetting(key).toInt();
if (idx > 0) {
char payload[128];
@ -82,7 +82,7 @@ template<typename T> void domoticzSend(const char * key, T nvalue) {
}
void domoticzSendRelay(unsigned int relayID) {
if (!_dczEnabled) return;
if (!_dcz_enabled) return;
char buffer[15];
snprintf_P(buffer, sizeof(buffer), PSTR("dczRelayIdx%d"), relayID);
domoticzSend(buffer, relayStatus(relayID) ? "1" : "0");
@ -95,7 +95,7 @@ int domoticzIdx(unsigned int relayID) {
}
void domoticzConfigure() {
_dczEnabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
_dcz_enabled = getSetting("dczEnabled", DOMOTICZ_ENABLED).toInt() == 1;
}
void domoticzSetup() {
@ -103,4 +103,8 @@ void domoticzSetup() {
mqttRegister(_domoticzMqtt);
}
bool domoticzEnabled() {
return _dcz_enabled;
}
#endif

+ 2
- 2
code/espurna/ds18b20.ino View File

@ -40,7 +40,7 @@ void dsSetup() {
#if WEB_SUPPORT
apiRegister(DS18B20_TEMPERATURE_TOPIC, DS18B20_TEMPERATURE_TOPIC, [](char * buffer, size_t len) {
dtostrf(_dsTemperature, len-1, 1, buffer);
dtostrf(_dsTemperature, 1-len, 1, buffer);
});
#endif
@ -89,7 +89,7 @@ void dsLoop() {
else
_dsIsConnected = true;
dtostrf(t, 5, 1, _dsTemperatureStr);
dtostrf(t, 1-sizeof(_dsTemperatureStr), 1, _dsTemperatureStr);
DEBUG_MSG_P(PSTR("[DS18B20] Temperature: %s%s\n"),
getDSTemperatureStr(),


+ 0
- 217
code/espurna/emon.ino View File

@ -1,217 +0,0 @@
/*
EMON MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if EMON_SUPPORT
#include <EmonLiteESP.h>
#include <EEPROM.h>
#if EMON_PROVIDER == EMON_ADC121_PROVIDER
#include "brzo_i2c.h"
#endif
// ADC121 Registers
#define ADC121_REG_RESULT 0x00
#define ADC121_REG_ALERT 0x01
#define ADC121_REG_CONFIG 0x02
#define ADC121_REG_LIMITL 0x03
#define ADC121_REG_LIMITH 0x04
#define ADC121_REG_HYST 0x05
#define ADC121_REG_CONVL 0x06
#define ADC121_REG_CONVH 0x07
EmonLiteESP emon;
bool _emonReady = false;
double _emonCurrent = 0;
unsigned int _emonPower = 0;
unsigned int _emonVoltage = 0;
// -----------------------------------------------------------------------------
// Provider
// -----------------------------------------------------------------------------
unsigned int currentCallback() {
#if EMON_PROVIDER == EMON_ANALOG_PROVIDER
return analogRead(EMON_CURRENT_PIN);
#endif
#if EMON_PROVIDER == EMON_ADC121_PROVIDER
uint8_t buffer[2];
brzo_i2c_start_transaction(EMON_ADC121_ADDRESS, I2C_SCL_FREQUENCY);
buffer[0] = ADC121_REG_RESULT;
brzo_i2c_write(buffer, 1, false);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
unsigned int value;
value = (buffer[0] & 0x0F) << 8;
value |= buffer[1];
return value;
#endif
}
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
void setCurrentRatio(float value) {
emon.setCurrentRatio(value);
}
unsigned int getApparentPower() {
return int(getCurrent() * getVoltage());
}
double getCurrent() {
double current = emon.getCurrent(EMON_SAMPLES);
current -= EMON_CURRENT_OFFSET;
if (current < 0) current = 0;
return current;
}
unsigned int getVoltage() {
return getSetting("emonVoltage", EMON_MAINS_VOLTAGE).toInt();
}
// -----------------------------------------------------------------------------
void powerMonitorSetup() {
// backwards compatibility
String tmp;
moveSetting("pwMainsVoltage", "emonVoltage");
moveSetting("emonMains", "emonVoltage");
moveSetting("pwCurrentRatio", "emonRatio");
emon.initCurrent(
currentCallback,
EMON_ADC_BITS,
EMON_REFERENCE_VOLTAGE,
getSetting("emonRatio", EMON_CURRENT_RATIO).toFloat()
);
#if EMON_PROVIDER == EMON_ADC121_PROVIDER
uint8_t buffer[2];
buffer[0] = ADC121_REG_CONFIG;
buffer[1] = 0x00;
brzo_i2c_start_transaction(EMON_ADC121_ADDRESS, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 2, false);
brzo_i2c_end_transaction();
#endif
#if WEB_SUPPORT
apiRegister(EMON_APOWER_TOPIC, EMON_APOWER_TOPIC, [](char * buffer, size_t len) {
if (_emonReady) {
snprintf_P(buffer, len, PSTR("%d"), _emonPower);
} else {
buffer = NULL;
}
});
apiRegister(EMON_CURRENT_TOPIC, EMON_CURRENT_TOPIC, [](char * buffer, size_t len) {
if (_emonReady) {
dtostrf(_emonCurrent, len-1, 3, buffer);
} else {
buffer = NULL;
}
});
#endif // WEB_SUPPORT
}
void powerMonitorLoop() {
static unsigned long next_measurement = millis();
static bool warmup = true;
static byte measurements = 0;
static double max = 0;
static double min = 0;
static double sum = 0;
if (warmup) {
warmup = false;
emon.warmup();
}
if (millis() > next_measurement) {
int voltage = getVoltage();
{
double current = getCurrent();
if (measurements == 0) {
max = min = current;
} else {
if (_emonCurrent > max) max = current;
if (_emonCurrent < min) min = current;
}
sum += current;
++measurements;
DEBUG_MSG_P(PSTR("[ENERGY] Current: %sA\n"), String(current, 3).c_str());
DEBUG_MSG_P(PSTR("[ENERGY] Power: %dW\n"), int(current * voltage));
// Update websocket clients
#if WEB_SUPPORT
char buffer[100];
snprintf_P(buffer, sizeof(buffer), PSTR("{\"emonVisible\": 1, \"emonApparentPower\": %d, \"emonCurrent\": %s}"), int(current * voltage), String(current, 3).c_str());
wsSend(buffer);
#endif
}
// Send MQTT messages averaged every EMON_MEASUREMENTS
if (measurements == EMON_MEASUREMENTS) {
// Calculate average current (removing max and min values)
_emonCurrent = (sum - max - min) / (measurements - 2);
_emonPower = (int) (_emonCurrent * voltage);
_emonReady = true;
// Calculate energy increment (ppower times time)
double energy_delta = (double) _emonPower * EMON_INTERVAL * EMON_MEASUREMENTS / 1000.0 / 3600.0;
// Report values to MQTT broker
mqttSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), String(_emonPower).c_str());
mqttSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), String(_emonCurrent, 3).c_str());
mqttSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
// Report values to Domoticz
#if DOMOTICZ_SUPPORT
{
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), _emonPower, String(energy_delta, 3).c_str());
domoticzSend("dczPowIdx", 0, buffer);
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(energy_delta, 3).c_str());
domoticzSend("dczEnergyIdx", 0, buffer);
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(_emonCurrent, 3).c_str());
domoticzSend("dczCurrentIdx", 0, buffer);
}
#endif
#if INFLUXDB_SUPPORT
influxDBSend(getSetting("emonPowerTopic", EMON_APOWER_TOPIC).c_str(), _emonPower);
influxDBSend(getSetting("emonCurrTopic", EMON_CURRENT_TOPIC).c_str(), String(_emonCurrent, 3).c_str());
influxDBSend(getSetting("emonEnergyTopic", EMON_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
#endif
// Reset counters
sum = measurements = 0;
}
next_measurement += EMON_INTERVAL;
}
}
#endif

+ 12
- 20
code/espurna/espurna.ino View File

@ -155,12 +155,6 @@ void welcome() {
#if DS18B20_SUPPORT
DEBUG_MSG_P(PSTR(" DS18B20"));
#endif
#if EMON_SUPPORT
DEBUG_MSG_P(PSTR(" EMON"));
#endif
#if HLW8012_SUPPORT
DEBUG_MSG_P(PSTR(" HLW8012"));
#endif
#if HOMEASSISTANT_SUPPORT
DEBUG_MSG_P(PSTR(" HOMEASSISTANT"));
#endif
@ -204,7 +198,11 @@ void welcome() {
} else {
DEBUG_MSG_P(PSTR("[INIT] Last reset reason: %s\n"), (char *) ESP.getResetReason().c_str());
}
DEBUG_MSG_P(PSTR("[INIT] Free heap: %u bytes\n"), ESP.getFreeHeap());
#if ADC_VCC_ENABLED
DEBUG_MSG_P(PSTR("[INIT] Power: %d mV\n"), ESP.getVcc());
#endif
DEBUG_MSG_P(PSTR("\n"));
}
@ -224,10 +222,8 @@ void setup() {
settingsSetup();
if (getSetting("hostname").length() == 0) {
setSetting("hostname", getIdentifier());
saveSettings();
}
delay(500);
wifiSetup();
otaSetup();
#if TELNET_SUPPORT
@ -253,6 +249,9 @@ void setup() {
rfbSetup();
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
powerSetup();
#endif
#if NTP_SUPPORT
ntpSetup();
#endif
@ -268,9 +267,6 @@ void setup() {
#if INFLUXDB_SUPPORT
influxDBSetup();
#endif
#if HLW8012_SUPPORT
hlw8012Setup();
#endif
#if DS18B20_SUPPORT
dsSetup();
#endif
@ -286,9 +282,6 @@ void setup() {
#if RF_SUPPORT
rfSetup();
#endif
#if EMON_SUPPORT
powerMonitorSetup();
#endif
#if DOMOTICZ_SUPPORT
domoticzSetup();
#endif
@ -296,6 +289,8 @@ void setup() {
// Prepare configuration for version 2.0
hwUpwardsCompatibility();
saveSettings();
}
void loop() {
@ -317,6 +312,9 @@ void loop() {
rfbLoop();
#endif
#if POWER_PROVIDER != POWER_PROVIDER_NONE
powerLoop();
#endif
#if NTP_SUPPORT
ntpLoop();
#endif
@ -326,9 +324,6 @@ void loop() {
#if NOFUSS_SUPPORT
nofussLoop();
#endif
#if HLW8012_SUPPORT
hlw8012Loop();
#endif
#if DS18B20_SUPPORT
dsLoop();
#endif
@ -344,8 +339,5 @@ void loop() {
#if RF_SUPPORT
rfLoop();
#endif
#if EMON_SUPPORT
powerMonitorLoop();
#endif
}

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

@ -284,7 +284,7 @@ void hwUpwardsCompatibility() {
setSetting("chLogic", 5, 0);
setSetting("relays", 1);
#elif defined(ITEAD_BN_SZ01)
#elif defined(ITEAD_BNSZ01)
setSetting("board", 25);
setSetting("relayProvider", RELAY_PROVIDER_LIGHT);
@ -434,6 +434,14 @@ void hwUpwardsCompatibility() {
setSetting("chLogic", 4, 0);
setSetting("relays", 1);
#elif defined(GENERIC_V9261F)
setSetting("board", 37);
#elif defined(GENERIC_ECH1560)
setSetting("board", 38);
#else
#error "UNSUPPORTED HARDWARE!"


+ 0
- 344
code/espurna/hlw8012.ino View File

@ -1,344 +0,0 @@
/*
POW MODULE
Support for Sonoff POW HLW8012-based power monitor
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if HLW8012_SUPPORT
#include <HLW8012.h>
#include <Hash.h>
#include <ArduinoJson.h>
HLW8012 hlw8012;
bool _hlw8012Enabled = false;
bool _hlwReady = false;
int _hlwPower = 0;
double _hlwCurrent = 0;
int _hlwVoltage = 0;
// -----------------------------------------------------------------------------
// POW
// -----------------------------------------------------------------------------
// When using interrupts we have to call the library entry point
// whenever an interrupt is triggered
void ICACHE_RAM_ATTR hlw8012_cf1_interrupt() {
hlw8012.cf1_interrupt();
}
void ICACHE_RAM_ATTR hlw8012_cf_interrupt() {
hlw8012.cf_interrupt();
}
void hlw8012Enable(bool status) {
_hlw8012Enabled = status;
if (_hlw8012Enabled) {
#if HLW8012_USE_INTERRUPTS == 1
attachInterrupt(HLW8012_CF1_PIN, hlw8012_cf1_interrupt, CHANGE);
attachInterrupt(HLW8012_CF_PIN, hlw8012_cf_interrupt, CHANGE);
#endif
DEBUG_MSG_P(PSTR("[POW] Enabled\n"));
} else {
#if HLW8012_USE_INTERRUPTS == 1
detachInterrupt(HLW8012_CF1_PIN);
detachInterrupt(HLW8012_CF_PIN);
#endif
DEBUG_MSG_P(PSTR("[POW] Disabled\n"));
}
}
// -----------------------------------------------------------------------------
void hlw8012SaveCalibration() {
setSetting("powPowerMult", hlw8012.getPowerMultiplier());
setSetting("powCurrentMult", hlw8012.getCurrentMultiplier());
setSetting("powVoltageMult", hlw8012.getVoltageMultiplier());
saveSettings();
}
void hlw8012RetrieveCalibration() {
double value;
value = getSetting("powPowerMult", 0).toFloat();
if (value > 0) hlw8012.setPowerMultiplier(value);
value = getSetting("powCurrentMult", 0).toFloat();
if (value > 0) hlw8012.setCurrentMultiplier(value);
value = getSetting("powVoltageMult", 0).toFloat();
if (value > 0) hlw8012.setVoltageMultiplier(value);
}
void hlw8012SetExpectedActivePower(unsigned int power) {
if (power > 0) {
hlw8012.expectedActivePower(power);
hlw8012SaveCalibration();
}
}
void hlw8012SetExpectedCurrent(double current) {
if (current > 0) {
hlw8012.expectedCurrent(current);
hlw8012SaveCalibration();
}
}
void hlw8012SetExpectedVoltage(unsigned int voltage) {
if (voltage > 0) {
hlw8012.expectedVoltage(voltage);
hlw8012SaveCalibration();
}
}
void hlw8012Reset() {
hlw8012.resetMultipliers();
hlw8012SaveCalibration();
}
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
unsigned int getActivePower() {
unsigned int power = hlw8012.getActivePower();
if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0;
return power;
}
unsigned int getApparentPower() {
unsigned int power = hlw8012.getApparentPower();
if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0;
return power;
}
unsigned int getReactivePower() {
unsigned int power = hlw8012.getReactivePower();
if (HLW8012_MIN_POWER > power || power > HLW8012_MAX_POWER) power = 0;
return power;
}
double getCurrent() {
double current = hlw8012.getCurrent();
if (HLW8012_MIN_CURRENT > current || current > HLW8012_MAX_CURRENT) current = 0;
return current;
}
unsigned int getVoltage() {
return hlw8012.getVoltage();
}
double getPowerFactor() {
return hlw8012.getPowerFactor();
}
// -----------------------------------------------------------------------------
void hlw8012Setup() {
// Initialize HLW8012
// void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
// * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
// * currentWhen is the value in sel_pin to select current sampling
// * set use_interrupts to true to use interrupts to monitor pulse widths
// * leave pulse_timeout to the default value, recommended when using interrupts
#if HLW8012_USE_INTERRUPTS
hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, true);
#else
hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, false, 1000000);
#endif
// These values are used to calculate current, voltage and power factors as per datasheet formula
// These are the nominal values for the Sonoff POW resistors:
// * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
// * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
// * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
// Retrieve calibration values
hlw8012RetrieveCalibration();
// API definitions
#if WEB_SUPPORT
apiRegister(HLW8012_POWER_TOPIC, HLW8012_POWER_TOPIC, [](char * buffer, size_t len) {
if (_hlwReady) {
snprintf_P(buffer, len, PSTR("%d"), _hlwPower);
} else {
buffer = NULL;
}
});
apiRegister(HLW8012_CURRENT_TOPIC, HLW8012_CURRENT_TOPIC, [](char * buffer, size_t len) {
if (_hlwReady) {
dtostrf(_hlwCurrent, len-1, 3, buffer);
} else {
buffer = NULL;
}
});
apiRegister(HLW8012_VOLTAGE_TOPIC, HLW8012_VOLTAGE_TOPIC, [](char * buffer, size_t len) {
if (_hlwReady) {
snprintf_P(buffer, len, PSTR("%d"), _hlwVoltage);
} else {
buffer = NULL;
}
});
#endif // WEB_SUPPORT
}
void hlw8012Loop() {
static unsigned long last_update = 0;
static unsigned char report_count = HLW8012_REPORT_EVERY;
static bool power_spike = false;
static unsigned long power_sum = 0;
static unsigned long power_previous = 0;
static bool current_spike = false;
static double current_sum = 0;
static double current_previous = 0;
static bool voltage_spike = false;
static unsigned long voltage_sum = 0;
static unsigned long voltage_previous = 0;
static bool powWasEnabled = false;
// POW is disabled while there is no internet connection
// When the HLW8012 measurements are enabled back we reset the timer
if (!_hlw8012Enabled) {
powWasEnabled = false;
return;
}
if (!powWasEnabled) {
last_update = millis();
powWasEnabled = true;
}
if (millis() - last_update > HLW8012_UPDATE_INTERVAL) {
last_update = millis();
unsigned int power = getActivePower();
unsigned int voltage = getVoltage();
double current = getCurrent();
if (power > 0) {
power_spike = (power_previous == 0);
} else if (power_spike) {
power_sum -= power_previous;
power_spike = false;
}
power_previous = power;
if (current > 0) {
current_spike = (current_previous == 0);
} else if (current_spike) {
current_sum -= current_previous;
current_spike = false;
}
current_previous = current;
if (voltage > 0) {
voltage_spike = (voltage_previous == 0);
} else if (voltage_spike) {
voltage_sum -= voltage_previous;
voltage_spike = false;
}
voltage_previous = voltage;
#if WEB_SUPPORT
{
unsigned int apparent = getApparentPower();
double factor = getPowerFactor();
unsigned int reactive = getReactivePower();
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["powVisible"] = 1;
root["powActivePower"] = power;
root["powCurrent"] = String(current, 3);
root["powVoltage"] = voltage;
root["powApparentPower"] = apparent;
root["powReactivePower"] = reactive;
root["powPowerFactor"] = String(factor, 2);
String output;
root.printTo(output);
wsSend(output.c_str());
}
#endif
if (--report_count == 0) {
// Update globals
_hlwPower = power_sum / HLW8012_REPORT_EVERY;
_hlwCurrent = current_sum / HLW8012_REPORT_EVERY;
_hlwVoltage = voltage_sum / HLW8012_REPORT_EVERY;
_hlwReady = true;
// Calculate subproducts (apparent and reactive power, power factor and delta energy)
unsigned int apparent = _hlwCurrent * _hlwVoltage;
unsigned int reactive = (apparent > _hlwPower) ? sqrt(apparent * apparent - _hlwPower * _hlwPower) : 0;
double factor = (apparent > 0) ? (double) _hlwPower / apparent : 1;
if (factor > 1) factor = 1;
double energy_delta = (double) _hlwPower * HLW8012_REPORT_EVERY * HLW8012_UPDATE_INTERVAL / 1000.0 / 3600.0;
// Report values to MQTT broker
mqttSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
mqttSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
mqttSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
mqttSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
mqttSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
mqttSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
mqttSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
// Report values to Domoticz
#if DOMOTICZ_SUPPORT
{
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), _hlwPower, String(energy_delta, 3).c_str());
domoticzSend("dczPowIdx", 0, buffer);
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(energy_delta, 3).c_str());
domoticzSend("dczEnergyIdx", 0, buffer);
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _hlwVoltage);
domoticzSend("dczVoltIdx", 0, buffer);
snprintf_P(buffer, sizeof(buffer), PSTR("%s"), String(_hlwCurrent).c_str());
domoticzSend("dczCurrentIdx", 0, buffer);
}
#endif
#if INFLUXDB_SUPPORT
influxDBSend(getSetting("powPowerTopic", HLW8012_POWER_TOPIC).c_str(), String(_hlwPower).c_str());
influxDBSend(getSetting("powCurrentTopic", HLW8012_CURRENT_TOPIC).c_str(), String(_hlwCurrent, 3).c_str());
influxDBSend(getSetting("powVoltageTopic", HLW8012_VOLTAGE_TOPIC).c_str(), String(_hlwVoltage).c_str());
influxDBSend(getSetting("powEnergyTopic", HLW8012_ENERGY_TOPIC).c_str(), String(energy_delta, 3).c_str());
influxDBSend(getSetting("powAPowerTopic", HLW8012_APOWER_TOPIC).c_str(), String(apparent).c_str());
influxDBSend(getSetting("powRPowerTopic", HLW8012_RPOWER_TOPIC).c_str(), String(reactive).c_str());
influxDBSend(getSetting("powPFactorTopic", HLW8012_PFACTOR_TOPIC).c_str(), String(factor, 2).c_str());
#endif
// Reset counters
power_sum = current_sum = voltage_sum = 0;
report_count = HLW8012_REPORT_EVERY;
}
// Post - Accumulators
power_sum += power_previous;
current_sum += current_previous;
voltage_sum += voltage_previous;
// Toggle between current and voltage monitoring
#if HLW8012_USE_INTERRUPTS == 0
hlw8012.toggleMode();
#endif
}
}
#endif

+ 15
- 12
code/espurna/influxdb.ino View File

@ -11,18 +11,18 @@ Copyright (C) 2017 by Xose Pérez <xose dot perez at gmail dot com>
#include "ESPAsyncTCP.h"
#include "SyncClient.h"
bool influxDBEnabled = false;
SyncClient _influxClient;
bool _influxdb_enabled = false;
SyncClient _influx_client;
template<typename T> bool influxDBSend(const char * topic, T payload) {
if (!influxDBEnabled) return true;
if (!_influxdb_enabled) return true;
if (!wifiConnected() || (WiFi.getMode() != WIFI_STA)) return true;
DEBUG_MSG("[INFLUXDB] Sending\n");
_influxClient.setTimeout(2);
if (!_influxClient.connect(getSetting("idbHost").c_str(), getSetting("idbPort", INFLUXDB_PORT).toInt())) {
_influx_client.setTimeout(2);
if (!_influx_client.connect(getSetting("idbHost").c_str(), getSetting("idbPort", INFLUXDB_PORT).toInt())) {
DEBUG_MSG("[INFLUXDB] Connection failed\n");
return false;
}
@ -37,22 +37,25 @@ template<typename T> bool influxDBSend(const char * topic, T payload) {
getSetting("idbHost").c_str(), getSetting("idbPort", INFLUXDB_PORT).toInt(),
strlen(data), data);
if (_influxClient.printf(request) > 0) {
while (_influxClient.connected() && _influxClient.available() == 0) delay(1);
while (_influxClient.available()) _influxClient.read();
if (_influxClient.connected()) _influxClient.stop();
if (_influx_client.printf(request) > 0) {
while (_influx_client.connected() && _influx_client.available() == 0) delay(1);
while (_influx_client.available()) _influx_client.read();
if (_influx_client.connected()) _influx_client.stop();
return true;
}
_influxClient.stop();
_influx_client.stop();
DEBUG_MSG("[INFLUXDB] Sent failed\n");
while (_influxClient.connected()) delay(0);
while (_influx_client.connected()) delay(0);
return false;
}
bool influxdbEnabled() {
return _influxdb_enabled;
}
void influxDBConfigure() {
influxDBEnabled = getSetting("idbHost").length() > 0;
_influxdb_enabled = getSetting("idbHost").length() > 0;
}
void influxDBSetup() {


+ 103
- 116
code/espurna/mqtt.ino View File

@ -31,7 +31,8 @@ WiFiClientSecure _mqtt_client_secure;
#endif // MQTT_USE_ASYNC
bool _mqtt_enabled = MQTT_ENABLED;
unsigned char _mqtt_connection_tries = 0;
bool _mqtt_use_json = false;
unsigned long _mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
String _mqtt_topic;
String _mqtt_setter;
String _mqtt_getter;
@ -135,7 +136,7 @@ void _mqttFlush() {
}
void mqttSend(const char * topic, const char * message, bool force) {
bool useJson = force ? false : getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1;
bool useJson = force ? false : _mqtt_use_json;
if (useJson) {
mqtt_message_t element;
element.topic = strdup(topic);
@ -216,6 +217,7 @@ void _mqttCallback(unsigned int type, const char * topic, const char * payload)
void _mqttOnConnect() {
DEBUG_MSG_P(PSTR("[MQTT] Connected!\n"));
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
#if MQTT_SKIP_RETAINED
_mqtt_connected_at = millis();
@ -316,137 +318,134 @@ bool mqttEnabled() {
void mqttConnect() {
if (_mqtt_enabled & !_mqtt.connected()) {
// Disable MQTT after MQTT_MAX_TRIES attemps in a row
#if MQTT_MAX_TRIES > 0
static unsigned long last_try = millis();
if (millis() - last_try < MQTT_TRY_INTERVAL) {
if (++_mqtt_connection_tries > MQTT_MAX_TRIES) {
DEBUG_MSG_P(PSTR("[MQTT] MQTT_MAX_TRIES met, disabling MQTT\n"));
mqttEnabled(false);
_mqtt_connection_tries = 0;
return;
}
} else {
_mqtt_connection_tries = 0;
}
last_try = millis();
#endif
// Do not connect if disabled
if (!_mqtt_enabled) return;
if (_mqtt_user) free(_mqtt_user);
if (_mqtt_pass) free(_mqtt_pass);
// Do not connect if already connected
if (_mqtt.connected()) return;
char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
if (strlen(host) == 0) return;
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
_mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
_mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
if (_mqtt_will) free(_mqtt_will);
_mqtt_will = strdup((_mqtt_topic + MQTT_TOPIC_STATUS).c_str());
// Check reconnect interval
static unsigned long last = 0;
if (millis() - last < _mqtt_reconnect_delay) return;
last = millis();
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
// Increase the reconnect delay
_mqtt_reconnect_delay += MQTT_RECONNECT_DELAY_STEP;
if (_mqtt_reconnect_delay > MQTT_RECONNECT_DELAY_MAX) {
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MAX;
}
#if MQTT_USE_ASYNC
char * host = strdup(getSetting("mqttServer", MQTT_SERVER).c_str());
if (strlen(host) == 0) return;
unsigned int port = getSetting("mqttPort", MQTT_PORT).toInt();
_mqtt.setServer(host, port);
_mqtt.setKeepAlive(MQTT_KEEPALIVE).setCleanSession(false);
_mqtt.setWill(_mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
_mqtt.setCredentials(_mqtt_user, _mqtt_pass);
}
if (_mqtt_user) free(_mqtt_user);
if (_mqtt_pass) free(_mqtt_pass);
if (_mqtt_will) free(_mqtt_will);
#if ASYNC_TCP_SSL_ENABLED
_mqtt_user = strdup(getSetting("mqttUser", MQTT_USER).c_str());
_mqtt_pass = strdup(getSetting("mqttPassword", MQTT_PASS).c_str());
_mqtt_will = strdup((_mqtt_topic + MQTT_TOPIC_STATUS).c_str());
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
_mqtt.setSecure(secure);
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
unsigned char fp[20] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
_mqtt.addServerFingerprint(fp);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
}
DEBUG_MSG_P(PSTR("[MQTT] Connecting to broker at %s:%d\n"), host, port);
#if MQTT_USE_ASYNC
_mqtt.setServer(host, port);
_mqtt.setKeepAlive(MQTT_KEEPALIVE).setCleanSession(false);
_mqtt.setWill(_mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
_mqtt.setCredentials(_mqtt_user, _mqtt_pass);
}
#if ASYNC_TCP_SSL_ENABLED
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
_mqtt.setSecure(secure);
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
unsigned char fp[20] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
_mqtt.addServerFingerprint(fp);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
}
}
#endif // ASYNC_TCP_SSL_ENABLED
#endif // ASYNC_TCP_SSL_ENABLED
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), MQTT_QOS);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), MQTT_RETAIN);
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), MQTT_QOS);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), MQTT_RETAIN);
_mqtt.connect();
_mqtt.connect();
#else // not MQTT_USE_ASYNC
#else // not MQTT_USE_ASYNC
bool response = true;
bool response = true;
#if ASYNC_TCP_SSL_ENABLED
#if ASYNC_TCP_SSL_ENABLED
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
if (_mqtt_client_secure.connect(host, port)) {
char fp[60] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (_mqtt_client_secure.verify(fp, host)) {
_mqtt.setClient(_mqtt_client_secure);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n"));
response = false;
}
_mqtt_client_secure.stop();
yield();
bool secure = getSetting("mqttUseSSL", MQTT_SSL_ENABLED).toInt() == 1;
if (secure) {
DEBUG_MSG_P(PSTR("[MQTT] Using SSL\n"));
if (_mqtt_client_secure.connect(host, port)) {
char fp[60] = {0};
if (mqttFormatFP(getSetting("mqttFP", MQTT_SSL_FINGERPRINT).c_str(), fp)) {
if (_mqtt_client_secure.verify(fp, host)) {
_mqtt.setClient(_mqtt_client_secure);
} else {
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
DEBUG_MSG_P(PSTR("[MQTT] Invalid fingerprint\n"));
response = false;
}
_mqtt_client_secure.stop();
yield();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n"));
DEBUG_MSG_P(PSTR("[MQTT] Wrong fingerprint\n"));
response = false;
}
} else {
_mqtt.setClient(_mqtt_client);
DEBUG_MSG_P(PSTR("[MQTT] Client connection failed\n"));
response = false;
}
#else // not ASYNC_TCP_SSL_ENABLED
} else {
_mqtt.setClient(_mqtt_client);
}
#endif // ASYNC_TCP_SSL_ENABLED
if (response) {
#else // not ASYNC_TCP_SSL_ENABLED
_mqtt.setServer(host, port);
_mqtt.setClient(_mqtt_client);
if ((strlen(_mqtt_user) > 0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
response = _mqtt.connect(getIdentifier().c_str(), _mqtt_user, _mqtt_pass, _mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
} else {
response = _mqtt.connect(getIdentifier().c_str(), _mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
}
#endif // ASYNC_TCP_SSL_ENABLED
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), MQTT_QOS);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), MQTT_RETAIN);
if (response) {
}
_mqtt.setServer(host, port);
if (response) {
_mqttOnConnect();
_mqtt_connected = true;
if ((strlen(_mqtt_user) >> <span class="mi">0) && (strlen(_mqtt_pass) > 0)) {
DEBUG_MSG_P(PSTR("[MQTT] Connecting as user %s\n"), _mqtt_user);
response = _mqtt.connect(getIdentifier().c_str(), _mqtt_user, _mqtt_pass, _mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
} else {
DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
response = _mqtt.connect(getIdentifier().c_str(), _mqtt_will, MQTT_QOS, MQTT_RETAIN, "0");
}
#endif // MQTT_USE_ASYNC
DEBUG_MSG_P(PSTR("[MQTT] Will topic: %s\n"), _mqtt_will);
DEBUG_MSG_P(PSTR("[MQTT] QoS: %d\n"), MQTT_QOS);
DEBUG_MSG_P(PSTR("[MQTT] Retain flag: %d\n"), MQTT_RETAIN);
free(host);
}
}
if (response) {
_mqttOnConnect();
} else {
DEBUG_MSG_P(PSTR("[MQTT] Connection failed\n"));
}
#endif // MQTT_USE_ASYNC
free(host);
}
@ -463,12 +462,14 @@ void mqttConfigure() {
_mqtt_forward = !_mqtt_getter.equals(_mqtt_setter);
// Enable
_mqtt_connection_tries = 0;
if (getSetting("mqttServer", MQTT_SERVER).length() == 0) {
mqttEnabled(false);
} else {
_mqtt_enabled = getSetting("mqttEnabled", MQTT_ENABLED).toInt() == 1;
}
_mqtt_use_json = (getSetting("mqttUseJson", MQTT_USE_JSON).toInt() == 1);
_mqtt_reconnect_delay = MQTT_RECONNECT_DELAY_MIN;
}
@ -553,22 +554,14 @@ void mqttSetup() {
void mqttLoop() {
#if MQTT_USE_ASYNC
if (WiFi.status() != WL_CONNECTED) return;
if (!_mqtt_enabled) return;
if (WiFi.status() != WL_CONNECTED) return;
if (_mqtt.connected()) return;
#if MQTT_USE_ASYNC
static unsigned long last = 0;
if (millis() - last > MQTT_RECONNECT_DELAY) {
last = millis();
mqttConnect();
}
mqttConnect();
#else // not MQTT_USE_ASYNC
if (WiFi.status() != WL_CONNECTED) return;
if (_mqtt.connected()) {
_mqtt.loop();
@ -580,13 +573,7 @@ void mqttLoop() {
_mqtt_connected = false;
}
if (_mqtt_enabled) {
static unsigned long last = 0;
if (millis() - last > MQTT_RECONNECT_DELAY) {
last = millis();
mqttConnect();
}
}
mqttConnect();
}


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

@ -56,9 +56,9 @@ void otaSetup() {
ArduinoOTA.begin();
// Public ESPurna related txt for OTA discovery
MDNS.addServiceTxt("arduino", "tcp", "firmware", APP_NAME);
MDNS.addServiceTxt("arduino", "tcp", "espurna_version", APP_VERSION);
MDNS.addServiceTxt("arduino", "tcp", "espurna_board", DEVICE_NAME);
MDNS.addServiceTxt("arduino", "tcp", "app_name", APP_NAME);
MDNS.addServiceTxt("arduino", "tcp", "app_version", APP_VERSION);
MDNS.addServiceTxt("arduino", "tcp", "target_board", DEVICE_NAME);
}


+ 70
- 0
code/espurna/power.h View File

@ -0,0 +1,70 @@
// -----------------------------------------------------------------------------
// Stream Injector
// -----------------------------------------------------------------------------
#pragma once
class MedianFilter {
public:
MedianFilter(unsigned char size) {
_size = size;
_data = new double[_size+1];
reset();
}
virtual void reset() {
if (_pointer > 1) _data[0] = _data[_pointer-1];
_pointer = 1;
for (unsigned char i=1; i<=_size; i++) _data[i] = 0;
}
virtual void add(double value) {
if (_pointer <= _size) {
_data[_pointer] = value;
++_pointer;
}
}
virtual double average(bool do_reset = false) {
double sum = 0;
if (_pointer > 2) {
for (unsigned char i = 1; i<_pointer-1; i++) {
double previous = _data[i-1];
double current = _data[i];
double next = _data[i+1];
if (previous > current) std::swap(previous, current);
if (current > next) std::swap(current, next);
if (previous > current) std::swap(previous, current);
sum += current;
}
sum /= (_pointer - 2);
}
if (do_reset) reset();
return sum;
}
virtual unsigned char count() {
return _pointer - 1;
}
private:
double *_data;
unsigned char _size = 0;
unsigned char _pointer = 0;
};

+ 334
- 0
code/espurna/power.ino View File

@ -0,0 +1,334 @@
/*
POWER MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if POWER_PROVIDER != POWER_PROVIDER_NONE
// -----------------------------------------------------------------------------
// MODULE GLOBALS AND CACHE
// -----------------------------------------------------------------------------
#include "power.h"
#include <Hash.h>
#include <ArduinoJson.h>
bool _power_enabled = false;
bool _power_ready = false;
bool _power_newdata = false;
double _power_current = 0;
double _power_voltage = 0;
double _power_apparent = 0;
MedianFilter _filter_current = MedianFilter(POWER_REPORT_BUFFER);
#if POWER_HAS_ACTIVE
double _power_active = 0;
double _power_reactive = 0;
double _power_factor = 0;
MedianFilter _filter_voltage = MedianFilter(POWER_REPORT_BUFFER);
MedianFilter _filter_active = MedianFilter(POWER_REPORT_BUFFER);
MedianFilter _filter_apparent = MedianFilter(POWER_REPORT_BUFFER);
#endif
// -----------------------------------------------------------------------------
// PRIVATE METHODS
// -----------------------------------------------------------------------------
#if WEB_SUPPORT
void _powerAPISetup() {
apiRegister(MQTT_TOPIC_CURRENT, MQTT_TOPIC_CURRENT, [](char * buffer, size_t len) {
if (_power_ready) {
dtostrf(getCurrent(), len-1, POWER_CURRENT_DECIMALS, buffer);
} else {
buffer = NULL;
}
});
apiRegister(MQTT_TOPIC_VOLTAGE, MQTT_TOPIC_VOLTAGE, [](char * buffer, size_t len) {
if (_power_ready) {
snprintf_P(buffer, len, PSTR("%d"), getVoltage());
} else {
buffer = NULL;
}
});
apiRegister(MQTT_TOPIC_POWER_APPARENT, MQTT_TOPIC_POWER_APPARENT, [](char * buffer, size_t len) {
if (_power_ready) {
snprintf_P(buffer, len, PSTR("%d"), getApparentPower());
} else {
buffer = NULL;
}
});
#if POWER_HAS_ACTIVE
apiRegister(MQTT_TOPIC_POWER_ACTIVE, MQTT_TOPIC_POWER_ACTIVE, [](char * buffer, size_t len) {
if (_power_ready) {
snprintf_P(buffer, len, PSTR("%d"), getActivePower());
} else {
buffer = NULL;
}
});
#endif
}
#endif // WEB_SUPPORT
void _powerReset() {
_filter_current.reset();
#if POWER_HAS_ACTIVE
_filter_apparent.reset();
_filter_voltage.reset();
_filter_active.reset();
#endif
}
void _powerRead() {
// Get instantaneous values from HAL
double current = _powerCurrent();
double voltage = _powerVoltage();
double apparent = _powerApparentPower();
#if POWER_HAS_ACTIVE
double active = _powerActivePower();
double reactive = (apparent > active) ? sqrt(apparent * apparent - active * active) : 0;
double factor = (apparent > 0) ? active / apparent : 1;
if (factor > 1) factor = 1;
#endif
// Filters
_filter_current.add(current);
#if POWER_HAS_ACTIVE
_filter_apparent.add(apparent);
_filter_voltage.add(voltage);
_filter_active.add(active);
#endif
/* THERE IS A BUG HERE SOMEWHERE :)
char current_buffer[10];
dtostrf(current, sizeof(current_buffer)-1, POWER_CURRENT_DECIMALS, current_buffer);
DEBUG_MSG_P(PSTR("[POWER] Current: %sA\n"), current_buffer);
DEBUG_MSG_P(PSTR("[POWER] Voltage: %sA\n"), int(voltage));
DEBUG_MSG_P(PSTR("[POWER] Apparent Power: %dW\n"), int(apparent));
#if POWER_HAS_ACTIVE
DEBUG_MSG_P(PSTR("[POWER] Active Power: %dW\n"), int(active));
DEBUG_MSG_P(PSTR("[POWER] Reactive Power: %dW\n"), int(reactive));
DEBUG_MSG_P(PSTR("[POWER] Power Factor: %d%%\n"), int(100 * factor));
#endif
*/
// Update websocket clients
#if WEB_SUPPORT
if (wsConnected()) {
DynamicJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["pwrVisible"] = 1;
root["pwrCurrent"] = roundTo(current, POWER_CURRENT_DECIMALS);
root["pwrVoltage"] = roundTo(voltage, POWER_VOLTAGE_DECIMALS);
root["pwrApparent"] = roundTo(apparent, POWER_POWER_DECIMALS);
#if POWER_HAS_ACTIVE
root["pwrActive"] = roundTo(active, POWER_POWER_DECIMALS);
root["pwrReactive"] = roundTo(reactive, POWER_POWER_DECIMALS);
root["pwrFactor"] = int(100 * factor);
#endif
#if (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121)
root["emonVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
root["hlwVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
root["v9261fVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560
root["ech1560Visible"] = 1;
#endif
String output;
root.printTo(output);
wsSend(output.c_str());
}
#endif
}
void _powerReport() {
// Get the fitered values
_power_current = _filter_current.average(true);
#if POWER_HAS_ACTIVE
_power_apparent = _filter_apparent.average(true);
_power_voltage = _filter_voltage.average(true);
_power_active = _filter_active.average(true);
if (_power_active > _power_apparent) _power_apparent = _power_active;
_power_reactive = (_power_apparent > _power_active) ? sqrt(_power_apparent * _power_apparent - _power_active * _power_active) : 0;
_power_factor = (_power_apparent > 0) ? _power_active / _power_apparent : 1;
if (_power_factor > 1) _power_factor = 1;
double power = _power_active;
#else
_power_apparent = _power_current * _power_voltage;
double power = _power_apparent;
#endif
double energy_delta = power * POWER_ENERGY_FACTOR;
_power_ready = true;
char buf_current[10];
char buf_energy[10];
dtostrf(_power_current, 1-sizeof(buf_current), POWER_CURRENT_DECIMALS, buf_current);
dtostrf(energy_delta, 1-sizeof(buf_energy), POWER_CURRENT_DECIMALS, buf_energy);
{
mqttSend(MQTT_TOPIC_CURRENT, buf_current);
mqttSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str());
mqttSend(MQTT_TOPIC_ENERGY, buf_energy);
#if POWER_HAS_ACTIVE
mqttSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str());
mqttSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str());
mqttSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
mqttSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str());
#endif
}
#if DOMOTICZ_SUPPORT
if (domoticzEnabled()) {
char buffer[20];
snprintf_P(buffer, sizeof(buffer), PSTR("%d;%s"), power, buf_energy);
domoticzSend("dczPowIdx", 0, buffer);
domoticzSend("dczCurrentIdx", 0, buf_current);
domoticzSend("dczEnergyIdx", 0, buf_energy);
#if POWER_HAS_ACTIVE
snprintf_P(buffer, sizeof(buffer), PSTR("%d"), _power_voltage);
domoticzSend("dczVoltIdx", 0, buffer);
#endif
}
#endif
#if INFLUXDB_SUPPORT
if (influxdbEnabled()) {
influxDBSend(MQTT_TOPIC_CURRENT, buf_current);
influxDBSend(MQTT_TOPIC_POWER_APPARENT, String((int) _power_apparent).c_str());
influxDBSend(MQTT_TOPIC_ENERGY, buf_energy);
#if POWER_HAS_ACTIVE
influxDBSend(MQTT_TOPIC_POWER_ACTIVE, String((int) _power_active).c_str());
influxDBSend(MQTT_TOPIC_POWER_REACTIVE, String((int) _power_reactive).c_str());
influxDBSend(MQTT_TOPIC_VOLTAGE, String((int) _power_voltage).c_str());
influxDBSend(MQTT_TOPIC_POWER_FACTOR, String((int) 100 * _power_factor).c_str());
#endif
}
#endif
}
// -----------------------------------------------------------------------------
// MAGNITUDE API
// -----------------------------------------------------------------------------
bool hasActivePower() {
return POWER_HAS_ACTIVE;
}
double getCurrent() {
return roundTo(_power_current, POWER_CURRENT_DECIMALS);
}
double getVoltage() {
return roundTo(_power_voltage, POWER_VOLTAGE_DECIMALS);
}
double getApparentPower() {
return roundTo(_power_apparent, POWER_POWER_DECIMALS);
}
#if POWER_HAS_ACTIVE
double getActivePower() {
return roundTo(_power_active, POWER_POWER_DECIMALS);
}
double getReactivePower() {
return roundTo(_power_reactive, POWER_POWER_DECIMALS);
}
double getPowerFactor() {
return roundTo(_power_factor, 2);
}
#endif
// -----------------------------------------------------------------------------
// PUBLIC API
// -----------------------------------------------------------------------------
bool powerEnabled() {
return _power_enabled;
}
void powerEnabled(bool enabled) {
if (enabled & !_power_enabled) _powerReset();
_power_enabled = enabled;
_powerEnabledProvider();
}
void powerCalibrate(unsigned char magnitude, double value) {
_powerCalibrateProvider(magnitude, value);
}
void powerResetCalibration() {
_powerResetCalibrationProvider();
}
void powerConfigure() {
_powerConfigureProvider();
}
void powerSetup() {
// backwards compatibility
moveSetting("pwMainsVoltage", "pwrVoltage");
moveSetting("emonMains", "pwrVoltage");
moveSetting("emonVoltage", "pwrVoltage");
moveSetting("pwCurrentRatio", "pwrRatioC");
moveSetting("emonRatio", "pwrRatioC");
moveSetting("powPowerMult", "pwrRatioP");
moveSetting("powCurrentMult", "pwrRatioC");
moveSetting("powVoltageMult", "pwrRatioV");
moveSetting("powerVoltage", "pwrVoltage");
moveSetting("powerRatioC", "pwrRatioC");
moveSetting("powerRatioV", "pwrRatioV");
moveSetting("powerRatioP", "pwrRatioP");
_powerSetupProvider();
// API
#if WEB_SUPPORT
_powerAPISetup();
#endif
DEBUG_MSG_P(PSTR("[POWER] POWER_PROVIDER = %d\n"), POWER_PROVIDER);
}
void powerLoop() {
_powerLoopProvider(true);
if (_power_newdata) {
_power_newdata = false;
_powerRead();
}
static unsigned long last = 0;
if (millis() - last > POWER_REPORT_INTERVAL) {
last = millis();
_powerReport();
}
_powerLoopProvider(false);
}
#endif // POWER_PROVIDER != POWER_PROVIDER_NONE

+ 207
- 0
code/espurna/power_ech1560.ino View File

@ -0,0 +1,207 @@
/*
POWER ECH1560 MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560
// -----------------------------------------------------------------------------
// MODULE GLOBALS AND CACHE
// -----------------------------------------------------------------------------
volatile long _ech1560_bits_count = 0;
volatile long _ech1560_clk_count = 0;
volatile bool _ech1560_dosync = false;
volatile bool _ech1560_nextbit = true;
double _ech1560_apparent = 0;
double _ech1560_voltage = 0;
double _ech1560_current = 0;
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
void _ech1560_sync() {
unsigned int byte1 = 0;
unsigned int byte2 = 0;
unsigned int byte3 = 0;
_ech1560_bits_count = 0;
while (_ech1560_bits_count < 40); // skip the uninteresting 5 first bytes
_ech1560_bits_count = 0;
while (_ech1560_bits_count < 24) { // loop through the next 3 Bytes (6-8) and save byte 6 and 7 in Ba and Bb
if (_ech1560_nextbit) {
if (_ech1560_bits_count < 9) { // first Byte/8 bits in Ba
byte1 = byte1 << 1;
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte1 |= 1;
_ech1560_nextbit = false;
} else if (_ech1560_bits_count < 17) { // bit 9-16 is byte 7, stor in Bb
byte2 = byte2 << 1;
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte2 |= 1;
_ech1560_nextbit = false;
}
}
}
if (byte2 != 3) { // if bit Bb is not 3, we have reached the important part, U is allready in Ba and Bb and next 8 Bytes will give us the Power.
// voltage = 2 * (Ba + Bb / 255)
_ech1560_voltage = 2.0 * ((float) byte1 + (float) byte2 / 255.0);
// power:
_ech1560_bits_count = 0;
while (_ech1560_bits_count < 40); // skip the uninteresting 5 first bytes
_ech1560_bits_count = 0;
byte1 = 0;
byte2 = 0;
byte3 = 0;
while (_ech1560_bits_count < 24) { //store byte 6, 7 and 8 in Ba and Bb & Bc.
if (_ech1560_nextbit) {
if (_ech1560_bits_count < 9) {
byte1 = byte1 << 1;
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte1 |= 1;
_ech1560_nextbit = false;
} else if (_ech1560_bits_count < 17) {
byte2 = byte2 << 1;
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte2 |= 1;
_ech1560_nextbit = false;
} else {
byte3 = byte3 << 1;
if (digitalRead(ECH1560_MISO_PIN) == HIGH) byte3 |= 1;
_ech1560_nextbit = false;
}
}
}
#if ECH1560_INVERTED
byte1 = 255 - byte1;
byte2 = 255 - byte2;
byte3 = 255 - byte3;
#endif
// power = (Ba*255+Bb+Bc/255)/2
_ech1560_apparent = ( (float) byte1 * 255 + (float) byte2 + (float) byte3 / 255.0) / 2;
_ech1560_current = _ech1560_apparent / _ech1560_voltage;
_power_newdata = true;
_ech1560_dosync = false;
}
// If Bb is not 3 or something else than 0, something is wrong!
if (byte2 == 0) _ech1560_dosync = false;
}
void ICACHE_RAM_ATTR _ech1560_isr() {
// if we are trying to find the sync-time (CLK goes high for 1-2ms)
if (_ech1560_dosync == false) {
_ech1560_clk_count = 0;
// register how long the ClkHigh is high to evaluate if we are at the part wher clk goes high for 1-2 ms
while (digitalRead(ECH1560_CLK_PIN) == HIGH) {
_ech1560_clk_count += 1;
delayMicroseconds(30); //can only use delayMicroseconds in an interrupt.
}
// if the Clk was high between 1 and 2 ms than, its a start of a SPI-transmission
if (_ech1560_clk_count >= 33 && _ech1560_clk_count <= 67) {
_ech1560_dosync = true;
}
// we are in sync and logging CLK-highs
} else {
// increment an integer to keep track of how many bits we have read.
_ech1560_bits_count += 1;
_ech1560_nextbit = true;
}
}
// -----------------------------------------------------------------------------
// POWER API
// -----------------------------------------------------------------------------
double _powerCurrent() {
return _ech1560_current;
}
double _powerVoltage() {
return _ech1560_voltage;
}
double _powerActivePower() {
return 0;
}
double _powerApparentPower() {
return _ech1560_apparent;
}
double _powerReactivePower() {
return 0;
}
double _powerPowerFactor() {
return 1;
}
void _powerEnabledProvider() {
// Nothing to do
}
void _powerConfigureProvider() {
// Nothing to do
}
void _powerCalibrateProvider(unsigned char magnitude, double value) {
// Nothing to do
}
void _powerResetCalibrationProvider() {
// Nothing to do
}
void _powerSetupProvider() {
pinMode(ECH1560_CLK_PIN, INPUT);
pinMode(ECH1560_MISO_PIN, INPUT);
attachInterrupt(ECH1560_CLK_PIN, _ech1560_isr, RISING);
}
void _powerLoopProvider(bool before) {
if (!before) {
if (_ech1560_dosync) _ech1560_sync();
}
}
#endif // POWER_PROVIDER == POWER_PROVIDER_ECH1560

+ 164
- 0
code/espurna/power_emon.ino View File

@ -0,0 +1,164 @@
/*
POWER EMON MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121)
// -----------------------------------------------------------------------------
// MODULE GLOBALS AND CACHE
// -----------------------------------------------------------------------------
#include <EmonLiteESP.h>
EmonLiteESP _emon;
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
#include "brzo_i2c.h"
// ADC121 Registers
#define ADC121_REG_RESULT 0x00
#define ADC121_REG_ALERT 0x01
#define ADC121_REG_CONFIG 0x02
#define ADC121_REG_LIMITL 0x03
#define ADC121_REG_LIMITH 0x04
#define ADC121_REG_HYST 0x05
#define ADC121_REG_CONVL 0x06
#define ADC121_REG_CONVH 0x07
#endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
unsigned int currentCallback() {
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG
return analogRead(A0);
#endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
uint8_t buffer[2];
brzo_i2c_start_transaction(ADC121_I2C_ADDRESS, I2C_SCL_FREQUENCY);
buffer[0] = ADC121_REG_RESULT;
brzo_i2c_write(buffer, 1, false);
brzo_i2c_read(buffer, 2, false);
brzo_i2c_end_transaction();
unsigned int value;
value = (buffer[0] & 0x0F) << 8;
value |= buffer[1];
return value;
#endif // POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
}
// -----------------------------------------------------------------------------
// POWER API
// -----------------------------------------------------------------------------
double _powerCurrent() {
static unsigned long last = 0;
static double current = 0;
if (millis() - last > 1000) {
last = millis();
current = _emon.getCurrent(EMON_SAMPLES);
current -= EMON_CURRENT_OFFSET;
if (current < 0) current = 0;
}
return current;
}
double _powerVoltage() {
return _power_voltage;
}
double _powerActivePower() {
return 0;
}
double _powerApparentPower() {
return _powerCurrent() * _powerVoltage();
}
double _powerReactivePower() {
return 0;
}
double _powerPowerFactor() {
return 1;
}
void _powerEnabledProvider() {
// Nothing to do
}
void _powerCalibrateProvider(unsigned char magnitude, double value) {
if (value <= 0) return;
if (magnitude == POWER_MAGNITUDE_ACTIVE) {
double power = _powerApparentPower();
double ratio = getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat();
ratio = ratio * (value / power);
_emon.setCurrentRatio(ratio);
setSetting("pwrRatioC", ratio);
saveSettings();
}
if (magnitude == POWER_MAGNITUDE_VOLTAGE) {
_power_voltage = value;
setSetting("pwrVoltage", value);
saveSettings();
}
}
void _powerResetCalibrationProvider() {
delSetting("pwrRatioC");
_powerConfigureProvider();
saveSettings();
}
void _powerConfigureProvider() {
_emon.setCurrentRatio(getSetting("pwrRatioC", EMON_CURRENT_RATIO).toFloat());
_power_voltage = getSetting("pwrVoltage", POWER_VOLTAGE).toFloat();
}
void _powerSetupProvider() {
_emon.initCurrent(currentCallback, EMON_ADC_BITS, EMON_REFERENCE_VOLTAGE, EMON_CURRENT_RATIO);
#if POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121
uint8_t buffer[2];
buffer[0] = ADC121_REG_CONFIG;
buffer[1] = 0x00;
brzo_i2c_start_transaction(ADC121_I2C_ADDRESS, I2C_SCL_FREQUENCY);
brzo_i2c_write(buffer, 2, false);
brzo_i2c_end_transaction();
#endif
_powerConfigureProvider();
_emon.warmup();
}
void _powerLoopProvider(bool before) {
if (before) {
static unsigned long last = 0;
if (millis() - last > POWER_READ_INTERVAL) {
last = millis();
_power_newdata = true;
}
}
}
#endif // (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121)

+ 161
- 0
code/espurna/power_hlw8012.ino View File

@ -0,0 +1,161 @@
/*
POWER HLW8012 MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
// -----------------------------------------------------------------------------
// MODULE GLOBALS AND CACHE
// -----------------------------------------------------------------------------
#include <HLW8012.h>
#include <ESP8266WiFi.h>
HLW8012 _hlw8012;
WiFiEventHandler _power_wifi_onconnect;
WiFiEventHandler _power_wifi_ondisconnect;
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
void ICACHE_RAM_ATTR _hlw_cf1_isr() {
_hlw8012.cf1_interrupt();
}
void ICACHE_RAM_ATTR _hlw_cf_isr() {
_hlw8012.cf_interrupt();
}
void _hlwRestoreCalibration() {
double value;
value = getSetting("pwrRatioP", 0).toFloat();
if (value > 0) _hlw8012.setPowerMultiplier(value);
value = getSetting("pwrRatioC", 0).toFloat();
if (value > 0) _hlw8012.setCurrentMultiplier(value);
value = getSetting("pwrRatioV", 0).toFloat();
if (value > 0) _hlw8012.setVoltageMultiplier(value);
}
void _hlwPersistCalibration() {
setSetting("pwrRatioP", _hlw8012.getPowerMultiplier());
setSetting("pwrRatioC", _hlw8012.getCurrentMultiplier());
setSetting("pwrRatioV", _hlw8012.getVoltageMultiplier());
saveSettings();
}
// -----------------------------------------------------------------------------
// POWER API
// -----------------------------------------------------------------------------
double _powerCurrent() {
return _hlw8012.getCurrent();
}
double _powerVoltage() {
return _hlw8012.getVoltage();
}
double _powerActivePower() {
return _hlw8012.getActivePower();
}
double _powerApparentPower() {
return _hlw8012.getApparentPower();
}
double _powerReactivePower() {
return _hlw8012.getReactivePower();
}
double _powerPowerFactor() {
return _hlw8012.getPowerFactor();
}
void _powerEnabledProvider() {
if (_power_enabled) {
attachInterrupt(HLW8012_CF1_PIN, _hlw_cf1_isr, CHANGE);
attachInterrupt(HLW8012_CF_PIN, _hlw_cf_isr, CHANGE);
} else {
detachInterrupt(HLW8012_CF1_PIN);
detachInterrupt(HLW8012_CF_PIN);
}
}
void _powerCalibrateProvider(unsigned char magnitude, double value) {
if (value <= 0) return;
if (magnitude == POWER_MAGNITUDE_ACTIVE) _hlw8012.expectedActivePower(value);
if (magnitude == POWER_MAGNITUDE_CURRENT) _hlw8012.expectedCurrent(value);
if (magnitude == POWER_MAGNITUDE_VOLTAGE) _hlw8012.expectedVoltage(value);
_hlwPersistCalibration();
}
void _powerResetCalibrationProvider() {
_hlw8012.resetMultipliers();
delSetting("pwrRatioC");
delSetting("pwrRatioV");
delSetting("pwrRatioP");
saveSettings();
}
void _powerConfigureProvider() {
// Nothing to do
}
void _powerSetupProvider() {
// Initialize HLW8012
// void begin(unsigned char cf_pin, unsigned char cf1_pin, unsigned char sel_pin, unsigned char currentWhen = HIGH, bool use_interrupts = false, unsigned long pulse_timeout = PULSE_TIMEOUT);
// * cf_pin, cf1_pin and sel_pin are GPIOs to the HLW8012 IC
// * currentWhen is the value in sel_pin to select current sampling
// * set use_interrupts to true to use interrupts to monitor pulse widths
// * leave pulse_timeout to the default value, recommended when using interrupts
#if HLW8012_USE_INTERRUPTS
_hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, true);
#else
_hlw8012.begin(HLW8012_CF_PIN, HLW8012_CF1_PIN, HLW8012_SEL_PIN, HLW8012_SEL_CURRENT, false, 1000000);
#endif
// These values are used to calculate current, voltage and power factors as per datasheet formula
// These are the nominal values for the Sonoff POW resistors:
// * The CURRENT_RESISTOR is the 1milliOhm copper-manganese resistor in series with the main line
// * The VOLTAGE_RESISTOR_UPSTREAM are the 5 470kOhm resistors in the voltage divider that feeds the V2P pin in the HLW8012
// * The VOLTAGE_RESISTOR_DOWNSTREAM is the 1kOhm resistor in the voltage divider that feeds the V2P pin in the HLW8012
_hlw8012.setResistors(HLW8012_CURRENT_R, HLW8012_VOLTAGE_R_UP, HLW8012_VOLTAGE_R_DOWN);
_hlwRestoreCalibration();
_power_wifi_onconnect = WiFi.onStationModeGotIP([](WiFiEventStationModeGotIP ipInfo) {
powerEnabled(true);
});
_power_wifi_ondisconnect = WiFi.onStationModeDisconnected([](WiFiEventStationModeDisconnected ipInfo) {
powerEnabled(false);
});
}
void _powerLoopProvider(bool before) {
if (before) {
static unsigned long last = 0;
if (millis() - last > POWER_READ_INTERVAL) {
last = millis();
_power_newdata = true;
// Toggle between current and voltage monitoring
#if (HLW8012_USE_INTERRUPTS == 0)
_hlw8012.toggleMode();
#endif // (HLW8012_USE_INTERRUPTS == 0)
}
}
}
#endif // POWER_PROVIDER == POWER_PROVIDER_HLW8012

+ 234
- 0
code/espurna/power_v9261f.ino View File

@ -0,0 +1,234 @@
/*
POWER V9261F MODULE
Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
*/
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
// -----------------------------------------------------------------------------
// MODULE GLOBALS AND CACHE
// -----------------------------------------------------------------------------
#include <SoftwareSerial.h>
SoftwareSerial * _v9261f_uart;
double _v9261f_active = 0;
double _v9261f_reactive = 0;
double _v9261f_voltage = 0;
double _v9261f_current = 0;
double _v9261f_ratioP = V9261F_POWER_FACTOR;
double _v9261f_ratioC = V9261F_CURRENT_FACTOR;
double _v9261f_ratioV = V9261F_VOLTAGE_FACTOR;
double _v9261f_ratioR = V9261F_RPOWER_FACTOR;
unsigned char _v9261f_data[24];
// -----------------------------------------------------------------------------
// HAL
// -----------------------------------------------------------------------------
void _v9261fRead() {
static unsigned char state = 0;
static unsigned long last = 0;
static bool found = false;
static unsigned char index = 0;
if (state == 0) {
while (_v9261f_uart->available()) {
_v9261f_uart->flush();
found = true;
last = millis();
}
if (found && (millis() - last > V9261F_SYNC_INTERVAL)) {
_v9261f_uart->flush();
index = 0;
state = 1;
}
} else if (state == 1) {
while (_v9261f_uart->available()) {
_v9261f_uart->read();
if (index++ >= 7) {
_v9261f_uart->flush();
index = 0;
state = 2;
}
}
} else if (state == 2) {
while (_v9261f_uart->available()) {
_v9261f_data[index] = _v9261f_uart->read();
if (index++ >= 19) {
_v9261f_uart->flush();
last = millis();
state = 3;
}
}
} else if (state == 3) {
if (_v9261fChecksum()) {
_v9261f_active = (double) (
(_v9261f_data[3]) +
(_v9261f_data[4] << 8) +
(_v9261f_data[5] << 16) +
(_v9261f_data[6] << 24)
) / _v9261f_ratioP;
_v9261f_reactive = (double) (
(_v9261f_data[7]) +
(_v9261f_data[8] << 8) +
(_v9261f_data[9] << 16) +
(_v9261f_data[10] << 24)
) / _v9261f_ratioR;
_v9261f_voltage = (double) (
(_v9261f_data[11]) +
(_v9261f_data[12] << 8) +
(_v9261f_data[13] << 16) +
(_v9261f_data[14] << 24)
) / _v9261f_ratioV;
_v9261f_current = (double) (
(_v9261f_data[15]) +
(_v9261f_data[16] << 8) +
(_v9261f_data[17] << 16) +
(_v9261f_data[18] << 24)
) / _v9261f_ratioC;
if (_v9261f_active < 0) _v9261f_active = 0;
if (_v9261f_reactive < 0) _v9261f_reactive = 0;
if (_v9261f_voltage < 0) _v9261f_voltage = 0;
if (_v9261f_current < 0) _v9261f_current = 0;
_power_newdata = true;
}
last = millis();
index = 0;
state = 4;
} else if (state == 4) {
while (_v9261f_uart->available()) {
_v9261f_uart->flush();
last = millis();
}
if (millis() - last > V9261F_SYNC_INTERVAL) {
state = 1;
}
}
}
bool _v9261fChecksum() {
unsigned char checksum = 0;
for (unsigned char i = 0; i < 19; i++) {
checksum = checksum + _v9261f_data[i];
}
checksum = ~checksum + 0x33;
return checksum == _v9261f_data[19];
}
// -----------------------------------------------------------------------------
// POWER API
// -----------------------------------------------------------------------------
double _powerCurrent() {
return _v9261f_current;
}
double _powerVoltage() {
return _v9261f_voltage;
}
double _powerActivePower() {
return _v9261f_active;
}
double _powerApparentPower() {
return sqrt(_v9261f_reactive * _v9261f_reactive + _v9261f_active * _v9261f_active);
}
double _powerReactivePower() {
return _v9261f_reactive;
}
double _powerPowerFactor() {
double apparent = _powerApparentPower();
if (apparent > 0) return _powerActivePower() / apparent;
return 1;
}
void _powerEnabledProvider() {
// Nothing to do
}
void _powerConfigureProvider() {
_v9261f_ratioP = getSetting("pwrRatioP", V9261F_POWER_FACTOR).toFloat();
_v9261f_ratioV = getSetting("pwrRatioV", V9261F_VOLTAGE_FACTOR).toFloat();
_v9261f_ratioC = getSetting("pwrRatioC", V9261F_CURRENT_FACTOR).toFloat();
_v9261f_ratioR = getSetting("pwrRatioR", V9261F_RPOWER_FACTOR).toFloat();
}
void _powerCalibrateProvider(unsigned char magnitude, double value) {
if (value <= 0) return;
if (magnitude == POWER_MAGNITUDE_ACTIVE) {
_v9261f_ratioP = _v9261f_ratioP * (_v9261f_active / value);
setSetting("pwrRatioP", _v9261f_ratioP);
}
if (magnitude == POWER_MAGNITUDE_CURRENT) {
_v9261f_ratioC = _v9261f_ratioC * (_v9261f_current / value);
setSetting("pwrRatioC", _v9261f_ratioC);
}
if (magnitude == POWER_MAGNITUDE_VOLTAGE) {
_v9261f_ratioV = _v9261f_ratioV * (_v9261f_voltage / value);
setSetting("pwrRatioV", _v9261f_ratioV);
}
if (magnitude == POWER_MAGNITUDE_POWER_FACTOR) {
if (value < 100) {
double apparent = _v9261f_ratioP / (value / 100);
value = sqrt(apparent * apparent - _v9261f_ratioP * _v9261f_ratioP);
_v9261f_ratioR = _v9261f_ratioR * (_v9261f_reactive / value);
setSetting("pwrRatioR", _v9261f_ratioR);
}
}
saveSettings();
}
void _powerResetCalibrationProvider() {
delSetting("pwrRatioP");
delSetting("pwrRatioC");
delSetting("pwrRatioV");
delSetting("pwrRatioR");
_powerConfigureProvider();
saveSettings();
}
void _powerSetupProvider() {
_v9261f_uart = new SoftwareSerial(V9261F_PIN, SW_SERIAL_UNUSED_PIN, V9261F_PIN_INVERSE, 256);
_v9261f_uart->begin(V9261F_BAUDRATE);
}
void _powerLoopProvider(bool before) {
if (before) {
_v9261fRead();
}
}
#endif // POWER_PROVIDER == POWER_PROVIDER_V9261F

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

@ -135,6 +135,16 @@ void settingsSetup() {
e->response(Embedis::OK);
});
Embedis::command( F("INFO"), [](Embedis* e) {
welcome();
e->response(Embedis::OK);
});
Embedis::command( F("UPTIME"), [](Embedis* e) {
e->stream->printf("Uptime: %d seconds\n", getUptime());
e->response(Embedis::OK);
});
Embedis::command( F("RESET"), [](Embedis* e) {
e->response(Embedis::OK);
customReset(CUSTOM_RESET_TERMINAL);
@ -260,6 +270,13 @@ void settingsSetup() {
e->response(Embedis::OK);
});
#if DEBUG_SUPPORT
Embedis::command( F("CRASH"), [](Embedis* e) {
debugDumpCrashInfo();
e->response(Embedis::OK);
});
#endif
Embedis::command( F("DUMP.RAW"), [](Embedis* e) {
for (unsigned int i = 0; i < SPI_FLASH_SEC_SIZE; i++) {
if (i % 16 == 0) e->stream->printf("\n[%04X] ", i);
@ -285,7 +302,7 @@ void settingsDump() {
void settingsLoop() {
if (_settings_save) {
DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
//DEBUG_MSG_P(PSTR("[SETTINGS] Saving\n"));
EEPROM.commit();
_settings_save = false;
}


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


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

@ -177,3 +177,9 @@ char * ltrim(char * s) {
while ((unsigned char) *p == ' ') ++p;
return p;
}
double roundTo(double num, unsigned char positions) {
double multiplier = 1;
while (positions-- > 0) multiplier *= 10;
return round(num * multiplier) / multiplier;
}

+ 51
- 42
code/espurna/web.ino View File

@ -10,6 +10,7 @@ Copyright (C) 2016-2017 by Xose Pérez <xose dot perez at gmail dot com>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include <Hash.h>
#include <FS.h>
#include <AsyncJson.h>
#include <ArduinoJson.h>
@ -228,32 +229,40 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
// Skip firmware filename
if (key.equals("filename")) continue;
#if HLW8012_SUPPORT
#if POWER_PROVIDER != POWER_PROVIDER_NONE
if (key == "powExpectedPower") {
hlw8012SetExpectedActivePower(value.toInt());
if (key == "pwrExpectedP") {
powerCalibrate(POWER_MAGNITUDE_ACTIVE, value.toFloat());
changed = true;
continue;
}
if (key == "powExpectedVoltage") {
hlw8012SetExpectedVoltage(value.toInt());
if (key == "pwrExpectedV") {
powerCalibrate(POWER_MAGNITUDE_VOLTAGE, value.toFloat());
changed = true;
continue;
}
if (key == "powExpectedCurrent") {
hlw8012SetExpectedCurrent(value.toFloat());
if (key == "pwrExpectedC") {
powerCalibrate(POWER_MAGNITUDE_CURRENT, value.toFloat());
changed = true;
continue;
}
if (key == "powExpectedReset") {
if (key == "pwrExpectedF") {
powerCalibrate(POWER_MAGNITUDE_POWER_FACTOR, value.toFloat());
changed = true;
continue;
}
if (key == "pwrResetCalibration") {
if (value.toInt() == 1) {
hlw8012Reset();
powerResetCalibration();
changed = true;
}
continue;
}
if (key.startsWith("pow")) continue;
#endif
#if DOMOTICZ_SUPPORT
@ -388,8 +397,8 @@ void _wsParse(uint32_t client_id, uint8_t * payload, size_t length) {
#if RF_SUPPORT
rfBuildCodes();
#endif
#if EMON_SUPPORT
setCurrentRatio(getSetting("emonRatio").toFloat());
#if POWER_PROVIDER != POWER_PROVIDER_NONE
powerConfigure();
#endif
#if NTP_SUPPORT
if (changedNTP) ntpConnect();
@ -533,21 +542,17 @@ void _wsStart(uint32_t client_id) {
root["dczTmpIdx"] = getSetting("dczTmpIdx").toInt();
#endif
#if EMON_SUPPORT
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#endif
#if ANALOG_SUPPORT
root["dczAnaIdx"] = getSetting("dczAnaIdx").toInt();
#endif
#if HLW8012_SUPPORT
#if POWER_PROVIDER != POWER_PROVIDER_NONE
root["dczPowIdx"] = getSetting("dczPowIdx").toInt();
root["dczEnergyIdx"] = getSetting("dczEnergyIdx").toInt();
root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt();
root["dczCurrentIdx"] = getSetting("dczCurrentIdx").toInt();
#if POWER_HAS_ACTIVE
root["dczVoltIdx"] = getSetting("dczVoltIdx").toInt();
#endif
#endif
#endif
@ -583,14 +588,6 @@ void _wsStart(uint32_t client_id) {
root["rfDevice"] = getSetting("rfDevice", RF_DEVICE);
#endif
#if EMON_SUPPORT
root["emonVisible"] = 1;
root["emonApparentPower"] = getApparentPower();
root["emonCurrent"] = getCurrent();
root["emonVoltage"] = getVoltage();
root["emonRatio"] = getSetting("emonRatio", EMON_CURRENT_RATIO);
#endif
#if ANALOG_SUPPORT
root["analogVisible"] = 1;
root["analogValue"] = getAnalog();
@ -601,14 +598,28 @@ void _wsStart(uint32_t client_id) {
root["counterValue"] = getCounter();
#endif
#if HLW8012_SUPPORT
root["powVisible"] = 1;
root["powActivePower"] = getActivePower();
root["powApparentPower"] = getApparentPower();
root["powReactivePower"] = getReactivePower();
root["powVoltage"] = getVoltage();
root["powCurrent"] = String(getCurrent(), 3);
root["powPowerFactor"] = String(getPowerFactor(), 2);
#if POWER_PROVIDER != POWER_PROVIDER_NONE
root["pwrVisible"] = 1;
root["pwrCurrent"] = getCurrent();
root["pwrVoltage"] = getVoltage();
root["pwrApparent"] = getApparentPower();
#if POWER_HAS_ACTIVE
root["pwrActive"] = getActivePower();
root["pwrReactive"] = getReactivePower();
root["pwrFactor"] = int(100 * getPowerFactor());
#endif
#if (POWER_PROVIDER == POWER_PROVIDER_EMON_ANALOG) || (POWER_PROVIDER == POWER_PROVIDER_EMON_ADC121)
root["emonVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_HLW8012
root["hlwVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_V9261F
root["v9261fVisible"] = 1;
#endif
#if POWER_PROVIDER == POWER_PROVIDER_ECH1560
root["ech1560fVisible"] = 1;
#endif
#endif
#if NOFUSS_SUPPORT
@ -631,7 +642,6 @@ void _wsStart(uint32_t client_id) {
}
#endif
root["wifiGain"] = getSetting("wifiGain", WIFI_GAIN).toFloat();
root["maxNetworks"] = WIFI_MAX_NETWORKS;
JsonArray& wifi = root.createNestedArray("wifi");
for (byte i=0; i<WIFI_MAX_NETWORKS; i++) {
@ -809,7 +819,6 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
// Get response from callback
char value[API_BUFFER_SIZE];
(api.getFn)(value, API_BUFFER_SIZE);
char *p = ltrim(value);
// The response will be a 404 NOT FOUND if the resource is not available
if (!value) {
@ -817,15 +826,15 @@ ArRequestHandlerFunction _bindAPI(unsigned int apiID) {
request->send(404);
return;
}
DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), p);
DEBUG_MSG_P(PSTR("[API] Sending response '%s'\n"), value);
// Format response according to the Accept header
if (_asJson(request)) {
char buffer[64];
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, p);
snprintf_P(buffer, sizeof(buffer), PSTR("{ \"%s\": %s }"), api.key, value);
request->send(200, "application/json", buffer);
} else {
request->send(200, "text/plain", p);
request->send(200, "text/plain", value);
}
};


+ 0
- 16
code/espurna/wifi.ino View File

@ -28,9 +28,6 @@ String getNetwork() {
}
void wifiDisconnect() {
#if HLW8012_SUPPORT
hlw8012Enable(false);
#endif
jw.disconnect();
}
@ -50,8 +47,6 @@ bool createAP() {
void wifiConfigure() {
WiFi.setOutputPower(getSetting("wifiGain", WIFI_GAIN).toFloat());
jw.setHostname(getSetting("hostname").c_str());
jw.setSoftAP(getSetting("hostname").c_str(), getSetting("adminPass", ADMIN_PASS).c_str());
jw.setConnectTimeout(WIFI_CONNECT_TIMEOUT);
@ -263,17 +258,6 @@ void wifiSetup() {
}
#endif
// Manage POW
#if HLW8012_SUPPORT
if (code == MESSAGE_CONNECTED) {
hlw8012Enable(true);
}
if (code == MESSAGE_DISCONNECTED) {
hlw8012Enable(false);
}
#endif
});
}


+ 2
- 2
code/html/custom.js View File

@ -66,8 +66,8 @@ function doUpdate() {
websock.send(JSON.stringify({'config': data}));
$(".powExpected").val(0);
$("input[name='powExpectedReset']")
$(".pwrExpected").val(0);
$("input[name='pwrResetCalibration']")
.prop("checked", false)
.iphoneStyle("refresh");


+ 44
- 60
code/html/index.html View File

@ -111,7 +111,7 @@
<a href="#" class="pure-menu-link" data="panel-idb">INFLUXDB</a>
</li>
<li class="pure-menu-item module module-pow">
<li class="pure-menu-item module module-hlw module-emon module-v9261f">
<a href="#" class="pure-menu-link" data="panel-power">POWER</a>
</li>
@ -190,44 +190,34 @@
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="dhtHum" readonly />
</div>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="emonApparentPower">Apparent Power (VA)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="emonApparentPower" readonly />
<div class="pure-g module module-emon module-hlw module-v9261f module-ech1560">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrCurrent">Current</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrCurrent" post=" A" readonly />
</div>
<div class="pure-g module module-emon">
<label class="pure-u-1 pure-u-sm-1-4" for="emonCurrent">Current (A)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="emonCurrent" readonly />
<div class="pure-g module module-emon module-hlw module-v9261f module-ech1560">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrVoltage">Voltage</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrVoltage" post=" V" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powActivePower">Active Power (W)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powActivePower" readonly />
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrActive">Active Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrActive" post=" W" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powApparentPower">Apparent Power (VA)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powApparentPower" readonly />
<div class="pure-g module module-emon module-hlw module-v9261f module-ech1560">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrApparent">Apparent Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrApparent" post=" VA" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powReactivePower">Reactive Power (VAR)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powReactivePower" readonly />
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrReactive">Reactive Power</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrReactive" post=" VAR" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powCurrent">Current (A)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powCurrent" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powVoltage">Voltage (V)</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powVoltage" readonly />
</div>
<div class="pure-g module module-pow">
<label class="pure-u-1 pure-u-sm-1-4" for="powPowerFactor">Power Factor</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="powPowerFactor" readonly />
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-sm-1-4" for="pwrFactor">Power Factor</label>
<input class="pure-u-1 pure-u-sm-3-4" type="text" name="pwrFactor" post="%" readonly />
</div>
<div class="pure-u-1 state">
@ -397,19 +387,6 @@
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="alexaEnabled" tabindex="11" /></div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="wifiGain">WiFi gain</label>
<div class="pure-u-1 pure-u-md-3-4">
<select name="wifiGain" class="pure-u-3-4" tabindex="3">
<option value="0">Normal</a>
<option value="10">High</a>
<option value="20.5">Highest</a>
</select>
</div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">More gain means better range but also more power consumption. Might not work equally on all devices.</div>
</div>
<div class="pure-g module module-ha">
<label class="pure-u-1 pure-u-md-1-4" for="haPrefix">Home Assistant Prefix</label>
<input class="pure-u-1 pure-u-md-1-2" name="haPrefix" type="text" tabindex="31" />
@ -700,25 +677,25 @@
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div class="pure-g module module-pow module-emon">
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-sm-1-4" for="dczPowIdx">Power IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczPowIdx" type="number" min="0" tabindex="35" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div class="pure-g module module-pow module-emon">
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-sm-1-4" for="dczEnergyIdx">Energy increment IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczEnergyIdx" type="number" min="0" tabindex="36" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div class="pure-g module module-pow">
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-sm-1-4" for="dczVoltIdx">Voltage IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczVoltIdx" type="number" min="0" tabindex="37" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
</div>
<div class="pure-g module module-pow module-emon">
<div class="pure-g module module-pwr">
<label class="pure-u-1 pure-u-sm-1-4" for="dczCurrentIdx">Current IDX</label>
<div class="pure-u-1 pure-u-sm-1-8"><input class="pure-u-sm-23-24" name="dczCurrentIdx" type="number" min="0" tabindex="38" data="0" /></div>
<div class="pure-u-1 pure-u-sm-5-8 hint center">Set to 0 to disable notifications.</div>
@ -784,7 +761,7 @@
<div class="panel" id="panel-power">
<div class="header">
<h1>POWER</h1>
<h1>POWER CALIBRATION</h1>
<h2>
Calibrate your power monitor device. Use a pure resistive load and introduce the expected values for active power, current and voltage. Use the nominal values or a multimeter to get the proper numbers. Set any field to 0 to leave the calibration value untouched.
</h2>
@ -794,32 +771,39 @@
<fieldset>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedPower">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedPower" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-g module module-hlw module-emon module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedP">AC RMS Active Power</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedP" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Watts (W). If you are using a pure resistive load like a bulb this will be writen on it, otherwise use a socket multimeter to get this value.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedVoltage">AC RMS Voltage</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedVoltage" type="text" size="8" tabindex="52" placeholder="0" />
<div class="pure-g module module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedF">AC Power Factor</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedF" type="text" size="8" tabindex="51" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In percentage (%). You will need to use a calibrated multimeter to get this value and a mixed resistive and reactive load.</div>
</div>
<div class="pure-g module module-emon module-hlw module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedV">AC RMS Voltage</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedV" type="text" size="8" tabindex="52" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Volts (V). Enter your the nominal AC voltage for your household or facility, or use multimeter to get this value.</div>
</div>
<div class="pure-g">
<label class="pure-u-1 pure-u-md-1-4" for="powExpectedCurrent">AC RMS Current</label>
<input class="pure-u-1 pure-u-md-3-4 powExpected" name="powExpectedCurrent" type="text" size="8" tabindex="55" placeholder="0" />
<div class="pure-g module module-hlw module-v9261f">
<label class="pure-u-1 pure-u-md-1-4" for="pwrExpectedC">AC RMS Current</label>
<input class="pure-u-1 pure-u-md-3-4 pwrExpected" name="pwrExpectedC" type="text" size="8" tabindex="55" placeholder="0" />
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">In Ampers (A). If you are using a pure resistive load like a bulb this will the ratio between the two previous values, i.e. power / voltage. You can also use a current clamp around one fo the power wires to get this value.</div>
</div>
<div class="pure-g">
<div class="pure-u-1 pure-u-sm-1-4"><label for="powExpectedReset">Reset calibration</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="powExpectedReset" /></div>
<div class="pure-g module module-hlw module-emon module-v9261f">
<div class="pure-u-1 pure-u-sm-1-4"><label for="pwrResetCalibration">Reset calibration</label></div>
<div class="pure-u-1 pure-u-sm-1-4"><input type="checkbox" name="pwrResetCalibration" /></div>
<div class="pure-u-0 pure-u-md-1-4">&nbsp;</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Move this switch to ON and press "Update" to revert to factory values.</div>
<div class="pure-u-1 pure-u-md-3-4 hint">Move this switch to ON and press "Update" to revert to factory calibration values.</div>
</div>
</fieldset>


+ 9
- 6
code/ota_flash.sh View File

@ -24,11 +24,12 @@ useAvahi() {
echo_pad "#" 4
echo_pad "HOSTNAME" 20
echo_pad "IP" 20
echo_pad "APP" 15
echo_pad "VERSION" 15
echo_pad "DEVICE" 30
echo_pad "VERSION" 10
echo
printf -v line '%*s\n' 84
printf -v line '%*s\n' 104
echo ${line// /-}
counter=0
@ -40,7 +41,7 @@ useAvahi() {
echo -n "" > $board_file
echo -n "$counter" > $count_file
avahi-browse -t -r -p "_arduino._tcp" 2>/dev/null | grep ^= | while read line; do
avahi-browse -t -r -p "_arduino._tcp" 2>/dev/null | grep ^= | sort -t ';' -k 3 | while read line; do
(( counter++ ))
echo "$counter" > $count_file
@ -48,8 +49,9 @@ useAvahi() {
hostname=`echo $line | cut -d ';' -f4`
ip=`echo $line | cut -d ';' -f8`
txt=`echo $line | cut -d ';' -f10`
board=`echo $txt | sed -n "s/.*espurna_board=\([^\"]*\).*/\1/p"`
version=`echo $txt | sed -n "s/.*espurna_version=\([^\"]*\).*/\1/p"`
app_name=`echo $txt | sed -n "s/.*app_name=\([^\"]*\).*/\1/p"`
app_version=`echo $txt | sed -n "s/.*app_version=\([^\"]*\).*/\1/p"`
board=`echo $txt | sed -n "s/.*target_board=\([^\"]*\).*/\1/p"`
echo -n "$ip;" >> $ip_file
echo -n "$board;" >> $board_file
@ -57,8 +59,9 @@ useAvahi() {
echo_pad "$counter" 4
echo_pad "$hostname" 20
echo_pad "$ip" 20
echo_pad "$app_name" 15
echo_pad "$app_version" 15
echo_pad "$board" 30
echo_pad "$version" 10
echo


+ 9
- 6
code/ota_list.sh View File

@ -16,30 +16,33 @@ useAvahi() {
echo_pad "#" 4
echo_pad "HOSTNAME" 20
echo_pad "IP" 20
echo_pad "APP" 15
echo_pad "VERSION" 15
echo_pad "DEVICE" 30
echo_pad "VERSION" 10
echo
printf -v line '%*s\n' 84
printf -v line '%*s\n' 104
echo ${line// /-}
counter=0
avahi-browse -t -r -p "_arduino._tcp" 2>/dev/null | grep ^= | while read line; do
avahi-browse -t -r -p "_arduino._tcp" 2>/dev/null | grep ^= | sort -t ';' -k 3 | while read line; do
(( counter++ ))
hostname=`echo $line | cut -d ';' -f4`
ip=`echo $line | cut -d ';' -f8`
txt=`echo $line | cut -d ';' -f10`
board=`echo $txt | sed -n "s/.*espurna_board=\([^\"]*\).*/\1/p"`
version=`echo $txt | sed -n "s/.*espurna_version=\([^\"]*\).*/\1/p"`
app_name=`echo $txt | sed -n "s/.*app_name=\([^\"]*\).*/\1/p"`
app_version=`echo $txt | sed -n "s/.*app_version=\([^\"]*\).*/\1/p"`
board=`echo $txt | sed -n "s/.*target_board=\([^\"]*\).*/\1/p"`
echo_pad "$counter" 4
echo_pad "$hostname" 20
echo_pad "$ip" 20
echo_pad "$app_name" 15
echo_pad "$app_version" 15
echo_pad "$board" 30
echo_pad "$version" 10
echo
done


+ 81
- 22
code/platformio.ini View File

@ -22,6 +22,7 @@ lib_deps =
OneWire
DallasTemperature
Brzo I2C
EspSoftwareSerial
https://bitbucket.org/xoseperez/justwifi.git#1.1.4
https://bitbucket.org/xoseperez/hlw8012.git#1.0.1
https://bitbucket.org/xoseperez/fauxmoesp.git#2.2.0
@ -249,7 +250,7 @@ upload_flags = --auth=fibonacci --port 8266
[env:itead-sonoff-4ch]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -259,7 +260,7 @@ monitor_baud = 115200
[env:itead-sonoff-4ch-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -272,7 +273,7 @@ monitor_baud = 115200
[env:itead-sonoff-4ch-pro]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -282,7 +283,7 @@ monitor_baud = 115200
[env:itead-sonoff-4ch-pro-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -295,7 +296,7 @@ monitor_baud = 115200
[env:itead-sonoff-touch]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -305,7 +306,7 @@ monitor_baud = 115200
[env:itead-sonoff-touch-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -341,7 +342,7 @@ monitor_baud = 115200
[env:itead-sonoff-t1-1ch]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -351,7 +352,7 @@ monitor_baud = 115200
[env:itead-sonoff-t1-1ch-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -364,7 +365,7 @@ monitor_baud = 115200
[env:itead-sonoff-t1-2ch]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -374,7 +375,7 @@ monitor_baud = 115200
[env:itead-sonoff-t1-2ch-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -387,7 +388,7 @@ monitor_baud = 115200
[env:itead-sonoff-t1-3ch]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -397,7 +398,7 @@ monitor_baud = 115200
[env:itead-sonoff-t1-3ch-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -683,6 +684,7 @@ monitor_baud = 115200
platform = espressif8266
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DMAGICHOME_LED_CONTROLLER
@ -692,6 +694,7 @@ monitor_baud = 115200
platform = espressif8266
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DMAGICHOME_LED_CONTROLLER
@ -749,7 +752,7 @@ monitor_baud = 115200
[env:itead-bnsz01]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -759,7 +762,7 @@ monitor_baud = 115200
[env:itead-bnsz01-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
@ -815,21 +818,77 @@ upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
# ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS
# ------------------------------------------------------------------------------
[env:wemos-v9261f]
platform = espressif8266
framework = arduino
board = d1_mini
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DGENERIC_V9261F
upload_speed = 460800
monitor_baud = 115200
[env:wemos-v9261f-ota]
platform = espressif8266
framework = arduino
board = d1_mini
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags} -DGENERIC_V9261F
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:esp01-v9261f]
platform = espressif8266
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DGENERIC_V9261F
monitor_baud = 115200
[env:esp8285-1m-ota]
[env:esp01-v9261f-ota]
platform = espressif8266
framework = arduino
board = esp8285
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -D${env.ESPURNA_BOARD}
build_flags = ${common.build_flags_1m} -DGENERIC_V9261F
upload_speed = 115200
upload_port = "${env.ESPURNA_IP}"
upload_flags = --auth=${env.ESPURNA_AUTH} --port 8266
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
[env:esp01-ech1560]
platform = espressif8266
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DGENERIC_ECH1560
monitor_baud = 115200
[env:esp01-ech1560-ota]
platform = espressif8266
framework = arduino
board = esp01_1m
board_flash_mode = dout
lib_deps = ${common.lib_deps}
lib_ignore = ${common.lib_ignore}
build_flags = ${common.build_flags_1m} -DGENERIC_ECH1560
upload_speed = 115200
upload_port = "192.168.4.1"
upload_flags = --auth=fibonacci --port 8266
monitor_baud = 115200
# ------------------------------------------------------------------------------
# GENERIC OTA ENVIRONMENTS
# ------------------------------------------------------------------------------
[env:esp8266-1m-ota]
platform = espressif8266


BIN
images/devices/generic-v9261f.jpg View File

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

Loading…
Cancel
Save