├── .cproject ├── .gitignore ├── .project ├── .settings ├── language.settings.xml ├── org.eclipse.cdt.core.prefs └── org.eclipse.core.resources.prefs ├── .sproject ├── Aurora-Web-Invert-Monitor.ino ├── LICENSE.md ├── README.md ├── data ├── aurora-web.min.js.gz ├── favicon.ico ├── index.html ├── launcher-icon-0x.png ├── launcher-icon-1x.png ├── launcher-icon-2x.png ├── launcher-icon-3x.png ├── launcher-icon-4x.png ├── launcher-icon-5x.png ├── launcher-icon-trasp.png ├── launcher-icon.png ├── launcher-orig.png ├── manifest.json └── service-worker.js ├── firmware-release ├── Aurora_Web_Inverter_Monitor.d1_mini.bin ├── Aurora_Web_Inverter_Monitor.spiffs.bin ├── Aurora_Web_Inverter_Monitor_DEBUG.d1_mini.bin ├── Aurora_Web_Inverter_Monitor_DEBUG_FTP.d1_mini.bin └── README.md └── spec.d /.cproject: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 42 | 43 | 44 | 45 | 66 | 67 | 68 | 69 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /sloeber.ino.cpp 2 | /Release/ 3 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | AuroraConnectionStoreJsonSD 4 | 5 | 6 | 7 | 8 | 9 | io.sloeber.core.inoToCpp 10 | 11 | 12 | 13 | 14 | org.eclipse.cdt.managedbuilder.core.genmakebuilder 15 | clean,full,incremental, 16 | 17 | 18 | 19 | 20 | org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder 21 | full,incremental, 22 | 23 | 24 | 25 | 26 | 27 | org.eclipse.cdt.core.cnature 28 | org.eclipse.cdt.core.ccnature 29 | org.eclipse.cdt.managedbuilder.core.managedBuildNature 30 | org.eclipse.cdt.managedbuilder.core.ScannerConfigNature 31 | io.sloeber.arduinonature 32 | 33 | 34 | 35 | core/core 36 | 2 37 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/cores/esp8266 38 | 39 | 40 | core/variant 41 | 2 42 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/variants/d1_mini 43 | 44 | 45 | libraries/ArduinoJson 46 | 2 47 | D:/program/Sloeber/arduinoPlugin/libraries/ArduinoJson/6.5.0-beta 48 | 49 | 50 | libraries/ArduinoThread 51 | 2 52 | WORKSPACE_LOC/ArduinoThread 53 | 54 | 55 | libraries/DNSServer 56 | 2 57 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/DNSServer 58 | 59 | 60 | libraries/EMailSender 61 | 2 62 | D:/Projects/Arduino/sloeber-workspace-lib/EMailSender 63 | 64 | 65 | libraries/ESP8266WebServer 66 | 2 67 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/ESP8266WebServer 68 | 69 | 70 | libraries/ESP8266WiFi 71 | 2 72 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/ESP8266WiFi 73 | 74 | 75 | libraries/ESP8266mDNS 76 | 2 77 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/ESP8266mDNS 78 | 79 | 80 | libraries/Hash 81 | 2 82 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/Hash 83 | 84 | 85 | libraries/NTPClient 86 | 2 87 | D:/program/Sloeber/arduinoPlugin/libraries/NTPClient/3.2.1 88 | 89 | 90 | libraries/SD 91 | 2 92 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/SD 93 | 94 | 95 | libraries/SPI 96 | 2 97 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/SPI 98 | 99 | 100 | libraries/Thread 101 | 2 102 | ECLIPSE_HOME/arduinoPlugin/libraries/Thread/0.0.2 103 | 104 | 105 | libraries/TimeLib 106 | 2 107 | WORKSPACE_LOC/TimeLib 108 | 109 | 110 | libraries/Timezone 111 | 2 112 | D:/program/Sloeber/arduinoPlugin/libraries/Timezone/1.2.4 113 | 114 | 115 | libraries/WebSockets 116 | 2 117 | D:/program/Sloeber/arduinoPlugin/libraries/WebSockets/2.3.5 118 | 119 | 120 | libraries/WiFiManager 121 | 2 122 | WORKSPACE_LOC/WiFiManager 123 | 124 | 125 | libraries/Wire 126 | 2 127 | D:/program/Sloeber/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/libraries/Wire 128 | 129 | 130 | libraries/aurora_communication_protocol 131 | 2 132 | WORKSPACE_LOC/aurora_communication_protocol 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /.settings/language.settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /.settings/org.eclipse.cdt.core.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | environment/project/io.sloeber.core.toolChain.release.1470130863/PATH/delimiter=; 3 | environment/project/io.sloeber.core.toolChain.release.1470130863/PATH/operation=replace 4 | environment/project/io.sloeber.core.toolChain.release.1470130863/PATH/value=${A.COMPILER.PATH}${PathDelimiter}${A.BUILD.GENERIC.PATH}${PathDelimiter}${SystemRoot}\\system32${PathDelimiter}${SystemRoot}${PathDelimiter}${SystemRoot}\\system32\\Wbem${PathDelimiter}${sloeber_path_extension} 5 | environment/project/io.sloeber.core.toolChain.release.1470130863/append=true 6 | environment/project/io.sloeber.core.toolChain.release.1470130863/appendContributed=true 7 | -------------------------------------------------------------------------------- /.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | eclipse.preferences.version=1 2 | encoding/=UTF-8 3 | -------------------------------------------------------------------------------- /.sproject: -------------------------------------------------------------------------------- 1 | Config.Release.board.BOARD.ID=d1_mini 2 | Config.Release.board.BOARD.MENU.CpuFrequency=80 3 | Config.Release.board.BOARD.MENU.Debug=Disabled 4 | Config.Release.board.BOARD.MENU.DebugLevel=None____ 5 | Config.Release.board.BOARD.MENU.FlashErase=none 6 | Config.Release.board.BOARD.MENU.FlashSize=4M1M 7 | Config.Release.board.BOARD.MENU.LwIPVariant=v2mss536 8 | Config.Release.board.BOARD.MENU.UploadSpeed=921600 9 | Config.Release.board.BOARD.MENU.VTable=flash 10 | Config.Release.board.BOARD.MENU.baud=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "921600" per il menu con ID "baud" per la board "d1_mini" 11 | Config.Release.board.BOARD.MENU.dbg=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "Disabled" per il menu con ID "dbg" per la board "d1_mini" 12 | Config.Release.board.BOARD.MENU.eesz=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "4MB (FS:2MB OTA:~1019KB)" per il menu con ID "eesz" per la board "d1_mini" 13 | Config.Release.board.BOARD.MENU.exception=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "Enabled" per il menu con ID "exception" per la board "d1_mini" 14 | Config.Release.board.BOARD.MENU.ip=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "v2 Lower Memory" per il menu con ID "ip" per la board "d1_mini" 15 | Config.Release.board.BOARD.MENU.lvl=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "None" per il menu con ID "lvl" per la board "d1_mini" 16 | Config.Release.board.BOARD.MENU.ssl=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "All SSL ciphers (most compatible)" per il menu con ID "ssl" per la board "d1_mini" 17 | Config.Release.board.BOARD.MENU.vt=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "Flash" per il menu con ID "vt" per la board "d1_mini" 18 | Config.Release.board.BOARD.MENU.wipe=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "Only Sketch" per il menu con ID "wipe" per la board "d1_mini" 19 | Config.Release.board.BOARD.MENU.xtal=getMenuItemIDFromMenuItemName non ha trovato la voce di menu denominata "80 MHz" per il menu con ID "xtal" per la board "d1_mini" 20 | Config.Release.board.BOARD.TXT=${SLOEBER_HOME}/arduinoPlugin/packages/esp8266/hardware/esp8266/2.4.2/boards.txt 21 | Config.Release.board.PROGRAMMER.NAME= 22 | Config.Release.board.UPLOAD.PORT=COM16 23 | Config.Release.compile.sloeber.extra.all= 24 | Config.Release.compile.sloeber.extra.archive= 25 | Config.Release.compile.sloeber.extra.assembly= 26 | Config.Release.compile.sloeber.extra.c.compile= 27 | Config.Release.compile.sloeber.extra.compile=-DARDUINO_BOARD=\"ESP8266_WEMOS_D1MINI\" 28 | Config.Release.compile.sloeber.extra.cpp.compile= 29 | Config.Release.compile.sloeber.extra.link= 30 | Config.Release.compile.sloeber.size.custom= 31 | Config.Release.compile.sloeber.size.type=RAW_RESULT 32 | Config.Release.compile.sloeber.warning_level=ALL 33 | Config.Release.compile.sloeber.warning_level.custom= 34 | Config.Release.other.IS_VERSION_CONTROLLED=false 35 | -------------------------------------------------------------------------------- /Aurora-Web-Invert-Monitor.ino: -------------------------------------------------------------------------------- 1 | /* 2 | * This work is licensed under the 3 | * Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Italy License. 4 | * To view a copy of this license, visit 5 | * http://creativecommons.org/licenses/by-nc-nd/3.0/it/ 6 | * or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. 7 | * 8 | * Renzo Mischianti 9 | * 10 | * https://www.mischianti.org/category/project/web-monitoring-station-for-abb-aurora-inverter-ex-power-one-now-fimer/ 11 | * 12 | */ 13 | #include "Arduino.h" 14 | #include 15 | #include 16 | #define FS_NO_GLOBALS 17 | #include "FS.h" 18 | #include 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include // Include the mDNS library 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #define SEND_EMAIL 34 | //#define FORCE_SEND_ERROR_MESSAGE 35 | 36 | #ifdef SEND_EMAIL 37 | #include 38 | #endif 39 | 40 | #define WRITE_SETTINGS_IF_EXIST 41 | 42 | // SD 43 | #define SD_WRONG_WRITE_NUMBER_ALERT 10 44 | #define CS_PIN D8 45 | 46 | #include // https://github.com/JChristensen/Timezone 47 | 48 | #define WS_ACTIVE 49 | 50 | #ifdef WS_ACTIVE 51 | #include 52 | #endif 53 | 54 | #define WS_PORT 8081 55 | #ifdef WS_ACTIVE 56 | WebSocketsServer webSocket = WebSocketsServer(WS_PORT); 57 | 58 | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length); 59 | #endif 60 | 61 | // HEAP file 62 | #define CONFIG_FILE_HEAP 4096 63 | #define BATTERY_STATE_HEAP 4096 64 | #define ALARM_IN_A_DAY 4096 65 | #define INVERTER_INFO_HEAP 4096 66 | #define PRODUCTION_AND_CUMULATED_HEAP 8192+8192 67 | #define CUMULATED_VALUES_HEAP 2048 68 | #define CUMULATED_VALUES_TOT_HEAP 4096 69 | 70 | // Battery voltage resistance 71 | #define BAT_RES_VALUE_GND 20.0 72 | #define BAT_RES_VALUE_VCC 10.0 73 | 74 | // Uncomment to enable server ftp. 75 | //#define SERVER_FTP 76 | //#define SERVER_HTTPS 77 | #define HARDWARE_SERIAL 78 | 79 | #ifdef SERVER_FTP 80 | #include 81 | #endif 82 | 83 | #ifdef SERVER_HTTPS 84 | #include 85 | #endif 86 | 87 | // Uncomment to enable printing out nice debug messages. 88 | #define AURORA_SERVER_DEBUG 89 | 90 | // Define where debug output will be printed. 91 | #define DEBUG_PRINTER Serial1 92 | 93 | // Setup debug printing macros. 94 | #ifdef AURORA_SERVER_DEBUG 95 | #define DEBUG_PRINT(...) { DEBUG_PRINTER.print(__VA_ARGS__); } 96 | #define DEBUG_PRINTLN(...) { DEBUG_PRINTER.println(__VA_ARGS__); } 97 | #else 98 | #define DEBUG_PRINT(...) {} 99 | #define DEBUG_PRINTLN(...) {} 100 | #endif 101 | 102 | 103 | char hostname[] = "InverterCentraline"; 104 | 105 | // Interval get data 106 | #define REALTIME_INTERVAL 5 107 | 108 | #define DAILY_INTERVAL 10 109 | #define CUMULATIVE_INTERVAL 10 110 | #define CUMULATIVE_TOTAL_INTERVAL 10 111 | #define STATE_INTERVAL 10 112 | #define STATE_STORE_INTERVAL 10 113 | #define BATTERY_INTERVAL 20 114 | #define STATIC_DATA_INTERVAL 6 * 60 115 | 116 | // LED 117 | #ifdef HARDWARE_SERIAL 118 | #define ERROR_PIN D3 119 | #else 120 | #define ERROR_PIN D1 121 | #endif 122 | 123 | #define INVERTER_COMMUNICATION_CONTROL_PIN D0 124 | // Inverte inizialization 125 | #ifdef HARDWARE_SERIAL 126 | Aurora inverter = Aurora(2, &Serial, INVERTER_COMMUNICATION_CONTROL_PIN); 127 | #else 128 | Aurora inverter = Aurora(2, D2, D3, INVERTER_COMMUNICATION_CONTROL_PIN); 129 | #endif 130 | 131 | void manageStaticDataCallback (); 132 | void leggiProduzioneCallback(); 133 | void realtimeDataCallbak(); 134 | void leggiStatoInverterCallback(); 135 | void leggiStatoBatteriaCallback(); 136 | void updateLocalTimeWithNTPCallback(); 137 | float getBatteryVoltage(); 138 | Timezone getTimezoneData(const String code); 139 | time_t getLocalTime(void); 140 | 141 | bool isFileSaveOK = true; 142 | bool saveJSonToAFile(DynamicJsonDocument *doc, String filename); 143 | JsonObject getJSonFromFile(DynamicJsonDocument *doc, String filename, bool forceCleanONJsonError = true); 144 | 145 | void errorLed(bool flag); 146 | 147 | Thread RealtimeData = Thread(); 148 | 149 | Thread ManageStaticData = Thread(); 150 | 151 | Thread LeggiStatoInverter = Thread(); 152 | Thread LeggiStatoBatteria = Thread(); 153 | 154 | Thread LeggiProduzione = Thread(); 155 | 156 | #define HTTP_REST_PORT 8080 157 | ESP8266WebServer httpRestServer(HTTP_REST_PORT); 158 | 159 | #ifdef SERVER_HTTPS 160 | static const char serverCert[] PROGMEM = R"EOF( 161 | -----BEGIN CERTIFICATE----- 162 | MIIDDjCCAfagAwIBAgIQNqQC531UdrtOiCvz8JHQTDANBgkqhkiG9w0BAQsFADAU 163 | MRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwOTMwMjAwMDQ1WhcNMzgwOTMwMjAx 164 | MDQ0WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IB 165 | DwAwggEKAoIBAQCrY932OMWnQynPBqtOOm/4dR6aQaSXdeLAUcRjlDy+AEl8C0Xe 166 | sBbwi1dpBatqyFpnYnoWkfduLOCJM4x8J1aoufMHO/XkNP19b8tViFpsmEiwtQnQ 167 | uSKQy8tGtlehYHBEqSqv1E5OkgTAlgYtAEJcY1Ih8DG1vWhMJ73CJcR5fkSQ1OMR 168 | 6AUaJORxLyKk0pgmKHmXeue2jP9aiuf1gHJQE9BcIRLpX9jElEA9rTvH5f9yLieg 169 | gP8r1x6BmB/gsyIQ0mnft6aJpyEaJ2XLL4ggnqpHOjrVUZ5AKzk2GQrnQlaZnnZ2 170 | xiiY2R9rp6U6lDWivPgOpaQUZ1c8jGvaV/2hAgMBAAGjXDBaMA4GA1UdDwEB/wQE 171 | AwIFoDAUBgNVHREEDTALgglsb2NhbGhvc3QwEwYDVR0lBAwwCgYIKwYBBQUHAwEw 172 | HQYDVR0OBBYEFOtRfncRTORMIF/wndrep6DmLsZUMA0GCSqGSIb3DQEBCwUAA4IB 173 | AQABwfZ54kRSMGSHXQkIkSJaI8DRByMCro+WCjZnWawfaqJOpihbVC5riZ1oQuyD 174 | gNe8sMoqI2R/xKthUaYsrXb50yVlZ2QLiW9MzOHnd6DkivDNGVBMsu0pKG7nLEiZ 175 | 1wnoMxzIrkSYakHLBPhmMMeCl7ahRC29xRMlup46okxcH5xTirM27TUl3Oy8mZcY 176 | UpT+QiqXdcWVFAsTKZfJRzCa1+Su480clKeUuFTYSnuyQ1CNCcnoj3RtR7p8TAzk 177 | Ht6qiI8BQ8kcPiRcYCdWK5g2SBs5QpaAB3/S3IxN8CAu5+CiTkPjmdcCijUJ1VoL 178 | /VxlMAt54NKKnN1MfCgVh4Gb 179 | -----END CERTIFICATE----- 180 | )EOF"; 181 | 182 | static const char serverKey[] PROGMEM = R"EOF( 183 | -----BEGIN RSA PRIVATE KEY----- 184 | MIIEowIBAAKCAQEAq2Pd9jjFp0MpzwarTjpv+HUemkGkl3XiwFHEY5Q8vgBJfAtF 185 | 3rAW8ItXaQWrashaZ2J6FpH3bizgiTOMfCdWqLnzBzv15DT9fW/LVYhabJhIsLUJ 186 | 0LkikMvLRrZXoWBwRKkqr9ROTpIEwJYGLQBCXGNSIfAxtb1oTCe9wiXEeX5EkNTj 187 | EegFGiTkcS8ipNKYJih5l3rntoz/Worn9YByUBPQXCES6V/YxJRAPa07x+X/ci4n 188 | oID/K9cegZgf4LMiENJp37emiachGidlyy+IIJ6qRzo61VGeQCs5NhkK50JWmZ52 189 | dsYomNkfa6elOpQ1orz4DqWkFGdXPIxr2lf9oQIDAQABAoIBAB/Y9NvV/NRx5Ij1 190 | wktNDJVsnf0oCX+jhjkaeJXQa+EaiI0mQxt4OSsFmX6IcSvsgvAHGoyrHwE4EZkt 191 | HQPNA4ti0kgb2jtHpXrzlSMVrUfUnF1JpsNEQ6oIVIOVSn9QPkxj6uy1VL/A3mUy 192 | +37NN4eXZSGtUm9k/MZ59AbpobK5eAXmCxvgss4ZPDW3QGnXqVT8bu6RXaF7Ph64 193 | KMUajkrGEyU9ff8MRzykb3kBit0nchOBvtETRg39L2MtXpnJoLzElZPr5ZbcwHk2 194 | Xp4iuL8D6hoiA0r0jAeYNYZkTW96u4u0eysyUV4xbVuOmozFHCiE/zOa0uW7xjrp 195 | hzPiE2kCgYEA1CnXyUCWiaIN29T//PfuZRiAHFWxG3RaiR7VprMZGsebx7aEVRsP 196 | EPgw0PILAOvNQIWwJWClCoKIWSf0grgDp+QhKAtyk5AaEspr7fXCXf8IK6xh+tDz 197 | OxlRbvQlYwWOFsXLZ3cu1vPuIAUEL4ez/EKeYQ1GC/W86cRBcM6r0mMCgYEAzs1c 198 | r34Tv7Asrec+BxZKrPbq1gaLG4ivVoiZTpo+6MOIGI236tKZIWouy2GklknoE36N 199 | rxz2KrPAFwg6V8N7q1CG/yNn5WJbasPLnb7ygauZ6yiyC4bqaXSKjrZJ2P4UYVOX 200 | t66K/Qr7Ud5gvDfStXPFdun0dUEgfDZqovpS7SsCgYBi5yqft8s1V+UsAIxhCdcJ 201 | K7W0/8FzMfduioBAmKbwU/Lr08q2vcl1OK3RCbRVdpcVJ/0oP3hQgO882KJkOZIC 202 | txc5yrRb08ZD0jckE/fKx7OwYEjAmp14hGHw3kF7esB1Hzml/upH7Ciqpov/+DvQ 203 | MeIRDhYER0cMlp+HDeENTwKBgEXSYlvCForewYcJjxC3fwj86PbQCMGIGaL+xbwb 204 | KehOtDGOD62R4y+7+Qaj9fzkAR4r2UxpW9e5Dr74ATLGhoelzZ5w5tA0sCbQ6ntd 205 | D+Wl+XbDK7HmoFhwh6N9eltwFZNytMPIg5bB0W6nxUNnGZY3+1CV1vqLvZsSiFh0 206 | afE3AoGBAMDHbH2YCshMkDJ5RqZs3gVFQX5jXbUh1+j8/bJrWyW49TErGqWOFKxy 207 | 0hJbrLFftgnNuABWQw2Ve11rdboXGqIGhLNksRDFPHOYhgrzvw41DCllI7d3n5ij 208 | sYyW+iXYsiV5NCrE9KvlKkjxA5FJMdb8qiq5uZ03PxxtNTyVhfy+ 209 | -----END RSA PRIVATE KEY----- 210 | )EOF"; 211 | 212 | #define SECURE_HTTP_PORT 443 213 | BearSSL::ESP8266WebServerSecure httpServer(SECURE_HTTP_PORT); 214 | #else 215 | #define HTTP_PORT 80 216 | ESP8266WebServer httpServer(HTTP_PORT); 217 | #endif 218 | 219 | void restServerRouting(); 220 | void serverRouting(); 221 | 222 | WiFiUDP ntpUDP; 223 | // By default 'pool.ntp.org' is used with 60 seconds update interval and 224 | // no offset 225 | NTPClient timeClient(ntpUDP, "europe.pool.ntp.org", 0*60*60, 60*60*1000); 226 | 227 | Thread UpdateLocalTimeWithNTP = Thread(); 228 | 229 | bool fixedTime = false; 230 | bool sdStarted = false; 231 | bool wifiConnected = false; 232 | 233 | float setPrecision(float val, byte precision); 234 | 235 | #define DSP_GRID_POWER_ALL_FILENAME F("power.jso") /* Global */ 236 | #define DSP_GRID_CURRENT_ALL_FILENAME F("current.jso") 237 | #define DSP_GRID_VOLTAGE_ALL_FILENAME F("voltage.jso") 238 | 239 | #define BATTERY_RT F("bat_rt") /* Global */ 240 | #define WIFI_SIGNAL_STRENGHT_RT F("wifi_rt") /* Global */ 241 | #define DSP_GRID_POWER_ALL_TYPE_RT F("power_rt") /* Global */ 242 | 243 | #define DSP_GRID_POWER_ALL_TYPE F("power") /* Global */ 244 | #define DSP_GRID_CURRENT_ALL_TYPE F("current") 245 | #define DSP_GRID_VOLTAGE_ALL_TYPE F("voltage") 246 | 247 | #define CUMULATED_ENERGY_TYPE F("cumulated") 248 | #define ERROR_TYPE F("error") 249 | #define ERROR_INVERTER_TYPE F("error_inverter") 250 | 251 | #ifdef SEND_EMAIL 252 | EMailSender emailSend("", ""); 253 | #endif 254 | 255 | #ifdef SERVER_FTP 256 | FtpServer ftpSrv; //set #define FTP_DEBUG in ESP8266FtpServer.h to see ftp verbose on serial 257 | #endif 258 | 259 | int timeOffset = 0; 260 | 261 | String codeDST = "GTM"; 262 | 263 | void setup() { 264 | pinMode(A0, INPUT); 265 | 266 | pinMode(ERROR_PIN, OUTPUT); 267 | digitalWrite(ERROR_PIN, HIGH); 268 | 269 | #ifdef AURORA_SERVER_DEBUG 270 | // Inizilization of serial debug 271 | Serial1.begin(19200); 272 | Serial1.setTimeout(500); 273 | // Wait to finish inizialization 274 | delay(600); 275 | #endif 276 | 277 | DEBUG_PRINT(F("Inizializing FS...")); 278 | if (SPIFFS.begin()){ 279 | DEBUG_PRINTLN(F("done.")); 280 | }else{ 281 | DEBUG_PRINTLN(F("fail.")); 282 | } 283 | //WiFiManager 284 | //Local intialization. Once its business is done, there is no need to keep it around 285 | WiFiManager wifiManager; 286 | // wifiManager.setConfigPortalTimeout(10); 287 | WiFi.hostname(hostname); 288 | wifi_station_set_hostname(hostname); 289 | 290 | DEBUG_PRINT(F("Open config file...")); 291 | fs::File configFile = SPIFFS.open(F("/mc/config.txt"), "r"); 292 | if (configFile) { 293 | // while (configFile.available()) 294 | // { 295 | // Serial1.write(configFile.read()); 296 | // } 297 | // 298 | DEBUG_PRINTLN(F("done.")); 299 | DynamicJsonDocument doc(CONFIG_FILE_HEAP); 300 | ArduinoJson::DeserializationError error = deserializeJson(doc, configFile); 301 | // close the file: 302 | configFile.close(); 303 | 304 | if (error){ 305 | // if the file didn't open, print an error: 306 | DEBUG_PRINT(F("Error parsing JSON ")); 307 | DEBUG_PRINTLN(error.c_str()); 308 | 309 | }else{ 310 | JsonObject rootObj = doc.as(); 311 | JsonObject preferences = rootObj[F("preferences")]; 312 | bool isGTM = preferences.containsKey(F("GTM")); 313 | if (isGTM){ 314 | JsonObject GTM = preferences[F("GTM")]; 315 | bool isValue = GTM.containsKey(F("value")); 316 | if (isValue){ 317 | int value = GTM[F("value")]; 318 | 319 | DEBUG_PRINT(F("Impostazione GTM+")) 320 | DEBUG_PRINTLN(value) 321 | 322 | // timeClient.setTimeOffset(value*60*60); 323 | timeOffset = value*60*60; 324 | } 325 | } 326 | 327 | bool isDST = preferences.containsKey(F("DST")); 328 | if (isDST){ 329 | JsonObject DST = preferences[F("DST")]; 330 | bool isCode = DST.containsKey(F("code")); 331 | if (isCode){ 332 | const String code = DST[F("code")]; 333 | // const String desc = DST[F("description")]; 334 | 335 | codeDST = code; 336 | DEBUG_PRINT(F("Impostazione DST ")) 337 | DEBUG_PRINTLN(code) 338 | // DEBUG_PRINT(F("Description DST ")) 339 | // DEBUG_PRINTLN(desc) 340 | 341 | // timeClient.setTimeOffset(value*60*60); 342 | // timeOffset = value*60*60; 343 | } 344 | }else{ 345 | codeDST = "GTM"; 346 | } 347 | 348 | JsonObject serverConfig = rootObj[F("server")]; 349 | bool isStatic = serverConfig[F("isStatic")]; 350 | if (isStatic==true){ 351 | const char* address = serverConfig[F("address")]; 352 | const char* gatway = serverConfig[F("gatway")]; 353 | const char* netMask = serverConfig[F("netMask")]; 354 | 355 | const char* dns1 = serverConfig[F("dns1")]; 356 | const char* dns2 = serverConfig[F("dns2")]; 357 | 358 | const char* _hostname = serverConfig[F("hostname")]; 359 | //start-block2 360 | IPAddress _ip; 361 | bool parseSuccess; 362 | parseSuccess = _ip.fromString(address); 363 | if (parseSuccess) { 364 | DEBUG_PRINTLN(F("Address correctly parsed!")); 365 | } 366 | 367 | IPAddress _gw; 368 | parseSuccess = _gw.fromString(gatway); 369 | if (parseSuccess) { 370 | DEBUG_PRINTLN(F("Gatway correctly parsed!")); 371 | } 372 | 373 | IPAddress _sn; 374 | parseSuccess = _sn.fromString(netMask); 375 | if (parseSuccess) { 376 | DEBUG_PRINTLN(F("Subnet correctly parsed!")); 377 | } 378 | 379 | IPAddress _dns1; 380 | IPAddress _dns2; 381 | bool isDNS = false; 382 | if (dns1 && sizeof(_dns1) > 7 && dns2 && sizeof(_dns2) > 7 ){ 383 | parseSuccess = _dns1.fromString(dns1); 384 | if (parseSuccess) { 385 | DEBUG_PRINTLN(F("DNS 1 correctly parsed!")); 386 | isDNS = true; 387 | } 388 | 389 | parseSuccess = _dns2.fromString(dns2); 390 | if (parseSuccess) { 391 | DEBUG_PRINTLN(F("DNS 2 correctly parsed!")); 392 | } 393 | //end-block2 394 | } 395 | 396 | DEBUG_PRINT(F("Set static data...")); 397 | if (isDNS){ 398 | wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn, _dns1, _dns2); 399 | }else{ 400 | wifiManager.setSTAStaticIPConfig(_ip, _gw, _sn); 401 | } 402 | 403 | if (_hostname && sizeof(_hostname)>1){ 404 | strcpy(hostname, _hostname); 405 | } 406 | // IPAddress(85, 37, 17, 12), IPAddress(8, 8, 8, 8) 407 | // 408 | // emailSend.setEMailLogin("smtp.mischianti@gmail.com"); 409 | DEBUG_PRINTLN(F("done.")); 410 | } 411 | } 412 | }else{ 413 | DEBUG_PRINTLN(F("fail.")); 414 | } 415 | 416 | //reset saved settings 417 | // wifiManager.resetSettings(); 418 | 419 | 420 | //set custom ip for portal 421 | //wifiManager.setAPStaticIPConfig(IPAddress(10,0,1,1), IPAddress(10,0,1,1), IPAddress(255,255,255,0)); 422 | 423 | //fetches ssid and pass from eeprom and tries to connect 424 | //if it does not connect it starts an access point with the specified name 425 | //here "AutoConnectAP" 426 | //and goes into a blocking loop awaiting configuration 427 | wifiManager.autoConnect("InverterCentralineConfiguration"); 428 | //or use this for auto generated name ESP + ChipID 429 | //wifiManager.autoConnect(); 430 | 431 | 432 | //if you get here you have connected to the WiFi 433 | DEBUG_PRINTLN(F("WIFIManager connected!")); 434 | 435 | DEBUG_PRINT(F("IP --> ")); 436 | DEBUG_PRINTLN(WiFi.localIP()); 437 | DEBUG_PRINT(F("GW --> ")); 438 | DEBUG_PRINTLN(WiFi.gatewayIP()); 439 | DEBUG_PRINT(F("SM --> ")); 440 | DEBUG_PRINTLN(WiFi.subnetMask()); 441 | 442 | DEBUG_PRINT(F("DNS 1 --> ")); 443 | DEBUG_PRINTLN(WiFi.dnsIP(0)); 444 | 445 | DEBUG_PRINT(F("DNS 2 --> ")); 446 | DEBUG_PRINTLN(WiFi.dnsIP(1)); 447 | 448 | 449 | #ifndef WRITE_SETTINGS_IF_EXIST 450 | if (!SPIFFS.exists(F("/settings.json"))){ 451 | #endif 452 | DEBUG_PRINT(F("Recreate settings file...")); 453 | fs::File settingsFile = SPIFFS.open(F("/settings.json"), "w"); 454 | if (!settingsFile) { 455 | DEBUG_PRINTLN(F("fail.")); 456 | }else{ 457 | 458 | DEBUG_PRINTLN(F("done.")); 459 | DynamicJsonDocument doc(2048); 460 | JsonObject postObj = doc.to(); 461 | // postObj[F("localIP")] = WiFi.localIP().toString(); 462 | postObj[F("localRestPort")] = HTTP_REST_PORT; 463 | postObj[F("localWSPort")] = WS_PORT; 464 | 465 | String buf; 466 | serializeJson(postObj, buf); 467 | 468 | settingsFile.print(F("var settings = ")); 469 | settingsFile.print(buf); 470 | settingsFile.print(";"); 471 | 472 | settingsFile.close(); 473 | 474 | // serializeJson(doc, settingsFile); 475 | DEBUG_PRINTLN(F("success.")); 476 | } 477 | 478 | #ifndef WRITE_SETTINGS_IF_EXIST 479 | } 480 | #endif 481 | 482 | // Start inverter serial 483 | DEBUG_PRINT(F("Initializing Inverter serial...")); 484 | inverter.begin(); 485 | DEBUG_PRINTLN(F("initialization done.")); 486 | 487 | // Start inizialization of SD cart 488 | DEBUG_PRINT(F("Initializing SD card...")); 489 | 490 | // Initialize SD library 491 | while (!SD.begin(CS_PIN)) { 492 | Serial.println(F("Failed to initialize SD library")); 493 | delay(1000); 494 | sdStarted = false; 495 | } 496 | sdStarted = true; 497 | 498 | // if (!SD.begin(CS_PIN, SPI_HALF_SPEED)) { 499 | // DEBUG_PRINTLN(F("initialization failed!")); 500 | // sdStarted = false; 501 | // // return to stop all 502 | // return; 503 | // }else{ 504 | // sdStarted = true; 505 | // 506 | // } 507 | DEBUG_PRINTLN(F("Inizialization done.")); 508 | 509 | ManageStaticData.onRun(manageStaticDataCallback); 510 | ManageStaticData.setInterval(STATIC_DATA_INTERVAL * 60 * 1000); 511 | 512 | // Start thread, and set it every 1 minutes 513 | LeggiStatoInverter.onRun(leggiStatoInverterCallback); 514 | LeggiStatoInverter.setInterval(STATE_INTERVAL * 60 * 1000); 515 | 516 | // Start thread, and set it every 60 517 | LeggiStatoBatteria.onRun(leggiStatoBatteriaCallback); 518 | LeggiStatoBatteria.setInterval(BATTERY_INTERVAL * 60 * 1000); 519 | 520 | // Start thread, and set it every 1 minutes 521 | LeggiProduzione.onRun(leggiProduzioneCallback); 522 | LeggiProduzione.setInterval(60 * 1000); 523 | 524 | RealtimeData.onRun(realtimeDataCallbak); 525 | RealtimeData.setInterval(1000 * REALTIME_INTERVAL); 526 | 527 | int timeToRetry = 10; 528 | while ( WiFi.status() != WL_CONNECTED && timeToRetry>0 ) { 529 | delay ( 500 ); 530 | DEBUG_PRINT ( "." ); 531 | timeToRetry--; 532 | } 533 | wifiConnected = true; 534 | 535 | //#ifndef SERVER_FTP 536 | 537 | timeClient.begin(); 538 | updateLocalTimeWithNTPCallback(); 539 | 540 | UpdateLocalTimeWithNTP.onRun(updateLocalTimeWithNTPCallback); 541 | UpdateLocalTimeWithNTP.setInterval(60*60 * 1000); 542 | 543 | restServerRouting(); 544 | httpRestServer.begin(); 545 | DEBUG_PRINTLN(F("HTTP REST Server Started")); 546 | 547 | #ifdef SERVER_HTTPS 548 | httpServer.setRSACert(new BearSSLX509List(serverCert), new BearSSLPrivateKey(serverKey)); 549 | #endif 550 | serverRouting(); 551 | httpServer.begin(); 552 | #ifdef SERVER_HTTPS 553 | DEBUG_PRINTLN(F("HTTPS Server Started")); 554 | #else 555 | DEBUG_PRINTLN(F("HTTP Server Started")); 556 | #endif 557 | 558 | #ifdef SERVER_FTP 559 | // SPIFFS.format(); 560 | ftpSrv.begin(F("aurora"),F("aurora")); //username, password for ftp. set ports in ESP8266FtpServer.h (default 21, 50009 for PASV) 561 | DEBUG_PRINTLN(F("FTP Server Started")); 562 | #endif 563 | 564 | if (!MDNS.begin(hostname)) { // Start the mDNS responder for esp8266.local 565 | DEBUG_PRINTLN(F("Error setting up mDNS responder!")); 566 | } 567 | DEBUG_PRINT(hostname); 568 | DEBUG_PRINTLN(F(" --> mDNS responder started")); 569 | 570 | DEBUG_PRINT(F("ERROR --> ")); 571 | DEBUG_PRINTLN(!(fixedTime && sdStarted&& wifiConnected)); 572 | 573 | #ifdef WS_ACTIVE 574 | webSocket.begin(); 575 | webSocket.onEvent(webSocketEvent); 576 | DEBUG_PRINTLN(F("WS Server Started")); 577 | #endif 578 | 579 | errorLed(!(fixedTime && sdStarted&& wifiConnected && isFileSaveOK)); 580 | 581 | // digitalWrite(ERROR_PIN, !(fixedTime && sdStarted&& wifiConnected && isFileSaveOK)); 582 | 583 | if (fixedTime && sdStarted){ 584 | DEBUG_PRINTLN(F("FIRST LOAD...")) 585 | leggiStatoInverterCallback(); 586 | leggiStatoBatteriaCallback(); 587 | manageStaticDataCallback(); 588 | leggiProduzioneCallback(); 589 | } 590 | } 591 | 592 | void loop() { 593 | //#ifndef SERVER_FTP 594 | // Activate thread 595 | if (fixedTime && LeggiProduzione.shouldRun()) { 596 | LeggiProduzione.run(); 597 | } 598 | 599 | if (fixedTime && RealtimeData.shouldRun()) { 600 | RealtimeData.run(); 601 | } 602 | 603 | if (fixedTime && sdStarted && LeggiStatoInverter.shouldRun()) { 604 | LeggiStatoInverter.run(); 605 | } 606 | 607 | if (fixedTime && sdStarted && LeggiStatoBatteria.shouldRun()) { 608 | LeggiStatoBatteria.run(); 609 | } 610 | 611 | if (fixedTime && sdStarted && ManageStaticData.shouldRun()) { 612 | ManageStaticData.run(); 613 | } 614 | if (UpdateLocalTimeWithNTP.shouldRun()) { 615 | UpdateLocalTimeWithNTP.run(); 616 | } 617 | 618 | #ifdef WS_ACTIVE 619 | webSocket.loop(); 620 | #endif 621 | 622 | httpRestServer.handleClient(); 623 | httpServer.handleClient(); 624 | 625 | timeClient.update(); 626 | //#endif 627 | 628 | #ifdef SERVER_FTP 629 | ftpSrv.handleFTP(); //make sure in loop you call handleFTP()!! 630 | #endif 631 | 632 | } 633 | 634 | // We can read/write a file at time in the SD card 635 | File myFileSDCart; // @suppress("Ambiguous problem") 636 | 637 | void leggiStatoBatteriaCallback() { 638 | DEBUG_PRINT(F("Thread call (leggiStatoBatteriaCallback) --> ")); 639 | DEBUG_PRINT(getEpochStringByParams(getLocalTime())); 640 | DEBUG_PRINT(F(" MEM --> ")); 641 | DEBUG_PRINTLN(ESP.getFreeHeap()) 642 | 643 | float bv = getBatteryVoltage(); 644 | DEBUG_PRINT(F(" BATTERY --> ")); 645 | DEBUG_PRINTLN(bv); 646 | DEBUG_PRINTLN(bv>2); 647 | DEBUG_PRINTLN(bv>1); 648 | if (bv>1) { 649 | String scopeDirectory = F("battery"); 650 | if (!SD.exists(scopeDirectory)) { 651 | SD.mkdir(scopeDirectory); 652 | } 653 | String filename = scopeDirectory +"/"+ getEpochStringByParams(getLocalTime(), (char*) "%Y%m%d") + F(".jso"); 654 | 655 | DynamicJsonDocument doc(BATTERY_STATE_HEAP); 656 | JsonObject obj; 657 | 658 | obj = getJSonFromFile(&doc, filename); 659 | 660 | obj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 661 | 662 | JsonObject data; 663 | if (!obj.containsKey(F("data"))) { 664 | data = obj.createNestedObject(F("data")); 665 | } else { 666 | data = obj[F("data")]; 667 | } 668 | 669 | data[getEpochStringByParams(getLocalTime(),(char*) "%H%M")]=bv; 670 | 671 | DEBUG_PRINTLN(F("done.")); 672 | 673 | isFileSaveOK = saveJSonToAFile(&doc, filename); 674 | 675 | } 676 | 677 | } 678 | int lastAlarm = 0; 679 | 680 | bool saveInverterStats(String scopeDirectory, Aurora::DataState dataState){ 681 | if (!SD.exists(scopeDirectory)) { 682 | SD.mkdir(scopeDirectory); 683 | } 684 | 685 | String filename = scopeDirectory + F("/alarStat.jso"); 686 | 687 | DynamicJsonDocument doc(2048); 688 | JsonObject rootObj = doc.to(); 689 | 690 | rootObj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 691 | 692 | rootObj[F("alarmStateParam")] = dataState.alarmState; 693 | rootObj[F("alarmState")] = dataState.getAlarmState(); 694 | 695 | rootObj[F("channel1StateParam")] = dataState.channel1State; 696 | rootObj[F("channel1State")] = dataState.getDcDcChannel1State(); 697 | 698 | rootObj[F("channel2StateParam")] = dataState.channel2State; 699 | rootObj[F("channel2State")] = dataState.getDcDcChannel2State(); 700 | 701 | rootObj[F("inverterStateParam")] = dataState.inverterState; 702 | rootObj[F("inverterState")] = dataState.getInverterState(); 703 | 704 | DEBUG_PRINTLN(F("done.")); 705 | 706 | isFileSaveOK = saveJSonToAFile(&doc, filename); 707 | 708 | return isFileSaveOK; 709 | } 710 | 711 | struct LastDataState { 712 | byte asp; 713 | byte c1sp; 714 | byte c2sp; 715 | byte isp; 716 | 717 | bool variationFromPrevious = false; 718 | bool inverterProblem = false; 719 | bool firstElement = true; 720 | bool needNotify = false; 721 | }; 722 | 723 | LastDataState manageLastDataState(String scopeDirectory, Aurora::DataState dataState){ 724 | LastDataState lds; 725 | 726 | String dayDirectory = getEpochStringByParams(getLocalTime(), (char*) "%Y%m%d"); 727 | 728 | String filenameAL = scopeDirectory + '/' + dayDirectory + F("/alarms.jso"); 729 | 730 | DynamicJsonDocument docAS(ALARM_IN_A_DAY); 731 | JsonObject obj; 732 | 733 | obj = getJSonFromFile(&docAS, filenameAL); 734 | 735 | obj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 736 | 737 | JsonArray data; 738 | if (!obj.containsKey(F("data"))) { 739 | data = obj.createNestedArray(F("data")); 740 | } else { 741 | data = obj[F("data")]; 742 | } 743 | 744 | bool inverterProblem = dataState.alarmState > 0 || (dataState.inverterState != 2 && dataState.inverterState != 1); 745 | 746 | lds.inverterProblem = inverterProblem; 747 | 748 | bool firstElement = data.size() == 0; 749 | 750 | lds.firstElement = firstElement; 751 | 752 | if (inverterProblem || !firstElement) { 753 | JsonObject lastData; 754 | if (data.size() > 0) { 755 | lastData = data[data.size() - 1]; 756 | 757 | lds.asp = lastData[F("asp")]; 758 | lds.c1sp = lastData[F("c1sp")]; 759 | lds.c2sp = lastData[F("c2sp")]; 760 | lds.isp = lastData[F("isp")]; 761 | 762 | } 763 | 764 | bool variationFromPrevious = (data.size() > 0 765 | && (lastData[F("asp")] != dataState.alarmState 766 | || lastData[F("c1sp")] != dataState.channel1State 767 | || lastData[F("c2sp")] != dataState.channel2State 768 | || lastData[F("isp")] != dataState.inverterState)); 769 | 770 | lds.variationFromPrevious = variationFromPrevious; 771 | 772 | byte ldState = lastData[F("isp")]; 773 | 774 | DEBUG_PRINT(F("Last data state --> ")); 775 | DEBUG_PRINTLN(ldState); 776 | 777 | DEBUG_PRINT(F("Data state --> ")); 778 | DEBUG_PRINTLN(dataState.inverterState); 779 | 780 | DEBUG_PRINT(F("Last data vs data state is different --> ")); 781 | DEBUG_PRINTLN(lastData[F("isp")] != dataState.inverterState); 782 | 783 | 784 | DEBUG_PRINT(F("Inverter problem --> ")); 785 | DEBUG_PRINTLN(inverterProblem); 786 | 787 | DEBUG_PRINT(F("firstElement --> ")); 788 | DEBUG_PRINTLN(firstElement); 789 | 790 | DEBUG_PRINT(F("Variation From Previous --> ")); 791 | DEBUG_PRINTLN(variationFromPrevious); 792 | 793 | lds.needNotify = false; 794 | 795 | if ((inverterProblem && firstElement) 796 | || (!firstElement && variationFromPrevious)) { 797 | 798 | lds.needNotify = true; 799 | 800 | if (docAS.memoryUsage()>(ALARM_IN_A_DAY-1024)){ 801 | DEBUG_PRINTLN(F("Too much data in a day, removed 4 elements")); 802 | data.remove(0); 803 | data.remove(1); 804 | data.remove(2); 805 | data.remove(3); 806 | // for (int i = 0; i<4; i++){ 807 | // data.remove[(const char*)i]; 808 | // } 809 | } 810 | 811 | JsonObject objArrayData = data.createNestedObject(); 812 | 813 | objArrayData[F("h")] = getEpochStringByParams(getLocalTime(), 814 | (char*) "%H%M"); 815 | 816 | objArrayData[F("asp")] = dataState.alarmState; 817 | // objArrayData[F("as")] = dataState.getAlarmState(); 818 | 819 | objArrayData[F("c1sp")] = dataState.channel1State; 820 | // objArrayData[F("c1s")] = dataState.getDcDcChannel1State(); 821 | 822 | objArrayData[F("c2sp")] = dataState.channel2State; 823 | // objArrayData[F("c2s")] = dataState.getDcDcChannel2State(); 824 | 825 | objArrayData[F("isp")] = dataState.inverterState; 826 | // objArrayData["is"] = dataState.getInverterState(); 827 | 828 | DEBUG_PRINTLN(F("Store alarms --> ")); 829 | // serializeJson(doc, Serial); 830 | DEBUG_PRINT(docAS.memoryUsage()); 831 | DEBUG_PRINTLN(); 832 | 833 | if (!SD.exists(scopeDirectory + '/' + dayDirectory)) { 834 | SD.mkdir(scopeDirectory + '/' + dayDirectory); 835 | } 836 | 837 | isFileSaveOK = saveJSonToAFile(&docAS, filenameAL); 838 | } 839 | } 840 | return lds; 841 | } 842 | 843 | #ifdef WS_ACTIVE 844 | void sendWSMessageAlarm(Aurora::DataState dataState, bool inverterProblem, bool variationFromPrevious){ 845 | if (inverterProblem || variationFromPrevious){ 846 | DEBUG_PRINT(F(" MEM Condition --> ")); 847 | DEBUG_PRINTLN(ESP.getFreeHeap()) 848 | 849 | DynamicJsonDocument docws(512); 850 | JsonObject objws = docws.to(); 851 | String dateFormatted = getEpochStringByParams(getLocalTime()); 852 | objws[F("type")] = ERROR_INVERTER_TYPE; 853 | objws[F("date")] = dateFormatted; 854 | 855 | JsonObject objValue = objws.createNestedObject(F("value")); 856 | 857 | objValue[F("inverterProblem")] = inverterProblem; 858 | 859 | objValue[F("asp")] = dataState.alarmState; 860 | objValue[F("c1sp")] = dataState.channel1State; 861 | objValue[F("c2sp")] = dataState.channel2State; 862 | objValue[F("isp")] = dataState.inverterState; 863 | 864 | objValue[F("alarm")] = dataState.getAlarmState(); 865 | objValue[F("ch1state")] = dataState.getDcDcChannel1State(); 866 | objValue[F("ch2state")] = dataState.getDcDcChannel2State(); 867 | objValue[F("state")] = dataState.getInverterState(); 868 | 869 | String buf; 870 | serializeJson(objws, buf); 871 | 872 | webSocket.broadcastTXT(buf); 873 | } 874 | } 875 | #endif 876 | 877 | void leggiStatoInverterCallback() { 878 | DEBUG_PRINT(F("Thread call (LeggiStatoInverterCallback) --> ")); 879 | DEBUG_PRINT(getEpochStringByParams(getLocalTime())); 880 | DEBUG_PRINT(F(" MEM --> ")); 881 | DEBUG_PRINTLN(ESP.getFreeHeap()) 882 | 883 | Aurora::DataState dataState = inverter.readState(); 884 | #ifdef FORCE_SEND_ERROR_MESSAGE 885 | if (lastAlarm==0){ 886 | dataState.alarmState = 2; 887 | lastAlarm = 2; 888 | }else{ 889 | dataState.alarmState = 0; 890 | lastAlarm = 0; 891 | } 892 | dataState.state.readState = 1; 893 | #endif 894 | 895 | DEBUG_PRINT(F("Read state --> ")); 896 | DEBUG_PRINTLN(dataState.state.readState); 897 | 898 | if (dataState.state.readState == 1) { 899 | DEBUG_PRINTLN(F("done.")); 900 | 901 | DEBUG_PRINT(F("Create json...")); 902 | 903 | String scopeDirectory = F("alarms"); 904 | 905 | saveInverterStats(scopeDirectory, dataState); 906 | 907 | LastDataState lds = manageLastDataState(scopeDirectory, dataState); 908 | 909 | sendWSMessageAlarm(dataState, lds.inverterProblem, lds.variationFromPrevious); 910 | #ifdef SEND_EMAIL 911 | 912 | DEBUG_PRINT(F("MEM ")); 913 | DEBUG_PRINTLN(ESP.getFreeHeap()); 914 | 915 | if (lds.needNotify){ 916 | DEBUG_PRINT(F("Open config file...")); 917 | fs::File configFile = SPIFFS.open(F("/mc/config.txt"), "r"); 918 | if (configFile) { 919 | DEBUG_PRINTLN(F("done.")); 920 | DynamicJsonDocument doc(CONFIG_FILE_HEAP); 921 | DeserializationError error = deserializeJson(doc, configFile); 922 | if (error) { 923 | // if the file didn't open, print an error: 924 | DEBUG_PRINT(F("Error parsing JSON ")); 925 | DEBUG_PRINTLN(error.c_str()); 926 | } 927 | 928 | // close the file: 929 | configFile.close(); 930 | 931 | DEBUG_PRINT(F("MEM ")); 932 | DEBUG_PRINTLN(ESP.getFreeHeap()); 933 | 934 | JsonObject rootObj = doc.as(); 935 | 936 | DEBUG_PRINT(F("After read config check serverSMTP and emailNotification ")); 937 | DEBUG_PRINTLN(rootObj.containsKey(F("serverSMTP")) && rootObj.containsKey(F("emailNotification"))); 938 | 939 | if (rootObj.containsKey(F("serverSMTP")) && rootObj.containsKey(F("emailNotification"))){ 940 | // JsonObject serverConfig = rootObj["server"]; 941 | JsonObject serverSMTP = rootObj[F("serverSMTP")]; 942 | JsonObject emailNotification = rootObj[F("emailNotification")]; 943 | 944 | bool isNotificationEnabled = (emailNotification.containsKey(F("isNotificationEnabled")))?emailNotification[F("isNotificationEnabled")]:false; 945 | 946 | DEBUG_PRINT(F("isNotificationEnabled ")); 947 | DEBUG_PRINTLN(isNotificationEnabled); 948 | 949 | if (isNotificationEnabled){ 950 | const char* serverSMTPAddr = serverSMTP[F("server")]; 951 | emailSend.setSMTPServer(serverSMTPAddr); 952 | uint16_t portSMTP = serverSMTP[F("port")]; 953 | emailSend.setSMTPPort(portSMTP); 954 | const char* loginSMTP = serverSMTP[F("login")]; 955 | emailSend.setEMailLogin(loginSMTP); 956 | const char* passwordSMTP = serverSMTP[F("password")]; 957 | emailSend.setEMailPassword(passwordSMTP); 958 | const char* fromSMTP = serverSMTP[F("from")]; 959 | emailSend.setEMailFrom(fromSMTP); 960 | 961 | DEBUG_PRINT(F("server ")); 962 | DEBUG_PRINTLN(serverSMTPAddr); 963 | DEBUG_PRINT(F("port ")); 964 | DEBUG_PRINTLN(portSMTP); 965 | DEBUG_PRINT(F("login ")); 966 | DEBUG_PRINTLN(loginSMTP); 967 | DEBUG_PRINT(F("password ")); 968 | DEBUG_PRINTLN(passwordSMTP); 969 | DEBUG_PRINT(F("from ")); 970 | DEBUG_PRINTLN(fromSMTP); 971 | 972 | EMailSender::EMailMessage message; 973 | const String sub = emailNotification[F("subject")]; 974 | message.subject = sub; 975 | 976 | JsonArray emailList = emailNotification[F("emailList")]; 977 | 978 | DEBUG_PRINT(F("Email list ")); 979 | 980 | for (uint8_t i=0; i 0) 1017 | || 1018 | (ch1==F("on_problem") && dataState.channel1State != 2) 1019 | || 1020 | (ch2==F("on_problem") && dataState.channel1State != 2) 1021 | || 1022 | (state==F("on_problem") && (dataState.inverterState != 2 && dataState.inverterState != 1)) 1023 | ); 1024 | 1025 | DEBUG_PRINT(F("Check allNotification ")); 1026 | DEBUG_PRINTLN(allNotification); 1027 | 1028 | DEBUG_PRINT(F("Check onProblem ")); 1029 | DEBUG_PRINTLN(onProblem); 1030 | 1031 | DEBUG_PRINT(F("MEM ")); 1032 | DEBUG_PRINTLN(ESP.getFreeHeap()); 1033 | 1034 | 1035 | if ( 1036 | allNotification 1037 | || 1038 | onProblem 1039 | ){ 1040 | const String mp = emailNotification[F("messageProblem")]; 1041 | const String mnp = emailNotification[F("messageNoProblem")]; 1042 | message.message = ((lds.inverterProblem)?mp:mnp)+ 1043 | F("
Alarm: ")+dataState.getAlarmState()+ 1044 | F("
CH1: ")+dataState.getDcDcChannel1State() + 1045 | F("
CH2: ")+dataState.getDcDcChannel2State()+ 1046 | F("
Stato: ")+dataState.getInverterState(); 1047 | 1048 | const String em = emailElem[F("email")]; 1049 | EMailSender::Response resp = emailSend.send(em, message); 1050 | 1051 | DEBUG_PRINTLN(F("Sending status: ")); 1052 | 1053 | DEBUG_PRINTLN(em); 1054 | DEBUG_PRINTLN(resp.status); 1055 | DEBUG_PRINTLN(resp.code); 1056 | DEBUG_PRINTLN(resp.desc); 1057 | } 1058 | 1059 | 1060 | } 1061 | } 1062 | } 1063 | }else{ 1064 | DEBUG_PRINTLN(F("fail.")); 1065 | } 1066 | } 1067 | #endif 1068 | // } 1069 | // 1070 | // } 1071 | // } 1072 | } 1073 | 1074 | } 1075 | 1076 | void manageStaticDataCallback () { 1077 | DEBUG_PRINT(F("Thread call (manageStaticDataCallback) --> ")); 1078 | DEBUG_PRINT(getEpochStringByParams(getLocalTime())); 1079 | DEBUG_PRINT(F(" MEM --> ")); 1080 | DEBUG_PRINTLN(ESP.getFreeHeap()) 1081 | 1082 | DEBUG_PRINT(F("Data version read... ")); 1083 | Aurora::DataVersion dataVersion = inverter.readVersion(); 1084 | DEBUG_PRINTLN(dataVersion.state.readState); 1085 | 1086 | DEBUG_PRINT(F("Firmware release read... ")); 1087 | Aurora::DataFirmwareRelease firmwareRelease = inverter.readFirmwareRelease(); 1088 | DEBUG_PRINTLN(firmwareRelease.state.readState); 1089 | 1090 | DEBUG_PRINT(F("System SN read... ")); 1091 | Aurora::DataSystemSerialNumber systemSN = inverter.readSystemSerialNumber(); 1092 | DEBUG_PRINTLN(systemSN.readState); 1093 | 1094 | DEBUG_PRINT(F("Manufactoru Week Year read... ")); 1095 | Aurora::DataManufacturingWeekYear manufactoryWeekYear = inverter.readManufacturingWeekYear(); 1096 | DEBUG_PRINTLN(manufactoryWeekYear.state.readState); 1097 | 1098 | DEBUG_PRINT(F("systemPN read... ")); 1099 | Aurora::DataSystemPN systemPN = inverter.readSystemPN(); 1100 | DEBUG_PRINTLN(systemPN.readState); 1101 | 1102 | DEBUG_PRINT(F("configStatus read... ")); 1103 | Aurora::DataConfigStatus configStatus = inverter.readConfig(); 1104 | DEBUG_PRINTLN(configStatus.state.readState); 1105 | 1106 | if (dataVersion.state.readState == 1){ 1107 | DEBUG_PRINTLN(F("done.")); 1108 | 1109 | DEBUG_PRINT(F("Create json...")); 1110 | 1111 | String scopeDirectory = F("static"); 1112 | if (!SD.exists(scopeDirectory)){ 1113 | SD.mkdir(scopeDirectory); 1114 | } 1115 | 1116 | String filename = scopeDirectory+ F("/invinfo.jso"); 1117 | 1118 | DynamicJsonDocument doc(INVERTER_INFO_HEAP); 1119 | JsonObject rootObj = doc.to(); 1120 | 1121 | rootObj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 1122 | 1123 | rootObj[F("modelNameParam")] = dataVersion.par1; 1124 | rootObj[F("modelName")] = dataVersion.getModelName().name; 1125 | rootObj[F("modelNameIndoorOutdoorType")] = dataVersion.getIndoorOutdoorAndType(); 1126 | 1127 | rootObj[F("gridStandardParam")] = dataVersion.par2; 1128 | rootObj[F("gridStandard")] = dataVersion.getGridStandard(); 1129 | 1130 | rootObj[F("trasformerLessParam")] = dataVersion.par3; 1131 | rootObj[F("trasformerLess")] = dataVersion.getTrafoOrNonTrafo(); 1132 | 1133 | rootObj[F("windOrPVParam")] = dataVersion.par4; 1134 | rootObj[F("windOrPV")] = dataVersion.getWindOrPV(); 1135 | 1136 | rootObj[F("firmwareRelease")] = firmwareRelease.release; 1137 | rootObj[F("systemSN")] = systemSN.SerialNumber; 1138 | 1139 | rootObj[F("systemPN")] = systemPN.PN; 1140 | 1141 | JsonObject mWY = rootObj.createNestedObject(F("manufactory")); 1142 | mWY[F("Year")] = manufactoryWeekYear.Year; 1143 | mWY[F("Week")] = manufactoryWeekYear.Week; 1144 | 1145 | JsonObject cs = rootObj.createNestedObject(F("configStatus")); 1146 | cs[F("code")] = configStatus.configStatus; 1147 | cs[F("desc")] = configStatus.getConfigStatus(); 1148 | 1149 | DEBUG_PRINTLN(F("done.")); 1150 | 1151 | isFileSaveOK = saveJSonToAFile(&doc, filename); 1152 | } 1153 | 1154 | } 1155 | 1156 | void cumulatedEnergyDaily(tm nowDt){ 1157 | String scopeDirectory = F("monthly"); 1158 | // Save cumulative data 1159 | if (nowDt.tm_min % CUMULATIVE_INTERVAL == 0) { 1160 | Aurora::DataCumulatedEnergy dce = inverter.readCumulatedEnergy( 1161 | CUMULATED_DAILY_ENERGY); 1162 | DEBUG_PRINT(F("Read state --> ")); 1163 | DEBUG_PRINTLN(dce.state.readState); 1164 | 1165 | if (dce.state.readState == 1) { 1166 | unsigned long energy = dce.energy; 1167 | 1168 | if (!SD.exists(scopeDirectory)) { 1169 | SD.mkdir(scopeDirectory); 1170 | } 1171 | 1172 | Aurora::DataDSP dsp = inverter.readDSP(DSP_POWER_PEAK_TODAY_ALL); 1173 | float powerPeak = dsp.value; 1174 | 1175 | if (energy && energy > 0) { 1176 | String filename = scopeDirectory + "/" 1177 | + getEpochStringByParams(getLocalTime(), (char*) "%Y%m") 1178 | + F(".jso"); 1179 | String tagName = getEpochStringByParams(getLocalTime(), (char*) "%d"); 1180 | 1181 | DynamicJsonDocument doc(PRODUCTION_AND_CUMULATED_HEAP); 1182 | JsonObject obj; 1183 | 1184 | obj = getJSonFromFile(&doc, filename); 1185 | 1186 | obj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 1187 | 1188 | JsonObject series; 1189 | if (!obj.containsKey(F("series"))) { 1190 | series = obj.createNestedObject(F("series")); 1191 | } else { 1192 | series = obj[F("series")]; 1193 | } 1194 | 1195 | JsonObject dayData = series.createNestedObject(tagName); 1196 | dayData[F("pow")] = energy; 1197 | dayData[F("powPeak")] = setPrecision(powerPeak, 1); 1198 | 1199 | DEBUG_PRINT(F("Store cumulated energy --> ")); 1200 | // serializeJson(doc, Serial); 1201 | DEBUG_PRINT(doc.memoryUsage()); 1202 | DEBUG_PRINTLN(); 1203 | 1204 | isFileSaveOK = saveJSonToAFile(&doc, filename); 1205 | } 1206 | 1207 | } 1208 | } 1209 | } 1210 | 1211 | #ifdef WS_ACTIVE 1212 | void sendWSCumulatedEnergy( unsigned long energyLifetime, 1213 | unsigned long energyYearly, 1214 | unsigned long energyMonthly, 1215 | unsigned long energyWeekly, 1216 | unsigned long energyDaily){ 1217 | DynamicJsonDocument docws(512); 1218 | JsonObject objws = docws.to(); 1219 | String dateFormatted = getEpochStringByParams(getLocalTime()); 1220 | objws["type"] = CUMULATED_ENERGY_TYPE; 1221 | objws["date"] = dateFormatted; 1222 | 1223 | JsonObject objValue = objws.createNestedObject("value"); 1224 | 1225 | objValue[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 1226 | 1227 | if (energyLifetime>0){ 1228 | objValue[F("energyLifetime")] = energyLifetime; 1229 | } 1230 | if (energyYearly>0){ 1231 | objValue[F("energyYearly")] = energyYearly; 1232 | } 1233 | if (energyMonthly>0){ 1234 | objValue[F("energyMonthly")] = energyMonthly; 1235 | } 1236 | if (energyWeekly>0){ 1237 | objValue[F("energyWeekly")] = energyWeekly; 1238 | } 1239 | if (energyDaily>0){ 1240 | objValue[F("energyDaily")] = energyDaily; 1241 | } 1242 | 1243 | String buf; 1244 | serializeJson(objws, buf); 1245 | 1246 | webSocket.broadcastTXT(buf); 1247 | } 1248 | #endif 1249 | void readTotals(tm nowDt){ 1250 | String scopeDirectory = F("states"); 1251 | // Save cumulative data 1252 | if (nowDt.tm_min % CUMULATIVE_TOTAL_INTERVAL == 0) { 1253 | 1254 | DEBUG_PRINTLN(F("Get all totals.")); 1255 | Aurora::DataCumulatedEnergy dce = inverter.readCumulatedEnergy( 1256 | CUMULATED_TOTAL_ENERGY_LIFETIME); 1257 | unsigned long energyLifetime = dce.energy; 1258 | DEBUG_PRINTLN(energyLifetime); 1259 | 1260 | if (dce.state.readState == 1) { 1261 | 1262 | dce = inverter.readCumulatedEnergy(CUMULATED_YEARLY_ENERGY); 1263 | unsigned long energyYearly = dce.energy; 1264 | DEBUG_PRINTLN(energyYearly); 1265 | 1266 | dce = inverter.readCumulatedEnergy(CUMULATED_MONTHLY_ENERGY); 1267 | unsigned long energyMonthly = dce.energy; 1268 | DEBUG_PRINTLN(energyMonthly); 1269 | 1270 | dce = inverter.readCumulatedEnergy(CUMULATED_WEEKLY_ENERGY); 1271 | unsigned long energyWeekly = dce.energy; 1272 | DEBUG_PRINTLN(energyWeekly); 1273 | 1274 | dce = inverter.readCumulatedEnergy(CUMULATED_DAILY_ENERGY); 1275 | unsigned long energyDaily = dce.energy; 1276 | DEBUG_PRINTLN(energyDaily); 1277 | 1278 | 1279 | if (!SD.exists(scopeDirectory)) { 1280 | SD.mkdir(scopeDirectory); 1281 | } 1282 | if (dce.state.readState == 1 && energyLifetime) { 1283 | #ifdef WS_ACTIVE 1284 | sendWSCumulatedEnergy(energyLifetime, energyYearly, energyMonthly, energyWeekly, energyDaily); 1285 | #endif 1286 | 1287 | String filename = scopeDirectory + F("/lastStat.jso"); 1288 | String tagName = getEpochStringByParams(getLocalTime(), (char*) "%d"); 1289 | 1290 | DynamicJsonDocument doc(CUMULATED_VALUES_HEAP); 1291 | JsonObject obj = doc.to();; 1292 | 1293 | obj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 1294 | 1295 | if (energyLifetime>0){ 1296 | obj[F("energyLifetime")] = energyLifetime; 1297 | } 1298 | if (energyYearly>0){ 1299 | obj[F("energyYearly")] = energyYearly; 1300 | } 1301 | if (energyMonthly>0){ 1302 | obj[F("energyMonthly")] = energyMonthly; 1303 | } 1304 | if (energyWeekly>0){ 1305 | obj[F("energyWeekly")] = energyWeekly; 1306 | } 1307 | if (energyDaily>0){ 1308 | obj[F("energyDaily")] = energyDaily; 1309 | } 1310 | 1311 | obj[F("W")] = getEpochStringByParams(getLocalTime(), (char*) "%Y%W"); 1312 | obj[F("M")] = getEpochStringByParams(getLocalTime(), (char*) "%Y%m"); 1313 | obj[F("Y")] = getEpochStringByParams(getLocalTime(), (char*) "%Y"); 1314 | 1315 | DEBUG_PRINT(F("Store energyLifetime energy --> ")); 1316 | // serializeJson(doc, Serial); 1317 | DEBUG_PRINT(doc.memoryUsage()); 1318 | DEBUG_PRINTLN(); 1319 | 1320 | isFileSaveOK = saveJSonToAFile(&doc, filename); 1321 | 1322 | { 1323 | DynamicJsonDocument docW(CUMULATED_VALUES_TOT_HEAP); 1324 | JsonObject objW; 1325 | 1326 | String dir = scopeDirectory + F("/weeks"); 1327 | if (!SD.exists(dir)) { 1328 | SD.mkdir(dir); 1329 | } 1330 | 1331 | String filenamew = dir +'/'+getEpochStringByParams(getLocalTime(), (char*) "%Y")+ F(".jso"); 1332 | 1333 | objW = getJSonFromFile(&docW, filenamew); 1334 | objW[getEpochStringByParams(getLocalTime(), (char*) "%Y%W")] = energyWeekly; 1335 | 1336 | isFileSaveOK = saveJSonToAFile(&docW, filenamew); 1337 | } 1338 | { 1339 | DynamicJsonDocument docM(CUMULATED_VALUES_TOT_HEAP); 1340 | JsonObject objM; 1341 | 1342 | String dir = scopeDirectory + F("/months"); 1343 | if (!SD.exists(dir)) { 1344 | SD.mkdir(dir); 1345 | } 1346 | 1347 | String filenamem = dir +'/'+getEpochStringByParams(getLocalTime(), (char*) "%Y")+ F(".jso"); 1348 | 1349 | objM = getJSonFromFile(&docM, filenamem); 1350 | 1351 | objM[getEpochStringByParams(getLocalTime(), (char*) "%Y%m")] = energyMonthly; 1352 | 1353 | isFileSaveOK = saveJSonToAFile(&docM, filenamem); 1354 | } 1355 | { 1356 | DynamicJsonDocument docY(CUMULATED_VALUES_TOT_HEAP); 1357 | JsonObject objY; 1358 | 1359 | String filenamey = scopeDirectory + F("/years.jso"); 1360 | 1361 | objY = getJSonFromFile(&docY, filenamey); 1362 | 1363 | objY[getEpochStringByParams(getLocalTime(), (char*) "%Y")] = energyYearly; 1364 | 1365 | isFileSaveOK = saveJSonToAFile(&docY, filenamey); 1366 | } 1367 | 1368 | } 1369 | } 1370 | } 1371 | 1372 | } 1373 | #ifdef WS_ACTIVE 1374 | void sendSimpleWSMessage(String type, String val){ 1375 | DynamicJsonDocument docws(512); 1376 | JsonObject objws = docws.to(); 1377 | String dateFormatted = getEpochStringByParams(getLocalTime()); 1378 | objws["type"] = type; 1379 | objws["date"] = dateFormatted; 1380 | 1381 | objws["value"] = val; 1382 | 1383 | String buf; 1384 | serializeJson(objws, buf); 1385 | 1386 | webSocket.broadcastTXT(buf); 1387 | } 1388 | #endif 1389 | 1390 | void readProductionDaily(tm nowDt){ 1391 | String scopeDirectory = F("product"); 1392 | if (!SD.exists(scopeDirectory)) { 1393 | SD.mkdir(scopeDirectory); 1394 | } 1395 | for (int i = 0; i < 3; i++) { 1396 | if (nowDt.tm_min % DAILY_INTERVAL == 0) { 1397 | byte read = DSP_GRID_POWER_ALL; 1398 | String filename = DSP_GRID_POWER_ALL_FILENAME; 1399 | String type = DSP_GRID_POWER_ALL_TYPE; 1400 | 1401 | switch (i) { 1402 | case (1): 1403 | read = DSP_GRID_CURRENT_ALL; 1404 | filename = DSP_GRID_CURRENT_ALL_FILENAME; 1405 | type = DSP_GRID_CURRENT_ALL_TYPE; 1406 | break; 1407 | case (2): 1408 | read = DSP_GRID_VOLTAGE_ALL; 1409 | filename = DSP_GRID_VOLTAGE_ALL_FILENAME; 1410 | type = DSP_GRID_VOLTAGE_ALL_TYPE; 1411 | break; 1412 | } 1413 | 1414 | Aurora::DataDSP dsp = inverter.readDSP(read); 1415 | DEBUG_PRINT(F("Read state --> ")); 1416 | DEBUG_PRINTLN(dsp.state.readState); 1417 | if (dsp.state.readState == 1) { 1418 | float val = dsp.value; 1419 | 1420 | if (val && val > 0) { 1421 | #ifdef WS_ACTIVE 1422 | sendSimpleWSMessage(type, String(setPrecision(val, 1))); 1423 | #endif 1424 | 1425 | String dataDirectory = getEpochStringByParams(getLocalTime(), 1426 | (char*) "%Y%m%d"); 1427 | 1428 | if (i == 0 1429 | && !SD.exists( 1430 | scopeDirectory + '/' + dataDirectory)) { 1431 | SD.mkdir(scopeDirectory + '/' + dataDirectory); 1432 | } 1433 | 1434 | String filenameDir = scopeDirectory + "/" + dataDirectory 1435 | + "/" + filename; 1436 | 1437 | DynamicJsonDocument doc(PRODUCTION_AND_CUMULATED_HEAP); 1438 | JsonObject obj; 1439 | 1440 | obj = getJSonFromFile(&doc, filenameDir); 1441 | 1442 | obj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 1443 | 1444 | JsonArray data; 1445 | if (!obj.containsKey(F("data"))) { 1446 | data = obj.createNestedArray(F("data")); 1447 | } else { 1448 | data = obj["data"]; 1449 | } 1450 | 1451 | JsonObject objArrayData = data.createNestedObject(); 1452 | objArrayData[F("h")] = getEpochStringByParams(getLocalTime(), 1453 | (char*) "%H%M"); 1454 | objArrayData[F("val")] = setPrecision(val, 1); 1455 | DEBUG_PRINTLN(F("Store production --> ")); 1456 | DEBUG_PRINT(doc.memoryUsage()); 1457 | DEBUG_PRINTLN(); 1458 | 1459 | isFileSaveOK = saveJSonToAFile(&doc, filenameDir); 1460 | } 1461 | } 1462 | } 1463 | } 1464 | 1465 | } 1466 | 1467 | #ifdef WS_ACTIVE 1468 | void realtimeDataCallbak() { 1469 | DEBUG_PRINT(F("Thread call (realtimeDataCallbak) --> ")); 1470 | DEBUG_PRINT(getEpochStringByParams(getLocalTime())); 1471 | DEBUG_PRINT(F(" MEM --> ")); 1472 | DEBUG_PRINTLN(ESP.getFreeHeap()); 1473 | 1474 | 1475 | byte read = DSP_GRID_POWER_ALL; 1476 | String type = DSP_GRID_POWER_ALL_TYPE_RT; 1477 | 1478 | // read = DSP_GRID_CURRENT_ALL; 1479 | // filename = DSP_GRID_CURRENT_ALL_FILENAME; 1480 | // type = DSP_GRID_CURRENT_ALL_TYPE; 1481 | // read = DSP_GRID_VOLTAGE_ALL; 1482 | // filename = DSP_GRID_VOLTAGE_ALL_FILENAME; 1483 | // type = DSP_GRID_VOLTAGE_ALL_TYPE; 1484 | 1485 | Aurora::DataDSP dsp = inverter.readDSP(read); 1486 | DEBUG_PRINT(F("Read DSP state --> ")); 1487 | DEBUG_PRINTLN(dsp.state.readState); 1488 | if (dsp.state.readState == 1) { 1489 | float val = dsp.value; 1490 | 1491 | if (val && val > 0) { 1492 | sendSimpleWSMessage(type, String(setPrecision(val, 1))); 1493 | } 1494 | } 1495 | 1496 | float bv = getBatteryVoltage(); 1497 | DEBUG_PRINT(F("BATTERY --> ")); 1498 | DEBUG_PRINTLN(bv); 1499 | 1500 | type = BATTERY_RT; 1501 | if (bv>1) { 1502 | sendSimpleWSMessage(type, String(setPrecision(bv, 2))); 1503 | } 1504 | 1505 | DEBUG_PRINT(F("WIFI SIGNAL STRENGHT --> ")); 1506 | DEBUG_PRINTLN(WiFi.RSSI()); 1507 | 1508 | type = WIFI_SIGNAL_STRENGHT_RT; 1509 | sendSimpleWSMessage(type, String(WiFi.RSSI())); 1510 | } 1511 | #endif 1512 | 1513 | void leggiProduzioneCallback() { 1514 | DEBUG_PRINT(F("Thread call (leggiProduzioneCallback) --> ")); 1515 | DEBUG_PRINT(getEpochStringByParams(getLocalTime())); 1516 | DEBUG_PRINT(F(" MEM --> ")); 1517 | DEBUG_PRINTLN(ESP.getFreeHeap()); 1518 | 1519 | tm nowDt = getDateTimeByParams(getLocalTime()); 1520 | 1521 | cumulatedEnergyDaily(nowDt); 1522 | readTotals(nowDt); 1523 | readProductionDaily(nowDt); 1524 | 1525 | errorLed(!(fixedTime && sdStarted&& wifiConnected && isFileSaveOK)); 1526 | 1527 | // digitalWrite(ERROR_PIN, !(fixedTime && sdStarted&& wifiConnected && isFileSaveOK)); 1528 | } 1529 | 1530 | JsonObject getJSonFromFile(DynamicJsonDocument *doc, String filename, bool forceCleanONJsonError ) { 1531 | // open the file for reading: 1532 | myFileSDCart = SD.open(filename); 1533 | if (myFileSDCart) { 1534 | // read from the file until there's nothing else in it: 1535 | // if (myFileSDCart.available()) { 1536 | // firstWrite = false; 1537 | // } 1538 | 1539 | DeserializationError error = deserializeJson(*doc, myFileSDCart); 1540 | if (error) { 1541 | // if the file didn't open, print an error: 1542 | DEBUG_PRINT(F("Error parsing JSON ")); 1543 | DEBUG_PRINTLN(error.c_str()); 1544 | 1545 | if (forceCleanONJsonError){ 1546 | return doc->to(); 1547 | } 1548 | } 1549 | 1550 | // close the file: 1551 | myFileSDCart.close(); 1552 | 1553 | return doc->as(); 1554 | } else { 1555 | // if the file didn't open, print an error: 1556 | DEBUG_PRINT(F("Error opening (or file not exists) ")); 1557 | DEBUG_PRINTLN(filename); 1558 | 1559 | DEBUG_PRINTLN(F("Empty json created")); 1560 | return doc->to(); 1561 | } 1562 | 1563 | } 1564 | 1565 | bool saveJSonToAFile(DynamicJsonDocument *doc, String filename) { 1566 | SD.remove(filename); 1567 | 1568 | // open the file. note that only one file can be open at a time, 1569 | // so you have to close this one before opening another. 1570 | DEBUG_PRINTLN(F("Open file in write mode")); 1571 | myFileSDCart = SD.open(filename, FILE_WRITE); 1572 | if (myFileSDCart) { 1573 | DEBUG_PRINT(F("Filename --> ")); 1574 | DEBUG_PRINTLN(filename); 1575 | 1576 | DEBUG_PRINT(F("Start write...")); 1577 | 1578 | serializeJson(*doc, myFileSDCart); 1579 | 1580 | DEBUG_PRINT(F("...")); 1581 | // close the file: 1582 | myFileSDCart.close(); 1583 | DEBUG_PRINTLN(F("done.")); 1584 | 1585 | return true; 1586 | } else { 1587 | // if the file didn't open, print an error: 1588 | DEBUG_PRINT(F("Error opening ")); 1589 | DEBUG_PRINTLN(filename); 1590 | 1591 | return false; 1592 | } 1593 | } 1594 | 1595 | void setCrossOrigin(){ 1596 | httpRestServer.sendHeader(F("Access-Control-Allow-Origin"), F("*")); 1597 | httpRestServer.sendHeader(F("Access-Control-Max-Age"), F("600")); 1598 | httpRestServer.sendHeader(F("Access-Control-Allow-Methods"), F("PUT,POST,GET,OPTIONS")); 1599 | httpRestServer.sendHeader(F("Access-Control-Allow-Headers"), F("*")); 1600 | }; 1601 | 1602 | void streamFileOnRest(String filename){ 1603 | if (SD.exists(filename)){ 1604 | myFileSDCart = SD.open(filename); 1605 | if (myFileSDCart){ 1606 | if (myFileSDCart.available()){ 1607 | DEBUG_PRINT(F("Stream file...")); 1608 | DEBUG_PRINT(filename); 1609 | httpRestServer.streamFile(myFileSDCart, F("application/json")); 1610 | DEBUG_PRINTLN(F("...done.")); 1611 | }else{ 1612 | DEBUG_PRINTLN(F("Data not available!")); 1613 | httpRestServer.send(204, F("text/html"), F("Data not available!")); 1614 | } 1615 | myFileSDCart.close(); 1616 | }else{ 1617 | DEBUG_PRINT(filename); 1618 | DEBUG_PRINTLN(F(" not found!")); 1619 | 1620 | httpRestServer.send(204, F("text/html"), F("No content found!")); 1621 | } 1622 | }else{ 1623 | DEBUG_PRINT(filename); 1624 | DEBUG_PRINTLN(F(" not found!")); 1625 | httpRestServer.send(204, F("text/html"), F("File not exist!")); 1626 | } 1627 | } 1628 | String getContentType(String filename){ 1629 | if(filename.endsWith(F(".htm"))) return F("text/html"); 1630 | else if(filename.endsWith(F(".html"))) return F("text/html"); 1631 | else if(filename.endsWith(F(".css"))) return F("text/css"); 1632 | else if(filename.endsWith(F(".js"))) return F("application/javascript"); 1633 | else if(filename.endsWith(F(".json"))) return F("application/json"); 1634 | else if(filename.endsWith(F(".png"))) return F("image/png"); 1635 | else if(filename.endsWith(F(".gif"))) return F("image/gif"); 1636 | else if(filename.endsWith(F(".jpg"))) return F("image/jpeg"); 1637 | else if(filename.endsWith(F(".ico"))) return F("image/x-icon"); 1638 | else if(filename.endsWith(F(".xml"))) return F("text/xml"); 1639 | else if(filename.endsWith(F(".pdf"))) return F("application/x-pdf"); 1640 | else if(filename.endsWith(F(".zip"))) return F("application/x-zip"); 1641 | else if(filename.endsWith(F(".gz"))) return F("application/x-gzip"); 1642 | return F("text/plain"); 1643 | } 1644 | 1645 | bool handleFileRead(String path){ // send the right file to the client (if it exists) 1646 | DEBUG_PRINT(F("handleFileRead: ")); 1647 | DEBUG_PRINTLN(path); 1648 | 1649 | if(path.endsWith("/")) path += F("index.html"); // If a folder is requested, send the index file 1650 | String contentType = getContentType(path); // Get the MIME type 1651 | String pathWithGz = path + F(".gz"); 1652 | if(SPIFFS.exists(pathWithGz) || SPIFFS.exists(path)){ // If the file exists, either as a compressed archive, or normal 1653 | if(SPIFFS.exists(pathWithGz)) // If there's a compressed version available 1654 | path += F(".gz"); // Use the compressed version 1655 | fs::File file = SPIFFS.open(path, "r"); // Open the file 1656 | size_t sent = httpServer.streamFile(file, contentType); // Send it to the client 1657 | file.close(); // Close the file again 1658 | DEBUG_PRINTLN(String(F("\tSent file: ")) + path + String(F(" of size ")) + sent); 1659 | return true; 1660 | } 1661 | DEBUG_PRINTLN(String(F("\tFile Not Found: ")) + path); 1662 | return false; // If the file doesn't exist, return false 1663 | } 1664 | 1665 | void streamFile(const String filename){ 1666 | if (SPIFFS.exists(filename)){ 1667 | fs::File fileToStream = SPIFFS.open(filename, "r"); 1668 | if (fileToStream){ 1669 | if (fileToStream.available()){ 1670 | DEBUG_PRINT(F("Stream file...")); 1671 | const String appContext = getContentType(filename); 1672 | httpRestServer.streamFile(fileToStream, appContext); 1673 | DEBUG_PRINTLN(F("done.")); 1674 | }else{ 1675 | DEBUG_PRINTLN(F("Data not available!")); 1676 | httpRestServer.send(204, F("text/html"), F("Data not available!")); 1677 | } 1678 | fileToStream.close(); 1679 | }else{ 1680 | DEBUG_PRINTLN(F("File not found!")); 1681 | httpRestServer.send(204, "text/html", "No content found!"); 1682 | } 1683 | }else{ 1684 | DEBUG_PRINTLN(F("File not found!")); 1685 | httpRestServer.send(204, "text/html", "File not exist!"); 1686 | } 1687 | } 1688 | 1689 | void getProduction(){ 1690 | DEBUG_PRINTLN(F("getProduction")); 1691 | 1692 | setCrossOrigin(); 1693 | 1694 | if (httpRestServer.arg(F("day"))== "" || httpRestServer.arg(F("type"))== "" ){ //Parameter not found 1695 | httpRestServer.send(400, F("text/html"), F("Missing required parameter!")); 1696 | DEBUG_PRINTLN(F("No parameter")); 1697 | }else{ //Parameter found 1698 | DEBUG_PRINT(F("Read file: ")); 1699 | String filename = "product/"+httpRestServer.arg("day")+"/"+httpRestServer.arg("type")+F(".jso"); 1700 | 1701 | DEBUG_PRINTLN(filename); 1702 | 1703 | streamFileOnRest(filename); 1704 | } 1705 | } 1706 | 1707 | void getBatteryInfo(){ 1708 | DEBUG_PRINTLN(F("getBatteryInfo")); 1709 | 1710 | setCrossOrigin(); 1711 | 1712 | if (httpRestServer.arg(F("day"))== "" ){ //Parameter not found 1713 | httpRestServer.send(400, F("text/html"), F("Missing required parameter!")); 1714 | DEBUG_PRINTLN(F("No parameter")); 1715 | }else{ //Parameter found 1716 | DEBUG_PRINT(F("Read file: ")); 1717 | String filename = "battery/"+httpRestServer.arg(F("day"))+".jso"; 1718 | 1719 | DEBUG_PRINTLN(filename); 1720 | 1721 | streamFileOnRest(filename); 1722 | } 1723 | } 1724 | void getProductionTotal(){ 1725 | DEBUG_PRINTLN(F("getProduction")); 1726 | 1727 | setCrossOrigin(); 1728 | 1729 | 1730 | DEBUG_PRINT(F("Read file: ")); 1731 | String scopeDirectory = F("states"); 1732 | String filename = scopeDirectory+F("/lastStat.jso"); 1733 | 1734 | 1735 | DEBUG_PRINTLN(filename); 1736 | 1737 | streamFileOnRest(filename); 1738 | 1739 | } 1740 | 1741 | void getMontlyValue(){ 1742 | DEBUG_PRINTLN(F("getMontlyValue")); 1743 | 1744 | setCrossOrigin(); 1745 | String scopeDirectory = F("monthly"); 1746 | 1747 | if (httpRestServer.arg("month")== ""){ //Parameter not found 1748 | httpRestServer.send(400, F("text/html"), F("Missing required parameter!")); 1749 | DEBUG_PRINTLN(F("No parameter")); 1750 | }else{ //Parameter found 1751 | DEBUG_PRINT(F("Read file: ")); 1752 | String filename = scopeDirectory+'/'+httpRestServer.arg(F("month"))+F(".jso"); 1753 | streamFileOnRest(filename); 1754 | } 1755 | } 1756 | 1757 | void getHistoricalValue(){ 1758 | DEBUG_PRINTLN(F("getHistoricalValue")); 1759 | 1760 | setCrossOrigin(); 1761 | String scopeDirectory = F("states"); 1762 | 1763 | if (httpRestServer.arg("frequence")== ""){ //Parameter not found 1764 | httpRestServer.send(400, F("text/html"), F("Missing required parameter!")); 1765 | DEBUG_PRINTLN(F("No frequence parameter")); 1766 | }else{ //Parameter found 1767 | String filename; 1768 | if (httpRestServer.arg("frequence") == F("years")){ 1769 | filename = scopeDirectory+'/'+httpRestServer.arg("frequence")+".jso"; 1770 | }else{ 1771 | if (httpRestServer.arg("year")== ""){ 1772 | httpRestServer.send(400, F("text/html"), F("Missing required parameter!")); 1773 | DEBUG_PRINTLN(F("No year parameter")); 1774 | }else{ 1775 | filename = scopeDirectory+'/'+httpRestServer.arg("frequence")+'/'+httpRestServer.arg("year")+F(".jso"); 1776 | } 1777 | } 1778 | DEBUG_PRINT(F("Read file: ")); 1779 | streamFileOnRest(filename); 1780 | } 1781 | } 1782 | 1783 | void postConfigFile() { 1784 | DEBUG_PRINTLN(F("postConfigFile")); 1785 | 1786 | setCrossOrigin(); 1787 | 1788 | String postBody = httpRestServer.arg("plain"); 1789 | DEBUG_PRINTLN(postBody); 1790 | 1791 | DynamicJsonDocument doc(CONFIG_FILE_HEAP); 1792 | DeserializationError error = deserializeJson(doc, postBody); 1793 | if (error) { 1794 | // if the file didn't open, print an error: 1795 | DEBUG_PRINT(F("Error parsing JSON ")); 1796 | DEBUG_PRINTLN(error.c_str()); 1797 | 1798 | String msg = error.c_str(); 1799 | 1800 | httpRestServer.send(400, F("text/html"), "Error in parsin json body!
"+msg); 1801 | 1802 | }else{ 1803 | JsonObject postObj = doc.as(); 1804 | 1805 | DEBUG_PRINT(F("HTTP Method: ")); 1806 | DEBUG_PRINTLN(httpRestServer.method()); 1807 | 1808 | if (httpRestServer.method() == HTTP_POST) { 1809 | if ((postObj.containsKey("server"))) { 1810 | 1811 | DEBUG_PRINT(F("Open config file...")); 1812 | fs::File configFile = SPIFFS.open(F("/mc/config.txt"), "w"); 1813 | if (!configFile) { 1814 | DEBUG_PRINTLN(F("fail.")); 1815 | httpRestServer.send(304, F("text/html"), F("Fail to store data, can't open file!")); 1816 | }else{ 1817 | DEBUG_PRINTLN(F("done.")); 1818 | serializeJson(doc, configFile); 1819 | // httpRestServer.sendHeader("Content-Length", String(postBody.length())); 1820 | httpRestServer.send(201, F("application/json"), postBody); 1821 | 1822 | // DEBUG_PRINTLN(F("Sent reset page")); 1823 | // delay(5000); 1824 | // ESP.restart(); 1825 | // delay(2000); 1826 | } 1827 | } 1828 | else { 1829 | httpRestServer.send(204, F("text/html"), F("No data found, or incorrect!")); 1830 | } 1831 | } 1832 | } 1833 | } 1834 | 1835 | void getConfigFile(){ 1836 | DEBUG_PRINTLN(F("getConfigFile")); 1837 | 1838 | setCrossOrigin(); 1839 | 1840 | DEBUG_PRINT(F("Read file: ")); 1841 | if (SPIFFS.exists(F("/mc/config.txt"))){ 1842 | fs::File configFile = SPIFFS.open(F("/mc/config.txt"), "r"); 1843 | if (configFile){ 1844 | if (configFile.available()){ 1845 | DEBUG_PRINT(F("Stream file...")); 1846 | httpRestServer.streamFile(configFile, F("application/json")); 1847 | DEBUG_PRINTLN(F("done.")); 1848 | }else{ 1849 | DEBUG_PRINTLN(F("File not found!")); 1850 | httpRestServer.send(204, F("text/html"), F("No content found!")); 1851 | } 1852 | configFile.close(); 1853 | } 1854 | }else{ 1855 | DEBUG_PRINTLN(F("File not found!")); 1856 | } 1857 | } 1858 | 1859 | void getInverterInfo(){ 1860 | DEBUG_PRINTLN(F("getInverterInfo")); 1861 | 1862 | setCrossOrigin(); 1863 | 1864 | String scopeDirectory = F("static"); 1865 | 1866 | DEBUG_PRINT(F("Read file: ")); 1867 | String filename = scopeDirectory+F("/invinfo.jso"); 1868 | streamFileOnRest(filename); 1869 | } 1870 | 1871 | void inverterDayWithProblem() { 1872 | DEBUG_PRINTLN(F("inverterDayWithProblem")); 1873 | 1874 | setCrossOrigin(); 1875 | 1876 | String scopeDirectory = F("alarms"); 1877 | 1878 | myFileSDCart = SD.open(scopeDirectory); 1879 | 1880 | 1881 | DynamicJsonDocument doc(ALARM_IN_A_DAY); 1882 | JsonObject rootObj = doc.to(); 1883 | 1884 | JsonArray data = rootObj.createNestedArray("data"); 1885 | 1886 | if (myFileSDCart){ 1887 | while (true) { 1888 | 1889 | File entry = myFileSDCart.openNextFile(); 1890 | if (!entry) { 1891 | // no more files 1892 | break; 1893 | } 1894 | DEBUG_PRINTLN(entry.name()); 1895 | if (entry.isDirectory()) { 1896 | data.add(entry.name()); 1897 | } 1898 | entry.close(); 1899 | } 1900 | 1901 | myFileSDCart.close(); 1902 | } 1903 | if (data.size() > 0) { 1904 | DEBUG_PRINT(F("Stream file...")); 1905 | String buf; 1906 | serializeJson(rootObj, buf); 1907 | httpRestServer.send(200, F("application/json"), buf); 1908 | DEBUG_PRINTLN(F("done.")); 1909 | } else { 1910 | DEBUG_PRINTLN(F("No content found!")); 1911 | httpRestServer.send(204, F("text/html"), F("No content found!")); 1912 | } 1913 | } 1914 | 1915 | void getInverterDayState() { 1916 | DEBUG_PRINTLN(F("getInverterDayState")); 1917 | 1918 | setCrossOrigin(); 1919 | 1920 | String scopeDirectory = F("alarms"); 1921 | 1922 | DEBUG_PRINT(F("Read file: ")); 1923 | 1924 | if (httpRestServer.arg("day") == "") { //Parameter not found 1925 | httpRestServer.send(400, F("text/html"), F("Missing required parameter!")); 1926 | DEBUG_PRINTLN(F("No parameter")); 1927 | } else { //Parameter found 1928 | 1929 | String filename; 1930 | String parameter = httpRestServer.arg("day"); 1931 | filename = scopeDirectory + '/' + parameter + F("/alarms.jso"); 1932 | 1933 | DEBUG_PRINTLN(filename); 1934 | 1935 | streamFileOnRest(filename); 1936 | } 1937 | } 1938 | 1939 | void getInverterLastState(){ 1940 | DEBUG_PRINTLN(F("getInverterLastState")); 1941 | 1942 | setCrossOrigin(); 1943 | 1944 | String scopeDirectory = F("alarms"); 1945 | 1946 | DEBUG_PRINT(F("Read file: ")); 1947 | 1948 | String filename; 1949 | String parameter = httpRestServer.arg("day"); 1950 | filename = scopeDirectory+F("/alarStat.jso"); 1951 | 1952 | streamFileOnRest(filename); 1953 | } 1954 | 1955 | float getBatteryVoltage(){ 1956 | //************ Measuring Battery Voltage *********** 1957 | float sample1 = 0; 1958 | 1959 | for (int i = 0; i < 100; i++) { 1960 | sample1 = sample1 + analogRead(A0); //read the voltage from the divider circuit 1961 | delay(2); 1962 | } 1963 | sample1 = sample1 / 100; 1964 | DEBUG_PRINT(F("AnalogRead...")); 1965 | DEBUG_PRINTLN(sample1); 1966 | float batVolt = (sample1 * 3.3 * (BAT_RES_VALUE_VCC + BAT_RES_VALUE_GND) / BAT_RES_VALUE_GND) / 1023; 1967 | return batVolt; 1968 | } 1969 | 1970 | void getServerState(){ 1971 | DEBUG_PRINTLN(F("getServerState")); 1972 | 1973 | setCrossOrigin(); 1974 | 1975 | DynamicJsonDocument doc(2048); 1976 | JsonObject rootObj = doc.to(); 1977 | 1978 | JsonObject net = rootObj.createNestedObject("network"); 1979 | rootObj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 1980 | 1981 | net[F("ip")] = WiFi.localIP().toString(); 1982 | net[F("gw")] = WiFi.gatewayIP().toString(); 1983 | net[F("nm")] = WiFi.subnetMask().toString(); 1984 | 1985 | net[F("dns1")] = WiFi.dnsIP(0).toString(); 1986 | net[F("dns2")] = WiFi.dnsIP(1).toString(); 1987 | 1988 | net[F("signalStrengh")] = WiFi.RSSI(); 1989 | 1990 | JsonObject chip = rootObj.createNestedObject("chip"); 1991 | chip[F("chipId")] = ESP.getChipId(); 1992 | chip[F("flashChipId")] = ESP.getFlashChipId(); 1993 | chip[F("flashChipSize")] = ESP.getFlashChipSize(); 1994 | chip[F("flashChipRealSize")] = ESP.getFlashChipRealSize(); 1995 | 1996 | chip[F("freeHeap")] = ESP.getFreeHeap(); 1997 | 1998 | chip[F("batteryVoltage")] = getBatteryVoltage(); //sample1;//setPrecision(batVolt,2); 1999 | 2000 | DEBUG_PRINT(F("Stream file...")); 2001 | String buf; 2002 | serializeJson(rootObj, buf); 2003 | httpRestServer.send(200, F("application/json"), buf); 2004 | DEBUG_PRINTLN(F("done.")); 2005 | } 2006 | void getReset(){ 2007 | DEBUG_PRINTLN(F("getReset")); 2008 | 2009 | setCrossOrigin(); 2010 | 2011 | // DynamicJsonDocument doc(2048); 2012 | // JsonObject rootObj = doc.to(); 2013 | // 2014 | // JsonObject net = rootObj.createNestedObject("network"); 2015 | // rootObj[F("lastUpdate")] = getEpochStringByParams(getLocalTime()); 2016 | // 2017 | // net[F("ip")] = WiFi.localIP().toString(); 2018 | // net[F("gw")] = WiFi.gatewayIP().toString(); 2019 | // net[F("nm")] = WiFi.subnetMask().toString(); 2020 | // 2021 | // net[F("dns1")] = WiFi.dnsIP(0).toString(); 2022 | // net[F("dns2")] = WiFi.dnsIP(1).toString(); 2023 | // 2024 | // net[F("signalStrengh")] = WiFi.RSSI(); 2025 | // 2026 | // JsonObject chip = rootObj.createNestedObject("chip"); 2027 | // chip[F("chipId")] = ESP.getChipId(); 2028 | // chip[F("flashChipId")] = ESP.getFlashChipId(); 2029 | // chip[F("flashChipSize")] = ESP.getFlashChipSize(); 2030 | // chip[F("flashChipRealSize")] = ESP.getFlashChipRealSize(); 2031 | // 2032 | // chip[F("freeHeap")] = ESP.getFreeHeap(); 2033 | // 2034 | // chip[F("batteryVoltage")] = getBatteryVoltage(); //sample1;//setPrecision(batVolt,2); 2035 | // 2036 | // DEBUG_PRINT(F("Stream file...")); 2037 | // String buf; 2038 | // serializeJson(rootObj, buf); 2039 | // httpRestServer.send(200, F("application/json"), buf); 2040 | // DEBUG_PRINTLN(F("done.")); 2041 | DEBUG_PRINT(F("Reset...")); 2042 | WiFiManager wifiManager; 2043 | wifiManager.resetSettings(); 2044 | 2045 | DEBUG_PRINT(SPIFFS.remove(F("/mc/config.txt"))); 2046 | 2047 | DEBUG_PRINTLN(F("done")); 2048 | 2049 | } 2050 | 2051 | void sendCrossOriginHeader(){ 2052 | DEBUG_PRINTLN(F("sendCORSHeader")); 2053 | 2054 | httpRestServer.sendHeader(F("access-control-allow-credentials"), F("false")); 2055 | 2056 | setCrossOrigin(); 2057 | 2058 | httpRestServer.send(204); 2059 | } 2060 | 2061 | void restServerRouting() { 2062 | // httpRestServer.header("Access-Control-Allow-Headers: Authorization, Content-Type"); 2063 | // 2064 | httpRestServer.on(F("/"), HTTP_GET, []() { 2065 | httpRestServer.send(200, F("text/html"), 2066 | F("Welcome to the Inverter Centraline REST Web Server")); 2067 | }); 2068 | httpRestServer.on(F("/production"), HTTP_GET, getProduction); 2069 | httpRestServer.on(F("/productionTotal"), HTTP_GET, getProductionTotal); 2070 | httpRestServer.on(F("/monthly"), HTTP_GET, getMontlyValue); 2071 | 2072 | httpRestServer.on(F("/historical"), HTTP_GET, getHistoricalValue); 2073 | 2074 | httpRestServer.on(F("/config"), HTTP_OPTIONS, sendCrossOriginHeader); 2075 | httpRestServer.on(F("/config"), HTTP_POST, postConfigFile); 2076 | httpRestServer.on(F("/config"), HTTP_GET, getConfigFile); 2077 | 2078 | httpRestServer.on(F("/inverterInfo"), HTTP_GET, getInverterInfo); 2079 | httpRestServer.on(F("/inverterState"), HTTP_GET, getInverterLastState); 2080 | httpRestServer.on(F("/inverterDayWithProblem"), HTTP_GET, inverterDayWithProblem); 2081 | httpRestServer.on(F("/inverterDayState"), HTTP_GET, getInverterDayState); 2082 | 2083 | httpRestServer.on(F("/serverState"), HTTP_GET, getServerState); 2084 | 2085 | httpRestServer.on(F("/battery"), HTTP_GET, getBatteryInfo); 2086 | 2087 | httpRestServer.on(F("/reset"), HTTP_GET, getReset); 2088 | 2089 | 2090 | } 2091 | 2092 | void serverRouting() { 2093 | httpServer.onNotFound([]() { // If the client requests any URI 2094 | DEBUG_PRINTLN(F("On not found")); 2095 | if (!handleFileRead(httpServer.uri())){ // send it if it exists 2096 | DEBUG_PRINTLN(F("Not found")); 2097 | httpServer.send(404, F("text/plain"), F("404: Not Found")); // otherwise, respond with a 404 (Not Found) error 2098 | } 2099 | }); 2100 | 2101 | DEBUG_PRINTLN(F("Set cache!")); 2102 | 2103 | httpServer.serveStatic("/settings.json", SPIFFS, "/settings.json","no-cache, no-store, must-revalidate"); 2104 | httpServer.serveStatic("/", SPIFFS, "/","max-age=31536000"); 2105 | } 2106 | 2107 | 2108 | void updateLocalTimeWithNTPCallback(){ 2109 | DEBUG_PRINT(F("Thread call (updateLocalTimeWithNTPCallback) --> ")); 2110 | DEBUG_PRINTLN(getEpochStringByParams(now())); 2111 | 2112 | if (!fixedTime){ 2113 | DEBUG_PRINTLN(F("Update NTP Time.")); 2114 | if (timeClient.forceUpdate()){ 2115 | unsigned long epoch = timeClient.getEpochTime(); 2116 | DEBUG_PRINTLN(epoch); 2117 | 2118 | DEBUG_PRINTLN(F("NTP Time retrieved.")); 2119 | adjustTime(epoch); 2120 | fixedTime = true; 2121 | }else{ 2122 | DEBUG_PRINTLN(F("NTP Time not retrieved.")); 2123 | 2124 | DEBUG_PRINTLN(F("Get Inverter Time.")); 2125 | // Get DateTime from inverter 2126 | Aurora::DataTimeDate timeDate = inverter.readTimeDate(); 2127 | Timezone tz = getTimezoneData(codeDST); 2128 | tm tempTime = timeDate.getDateTime(); 2129 | time_t currentUTCTime = tz.toUTC(mktime(&tempTime)); 2130 | 2131 | if (timeDate.state.readState){ 2132 | DEBUG_PRINTLN(F("Inverter Time retrieved.")); 2133 | // Set correct time in Arduino Time librery 2134 | adjustTime(currentUTCTime); // - timeOffset); 2135 | fixedTime = true; 2136 | }else{ 2137 | DEBUG_PRINTLN(F("Inverter Time not retrieved.")); 2138 | DEBUG_PRINTLN(F("DANGER, SYSTEM INCONCISTENCY")); 2139 | } 2140 | } 2141 | 2142 | Timezone tz = getTimezoneData(codeDST); 2143 | 2144 | DEBUG_PRINTLN(getEpochStringByParams(now())); 2145 | DEBUG_PRINTLN(tz.utcIsDST(now())); 2146 | DEBUG_PRINTLN(getEpochStringByParams(tz.toLocal(now()))); 2147 | 2148 | } 2149 | 2150 | errorLed(!(fixedTime && sdStarted&& wifiConnected && isFileSaveOK)); 2151 | // digitalWrite(ERROR_PIN, !(fixedTime && sdStarted&& wifiConnected && isFileSaveOK)); 2152 | } 2153 | 2154 | float setPrecision(float val, byte precision){ 2155 | return ((int)(val*(10*precision)))/(float)(precision*10); 2156 | } 2157 | 2158 | uint8_t sdWrongReadNumber = 0; 2159 | bool alreadySendNotification = false; 2160 | 2161 | unsigned long lastTimeSendErrorNotification = millis(); 2162 | 2163 | #define DEBOUNCE_ERROR_NOTIFICATION 5000 2164 | 2165 | #ifdef WS_ACTIVE 2166 | void sendWSErrorCentraline(){ 2167 | DEBUG_PRINTLN(F("SEND sendWSErrorCentraline!")); 2168 | if (sdWrongReadNumber==1 || (lastTimeSendErrorNotification+DEBOUNCE_ERROR_NOTIFICATION(); 2171 | String dateFormatted = getEpochStringByParams(getLocalTime()); 2172 | objws["type"] = ERROR_TYPE; 2173 | objws["date"] = dateFormatted; 2174 | 2175 | JsonObject objValue = objws.createNestedObject("value"); 2176 | 2177 | objValue[F("h")] = getEpochStringByParams(getLocalTime()); 2178 | 2179 | const String ft = (fixedTime)?F("OK"):F("NO"); 2180 | const String sd = (sdStarted)?F("OK"):F("NO"); 2181 | const String wc = (wifiConnected)?F("OK"):F("NO"); 2182 | const String sf = (isFileSaveOK)?F("OK"):F("NO"); 2183 | 2184 | objValue[F("fixedTime")] = fixedTime; 2185 | objValue[F("sdStarted")] = sdStarted; 2186 | objValue[F("wifiConnected")] = wifiConnected; 2187 | objValue[F("isFileSaveOK")] = isFileSaveOK; 2188 | objValue[F("sdWrongReadNumber")] = sdWrongReadNumber; 2189 | 2190 | 2191 | 2192 | String buf; 2193 | serializeJson(objws, buf); 2194 | 2195 | webSocket.broadcastTXT(buf); 2196 | 2197 | lastTimeSendErrorNotification = millis(); 2198 | } 2199 | } 2200 | #endif 2201 | 2202 | void errorLed(bool flag){ 2203 | if (flag){ 2204 | sdWrongReadNumber++; 2205 | }else{ 2206 | sdWrongReadNumber = 0; 2207 | alreadySendNotification = false; 2208 | } 2209 | 2210 | #ifdef WS_ACTIVE 2211 | if (!alreadySendNotification && 2212 | ( 2213 | !fixedTime 2214 | || !sdStarted 2215 | || !wifiConnected 2216 | || !isFileSaveOK 2217 | ) 2218 | ){ 2219 | sendWSErrorCentraline(); 2220 | } 2221 | #endif 2222 | 2223 | DEBUG_PRINT(F("alreadySendNotification -> ")); 2224 | DEBUG_PRINTLN(alreadySendNotification); 2225 | 2226 | DEBUG_PRINT(F("sdWrongReadNumber -> ")); 2227 | DEBUG_PRINTLN(sdWrongReadNumber); 2228 | 2229 | 2230 | 2231 | digitalWrite(ERROR_PIN, flag); 2232 | if (!alreadySendNotification && sdWrongReadNumber>=SD_WRONG_WRITE_NUMBER_ALERT){ 2233 | alreadySendNotification = true; 2234 | 2235 | #ifdef SEND_EMAIL 2236 | 2237 | DEBUG_PRINT(F("Open config file...")); 2238 | fs::File configFile = SPIFFS.open(F("/mc/config.txt"), "r"); 2239 | if (configFile) { 2240 | DEBUG_PRINTLN("done."); 2241 | DynamicJsonDocument doc(CONFIG_FILE_HEAP); 2242 | DeserializationError error = deserializeJson(doc, configFile); 2243 | if (error) { 2244 | // if the file didn't open, print an error: 2245 | DEBUG_PRINT(F("Error parsing JSON ")); 2246 | DEBUG_PRINTLN(error.c_str()); 2247 | } 2248 | 2249 | // close the file: 2250 | configFile.close(); 2251 | 2252 | JsonObject rootObj = doc.as(); 2253 | 2254 | DEBUG_PRINT(F("After read config check serverSMTP and preferences ")); 2255 | DEBUG_PRINTLN(rootObj.containsKey(F("serverSMTP")) && rootObj.containsKey(F("preferences"))); 2256 | 2257 | if (rootObj.containsKey(F("serverSMTP")) && rootObj.containsKey(F("preferences"))){ 2258 | JsonObject serverSMTP = rootObj[F("serverSMTP")]; 2259 | JsonObject preferences = rootObj[F("preferences")]; 2260 | 2261 | DEBUG_PRINT(F("(preferences.containsKey(adminEmail) && preferences[adminEmail]!="")")); 2262 | DEBUG_PRINTLN((preferences.containsKey(F("adminEmail")) && preferences[F("adminEmail")]!="")); 2263 | 2264 | if (preferences.containsKey(F("adminEmail")) && preferences[F("adminEmail")]!=""){ 2265 | const char* serverSMTPAddr = serverSMTP[F("server")]; 2266 | emailSend.setSMTPServer(serverSMTPAddr); 2267 | uint16_t portSMTP = serverSMTP[F("port")]; 2268 | emailSend.setSMTPPort(portSMTP); 2269 | const char* loginSMTP = serverSMTP[F("login")]; 2270 | emailSend.setEMailLogin(loginSMTP); 2271 | const char* passwordSMTP = serverSMTP[F("password")]; 2272 | emailSend.setEMailPassword(passwordSMTP); 2273 | const char* fromSMTP = serverSMTP[F("from")]; 2274 | emailSend.setEMailFrom(fromSMTP); 2275 | 2276 | DEBUG_PRINT(F("server ")); 2277 | DEBUG_PRINTLN(serverSMTPAddr); 2278 | DEBUG_PRINT(F("port ")); 2279 | DEBUG_PRINTLN(portSMTP); 2280 | DEBUG_PRINT(F("login ")); 2281 | DEBUG_PRINTLN(loginSMTP); 2282 | DEBUG_PRINT(F("password ")); 2283 | DEBUG_PRINTLN(passwordSMTP); 2284 | DEBUG_PRINT(F("from ")); 2285 | DEBUG_PRINTLN(fromSMTP); 2286 | 2287 | EMailSender::EMailMessage message; 2288 | const String sub = F("Inverter error!"); 2289 | message.subject = sub; 2290 | 2291 | const String mp = F("Error on inverter centraline"); 2292 | const String ft = (fixedTime)?F("OK"):F("NO"); 2293 | const String sd = (sdStarted)?F("OK"):F("NO"); 2294 | const String wc = (wifiConnected)?F("OK"):F("NO"); 2295 | const String sf = (isFileSaveOK)?F("OK"):F("NO"); 2296 | 2297 | message.message = mp+ 2298 | F("
Time fixed: ")+ft+ 2299 | F("
SD Initialized: ")+sd+ 2300 | F("
Wifi Connecter :P: ")+wc+ 2301 | F("
Saving file ok: ")+sf+ 2302 | F("
Wrong saving attempts: ")+sdWrongReadNumber 2303 | ; 2304 | 2305 | const char* emailToSend = preferences[F("adminEmail")]; 2306 | EMailSender::Response resp = emailSend.send(emailToSend, message); 2307 | 2308 | DEBUG_PRINTLN(F("Sending status: ")); 2309 | DEBUG_PRINTLN(emailToSend); 2310 | DEBUG_PRINTLN(resp.status); 2311 | DEBUG_PRINTLN(resp.code); 2312 | DEBUG_PRINTLN(resp.desc); 2313 | } 2314 | } 2315 | } 2316 | #endif 2317 | } 2318 | } 2319 | 2320 | Timezone getTimezoneData(const String code){ 2321 | // DEBUG_PRINT("CODE DST RETRIVED: "); 2322 | // DEBUG_PRINTLN(code); 2323 | if (code==F("AETZ")){ 2324 | // Australia Eastern Time Zone (Sydney, Melbourne) 2325 | TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; // UTC + 11 hours 2326 | TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; // UTC + 10 hours 2327 | Timezone tzTmp = Timezone(aEDT, aEST); 2328 | return tzTmp; 2329 | }else if (code==F("CET")){ 2330 | // DEBUG_PRINTLN(F("CET FIND")); 2331 | // Central European Time (Frankfurt, Paris) 2332 | TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time 2333 | TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time 2334 | Timezone tzTmp = Timezone(CEST, CET); 2335 | return tzTmp; 2336 | }else if(code==F("MSK")){ 2337 | // Moscow Standard Time (MSK, does not observe DST) 2338 | TimeChangeRule msk = {"MSK", Last, Sun, Mar, 1, 180}; 2339 | Timezone tzTmp = Timezone(msk); 2340 | return tzTmp; 2341 | }else if(code==F("UK")){ 2342 | // United Kingdom (London, Belfast) 2343 | TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time 2344 | TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; // Standard Time 2345 | Timezone tzTmp = Timezone(BST, GMT); 2346 | return tzTmp; 2347 | }else if(code==F("USCTZ")){ 2348 | // US Central Time Zone (Chicago, Houston) 2349 | TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300}; 2350 | TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360}; 2351 | Timezone tzTmp = Timezone(usCDT, usCST); 2352 | return tzTmp; 2353 | }else if(code==F("USMTZ")){ 2354 | // US Mountain Time Zone (Denver, Salt Lake City) 2355 | TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360}; 2356 | TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420}; 2357 | Timezone tzTmp = Timezone(usMDT, usMST); 2358 | return tzTmp; 2359 | }else if(code==F("ARIZONA")){ 2360 | // Arizona is US Mountain Time Zone but does not use DST 2361 | TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420}; 2362 | Timezone tzTmp = Timezone(usMST); 2363 | return tzTmp; 2364 | }else if(code==F("USPTZ")){ 2365 | // US Pacific Time Zone (Las Vegas, Los Angeles) 2366 | TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420}; 2367 | TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480}; 2368 | Timezone tzTmp = Timezone(usPDT, usPST); 2369 | return tzTmp; 2370 | }else if(code==F("UTC")){ 2371 | // UTC 2372 | TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0}; // UTC 2373 | Timezone tzTmp = Timezone(utcRule); 2374 | return tzTmp; 2375 | }else{ 2376 | // UTC 2377 | // DEBUG_PRINT("TIME_OFFSET "); 2378 | int to = timeOffset/60; 2379 | // DEBUG_PRINTLN(to); 2380 | TimeChangeRule utcRule = {"GTM", Last, Sun, Mar, 1, to}; // UTC 2381 | Timezone tzTmp = Timezone(utcRule); 2382 | return tzTmp; 2383 | 2384 | } 2385 | 2386 | 2387 | } 2388 | 2389 | time_t getLocalTime(void){ 2390 | Timezone tz = getTimezoneData(codeDST); 2391 | 2392 | // DEBUG_PRINTLN(getEpochStringByParams(now())); 2393 | // DEBUG_PRINTLN(tz.utcIsDST(now())); 2394 | // DEBUG_PRINTLN(getEpochStringByParams(tz.toLocal(now()))); 2395 | // 2396 | return tz.toLocal(now()); 2397 | } 2398 | 2399 | #ifdef WS_ACTIVE 2400 | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { 2401 | 2402 | switch(type) { 2403 | case WStype_DISCONNECTED: 2404 | webSocket.sendTXT(num, "{\"connection\": false}"); 2405 | 2406 | DEBUG_PRINT(F(" Disconnected ")); 2407 | DEBUG_PRINTLN(num, DEC); 2408 | 2409 | // DEBUG_PRINTF_AI(F("[%u] Disconnected!\n"), num); 2410 | break; 2411 | case WStype_CONNECTED: 2412 | { 2413 | IPAddress ip = webSocket.remoteIP(num); 2414 | // DEBUG_PRINTF_AI(F("[%u] Connected from %d.%d.%d.%d url: %s\n"), num, ip[0], ip[1], ip[2], ip[3], payload); 2415 | 2416 | DEBUG_PRINT(num); 2417 | DEBUG_PRINT(F("Connected from: ")); 2418 | DEBUG_PRINT(ip.toString()); 2419 | DEBUG_PRINT(F(" ")); 2420 | DEBUG_PRINTLN((char*)payload); 2421 | 2422 | // send message to client 2423 | webSocket.sendTXT(num, "{\"connection\": true}"); 2424 | } 2425 | break; 2426 | case WStype_TEXT: 2427 | // DEBUG_PRINTF_AI(F("[%u] get Text: %s\n"), num, payload); 2428 | 2429 | // send message to client 2430 | // webSocket.sendTXT(num, "message here"); 2431 | 2432 | // send data to all connected clients 2433 | // webSocket.broadcastTXT("message here"); 2434 | break; 2435 | case WStype_BIN: 2436 | // DEBUG_PRINTF_AI(F("[%u] get binary length: %u\n"), num, length); 2437 | hexdump(payload, length); 2438 | 2439 | // send message to client 2440 | // webSocket.sendBIN(num, payload, length); 2441 | break; 2442 | case WStype_ERROR: 2443 | case WStype_FRAGMENT_TEXT_START: 2444 | case WStype_FRAGMENT_BIN_START: 2445 | case WStype_FRAGMENT: 2446 | case WStype_FRAGMENT_FIN: 2447 | case WStype_PING: 2448 | case WStype_PONG: 2449 | 2450 | // DEBUG_PRINTF_AI(F("[%u] get binary length: %u\n"), num, length); 2451 | DEBUG_PRINT(F("WS : ")) 2452 | DEBUG_PRINT(type) 2453 | DEBUG_PRINT(F(" - ")) 2454 | DEBUG_PRINTLN((char*)payload); 2455 | 2456 | // send message to client 2457 | // webSocket.sendBIN(num, payload, length); 2458 | break; 2459 | } 2460 | 2461 | } 2462 | #endif 2463 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Italy License. To view a copy of this license, visit http://creativecommons.org/licenses/by-nc-nd/3.0/it/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | Support forum ABB (ex Power One) Aurora Web Inverter Monitor (WIM) 5 |
6 |
7 | Forum supporto ABB (ex Power One) Aurora Web Inverter Monitor (WIM) 10 |
11 | 12 | 13 | 14 | # 15 | # 16 | 17 | ABB (ex Power One) Aurora Web Inverter Monitor (WIM): project introduction 18 | ============================================================================== 19 | 20 | by [Renzo Mischianti] 21 | 22 | [![Watch the video](https://img.youtube.com/vi/uInRM3YqIv0/hqdefault.jpg)](https://www.youtube.com/watch?v=uInRM3YqIv0) 23 | 24 | 02/11/2021 Fix default language to English 25 | 26 | Hi all, I put solar panels over my roof some years agò, the company that 27 | installed them had also guaranteed me a production monitoring and 28 | analysis system, but they forgot to tell me that it would be free only 29 | for the first year, and I would have had to pay to access my data that 30 | are stored on a site, the cost is not so enough (70€ for year) **but I 31 | felt cheated**. 32 | 33 | ![ABB Aurora Web Inverter Monitor Station Introduction](https://www.mischianti.org/wp-content/uploads/2020/06/ABB-Aurora-Web-Inverter-Centraline-Logging-Introduction-1024x586.jpg) 34 | 35 | ABB Aurora Web Inverter Monitor Station Introduction 36 | 37 | So my solution is to create an autonomous centraline with an esp8266 38 | that grab and store data from inverter and show me **chart and various 39 | data of production** and send me an **email if there are some 40 | problems**. 41 | 42 | It is a quite user-friendly browser based monitoring solution, It’s 43 | allows to track energy produced on a solar power plant in a simple and 44 | intuitive fashion. It’s can track key energy metrics as well as the 45 | energy produced throughout the lifetime of their solar power plant.  46 | 47 | Here the video when the project WOR work in progress 48 | 49 | I created a simple PCB milled and tested for some month of activity 50 | without problem. 51 | 52 | ![](https://www.mischianti.org/wp-content/uploads/2019/03/ABBAuroraOK-1024x850.jpg) 53 | 54 | ABB Aurora PCB multiple step 55 | 56 | ABB Aurora Web Monitor 57 | 58 | Library dependencies 59 | ----------------------------------------------------- 60 | 61 | 62 | ArduinoJson 63 | ArduinoThread 64 | aurora_communication_protocol 65 | DNSServer 66 | EMailSender 67 | ESP8266mDNS 68 | ESP8266SdFat 69 | ESP8266WebServer 70 | ESP8266WiFi 71 | Hash 72 | NTPClient 73 | SD 74 | SDFS 75 | SPI 76 | TimeLib 77 | Timezone 78 | WebSockets 79 | WiFiManager 80 | Wire 81 | 82 | Inverter Aurora ABB (ex PowerOne now Fimer) supported 83 | ----------------------------------------------------- 84 | 85 | Here a partial list of Aurora PV series supported 86 | 87 | - PVI-2000 88 | - PVI-2000-OUTD 89 | - PVI-3600 90 | - PVI-3.6-OUTD 91 | - PVI-5000-OUTD 92 | - PVI-6000-OUTD 93 | - 3-phase interface (3G74) 94 | - PVI-CENTRAL-50 module 95 | - PVI-4.2-OUTD 96 | - PVI-3.6-OUTD 97 | - PVI-3.3-OUTD 98 | - **PVI-3.0-OUTD** 99 | - PVI-12.5-OUTD 100 | - PVI-10.0-OUTD 101 | - PVI-4.6-I-OUTD 102 | - PVI-3.8-I-OUTD 103 | - PVI-12.0-I-OUTD (output 480 VAC) 104 | - PVI-10.0-I-OUTD (output 480 VAC) 105 | - PVI-12.0-I-OUTD (output 208 VAC) 106 | - PVI-10.0-I-OUTD (output 208 VAC) 107 | - PVI-12.0-I-OUTD (output 380 VAC) 108 | - PVI-10.0-I-OUTD (output 380 VAC) 109 | - PVI-12.0-I-OUTD (output 600 VAC) 110 | - PVI-10.0-I-OUTD (output 600 VAC)” 111 | - PVI-CENTRAL-250 112 | - PVI-10.0-I-OUTD (output 480 VAC current limit 12 A) 113 | - TRIO-27.6-TL-OUTD 114 | - TRIO-20-TL 115 | - UNO-2.0-I 116 | - UNO-2.5-I 117 | - PVI-CENTRAL-350 Liquid Cooled (control board) 118 | - PVI-CENTRAL-350 Liquid Cooled (display board) 119 | - PVI-CENTRAL-350 Liquid Cooled (AC gathering) 120 | 121 | My inverter is in bold. 122 | 123 | Introduction 124 | ------------ 125 | 126 | My idea is to use an esp8266 (Wemos D1) with enough power to manage an 127 | http server, a rest server and ftp server, naturally with an IC can 128 | interface my inverter (ABB Autora – ex PowerOne), all data taken from 129 | the inverter will be stored in an SD. 130 | 131 | ![](https://www.mischianti.org/wp-content/uploads/2019/05/elementInCommunication-e1557000905880.jpg) 132 | 133 | ABB Aurora inverter centraline components 134 | 135 | Phisical layers as you can see in the image are very simple, I add some 136 | additional logic layer. 137 | 138 | First I create a library to manage a full set of informations of the 139 | inverter from the interface RS-485 available, than I create a series of 140 | thread (simulated) with specified delay to get data and store they in an 141 | SD in JSON format, than I create a full set of REST api to retrieve this 142 | set of information, a WebSocket server for realtime data, and a 143 | responsive web app to show all this data finally a configurable 144 | notification system via mail. 145 | 146 | ![](https://www.mischianti.org/wp-content/uploads/2019/05/swLayer.jpg) 147 | 148 | ABB Aurora inverter centraline software layer 149 | 150 | Monitor specs and device 151 | ------------------------ 152 | 153 | My selected microcontroller is an WeMos D1 mini, I choice this esp8266 154 | device because It’s very low cost and have sufficient specs to do all 155 | features I have in my mind. Here a mini guide on how to configure your 156 | IDE “[WeMos D1 mini (esp8266), pinout, specs and IDE 157 | configuration](https://www.mischianti.org/2019/08/20/wemos-d1-mini-esp8266-specs-and-ide-configuration-part-1/)“. 158 | 159 | ### Pinouts 160 | 161 | ![](https://www.mischianti.org/wp-content/uploads/2019/05/D1wemosPinout.jpg) 162 | 163 | WeMos D1 mini pinout 164 | 165 | I think that an interesting thing is that It has more Hardware Serial, 166 | so you can use Serial for communication with Inverter and Serial1 D4 167 | (only Transmission) to debug. You can check how to connect debug 168 | USBtoTTL device on “[WeMos D1 mini (esp8266), debug on secondary 169 | UART](https://www.mischianti.org/2019/09/19/wemos-d1-mini-esp8266-debug-on-secondary-uart-part-3/)“. 170 | 171 | Sketch OTA update File system EEPROM WiFi config 172 | 173 | We are going to put **WebServer data in SPIFFS**, the size needed is 174 | less than 2Mb. SPIFFS is explained in this article “[WeMos D1 mini 175 | (esp8266), integrated SPIFFS 176 | Filesystem](https://www.mischianti.org/2019/08/30/wemos-d1-mini-esp8266-integrated-spiffs-filesistem-part-2/)“. 177 | 178 | To update WebServer pages I use an integrated FTP server “[FTP server on 179 | esp8266 and 180 | esp32](https://www.mischianti.org/2020/02/08/ftp-server-on-esp8266-and-esp32/)“. 181 | 182 | To **store logging data we must add an SD** card, It’s not sure use 183 | SPIFFS (exist a 16Mb version of esp8266) because have a write cycle 184 | limitation. You can connect directly via an SD adapter, but I prefer a 185 | module to better fit in my case. You can find information on how to 186 | connect SD card in this article “[How to use SD card with esp8266, esp32 187 | and 188 | Arduino](https://www.mischianti.org/2019/12/15/how-to-use-sd-card-with-esp8266-and-arduino/)“. 189 | 190 | Aurora ABB (ex PowerOne) communicate via RS-485 connection, so the most 191 | important features is the communication protocol, and for first I create 192 | a complete library to interface on this interface via Arduino, esp8266 193 | or esp32 device. 194 | 195 | I use a 18650 rechargeable battery as UPS to grant server active when 196 | It’s nigth and there aren’t energy production, I use the schema from 197 | this article “[Emergency power bank 198 | homemade](https://www.mischianti.org/2019/01/24/emergency-power-bank-homemade/)“. 199 | 200 | To logging data It’s also important get current date and time, so I 201 | choice to try to get data from NPT server, if It isn’t possible I get 202 | data from internal clock of inverter. 203 | 204 | To connect device I use and fix WIFIManager thar start esp8266 as Access 205 | Point and give an interface to set connection parameter. 206 | 207 | Thanks 208 | ------ 209 |
  1. ABB Aurora Web Inverter Monitor (WIM): project introduction
  2. ABB Aurora Web Inverter Monitor (WIM): wiring Arduino to RS-485
  3. ABB Aurora Web Inverter Monitor (WIM): storage devices
  4. ABB Aurora Web Inverter Monitor (WIM): debug and notification
  5. ABB Aurora Web Inverter Monitor (WIM): set time and UPS
  6. ABB Aurora Web Inverter Monitor (WIM): WIFI configuration and REST Server
  7. ABB Aurora Web Inverter Monitor (WIM): WebSocket and Web Server
  8. ABB Aurora Web Inverter Monitor (WIM): Wiring and PCB soldering
  9. ABB Aurora Web Inverter Monitor (WIM): upload the sketch and front end
  10. ABB Aurora web inverter Monitor (WIM): 3D printed case to complete project
  11. ABB Aurora web inverter monitor (WIM): repair E013 error
210 | 211 |

GitHub repository with all code BE and FE transpiled

-------------------------------------------------------------------------------- /data/aurora-web.min.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/aurora-web.min.js.gz -------------------------------------------------------------------------------- /data/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/favicon.ico -------------------------------------------------------------------------------- /data/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Aurora Inverter Centraline 21 | 22 |
23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /data/launcher-icon-0x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-0x.png -------------------------------------------------------------------------------- /data/launcher-icon-1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-1x.png -------------------------------------------------------------------------------- /data/launcher-icon-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-2x.png -------------------------------------------------------------------------------- /data/launcher-icon-3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-3x.png -------------------------------------------------------------------------------- /data/launcher-icon-4x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-4x.png -------------------------------------------------------------------------------- /data/launcher-icon-5x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-5x.png -------------------------------------------------------------------------------- /data/launcher-icon-trasp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon-trasp.png -------------------------------------------------------------------------------- /data/launcher-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-icon.png -------------------------------------------------------------------------------- /data/launcher-orig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/data/launcher-orig.png -------------------------------------------------------------------------------- /data/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AuroraInverter", 3 | "short_name": "AuroraInv", 4 | "start_url": "./", 5 | "display": "standalone", 6 | "theme_color": "#ffff00", 7 | "background_color": "#ffff00", 8 | "description": "Aurora inverter web app.", 9 | "orientation": "portrait", 10 | 11 | "gcm_sender_id": "123456789", 12 | "gcm_user_visible_only": true, 13 | "permissions": [ 14 | "gcm" 15 | ], 16 | 17 | "icons": [{ 18 | "src": "launcher-icon-0x.png", 19 | "sizes": "48x48", 20 | "type": "image/png" 21 | }, { 22 | "src": "launcher-icon-1x.png", 23 | "sizes": "72x72", 24 | "type": "image/png" 25 | }, { 26 | "src": "launcher-icon-2x.png", 27 | "sizes": "96x96", 28 | "type": "image/png" 29 | }, { 30 | "src": "launcher-icon-3x.png", 31 | "sizes": "144x144", 32 | "type": "image/png" 33 | }, { 34 | "src": "launcher-icon-4x.png", 35 | "sizes": "192x192", 36 | "type": "image/png" 37 | }, { 38 | "src": "launcher-icon-5x.png", 39 | "sizes": "512x512", 40 | "type": "image/png" 41 | }] 42 | } -------------------------------------------------------------------------------- /data/service-worker.js: -------------------------------------------------------------------------------- 1 | // Set this to true for production 2 | const doCache = false; 3 | 4 | // Name our cache 5 | const CACHE_NAME = 'aurora-cache-v1'; 6 | 7 | // Delete old caches that are not our current one! 8 | self.addEventListener('activate', (event) => { 9 | const cacheWhitelist = [CACHE_NAME]; 10 | event.waitUntil( 11 | caches.keys() 12 | .then(keyList => Promise.all(keyList.map((key) => { 13 | if (!cacheWhitelist.includes(key)) { 14 | console.log(`Deleting cache: ${key}`); 15 | return caches.delete(key); 16 | } 17 | }))) 18 | ); 19 | }); 20 | 21 | // The first time the user starts up the PWA, 'install' is triggered. 22 | self.addEventListener('install', (event) => { 23 | if (doCache) { 24 | event.waitUntil( 25 | caches.open(CACHE_NAME) 26 | .then((cache) => { 27 | // Get the assets manifest so we can see what our js file is named 28 | // This is because webpack hashes it 29 | fetch('asset-manifest.json') 30 | .then((response) => { 31 | response.json(); 32 | }) 33 | .then((assets) => { 34 | // Open a cache and cache our files 35 | // We want to cache the page and the main.js generated by webpack 36 | // We could also cache any static assets like CSS or images 37 | const urlsToCache = [ 38 | '/', 39 | // assets["../aurora-web.js"] 40 | // ,assets["main.js"] 41 | ]; 42 | cache.addAll(urlsToCache); 43 | console.log('cached'); 44 | }); 45 | }) 46 | ); 47 | } 48 | }); 49 | 50 | // When the webpage goes to fetch files, we intercept that request and serve up the matching files 51 | // if we have them 52 | self.addEventListener('fetch', (event) => { 53 | if (doCache) { 54 | event.respondWith( 55 | caches.match(event.request).then(response => response || fetch(event.request)) 56 | ); 57 | } 58 | }); 59 | 60 | self.addEventListener('install', () => self.skipWaiting()); 61 | self.addEventListener('activate', () => self.clients.claim()); 62 | -------------------------------------------------------------------------------- /firmware-release/Aurora_Web_Inverter_Monitor.d1_mini.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/firmware-release/Aurora_Web_Inverter_Monitor.d1_mini.bin -------------------------------------------------------------------------------- /firmware-release/Aurora_Web_Inverter_Monitor.spiffs.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/firmware-release/Aurora_Web_Inverter_Monitor.spiffs.bin -------------------------------------------------------------------------------- /firmware-release/Aurora_Web_Inverter_Monitor_DEBUG.d1_mini.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/firmware-release/Aurora_Web_Inverter_Monitor_DEBUG.d1_mini.bin -------------------------------------------------------------------------------- /firmware-release/Aurora_Web_Inverter_Monitor_DEBUG_FTP.d1_mini.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xreef/Aurora_Web_Invert_Monitor/778a43b4bd160302585207c59b53e59307f98682/firmware-release/Aurora_Web_Inverter_Monitor_DEBUG_FTP.d1_mini.bin -------------------------------------------------------------------------------- /firmware-release/README.md: -------------------------------------------------------------------------------- 1 | Aurora_Web_Inverter_Monitor_DEBUG_FTP.d1_mini.bin 2 | Firmware with FTP server to upload filesystem and DEBUG active 3 | 4 | Aurora_Web_Inverter_Monitor_DEBUG.d1_mini.bin 5 | Firmware with DEBUG active 6 | 7 | Aurora_Web_Inverter_Monitor.d1_mini.bin 8 | Firmware standard 9 | 10 | Aurora_Web_Inverter_Monitor.spiffs.bin 11 | Filesystem in binary 12 | 13 | Follow the guide on my site to upload with a GUI 14 | www.mischianti.org 15 | 16 | esptool --port /dev/COM24 write_flash -fm dio 0x00000 Aurora_Web_Inverter_Monitor.d1_mini.bin 17 | 18 | esptool --port /dev/COM24 write_flash -fm dio 0x10000 Aurora_Web_Inverter_Monitor.spiffs.bin 19 | -------------------------------------------------------------------------------- /spec.d: -------------------------------------------------------------------------------- 1 | spec.o: \ 2 | D:/Projects/Arduino/sloeber-workspace-aurora-new/.metadata/.plugins/org.eclipse.cdt.managedbuilder.core/spec.cpp 3 | --------------------------------------------------------------------------------