├── gui ├── src │ ├── assets.js │ ├── ViewModels │ │ ├── PasswordViewModel.js │ │ ├── BaseViewModel.js │ │ ├── StatusViewModel.js │ │ ├── LastValuesViewModel.js │ │ ├── WiFiScanViewModel.js │ │ ├── ConfigViewModel.js │ │ └── WiFiConfigViewModel.js │ ├── term.html │ ├── term.js │ ├── config.js │ └── style.css ├── .eslintignore ├── .gitignore ├── lib │ └── index.js ├── .env.example ├── jsconfig.json ├── .eslintrc.json ├── readme.md ├── package.json ├── assets │ ├── wifi_signal_5.svg │ ├── wifi_signal_4.svg │ ├── wifi_signal_3.svg │ ├── wifi_signal_2.svg │ └── wifi_signal_1.svg ├── webpack.config.js └── .jshintrc ├── esp-test ├── CheckFlashConfig │ ├── .gitignore │ ├── platformio.ini │ ├── lib │ │ └── readme.txt │ └── src │ │ └── src.ino └── .travis.yml ├── blank_1MB.bin ├── docs ├── mqtt.png ├── wifi.png ├── admin.png ├── emoncms.png ├── emonesp.png ├── input.png ├── system.png └── wifi-scan.png ├── compiled ├── sonoff │ ├── spiffs.bin │ ├── firmware.bin │ └── readme.md └── wifirelay │ ├── firmware.bin │ └── spiffs.bin ├── guide └── esp_dht22_emoncms │ ├── fritzESP.png │ ├── platformio.ini │ ├── readme.md │ └── esp_dht22_emoncms.ino ├── src ├── event.h ├── urlencode.h ├── debug.h ├── debug.cpp ├── profile.h ├── web_static │ ├── web_server.assets.js.h │ ├── web_server.term.html.h │ ├── web_server.split_term.html.h │ ├── web_server.term.js.h │ ├── web_server_static_files.h │ ├── web_server.wifi_signal_5.svg.h │ ├── web_server.wifi_signal_4.svg.h │ ├── web_server.wifi_signal_3.svg.h │ ├── web_server.wifi_signal_2.svg.h │ ├── web_server.wifi_signal_1.svg.h │ └── web_server.style.css.h ├── web_server_static.h ├── autoauth.h ├── ota.h ├── emoncms.h ├── input.h ├── urlencode.cpp ├── ota.cpp ├── http.h ├── web_server.h ├── input.cpp ├── mqtt.h ├── emonesp.h ├── energy_meter.h ├── emoncms.cpp ├── autoauth.cpp ├── esp_wifi.h ├── http.cpp ├── app_config.h ├── web_server_static.cpp ├── src.ino ├── mqtt.cpp └── app_config_v1.cpp ├── .travis.yml ├── .editorconfig ├── tests.txt ├── lib └── readme.txt ├── .gitignore ├── ota ├── firmware.php └── Readme.md ├── sonoffS20.md ├── test.http ├── platformio.ini └── scripts └── extra_script.py /gui/src/assets.js: -------------------------------------------------------------------------------- 1 | import "./style.css"; 2 | -------------------------------------------------------------------------------- /esp-test/CheckFlashConfig/.gitignore: -------------------------------------------------------------------------------- 1 | .pioenvs -------------------------------------------------------------------------------- /gui/.eslintignore: -------------------------------------------------------------------------------- 1 | src/lib/js/*.min.js 2 | src/data/lib.js 3 | -------------------------------------------------------------------------------- /blank_1MB.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/blank_1MB.bin -------------------------------------------------------------------------------- /docs/mqtt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/mqtt.png -------------------------------------------------------------------------------- /docs/wifi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/wifi.png -------------------------------------------------------------------------------- /docs/admin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/admin.png -------------------------------------------------------------------------------- /docs/emoncms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/emoncms.png -------------------------------------------------------------------------------- /docs/emonesp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/emonesp.png -------------------------------------------------------------------------------- /docs/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/input.png -------------------------------------------------------------------------------- /docs/system.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/system.png -------------------------------------------------------------------------------- /docs/wifi-scan.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/docs/wifi-scan.png -------------------------------------------------------------------------------- /gui/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | stats.json 4 | .env 5 | .vscode/settings.json 6 | -------------------------------------------------------------------------------- /compiled/sonoff/spiffs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/compiled/sonoff/spiffs.bin -------------------------------------------------------------------------------- /gui/lib/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | module.exports = path.resolve(__dirname, '../dist'); 3 | -------------------------------------------------------------------------------- /compiled/sonoff/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/compiled/sonoff/firmware.bin -------------------------------------------------------------------------------- /compiled/wifirelay/firmware.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/compiled/wifirelay/firmware.bin -------------------------------------------------------------------------------- /compiled/wifirelay/spiffs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/compiled/wifirelay/spiffs.bin -------------------------------------------------------------------------------- /gui/.env.example: -------------------------------------------------------------------------------- 1 | EMONESP_ENDPOINT=http://localhost:3000 2 | #EMONESP_ENDPOINT=http://emonesp.local 3 | DEV_HOST=localhost 4 | -------------------------------------------------------------------------------- /guide/esp_dht22_emoncms/fritzESP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircuitSetup/EmonESP/HEAD/guide/esp_dht22_emoncms/fritzESP.png -------------------------------------------------------------------------------- /compiled/sonoff/readme.md: -------------------------------------------------------------------------------- 1 | # Command line upload: 2 | 3 | esptool.py erase_flash 4 | esptool.py -b 921600 write_flash 0x000000 firmware.bin 0x7B000 spiffs.bin 5 | -------------------------------------------------------------------------------- /src/event.h: -------------------------------------------------------------------------------- 1 | #ifndef __EVENT_H 2 | #define __EVENT_H 3 | 4 | #include 5 | 6 | void event_send(String event); 7 | void event_send(JsonDocument &event); 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /src/urlencode.h: -------------------------------------------------------------------------------- 1 | #ifndef urlencode_h 2 | #define urlencode_h 3 | 4 | #include 5 | 6 | String urldecode(String str); 7 | String urlencode(String str); 8 | 9 | #endif // urlencode_h -------------------------------------------------------------------------------- /src/debug.h: -------------------------------------------------------------------------------- 1 | #ifndef __DEBUG_H 2 | #define __DEBUG_H 3 | 4 | #undef DEBUG_PORT 5 | #define DEBUG_PORT SerialDebug 6 | 7 | #include "MicroDebug.h" 8 | #include "StreamSpy.h" 9 | 10 | extern StreamSpy SerialDebug; 11 | 12 | extern void debug_setup(); 13 | 14 | #endif // __DEBUG_H 15 | -------------------------------------------------------------------------------- /esp-test/CheckFlashConfig/platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # PlatformIO Project Configuration File 3 | # 4 | # Please make sure to read documentation with examples first 5 | # http://docs.platformio.org/en/stable/projectconf.html 6 | # 7 | [env:esp12e] 8 | platform = espressif 9 | framework = arduino 10 | board = esp12e 11 | -------------------------------------------------------------------------------- /gui/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=759670 3 | // for the documentation about the jsconfig.json format 4 | "compilerOptions": { 5 | "target": "es6", 6 | "module": "commonjs", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "exclude": [ 10 | "node_modules", 11 | "bower_components", 12 | "jspm_packages", 13 | "tmp", 14 | "temp" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /src/debug.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #ifndef ENABLE_DEBUG 4 | #define ENABLE_DEBUG 5 | #endif 6 | 7 | #ifndef DEBUG_PORT 8 | #define DEBUG_PORT Serial 9 | #endif 10 | 11 | #ifndef DEBUG_LOG_BUFFER 12 | #define DEBUG_LOG_BUFFER 512 13 | #endif 14 | 15 | StreamSpy SerialDebug(DEBUG_PORT); 16 | 17 | void debug_setup() 18 | { 19 | DEBUG_PORT.begin(115200); 20 | SerialDebug.begin(DEBUG_LOG_BUFFER); 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - '2.7' 4 | sudo: false 5 | cache: 6 | directories: 7 | - "~/.platformio" 8 | env: 9 | - PIO_ENV=emonesp 10 | - PIO_ENV=smartplug 11 | - PIO_ENV=wifirelay 12 | - PIO_ENV=hpmon 13 | - PIO_ENV=esp12e 14 | install: 15 | - pip install -U platformio 16 | - platformio platform install espressif8266 17 | script: 18 | - platformio --version 19 | - platformio run -e $PIO_ENV 20 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | # The JSON files contain newlines inconsistently 12 | [*.json] 13 | insert_final_newline = ignore 14 | 15 | # Minified JavaScript files shouldn't be changed 16 | [**.min.js] 17 | indent_style = ignore 18 | insert_final_newline = ignore 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | 23 | -------------------------------------------------------------------------------- /src/profile.h: -------------------------------------------------------------------------------- 1 | #ifndef __PROFILE_H 2 | #define __PROFILE_H 3 | 4 | #if defined(ENABLE_PROFILE) && defined(ENABLE_DEBUG) 5 | 6 | #define Profile_Start(x) \ 7 | unsigned long profile ## x = millis() 8 | 9 | #define Profile_End(x, max) \ 10 | unsigned long profile ## x ## Diff = millis() - profile ## x; \ 11 | if(profile ## x ## Diff > max) { \ 12 | DBUGF(">> Slow " #x " %lums", profile ## x ## Diff);\ 13 | } 14 | 15 | #else // ENABLE_PROFILE 16 | 17 | #define Profile_Start(x) 18 | #define Profile_End(x, min) 19 | 20 | #endif // ENABLE_PROFILE 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /gui/src/ViewModels/PasswordViewModel.js: -------------------------------------------------------------------------------- 1 | /* global ko */ 2 | /* exported PasswordViewModel */ 3 | 4 | function PasswordViewModel(password) 5 | { 6 | "use strict"; 7 | var self = this; 8 | 9 | self.show = ko.observable(false); 10 | self.value = ko.computed({ 11 | read: () => { 12 | if(self.show() && self.isDummy()) { 13 | return ""; 14 | } 15 | 16 | return password(); 17 | }, 18 | write: (value) => { 19 | password(value); 20 | } 21 | }); 22 | self.isDummy = ko.computed(() => { 23 | return ["___DUMMY_PASSWORD___", "_DUMMY_PASSWORD"].includes(password()); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /guide/esp_dht22_emoncms/platformio.ini: -------------------------------------------------------------------------------- 1 | # 2 | # Project Configuration File 3 | # 4 | # A detailed documentation with the EXAMPLES is located here: 5 | # http://docs.platformio.org/en/latest/projectconf.html 6 | # 7 | 8 | # A sign `#` at the beginning of the line indicates a comment 9 | # Comment lines are ignored. 10 | 11 | # Simple and base environment 12 | # [env:mybaseenv] 13 | # platform = %INSTALLED_PLATFORM_NAME_HERE% 14 | # framework = 15 | # board = 16 | # 17 | # Automatic targets - enable auto-uploading 18 | # targets = upload 19 | 20 | [platformio] 21 | env_default = emonesp 22 | src_dir = . 23 | 24 | [env:emonesp] 25 | platform = espressif 26 | framework = arduino 27 | board = esp12e 28 | -------------------------------------------------------------------------------- /gui/src/ViewModels/BaseViewModel.js: -------------------------------------------------------------------------------- 1 | function BaseViewModel(defaults, remoteUrl, mappings) { 2 | if(mappings === undefined){ 3 | mappings = {}; 4 | } 5 | var self = this; 6 | self.remoteUrl = remoteUrl; 7 | 8 | // Observable properties 9 | ko.mapping.fromJS(defaults, mappings, self); 10 | self.fetching = ko.observable(false); 11 | } 12 | 13 | BaseViewModel.prototype.update = function (after) { 14 | if(after === undefined){ 15 | after = function () { }; 16 | } 17 | var self = this; 18 | self.fetching(true); 19 | $.get(self.remoteUrl, function (data) { 20 | ko.mapping.fromJS(data, self); 21 | }, "json").always(function () { 22 | self.fetching(false); 23 | after(); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /gui/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true, 7 | "jquery": true 8 | }, 9 | "globals": { 10 | "ko": true, 11 | "Sammy": true 12 | }, 13 | "parserOptions": { 14 | "ecmaFeatures": { 15 | "jsx": true 16 | }, 17 | "sourceType": "module" 18 | }, 19 | "rules": { 20 | "no-const-assign": "warn", 21 | "no-this-before-super": "warn", 22 | "no-undef": "warn", 23 | "no-unreachable": "warn", 24 | "no-unused-vars": "warn", 25 | "constructor-super": "warn", 26 | "valid-typeof": "warn", 27 | "quotes": ["warn", "double"] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /guide/esp_dht22_emoncms/readme.md: -------------------------------------------------------------------------------- 1 | # ESP DHT22 to Emoncms Example 2 | 3 | An example made using the [Adafruit Huzzah](https://www.adafruit.com/product/2471) board with ESP-12 module and the DHT22 temperature humidity sensor. 4 | 5 | This code example, when loaded onto the ESP will continuously upload temperature and humidity data onto [emoncms.org](https://emoncms.org) 6 | 7 | [CP2102 USB to UART adapter](https://shop.openenergymonitor.com/programmers) was used as power supply and to program the ESP. 8 | PlatformIO or Arduino IDE can be used to compile and upload code. See [PlatformIO compile and upload guide](https://guide.openenergymonitor.org/technical/compiling) 9 | 10 | ![image](fritzESP.png) 11 | 12 | Thanks to @myrddinmuse for puting together this example. 13 | -------------------------------------------------------------------------------- /tests.txt: -------------------------------------------------------------------------------- 1 | EmonTX CM 2 | 3 | MSG:463673,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 4 | MSG:463674,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 5 | MSG:463675,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 6 | MSG:463676,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 7 | MSG:463677,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 8 | MSG:463678,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 9 | MSG:463679,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 10 | MSG:463680,Vrms:234.99,P1:965,P2:274,P4:1,E1:326485,E2:175951,E4:498,T1:23.00,pulse:0 11 | 12 | 13 | 14 | Startup 15 | 16 | 17 | 18 | Calibration 19 | 20 | p10 21 | k0 268.97 22 | k1 90.90 4.20 23 | k2 90.90 4.20 24 | k3 90.90 4.20 25 | k4 16.67 6.00 26 | t0 1 27 | -------------------------------------------------------------------------------- /gui/src/term.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | EmonESP Terminal 4 | 30 | 31 | 32 | 33 | 34 |

35 |   
36 | 37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | See additional options for PlatformIO Library Dependency Finder `lib_*`: 36 | 37 | http://docs.platformio.org/en/latest/projectconf.html#lib-install 38 | 39 | -------------------------------------------------------------------------------- /src/web_static/web_server.assets.js.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_ASSETS_JS[] PROGMEM = 2 | "!function(r){var n={};function o(e){if(n[e])return n[e].exports;var t=n[e]={i:e,l:!1,exports:{}};return r[e].call(t.exports,t,t.exports,o),t.l=!0,t.exports}o.m=r,o.c=n,o.d=function(e,t,r){o.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},o.r=function(e){\"undefined\"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:\"Module\"}),Object.defineProperty(e,\"__esModule\",{value:!0})},o.t=function(t,e){if(1&e&&(t=o(t)),8&e)return t;if(4&e&&\"object\"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(o.r(r),Object.defineProperty(r,\"default\",{enumerable:!0,value:t}),2&e&&\"string\"!=typeof t)for(var n in t)o.d(r,n,function(e){return t[e]}.bind(null,n));return r},o.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return o.d(t,\"a\",t),t},o.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},o.p=\"\",o(o.s=0)}([function(e,t,r){\"use strict\";r.r(t);t=r(1)},function(e,t,r){}]);\n"; 3 | -------------------------------------------------------------------------------- /src/web_static/web_server.term.html.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_TERM_HTML[] PROGMEM = 2 | " EmonESP Terminal
 
\n"; 26 | -------------------------------------------------------------------------------- /esp-test/CheckFlashConfig/lib/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | This directory is intended for the project specific (private) libraries. 3 | PlatformIO will compile them to static libraries and link to executable file. 4 | 5 | The source code of each library should be placed in separate directory, like 6 | "lib/private_lib/[here are source files]". 7 | 8 | For example, see how can be organized `Foo` and `Bar` libraries: 9 | 10 | |--lib 11 | | |--Bar 12 | | | |--docs 13 | | | |--examples 14 | | | |--src 15 | | | |- Bar.c 16 | | | |- Bar.h 17 | | |--Foo 18 | | | |- Foo.c 19 | | | |- Foo.h 20 | | |- readme.txt --> THIS FILE 21 | |- platformio.ini 22 | |--src 23 | |- main.c 24 | 25 | Then in `src/main.c` you should use: 26 | 27 | #include 28 | #include 29 | 30 | // rest H/C/CPP code 31 | 32 | PlatformIO will find your libraries automatically, configure preprocessor's 33 | include paths and build them. 34 | 35 | See additional options for PlatformIO Library Dependency Finder `lib_*`: 36 | 37 | http://docs.platformio.org/en/stable/projectconf.html#lib-install 38 | 39 | -------------------------------------------------------------------------------- /esp-test/CheckFlashConfig/src/src.ino: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | ESP8266 CheckFlashConfig by Markus Sattler 4 | 5 | This sketch tests if the EEPROM settings of the IDE match to the Hardware 6 | 7 | */ 8 | 9 | void setup(void) { 10 | Serial.begin(115200); 11 | } 12 | 13 | void loop() { 14 | 15 | uint32_t realSize = ESP.getFlashChipRealSize(); 16 | uint32_t ideSize = ESP.getFlashChipSize(); 17 | FlashMode_t ideMode = ESP.getFlashChipMode(); 18 | 19 | Serial.printf("Flash real id: %08X\n", ESP.getFlashChipId()); 20 | Serial.printf("Flash real size: %u\n\n", realSize); 21 | 22 | Serial.printf("Flash ide size: %u\n", ideSize); 23 | Serial.printf("Flash ide speed: %u\n", ESP.getFlashChipSpeed()); 24 | Serial.printf("Flash ide mode: %s\n", (ideMode == FM_QIO ? "QIO" : ideMode == FM_QOUT ? "QOUT" : ideMode == FM_DIO ? "DIO" : ideMode == FM_DOUT ? "DOUT" : "UNKNOWN")); 25 | 26 | if(ideSize != realSize) { 27 | Serial.println("Flash Chip configuration wrong!\n"); 28 | } else { 29 | Serial.println("Flash Chip configuration ok.\n"); 30 | } 31 | 32 | delay(5000); 33 | } 34 | -------------------------------------------------------------------------------- /gui/readme.md: -------------------------------------------------------------------------------- 1 | # OpenEVSE WiFi GUI 2 | 3 | This is the Web UI for OpenEVSE WiFi module. It is intended to be served via the [ESP8266](https://github.com/OpenEVSE/ESP8266_WiFi_v2.x), [ESP32](https://github.com/OpenEVSE/ESP32_WiFi_v3.x) or the [Node.JS](https://github.com/openevse/openevse_wifi_server) 4 | 5 | ## Building the UI 6 | 7 | ```shell 8 | npm install 9 | npm run build 10 | ``` 11 | 12 | ## Dev server 13 | 14 | To allow for easier development of the GUI there is a development server built in to Webpack. This can be configured to pass on calls to the backend to 15 | a real device or the simulator. 16 | 17 | ### Config 18 | 19 | You can configure the dev server using [dotenv](https://www.npmjs.com/package/dotenv). An example `.env` file can be found [here](.env.example). 20 | 21 | `OPENEVSE_ENDPOINT` - URL of the OpenEVSE to test against. 22 | 23 | `DEV_HOST` - By default the dev server is only available to localhost. If you want to expose the server to the local network then set this to the IP address or hostname of the external network interface. 24 | 25 | ### Starting 26 | 27 | To start the dev server use the command: 28 | 29 | `npm start` 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | applet 3 | application.linux32 4 | application.linux64 5 | application.windows32 6 | application.windows64 7 | application.macosx 8 | firmware.map 9 | 10 | # ========================= 11 | # Operating System Files 12 | # ========================= 13 | 14 | # OSX 15 | # ========================= 16 | 17 | .DS_Store 18 | .AppleDouble 19 | .LSOverride 20 | 21 | # Thumbnails 22 | ._* 23 | 24 | # Files that might appear on external disk 25 | .Spotlight-V100 26 | .Trashes 27 | 28 | # Directories potentially created on remote AFP share 29 | .AppleDB 30 | .AppleDesktop 31 | Network Trash Folder 32 | Temporary Items 33 | .apdisk 34 | 35 | # Windows 36 | # ========================= 37 | 38 | # Windows image file caches 39 | Thumbs.db 40 | ehthumbs.db 41 | 42 | # Folder config file 43 | Desktop.ini 44 | 45 | # Recycle Bin used on file shares 46 | $RECYCLE.BIN/ 47 | 48 | # Windows Installer files 49 | *.cab 50 | *.msi 51 | *.msm 52 | *.msp 53 | 54 | # Windows shortcuts 55 | *.lnk 56 | .pioenvs 57 | .piolibdeps 58 | .clang_complete 59 | .gcc-flags.json 60 | *~ 61 | .vscode/browse.vc.db* 62 | .vscode 63 | .pio 64 | node_modules 65 | gui/package-lock.json 66 | -------------------------------------------------------------------------------- /src/web_static/web_server.split_term.html.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_SPLIT_TERM_HTML[] PROGMEM = 2 | " EmonESP Terminal
\n"; 31 | -------------------------------------------------------------------------------- /src/web_static/web_server.term.js.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_TERM_JS[] PROGMEM = 2 | "\"use strict\";var socket=!1;!function(){var t,n=!1,o=\"debug\";\"\"!==window.location.search&&(t=window.location.search.substr(1),[\"debug\",\"emontx\"].includes(t)&&(o=t));var e=new URL(o,window.location),i=\"ws://\"+e.hostname;function c(t,n){var o=1 //https://github.com/me-no-dev/AsyncTCP 6 | #elif defined(ESP8266) 7 | #include 8 | #include 9 | #endif 10 | #include 11 | 12 | struct StaticFile 13 | { 14 | const char *filename; 15 | const char *data; 16 | size_t length; 17 | const char *type; 18 | }; 19 | 20 | class StaticFileWebHandler: public AsyncWebHandler 21 | { 22 | private: 23 | bool _getFile(AsyncWebServerRequest *request, StaticFile **file = NULL) const; 24 | protected: 25 | public: 26 | StaticFileWebHandler(); 27 | virtual bool canHandle(AsyncWebServerRequest *request) const override final; 28 | virtual void handleRequest(AsyncWebServerRequest *request) override final; 29 | }; 30 | 31 | class StaticFileResponse: public AsyncWebServerResponse 32 | { 33 | private: 34 | String _header; 35 | StaticFile *_content; 36 | 37 | const char *ptr; 38 | size_t length; 39 | 40 | size_t writeData(AsyncWebServerRequest *request); 41 | size_t write(AsyncWebServerRequest *request); 42 | 43 | public: 44 | StaticFileResponse(int code, StaticFile *file); 45 | void _respond(AsyncWebServerRequest *request); 46 | size_t _ack(AsyncWebServerRequest *request, size_t len, uint32_t time); 47 | bool _sourceValid() const { return true; } 48 | 49 | }; 50 | 51 | #endif // _EMONESP_WEB_SERVER_STATIC_H 52 | -------------------------------------------------------------------------------- /src/autoauth.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_AUTOAUTH_H 28 | #define _EMONESP_AUTOAUTH_H 29 | 30 | void auth_request(); 31 | void auth_setup(); 32 | void auth_loop(); 33 | 34 | #endif 35 | 36 | -------------------------------------------------------------------------------- /ota/firmware.php: -------------------------------------------------------------------------------- 1 | tag_name; 14 | $currentTag = $_GET["tag"]; 15 | 16 | // return latest version if no ?tag=XXXX 17 | if (empty($currentTag)){ 18 | echo $latestTag; 19 | } 20 | 21 | if (($latestTag != $currentTag) && ($currentTag!=NULL)) { 22 | $binPath = $json->assets[0]->browser_download_url; 23 | // the file you want to send 24 | $ch = curl_init(); 25 | curl_setopt($ch, CURLOPT_URL, $binPath); 26 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 27 | curl_setopt($ch, CURLOPT_HEADER, 0); 28 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 29 | $out = curl_exec($ch); 30 | curl_close($ch); 31 | 32 | // Set header for binary 33 | header('Content-type: application/octet-stream'); 34 | header('Content-disposition: attachment; filename="firmware.bin"'); 35 | header('Content-Transfer-Encoding: binary'); 36 | header("Content-Length: ".strlen($out)); 37 | 38 | echo $out; 39 | } 40 | 41 | exit(); 42 | ?> 43 | -------------------------------------------------------------------------------- /gui/src/ViewModels/StatusViewModel.js: -------------------------------------------------------------------------------- 1 | 2 | function StatusViewModel() { 3 | var self = this; 4 | 5 | BaseViewModel.call(self, { 6 | "mode": "ERR", 7 | "wifi_client_connected": 0, 8 | "net_connected": 0, 9 | "srssi": 0, 10 | "ipaddress": "", 11 | "packets_sent": 0, 12 | "packets_success": 0, 13 | "emoncms_connected": 0, 14 | "mqtt_connected": 0, 15 | "free_heap": 0, 16 | "time":"", 17 | "ctrl_mode":"off", 18 | "ctrl_state":0, 19 | "ota_update": false 20 | }, baseEndpoint + "/status"); 21 | 22 | // Some devired values 23 | self.isWiFiError = ko.pureComputed(function () { 24 | return ("ERR" === self.mode()); 25 | }); 26 | self.isWifiClient = ko.pureComputed(function () { 27 | return ("STA" == self.mode()) || ("STA+AP" == self.mode()); 28 | }); 29 | self.isWifiAccessPoint = ko.pureComputed(function () { 30 | return ("AP" == self.mode()) || ("STA+AP" == self.mode()); 31 | }); 32 | self.isWired = ko.pureComputed(() => { 33 | return ("Wired" === self.mode()); 34 | }); 35 | self.fullMode = ko.pureComputed(function () { 36 | switch (self.mode()) { 37 | case "AP": 38 | return "Access Point (AP)"; 39 | case "STA": 40 | return "Client (STA)"; 41 | case "STA+AP": 42 | return "Client + Access Point (STA+AP)"; 43 | case "Wired": 44 | return "Wired Ethernet"; 45 | } 46 | 47 | return "Unknown (" + self.mode() + ")"; 48 | }); 49 | } 50 | StatusViewModel.prototype = Object.create(BaseViewModel.prototype); 51 | StatusViewModel.prototype.constructor = StatusViewModel; 52 | -------------------------------------------------------------------------------- /src/web_static/web_server_static_files.h: -------------------------------------------------------------------------------- 1 | #include "web_server.assets.js.h" 2 | #include "web_server.config.js.h" 3 | #include "web_server.home.html.h" 4 | #include "web_server.lib.js.h" 5 | #include "web_server.style.css.h" 6 | #include "web_server.term.html.h" 7 | #include "web_server.term.js.h" 8 | #include "web_server.wifi_signal_1.svg.h" 9 | #include "web_server.wifi_signal_2.svg.h" 10 | #include "web_server.wifi_signal_3.svg.h" 11 | #include "web_server.wifi_signal_4.svg.h" 12 | #include "web_server.wifi_signal_5.svg.h" 13 | StaticFile staticFiles[] = { 14 | { "/assets.js", CONTENT_ASSETS_JS, sizeof(CONTENT_ASSETS_JS) - 1, _CONTENT_TYPE_JS }, 15 | { "/config.js", CONTENT_CONFIG_JS, sizeof(CONTENT_CONFIG_JS) - 1, _CONTENT_TYPE_JS }, 16 | { "/home.html", CONTENT_HOME_HTML, sizeof(CONTENT_HOME_HTML) - 1, _CONTENT_TYPE_HTML }, 17 | { "/lib.js", CONTENT_LIB_JS, sizeof(CONTENT_LIB_JS) - 1, _CONTENT_TYPE_JS }, 18 | { "/style.css", CONTENT_STYLE_CSS, sizeof(CONTENT_STYLE_CSS) - 1, _CONTENT_TYPE_CSS }, 19 | { "/term.html", CONTENT_TERM_HTML, sizeof(CONTENT_TERM_HTML) - 1, _CONTENT_TYPE_HTML }, 20 | { "/term.js", CONTENT_TERM_JS, sizeof(CONTENT_TERM_JS) - 1, _CONTENT_TYPE_JS }, 21 | { "/wifi_signal_1.svg", CONTENT_WIFI_SIGNAL_1_SVG, sizeof(CONTENT_WIFI_SIGNAL_1_SVG) - 1, _CONTENT_TYPE_SVG }, 22 | { "/wifi_signal_2.svg", CONTENT_WIFI_SIGNAL_2_SVG, sizeof(CONTENT_WIFI_SIGNAL_2_SVG) - 1, _CONTENT_TYPE_SVG }, 23 | { "/wifi_signal_3.svg", CONTENT_WIFI_SIGNAL_3_SVG, sizeof(CONTENT_WIFI_SIGNAL_3_SVG) - 1, _CONTENT_TYPE_SVG }, 24 | { "/wifi_signal_4.svg", CONTENT_WIFI_SIGNAL_4_SVG, sizeof(CONTENT_WIFI_SIGNAL_4_SVG) - 1, _CONTENT_TYPE_SVG }, 25 | { "/wifi_signal_5.svg", CONTENT_WIFI_SIGNAL_5_SVG, sizeof(CONTENT_WIFI_SIGNAL_5_SVG) - 1, _CONTENT_TYPE_SVG }, 26 | }; 27 | -------------------------------------------------------------------------------- /ota/Readme.md: -------------------------------------------------------------------------------- 1 | This php script has been lifted from [@squix78's project](https://github.com/squix78/esp8266-ci-ota), see [blog post](http://blog.squix.org/2016/06/esp8266-continuous-delivery-pipeline-push-to-production.html). Thanks a lot to Daniel Eichhorn for developing the ESP OTA CONTINUOUS DELIVERY workflow. 2 | 3 | From @squi78's blog: 4 | 5 | > ## The PHP script 6 | 7 | > It would have been nice if the production ESPs could have contacted the Github API directly. But there are two issues that made be use the intermediate PHP script: 8 | 9 | > - The ESPhttpUpdate currently cannot follow redirects. This is important since github hosts the release artefacts on Amazon AWS. But in the API JSON > object the address points to github, so the http client has to follow a redirect to download the artefact. 10 | > - Github uses https for its API and will redirect you to it if you are trying plain HTTP. This means that you would have to know the SSL fingerprints > of the github API server and the AWS hosting instance since this is required by the ESPs secure client interface. After all the ESPs don’t have a chain > of trusted certificates stored somewhere. While the fingerprint of the github API might be stable, the redirection on Amazon AWS might not always use > the same certificate. 11 | > 12 | > So what does the script do? 13 | > 14 | > It connects to the github API and fetches a JSON object describing the latest release. I’m currently only interested in the tag name and the firmware > download URL. The ESPs will send their firmware tag version with the update request and if the latest tag on Github and the one from the request are > identical nothing will happen. But if they are different the script will fetch the binary from github (with a hidden redirection to Amazon) and proxy > it to the ESP’s requesting it. -------------------------------------------------------------------------------- /esp-test/.travis.yml: -------------------------------------------------------------------------------- 1 | # Continuous Integration (CI) is the practice, in software 2 | # engineering, of merging all developer working copies with a shared mainline 3 | # several times a day < http://docs.platformio.org/en/stable/ci/index.html > 4 | # 5 | # Documentation: 6 | # 7 | # * Travis CI Embedded Builds with PlatformIO 8 | # < https://docs.travis-ci.com/user/integration/platformio/ > 9 | # 10 | # * PlatformIO integration with Travis CI 11 | # < http://docs.platformio.org/en/stable/ci/travis.html > 12 | # 13 | # * User Guide for `platformio ci` command 14 | # < http://docs.platformio.org/en/stable/userguide/cmd_ci.html > 15 | # 16 | # 17 | # Please choice one of the following templates (proposed below) and uncomment 18 | # it (remove "# " before each line) or use own configuration according to the 19 | # Travis CI documentation (see above). 20 | # 21 | 22 | 23 | # 24 | # Template #1: General project. Test it using existing `platformio.ini`. 25 | # 26 | 27 | # language: python 28 | # python: 29 | # - "2.7" 30 | # 31 | # sudo: false 32 | # cache: 33 | # directories: 34 | # - "~/.platformio" 35 | # 36 | # install: 37 | # - pip install -U platformio 38 | # 39 | # script: 40 | # - platformio run 41 | 42 | 43 | # 44 | # Template #2: The project is intended to by used as a library with examples 45 | # 46 | 47 | # language: python 48 | # python: 49 | # - "2.7" 50 | # 51 | # sudo: false 52 | # cache: 53 | # directories: 54 | # - "~/.platformio" 55 | # 56 | # env: 57 | # - PLATFORMIO_CI_SRC=path/to/test/file.c 58 | # - PLATFORMIO_CI_SRC=examples/file.ino 59 | # - PLATFORMIO_CI_SRC=path/to/test/directory 60 | # 61 | # install: 62 | # - pip install -U platformio 63 | # 64 | # script: 65 | # - platformio ci --lib="." --board=TYPE_1 --board=TYPE_2 --board=TYPE_N 66 | -------------------------------------------------------------------------------- /gui/src/ViewModels/LastValuesViewModel.js: -------------------------------------------------------------------------------- 1 | function LastValuesViewModel() { 2 | var self = this; 3 | self.remoteUrl = baseEndpoint + "/lastvalues"; 4 | 5 | // Observable properties 6 | self.fetching = ko.observable(false); 7 | self.lastValues = ko.observable(false); 8 | self.values = ko.mapping.fromJS([]); 9 | self.entries = ko.mapping.fromJS([]); 10 | 11 | let oldData = ""; 12 | 13 | self.update = function (after) { 14 | if(after === undefined){ 15 | after = function () { }; 16 | } 17 | self.fetching(true); 18 | $.get(self.remoteUrl, (data) => 19 | { 20 | // Transform the data into something a bit easier to handle as a binding 21 | var vals = []; 22 | if (data != "" && data !== oldData) 23 | { 24 | oldData = data; 25 | self.entries.push({ 26 | timestamp: new Date().toISOString(), 27 | log: data 28 | }); 29 | 30 | try 31 | { 32 | 33 | var parsed = JSON.parse(data); 34 | for (var key in parsed) { 35 | let value = parsed[key]; 36 | var units = ""; 37 | 38 | if (key.startsWith("CT")) units = " A"; 39 | if (key.startsWith("P")) units = " W"; 40 | if (key.startsWith("E")) units = " Wh"; 41 | if (key.startsWith("V")) units = " V"; 42 | if (key.startsWith("T")) units = " "+String.fromCharCode(176)+"C"; 43 | 44 | vals.push({key: key, value: value+units}); 45 | } 46 | self.lastValues(true); 47 | ko.mapping.fromJS(vals, self.values); 48 | } 49 | catch(e) { 50 | console.error(e); 51 | self.lastValues(false); 52 | } 53 | } else { 54 | self.lastValues(data!=""); 55 | } 56 | }, "text").always(function () { 57 | self.fetching(false); 58 | after(); 59 | }); 60 | }; 61 | } 62 | -------------------------------------------------------------------------------- /gui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "emonesp_gui", 3 | "version": "1.0.0", 4 | "description": "GUI for the EmonESP WiFi module", 5 | "dependencies": { 6 | "jquery": "^3.4.1", 7 | "knockout": "^3.5.0", 8 | "knockout-mapping": "^2.6.0" 9 | }, 10 | "devDependencies": { 11 | "@babel/core": "^7.6.2", 12 | "@babel/preset-env": "^7.6.2", 13 | "clean-webpack-plugin": "^0.1.19", 14 | "copy-webpack-plugin": "^5.1.1", 15 | "css-loader": "^1.0.1", 16 | "dotenv": "^8.2.0", 17 | "eslint": "^5.16.0", 18 | "extract-text-webpack-plugin": "^4.0.0-beta.0", 19 | "file-loader": "^2.0.0", 20 | "html-webpack-plugin": "^3.2.0", 21 | "imports-loader": "^0.8.0", 22 | "mini-css-extract-plugin": "^0.4.5", 23 | "optimize-css-assets-webpack-plugin": "^5.0.3", 24 | "style-loader": "^0.23.0", 25 | "uglify-js": "^3.6.0", 26 | "uglifyjs-webpack-plugin": "^2.2.0", 27 | "webpack": "^4.41.0", 28 | "webpack-bundle-analyzer": "^3.5.2", 29 | "webpack-cli": "^3.3.9", 30 | "webpack-dev-server": "^3.8.2", 31 | "webpack-merge-and-include-globally": "^2.1.20" 32 | }, 33 | "scripts": { 34 | "test": "echo \"Error: no test specified\" && exit 1", 35 | "watch": "webpack --watch", 36 | "start": "webpack-dev-server --open", 37 | "build": "webpack", 38 | "prepare": "npm run build", 39 | "lint": "eslint src" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "git+https://github.com/openenergymonitor/EmonESP.git" 44 | }, 45 | "keywords": [ 46 | "evse" 47 | ], 48 | "author": "Jeremy Poulter ", 49 | "license": "ISC", 50 | "bugs": { 51 | "url": "https://github.com/openenergymonitor/EmonESP/issues" 52 | }, 53 | "homepage": "https://github.com/openenergymonitor/EmonESP#readme", 54 | "files": [ 55 | "dist/", 56 | "lib/" 57 | ], 58 | "main": "lib/index.js", 59 | "directories": { 60 | "dist": "dist", 61 | "lib": "lib" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/ota.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_OTA_H 28 | #define _EMONESP_OTA_H 29 | 30 | // ------------------------------------------------------------------- 31 | // Support for updating the fitmware os the ESP8266 32 | // ------------------------------------------------------------------- 33 | 34 | #include 35 | #include 36 | #include // local OTA update from Arduino IDE 37 | #ifdef ESP32 38 | #include // ESP32 remote OTA update from server 39 | #elif defined(ESP8266) 40 | #include // ESP8266 remote OTA update from server 41 | #endif 42 | 43 | 44 | void ota_setup(); 45 | void ota_loop(); 46 | String ota_get_latest_version(); 47 | 48 | #ifdef ESP8266 49 | t_httpUpdate_return ota_http_update(); 50 | #endif 51 | 52 | #endif // _EMONESP_OTA_H 53 | -------------------------------------------------------------------------------- /src/emoncms.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_EMONCMS_H 28 | #define _EMONESP_EMONCMS_H 29 | 30 | #include 31 | #include 32 | 33 | // ------------------------------------------------------------------- 34 | // Commutication with EmonCMS 35 | // ------------------------------------------------------------------- 36 | 37 | extern boolean emoncms_connected; 38 | extern boolean emoncms_updated; 39 | 40 | extern unsigned long packets_sent; 41 | extern unsigned long packets_success; 42 | 43 | // ------------------------------------------------------------------- 44 | // Publish values to EmonCMS 45 | // 46 | // data: a comma seperated list of name:value pairs to send 47 | // ------------------------------------------------------------------- 48 | void emoncms_publish(JsonDocument &data); 49 | 50 | #endif // _EMONESP_EMONCMS_H 51 | -------------------------------------------------------------------------------- /src/input.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_INPUT_H 28 | #define _EMONESP_INPUT_H 29 | 30 | #include 31 | #include 32 | #ifdef ESP32 33 | #include 34 | #elif defined(ESP8266) 35 | #include 36 | #endif 37 | 38 | // ------------------------------------------------------------------- 39 | // Support for reading input 40 | // ------------------------------------------------------------------- 41 | 42 | extern char last_datastr[MAX_DATA_LEN]; 43 | extern char input_string[MAX_DATA_LEN]; 44 | 45 | // ------------------------------------------------------------------- 46 | // Read input sent via the web_server or serial. 47 | // 48 | // data: if true is returned data will be updated with the new line of 49 | // input 50 | // ------------------------------------------------------------------- 51 | extern boolean input_get(JsonDocument &data); 52 | 53 | #endif // _EMONESP_INPUT_H 54 | -------------------------------------------------------------------------------- /gui/assets/wifi_signal_5.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 54 | 55 | -------------------------------------------------------------------------------- /gui/src/term.js: -------------------------------------------------------------------------------- 1 | /* global $, Terminal */ 2 | 3 | var socket = false; 4 | 5 | (function () { 6 | "use strict"; 7 | 8 | var reconnectInterval = false; 9 | 10 | var validTerminals = ["debug", "emontx"]; 11 | 12 | var type = "debug"; 13 | if ("" !== window.location.search) { 14 | var val = window.location.search.substr(1); 15 | if (validTerminals.includes(val)) { 16 | type = val; 17 | } 18 | } 19 | 20 | var url = new URL(type, window.location); 21 | 22 | var ws = "ws://" + url.hostname; 23 | if ("https:" === url.protocol) { 24 | ws = "wss://" + url.hostname; 25 | } 26 | if (url.port && 80 !== url.port) { 27 | ws += ":" + url.port; 28 | } 29 | ws += url.pathname + "/console"; 30 | 31 | function addText(text, clear = false) { 32 | var term = $("#term"); 33 | var scroll = ($(document).height() - ($(document).scrollTop() + window.innerHeight)) < 10; 34 | text = text.replace(/(\r\n|\n\r|\n|\r)/gm, "\n"); 35 | if(clear) { 36 | term.text(text); 37 | } else { 38 | term.append(text); 39 | } 40 | if(scroll) { 41 | $(document).scrollTop($(document).height()); 42 | } 43 | } 44 | 45 | function connect() { 46 | socket = new WebSocket(ws); 47 | socket.onclose = () => { 48 | reconnect(); 49 | }; 50 | socket.onmessage = (msg) => { 51 | addText(msg.data); 52 | }; 53 | socket.onerror = (ev) => { 54 | console.log(ev); 55 | socket.close(); 56 | reconnect(); 57 | }; 58 | } 59 | 60 | function reconnect() { 61 | if (false === reconnectInterval) { 62 | reconnectInterval = setTimeout(() => { 63 | reconnectInterval = false; 64 | connect(); 65 | }, 500); 66 | } 67 | } 68 | 69 | $(() => { 70 | $.get(url.href, (data) => { 71 | addText(data, true); 72 | connect(); 73 | }, "text"); 74 | 75 | $("#term").click(() => { 76 | $("#input").focus(); 77 | }) 78 | $("body").click(() => { 79 | $("#input").focus(); 80 | }) 81 | }); 82 | })(); 83 | 84 | var inputHistory = []; 85 | var historyPointer = inputHistory.length; 86 | 87 | function term_input() 88 | { 89 | var line = $("#input").val(); 90 | $("#input").val(""); 91 | 92 | socket.send(line+"\r\n"); 93 | inputHistory.push(line); 94 | historyPointer = inputHistory.length; 95 | 96 | return false; 97 | } 98 | -------------------------------------------------------------------------------- /gui/src/ViewModels/WiFiScanViewModel.js: -------------------------------------------------------------------------------- 1 | /* global $, ko */ 2 | /* exported WiFiScanViewModel */ 3 | 4 | function WiFiScanResultViewModel(data) 5 | { 6 | "use strict"; 7 | var self = this; 8 | ko.mapping.fromJS(data, {}, self); 9 | 10 | // Return a strength from 0-5 based on rssi, https://eyesaas.com/wi-fi-signal-strength/ 11 | self.strength = ko.computed(() => { 12 | var rssi = self.rssi(); 13 | 14 | if(rssi >= -50) { 15 | return 5; 16 | } 17 | if(rssi >= -60) { 18 | return 4; 19 | } 20 | if(rssi >= -67) { 21 | return 3; 22 | } 23 | if(rssi >= -70) { 24 | return 2; 25 | } 26 | if(rssi >= -80) { 27 | return 1; 28 | } 29 | 30 | return 0; 31 | }); 32 | } 33 | 34 | function WiFiScanViewModel(baseEndpoint) 35 | { 36 | "use strict"; 37 | var self = this; 38 | var endpoint = ko.pureComputed(function () { return baseEndpoint() + "/scan"; }); 39 | 40 | const netListMappingSettings = { 41 | key: function(data) { 42 | return ko.utils.unwrapObservable(data.bssid); 43 | }, 44 | create: function (options) { 45 | return new WiFiScanResultViewModel(options.data); 46 | } 47 | }; 48 | 49 | self.results = ko.mapping.fromJS([], netListMappingSettings); 50 | self.filteredResults = ko.mapping.fromJS([], netListMappingSettings); 51 | 52 | // Observable properties 53 | self.fetching = ko.observable(false); 54 | 55 | self.update = function (after = function () { }) { 56 | self.fetching(true); 57 | $.get(endpoint(), function (data) { 58 | if(data.length > 0) { 59 | data = data.sort(function (left, right) { 60 | if(left.ssid === right.ssid) { 61 | return left.rssi < right.rssi ? 1 : -1; 62 | } 63 | return left.ssid < right.ssid ? -1 : 1; 64 | }); 65 | 66 | ko.mapping.fromJS(data, self.results); 67 | 68 | const uniqueArray = data.filter((net, index) => { 69 | return net.rssi >= -80 && index === data.findIndex(obj => { 70 | return net.ssid === obj.ssid; 71 | }); 72 | }).sort(function (left, right) { 73 | return left.rssi < right.rssi ? 1 : -1; 74 | }); 75 | 76 | ko.mapping.fromJS(uniqueArray, self.filteredResults); 77 | } 78 | }, "json").always(function () { 79 | self.fetching(false); 80 | after(); 81 | }); 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /gui/assets/wifi_signal_4.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 54 | 57 | 60 | 64 | 65 | -------------------------------------------------------------------------------- /gui/assets/wifi_signal_3.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 55 | 58 | 61 | 65 | 66 | -------------------------------------------------------------------------------- /gui/assets/wifi_signal_2.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 55 | 59 | 62 | 66 | 67 | -------------------------------------------------------------------------------- /gui/assets/wifi_signal_1.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 20 | 22 | image/svg+xml 23 | 25 | 26 | 27 | 28 | 29 | 31 | 51 | 55 | 59 | 63 | 67 | 68 | -------------------------------------------------------------------------------- /sonoffS20.md: -------------------------------------------------------------------------------- 1 | # Using EmonESP on the Sonoff S20 smart plug 2 | 3 | This guide details how to use EmonESP on the Sonoff S20 smart plug. The SonOff can then be turned on and off via MQTT or/and used in conjunction with the emoncms demandshaper module for appliance scheduling based on the best time to use from a grid or/and carbon perspective. See: 4 | 5 | [https://github.com/emoncms/demandshaper](https://github.com/emoncms/demandshaper) 6 | 7 | **Sonoff S20 programmer:** 8 | 9 | A standard USB to UART programing cable can be used to upload firmware to the SonOffS20 smart plug. Connect up power and RX/TX lines to the 4 pin programming connector on the plug: 10 | 11 | ![sonoffs20.png](docs/sonoffs20.png) 12 | 13 | To upload firmware, hold down the push button, reset the power to the plug (unplug and plug the programmer back in) and then keep holding down the push button until the firmware upload progress starts. 14 | 15 | **Safety: Disconnect the plug from the mains when programming** 16 | It's advised not to connect to the mains when the case is open from a safety perspective for obvious reasons. 17 | 18 | **Firmware:** 19 | 20 | Download EmonESP: 21 | 22 | git clone https://github.com/openenergymonitor/EmonESP.git 23 | 24 | **Upload pre-compiled binaries** 25 | 26 | [https://github.com/openenergymonitor/EmonESP/tree/master/compiled/sonoff](https://github.com/openenergymonitor/EmonESP/tree/master/compiled/sonoff) 27 | 28 | **Compilation using PlatformIO:** 29 | 30 | cd EmonESP 31 | pio run -esmartplug -t upload 32 | pio run -esmartplug -t uploadfs 33 | 34 | **Compilation using Arduino:** 35 | 36 | Sonoff S20 smart plugs can have either the ESP8266 or **ESP8285** core. 37 | 38 | Select in Arduino > Tools: 39 | 40 | - Generic ESP8285 Module 41 | - Flash Size "1M (512K SPIFFS)" 42 | 43 | Compile and upload both the firmware and the sketch data. 44 | 45 | 46 | ### Setup 47 | 48 | 1. SmartPlug creates a WIFI access point, connect to access point, enter home WIFI network. 49 | 50 | 2. To use the plug with the emoncms demand shaper module see the user guide at the bottom of the readme here: [https://github.com/emoncms/demandshaper](https://github.com/emoncms/demandshaper). 51 | 52 | 3. Alternatively to control the Sonoff S20 directly via MQTT. Enter the MQTT server details on the EmonESP configuration page. 53 | 54 | To turn the plug on, publish a topic:message of the following format: 55 | 56 | topic message 57 | emon/smartplug1/status 1 58 | 59 | To turn the plug off, publish a topic:message of the following format: 60 | 61 | topic message 62 | emon/smartplug1/status 0 63 | -------------------------------------------------------------------------------- /src/urlencode.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Based on: https://github.com/zenmanenergy/ESP8266-Arduino-Examples/blob/master/helloWorld_urlencoded/urlencode.ino 3 | 4 | ESP8266 Hello World urlencode by Steve Nelson 5 | URLEncoding is used all the time with internet urls. This is how urls handle funny characters 6 | in a URL. For example a space is: %20 7 | 8 | These functions simplify the process of encoding and decoding the urlencoded format. 9 | 10 | It has been tested on an esp12e (NodeMCU development board) 11 | This example code is in the public domain, use it however you want. 12 | 13 | Prerequisite Examples: 14 | https://github.com/zenmanenergy/ESP8266-Arduino-Examples/tree/master/helloworld_serial 15 | 16 | */ 17 | 18 | #include "urlencode.h" 19 | 20 | static unsigned char h2int(char c); 21 | 22 | String urldecode(String str) 23 | { 24 | 25 | String encodedString=""; 26 | char c; 27 | char code0; 28 | char code1; 29 | for (unsigned int i =0; i < str.length(); i++){ 30 | c=str.charAt(i); 31 | if (c == '+'){ 32 | encodedString+=' '; 33 | }else if (c == '%') { 34 | i++; 35 | code0=str.charAt(i); 36 | i++; 37 | code1=str.charAt(i); 38 | c = (h2int(code0) << 4) | h2int(code1); 39 | encodedString+=c; 40 | } else{ 41 | 42 | encodedString+=c; 43 | } 44 | 45 | yield(); 46 | } 47 | 48 | return encodedString; 49 | } 50 | 51 | String urlencode(String str) 52 | { 53 | String encodedString=""; 54 | char c; 55 | char code0; 56 | char code1; 57 | //char code2; 58 | for (unsigned int i =0; i < str.length(); i++){ 59 | c=str.charAt(i); 60 | if (c == ' '){ 61 | encodedString+= '+'; 62 | } else if (isalnum(c)){ 63 | encodedString+=c; 64 | } else{ 65 | code1=(c & 0xf)+'0'; 66 | if ((c & 0xf) >9){ 67 | code1=(c & 0xf) - 10 + 'A'; 68 | } 69 | c=(c>>4)&0xf; 70 | code0=c+'0'; 71 | if (c > 9){ 72 | code0=c - 10 + 'A'; 73 | } 74 | //code2='\0'; 75 | encodedString+='%'; 76 | encodedString+=code0; 77 | encodedString+=code1; 78 | //encodedString+=code2; 79 | } 80 | yield(); 81 | } 82 | return encodedString; 83 | 84 | } 85 | 86 | static unsigned char h2int(char c) 87 | { 88 | if (c >= '0' && c <='9'){ 89 | return((unsigned char)c - '0'); 90 | } 91 | if (c >= 'a' && c <='f'){ 92 | return((unsigned char)c - 'a' + 10); 93 | } 94 | if (c >= 'A' && c <='F'){ 95 | return((unsigned char)c - 'A' + 10); 96 | } 97 | return(0); 98 | } 99 | -------------------------------------------------------------------------------- /gui/src/ViewModels/ConfigViewModel.js: -------------------------------------------------------------------------------- 1 | function ConfigViewModel() { 2 | BaseViewModel.call(this, { 3 | "node_name": "emonESP", 4 | "node_description":"WiFi EmonCMS Link for CircuitSetup Energy Meters", 5 | "node_type": "", 6 | "ssid": "", 7 | "pass": "", 8 | "emoncms_enabled": false, 9 | "emoncms_server": "data.openevse.com", 10 | "emoncms_path": "/emoncms", 11 | "emoncms_apikey": "", 12 | "emoncms_node": "", 13 | "emoncms_fingerprint": "", 14 | "mqtt_enabled": false, 15 | "mqtt_server": "", 16 | "mqtt_port": "", 17 | "mqtt_topic": "", 18 | "mqtt_feed_prefix": "", 19 | "mqtt_user": "", 20 | "mqtt_pass": "", 21 | "www_username": "", 22 | "www_password": "", 23 | "voltage_cal": "", 24 | "ct1_cal": "", 25 | "ct2_cal": "", 26 | "freq_cal": "", 27 | "gain_cal": "", 28 | "espflash": "", 29 | "version": "0.0.0", 30 | "timer_start1":"", 31 | "timer_stop1":"", 32 | "timer_start2":"", 33 | "timer_stop2":"", 34 | "voltage_output":"", 35 | "time_offset":"" 36 | }, baseEndpoint + "/config"); 37 | 38 | this.f_timer_start1 = ko.pureComputed({ 39 | read: function () { 40 | return addcolon(this.timer_start1()); 41 | }, 42 | write: function (value) { 43 | this.timer_start1(value.replace(":","")); 44 | }, 45 | owner: this 46 | }); 47 | this.f_timer_stop1 = ko.pureComputed({ 48 | read: function () { 49 | return addcolon(this.timer_stop1()); 50 | }, 51 | write: function (value) { 52 | this.timer_stop1(value.replace(":","")); 53 | }, 54 | owner: this 55 | }); 56 | this.f_timer_start2 = ko.pureComputed({ 57 | read: function () { 58 | return addcolon(this.timer_start2()); 59 | }, 60 | write: function (value) { 61 | this.timer_start2(value.replace(":","")); 62 | }, 63 | owner: this 64 | }); 65 | this.f_timer_stop2 = ko.pureComputed({ 66 | read: function () { 67 | return addcolon(this.timer_stop2()); 68 | }, 69 | write: function (value) { 70 | this.timer_stop2(value.replace(":","")); 71 | }, 72 | owner: this 73 | }); 74 | this.flowT = ko.pureComputed({ 75 | read: function () { 76 | return (this.voltage_output()*0.0371)+7.14; 77 | }, 78 | write: function (value) { 79 | this.voltage_output((value-7.14)/0.0371); 80 | }, 81 | owner: this 82 | }); 83 | } 84 | ConfigViewModel.prototype = Object.create(BaseViewModel.prototype); 85 | ConfigViewModel.prototype.constructor = ConfigViewModel; 86 | -------------------------------------------------------------------------------- /src/ota.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #include "app_config.h" 28 | #include "emonesp.h" 29 | #include "ota.h" 30 | #include "web_server.h" 31 | #include "esp_wifi.h" 32 | #include "http.h" 33 | 34 | // ------------------------------------------------------------------- 35 | //OTA UPDATE SETTINGS 36 | // ------------------------------------------------------------------- 37 | //UPDATE SERVER strings and interfers for upate server 38 | // Array of strings Used to check firmware version 39 | //const char* u_host = "217.9.195.227"; 40 | //const char* u_url = "/esp/firmware.php"; 41 | 42 | void ota_setup() 43 | { 44 | // Start local OTA update server 45 | ArduinoOTA.setHostname(node_name.c_str()); 46 | ArduinoOTA.begin(); 47 | #ifdef WIFI_LED 48 | ArduinoOTA.onProgress([](unsigned int pos, unsigned int size) { 49 | static int state = LOW; 50 | state = !state; 51 | digitalWrite(WIFI_LED, state); 52 | }); 53 | #endif 54 | } 55 | 56 | void ota_loop() 57 | { 58 | ArduinoOTA.handle(); 59 | } 60 | 61 | String ota_get_latest_version() 62 | { 63 | // Get latest firmware version number 64 | //String url = u_url; 65 | //return get_http(u_host, url); 66 | } 67 | 68 | #ifdef ESP8266 69 | t_httpUpdate_return ota_http_update() 70 | { 71 | t_httpUpdate_return ret = ESPhttpUpdate.update("http://" + String(u_host) + String(u_url) + "?tag=" + currentfirmware); 72 | return ret; 73 | } 74 | #endif 75 | -------------------------------------------------------------------------------- /src/web_static/web_server.wifi_signal_5.svg.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_WIFI_SIGNAL_5_SVG[] PROGMEM = 2 | "\n" 3 | "\n" 18 | " \n" 20 | " \n" 21 | " \n" 23 | " image/svg+xml\n" 24 | " \n" 26 | " \n" 27 | " \n" 28 | " \n" 29 | " \n" 30 | " \n" 32 | " \n" 52 | " \n" 55 | "\n"; 56 | -------------------------------------------------------------------------------- /src/http.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_HTTP_H 28 | #define _EMONESP_HTTP_H 29 | 30 | // ------------------------------------------------------------------- 31 | // HTTP(S) support functions 32 | // ------------------------------------------------------------------- 33 | 34 | #include 35 | #include 36 | #ifdef ESP32 37 | #include // http GET request 38 | #include // Secure https GET request 39 | #include 40 | #elif defined(ESP8266) 41 | #include 42 | #include 43 | #endif 44 | 45 | /* 46 | #ifdef ESP32 47 | // ------------------------------------------------------------------- 48 | // HTTP or HTTPS GET Request 49 | // url: N/A 50 | // ------------------------------------------------------------------- 51 | extern String get_http(const char * host, String url, int port=80, const char * fingerprint=NULL); 52 | 53 | #elif defined(ESP8266) 54 | */ 55 | // ------------------------------------------------------------------- 56 | // HTTPS SECURE GET Request 57 | // url: N/A 58 | // ------------------------------------------------------------------- 59 | extern String get_https(const char* fingerprint, const char* host, String &path, int httpsPort); 60 | 61 | // ------------------------------------------------------------------- 62 | // HTTP GET Request 63 | // url: N/A 64 | // ------------------------------------------------------------------- 65 | extern String get_http(const char* host, String &path); 66 | //#endif 67 | 68 | #endif // _EMONESP_HTTP_H 69 | -------------------------------------------------------------------------------- /test.http: -------------------------------------------------------------------------------- 1 | # Name: REST Client 2 | # Id: humao.rest-client 3 | # Description: REST Client for Visual Studio Code 4 | # Version: 0.21.3 5 | # Publisher: Huachao Mao 6 | # VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=humao.rest-client 7 | 8 | # You should use environment vars (https://marketplace.visualstudio.com/items?itemName=humao.rest-client#environment-variables) for these 9 | # but you can also set here if needed (just don't check in!) 10 | 11 | #@baseUrl = http://emonesp.local 12 | 13 | #@ssid = your_ssid 14 | #@pass = your_password 15 | #@apikey = your_key 16 | 17 | ### 18 | # @name index 19 | GET {{baseUrl}}/ HTTP/1.1 20 | 21 | ### 22 | 23 | @lastModified = {{index.response.headers.Last-Modified}} 24 | 25 | GET {{baseUrl}}/ HTTP/1.1 26 | If-Modified-Since: {{lastModified}} 27 | 28 | ### 29 | 30 | GET {{baseUrl}}/status HTTP/1.1 31 | 32 | ### 33 | 34 | GET {{baseUrl}}/config HTTP/1.1 35 | 36 | ### 37 | 38 | GET {{baseUrl}}/lastvalues HTTP/1.1 39 | 40 | ### 41 | 42 | POST {{baseUrl}}/config HTTP/1.1 43 | Content-Type: application/json 44 | 45 | { 46 | "emoncms_enabled": true, 47 | "emoncms_server": "emoncms.org", 48 | "emoncms_node": "emonesp_test", 49 | "emoncms_apikey": "{{apikey_emoncms}}", 50 | "emoncms_fingerprint": "" 51 | } 52 | 53 | ### 54 | 55 | POST {{baseUrl}}/config HTTP/1.1 56 | Content-Type: application/json 57 | 58 | { 59 | "emoncms_enabled": false 60 | } 61 | 62 | ### 63 | 64 | POST {{baseUrl}}/config HTTP/1.1 65 | Content-Type: application/json 66 | 67 | { 68 | "mqtt_enabled": true, 69 | "mqtt_server": "home.lan", 70 | "mqtt_port": 1883, 71 | "mqtt_topic": "emonesp_test", 72 | "mqtt_user": "emonpi", 73 | "mqtt_pass": "emonpimqtt2016" 74 | } 75 | 76 | ### 77 | 78 | POST {{baseUrl}}/config HTTP/1.1 79 | Content-Type: application/json 80 | 81 | { 82 | "mqtt_enabled": false 83 | } 84 | 85 | ### 86 | 87 | GET {{baseUrl}}/upload 88 | 89 | ### 90 | 91 | POST {{baseUrl}}/upload 92 | Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW 93 | 94 | ----WebKitFormBoundary7MA4YWxkTrZu0gW 95 | Content-Disposition: form-data; name="image"; filename="firmware.bin" 96 | Content-Type: application/octet-stream 97 | 98 | < ./.pio/build/emonesp/firmware.bin 99 | ----WebKitFormBoundary7MA4YWxkTrZu0gW 100 | 101 | ### 102 | 103 | GET {{baseUrl}}/debug HTTP/1.1 104 | 105 | ### 106 | 107 | GET {{baseUrl}}/emontx HTTP/1.1 108 | 109 | ### 110 | 111 | POST {{baseUrl}}/savenetwork HTTP/1.1 112 | Content-Type: application/x-www-form-urlencoded 113 | 114 | ssid={{ssid}}&pass={{pass}} 115 | 116 | ### 117 | 118 | GET {{baseUrl}}/scan HTTP/1.1 119 | 120 | ### 121 | 122 | GET {{baseUrl}}/firmware HTTP/1.1 123 | 124 | ### 125 | 126 | GET {{baseUrl}}/update HTTP/1.1 127 | 128 | ### 129 | 130 | GET {{baseUrl}}/restart HTTP/1.1 131 | 132 | -------------------------------------------------------------------------------- /src/web_server.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_WEB_SERVER_H 28 | #define _EMONESP_WEB_SERVER_H 29 | 30 | #include 31 | #ifdef ESP32 32 | #include 33 | #include //https://github.com/me-no-dev/AsyncTCP 34 | #elif defined(ESP8266) 35 | #include 36 | #include 37 | #include 38 | #endif 39 | #include 40 | #include 41 | // Content Types 42 | extern const char _CONTENT_TYPE_HTML[]; 43 | #define CONTENT_TYPE_HTML FPSTR(_CONTENT_TYPE_HTML) 44 | 45 | extern const char _CONTENT_TYPE_TEXT[]; 46 | #define CONTENT_TYPE_TEXT FPSTR(_CONTENT_TYPE_TEXT) 47 | 48 | extern const char _CONTENT_TYPE_CSS[]; 49 | #define CONTENT_TYPE_CSS FPSTR(_CONTENT_TYPE_CSS) 50 | 51 | extern const char _CONTENT_TYPE_JSON[]; 52 | #define CONTENT_TYPE_JSON FPSTR(_CONTENT_TYPE_JSON) 53 | 54 | extern const char _CONTENT_TYPE_JS[]; 55 | #define CONTENT_TYPE_JS FPSTR(_CONTENT_TYPE_JS) 56 | 57 | extern const char _CONTENT_TYPE_JPEG[]; 58 | #define CONTENT_TYPE_JPEG FPSTR(_CONTENT_TYPE_JPEG) 59 | 60 | extern const char _CONTENT_TYPE_PNG[]; 61 | #define CONTENT_TYPE_PNG FPSTR(_CONTENT_TYPE_PNG) 62 | 63 | extern const char _CONTENT_TYPE_SVG[]; 64 | #define CONTENT_TYPE_SVG FPSTR(_CONTENT_TYPE_SVG) 65 | 66 | extern AsyncWebServer server; 67 | extern String currentfirmware; 68 | 69 | extern void web_server_setup(); 70 | extern void web_server_loop(); 71 | 72 | extern void web_server_event(JsonDocument &event); 73 | 74 | extern void dumpRequest(AsyncWebServerRequest *request); 75 | 76 | #endif // _EMONESP_WEB_SERVER_H 77 | -------------------------------------------------------------------------------- /src/web_static/web_server.wifi_signal_4.svg.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_WIFI_SIGNAL_4_SVG[] PROGMEM = 2 | "\n" 3 | "\n" 18 | " \n" 20 | " \n" 21 | " \n" 23 | " image/svg+xml\n" 24 | " \n" 26 | " \n" 27 | " \n" 28 | " \n" 29 | " \n" 30 | " \n" 32 | " \n" 52 | " \n" 55 | " \n" 58 | " \n" 61 | " \n" 65 | "\n"; 66 | -------------------------------------------------------------------------------- /src/web_static/web_server.wifi_signal_3.svg.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_WIFI_SIGNAL_3_SVG[] PROGMEM = 2 | "\n" 3 | "\n" 18 | " \n" 20 | " \n" 21 | " \n" 23 | " image/svg+xml\n" 24 | " \n" 26 | " \n" 27 | " \n" 28 | " \n" 29 | " \n" 30 | " \n" 32 | " \n" 52 | " \n" 56 | " \n" 59 | " \n" 62 | " \n" 66 | "\n"; 67 | -------------------------------------------------------------------------------- /src/web_static/web_server.wifi_signal_2.svg.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_WIFI_SIGNAL_2_SVG[] PROGMEM = 2 | "\n" 3 | "\n" 18 | " \n" 20 | " \n" 21 | " \n" 23 | " image/svg+xml\n" 24 | " \n" 26 | " \n" 27 | " \n" 28 | " \n" 29 | " \n" 30 | " \n" 32 | " \n" 52 | " \n" 56 | " \n" 60 | " \n" 63 | " \n" 67 | "\n"; 68 | -------------------------------------------------------------------------------- /src/web_static/web_server.wifi_signal_1.svg.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_WIFI_SIGNAL_1_SVG[] PROGMEM = 2 | "\n" 3 | "\n" 18 | " \n" 20 | " \n" 21 | " \n" 23 | " image/svg+xml\n" 24 | " \n" 26 | " \n" 27 | " \n" 28 | " \n" 29 | " \n" 30 | " \n" 32 | " \n" 52 | " \n" 56 | " \n" 60 | " \n" 64 | " \n" 68 | "\n"; 69 | -------------------------------------------------------------------------------- /src/input.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #include "emonesp.h" 28 | #include "input.h" 29 | #include "emoncms.h" 30 | #include "espal.h" 31 | 32 | char input_string[MAX_DATA_LEN] = ""; 33 | char last_datastr[MAX_DATA_LEN] = ""; 34 | 35 | boolean input_get(JsonDocument &data) 36 | { 37 | boolean gotLine = false; 38 | boolean gotData = false; 39 | char line[MAX_DATA_LEN]; 40 | 41 | // If data from test API e.g `http:///input?string=CT1:3935,CT2:325,T1:12.5,T2:16.9,T3:11.2,T4:34.7` 42 | if(strlen(input_string) > 0) { 43 | strcpy(line, input_string); 44 | strcpy(input_string, ""); 45 | gotLine = true; 46 | } 47 | 48 | if(gotLine) { 49 | int len = strlen(line); 50 | if(len > 0) { 51 | //DEBUG.printf_P(PSTR("Got '%s'\n"), line); 52 | for(int i = 0; i < len; i++) { 53 | String name = ""; 54 | 55 | // Get the name 56 | while (i < len && line[i] != ':') { 57 | name += line[i++]; 58 | } 59 | 60 | if (i++ >= len) { 61 | break; 62 | } 63 | 64 | // Get the value 65 | String value = ""; 66 | while (i < len && line[i] != ','){ 67 | value += line[i++]; 68 | } 69 | 70 | //DBUGVAR(name); 71 | //DBUGVAR(value); 72 | 73 | if(name.length() > 0 && value.length() > 0) { 74 | // IMPROVE: check that value is only a number, toDouble() will skip white space and and chars after the number 75 | data[name] = value.toDouble(); 76 | gotData = true; 77 | } 78 | } 79 | } 80 | } 81 | 82 | // Append some system info 83 | if(gotData) { 84 | data[F("freeram")] = ESPAL.getFreeHeap(); 85 | data[F("srssi")] = WiFi.RSSI(); 86 | data[F("psent")] = packets_sent; 87 | data[F("psuccess")] = packets_success; 88 | 89 | last_datastr[0] = '\0'; 90 | serializeJson(data, last_datastr); 91 | } 92 | 93 | return gotData; 94 | } 95 | -------------------------------------------------------------------------------- /src/mqtt.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_MQTT_H 28 | #define _EMONESP_MQTT_H 29 | 30 | // ------------------------------------------------------------------- 31 | // MQTT support 32 | // ------------------------------------------------------------------- 33 | 34 | #include 35 | #include 36 | #include // MQTT https://github.com/knolleary/pubsubclient PlatformIO lib: 89 37 | #include 38 | 39 | #ifndef MQTT_CONNECT_TIMEOUT 40 | #define MQTT_CONNECT_TIMEOUT (5 * 1000) 41 | #endif // !MQTT_CONNECT_TIMEOUT 42 | 43 | // ------------------------------------------------------------------- 44 | // Perform the background MQTT operations. Must be called in the main 45 | // loop function 46 | // ------------------------------------------------------------------- 47 | extern void mqtt_loop(); 48 | 49 | // ------------------------------------------------------------------- 50 | // Generic Publish to MQTT 51 | // ------------------------------------------------------------------- 52 | extern void mqtt_publish(String topic, String data); 53 | 54 | // ------------------------------------------------------------------- 55 | // Publish values to MQTT 56 | // 57 | // data: a comma seperated list of name:value pairs to send 58 | // ------------------------------------------------------------------- 59 | extern void mqtt_publish(JsonDocument &event); 60 | 61 | // ------------------------------------------------------------------- 62 | // Restart the MQTT connection 63 | // ------------------------------------------------------------------- 64 | extern void mqtt_restart(); 65 | 66 | 67 | // ------------------------------------------------------------------- 68 | // Return true if we are connected to an MQTT broker, false if not 69 | // ------------------------------------------------------------------- 70 | extern boolean mqtt_connected(); 71 | 72 | String uint64ToString(uint64_t input); 73 | 74 | #endif // _EMONESP_MQTT_H 75 | -------------------------------------------------------------------------------- /src/web_static/web_server.style.css.h: -------------------------------------------------------------------------------- 1 | static const char CONTENT_STYLE_CSS[] PROGMEM = 2 | "body{font-family:Arial,\"sans-serif\";font-size:14px;background-color:#fff;color:#777;margin:0}#page,body{width:100%;height:100%}#page{max-width:1024px;margin:0 auto;overflow-x:hidden}#container{width:100%;margin-bottom:40px}#eight,#five,#four,#nine,#one,#seven,#six,#three,#two{background-color:#f1f1f1;box-sizing:border-box;margin:5px}.hide{display:none}.itembody-wrapper{padding:5px 25px}.scrollable-logs{overflow:auto;max-height:500px}input[type=password],input[type=text]{box-sizing:border-box;border-radius:4px;padding:10px;margin-top:10px;width:280px}@media screen and (max-width:420px){input[type=password],input[type=text]{width:240px}}#update{background-color:red}.small-text{font-size:10px;word-wrap:break-word}table{border-collapse:collapse;margin:10px auto 0;max-width:380px;max-height:100px;border-style:none}th{background-color:#777;color:#fff}td,th{border:1px solid #f1f1f1;padding:8px}td{background-color:#fff;color:#777}p{padding-top:10px}a{text-decoration:none;color:#0699fa}a,h2{cursor:pointer}h2{font-size:20px;background-color:#82afcc;color:#fff;padding:10px 15px;margin:0;font-weight:400}h2:hover{background-color:#98c4e1}button{background-color:#0699fa;border-radius:10px;border:none;color:#fff;padding:13px 28px;text-align:center;text-decoration:none;display:inline-block;font-size:15px;font-weight:700;margin:4px 2px;cursor:pointer;outline:none}button:disabled{background-color:#eee;color:#ccc}.box{padding-top:5px;padding-bottom:5px;margin:0}.box h1{font-size:40px;font-weight:700;font-family:Arial,\"sans-serif\";text-align:center;margin:0}.box h1 span{color:#82afcc}.box h3{padding-top:7px;font-size:19px;color:#b6b6b6;margin:-6px 0 0}#footer-small-scrn,.box h3{font-weight:700;font-family:Arial,\"sans-serif\";text-align:center}#footer-small-scrn{width:100%;font-size:15px;padding:5px;color:#bcbcbc}#footer-large-scrn{display:none}@media screen and (min-width:1024px){#footer-small-scrn{display:none}}@media screen and (min-width:1024px){#footer-large-scrn{position:fixed;display:block;width:100%;font-weight:700;font-family:Arial,\"sans-serif\";font-size:15px;text-align:center;padding:5px;color:#bcbcbc;bottom:0;background-color:#fff}}#footer-large-scrn a,#footer-small-scrn a{color:#0699fa}#footer-large-scrn span,#footer-small-scrn span{color:#777}.loading{margin-top:40px;margin-bottom:40px;text-align:center}.btn-group button{width:auto;height:40px;padding:10px 20px;border:none;background-color:#666;color:#fff;cursor:pointer;border-radius:0;margin:0}.btn-group .active{background-color:#888}.btn-group .green{background-color:#76de32}.btn-group .red{background-color:#ff5d32}.btn-group button:first-child{border-radius:20px 0 0 20px}.btn-group button:last-child{border-radius:0 20px 20px 0}#wifiList{display:inline-block;width:100%;max-height:230px;overflow:auto}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0}.list-group-item{position:relative;display:flex;padding:.75rem 1.25rem;margin-bottom:-1px;background-color:#fff;border:1px solid rgba(0,0,0,.125);justify-content:space-between;align-items:center}.list-group-item.active{z-index:2;color:#fff;background-color:#0699fa;border-color:#0699fa}.list-group-item:first-child{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}#progressBack{width:100%;border:1px solid #0699fa;border-radius:4px}#progressBar{width:1%;height:30px;background-color:#0699fa}\n"; 3 | -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = emonesp-esp32 13 | data_dir = src/data 14 | 15 | [common] 16 | version = -DBUILD_TAG=3.2 17 | framework = arduino 18 | board_build.flash_mode = dout 19 | lib_deps = 20 | PubSubClient@^2.6 21 | esp32async/ESPAsyncWebServer@^3.7.7 22 | arduino-libraries/NTPClient@^3.1.0 23 | ArduinoJson@^6.16.1 24 | Micro Debug@^0.0.4 25 | ConfigJson@^0.0.4 26 | ESPAL@^0.0.2 27 | StreamSpy@^0.0.2 28 | ATM90E32@^1.0 29 | adafruit/Adafruit GFX Library@^1.10.4 ;for oled display 30 | adafruit/Adafruit BusIO@^1.7.1 31 | adafruit/Adafruit SSD1306@^2.4.1 ;for oled display 32 | build_flags = 33 | #-DENABLE_OLED_DISPLAY ;Enables support for an OLED display 34 | #-DEXPORT_METERING_VALS ;Exports more detailed data to EmonESP 35 | #-DSOLAR_METER ;For the split single phase solar adapter 36 | #-DENABLE_WDT ;Watchdog Timer will reset ESP if it does not respond within 5 seconds 37 | -mtarget-align 38 | -Wl,-Map,firmware.map 39 | build_unflags = -Wall 40 | build_src_flags = 41 | -DDEBUG_PORT=Serial 42 | -ggdb 43 | -DWIFI_LED=LED_BUILTIN 44 | -DWIFI_LED_ON_STATE=LOW 45 | board_build.f_cpu = 80000000L 46 | monitor_speed = 115200 47 | ;upload_port = COM1 48 | upload_speed = 921600 49 | upload_resetmethod = nodemcu 50 | extra_scripts = scripts/extra_script.py 51 | 52 | [env:emonesp-esp32] 53 | platform = platformio/espressif32 54 | board = nodemcu-32s 55 | framework = arduino 56 | platform_packages = platformio/framework-arduinoespressif32 57 | lib_deps = 58 | ${common.lib_deps} 59 | esp32async/AsyncTCP@^3.4.2 60 | build_src_flags = 61 | ${common.version} 62 | ${common.build_src_flags} 63 | build_flags = 64 | ${common.build_flags} 65 | ;upload_port = ${common.upload_port} 66 | upload_speed = 921600 67 | monitor_speed = 115200 68 | extra_scripts = ${common.extra_scripts} 69 | board_build.mcu = esp32 70 | board_build.flash_mode = dio 71 | 72 | [env:emonesp-esp8266] 73 | platform = espressif8266 74 | board = nodemcuv2 75 | framework = arduino 76 | lib_deps = 77 | ${common.lib_deps} 78 | ESPAsyncTCP@^1.2.3 79 | build_src_flags = 80 | ${common.version} 81 | ${common.build_src_flags} 82 | build_flags = 83 | ${common.build_flags} 84 | ;upload_port = ${common.upload_port} 85 | upload_speed = 921600 86 | monitor_speed = 115200 87 | extra_scripts = ${common.extra_scripts} 88 | board_build.mcu = esp8266 89 | board_build.flash_mode = dio 90 | 91 | [env:debug] 92 | extends = env:emonesp-esp32 93 | build_flags = 94 | ${common.build_flags} 95 | -DCORE_DEBUG_LEVEL=5 96 | -DENABLE_DEBUG 97 | -DENABLE_DEBUG_EMONCMS 98 | -DENABLE_DEBUG_WEB 99 | 100 | [env:emonesp_esp32_staging] 101 | extends = env:emonesp-esp32 102 | platform_packages = 103 | framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git 104 | lib_deps = 105 | https://github.com/knolleary/pubsubclient, 106 | https://github.com/me-no-dev/ESPAsyncWebServer.git, 107 | https://github.com/me-no-dev/AsyncTCP.git, 108 | https://github.com/CircuitSetup/ATM90E32.git 109 | build_flags = 110 | ${common.build_flags} 111 | -DCORE_DEBUG_LEVEL=5 112 | -DENABLE_DEBUG 113 | -DENABLE_DEBUG_EMONCMS 114 | -DENABLE_DEBUG_WEB -------------------------------------------------------------------------------- /guide/esp_dht22_emoncms/esp_dht22_emoncms.ino: -------------------------------------------------------------------------------- 1 | //simple sketch reading from DHT22 and posting up to emoncms.org using the ESP8266 wifi module. 2 | // This file is part of OpenEnergyMonitor project. 3 | 4 | 5 | 6 | #include 7 | #include "DHT.h" 8 | #define DHTPIN 12 9 | 10 | 11 | #define DHTTYPE DHT22 // DHT 22 (AM2302), AM2321 12 | 13 | 14 | DHT dht(DHTPIN, DHTTYPE); 15 | 16 | 17 | const char* ssid = "xxxxxxxxxxxxx"; //WIFI NAME 18 | const char* password = "xxxxxxxxxxxxx"; //WIFI PASSWORD 19 | 20 | const char* host = "emoncms.org"; 21 | 22 | 23 | void setup() { 24 | Serial.begin(9600);//Baud rate 25 | delay(10); 26 | 27 | 28 | 29 | Serial.println(); 30 | Serial.println(); 31 | Serial.print("Connecting to "); 32 | Serial.println(ssid); 33 | 34 | WiFi.begin(ssid, password); 35 | 36 | while (WiFi.status() != WL_CONNECTED) { 37 | delay(500); 38 | Serial.print("."); 39 | } 40 | 41 | Serial.println(""); 42 | Serial.println("WiFi connected"); 43 | Serial.println("IP address: "); 44 | Serial.println(WiFi.localIP()); 45 | 46 | 47 | dht.begin(); 48 | } 49 | 50 | int value = 0; 51 | 52 | void loop() { 53 | delay(2000); 54 | ++value; 55 | // DHT22_ERROR_t errorCode; 56 | 57 | 58 | 59 | Serial.print("Requesting data..."); 60 | // errorCode = DHT.readData(); 61 | // switch(errorCode) 62 | { 63 | 64 | 65 | float h = dht.readHumidity(); 66 | // Read temperature as Celsius (the default) 67 | float t = dht.readTemperature(); 68 | 69 | float f = dht.readTemperature(true); 70 | 71 | // Check if any reads failed and exit early (to try again). 72 | if (isnan(h) || isnan(t) || isnan(f)) { 73 | Serial.println("Failed to read from DHT sensor!"); 74 | return; 75 | } 76 | Serial.print("connecting to "); 77 | Serial.println(host); 78 | 79 | // Use WiFiClient class to create TCP connections 80 | WiFiClient client; 81 | const int httpPort = 80; 82 | if (!client.connect(host, httpPort)) { 83 | Serial.println("connection failed"); 84 | return; 85 | } 86 | 87 | // Compute heat index in Fahrenheit (the default) 88 | float hif = dht.computeHeatIndex(f, h); 89 | // Compute heat index in Celsius (isFahreheit = false) 90 | float hic = dht.computeHeatIndex(t, h, false); 91 | Serial.print("\n"); 92 | Serial.print("Humidity: "); 93 | Serial.print(h); 94 | Serial.print(" %\n"); 95 | Serial.print("Temperature: "); 96 | Serial.print(t); 97 | Serial.print(" *C\n "); 98 | 99 | // We now create a URI for the request 100 | String url = "/input/post.json?csv="+String(h)+","+String(t)+"&apikey=XXXXXXXXXXXXXXXXXXXXX"; //Enter api key here 101 | 102 | Serial.print("Requesting URL: "); 103 | Serial.println(url); 104 | 105 | // This will send the request to the server 106 | client.print(String("GET ") + url + " HTTP/1.1\r\n" + 107 | "Host: " + host + "\r\n" + 108 | "Connection: close\r\n\r\n"); 109 | unsigned long timeout = millis(); 110 | while (client.available() == 0) { 111 | if (millis() - timeout > 5000) { 112 | Serial.println(">>> Client Timeout !"); 113 | client.stop(); 114 | return; 115 | } 116 | } 117 | 118 | // Read all the lines of the reply from server and print them to Serial 119 | while(client.available()){ 120 | String line = client.readStringUntil('\r'); 121 | Serial.print(line); 122 | } 123 | 124 | Serial.println(); 125 | Serial.println("closing connection"); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/emonesp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_H 28 | #define _EMONESP_H 29 | 30 | #include 31 | #include "debug.h" 32 | #include "profile.h" 33 | 34 | String getTime(); 35 | 36 | void setTimeOffset(); 37 | 38 | //#define ENABLE_WDT 39 | 40 | #ifndef MAX_DATA_LEN 41 | #define MAX_DATA_LEN 4096 42 | #endif 43 | 44 | #ifndef WIFI_LED 45 | #define WIFI_LED 2 46 | #endif 47 | 48 | #ifdef WIFI_LED 49 | 50 | #ifndef WIFI_LED_ON_STATE 51 | #define WIFI_LED_ON_STATE LOW 52 | #endif 53 | 54 | //the time the LED actually stays on 55 | #ifndef WIFI_LED_ON_TIME 56 | #define WIFI_LED_ON_TIME 50 57 | #endif 58 | 59 | //time the LED is off in AP mode... 60 | #ifndef WIFI_LED_AP_TIME 61 | #define WIFI_LED_AP_TIME 2000 62 | #endif 63 | 64 | #ifndef WIFI_LED_AP_CONNECTED_TIME 65 | #define WIFI_LED_AP_CONNECTED_TIME 1000 66 | #endif 67 | 68 | #ifndef WIFI_LED_STA_CONNECTING_TIME 69 | #define WIFI_LED_STA_CONNECTING_TIME 500 70 | #endif 71 | 72 | #ifndef WIFI_LED_STA_CONNECTED_TIME 73 | #define WIFI_LED_STA_CONNECTED_TIME 4000 74 | #endif 75 | 76 | #endif 77 | 78 | #ifndef WIFI_BUTTON_AP_TIMEOUT 79 | #define WIFI_BUTTON_AP_TIMEOUT (5 * 1000) 80 | #endif 81 | 82 | #ifndef WIFI_BUTTON_FACTORY_RESET_TIMEOUT 83 | #define WIFI_BUTTON_FACTORY_RESET_TIMEOUT (10 * 1000) 84 | #endif 85 | 86 | #ifndef WIFI_CLIENT_DISCONNECT_RETRY 87 | #define WIFI_CLIENT_DISCONNECT_RETRY (10 * 1000) 88 | #endif 89 | 90 | #ifndef WIFI_CLIENT_RETRY_TIMEOUT 91 | #define WIFI_CLIENT_RETRY_TIMEOUT (5 * 60 * 1000) //5 min 92 | #endif 93 | 94 | #ifndef WIFI_BUTTON 95 | #define WIFI_BUTTON 14 96 | #endif 97 | 98 | #ifndef WIFI_BUTTON_PRESSED_STATE 99 | #define WIFI_BUTTON_PRESSED_STATE LOW 100 | #endif 101 | 102 | #ifndef CONTROL_PIN 103 | #define CONTROL_PIN 13 104 | #endif 105 | 106 | #ifndef CONTROL_PIN_ON_STATE 107 | #define CONTROL_PIN_ON_STATE HIGH 108 | #endif 109 | 110 | #ifndef NODE_TYPE 111 | #define NODE_TYPE "emonesp" 112 | #endif 113 | 114 | #ifndef NODE_DESCRIPTION 115 | #define NODE_DESCRIPTION "WiFi EmonCMS Link for CircuitSetup Energy Meters" 116 | #endif 117 | 118 | #endif // _EMONESP_H 119 | -------------------------------------------------------------------------------- /src/energy_meter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 6 | * All adaptation GNU General Public License as below. 7 | * 8 | * ------------------------------------------------------------------- 9 | * 10 | * This file is part of OpenEnergyMonitor.org project. 11 | * EmonESP is free software; you can redistribute it and/or modify 12 | * it under the terms of the GNU General Public License as published by 13 | * the Free Software Foundation; either version 3, or (at your option) 14 | * any later version. 15 | * EmonESP is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * You should have received a copy of the GNU General Public License 20 | * along with EmonESP; see the file COPYING. If not, write to the 21 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 22 | * Boston, MA 02111-1307, USA. 23 | */ 24 | 25 | #ifndef _ENERGY_METER 26 | #define _ENERGY_METER 27 | 28 | // for ATM90E32 energy meter 29 | #include 30 | #include 31 | 32 | //#define ENABLE_OLED_DISPLAY 33 | 34 | #ifdef ENABLE_OLED_DISPLAY 35 | #include 36 | #include 37 | #define SCREEN_WIDTH 128 // OLED display width, in pixels 38 | #define SCREEN_HEIGHT 64 // OLED display height, in pixels 39 | 40 | // Declaration for SSD1306 display connected using software SPI (default case): 41 | #define OLED_DC 0 42 | #define OLED_CS 16 43 | #define OLED_RESET 17 44 | #endif 45 | 46 | /* 47 | Uncomment if using the solar meter adapter board 48 | */ 49 | //#define SOLAR_METER 50 | 51 | /* 52 | Uncomment to send metering values to EmonCMS, like Fundamental, Harmonic, Reactive, Apparent Power, and Phase Angle 53 | */ 54 | //#define EXPORT_METERING_VALS 55 | 56 | /******************************************************************************* 57 | The following calibration values can be set here or in the EmonESP interface 58 | EmonESP values take priority if they are set 59 | ********************************************************************************/ 60 | 61 | /* 62 | 4485 for 60 Hz (North America) 63 | 389 for 50 hz (rest of the world) 64 | */ 65 | #define LINE_FREQ 4485 66 | 67 | /* 68 | * 0 for 1x (CTs up to 60mA/720mV) 69 | * 21 for 2x (CTs up to 30mA/360mV) 70 | * 42 for 4x (CTs up to 15mA/180mV) 71 | */ 72 | #define PGA_GAIN 0 73 | 74 | /* 75 | For meter <= v1.3: 76 | 42080 - 9v AC Transformer - Jameco 112336 77 | 32428 - 12v AC Transformer - Jameco 167151 78 | For meter = v1.4: 79 | 37106 - 9v AC Transformer - Jameco 157041 80 | 38302 - 9v AC Transformer - Jameco 112336 81 | 29462 - 12v AC Transformer - Jameco 167151 82 | For Meters >= v1.4 rev.3 83 | 3920 - 9v AC Transformer - Jameco 157041 - default 84 | */ 85 | #define VOLTAGE_GAIN 3920 86 | 87 | /* 88 | 10170 - SCT-006 20A/25mA 89 | 39571 - SCT-010 80A/26.6mA 90 | 25498 - SCT-013-000 100A/50mA 91 | 39473 - SCT-016 120A/40mA - default 92 | 46539 - Magnalab 100A 93 | 26315 - SCT-024 200A/100mA 94 | */ 95 | #define CURRENT_GAIN_CT1 39473 96 | #define CURRENT_GAIN_CT2 39473 97 | 98 | #ifdef SOLAR_METER 99 | #define VOLTAGE_GAIN_SOLAR 3920 100 | #define SOLAR_GAIN_CT1 39473 101 | #define SOLAR_GAIN_CT2 39473 102 | #endif 103 | 104 | extern void energy_meter_setup(); 105 | extern void energy_meter_loop(); 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /gui/src/config.js: -------------------------------------------------------------------------------- 1 | // Work out the endpoint to use, for dev you can change to point at a remote ESP 2 | // and run the HTML/JS from file, no need to upload to the ESP to test 3 | 4 | // development folder 5 | var development = ""; 6 | 7 | var baseHost = window.location.hostname; 8 | //var baseHost = 'emonesp.local'; 9 | //var baseHost = '192.168.4.1'; 10 | //var baseHost = '172.16.0.52'; 11 | var basePort = window.location.port; 12 | var baseProtocol = window.location.protocol; 13 | 14 | var baseEndpoint = "//" + baseHost; 15 | if(80 !== basePort) { 16 | baseEndpoint += ":" + basePort; 17 | } 18 | baseEndpoint += development 19 | 20 | var statusupdate = false; 21 | var selected_network_ssid = ""; 22 | var lastmode = ""; 23 | var ipaddress = ""; 24 | 25 | // Convert string to number, divide by scale, return result 26 | // as a string with specified precision 27 | function scaleString(string, scale, precision) { 28 | var tmpval = parseInt(string) / scale; 29 | return tmpval.toFixed(precision); 30 | } 31 | 32 | function addcolon(t) { 33 | t = new String(t); 34 | if (t.length < 3) { 35 | return "00:00"; 36 | } 37 | if (t.length == 3) { 38 | t = "0"+t; 39 | } 40 | return t.substr(0,2)+":"+t.substr(2,4); 41 | } 42 | 43 | $(function () { 44 | // Activates knockout.js 45 | var emonesp = new EmonEspViewModel(baseHost, basePort, baseProtocol); 46 | ko.applyBindings(emonesp); 47 | emonesp.start(); 48 | }); 49 | 50 | // ----------------------------------------------------------------------- 51 | // Event: Turn off Access Point 52 | // ----------------------------------------------------------------------- 53 | document.getElementById("apoff").addEventListener("click", function (e) { 54 | 55 | var r = new XMLHttpRequest(); 56 | r.open("POST", "apoff", true); 57 | r.onreadystatechange = function () { 58 | if (r.readyState != 4 || r.status != 200) 59 | return; 60 | var str = r.responseText; 61 | console.log(str); 62 | document.getElementById("apoff").style.display = "none"; 63 | if (ipaddress !== "") 64 | window.location = "http://" + ipaddress; 65 | }; 66 | r.send(); 67 | }); 68 | 69 | // ----------------------------------------------------------------------- 70 | // Event: Reset config and reboot 71 | // ----------------------------------------------------------------------- 72 | document.getElementById("reset").addEventListener("click", function (e) { 73 | 74 | if (confirm("CAUTION: Do you really want to Factory Reset? All setting and config will be lost.")) { 75 | var r = new XMLHttpRequest(); 76 | r.open("POST", "reset", true); 77 | r.onreadystatechange = function () { 78 | if (r.readyState != 4 || r.status != 200) 79 | return; 80 | var str = r.responseText; 81 | console.log(str); 82 | if (str !== 0) 83 | document.getElementById("reset").innerHTML = "Resetting..."; 84 | }; 85 | r.send(); 86 | } 87 | }); 88 | 89 | // ----------------------------------------------------------------------- 90 | // Event: Restart 91 | // ----------------------------------------------------------------------- 92 | document.getElementById("restart").addEventListener("click", function (e) { 93 | 94 | if (confirm("Restart emonESP? Current config will be saved, takes approximately 10s.")) { 95 | var r = new XMLHttpRequest(); 96 | r.open("POST", "restart", true); 97 | r.onreadystatechange = function () { 98 | if (r.readyState != 4 || r.status != 200) 99 | return; 100 | var str = r.responseText; 101 | console.log(str); 102 | if (str !== 0) 103 | document.getElementById("reset").innerHTML = "Restarting"; 104 | }; 105 | r.send(); 106 | } 107 | }); 108 | 109 | function toggle(id) { 110 | var e = document.getElementById(id); 111 | if(e.style.display == "block") 112 | e.style.display = "none"; 113 | else 114 | e.style.display = "block"; 115 | } 116 | -------------------------------------------------------------------------------- /src/emoncms.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #if defined(ENABLE_DEBUG) && !defined(ENABLE_DEBUG_EMONCMS) 28 | #undef ENABLE_DEBUG 29 | #endif 30 | 31 | #include "emonesp.h" 32 | #include "emoncms.h" 33 | #include "app_config.h" 34 | #include "http.h" 35 | #include "input.h" 36 | #include "event.h" 37 | #include "urlencode.h" 38 | 39 | boolean emoncms_connected = false; 40 | boolean emoncms_updated = false; 41 | 42 | unsigned long packets_sent = 0; 43 | unsigned long packets_success = 0; 44 | unsigned long emoncms_connection_error_count = 0; 45 | 46 | const char *e_url = "/input/post?"; 47 | 48 | static void emoncms_result(bool success, String message) { 49 | StaticJsonDocument<128> event; 50 | 51 | if(success) { 52 | packets_success++; 53 | emoncms_connection_error_count = 0; 54 | } else { 55 | if(++emoncms_connection_error_count > 30) { 56 | #ifdef ESP32 57 | esp_restart(); 58 | #else 59 | ESP.restart(); 60 | #endif 61 | } 62 | } 63 | 64 | if(emoncms_connected != success) { 65 | emoncms_connected = success; 66 | event[F("emoncms_connected")] = (int)emoncms_connected; 67 | event[F("emoncms_message")] = message.substring(0, 64); 68 | event_send(event); 69 | } 70 | } 71 | 72 | void emoncms_publish(JsonDocument &data) { 73 | Profile_Start(emoncms_publish); 74 | 75 | if (config_emoncms_enabled() && emoncms_apikey != 0) { 76 | String url = emoncms_path.c_str(); 77 | String json; 78 | serializeJson(data, json); 79 | url += e_url; 80 | url += F("fulljson="); 81 | url += urlencode(json); 82 | url += F("&node="); 83 | url += emoncms_node; 84 | url += F("&apikey="); 85 | url += emoncms_apikey; 86 | 87 | DBUGVAR(url); 88 | 89 | packets_sent++; 90 | 91 | // Send data to Emoncms server 92 | String result = ""; 93 | if (emoncms_fingerprint != 0) { 94 | // HTTPS on port 443 if HTTPS fingerprint is present 95 | DBUGLN(F("HTTPS Enabled")); 96 | //#ifdef ESP32 97 | //result = get_http(emoncms_server.c_str(), url, 443, emoncms_fingerprint.c_str()); 98 | //#else 99 | result = get_https(emoncms_fingerprint.c_str(), emoncms_server.c_str(), url, 443); 100 | //#endif 101 | } else { 102 | // Plain HTTP if other emoncms server e.g EmonPi 103 | DBUGLN(F("Plain old HTTP")); 104 | result = get_http(emoncms_server.c_str(), url); 105 | } 106 | 107 | const size_t capacity = JSON_OBJECT_SIZE(2) + result.length(); 108 | DynamicJsonDocument doc(capacity); 109 | if(DeserializationError::Code::Ok == deserializeJson(doc, result.c_str(), result.length())) { 110 | DBUGLN(F("Got JSON")); 111 | bool success = doc[F("success")]; // true 112 | emoncms_result(success, doc[F("message")]); 113 | } else if (result == F("ok")) { 114 | emoncms_result(true, result); 115 | } else { 116 | DEBUG.print(F("Emoncms error: ")); 117 | DEBUG.println(result); 118 | emoncms_result(false, result); 119 | } 120 | } else { 121 | if(emoncms_connected) { 122 | emoncms_result(false, String("Disabled")); 123 | } 124 | } 125 | 126 | Profile_End(emoncms_publish, 10); 127 | } 128 | -------------------------------------------------------------------------------- /src/autoauth.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #include "autoauth.h" 28 | #include "emonesp.h" 29 | #include "app_config.h" 30 | #include "http.h" 31 | #include 32 | #include 33 | 34 | WiFiUDP Udp; 35 | unsigned int localUdpPort = 5005; // local port to listen on 36 | char incomingPacket[255]; // buffer for incoming packets 37 | 38 | byte mqtt_auth_transfer_flag = 0; 39 | unsigned long last_auth_request_attempt = 0; 40 | 41 | void auth_request() { 42 | 43 | DEBUG.println(F("Fetching MQTT Auth")); 44 | DEBUG.println(mqtt_server.c_str()); 45 | 46 | // Fetch emoncms mqtt broker credentials 47 | String url = F("/emoncms/device/auth/request.json"); 48 | String result = ""; 49 | String mqtt_username = ""; 50 | String mqtt_password = ""; 51 | String mqtt_basetopic = ""; 52 | String mqtt_portnum = ""; 53 | int stringpart = 0; 54 | 55 | // This needs to be done with an encrypted request otherwise credentials are shared as plain text 56 | result = get_http(mqtt_server.c_str(), url); 57 | if (result != F("request registered")) { 58 | for (int i=0; i 0) 99 | { 100 | incomingPacket[len] = 0; 101 | } 102 | DBUGF("UDP packet contents: %s", incomingPacket); 103 | 104 | if (strcmp(incomingPacket,"emonpi.local")==0) { 105 | if (mqtt_server!=Udp.remoteIP().toString().c_str()) { 106 | config_save_mqtt_server(Udp.remoteIP().toString().c_str()); 107 | DBUGF("MQTT Server Updated"); 108 | mqtt_auth_transfer_flag = 1; 109 | auth_request(); 110 | // --------------------------------------------------------------------------------------------------- 111 | } 112 | } 113 | } 114 | 115 | if ((millis()-last_auth_request_attempt)>10000) { 116 | last_auth_request_attempt = millis(); 117 | 118 | if (mqtt_auth_transfer_flag==1) { 119 | auth_request(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /gui/webpack.config.js: -------------------------------------------------------------------------------- 1 | /* jslint node: true, esversion: 6 */ 2 | /* eslint-env node */ 3 | 4 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 5 | const MiniCssExtractPlugin = require("mini-css-extract-plugin"); 6 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin"); 7 | const CleanWebpackPlugin = require("clean-webpack-plugin"); 8 | const OptimizeCssAssetsPlugin = require("optimize-css-assets-webpack-plugin"); 9 | const ExtractTextPlugin = require("extract-text-webpack-plugin"); 10 | const MergeIntoSingleFilePlugin = require("webpack-merge-and-include-globally"); 11 | const webpack = require("webpack"); //to access built-in plugins 12 | const path = require("path"); 13 | const UglifyJS = require("uglify-js"); 14 | const babel = require("@babel/core"); 15 | const CopyPlugin = require("copy-webpack-plugin"); 16 | 17 | require("dotenv").config(); 18 | const emonespEndpoint = process.env.EMONESP_ENDPOINT || "http://emonesp.local"; 19 | const devHost = process.env.DEV_HOST || "localhost"; 20 | 21 | var htmlMinify = { 22 | removeComments: true, 23 | collapseWhitespace: true, 24 | conservativeCollapse: true 25 | }; 26 | 27 | module.exports = { 28 | mode: "production", 29 | entry: { 30 | assets: "./src/assets.js" 31 | }, 32 | output: { 33 | path: path.resolve(__dirname, "dist"), 34 | filename: "[name].js" 35 | }, 36 | devServer: { 37 | host: devHost, 38 | contentBase: "./dist", 39 | index: "home.html", 40 | proxy: [{ 41 | context: [ 42 | "/status", 43 | "/config", 44 | "/savenetwork", 45 | "/saveemoncms", 46 | "/savemqtt", 47 | "/savecal", 48 | "/saveadmin", 49 | "/savetimer", 50 | "/reset", 51 | "/restart", 52 | "/scan", 53 | "/apoff", 54 | "/input", 55 | "/lastvalues", 56 | "/emoncms/describe", 57 | "/time", 58 | "/ctrlmode", 59 | "/vout", 60 | "/flow", 61 | "/upload", 62 | "/firmware", 63 | "/update", 64 | "/debug" 65 | ], 66 | target: emonespEndpoint 67 | }, 68 | { 69 | context: [ 70 | "/ws", 71 | "/debug/console" 72 | ], 73 | target: emonespEndpoint, 74 | ws: true 75 | }] 76 | }, 77 | module: { 78 | rules: [ 79 | { 80 | test: /\.css$/, 81 | use: [ 82 | MiniCssExtractPlugin.loader, 83 | "css-loader" 84 | ] 85 | } 86 | ] 87 | }, 88 | plugins: [ 89 | //new CleanWebpackPlugin(['dist']), 90 | new HtmlWebpackPlugin({ 91 | filename: "home.html", 92 | template: "./src/home.html", 93 | inject: false, 94 | minify: htmlMinify 95 | }), 96 | new HtmlWebpackPlugin({ 97 | filename: "term.html", 98 | template: "./src/term.html", 99 | inject: false, 100 | minify: htmlMinify 101 | }), 102 | new MiniCssExtractPlugin({ 103 | // Options similar to the same options in webpackOptions.output 104 | // both options are optional 105 | filename: "style.css", 106 | chunkFilename: "[id].css" 107 | }), 108 | new MergeIntoSingleFilePlugin({ 109 | files: { 110 | "lib.js": [ 111 | "node_modules/jquery/dist/jquery.js", 112 | "node_modules/knockout/build/output/knockout-latest.js", 113 | "node_modules/knockout-mapping/dist/knockout.mapping.js" 114 | ], 115 | "config.js": [ 116 | "src/ViewModels/BaseViewModel.js", 117 | "src/ViewModels/StatusViewModel.js", 118 | "src/ViewModels/ConfigViewModel.js", 119 | "src/ViewModels/WiFiConfigViewModel.js", 120 | "src/ViewModels/WiFiScanViewModel.js", 121 | "src/ViewModels/LastValuesViewModel.js", 122 | "src/ViewModels/PasswordViewModel.js", 123 | "src/ViewModels/EmonEspViewModel.js", 124 | "src/config.js" 125 | ], 126 | "term.js": [ 127 | "src/term.js" 128 | ] 129 | }, 130 | transform: { 131 | "lib.js": code => uglify("lib.js", code), 132 | "config.js": code => uglify("config.js", code), 133 | "term.js": code => uglify("term.js", code) 134 | } 135 | }), 136 | new CopyPlugin([ 137 | { from: "assets/*", flatten: true } 138 | ]) 139 | ], 140 | optimization: { 141 | splitChunks: {}, 142 | minimizer: [ 143 | new UglifyJsPlugin({}), 144 | new OptimizeCssAssetsPlugin({}) 145 | ] 146 | } 147 | }; 148 | 149 | function uglify(name, code) 150 | { 151 | var compiled = babel.transformSync(code, { 152 | presets: ["@babel/preset-env"], 153 | sourceMaps: true 154 | }); 155 | var ugly = UglifyJS.minify(compiled.code, { 156 | warnings: true, 157 | sourceMap: { 158 | content: compiled.map, 159 | url: name+".map" 160 | } 161 | }); 162 | if(ugly.error) { 163 | console.log(ugly.error); 164 | return code; 165 | } 166 | return ugly.code; 167 | } 168 | -------------------------------------------------------------------------------- /scripts/extra_script.py: -------------------------------------------------------------------------------- 1 | from os.path import join, isfile, isdir, basename 2 | from os import listdir, system 3 | import json 4 | from pprint import pprint 5 | import re 6 | import requests 7 | import subprocess 8 | import sys 9 | 10 | Import("env") 11 | 12 | # Install pre-requisites 13 | npm_installed = (0 == system("npm --version")) 14 | 15 | # 16 | # Dump build environment (for debug) 17 | # print env.Dump() 18 | #print("Current build targets", map(str, BUILD_TARGETS)) 19 | # 20 | 21 | def get_c_name(source_file): 22 | return basename(source_file).upper().replace('.', '_').replace('-', '_') 23 | 24 | def text_to_header(source_file): 25 | with open(source_file) as source_fh: 26 | original = source_fh.read() 27 | filename = get_c_name(source_file) 28 | output = "static const char CONTENT_{}[] PROGMEM = ".format(filename) 29 | for line in original.splitlines(): 30 | output += u"\n \"{}\\n\"".format(line.replace('\\', '\\\\').replace('"', '\\"')) 31 | output += ";\n" 32 | return output 33 | 34 | def binary_to_header(source_file): 35 | filename = get_c_name(source_file) 36 | output = "static const char CONTENT_"+filename+"[] PROGMEM = {\n " 37 | count = 0 38 | 39 | with open(source_file, "rb") as source_fh: 40 | byte = source_fh.read(1) 41 | while byte != b"": 42 | output += "0x{:02x}, ".format(ord(byte)) 43 | count += 1 44 | if 16 == count: 45 | output += "\n " 46 | count = 0 47 | 48 | byte = source_fh.read(1) 49 | 50 | output += "0x00 };\n" 51 | return output 52 | 53 | def data_to_header(env, target, source): 54 | output = "" 55 | for source_file in source: 56 | #print("Reading {}".format(source_file)) 57 | file = source_file.get_abspath() 58 | if file.endswith(".css") or file.endswith(".js") or file.endswith(".htm") or file.endswith(".html") or file.endswith(".svg"): 59 | output += text_to_header(file) 60 | else: 61 | output += binary_to_header(file) 62 | target_file = target[0].get_abspath() 63 | print("Generating {}".format(target_file)) 64 | with open(target_file, "w") as output_file: 65 | output_file.write(output) 66 | 67 | def make_static(env, target, source): 68 | output = "" 69 | 70 | out_files = [] 71 | for file in listdir(dist_dir): 72 | if isfile(join(dist_dir, file)): 73 | out_files.append(file) 74 | 75 | # Sort files to make sure the order is constant 76 | out_files = sorted(out_files) 77 | 78 | # include the files 79 | for out_file in out_files: 80 | filename = "web_server."+out_file+".h" 81 | output += "#include \"{}\"\n".format(filename) 82 | 83 | output += "StaticFile staticFiles[] = {\n" 84 | 85 | for out_file in out_files: 86 | filetype = "TEXT" 87 | if out_file.endswith(".css"): 88 | filetype = "CSS" 89 | elif out_file.endswith(".js"): 90 | filetype = "JS" 91 | elif out_file.endswith(".htm") or out_file.endswith(".html"): 92 | filetype = "HTML" 93 | elif out_file.endswith(".jpg"): 94 | filetype = "JPEG" 95 | elif out_file.endswith(".png"): 96 | filetype = "PNG" 97 | elif out_file.endswith(".svg"): 98 | filetype = "SVG" 99 | 100 | c_name = get_c_name(out_file) 101 | output += " { \"/"+out_file+"\", CONTENT_"+c_name+", sizeof(CONTENT_"+c_name+") - 1, _CONTENT_TYPE_"+filetype+" },\n" 102 | 103 | output += "};\n" 104 | 105 | target_file = target[0].get_abspath() 106 | print("Generating {}".format(target_file)) 107 | with open(target_file, "w") as output_file: 108 | output_file.write(output) 109 | 110 | def process_html_app(source, dest, env): 111 | web_server_static_files = join(dest, "web_server_static_files.h") 112 | web_server_static = join("$BUILDSRC_DIR", "web_server_static.cpp.o") 113 | 114 | for file in sorted(listdir(source)): 115 | if isfile(join(source, file)): 116 | data_file = join(source, file) 117 | header_file = join(dest, "web_server."+file+".h") 118 | env.Command(header_file, data_file, data_to_header) 119 | env.Depends(web_server_static_files, header_file) 120 | 121 | env.Depends(web_server_static, env.Command(web_server_static_files, source, make_static)) 122 | 123 | # 124 | # Generate Web app resources 125 | # 126 | if npm_installed: 127 | headers_src = join(env.subst("$PROJECTSRC_DIR"), "web_static") 128 | 129 | gui_dir = join(env.subst("$PROJECT_DIR"), "gui") 130 | dist_dir = join(gui_dir, "dist") 131 | node_modules = join(gui_dir, "node_modules") 132 | 133 | # Check the GUI dir has been checked out 134 | if(isfile(join(gui_dir, "package.json"))): 135 | # Check to see if the Node modules have been downloaded 136 | if(isdir(node_modules)): 137 | if(isdir(dist_dir)): 138 | process_html_app(dist_dir, headers_src, env) 139 | else: 140 | print("Warning: GUI not built, run 'cd %s; npm run build'" % gui_dir) 141 | else: 142 | print("Warning: GUI dependencies not found, run 'cd %s; npm install'" % gui_dir) 143 | else: 144 | print("Warning: GUI files not found, run 'git submodule update --init'") 145 | else: 146 | print("Warning: Node.JS and NPM required to update the UI") 147 | -------------------------------------------------------------------------------- /src/esp_wifi.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_ESP_WIFI_H 28 | #define _EMONESP_ESP_WIFI_H 29 | 30 | #include 31 | #ifdef ESP32 32 | #include 33 | #include // Resolve URL for update server etc. 34 | #elif defined(ESP8266) 35 | #include 36 | #include // Resolve URL for update server etc. 37 | #endif 38 | #include // Required for captive portal 39 | 40 | 41 | #ifdef ESP32 42 | enum WiFiDisconnectReason 43 | { 44 | WIFI_DISCONNECT_REASON_UNSPECIFIED = 1, 45 | WIFI_DISCONNECT_REASON_AUTH_EXPIRE = 2, 46 | WIFI_DISCONNECT_REASON_AUTH_LEAVE = 3, 47 | WIFI_DISCONNECT_REASON_ASSOC_EXPIRE = 4, 48 | WIFI_DISCONNECT_REASON_ASSOC_TOOMANY = 5, 49 | WIFI_DISCONNECT_REASON_NOT_AUTHED = 6, 50 | WIFI_DISCONNECT_REASON_NOT_ASSOCED = 7, 51 | WIFI_DISCONNECT_REASON_ASSOC_LEAVE = 8, 52 | WIFI_DISCONNECT_REASON_ASSOC_NOT_AUTHED = 9, 53 | WIFI_DISCONNECT_REASON_DISASSOC_PWRCAP_BAD = 10, /* 11h */ 54 | WIFI_DISCONNECT_REASON_DISASSOC_SUPCHAN_BAD = 11, /* 11h */ 55 | WIFI_DISCONNECT_REASON_IE_INVALID = 13, /* 11i */ 56 | WIFI_DISCONNECT_REASON_MIC_FAILURE = 14, /* 11i */ 57 | WIFI_DISCONNECT_REASON_4WAY_HANDSHAKE_TIMEOUT = 15, /* 11i */ 58 | WIFI_DISCONNECT_REASON_GROUP_KEY_UPDATE_TIMEOUT = 16, /* 11i */ 59 | WIFI_DISCONNECT_REASON_IE_IN_4WAY_DIFFERS = 17, /* 11i */ 60 | WIFI_DISCONNECT_REASON_GROUP_CIPHER_INVALID = 18, /* 11i */ 61 | WIFI_DISCONNECT_REASON_PAIRWISE_CIPHER_INVALID = 19, /* 11i */ 62 | WIFI_DISCONNECT_REASON_AKMP_INVALID = 20, /* 11i */ 63 | WIFI_DISCONNECT_REASON_UNSUPP_RSN_IE_VERSION = 21, /* 11i */ 64 | WIFI_DISCONNECT_REASON_INVALID_RSN_IE_CAP = 22, /* 11i */ 65 | WIFI_DISCONNECT_REASON_802_1X_AUTH_FAILED = 23, /* 11i */ 66 | WIFI_DISCONNECT_REASON_CIPHER_SUITE_REJECTED = 24, /* 11i */ 67 | 68 | WIFI_DISCONNECT_REASON_BEACON_TIMEOUT = 200, 69 | WIFI_DISCONNECT_REASON_NO_AP_FOUND = 201, 70 | WIFI_DISCONNECT_REASON_AUTH_FAIL = 202, 71 | WIFI_DISCONNECT_REASON_ASSOC_FAIL = 203, 72 | WIFI_DISCONNECT_REASON_HANDSHAKE_TIMEOUT = 204, 73 | }; 74 | 75 | struct WiFiEventStationModeConnected 76 | { 77 | String ssid; 78 | uint8_t bssid[6]; 79 | uint8_t channel; 80 | }; 81 | 82 | struct WiFiEventStationModeDisconnected 83 | { 84 | String ssid; 85 | uint8_t bssid[6]; 86 | WiFiDisconnectReason reason; 87 | }; 88 | 89 | struct WiFiEventStationModeGotIP 90 | { 91 | IPAddress ip; 92 | IPAddress mask; 93 | IPAddress gw; 94 | }; 95 | 96 | struct WiFiEventSoftAPModeStationConnected 97 | { 98 | uint8_t mac[6]; 99 | uint8_t aid; 100 | }; 101 | 102 | struct WiFiEventSoftAPModeStationDisconnected 103 | { 104 | uint8_t mac[6]; 105 | uint8_t aid; 106 | }; 107 | 108 | #endif 109 | 110 | // Last discovered WiFi access points 111 | extern String st; 112 | extern String rssi; 113 | 114 | // Network state 115 | extern String ipaddress; 116 | 117 | extern void wifi_setup(); 118 | extern void wifi_loop(); 119 | 120 | extern void wifi_restart(); 121 | extern void wifi_disconnect(); 122 | 123 | extern void wifi_turn_off_ap(); 124 | extern void wifi_turn_on_ap(); 125 | extern bool wifi_client_connected(); 126 | 127 | #define wifi_is_client_configured() (WiFi.SSID() != "") 128 | 129 | // Wifi mode 130 | #define wifi_mode_is_sta() (WIFI_STA == (WiFi.getMode() & WIFI_STA)) 131 | #define wifi_mode_is_sta_only() (WIFI_STA == WiFi.getMode()) 132 | #define wifi_mode_is_ap() (WIFI_AP == (WiFi.getMode() & WIFI_AP)) 133 | 134 | // Performing a scan enables STA so we end up in AP+STA mode so treat AP+STA with no 135 | // ssid set as AP only 136 | #define wifi_mode_is_ap_only() ((WIFI_AP == WiFi.getMode()) || \ 137 | (WIFI_AP_STA == WiFi.getMode() && !wifi_is_client_configured())) 138 | 139 | #endif // _EMONESP_ESP_WIFI_H 140 | -------------------------------------------------------------------------------- /src/http.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #include "emonesp.h" 28 | #include "http.h" 29 | 30 | #define HTTP_TIMEOUT 4 31 | 32 | 33 | #ifdef ESP32 34 | //WiFiClientSecure client_ssl; // Create class for ESP32 HTTPS TCP connections get_http() 35 | WiFiClient client; // Create class for ESP32 HTTP TCP connections get_http() 36 | #endif 37 | 38 | /* 39 | #ifdef ESP32 40 | static char request[MAX_DATA_LEN+100]; 41 | 42 | // ------------------------------------------------------------------- 43 | // HTTP or HTTPS GET Request 44 | // url: N/A 45 | // ------------------------------------------------------------------- 46 | String get_http(const char * host, String url, int port, const char * fingerprint) { 47 | WiFiClientSecure * http; 48 | 49 | if (fingerprint) { 50 | http = &client_ssl; 51 | } else { 52 | http = (WiFiClientSecure *) &client; 53 | } 54 | 55 | // Use WiFiClient class to create TCP connections 56 | if (!http->connect(host, port, HTTP_TIMEOUT*1000)) { 57 | DEBUG.printf("%s:%d\n", host, port); //debug 58 | return ("Connection error"); 59 | } 60 | http->setTimeout(HTTP_TIMEOUT); 61 | 62 | sprintf(request, "GET %s HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n", url, host); 63 | http->print(request); 64 | // Handle wait for reply and timeout 65 | unsigned long timeout = millis(); 66 | while (http->available() == 0) { 67 | if (millis() - timeout > (HTTP_TIMEOUT*1000)) { 68 | http->stop(); 69 | return ("Client Timeout"); 70 | } 71 | #ifdef ENABLE_WDT 72 | feedLoopWDT(); 73 | #endif 74 | } 75 | // Handle message receive 76 | while (http->available()) { 77 | String line = http->readStringUntil('\r'); 78 | DEBUG.println(line); //debug 79 | if (line.startsWith("HTTP/1.1 200 OK")) { 80 | return ("ok"); 81 | } 82 | } 83 | return ("error " + String(host)); 84 | } 85 | 86 | #elif defined(ESP8266) 87 | */ 88 | static String get_http_internal(WiFiClient &client, const char *host, String &path, int port, bool secure) 89 | { 90 | HTTPClient http; // Create class for HTTP TCP connections get_http 91 | 92 | DBUGF("Connecting to http%s://%s:%d%s", secure ? "s" : "", host, port, path.c_str()); 93 | 94 | http.begin(client, host, port, path, secure); 95 | int httpCode = http.GET(); 96 | if((httpCode > 0) && (httpCode == HTTP_CODE_OK)) 97 | { 98 | String payload = http.getString(); 99 | DEBUG.println(payload); 100 | http.end(); 101 | return(payload); 102 | } else { 103 | http.end(); 104 | return(String(F("server error: "))+http.errorToString(httpCode)); 105 | } 106 | } 107 | 108 | // ------------------------------------------------------------------- 109 | // HTTPS SECURE GET Request 110 | // url: N/A 111 | // ------------------------------------------------------------------- 112 | 113 | String get_https(const char* fingerprint, const char* host, String &path, int httpsPort) 114 | { 115 | #ifdef ESP32 116 | // Use WiFiClient class to create TCP connections 117 | if (!client.connect(host, httpsPort)) { 118 | DEBUG.print(host + httpsPort); //debug 119 | return ("Connection error"); 120 | } 121 | client.print(String("GET ") + path + " HTTP/1.1\r\n" + "Host: " + host + 122 | "\r\n" + "Connection: close\r\n\r\n"); 123 | // Handle wait for reply and timeout 124 | unsigned long timeout = millis(); 125 | while (client.available() == 0) { 126 | if (millis() - timeout > 5000) { 127 | client.stop(); 128 | return ("Client Timeout"); 129 | } 130 | } 131 | // Handle message receive 132 | while (client.available()) { 133 | String line = client.readStringUntil('\r'); 134 | DEBUG.println(line); //debug 135 | if (line.startsWith("HTTP/1.1 200 OK")) { 136 | return ("ok"); 137 | } 138 | } 139 | 140 | return ("error " + String(host)); 141 | #else 142 | std::unique_ptrclient(new BearSSL::WiFiClientSecure); 143 | client->setBufferSizes(512, 512); 144 | 145 | // IMPROVE: use a certificate 146 | if(false == client->setFingerprint(fingerprint)) { 147 | return F("Invalid fingerprint"); 148 | } 149 | 150 | return get_http_internal(*client, host, path, httpsPort, true); 151 | #endif 152 | } 153 | 154 | // ------------------------------------------------------------------- 155 | // HTTP GET Request 156 | // url: N/A 157 | // ------------------------------------------------------------------- 158 | String get_http(const char *host, String &path){ 159 | WiFiClient client; 160 | return get_http_internal(client, host, path, 80, false); 161 | } // end http_get 162 | //#endif 163 | -------------------------------------------------------------------------------- /gui/src/ViewModels/WiFiConfigViewModel.js: -------------------------------------------------------------------------------- 1 | /* global $, ko */ 2 | /* exported WiFiConfigViewModel */ 3 | 4 | function WiFiConfigViewModel(baseEndpoint, config, status, scan) { 5 | "use strict"; 6 | var self = this; 7 | 8 | self.baseEndpoint = baseEndpoint; 9 | self.config = config; 10 | self.status = status; 11 | self.scan = scan; 12 | 13 | self.scanUpdating = ko.observable(false); 14 | 15 | self.selectedNet = ko.observable(false); 16 | self.bssid = ko.pureComputed({ 17 | read: () => { 18 | return (false === self.selectedNet()) ? "" : self.selectedNet().bssid(); 19 | }, 20 | write: (bssid) => { 21 | for(var i = 0; i < self.scan.results().length; i++) { 22 | var net = self.scan.results()[i]; 23 | if(bssid === net.bssid()) { 24 | self.selectedNet(net); 25 | return; 26 | } 27 | } 28 | } 29 | }); 30 | self.select = function(item) { 31 | self.selectedNet(item); 32 | }; 33 | 34 | self.setSsid = function(ssid) 35 | { 36 | if(false !== self.selectedNet() && ssid === self.selectedNet().ssid()) { 37 | return; 38 | } 39 | 40 | for(var i = 0; i < self.scan.filteredResults().length; i++) { 41 | var net = self.scan.filteredResults()[i]; 42 | if(ssid === net.ssid()) { 43 | self.selectedNet(net); 44 | return; 45 | } 46 | } 47 | 48 | for(var i = 0; i < self.scan.results().length; i++) { 49 | var net = self.scan.results()[i]; 50 | if(ssid === net.ssid()) { 51 | self.selectedNet(net); 52 | return; 53 | } 54 | } 55 | 56 | self.selectedNet(false); 57 | } 58 | 59 | var scanTimer = null; 60 | var scanTime = 3 * 1000; 61 | 62 | // ----------------------------------------------------------------------- 63 | // WiFi scan update 64 | // ----------------------------------------------------------------------- 65 | var scanEnabled = false; 66 | self.startScan = function () { 67 | if (self.scanUpdating()) { 68 | return; 69 | } 70 | scanEnabled = true; 71 | self.scanUpdating(true); 72 | if (null !== scanTimer) { 73 | clearTimeout(scanTimer); 74 | scanTimer = null; 75 | } 76 | self.scan.update(function () { 77 | if(scanEnabled) { 78 | scanTimer = setTimeout(self.startScan, scanTime); 79 | } 80 | 81 | // if bssid is not set see if we have a ssid that matches our configured result 82 | if("" === self.bssid()) { 83 | var ssid = self.config.ssid(); 84 | for(var i = 0; i < self.scan.results().length; i++) { 85 | var net = self.scan.results()[i]; 86 | if(ssid === net.ssid()) { 87 | self.bssid(net.bssid()); 88 | break; 89 | } 90 | } 91 | } 92 | 93 | self.scanUpdating(false); 94 | }); 95 | }; 96 | 97 | self.stopScan = function() { 98 | scanEnabled = false; 99 | if (self.scanUpdating()) { 100 | return; 101 | } 102 | 103 | if (null !== scanTimer) { 104 | clearTimeout(scanTimer); 105 | scanTimer = null; 106 | } 107 | }; 108 | 109 | self.enableScan = function (enable) { 110 | if(enable) { 111 | self.startScan(); 112 | } else { 113 | self.stopScan(); 114 | } 115 | }; 116 | 117 | self.forceConfig = ko.observable(false); 118 | self.canConfigure = ko.pureComputed(function () { 119 | if(self.status.isWiFiError() || self.wifiConnecting() || self.status.isWired()) { 120 | return false; 121 | } 122 | 123 | return !self.status.isWifiClient() || self.forceConfig(); 124 | }); 125 | 126 | self.wifiConnecting = ko.observable(false); 127 | self.canConfigure.subscribe(function (newValue) { 128 | self.enableScan(newValue); 129 | }); 130 | self.status.wifi_client_connected.subscribe(function (newValue) { 131 | if(newValue) { 132 | self.wifiConnecting(false); 133 | } 134 | }); 135 | self.enableScan(self.canConfigure()); 136 | 137 | // ----------------------------------------------------------------------- 138 | // Event: WiFi Connect 139 | // ----------------------------------------------------------------------- 140 | self.saveNetworkFetching = ko.observable(false); 141 | self.saveNetworkSuccess = ko.observable(false); 142 | self.saveNetwork = function () { 143 | if (self.config.ssid() === "") { 144 | alert("Please select network"); 145 | } else { 146 | self.saveNetworkFetching(true); 147 | self.saveNetworkSuccess(false); 148 | $.post(self.baseEndpoint() + "/savenetwork", { ssid: self.config.ssid(), pass: self.config.pass() }, function () { 149 | // HACK: Almost certainly won't get a status update with client connected set to false so manually clear it here 150 | self.status.wifi_client_connected(false); 151 | 152 | // Done with setting the config 153 | self.forceConfig(false); 154 | 155 | // Wait for a new WiFi connection 156 | self.wifiConnecting(true); 157 | 158 | // And indicate the save was successful 159 | self.saveNetworkSuccess(true); 160 | }).fail(function () { 161 | alert("Failed to save WiFi config"); 162 | }).always(function () { 163 | self.saveNetworkFetching(false); 164 | }); 165 | } 166 | }; 167 | 168 | // ----------------------------------------------------------------------- 169 | // Event: Turn off Access Point 170 | // ----------------------------------------------------------------------- 171 | self.turnOffAccessPointFetching = ko.observable(false); 172 | self.turnOffAccessPointSuccess = ko.observable(false); 173 | self.turnOffAccessPoint = function () { 174 | self.turnOffAccessPointFetching(true); 175 | self.turnOffAccessPointSuccess(false); 176 | $.post(self.baseEndpoint() + "/apoff", { 177 | }, function (data) { 178 | console.log(data); 179 | if (self.status.ipaddress() !== "") { 180 | setTimeout(function () { 181 | window.location = "//" + self.status.ipaddress(); 182 | self.turnOffAccessPointSuccess(true); 183 | }, 3000); 184 | } else { 185 | self.turnOffAccessPointSuccess(true); 186 | } 187 | }).fail(function () { 188 | alert("Failed to turn off Access Point"); 189 | }).always(function () { 190 | self.turnOffAccessPointFetching(false); 191 | }); 192 | }; 193 | } 194 | -------------------------------------------------------------------------------- /gui/.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // JSHint Default Configuration File (as on JSHint website) 3 | // See http://jshint.com/docs/ for more details 4 | 5 | "maxerr" : 50, // {int} Maximum error before stopping 6 | 7 | // Enforcing 8 | "bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.) 9 | "camelcase" : false, // true: Identifiers must be in camelCase 10 | "curly" : true, // true: Require {} for every new block or scope 11 | "eqeqeq" : true, // true: Require triple equals (===) for comparison 12 | "forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty() 13 | "freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc. 14 | "immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());` 15 | "latedef" : false, // true: Require variables/functions to be defined before being used 16 | "newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()` 17 | "noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee` 18 | "noempty" : true, // true: Prohibit use of empty blocks 19 | "nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters. 20 | "nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment) 21 | "plusplus" : false, // true: Prohibit use of `++` and `--` 22 | "quotmark" : false, // Quotation mark consistency: 23 | // false : do nothing (default) 24 | // true : ensure whatever is used is consistent 25 | // "single" : require single quotes 26 | // "double" : require double quotes 27 | "undef" : true, // true: Require all non-global variables to be declared (prevents global leaks) 28 | "unused" : true, // Unused variables: 29 | // true : all variables, last function parameter 30 | // "vars" : all variables only 31 | // "strict" : all variables, all function parameters 32 | "strict" : true, // true: Requires all functions run in ES5 Strict Mode 33 | "maxparams" : false, // {int} Max number of formal params allowed per function 34 | "maxdepth" : false, // {int} Max depth of nested blocks (within functions) 35 | "maxstatements" : false, // {int} Max number statements per function 36 | "maxcomplexity" : false, // {int} Max cyclomatic complexity per function 37 | "maxlen" : false, // {int} Max number of characters per line 38 | "varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed. 39 | 40 | // Relaxing 41 | "asi" : false, // true: Tolerate Automatic Semicolon Insertion (no semicolons) 42 | "boss" : false, // true: Tolerate assignments where comparisons would be expected 43 | "debug" : false, // true: Allow debugger statements e.g. browser breakpoints. 44 | "eqnull" : false, // true: Tolerate use of `== null` 45 | "esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere. 46 | "moz" : false, // true: Allow Mozilla specific syntax (extends and overrides esnext features) 47 | // (ex: `for each`, multiple try/catch, function expression…) 48 | "evil" : false, // true: Tolerate use of `eval` and `new Function()` 49 | "expr" : false, // true: Tolerate `ExpressionStatement` as Programs 50 | "funcscope" : false, // true: Tolerate defining variables inside control statements 51 | "globalstrict" : false, // true: Allow global "use strict" (also enables 'strict') 52 | "iterator" : false, // true: Tolerate using the `__iterator__` property 53 | "lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block 54 | "laxbreak" : false, // true: Tolerate possibly unsafe line breakings 55 | "laxcomma" : false, // true: Tolerate comma-first style coding 56 | "loopfunc" : false, // true: Tolerate functions being defined in loops 57 | "multistr" : false, // true: Tolerate multi-line strings 58 | "noyield" : false, // true: Tolerate generator functions with no yield statement in them. 59 | "notypeof" : false, // true: Tolerate invalid typeof operator values 60 | "proto" : false, // true: Tolerate using the `__proto__` property 61 | "scripturl" : false, // true: Tolerate script-targeted URLs 62 | "shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;` 63 | "sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation 64 | "supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;` 65 | "validthis" : false, // true: Tolerate using this in a non-constructor function 66 | 67 | // Environments 68 | "browser" : true, // Web Browser (window, document, etc) 69 | "browserify" : false, // Browserify (node.js code in the browser) 70 | "couch" : false, // CouchDB 71 | "devel" : true, // Development/debugging (alert, confirm, etc) 72 | "dojo" : false, // Dojo Toolkit 73 | "jasmine" : false, // Jasmine 74 | "jquery" : false, // jQuery 75 | "mocha" : true, // Mocha 76 | "mootools" : false, // MooTools 77 | "node" : false, // Node.js 78 | "nonstandard" : false, // Widely adopted globals (escape, unescape, etc) 79 | "phantom" : false, // PhantomJS 80 | "prototypejs" : false, // Prototype and Scriptaculous 81 | "qunit" : false, // QUnit 82 | "rhino" : false, // Rhino 83 | "shelljs" : false, // ShellJS 84 | "typed" : false, // Globals for typed array constructions 85 | "worker" : false, // Web Workers 86 | "wsh" : false, // Windows Scripting Host 87 | "yui" : false, // Yahoo User Interface 88 | 89 | // Custom Globals 90 | "globals" : {} // additional predefined global variables 91 | } 92 | -------------------------------------------------------------------------------- /gui/src/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | width: 100%; 3 | height: 100%; 4 | font-family: Arial,"sans-serif"; 5 | font-size: 14px; 6 | background-color: #ffffff; 7 | color: #777777; 8 | margin: 0; 9 | } 10 | 11 | /*----------------*/ 12 | 13 | #page { 14 | height: 100%; 15 | width: 100%; 16 | max-width: 1024px; 17 | margin: 0 auto; 18 | overflow-x: hidden; 19 | } 20 | 21 | #container { 22 | width: 100%; 23 | margin-bottom: 40px; 24 | } 25 | 26 | #one,#two,#three,#four,#five,#six,#seven,#eight,#nine { 27 | background-color: #f1f1f1; 28 | box-sizing: border-box; 29 | margin: 5px 5px 5px 5px; 30 | } 31 | 32 | .hide {display:none;} 33 | 34 | .itembody-wrapper { 35 | padding: 5px 25px 5px 25px; 36 | } 37 | 38 | .scrollable-logs { 39 | overflow: auto; 40 | max-height: 500px; 41 | } 42 | 43 | input[type="text"], 44 | input[type="password"] { 45 | box-sizing: border-box; 46 | border-radius: 4px; 47 | padding: 10px; 48 | margin-top: 10px; 49 | width: 280px; 50 | } 51 | 52 | @media screen and (max-width: 420px) { 53 | input[type="text"], 54 | input[type="password"] { 55 | width: 240px; 56 | } 57 | } 58 | 59 | #update { 60 | background-color: red; 61 | } 62 | 63 | .small-text{ 64 | font-size: 10px; 65 | word-wrap: break-word; 66 | } 67 | 68 | /*----------------*/ 69 | 70 | table { 71 | border-collapse: collapse; 72 | margin: 10px auto 0 auto; 73 | max-width: 380px; 74 | max-height: 100px; 75 | border-style: none; 76 | } 77 | 78 | th { 79 | background-color: #777777; 80 | color: #ffffff; 81 | border: 1px solid #f1f1f1; 82 | padding: 8px; 83 | } 84 | 85 | td { 86 | background-color: #ffffff; 87 | color: #777777; 88 | border: 1px solid #f1f1f1; 89 | padding: 8px; 90 | } 91 | 92 | /*----------------*/ 93 | 94 | p { 95 | padding-top: 10px; 96 | } 97 | 98 | 99 | a { 100 | text-decoration: none; 101 | color: #0699fa; 102 | cursor: pointer; 103 | } 104 | 105 | h2 { 106 | font-size: 20px; 107 | background-color: #82afcc; 108 | color: #fff; 109 | padding: 10px 15px 10px 15px; 110 | margin: 0; 111 | cursor:pointer; 112 | font-weight:normal; 113 | } 114 | 115 | h2:hover { 116 | background-color: #98c4e1; 117 | } 118 | 119 | button { 120 | background-color: #0699fa; 121 | border-radius: 10px; 122 | border: none; 123 | color: #ffffff; 124 | padding: 13px 28px; 125 | text-align: center; 126 | text-decoration: none; 127 | display: inline-block; 128 | font-size: 15px; 129 | font-weight: bold; 130 | margin: 4px 2px; 131 | cursor: pointer; 132 | outline: none; 133 | } 134 | 135 | button:disabled { 136 | background-color: #eee; 137 | color: #ccc; 138 | } 139 | 140 | /*----------------*/ 141 | 142 | .box { 143 | padding-top: 5px; 144 | padding-bottom: 5px; 145 | margin: 0; 146 | } 147 | 148 | .box h1 { 149 | font-size: 40px; 150 | font-weight: bold; 151 | font-family: Arial,"sans-serif"; 152 | text-align: center; 153 | margin: 0; 154 | } 155 | 156 | .box h1 span { 157 | color: #82afcc; 158 | } 159 | 160 | .box h3 { 161 | padding-top: 7px; 162 | font-weight: bold; 163 | font-family: Arial,"sans-serif"; 164 | font-size: 19px; 165 | text-align: center; 166 | color: #b6b6b6; 167 | margin: -6px 0 0 0; 168 | } 169 | 170 | /*----------------*/ 171 | 172 | #footer-small-scrn { 173 | width: 100%; 174 | font-weight: bold; 175 | font-family: Arial,"sans-serif"; 176 | font-size: 15px; 177 | text-align: center; 178 | padding: 5px; 179 | color: #bcbcbc; 180 | } 181 | 182 | #footer-large-scrn { 183 | display: none; 184 | } 185 | 186 | @media screen and (min-width: 1024px) { 187 | 188 | #footer-small-scrn { 189 | display: none; 190 | } 191 | } 192 | 193 | @media screen and (min-width: 1024px) { 194 | 195 | #footer-large-scrn { 196 | position: fixed; 197 | display: block; 198 | width: 100%; 199 | font-weight: bold; 200 | font-family: Arial,"sans-serif"; 201 | font-size: 15px; 202 | text-align: center; 203 | padding: 5px; 204 | color: #bcbcbc; 205 | bottom: 0; 206 | background-color: #ffffff; 207 | } 208 | } 209 | 210 | #footer-large-scrn a, #footer-small-scrn a { 211 | color: #0699fa; 212 | } 213 | 214 | #footer-large-scrn span, #footer-small-scrn span { 215 | color: #777777; 216 | } 217 | 218 | .loading { 219 | margin-top: 40px; 220 | margin-bottom: 40px; 221 | text-align: center; 222 | } 223 | 224 | .btn-group button{ 225 | width:auto; 226 | height:40px; 227 | padding:10px 20px 10px 20px; 228 | border:none; 229 | /*border-right:1px solid #888;*/ 230 | background-color:#666; 231 | color:#fff; 232 | cursor:pointer; 233 | border-radius: 0px; 234 | margin:0px; 235 | } 236 | 237 | .btn-group button:hover{ 238 | /*background-color:#888;*/ 239 | } 240 | 241 | .btn-group .active{ 242 | background-color:#888; 243 | } 244 | 245 | .btn-group .green {background-color:#76de32;} 246 | .btn-group .red {background-color:#ff5d32;} 247 | 248 | .btn-group button:first-child { 249 | border-radius: 20px 0px 0px 20px; 250 | } 251 | 252 | .btn-group button:last-child { 253 | border-radius: 0px 20px 20px 0px; 254 | } 255 | 256 | #wifiList { 257 | display: inline-block; 258 | width: 100%; 259 | max-height: 230px; 260 | overflow: auto 261 | } 262 | 263 | /* Stolen from Bootstrap */ 264 | 265 | .list-group { 266 | display: flex; 267 | flex-direction: column; 268 | padding-left: 0; 269 | margin-bottom: 0; 270 | } 271 | 272 | .list-group-item { 273 | position: relative; 274 | display: flex; 275 | padding: .75rem 1.25rem; 276 | margin-bottom: -1px; 277 | background-color: #fff; 278 | border: 1px solid rgba(0, 0, 0, .125); 279 | justify-content: space-between; 280 | align-items: center; 281 | } 282 | 283 | .list-group-item.active { 284 | z-index: 2; 285 | color: #fff; 286 | background-color: #0699fa; 287 | border-color: #0699fa; 288 | } 289 | 290 | .list-group-item:first-child { 291 | border-top-left-radius: .25rem; 292 | border-top-right-radius: .25rem; 293 | } 294 | 295 | .list-group-item:last-child { 296 | margin-bottom: 0; 297 | border-bottom-right-radius: .25rem; 298 | border-bottom-left-radius: .25rem; 299 | } 300 | 301 | #progressBack { 302 | width: 100%; 303 | border-color: #0699fa; 304 | border-style: solid; 305 | border-width: 1px; 306 | border-radius: 4px 307 | } 308 | 309 | #progressBar { 310 | width: 1%; 311 | height: 30px; 312 | background-color: #0699fa; 313 | } 314 | -------------------------------------------------------------------------------- /src/app_config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifndef _EMONESP_CONFIG_H 28 | #define _EMONESP_CONFIG_H 29 | 30 | #include 31 | #include // Save config settings 32 | #include //https://github.com/jeremypoulter/ConfigJson.git 33 | #include 34 | 35 | // ------------------------------------------------------------------- 36 | // Load and save the EmonESP config. 37 | // 38 | // This initial implementation saves the config to the EEPROM area of flash 39 | // ------------------------------------------------------------------- 40 | 41 | // Global config varables 42 | extern String node_type; 43 | extern int node_id; 44 | extern String node_name; 45 | extern String node_describe; 46 | extern String node_description; 47 | 48 | // Wifi Network Strings 49 | extern String esid; 50 | extern String epass; 51 | 52 | // Web server authentication (leave blank for none) 53 | extern String www_username; 54 | extern String www_password; 55 | 56 | // EMONCMS SERVER strings 57 | extern String emoncms_server; 58 | extern String emoncms_path; 59 | extern String emoncms_node; 60 | extern String emoncms_apikey; 61 | extern String emoncms_fingerprint; 62 | 63 | // MQTT Settings 64 | extern String mqtt_server; 65 | extern int mqtt_port; 66 | extern String mqtt_topic; 67 | extern String mqtt_user; 68 | extern String mqtt_pass; 69 | extern String mqtt_feed_prefix; 70 | 71 | // Calibration Settings for CircuitSetup energy meter 72 | extern int voltage_cal; 73 | extern int ct1_cal; 74 | extern int ct2_cal; 75 | extern int freq_cal; 76 | extern int gain_cal; 77 | #ifdef SOLAR_METER 78 | extern int svoltage_cal; 79 | extern int sct1_cal; 80 | extern int sct2_cal; 81 | #endif 82 | 83 | // Timer Settings 84 | extern int timer_start1; 85 | extern int timer_stop1; 86 | extern int timer_start2; 87 | extern int timer_stop2; 88 | extern int time_offset; 89 | 90 | extern int voltage_output; 91 | 92 | extern String ctrl_mode; 93 | extern bool ctrl_update; 94 | extern bool ctrl_state; 95 | 96 | // 24-bits of Flags 97 | extern uint32_t flags; 98 | 99 | #define CONFIG_SERVICE_EMONCMS (1 << 0) 100 | #define CONFIG_SERVICE_MQTT (1 << 1) 101 | #define CONFIG_CTRL_UPDATE (1 << 2) 102 | #define CONFIG_CTRL_STATE (1 << 3) 103 | 104 | inline bool config_emoncms_enabled() { 105 | return CONFIG_SERVICE_EMONCMS == (flags & CONFIG_SERVICE_EMONCMS); 106 | } 107 | 108 | inline bool config_mqtt_enabled() { 109 | return CONFIG_SERVICE_MQTT == (flags & CONFIG_SERVICE_MQTT); 110 | } 111 | 112 | inline bool config_ctrl_update() { 113 | return CONFIG_CTRL_UPDATE == (flags & CONFIG_CTRL_UPDATE); 114 | } 115 | 116 | inline bool config_ctrl_state() { 117 | return CONFIG_CTRL_STATE == (flags & CONFIG_CTRL_STATE); 118 | } 119 | // ------------------------------------------------------------------- 120 | // Load saved settings 121 | // ------------------------------------------------------------------- 122 | extern void config_load_settings(); 123 | extern void config_load_v1_settings(); 124 | 125 | // ------------------------------------------------------------------- 126 | // Save the EmonCMS server details 127 | // ------------------------------------------------------------------- 128 | extern void config_save_emoncms(bool enable, String server, String path, String node, String apikey, String fingerprint); 129 | 130 | // ------------------------------------------------------------------- 131 | // Save the MQTT broker details 132 | // ------------------------------------------------------------------- 133 | extern void config_save_mqtt(bool enable, String server, int port, String topic, String prefix, String user, String pass); 134 | extern void config_save_mqtt_server(String server); 135 | 136 | // ------------------------------------------------------------------- 137 | // Save the Calibration details 138 | // ------------------------------------------------------------------- 139 | #ifdef SOLAR_METER 140 | extern void config_save_cal(int voltage, int ct1, int ct2, int freq, int gain, int svoltage, int sct1, int sct2); 141 | #else 142 | extern void config_save_cal(int voltage, int ct1, int ct2, int freq, int gain); 143 | #endif 144 | 145 | // ------------------------------------------------------------------- 146 | // Save the admin/web interface details 147 | // ------------------------------------------------------------------- 148 | extern void config_save_admin(String user, String pass); 149 | 150 | // ------------------------------------------------------------------- 151 | // Save the timer interface details 152 | // ------------------------------------------------------------------- 153 | extern void config_save_timer(int start1, int stop1, int start2, int stop2, int qvoltage_output, int qtime_offset); 154 | extern void config_save_voltage_output(int qvoltage_output, int save_to_eeprom); 155 | 156 | // ------------------------------------------------------------------- 157 | // Save the Wifi details 158 | // ------------------------------------------------------------------- 159 | extern void config_save_wifi(String qsid, String qpass); 160 | 161 | // ------------------------------------------------------------------- 162 | // Reset the config back to defaults 163 | // ------------------------------------------------------------------- 164 | extern void config_reset(); 165 | 166 | void config_set(const char *name, uint32_t val); 167 | void config_set(const char *name, String val); 168 | void config_set(const char *name, bool val); 169 | void config_set(const char *name, double val); 170 | 171 | // Read config settings from JSON object 172 | bool config_deserialize(String& json); 173 | bool config_deserialize(const char *json); 174 | bool config_deserialize(DynamicJsonDocument &doc); 175 | void config_commit(); 176 | 177 | // Write config settings to JSON object 178 | bool config_serialize(String& json, bool longNames = true, bool compactOutput = false, bool hideSecrets = false); 179 | bool config_serialize(DynamicJsonDocument &doc, bool longNames = true, bool compactOutput = false, bool hideSecrets = false); 180 | 181 | #endif // _EMONESP_CONFIG_H 182 | -------------------------------------------------------------------------------- /src/web_server_static.cpp: -------------------------------------------------------------------------------- 1 | #if defined(ENABLE_DEBUG) && !defined(ENABLE_DEBUG_WEB_STATIC) 2 | #undef ENABLE_DEBUG 3 | #endif 4 | 5 | #include 6 | 7 | #include "emonesp.h" 8 | #include "web_server.h" 9 | #include "web_server_static.h" 10 | #include "app_config.h" 11 | #include "esp_wifi.h" 12 | 13 | // Static files 14 | #include "web_static/web_server_static_files.h" 15 | 16 | #define ARRAY_LENGTH(x) (sizeof(x)/sizeof((x)[0])) 17 | 18 | #define IS_ALIGNED(x) (0 == ((uint32_t)(x) & 0x3)) 19 | 20 | // Pages 21 | static const char _HOME_PAGE[] PROGMEM = "/home.html"; 22 | #define HOME_PAGE FPSTR(_HOME_PAGE) 23 | 24 | static const char _WIFI_PAGE[] PROGMEM = "/wifi_portal.html"; 25 | #define WIFI_PAGE FPSTR(_WIFI_PAGE) 26 | 27 | static const char _BUILD_TIME[] PROGMEM = __DATE__ " " __TIME__ " GMT"; 28 | #define BUILD_TIME FPSTR(_BUILD_TIME) 29 | 30 | static const char _HEADER_IF_MODIFIED_SINCE[] PROGMEM = "If-Modified-Since"; 31 | #define HEADER_IF_MODIFIED_SINCE FPSTR(_HEADER_IF_MODIFIED_SINCE) 32 | 33 | StaticFileWebHandler::StaticFileWebHandler() 34 | { 35 | } 36 | 37 | bool StaticFileWebHandler::_getFile(AsyncWebServerRequest *request, StaticFile **file) const 38 | { 39 | // Remove the found uri 40 | String path = request->url(); 41 | if(path == "/") { 42 | // path = String(wifi_mode_is_ap_only() ? WIFI_PAGE : HOME_PAGE); 43 | path = String(HOME_PAGE); 44 | } 45 | 46 | DBUGF("Looking for %s", path.c_str()); 47 | 48 | for(uint32_t i = 0; i < ARRAY_LENGTH(staticFiles); i++) { 49 | if(path == staticFiles[i].filename) 50 | { 51 | DBUGF("Found %s %d@%p", staticFiles[i].filename, staticFiles[i].length, staticFiles[i].data); 52 | 53 | if(file) { 54 | *file = &staticFiles[i]; 55 | } 56 | return true; 57 | } 58 | } 59 | 60 | return false; 61 | } 62 | 63 | bool StaticFileWebHandler::canHandle(AsyncWebServerRequest *request) const 64 | { 65 | StaticFile *file = NULL; 66 | if (request->method() == HTTP_GET && 67 | _getFile(request, &file)) 68 | { 69 | request->_tempObject = file; 70 | DBUGF("[StaticFileWebHandler::canHandle] TRUE"); 71 | // request->addInterestingHeader(HEADER_IF_MODIFIED_SINCE); // obsolete in current API 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | void StaticFileWebHandler::handleRequest(AsyncWebServerRequest *request) 79 | { 80 | dumpRequest(request); 81 | 82 | // Get the filename from request->_tempObject and free it 83 | StaticFile *file = (StaticFile *)request->_tempObject; 84 | if (file) 85 | { 86 | // Clear so we do not try and free 87 | request->_tempObject = NULL; 88 | 89 | // Are we authenticated 90 | if(wifi_mode_is_sta() && 91 | www_username != "" && www_password != "" && 92 | false == request->authenticate(www_username.c_str(), www_password.c_str())) 93 | { 94 | request->requestAuthentication(node_name.c_str()); 95 | return; 96 | } 97 | 98 | if (request->header(HEADER_IF_MODIFIED_SINCE).equals(BUILD_TIME)) { 99 | request->send(304); 100 | return; 101 | } 102 | 103 | AsyncWebServerResponse *response = new StaticFileResponse(200, file); 104 | //response->addHeader("Content-Encoding", "gzip"); 105 | response->addHeader("Last-Modified", BUILD_TIME); 106 | request->send(response); 107 | } else { 108 | request->send(404); 109 | } 110 | } 111 | 112 | StaticFileResponse::StaticFileResponse(int code, StaticFile *content){ 113 | _code = code; 114 | _content = content; 115 | _contentType = String(FPSTR(content->type)); 116 | _contentLength = content->length; 117 | ptr = content->data; 118 | addHeader("Connection","close"); 119 | } 120 | 121 | size_t StaticFileResponse::write(AsyncWebServerRequest *request) 122 | { 123 | size_t total = 0; 124 | size_t written = 0; 125 | do { 126 | written = writeData(request); 127 | if(written > 0) { 128 | total += written; 129 | } 130 | } while(written > 0); 131 | 132 | if(total > 0) 133 | { 134 | //DBUGF("%p: Sending %d", request, total); 135 | 136 | // How should failures to send be handled? 137 | request->client()->send(); 138 | } 139 | 140 | return total; 141 | } 142 | 143 | size_t StaticFileResponse::writeData(AsyncWebServerRequest *request) 144 | { 145 | size_t space = request->client()->space(); 146 | 147 | DBUGF("%p: StaticFileResponse::write: %s %d %d@%p, free %d", request, 148 | RESPONSE_SETUP == _state ? "RESPONSE_SETUP" : 149 | RESPONSE_HEADERS == _state ? "RESPONSE_HEADERS" : 150 | RESPONSE_CONTENT == _state ? "RESPONSE_CONTENT" : 151 | RESPONSE_WAIT_ACK == _state ? "RESPONSE_WAIT_ACK" : 152 | RESPONSE_END == _state ? "RESPONSE_END" : 153 | RESPONSE_FAILED == _state ? "RESPONSE_FAILED" : 154 | "UNKNOWN", 155 | space, length, ptr, ESP.getFreeHeap()); 156 | 157 | if(length > 0 && space > 0) 158 | { 159 | size_t written = 0; 160 | 161 | char buffer[128]; 162 | uint32_t copy = sizeof(buffer); 163 | if(copy > length) { 164 | copy = length; 165 | } 166 | if(copy > space) { 167 | copy = space; 168 | } 169 | //DBUGF("%p: write %d@%p", request, copy, ptr); 170 | if(IS_ALIGNED(ptr)) { 171 | uint32_t *end = (uint32_t *)(ptr + copy); 172 | for(uint32_t *src = (uint32_t *)ptr, *dst = (uint32_t *)buffer; 173 | src < end; src++, dst++) 174 | { 175 | *dst = *src; 176 | } 177 | } else { 178 | memcpy_P(buffer, ptr, copy); 179 | } 180 | 181 | written = request->client()->add(buffer, copy); 182 | if(written > 0) { 183 | _writtenLength += written; 184 | ptr += written; 185 | length -= written; 186 | } else { 187 | DBUGF("Failed to write data"); 188 | } 189 | 190 | /* 191 | bool aligned = RESPONSE_CONTENT == _state; 192 | if(aligned && (!IS_ALIGNED(ptr) || length < 32)) 193 | { 194 | char buffer[32]; 195 | uint32_t copy = sizeof(buffer) - ((uint32_t)ptr & 0x00000003); // byte aligned mask 196 | if(copy > length) { 197 | copy = length; 198 | } 199 | DBUGF("None aligned write %d@%p", copy, ptr); 200 | memcpy_P(buffer, ptr, copy); 201 | 202 | written = request->client()->write(buffer, copy); 203 | if(written > 0) { 204 | _writtenLength += written; 205 | ptr += written; 206 | length -= written; 207 | } else { 208 | DBUGF("Failed to write data"); 209 | } 210 | } 211 | 212 | if(!aligned || IS_ALIGNED(ptr)) 213 | { 214 | size_t outLen = length; 215 | if(outLen > space) { 216 | outLen = space; 217 | } 218 | DBUGF("Aligned write %d@%p", outLen, ptr); 219 | written = request->client()->write(ptr, outLen); 220 | if(written > 0) { 221 | _writtenLength += written; 222 | ptr += written; 223 | length -= written; 224 | } else { 225 | DBUGF("Failed to write data"); 226 | } 227 | } 228 | */ 229 | 230 | if(0 == length) 231 | { 232 | switch(_state) 233 | { 234 | case RESPONSE_HEADERS: 235 | _state = RESPONSE_CONTENT; 236 | ptr = _content->data; 237 | length = _content->length; 238 | break; 239 | case RESPONSE_CONTENT: 240 | _state = RESPONSE_WAIT_ACK; 241 | break; 242 | default: 243 | break; 244 | } 245 | } 246 | 247 | return written; 248 | } 249 | 250 | return 0; 251 | } 252 | 253 | void StaticFileResponse::_respond(AsyncWebServerRequest *request){ 254 | _state = RESPONSE_HEADERS; 255 | String headBuffer; 256 | _assembleHead(headBuffer, request->version()); 257 | _header = headBuffer; 258 | 259 | _state = RESPONSE_HEADERS; 260 | ptr = _header.c_str(); 261 | length = _header.length(); 262 | 263 | write(request); 264 | } 265 | 266 | size_t StaticFileResponse::_ack(AsyncWebServerRequest *request, size_t len, uint32_t time){ 267 | _ackedLength += len; 268 | return write(request); 269 | } 270 | -------------------------------------------------------------------------------- /src/src.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #ifdef ENABLE_WDT 28 | #include 29 | #endif 30 | #include "emonesp.h" 31 | #include "app_config.h" 32 | #include "esp_wifi.h" 33 | #include "web_server.h" 34 | #include "ota.h" 35 | #include "input.h" 36 | #include "emoncms.h" 37 | #include "mqtt.h" 38 | #include "http.h" 39 | #include "autoauth.h" 40 | #include 41 | #include "energy_meter.h" 42 | // See energy meter specific configuration in energy_meter.h 43 | 44 | WiFiUDP ntpUDP; 45 | NTPClient timeClient(ntpUDP,"europe.pool.ntp.org",time_offset,60000); 46 | unsigned long last_ctrl_update = 0; 47 | unsigned long last_pushbtn_check = 0; 48 | bool pushbtn_action = 0; 49 | bool pushbtn_state = 0; 50 | bool last_pushbtn_state = 0; 51 | 52 | static uint32_t last_mem = 0; 53 | static uint32_t start_mem = 0; 54 | static unsigned long mem_info_update = 0; 55 | 56 | // ------------------------------------------------------------------- 57 | // SETUP 58 | // ------------------------------------------------------------------- 59 | void setup() { 60 | 61 | #ifdef ENABLE_WDT 62 | enableLoopWDT(); 63 | #endif 64 | 65 | debug_setup(); 66 | 67 | DEBUG.println(); 68 | DEBUG.println(); 69 | DEBUG.print("EmonESP "); 70 | DEBUG.println(node_name.c_str()); 71 | DEBUG.println("Firmware: " + currentfirmware); 72 | DEBUG.printf("Free: %d\n", ESP.getFreeHeap()); 73 | 74 | DBUG("Node type: "); 75 | DBUGLN(node_type); 76 | 77 | // Read saved settings from the config 78 | config_load_settings(); 79 | timeClient.setTimeOffset(time_offset); 80 | 81 | DBUGF("After config_load_settings: %d", ESP.getFreeHeap()); 82 | 83 | DBUG("Node name: "); 84 | DBUGLN(node_name); 85 | 86 | // --------------------------------------------------------- 87 | // pin setup 88 | pinMode(WIFI_LED, OUTPUT); 89 | digitalWrite(WIFI_LED, !WIFI_LED_ON_STATE); 90 | 91 | pinMode(CONTROL_PIN, OUTPUT); 92 | digitalWrite(CONTROL_PIN, !CONTROL_PIN_ON_STATE); 93 | 94 | // custom: analog output pin 95 | #ifdef VOLTAGE_OUT_PIN 96 | pinMode(4, OUTPUT); 97 | #endif 98 | // --------------------------------------------------------- 99 | 100 | // Initial LED on 101 | led_flash(3000, 100); 102 | 103 | // Initialise the WiFi 104 | wifi_setup(); 105 | DBUGF("After wifi_setup: %d", ESP.getFreeHeap()); 106 | led_flash(50, 50); 107 | 108 | // Bring up the web server 109 | web_server_setup(); 110 | DBUGF("After web_server_setup: %d", ESP.getFreeHeap()); 111 | led_flash(50, 50); 112 | 113 | // Start the OTA update systems 114 | ota_setup(); 115 | DBUGF("After ota_setup: %d", ESP.getFreeHeap()); 116 | 117 | // Start auto auth 118 | auth_setup(); 119 | DBUGF("After auth_setup: %d", ESP.getFreeHeap()); 120 | 121 | // Time 122 | timeClient.begin(); 123 | DBUGF("After timeClient.begin: %d", ESP.getFreeHeap()); 124 | 125 | energy_meter_setup(); 126 | 127 | #ifdef ENABLE_WDT 128 | DEBUG.println("Watchdog timer is enabled."); 129 | feedLoopWDT(); 130 | #endif 131 | 132 | delay(100); 133 | 134 | start_mem = last_mem = ESP.getFreeHeap(); 135 | } // end setup 136 | 137 | void led_flash(int ton, int toff) { 138 | digitalWrite(WIFI_LED, WIFI_LED_ON_STATE); 139 | delay(ton); 140 | digitalWrite(WIFI_LED, WIFI_LED_ON_STATE); 141 | delay(toff); 142 | } 143 | 144 | // ------------------------------------------------------------------- 145 | // LOOP 146 | // ------------------------------------------------------------------- 147 | void loop() 148 | { 149 | #ifdef ENABLE_WDT 150 | feedLoopWDT(); 151 | #endif 152 | 153 | if (millis() > mem_info_update) { 154 | mem_info_update = millis() + 2000; 155 | uint32_t current = ESP.getFreeHeap(); 156 | int32_t diff = (int32_t)(last_mem - current); 157 | if(diff != 0) { 158 | DEBUG.printf("Free memory %u - diff %d %d\n", current, diff, start_mem - current); 159 | last_mem = current; 160 | } 161 | } 162 | 163 | ota_loop(); 164 | web_server_loop(); 165 | wifi_loop(); 166 | timeClient.update(); 167 | energy_meter_loop(); 168 | 169 | StaticJsonDocument<512> data; 170 | boolean gotInput = input_get(data); 171 | 172 | if (wifi_client_connected()) { 173 | mqtt_loop(); 174 | if(gotInput) { 175 | emoncms_publish(data); 176 | event_send(data); 177 | } 178 | } 179 | 180 | auth_loop(); 181 | 182 | // -------------------------------------------------------------- 183 | // CONTROL UPDATE 184 | // -------------------------------------------------------------- 185 | if ((millis()-last_ctrl_update)>1000 || ctrl_update) { 186 | last_ctrl_update = millis(); 187 | ctrl_update = 0; 188 | ctrl_state = 0; // default off 189 | 190 | // 1. Timer 191 | int timenow = timeClient.getHours()*100+timeClient.getMinutes(); 192 | 193 | if (timer_stop1>=timer_start1 && (timenow>=timer_start1 && timenow=timer_start2 && (timenow>=timer_start2 && timenow=timer_start1 || timenow=timer_start2 || timenow100) { 218 | last_pushbtn_check = millis(); 219 | 220 | last_pushbtn_state = pushbtn_state; 221 | pushbtn_state = !digitalRead(0); 222 | 223 | if (pushbtn_state && last_pushbtn_state && !pushbtn_action) { 224 | pushbtn_action = 1; 225 | if (ctrl_mode=="On") ctrl_mode = "Off"; else ctrl_mode = "On"; 226 | if (mqtt_server!=0) mqtt_publish("out/ctrlmode",String(ctrl_mode)); 227 | 228 | } 229 | if (!pushbtn_state && !last_pushbtn_state) pushbtn_action = 0; 230 | } 231 | 232 | } // end loop 233 | 234 | String getTime() { 235 | return timeClient.getFormattedTime(); 236 | } 237 | 238 | void setTimeOffset() { 239 | timeClient.setTimeOffset(time_offset); 240 | } 241 | 242 | void event_send(String &json) 243 | { 244 | StaticJsonDocument<512> event; 245 | deserializeJson(event, json); 246 | event_send(event); 247 | } 248 | 249 | void event_send(JsonDocument &event) 250 | { 251 | #ifdef ENABLE_DEBUG 252 | serializeJson(event, DEBUG_PORT); 253 | DBUGLN(""); 254 | #endif 255 | web_server_event(event); 256 | mqtt_publish(event); 257 | } 258 | -------------------------------------------------------------------------------- /src/mqtt.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #include "emonesp.h" 28 | #include "esp_wifi.h" 29 | #include "mqtt.h" 30 | #include "app_config.h" 31 | #include "espal.h" 32 | 33 | WiFiClient espClient; // Create client for MQTT 34 | PubSubClient mqttclient(espClient); // Create client for MQTT 35 | 36 | static long nextMqttReconnectAttempt = 0; 37 | static unsigned long mqttRestartTime = 0; 38 | 39 | int clientTimeout = 0; 40 | 41 | // ------------------------------------------------------------------- 42 | // MQTT Control callback for WIFI Relay and Sonoff smartplug 43 | // ------------------------------------------------------------------- 44 | static void mqtt_msg_callback(char *topic, byte *payload, unsigned int length) { 45 | 46 | String topicstr = String(topic); 47 | String payloadstr = String((char *)payload).substring(0, length); 48 | payloadstr.trim(); // remove unexpected whitespace 49 | 50 | DBUGF("Message arrived topic:[%s] payload: [%s]", topic, payload); 51 | 52 | // -------------------------------------------------------------------------- 53 | // State 54 | // -------------------------------------------------------------------------- 55 | if (topicstr.compareTo(mqtt_topic+"/"+node_name+"/in/ctrlmode")==0) { 56 | DEBUG.print(F("Status: ")); 57 | if (payloadstr.compareTo("2")==0) { 58 | ctrl_mode = "Timer"; 59 | } else if (payloadstr.compareTo("1")==0) { 60 | ctrl_mode = "On"; 61 | } else if (payloadstr.compareTo("0")==0) { 62 | ctrl_mode = "Off"; 63 | } else if (payloadstr.compareTo("Timer")==0) { 64 | ctrl_mode = "Timer"; 65 | } else if (payloadstr.compareTo("On")==0) { 66 | ctrl_mode = "On"; 67 | } else if (payloadstr.compareTo("Off")==0) { 68 | ctrl_mode = "Off"; 69 | } else { 70 | ctrl_mode = "Off"; 71 | } 72 | DEBUG.println(ctrl_mode); 73 | // -------------------------------------------------------------------------- 74 | // Timer 75 | // -------------------------------------------------------------------------- 76 | } else if (topicstr.compareTo(mqtt_topic+"/"+node_name+"/in/timer")==0) { 77 | DEBUG.print(F("Timer: ")); 78 | if (payloadstr.length()==9) { 79 | String tstart = payloadstr.substring(0,4); 80 | String tstop = payloadstr.substring(5,9); 81 | timer_start1 = tstart.toInt(); 82 | timer_stop1 = tstop.toInt(); 83 | DEBUG.println(tstart+" "+tstop); 84 | } 85 | if (payloadstr.length()==19) { 86 | String tstart1 = payloadstr.substring(0,4); 87 | String tstop1 = payloadstr.substring(5,9); 88 | timer_start1 = tstart1.toInt(); 89 | timer_stop1 = tstop1.toInt(); 90 | String tstart2 = payloadstr.substring(10,14); 91 | String tstop2 = payloadstr.substring(15,19); 92 | timer_start2 = tstart2.toInt(); 93 | timer_stop2 = tstop2.toInt(); 94 | DEBUG.println(tstart1+":"+tstop1+" "+tstart2+":"+tstop2); 95 | } 96 | // -------------------------------------------------------------------------- 97 | // Vout 98 | // -------------------------------------------------------------------------- 99 | } else if (topicstr.compareTo(mqtt_topic+"/"+node_name+"/in/vout")==0) { 100 | DEBUG.print(F("Vout: ")); 101 | voltage_output = payloadstr.toInt(); 102 | DEBUG.println(voltage_output); 103 | // -------------------------------------------------------------------------- 104 | // FlowT 105 | // -------------------------------------------------------------------------- 106 | } else if (topicstr.compareTo(mqtt_topic+"/"+node_name+"/in/flowT")==0) { 107 | DEBUG.print(F("FlowT: ")); 108 | float flow = payloadstr.toFloat(); 109 | voltage_output = (int) (flow - 7.14)/0.0371; 110 | DEBUG.println(String(flow)+" vout:"+String(voltage_output)); 111 | // -------------------------------------------------------------------------- 112 | // Return device state 113 | // -------------------------------------------------------------------------- 114 | } else if (topicstr.compareTo(mqtt_topic+"/"+node_name+"/in/state")==0) { 115 | DEBUG.println(F("State: ")); 116 | 117 | String s = "{"; 118 | s += "\"ip\":\""+ipaddress+"\","; 119 | // s += "\"time\":\"" + String(getTime()) + "\","; 120 | s += "\"ctrlmode\":\"" + String(ctrl_mode) + "\","; 121 | s += "\"timer\":\"" + String(timer_start1)+" "+String(timer_stop1)+" "+String(timer_start2)+" "+String(timer_stop2) + "\","; 122 | s += "\"vout\":\"" + String(voltage_output) + "\""; 123 | s += "}"; 124 | mqtt_publish("out/state",s); 125 | } 126 | } 127 | 128 | // ------------------------------------------------------------------- 129 | // MQTT Connect 130 | // ------------------------------------------------------------------- 131 | boolean mqtt_connect() 132 | { 133 | mqttclient.setServer(mqtt_server.c_str(), mqtt_port); 134 | mqttclient.setCallback(mqtt_msg_callback); //function to be called when mqtt msg is received on subscribed topic 135 | 136 | DEBUG.print(F("MQTT Connecting to...")); 137 | DEBUG.println(mqtt_server.c_str()); 138 | 139 | String strID = String(node_name.c_str()); 140 | String lwtTopic = mqtt_topic + "/" + node_name + "/status"; 141 | 142 | bool connected = mqttclient.connect( 143 | strID.c_str(), 144 | mqtt_user.c_str(), 145 | mqtt_pass.c_str(), 146 | lwtTopic.c_str(), 147 | 1, // QoS 148 | true, // retain 149 | "offline" 150 | ); 151 | 152 | if (connected) { // Attempt to connect 153 | DEBUG.println(F("MQTT connected")); 154 | mqtt_publish("status", "online"); 155 | mqtt_publish("describe", node_type); 156 | 157 | String subscribe_topic = mqtt_topic + "/" + node_name + "/in/#"; 158 | mqttclient.subscribe(subscribe_topic.c_str()); 159 | 160 | } else { 161 | DEBUG.print(F("MQTT failed: ")); 162 | DEBUG.println(mqttclient.state()); 163 | return(0); 164 | } 165 | return (1); 166 | } 167 | 168 | // ------------------------------------------------------------------- 169 | // Publish to MQTT 170 | // ------------------------------------------------------------------- 171 | void mqtt_publish(String topic_p2, String data) 172 | { 173 | if(!config_mqtt_enabled() || !mqttclient.connected()) { 174 | return; 175 | } 176 | 177 | String topic = String(mqtt_topic) + "/" + node_name + "/" + topic_p2; 178 | mqttclient.publish(topic.c_str(), data.c_str()); 179 | } 180 | 181 | // ------------------------------------------------------------------- 182 | // Publish to MQTT 183 | // Split up data string into sub topics: e.g 184 | // data = CT1:3935,CT2:325,T1:12.5,T2:16.9,T3:11.2,T4:34.7 185 | // base topic = emon/emonesp 186 | // MQTT Publish: emon/emonesp/CT1 > 3935 etc.. 187 | // ------------------------------------------------------------------- 188 | void mqtt_publish(JsonDocument &data) 189 | { 190 | Profile_Start(mqtt_publish); 191 | 192 | if(!config_mqtt_enabled() || !mqttclient.connected()) { 193 | return; 194 | } 195 | 196 | JsonObject root = data.as(); 197 | for (JsonPair kv : root) { 198 | String topic = mqtt_topic + "/"; 199 | topic += kv.key().c_str(); 200 | String val = kv.value().as(); 201 | mqttclient.publish(topic.c_str(), val.c_str()); 202 | } 203 | 204 | String ram_topic = mqtt_topic + "/" + node_name + "/" + mqtt_feed_prefix + "freeram"; 205 | String free_ram = String(ESP.getFreeHeap()); 206 | mqttclient.publish(ram_topic.c_str(), free_ram.c_str()); 207 | 208 | Profile_End(mqtt_publish, 5); 209 | } 210 | 211 | // ------------------------------------------------------------------- 212 | // MQTT state management 213 | // 214 | // Call every time around loop() if connected to the WiFi 215 | // ------------------------------------------------------------------- 216 | void mqtt_loop() 217 | { 218 | Profile_Start(mqtt_loop); 219 | 220 | // Do we need to restart MQTT? 221 | if(mqttRestartTime > 0 && millis() > mqttRestartTime) 222 | { 223 | mqttRestartTime = 0; 224 | if (mqttclient.connected()) { 225 | DBUGF("Disconnecting MQTT"); 226 | mqttclient.disconnect(); 227 | } 228 | nextMqttReconnectAttempt = 0; 229 | } 230 | 231 | if(config_mqtt_enabled()) 232 | { 233 | if (!mqttclient.connected()) { 234 | long now = millis(); 235 | // try and reconnect every x seconds 236 | if (now > nextMqttReconnectAttempt) { 237 | nextMqttReconnectAttempt = now + MQTT_CONNECT_TIMEOUT; 238 | mqtt_connect(); // Attempt to reconnect 239 | } 240 | } else { 241 | // if MQTT connected 242 | mqttclient.loop(); 243 | } 244 | } 245 | 246 | Profile_End(mqtt_loop, 5); 247 | } 248 | 249 | void mqtt_restart() 250 | { 251 | // If connected disconnect MQTT to trigger re-connect with new details 252 | mqttRestartTime = millis(); 253 | } 254 | 255 | boolean mqtt_connected() 256 | { 257 | return mqttclient.connected(); 258 | } 259 | -------------------------------------------------------------------------------- /src/app_config_v1.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * ------------------------------------------------------------------- 3 | * EmonESP Serial to Emoncms gateway 4 | * ------------------------------------------------------------------- 5 | * Adaptation of Chris Howells OpenEVSE ESP Wifi 6 | * by Trystan Lea, Glyn Hudson, OpenEnergyMonitor 7 | * Modified to use with the CircuitSetup.us energy meters by jdeglavina 8 | * All adaptation GNU General Public License as below. 9 | * 10 | * ------------------------------------------------------------------- 11 | * 12 | * This file is part of OpenEnergyMonitor.org project. 13 | * EmonESP is free software; you can redistribute it and/or modify 14 | * it under the terms of the GNU General Public License as published by 15 | * the Free Software Foundation; either version 3, or (at your option) 16 | * any later version. 17 | * EmonESP is distributed in the hope that it will be useful, 18 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 | * GNU General Public License for more details. 21 | * You should have received a copy of the GNU General Public License 22 | * along with EmonESP; see the file COPYING. If not, write to the 23 | * Free Software Foundation, Inc., 59 Temple Place - Suite 330, 24 | * Boston, MA 02111-1307, USA. 25 | */ 26 | 27 | #include "emonesp.h" 28 | #include "app_config.h" 29 | #include "espal.h" 30 | 31 | #include 32 | #include // Save config settings 33 | 34 | #define EEPROM_ESID_SIZE 32 35 | #define EEPROM_EPASS_SIZE 64 36 | #define EEPROM_EMON_API_KEY_SIZE 32 37 | #define EEPROM_EMON_SERVER_SIZE 32 38 | #define EEPROM_EMON_PATH_SIZE 16 39 | #define EEPROM_EMON_NODE_SIZE 32 40 | #define EEPROM_MQTT_SERVER_SIZE 32 41 | #define EEPROM_MQTT_PORT_SIZE 2 42 | #define EEPROM_MQTT_TOPIC_SIZE 32 43 | #define EEPROM_MQTT_USER_SIZE 32 44 | #define EEPROM_MQTT_PASS_SIZE 64 45 | #define EEPROM_EMON_FINGERPRINT_SIZE 60 46 | #define EEPROM_MQTT_FEED_PREFIX_SIZE 10 47 | #define EEPROM_WWW_USER_SIZE 16 48 | #define EEPROM_WWW_PASS_SIZE 16 49 | #define EEPROM_CAL_VOLTAGE_SIZE 6 50 | #define EEPROM_CAL_CT1_SIZE 6 51 | #define EEPROM_CAL_CT2_SIZE 6 52 | #define EEPROM_CAL_FREQ_SIZE 6 53 | #define EEPROM_CAL_GAIN_SIZE 6 54 | #ifdef SOLAR_METER 55 | #define EEPROM_CAL_SVOLTAGE_SIZE 6 56 | #define EEPROM_CAL_SCT1_SIZE 6 57 | #define EEPROM_CAL_SCT2_SIZE 6 58 | #endif 59 | #define EEPROM_TIMER_START1_SIZE 2 60 | #define EEPROM_TIMER_STOP1_SIZE 2 61 | #define EEPROM_TIMER_START2_SIZE 2 62 | #define EEPROM_TIMER_STOP2_SIZE 2 63 | #define EEPROM_VOLTAGE_OUTPUT_SIZE 2 64 | #define EEPROM_TIME_OFFSET_SIZE 2 65 | #define EEPROM_SIZE 1024 66 | 67 | #define EEPROM_ESID_START 0 68 | #define EEPROM_ESID_END (EEPROM_ESID_START + EEPROM_ESID_SIZE) 69 | #define EEPROM_EPASS_START EEPROM_ESID_END 70 | #define EEPROM_EPASS_END (EEPROM_EPASS_START + EEPROM_EPASS_SIZE) 71 | #define EEPROM_EMON_API_KEY_START EEPROM_EPASS_END 72 | #define EEPROM_EMON_API_KEY_END (EEPROM_EMON_API_KEY_START + EEPROM_EMON_API_KEY_SIZE) 73 | #define EEPROM_EMON_SERVER_START EEPROM_EMON_API_KEY_END 74 | #define EEPROM_EMON_SERVER_END (EEPROM_EMON_SERVER_START + EEPROM_EMON_SERVER_SIZE) 75 | #define EEPROM_EMON_NODE_START EEPROM_EMON_SERVER_END 76 | #define EEPROM_EMON_NODE_END (EEPROM_EMON_NODE_START + EEPROM_EMON_NODE_SIZE) 77 | #define EEPROM_MQTT_SERVER_START EEPROM_EMON_NODE_END 78 | #define EEPROM_MQTT_SERVER_END (EEPROM_MQTT_SERVER_START + EEPROM_MQTT_SERVER_SIZE) 79 | #define EEPROM_MQTT_PORT_START EEPROM_MQTT_SERVER_END 80 | #define EEPROM_MQTT_PORT_END (EEPROM_MQTT_PORT_START + EEPROM_MQTT_PORT_SIZE) 81 | #define EEPROM_MQTT_TOPIC_START EEPROM_MQTT_PORT_END 82 | #define EEPROM_MQTT_TOPIC_END (EEPROM_MQTT_TOPIC_START + EEPROM_MQTT_TOPIC_SIZE) 83 | #define EEPROM_MQTT_USER_START EEPROM_MQTT_TOPIC_END 84 | #define EEPROM_MQTT_USER_END (EEPROM_MQTT_USER_START + EEPROM_MQTT_USER_SIZE) 85 | #define EEPROM_MQTT_PASS_START EEPROM_MQTT_USER_END 86 | #define EEPROM_MQTT_PASS_END (EEPROM_MQTT_PASS_START + EEPROM_MQTT_PASS_SIZE) 87 | #define EEPROM_EMON_FINGERPRINT_START EEPROM_MQTT_PASS_END 88 | #define EEPROM_EMON_FINGERPRINT_END (EEPROM_EMON_FINGERPRINT_START + EEPROM_EMON_FINGERPRINT_SIZE) 89 | #define EEPROM_MQTT_FEED_PREFIX_START EEPROM_EMON_FINGERPRINT_END 90 | #define EEPROM_MQTT_FEED_PREFIX_END (EEPROM_MQTT_FEED_PREFIX_START + EEPROM_MQTT_FEED_PREFIX_SIZE) 91 | #define EEPROM_WWW_USER_START EEPROM_MQTT_FEED_PREFIX_END 92 | #define EEPROM_WWW_USER_END (EEPROM_WWW_USER_START + EEPROM_WWW_USER_SIZE) 93 | #define EEPROM_WWW_PASS_START EEPROM_WWW_USER_END 94 | #define EEPROM_WWW_PASS_END (EEPROM_WWW_PASS_START + EEPROM_WWW_PASS_SIZE) 95 | #define EEPROM_EMON_PATH_START EEPROM_WWW_PASS_END 96 | #define EEPROM_EMON_PATH_END (EEPROM_EMON_PATH_START + EEPROM_EMON_PATH_SIZE) 97 | 98 | #define EEPROM_TIMER_START1_START EEPROM_EMON_PATH_END 99 | #define EEPROM_TIMER_START1_END (EEPROM_TIMER_START1_START + EEPROM_TIMER_START1_SIZE) 100 | #define EEPROM_TIMER_STOP1_START EEPROM_TIMER_START1_END 101 | #define EEPROM_TIMER_STOP1_END (EEPROM_TIMER_STOP1_START + EEPROM_TIMER_STOP1_SIZE) 102 | #define EEPROM_TIMER_START2_START EEPROM_TIMER_STOP1_END 103 | #define EEPROM_TIMER_START2_END (EEPROM_TIMER_START2_START + EEPROM_TIMER_START2_SIZE) 104 | #define EEPROM_TIMER_STOP2_START EEPROM_TIMER_START2_END 105 | #define EEPROM_TIMER_STOP2_END (EEPROM_TIMER_STOP2_START + EEPROM_TIMER_STOP2_SIZE) 106 | 107 | #define EEPROM_VOLTAGE_OUTPUT_START EEPROM_TIMER_STOP2_END 108 | #define EEPROM_VOLTAGE_OUTPUT_END (EEPROM_VOLTAGE_OUTPUT_START + EEPROM_VOLTAGE_OUTPUT_SIZE) 109 | 110 | #define EEPROM_TIME_OFFSET_START EEPROM_VOLTAGE_OUTPUT_END 111 | #define EEPROM_TIME_OFFSET_END (EEPROM_TIME_OFFSET_START + EEPROM_TIME_OFFSET_SIZE) 112 | 113 | #define EEPROM_CAL_VOLTAGE_START EEPROM_TIME_OFFSET_END 114 | #define EEPROM_CAL_VOLTAGE_END (EEPROM_CAL_VOLTAGE_START + EEPROM_CAL_VOLTAGE_SIZE) 115 | #define EEPROM_CAL_CT1_START EEPROM_CAL_VOLTAGE_END 116 | #define EEPROM_CAL_CT1_END (EEPROM_CAL_CT1_START + EEPROM_CAL_CT1_SIZE) 117 | #define EEPROM_CAL_CT2_START EEPROM_CAL_CT1_END 118 | #define EEPROM_CAL_CT2_END (EEPROM_CAL_CT2_START + EEPROM_CAL_CT2_SIZE) 119 | #define EEPROM_CAL_FREQ_START EEPROM_CAL_CT2_END 120 | #define EEPROM_CAL_FREQ_END (EEPROM_CAL_FREQ_START + EEPROM_CAL_FREQ_SIZE) 121 | #define EEPROM_CAL_GAIN_START EEPROM_CAL_FREQ_END 122 | #define EEPROM_CAL_GAIN_END (EEPROM_CAL_GAIN_START + EEPROM_CAL_GAIN_SIZE) 123 | #ifdef SOLAR_METER 124 | #define EEPROM_CAL_SVOLTAGE_START EEPROM_CAL_GAIN_END 125 | #define EEPROM_CAL_SVOLTAGE_END (EEPROM_CAL_SVOLTAGE_START + EEPROM_CAL_SVOLTAGE_SIZE) 126 | #define EEPROM_CAL_SCT1_START EEPROM_CAL_SVOLTAGE_END 127 | #define EEPROM_CAL_SCT1_END (EEPROM_CAL_SCT1_START + EEPROM_CAL_SCT1_SIZE) 128 | #define EEPROM_CAL_SCT2_START EEPROM_CAL_SCT1_END 129 | #define EEPROM_CAL_SCT2_END (EEPROM_CAL_SCT2_START + EEPROM_CAL_SCT2_SIZE) 130 | #define EEPROM_CONFIG_END EEPROM_CAL_SCT2_END 131 | #else 132 | #define EEPROM_CONFIG_END EEPROM_CAL_GAIN_END 133 | #endif 134 | 135 | #if EEPROM_CONFIG_END > EEPROM_SIZE 136 | #error EEPROM_SIZE too small 137 | #endif 138 | 139 | int read_offset = 0; 140 | 141 | void EEPROM_read_string(int start, int count, String & val) { 142 | String newVal; 143 | start += read_offset; 144 | for (int i = 0; i < count; ++i){ 145 | byte c = EEPROM.read(start+i); 146 | if (c != 0 && c != 255) 147 | newVal += (char) c; 148 | } 149 | 150 | if(newVal) { 151 | val = newVal; 152 | } 153 | } 154 | 155 | void EEPROM_read_int(int start, int & val) { 156 | start += read_offset; 157 | byte high = EEPROM.read(start); 158 | byte low = EEPROM.read(start+1); 159 | val=word(high,low); 160 | } 161 | 162 | // ------------------------------------------------------------------- 163 | // Load saved settings from EEPROM 164 | // ------------------------------------------------------------------- 165 | void config_load_v1_settings() 166 | { 167 | DBUGLN("Loading config"); 168 | 169 | EEPROM.begin(EEPROM_SIZE); 170 | 171 | // Load WiFi values 172 | EEPROM_read_string(EEPROM_ESID_START, EEPROM_ESID_SIZE, esid); 173 | EEPROM_read_string(EEPROM_EPASS_START, EEPROM_EPASS_SIZE, epass); 174 | 175 | // EmonCMS settings 176 | EEPROM_read_string(EEPROM_EMON_API_KEY_START, EEPROM_EMON_API_KEY_SIZE, 177 | emoncms_apikey); 178 | EEPROM_read_string(EEPROM_EMON_SERVER_START, EEPROM_EMON_SERVER_SIZE, 179 | emoncms_server); 180 | EEPROM_read_string(EEPROM_EMON_PATH_START, EEPROM_EMON_PATH_SIZE, 181 | emoncms_path); 182 | EEPROM_read_string(EEPROM_EMON_NODE_START, EEPROM_EMON_NODE_SIZE, 183 | emoncms_node); 184 | EEPROM_read_string(EEPROM_EMON_FINGERPRINT_START, 185 | EEPROM_EMON_FINGERPRINT_SIZE, emoncms_fingerprint); 186 | 187 | flags &= ~CONFIG_SERVICE_EMONCMS; 188 | if(emoncms_apikey != 0) { 189 | flags |= CONFIG_SERVICE_EMONCMS; 190 | } 191 | 192 | // MQTT settings 193 | EEPROM_read_string(EEPROM_MQTT_SERVER_START, EEPROM_MQTT_SERVER_SIZE, mqtt_server); 194 | EEPROM_read_int(EEPROM_MQTT_PORT_START, mqtt_port); 195 | DBUGF("mqtt_port %d %2s", mqtt_port, (char *)&mqtt_port); 196 | if (mqtt_port==0) { 197 | mqtt_port = 1883; // apply a default port 198 | } 199 | 200 | // Anoyingly the mqtt_port was added in the middle of the mqtt block not at the end of EEPROM 201 | // detect some values that may be older firmwares 202 | if(word('e', 'm')) { 203 | mqtt_port = 1883; // apply a default port 204 | read_offset = -EEPROM_MQTT_PORT_SIZE; 205 | } 206 | 207 | EEPROM_read_string(EEPROM_MQTT_TOPIC_START, EEPROM_MQTT_TOPIC_SIZE, mqtt_topic); 208 | EEPROM_read_string(EEPROM_MQTT_FEED_PREFIX_START, EEPROM_MQTT_FEED_PREFIX_SIZE, mqtt_feed_prefix); 209 | EEPROM_read_string(EEPROM_MQTT_USER_START, EEPROM_MQTT_USER_SIZE, mqtt_user); 210 | EEPROM_read_string(EEPROM_MQTT_PASS_START, EEPROM_MQTT_PASS_SIZE, mqtt_pass); 211 | 212 | flags &= ~CONFIG_SERVICE_MQTT; 213 | if(mqtt_server != 0) { 214 | flags |= CONFIG_SERVICE_MQTT; 215 | } 216 | 217 | // Calibration settings 218 | EEPROM_read_int(EEPROM_CAL_VOLTAGE_START, voltage_cal); 219 | EEPROM_read_int(EEPROM_CAL_CT1_START, ct1_cal); 220 | EEPROM_read_int(EEPROM_CAL_CT2_START, ct2_cal); 221 | EEPROM_read_int(EEPROM_CAL_FREQ_START, freq_cal); 222 | EEPROM_read_int(EEPROM_CAL_GAIN_START, gain_cal); 223 | #ifdef SOLAR_METER 224 | EEPROM_read_int(EEPROM_CAL_SVOLTAGE_START, svoltage_cal); 225 | EEPROM_read_int(EEPROM_CAL_SCT1_START, sct1_cal); 226 | EEPROM_read_int(EEPROM_CAL_SCT2_START, sct2_cal); 227 | #endif 228 | 229 | // Web server credentials 230 | EEPROM_read_string(EEPROM_WWW_USER_START, EEPROM_WWW_USER_SIZE, www_username); 231 | EEPROM_read_string(EEPROM_WWW_PASS_START, EEPROM_WWW_PASS_SIZE, www_password); 232 | 233 | // Read timer settings 234 | EEPROM_read_int(EEPROM_TIMER_START1_START, timer_start1); 235 | EEPROM_read_int(EEPROM_TIMER_STOP1_START, timer_stop1); 236 | EEPROM_read_int(EEPROM_TIMER_START2_START, timer_start2); 237 | EEPROM_read_int(EEPROM_TIMER_STOP2_START, timer_stop2); 238 | 239 | EEPROM_read_int(EEPROM_VOLTAGE_OUTPUT_START, voltage_output); 240 | 241 | EEPROM_read_int(EEPROM_TIME_OFFSET_START, time_offset); 242 | 243 | EEPROM.end(); 244 | } 245 | --------------------------------------------------------------------------------