├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── binary └── src.ino.generic20210505.bin ├── images └── drawing.png └── src ├── CircularBuffer.h ├── Pages.ino ├── Settings.h ├── TickCounter.h ├── WifiState.ino ├── inverter.cpp ├── inverter.h ├── main.h └── src.ino /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: daromer2 4 | patreon: diytech 5 | 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | images/Thumbs.db 3 | .vscode/c_cpp_properties.json 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InverterOfThings Logger (FORK and total rebuild) 2 | ESP8266 based WiFi interface for Voltronic Axpert MppSolar PIP inverters 3 | 4 | 5 | **Note2 that all below info is from the forked project and in short this will be updated when all is refactored and perhaps working....** 6 | 7 | # Overview 8 | Based around an ESP8266 WiFi microcontroller. 9 | Software is built using [Arduino for ESP8266](https://github.com/esp8266/Arduino). It's the simplest way to get the toolchain. 10 | 11 | **PROGRAMMING:** You need to program it before hooking it up to the TTL board! Im using HW Serial since its so buggy using Software Serial and Wifi reliable so its not even worth it. 12 | 13 | **Alternate PROGRAMMING:** I have now included a BIN file that you just can flash. Nice right?!?! 14 | 15 | **UART0:** Talks to the inverter at 2400 baud and for programming. DISCONNECT RX before programming 16 | 17 | **UART1:** Used for debugging only. Just connect another serie adaptor to TX. 18 | 19 | 20 | **GPIO:** NOT IN USE Button on GPIO0, changes wifi mode, also used for programming when DTR pin isn't available. 21 | 22 | 23 | **POWER:** Using a DC/DC or USB power currently. My gear dont have any power on the serial port 24 | 25 | **WIFI:** The system defaults to AP mode on first setup. Surf to "SetSol" AP and then surf to http://192.168.4.1/ On this page configure WIFI and MQTT. Then you need to reboot the device either by the reboot botton or hard reboot. No changes are applied properly before reboot 26 | 27 | 28 | # Parts required to build one 29 | 30 | Most of the parts can be bought as modules, it's usually cheaper that way. 31 | 32 | Approximate prices found on Ebay 33 | - ESP8266 - Wemos D1 Mini - https://ebay.to/3660Nsy - ($2.66) 34 | - MAX3232 module ($0.70) - it's just a max3232 with 5 capacitors on a tiny little board - https://ebay.to/3la4G45 35 | - DC-DC buck module ($1.50) - 12-80v down to 5v https://ebay.to/3lbf3V7 36 | - LEDs ($0.50) - The leds are for status etc 37 | 38 | Total cost: $5 39 | 40 | Links above are affiliated and give me a tiny commission if used. 41 | -------------------------------------------------------------------------------- /binary/src.ino.generic20210505.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/InverterOfThings/ac904c5e9fb3c2e42826bf8ceac90d2d64e312e1/binary/src.ino.generic20210505.bin -------------------------------------------------------------------------------- /images/drawing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daromer2/InverterOfThings/ac904c5e9fb3c2e42826bf8ceac90d2d64e312e1/images/drawing.png -------------------------------------------------------------------------------- /src/CircularBuffer.h: -------------------------------------------------------------------------------- 1 | /* EspSoftSerial receiver 2 | Scott Day 2015 3 | github.com/scottwday/EspSoftSerial 4 | */ 5 | 6 | #ifndef CIRCULARBUFFER_H 7 | #define CIRCULARBUFFER_H 8 | 9 | template 10 | class CircularBuffer 11 | { 12 | private: 13 | 14 | const unsigned int _capacity = LEN; 15 | 16 | //Index of the oldest element 17 | unsigned int _out = 0; 18 | //The next empty index we can write to 19 | unsigned int _nextIn = 0; 20 | T _buffer[LEN]; 21 | 22 | public: 23 | 24 | void reset() 25 | { 26 | _nextIn = 0; 27 | _out = 0; 28 | } 29 | 30 | bool write(T value) 31 | { 32 | _buffer[_nextIn] = value; 33 | _nextIn = (_nextIn+1) % LEN; 34 | 35 | //If we've overwritten the first byte then move up the out value to keep overwriting the oldest stuff 36 | if (_nextIn == _out) 37 | _out = (_out+1) % LEN; 38 | } 39 | 40 | bool read(T& value) 41 | { 42 | if (_nextIn == _out) 43 | return false; 44 | 45 | value = _buffer[_out]; 46 | _out = (_out+1) % LEN; 47 | return true; 48 | } 49 | 50 | T read() 51 | { 52 | if (_nextIn == _out) 53 | return 0; 54 | 55 | T value = _buffer[_out]; 56 | _out = (_out+1) % LEN; 57 | 58 | return value; 59 | } 60 | 61 | unsigned int size() 62 | { 63 | unsigned int len = (_nextIn - _out) & 0x7FFF; 64 | } 65 | }; 66 | 67 | #endif 68 | -------------------------------------------------------------------------------- /src/Pages.ino: -------------------------------------------------------------------------------- 1 | //Pages: Holds pages to serve via HTTP 2 | 3 | String getNextToken(String& s, int& offset) 4 | { 5 | char c; 6 | String result = ""; 7 | 8 | do 9 | { 10 | c = s[offset]; 11 | ++offset; 12 | 13 | if ((c != 0) && (c != '&') && (c != '?') && (c != ' ') && (c != '\r') && (c != '\n')) 14 | { 15 | result += c; 16 | } 17 | else 18 | { 19 | return result; 20 | } 21 | 22 | } while(offset < s.length()); 23 | 24 | return result; 25 | } 26 | 27 | void appendHttp200(String& s) 28 | { 29 | s += F("HTTP/1.1 200 OK\r\n"); 30 | s += F("Content-Type: text/html\r\n\r\n"); 31 | s += F("\r\n\r\n"); 32 | } 33 | 34 | void serve404(WiFiClient& client) 35 | { 36 | String s = F("HTTP/1.1 404 Not Found\r\n"); 37 | s += F("Content-Type: text/html\r\n\r\n"); 38 | s += F("\r\n\r\n"); 39 | s += F("Page not found"); 40 | s += F(""); 41 | 42 | client.print(s); 43 | delay(1); 44 | 45 | } 46 | 47 | //Wifi setup, requests AP list via AJAX 48 | void serveWifiSetupPage(WiFiClient& client) 49 | { 50 | String s = ""; 51 | appendHttp200(s); 52 | 53 | //This thing was automatically generated from html source 54 | s += F("

Wifi Setup

\r\n
Searching for networks...
\r\n\r\n\r\n"); 55 | s += F(""); 65 | 66 | client.print(s); 67 | delay(1); 68 | 69 | } 70 | 71 | //AJAX reply with list of APs 72 | void serveWifiApList(WiFiClient& client) 73 | { 74 | String s = ""; 75 | 76 | s += F("HTTP/1.1 200 OK\r\n"); 77 | s += F("Content-Type: application/json; charset=utf-8\r\n"); 78 | s += F("Access-Control-Allow-Origin: *\r\n\r\n"); 79 | 80 | s += "["; 81 | int8_t n = WiFi.scanNetworks(); 82 | for (int8_t i=0; i 0) 110 | { 111 | Serial1.println(index0); 112 | int index1 = req.indexOf("&pass=", index0); 113 | if (index1 > 0) 114 | { 115 | Serial1.println(index1); 116 | int index2 = req.indexOf(" HTTP/", index1); 117 | if (index2 == -1) 118 | index2 = req.length(); 119 | String ssid = req.substring(index0+6, index1); 120 | String pass = req.substring(index1+6, index2); 121 | 122 | Serial1.println(index2); 123 | Serial1.println(ssid); 124 | Serial1.println(pass); 125 | 126 | _settings._wifiSsid = ssid; 127 | _settings._wifiPass = pass; 128 | _settings.save(); 129 | 130 | s += F("Set access point to "); 131 | s += ssid; 132 | s += ":"; 133 | s += pass; 134 | s += F("
Switching to client mode, this connection has now closed. Hopefully you'll find me again on "); 135 | s += F("

Back
"); 136 | s += ssid; 137 | 138 | Serial1.println(s); 139 | requestApMode = WIFI_STA; 140 | clientReconnect = true; 141 | delay(1); 142 | //ESP.restart(); 143 | } 144 | } 145 | 146 | 147 | //This thing was automatically generated from html source 148 | s += F(""); 149 | s += F(""); 150 | 151 | client.print(s); 152 | delay(1); 153 | 154 | } 155 | 156 | 157 | 158 | //MQTT and device name 159 | void serveMqtt(WiFiClient& client, String req) 160 | { 161 | String s = ""; 162 | appendHttp200(s); 163 | 164 | //This thing was automatically generated from html source 165 | s += F("

MQTT server & device name

\r\n\r\n"); 166 | 167 | s += F("
"); 168 | s += F(""); 169 | s += F(""); 170 | s += F(""); 175 | s += F(""); 180 | 181 | s += F(""); 184 | 185 | s += F(""); 188 | 189 | s += F(""); 190 | 191 | s += F(""); 194 | 195 | s += F(""); 196 | s += F("
SettingValue Key
Mqtt Server
Mqtt User/pass
Device type (MPI, PCM, PIP)
Device Name

Update rateseconds
"); 197 | s += F("\r\n\r\n"); 198 | client.print(s); 199 | delay(1); 200 | 201 | } 202 | 203 | bool setStringIfStartsWith(String& s, String startswith, String& set) 204 | { 205 | /*Serial1.print(" checking if "); 206 | Serial1.print(s); 207 | Serial1.print(" startswith "); 208 | Serial1.println(startswith);*/ 209 | 210 | if (s.startsWith(startswith)) 211 | { 212 | set = s.substring(startswith.length()); 213 | Serial1.print("match >"); 214 | Serial1.print(startswith); 215 | Serial1.print("< = >"); 216 | Serial1.print(set); 217 | Serial1.println("<"); 218 | 219 | return true; 220 | } 221 | return false; 222 | } 223 | 224 | //Apply mqtt settings 225 | void serveSetMqtt(WiFiClient& client, String req) 226 | { 227 | String s = ""; 228 | 229 | Serial1.println("Setting MQTT & Device keys"); 230 | Serial1.println(req); 231 | 232 | int offset = 0; 233 | String token = getNextToken(req, offset); 234 | 235 | while (token.length()) 236 | { 237 | setStringIfStartsWith(token, "w=", _settings._mqttServer); 238 | //setStringIfStartsWith(token, "r=", _settings._mqttPort); 239 | setStringIfStartsWith(token, "t=", _settings._deviceType); 240 | setStringIfStartsWith(token, "n=", _settings._deviceName); 241 | setStringIfStartsWith(token, "u=", _settings._mqttUser); 242 | setStringIfStartsWith(token, "p=", _settings._mqttPassword); 243 | if (setStringIfStartsWith(token, "r=", s)) 244 | _settings._mqttPort = (short)s.toInt(); 245 | 246 | token = getNextToken(req, offset); 247 | } 248 | 249 | _settings.save(); 250 | 251 | s = ""; 252 | appendHttp200(s); 253 | s += F("Saved"); 254 | s += F("

Back
"); 255 | 256 | s += F(""); 257 | 258 | client.print(s); 259 | delay(1); 260 | 261 | } 262 | 263 | void servePage(WiFiClient& client, String req) 264 | { 265 | String s = ""; 266 | appendHttp200(s); 267 | String ip = WiFi.localIP().toString(); 268 | s += F("

Solar2MQTT

\r\n\r\n"); 269 | s += F("Configure MQTT
"); 270 | s += F("Configure Wifi


"); 271 | //s += ("Devicename:"+ _settings._deviceName +"
"); 272 | //s += ("Type:"+ _settings._deviceType +"
"); 273 | //s += F("IP:"+ String(ip) +"
"); 274 | 275 | s += F("

Reboot device
"); 276 | 277 | s += F("




By Daromer aka Diy Tech & Repairs 2020
"); 278 | 279 | /*int offset = 0; 280 | String token = getNextToken(req, offset); 281 | while (token.length()) 282 | { 283 | setStringIfStartsWith(token, "w=", _settings._mqttServer); 284 | setStringIfStartsWith(token, "r=", _settings._mqttPort); 285 | setStringIfStartsWith(token, "t=", _settings._deviceType); 286 | setStringIfStartsWith(token, "n=", _settings._deviceName); 287 | setStringIfStartsWith(token, "u=", _settings._mqttUser); 288 | setStringIfStartsWith(token, "p=", _settings._mqttPassword); 289 | if (setStringIfStartsWith(token, "i=", s)) 290 | _settings._updateRateSec = (short)s.toInt(); 291 | 292 | token = getNextToken(req, offset); 293 | }*/ 294 | 295 | s += F("\r\n\r\n"); 296 | s += F(""); 297 | 298 | client.print(s); 299 | delay(1); 300 | } 301 | -------------------------------------------------------------------------------- /src/Settings.h: -------------------------------------------------------------------------------- 1 | //Settings: Stores persistant settings, loads and saves to EEPROM 2 | 3 | #include 4 | #include 5 | 6 | class Settings 7 | { 8 | public: 9 | 10 | bool _valid = false; 11 | String _wifiSsid = ""; 12 | String _wifiPass = ""; 13 | 14 | String _deviceType = ""; 15 | String _deviceName = ""; 16 | String _mqttServer = ""; 17 | //short _mqttPort = "1883"; 18 | String _mqttUser = ""; 19 | String _mqttPassword = ""; 20 | short _mqttPort = 30; 21 | 22 | short readShort(int offset) 23 | { 24 | byte b1 = EEPROM.read(offset + 0); 25 | byte b2 = EEPROM.read(offset + 1); 26 | return ((short)b1 << 8) | b2; 27 | } 28 | 29 | void writeShort(short value, int offset) 30 | { 31 | byte b1 = (byte)((value >> 8) & 0xFF); 32 | byte b2 = (byte)((value >> 0) & 0xFF); 33 | 34 | EEPROM.write(offset + 0, b1); 35 | EEPROM.write(offset + 1, b2); 36 | } 37 | 38 | void readString(String& s, int maxLen, int offset) 39 | { 40 | int i; 41 | s = ""; 42 | for (i=0; igetMillis(); 48 | } 49 | 50 | void reset() 51 | { 52 | _startMillis = _tickCounter->getMillis(); 53 | } 54 | 55 | //Call this once every 26 seconds or it'll roll over 56 | int compare(unsigned int millisSinceStart) 57 | { 58 | return (int)( _tickCounter->getMillis() - (_startMillis + millisSinceStart) ); 59 | } 60 | }; 61 | 62 | #endif 63 | -------------------------------------------------------------------------------- /src/WifiState.ino: -------------------------------------------------------------------------------- 1 | 2 | //---------------------------------------------------------------------- 3 | // Configure wifi as access point to allow client config 4 | void setupWifiAp() 5 | { 6 | WiFi.mode(WIFI_AP); 7 | WiFi.disconnect(); 8 | WiFi.softAP(ApSsid); 9 | } 10 | 11 | //---------------------------------------------------------------------- 12 | void setupWifiStation() 13 | { 14 | WiFi.disconnect(); 15 | delay(20); 16 | if (_settings._wifiSsid.length() == 0) 17 | { 18 | Serial1.println(F("No client SSID set, switching to AP")); 19 | WiFi.mode(WIFI_AP); 20 | WiFi.softAP(ApSsid); 21 | } 22 | else 23 | { 24 | Serial1.print(F("Connecting to ")); 25 | Serial1.print(_settings._wifiSsid); 26 | Serial1.print(":"); 27 | Serial1.println(_settings._wifiPass); 28 | WiFi.mode(WIFI_STA); 29 | if (_settings._deviceName.length() > 0) 30 | WiFi.hostname("ESP-" + _settings._deviceName); 31 | WiFi.begin(_settings._wifiSsid.c_str(), _settings._wifiPass.c_str()); 32 | } 33 | } 34 | 35 | //---------------------------------------------------------------------- 36 | void serviceWifiMode() 37 | { 38 | if (clientReconnect) 39 | { 40 | WiFi.disconnect(); 41 | delay(10); 42 | clientReconnect = false; 43 | currentApMode = CLIENT_NOTCONNECTED; 44 | } 45 | 46 | if (currentApMode != requestApMode) 47 | { 48 | if (requestApMode == WIFI_AP) 49 | { 50 | Serial1.println("Access Point Mode"); 51 | setupWifiAp(); 52 | currentApMode = WIFI_AP; 53 | } 54 | 55 | if (requestApMode == WIFI_STA) 56 | { 57 | Serial1.println("Station Mode"); 58 | setupWifiStation(); 59 | currentApMode = WIFI_STA; 60 | clientConnectionState = CLIENT_CONNECTING; 61 | } 62 | } 63 | 64 | if (clientConnectionState == CLIENT_CONNECTING) 65 | { 66 | Serial1.print(F("c:")); 67 | Serial1.println(WIFI_COUNT); 68 | WIFI_COUNT++; 69 | delay(40); // Else it will restart way to quickly. 70 | if (WIFI_COUNT > 500) { 71 | WIFI_COUNT=0; 72 | ESP.restart(); 73 | } 74 | if (WiFi.status() == WL_CONNECTED) 75 | { 76 | clientConnectionState = CLIENT_CONNECTED; 77 | WIFI_COUNT = 0; 78 | Serial1.print(F("IP address: ")); 79 | Serial1.println(WiFi.localIP()); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/inverter.cpp: -------------------------------------------------------------------------------- 1 | #include "Arduino.h" 2 | #include "main.h" 3 | #include "inverter.h" 4 | #include "tickCounter.h" 5 | 6 | #include "settings.h" 7 | 8 | extern TickCounter _tickCounter; 9 | extern Settings _settings; 10 | extern byte inverterType; 11 | extern byte MPI; 12 | extern byte PCM; 13 | extern byte PIP; 14 | extern bool crcCheck; 15 | extern int Led_Red; 16 | extern int Led_Green; 17 | 18 | String _setCommand; 19 | String _commandBuffer; 20 | String _otherBuffer =""; 21 | String _lastRequestedCommand = "-"; //Set to not empty to force a timeout on startup 22 | PollDelay _lastRequestedAt(_tickCounter); 23 | String _nextCommandNeeded = ""; 24 | String _rawCommand = ""; 25 | bool _allMessagesUpdated = false; 26 | bool _otherMessagesUpdated = false; 27 | PollDelay _lastReceivedAt(_tickCounter); 28 | 29 | 30 | // PCM and PIP inverters use below 31 | QpiMessage _qpiMessage = {0}; 32 | QpigsMessage _qpigsMessage = {0}; 33 | QmodMessage _qmodMessage = {0}; 34 | QpiwsMessage _qpiwsMessage = {0}; 35 | QflagMessage _qflagMessage = {0}; 36 | QidMessage _qidMessage = {0}; 37 | 38 | 39 | //MPI Inverters use below 40 | P003GSMessage _P003GSMessage = {0}; 41 | P003PSMessage _P003PSMessage = {0}; 42 | P006FPADJMessage _P006FPADJMessage = {0}; 43 | 44 | 45 | 46 | //Found here: http://forums.aeva.asn.au/pip4048ms-inverter_topic4332_post53760.html#53760 47 | #define INT16U unsigned int 48 | #define INT8U byte 49 | unsigned short cal_crc_half(byte* pin, byte len) 50 | { 51 | unsigned short crc; 52 | byte da; 53 | byte *ptr; 54 | byte bCRCHign; 55 | byte bCRCLow; 56 | 57 | const unsigned short crc_ta[16]= 58 | { 59 | 0x0000,0x1021,0x2042,0x3063,0x4084,0x50a5,0x60c6,0x70e7, 60 | 0x8108,0x9129,0xa14a,0xb16b,0xc18c,0xd1ad,0xe1ce,0xf1ef 61 | }; 62 | 63 | ptr=pin; 64 | crc=0; 65 | while(len--!=0) 66 | { 67 | da=((byte)(crc>>8))>>4; 68 | crc<<=4; 69 | crc^=crc_ta[da^(*ptr>>4)]; 70 | da=((byte)(crc>>8))>>4; 71 | crc<<=4; 72 | crc^=crc_ta[da^(*ptr&0x0f)]; 73 | ptr++; 74 | } 75 | bCRCLow = crc; 76 | bCRCHign= (byte)(crc>>8); 77 | if(bCRCLow==0x28||bCRCLow==0x0d||bCRCLow==0x0a) 78 | { 79 | bCRCLow++; 80 | } 81 | if(bCRCHign==0x28||bCRCHign==0x0d||bCRCHign==0x0a) 82 | { 83 | bCRCHign++; 84 | } 85 | crc = ((unsigned short)bCRCHign)<<8; 86 | crc += bCRCLow; 87 | return(crc); 88 | } 89 | 90 | 91 | 92 | //Parses out the next number in the command string, starting at index 93 | //updates index as it goes 94 | float getNextFloat(String& command, int& index) 95 | { 96 | String term = ""; 97 | while (index < command.length()) 98 | { 99 | char c = command[index]; 100 | ++index; 101 | 102 | if ((c == '.') || (c == '+') || (c == '-') || ((c >= '0') && (c <= '9'))) 103 | { 104 | term += c; 105 | } 106 | else 107 | { 108 | return term.toFloat(); 109 | } 110 | } 111 | return 0; 112 | } 113 | 114 | //Parses out the next number in the command string, starting at index 115 | //updates index as it goes 116 | long getNextLong(String& command, int& index) 117 | { 118 | String term = ""; 119 | while (index < command.length()) 120 | { 121 | char c = command[index]; 122 | ++index; 123 | 124 | if ((c == '.') || ((c >= '0') && (c <= '9'))) 125 | { 126 | term += c; 127 | } 128 | else 129 | { 130 | return term.toInt(); 131 | } 132 | } 133 | return 0; 134 | } 135 | 136 | //Gets if the next character is '1' 137 | bool getNextBit(String& command, int& index) 138 | { 139 | String term = ""; 140 | if (index < command.length()) 141 | { 142 | char c = command[index]; 143 | ++index; 144 | return c == '1'; 145 | } 146 | return false; 147 | } 148 | 149 | bool onP003PS() 150 | { 151 | //P003PS -- '81'^D07700139,00122,,,,,,0502,0973,0385,01860,0593,1040,0399,02032,031,1,1,1,1,2,1}⸮' 152 | Serial1.print("Processing data from: "); 153 | Serial1.println(_lastRequestedCommand); 154 | 155 | if (_commandBuffer.length() < 81) 156 | return false; 157 | int index = 5; //after the starting 'commands' 158 | _P003PSMessage.rxTimeSec = _tickCounter.getSeconds(); 159 | _P003PSMessage.solarWatt1 = (float)getNextFloat(_commandBuffer, index)/10; 160 | _P003PSMessage.solarWatt2 = (float)getNextFloat(_commandBuffer, index)/10; 161 | _P003PSMessage.batteryWatt = (float)getNextFloat(_commandBuffer, index)/10; 162 | _P003PSMessage.acin2_r = (float)getNextFloat(_commandBuffer, index)/10; 163 | _P003PSMessage.acin2_s = (float)getNextFloat(_commandBuffer, index)/10; 164 | _P003PSMessage.acin2_t = (float)getNextFloat(_commandBuffer, index)/10; 165 | _P003PSMessage.acin2_total = (float)getNextFloat(_commandBuffer, index)/10; 166 | 167 | _P003PSMessage.w_r = (float)getNextFloat(_commandBuffer, index)/10; 168 | _P003PSMessage.w_s = (float)getNextFloat(_commandBuffer, index)/10; 169 | _P003PSMessage.w_t = (float)getNextFloat(_commandBuffer, index)/10; 170 | _P003PSMessage.w_total = (float)getNextFloat(_commandBuffer, index)/10; 171 | _P003PSMessage.va_r = (float)getNextFloat(_commandBuffer, index)/10; 172 | _P003PSMessage.va_s = (float)getNextFloat(_commandBuffer, index)/10; 173 | _P003PSMessage.va_t = (float)getNextFloat(_commandBuffer, index)/10; 174 | _P003PSMessage.va_total = (float)getNextFloat(_commandBuffer, index)/10; 175 | _P003PSMessage.ac_output_procent = (short)getNextFloat(_commandBuffer, index); 176 | 177 | return true; 178 | } 179 | 180 | bool onP006FPADJ() 181 | { 182 | //P006FPADJ -- '34'^D0301,0000,1,0099,1,0109,1,0112⸮7' 183 | Serial1.print("Processing data from: "); 184 | Serial1.println(_lastRequestedCommand); 185 | 186 | if (_commandBuffer.length() < 34) 187 | return false; 188 | int index = 5; //after the starting 'commands' 189 | _P006FPADJMessage.dir = (float)getNextFloat(_commandBuffer, index); 190 | _P006FPADJMessage.watt = (float)getNextFloat(_commandBuffer, index); 191 | _P006FPADJMessage.feedingGridDirectionR = (float)getNextFloat(_commandBuffer, index); 192 | _P006FPADJMessage.calibrationWattR = (float)getNextFloat(_commandBuffer, index); 193 | _P006FPADJMessage.feedingGridDirectionS = (float)getNextFloat(_commandBuffer, index); 194 | _P006FPADJMessage.calibrationWattS = (float)getNextFloat(_commandBuffer, index); 195 | _P006FPADJMessage.feedingGridDirectionT = (float)getNextFloat(_commandBuffer, index); 196 | _P006FPADJMessage.calibrationWattT = (float)getNextFloat(_commandBuffer, index); 197 | 198 | return true; 199 | } 200 | 201 | bool onP003GS() 202 | { 203 | //P003GS -- '114'^D1103462,3468,0040,0035,0503,071,+00000,2369,2367,2350,5000,0000,0000,0000,2371,2365,2352,5000,,,,025,028,000,0b' 204 | Serial1.print("Processing data from: "); 205 | Serial1.println(_lastRequestedCommand); 206 | 207 | if (_commandBuffer.length() < 114) 208 | return false; 209 | //P003GS -- '114'^D1103464,3454,0032,0026,0503,071,+00000,2395,2401,2374,5000,0000,0000,0000,2397,2399,2374,5000,,,,025,029,000,0⸮ ' 210 | int index = 5; //after the starting 'commands' 211 | _P003GSMessage.rxTimeSec = _tickCounter.getSeconds(); 212 | _P003GSMessage.solarInputV1 = (float)getNextFloat(_commandBuffer, index)/10; 213 | _P003GSMessage.solarInputV2 = (float)getNextFloat(_commandBuffer, index)/10; 214 | _P003GSMessage.solarInputA1 = (float)getNextFloat(_commandBuffer, index)/100; 215 | _P003GSMessage.solarInputA2 = (float)getNextFloat(_commandBuffer, index)/100; 216 | _P003GSMessage.battV = getNextFloat(_commandBuffer, index)/10; 217 | _P003GSMessage.battCapacity = getNextFloat(_commandBuffer, index); 218 | _P003GSMessage.battA = getNextFloat(_commandBuffer, index)/10; 219 | _P003GSMessage.acInputVoltageR = (float)getNextFloat(_commandBuffer, index)/10; 220 | _P003GSMessage.acInputVoltageS = (float)getNextFloat(_commandBuffer, index)/10; 221 | _P003GSMessage.acInputVoltageT = (float)getNextFloat(_commandBuffer, index)/10; 222 | _P003GSMessage.acInputFrequency = (float)getNextFloat(_commandBuffer, index)/100; 223 | _P003GSMessage.acInputCurrentR = (float)getNextFloat(_commandBuffer, index)/10; 224 | _P003GSMessage.acInputCurrentS = (float)getNextFloat(_commandBuffer, index)/10; 225 | _P003GSMessage.acInputCurrentT = (float)getNextFloat(_commandBuffer, index)/10; 226 | _P003GSMessage.acOutputVoltageR = (float)getNextFloat(_commandBuffer, index)/10; 227 | _P003GSMessage.acOutputVoltageS = (float)getNextFloat(_commandBuffer, index)/10; 228 | _P003GSMessage.acOutputVoltageT = (float)getNextFloat(_commandBuffer, index)/10; 229 | _P003GSMessage.acOutputFrequency = (float)getNextFloat(_commandBuffer, index)/10; 230 | _P003GSMessage.acOutputCurrentR = (float)getNextFloat(_commandBuffer, index)/10; 231 | _P003GSMessage.acOutputCurrentS = (float)getNextFloat(_commandBuffer, index)/10; 232 | _P003GSMessage.acOutputCurrentT = (float)getNextFloat(_commandBuffer, index)/10; 233 | _P003GSMessage.innerTemp = (float)getNextFloat(_commandBuffer, index); 234 | _P003GSMessage.componentTemp = (float)getNextFloat(_commandBuffer, index); 235 | return true; 236 | } 237 | 238 | 239 | 240 | //Parse the response to QPIGS general status message, CRC has already been confirmed 241 | bool onPIGS() 242 | { 243 | Serial1.print("Processing data from: "); 244 | Serial1.println(_lastRequestedCommand); 245 | 246 | if (_commandBuffer.length() < 67) 247 | return false; 248 | 249 | int index = 1; //after the starting '(' 250 | 251 | // PCM and PIP uses same message so lets threat them differently. 252 | if (inverterType == 0) { //PCM 253 | _qpigsMessage.rxTimeSec = _tickCounter.getSeconds(); 254 | _qpigsMessage.solarV = (short)getNextFloat(_commandBuffer, index); 255 | _qpigsMessage.battV = getNextFloat(_commandBuffer, index); 256 | _qpigsMessage.battChargeA = getNextFloat(_commandBuffer, index); 257 | _qpigsMessage.solarA = getNextFloat(_commandBuffer, index); 258 | _qpigsMessage.solar2A = getNextFloat(_commandBuffer, index); 259 | _qpigsMessage.wattage = getNextFloat(_commandBuffer, index); 260 | } 261 | if (inverterType == 2) { //PIP 262 | _qpigsMessage.GridV = getNextFloat(_commandBuffer, index); 263 | _qpigsMessage.GridF = getNextFloat(_commandBuffer, index); 264 | _qpigsMessage.AcV = getNextFloat(_commandBuffer, index); 265 | _qpigsMessage.AcF = getNextFloat(_commandBuffer, index); 266 | _qpigsMessage.AcVA = getNextFloat(_commandBuffer, index); 267 | _qpigsMessage.AcW = getNextFloat(_commandBuffer, index); 268 | _qpigsMessage.Load = getNextFloat(_commandBuffer, index); 269 | _qpigsMessage.BusV = getNextFloat(_commandBuffer, index); 270 | _qpigsMessage.BattV = getNextFloat(_commandBuffer, index); 271 | _qpigsMessage.ChgeA = getNextFloat(_commandBuffer, index); 272 | _qpigsMessage.BattC = getNextFloat(_commandBuffer, index); 273 | _qpigsMessage.Temp = getNextFloat(_commandBuffer, index); 274 | _qpigsMessage.PVbattA = getNextFloat(_commandBuffer, index); 275 | _qpigsMessage.PVV = getNextFloat(_commandBuffer, index); 276 | _qpigsMessage.BattSCC = getNextFloat(_commandBuffer, index); 277 | _qpigsMessage.BattDisA = getNextFloat(_commandBuffer, index); 278 | _qpigsMessage.Stat = getNextFloat(_commandBuffer, index); 279 | _qpigsMessage.BattOffs = getNextFloat(_commandBuffer, index); 280 | _qpigsMessage.Eeprom = getNextFloat(_commandBuffer, index); 281 | _qpigsMessage.PVchW = getNextFloat(_commandBuffer, index); 282 | _qpigsMessage.b10 = getNextFloat(_commandBuffer, index); 283 | _qpigsMessage.b9 = getNextFloat(_commandBuffer, index); 284 | _qpigsMessage.b8 = getNextFloat(_commandBuffer, index); 285 | _qpigsMessage.reserved = getNextFloat(_commandBuffer, index); 286 | } 287 | return true; 288 | } 289 | 290 | //Parse the response to QMOD general status message, CRC has already been confirmed 291 | bool onMOD() 292 | { 293 | Serial1.print(F("QMOD '")); 294 | Serial1.print(_commandBuffer); 295 | Serial1.print(F("'")); 296 | 297 | if (_commandBuffer.length() < 2) 298 | return false; 299 | 300 | _qmodMessage.mode = _commandBuffer[1]; 301 | 302 | return true; 303 | } 304 | 305 | //Parse the response to QMOD general status message, CRC has already been confirmed 306 | bool onPIWS() 307 | { 308 | Serial1.print("Processing data from: "); 309 | Serial1.println(_lastRequestedCommand); 310 | 311 | if (_commandBuffer.length() < 32) 312 | return false; 313 | 314 | int index = 1; //after the starting '(' 315 | _qpiwsMessage.reserved0 = getNextBit(_commandBuffer, index); 316 | _qpiwsMessage.inverterFault = getNextBit(_commandBuffer, index); 317 | _qpiwsMessage.busOver = getNextBit(_commandBuffer, index); 318 | _qpiwsMessage.busUnder = getNextBit(_commandBuffer, index); 319 | _qpiwsMessage.busSoftFail = getNextBit(_commandBuffer, index); 320 | _qpiwsMessage.lineFail = getNextBit(_commandBuffer, index); 321 | _qpiwsMessage.opvShort = getNextBit(_commandBuffer, index); 322 | _qpiwsMessage.overTemperature = getNextBit(_commandBuffer, index); 323 | _qpiwsMessage.fanLocked = getNextBit(_commandBuffer, index); 324 | _qpiwsMessage.batteryVoltageHigh = getNextBit(_commandBuffer, index); 325 | _qpiwsMessage.batteryLowAlarm = getNextBit(_commandBuffer, index); 326 | _qpiwsMessage.reserved13 = getNextBit(_commandBuffer, index); 327 | _qpiwsMessage.batteryUnderShutdown = getNextBit(_commandBuffer, index); 328 | _qpiwsMessage.reserved15 = getNextBit(_commandBuffer, index); 329 | _qpiwsMessage.overload = getNextBit(_commandBuffer, index); 330 | _qpiwsMessage.eepromFault = getNextBit(_commandBuffer, index); 331 | _qpiwsMessage.inverterOverCurrent = getNextBit(_commandBuffer, index); 332 | _qpiwsMessage.inverterSoftFail = getNextBit(_commandBuffer, index); 333 | _qpiwsMessage.selfTestFail = getNextBit(_commandBuffer, index); 334 | _qpiwsMessage.opDcVoltageOver = getNextBit(_commandBuffer, index); 335 | _qpiwsMessage.batOpen = getNextBit(_commandBuffer, index); 336 | _qpiwsMessage.currentSensorFail = getNextBit(_commandBuffer, index); 337 | _qpiwsMessage.batteryShort = getNextBit(_commandBuffer, index); 338 | _qpiwsMessage.powerLimit = getNextBit(_commandBuffer, index); 339 | _qpiwsMessage.pvVoltageHigh = getNextBit(_commandBuffer, index); 340 | _qpiwsMessage.mpptOverloadFault = getNextBit(_commandBuffer, index); 341 | _qpiwsMessage.mpptOverloadWarning = getNextBit(_commandBuffer, index); 342 | _qpiwsMessage.batteryTooLowToCharge = getNextBit(_commandBuffer, index); 343 | _qpiwsMessage.reserved30 = getNextBit(_commandBuffer, index); 344 | _qpiwsMessage.reserved31 = getNextBit(_commandBuffer, index); 345 | 346 | return true; 347 | } 348 | 349 | 350 | //Parse the response to QFLAG flags message, CRC has already been confirmed 351 | bool onFLAG() 352 | { 353 | Serial1.print("Processing data from: "); 354 | Serial1.println(_lastRequestedCommand); 355 | 356 | if (_commandBuffer.length() < 10) 357 | return false; 358 | 359 | int index = 1; //after the starting '(' 360 | _qflagMessage.disableBuzzer = getNextBit(_commandBuffer, index); 361 | _qflagMessage.enableOverloadBypass = getNextBit(_commandBuffer, index); 362 | _qflagMessage.enablePowerSaving = getNextBit(_commandBuffer, index); 363 | _qflagMessage.enableLcdEscape = getNextBit(_commandBuffer, index); 364 | _qflagMessage.enableOverloadRestart = getNextBit(_commandBuffer, index); 365 | _qflagMessage.enableOvertempRestart = getNextBit(_commandBuffer, index); 366 | _qflagMessage.enableBacklight = getNextBit(_commandBuffer, index); 367 | _qflagMessage.enablePrimarySourceInterruptedAlarm = getNextBit(_commandBuffer, index); 368 | _qflagMessage.enableFaultCodeRecording = getNextBit(_commandBuffer, index); 369 | 370 | return true; 371 | } 372 | 373 | //Parse the response to QID device id message, CRC has already been confirmed 374 | bool onID() 375 | { 376 | Serial1.print("Processing data from: "); 377 | Serial1.println(_lastRequestedCommand); 378 | 379 | if (_commandBuffer.length() < 15) 380 | return false; 381 | 382 | //Discard the first '(' 383 | _commandBuffer.substring(1).toCharArray(_qidMessage.id, sizeof(_qidMessage.id)-1); 384 | 385 | return true; 386 | } 387 | 388 | //Parse the response to QPI protocol info message, CRC has already been confirmed 389 | bool onPI() 390 | { 391 | Serial1.print(F("QPI '")); 392 | Serial1.print(_commandBuffer); 393 | Serial1.print(F("'")); 394 | 395 | if (_commandBuffer.length() < 5) 396 | return false; 397 | 398 | //Get number after '(PI' 399 | int index = 3; 400 | _qpiMessage.protocolId = (byte)getNextLong(_commandBuffer, index); 401 | 402 | return true; 403 | } 404 | 405 | //Parse Other commands or so called RAWS 406 | bool onOther() 407 | { 408 | Serial1.print("Processing OTHER data from: "); 409 | Serial1.println(_lastRequestedCommand); 410 | 411 | if (_commandBuffer.length() < 4) 412 | return false; // If its below 4 the response is most likely false 413 | _otherMessagesUpdated = true; 414 | _otherBuffer = _commandBuffer; 415 | return true; 416 | } 417 | 418 | 419 | //Called once a line has been received from the inverter (on CR) 420 | void onInverterCommand() 421 | { 422 | if ((_commandBuffer.length() > 3) ) 423 | { 424 | //&& (_commandBuffer[0] == '(') 425 | unsigned short calculatedCrc = cal_crc_half((byte*)_commandBuffer.c_str(), _commandBuffer.length() - 2); 426 | unsigned short recievedCrc = ((unsigned short)_commandBuffer[_commandBuffer.length()-2] << 8) | 427 | _commandBuffer[_commandBuffer.length()-1]; 428 | 429 | if (crcCheck) { 430 | Serial1.print(F(" Calc: ")); 431 | Serial1.print(calculatedCrc, HEX); 432 | Serial1.print(F(" Rx: ")); 433 | Serial1.println(recievedCrc, HEX); 434 | } 435 | Serial1.print(F("Command sent: ")); 436 | Serial1.print(_lastRequestedCommand); 437 | Serial1.print(F(" Recieved data: ")); 438 | Serial1.println(_commandBuffer); 439 | 440 | //If CRC is okay, parse message and set next message to be requested 441 | if (calculatedCrc == recievedCrc) 442 | { 443 | //MPI 444 | digitalWrite(Led_Red, LOW); //If we got a valid command disable red led 445 | if (_lastRequestedCommand == "P003GS") 446 | { 447 | onP003GS(); 448 | _nextCommandNeeded = "P003PS"; 449 | } 450 | else if (_lastRequestedCommand == "P003PS") 451 | { 452 | onP003PS(); 453 | _nextCommandNeeded = "P006FPADJ"; 454 | } 455 | 456 | else if (_lastRequestedCommand == "P006FPADJ") 457 | { 458 | onP006FPADJ(); 459 | _nextCommandNeeded = ""; 460 | _allMessagesUpdated = true; 461 | } 462 | 463 | // Below for PCM & PIP 464 | else if (_lastRequestedCommand == "QPIGS" && (crcCheck)) 465 | { 466 | digitalWrite(Led_Red, LOW); //IF we got a valid command disable red led 467 | if (onPIGS()) { 468 | _allMessagesUpdated = true; 469 | } 470 | _nextCommandNeeded = ""; 471 | } 472 | 473 | 474 | //Below for PIP 475 | else if (_lastRequestedCommand == "QMOD") 476 | { 477 | onMOD(); 478 | _nextCommandNeeded = "QPIWS"; 479 | } 480 | else if (_lastRequestedCommand == "QPIWS") 481 | { 482 | onPIWS(); 483 | _nextCommandNeeded = "QFLAG"; 484 | } 485 | else if (_lastRequestedCommand == "QFLAG") 486 | { 487 | onFLAG(); 488 | _nextCommandNeeded = "QID"; 489 | } 490 | else if (_lastRequestedCommand == "QID") 491 | { 492 | onID(); 493 | _nextCommandNeeded = ""; 494 | _allMessagesUpdated = true; 495 | } 496 | // *********** ALL OTHER ********** 497 | else { 498 | onOther(); 499 | } 500 | } 501 | } 502 | 503 | _lastReceivedAt.reset(); 504 | _lastRequestedCommand = ""; 505 | } 506 | 507 | 508 | //Parses incoming characters from the serial port 509 | void serviceInverter() 510 | { 511 | byte c; 512 | //Check time since last requested command 513 | if (_lastRequestedAt.compare(INVERTER_COMMAND_TIMEOUT_MS) > 0) 514 | { 515 | digitalWrite(Led_Red, HIGH); //If we timeout and didnt get any response we need to shut the red led 516 | _commandBuffer = ""; 517 | _lastRequestedCommand = ""; 518 | _nextCommandNeeded = ""; 519 | } 520 | 521 | //Wait a bit after receiving the last command before requesting the next one 522 | // Dont send until _allMessagesUpdated is false 523 | if ((_lastRequestedCommand == "") && (_lastReceivedAt.compare(INVERTER_COMMAND_DELAY_MS) > 0) && (!_allMessagesUpdated)) 524 | { 525 | if (_nextCommandNeeded == "") { 526 | if (inverterType == 1) _nextCommandNeeded = "P003GS"; //IF MPI we start with that order 527 | else _nextCommandNeeded = "QPIGS"; //if PIP/PCM 528 | } 529 | 530 | if (_setCommand.length()) { // Raw command incomming. Need to process it as next command 531 | _nextCommandNeeded = _setCommand; 532 | } 533 | 534 | unsigned short crc = cal_crc_half((byte*)_nextCommandNeeded.c_str(), _nextCommandNeeded.length()); 535 | 536 | _lastRequestedCommand = _nextCommandNeeded; 537 | _lastRequestedAt.reset(); 538 | Serial1.print(F("Sent Command: ")); 539 | if (inverterType) { Serial.print("^"); Serial1.print("^");} //If MPI then prechar is needed 540 | Serial.print(_nextCommandNeeded); 541 | Serial1.println(_nextCommandNeeded); 542 | if (crcCheck) Serial.print((char)((crc >> 8) & 0xFF)); //ONLY CRC fo PCM/PIP 543 | if (crcCheck) Serial.print((char)((crc >> 0) & 0xFF)); //ONLY CRC fo PCM/PIP 544 | if (crcCheck) Serial1.println(F("Doing the CRC")); 545 | Serial.print("\r"); 546 | 547 | if (_setCommand.length()) { _nextCommandNeeded = ""; _setCommand = ""; } // If it was RAW command reset it. 548 | } 549 | 550 | while (Serial.available() > 0) 551 | { 552 | c = Serial.read(); 553 | //Only accept incoming characters if we've requested something 554 | if (_lastRequestedCommand != "") 555 | { 556 | if ((c != 0) && (c != '\r') && (c != '\n')) 557 | { 558 | if (_commandBuffer.length() < 255) 559 | _commandBuffer += (char)c; 560 | } 561 | else if ((c == '\r') || (c == '\n')) 562 | { 563 | onInverterCommand(); 564 | _commandBuffer = ""; 565 | } 566 | } 567 | } 568 | } 569 | -------------------------------------------------------------------------------- /src/inverter.h: -------------------------------------------------------------------------------- 1 | #ifndef INVERTER_H 2 | #define INVERTER_H 3 | 4 | #include 5 | 6 | #define INVERTER_COMMAND_TIMEOUT_MS 5000 7 | #define INVERTER_COMMAND_DELAY_MS 500 8 | 9 | //Send and receive periodic inverter commands 10 | void serviceInverter(); 11 | 12 | struct QpiMessage 13 | { 14 | byte protocolId; 15 | }; 16 | 17 | struct P003PSMessage 18 | { 19 | unsigned long rxTimeSec; 20 | float solarWatt1; 21 | float solarWatt2; 22 | float batteryWatt; 23 | float acin2_r; 24 | float acin2_s; 25 | float acin2_t; 26 | float acin2_total; 27 | float w_r; 28 | float w_s; 29 | float w_t; 30 | float w_total; 31 | float va_r; 32 | float va_s; 33 | float va_t; 34 | float va_total; 35 | float ac_output_procent; 36 | }; 37 | 38 | struct P006FPADJMessage 39 | { //-- '34'^D0301,0000,1,0099,1,0109,1,0112⸮7' 40 | unsigned long rxTimeSec; 41 | float dir; 42 | float watt; 43 | float feedingGridDirectionR; 44 | float calibrationWattR; 45 | float feedingGridDirectionS; 46 | float calibrationWattS; 47 | float feedingGridDirectionT; 48 | float calibrationWattT; 49 | }; 50 | 51 | struct P003GSMessage 52 | { 53 | unsigned long rxTimeSec; 54 | float solarInputV1; 55 | float solarInputV2; 56 | float solarInputA1; 57 | float solarInputA2; 58 | float battV; 59 | float battCapacity; 60 | float battA; 61 | float acInputVoltageR; 62 | float acInputVoltageS; 63 | float acInputVoltageT; 64 | float acInputFrequency; 65 | float acInputCurrentR; 66 | float acInputCurrentS; 67 | float acInputCurrentT; 68 | float acOutputVoltageR; 69 | float acOutputVoltageS; 70 | float acOutputVoltageT; 71 | float acOutputFrequency; 72 | float acOutputCurrentR; 73 | float acOutputCurrentS; 74 | float acOutputCurrentT; 75 | float innerTemp; 76 | float componentTemp; 77 | }; 78 | 79 | 80 | 81 | struct QpigsMessage 82 | { 83 | unsigned long rxTimeSec; 84 | //087.6 51.18 08.81 04.71 04.10 0450 +034 00.05 -030 0000 11000000O⸮' 85 | // First is for PCM since both PCM and PIP uses same message name 86 | float solarV; 87 | float battV; 88 | float battChargeA; 89 | float solarA; 90 | float solar2A; 91 | float wattage; 92 | 93 | // Below is for PIP 94 | float GridV; 95 | float GridF; 96 | float AcV; 97 | float AcF; 98 | float AcVA; 99 | float AcW; 100 | float Load; 101 | float BusV; 102 | float BattV; 103 | float ChgeA; 104 | float BattC; 105 | float Temp; 106 | float PVbattA; 107 | float PVV; 108 | float BattSCC; 109 | float BattDisA; 110 | float Stat; 111 | float BattOffs; 112 | float Eeprom; 113 | float PVchW; 114 | float b10; 115 | float b9; 116 | float b8; 117 | float reserved; 118 | 119 | }; 120 | 121 | struct QmodMessage 122 | { 123 | char mode; 124 | }; 125 | 126 | struct QpiwsMessage 127 | { 128 | bool reserved0; 129 | bool inverterFault; 130 | bool busOver; 131 | bool busUnder; 132 | bool busSoftFail; 133 | bool lineFail; 134 | bool opvShort; 135 | bool overTemperature; 136 | bool fanLocked; 137 | bool batteryVoltageHigh; 138 | bool batteryLowAlarm; 139 | bool reserved13; 140 | bool batteryUnderShutdown; 141 | bool reserved15; 142 | bool overload; 143 | bool eepromFault; 144 | bool inverterOverCurrent; 145 | bool inverterSoftFail; 146 | bool selfTestFail; 147 | bool opDcVoltageOver; 148 | bool batOpen; 149 | bool currentSensorFail; 150 | bool batteryShort; 151 | bool powerLimit; 152 | bool pvVoltageHigh; 153 | bool mpptOverloadFault; 154 | bool mpptOverloadWarning; 155 | bool batteryTooLowToCharge; 156 | bool reserved30; 157 | bool reserved31; 158 | }; 159 | 160 | struct QflagMessage 161 | { 162 | bool disableBuzzer; 163 | bool enableOverloadBypass; 164 | bool enablePowerSaving; 165 | bool enableLcdEscape; 166 | bool enableOverloadRestart; 167 | bool enableOvertempRestart; 168 | bool enableBacklight; 169 | bool enablePrimarySourceInterruptedAlarm; 170 | bool enableFaultCodeRecording; 171 | }; 172 | 173 | struct QidMessage 174 | { 175 | char id[16]; 176 | }; 177 | 178 | #endif 179 | -------------------------------------------------------------------------------- /src/main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN_H 2 | #define MAIN_H 3 | 4 | //#define ESP8266_CLOCK 80000000 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /src/src.ino: -------------------------------------------------------------------------------- 1 | 2 | /************************************************************************************* 3 | /* ALl credits for some bits from https://github.com/scottwday/InverterOfThings 4 | /* And i have trashed alot of his code, rewritten some. So thanks to him and credits to him. 5 | /* 6 | /* Changes done by Daniel aka Daromer aka DIY Tech & Repairs 2020 7 | /* https://github.com/daromer2/InverterOfThings 8 | /* https://www.youtube.com/channel/UCI6ASwT150rendNc5ytYYrQ? 9 | /*************************************************************************************/ 10 | 11 | 12 | //TODO: 13 | // Clean up webpages 14 | // Fix update timer? 15 | // Rewrite send to MQTT part so it sends json perhaps? 16 | 17 | // Add some code so we can set stuff on the inverters. 18 | // MPI should have the feedToGridCorrection so it can be set via mqtt 19 | 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "uptime_formatter.h" 28 | #include 29 | 30 | #include 31 | #include 32 | 33 | 34 | #include "main.h" 35 | #include "TickCounter.h" 36 | #include "Settings.h" 37 | #include "inverter.h" 38 | 39 | const char ApSsid[] = "SetSolar"; 40 | 41 | 42 | //--------------------------------- Wifi State 43 | #define CLIENT_NOTCONNECTED 0 44 | #define CLIENT_RECONNECT 1 45 | #define CLIENT_CONNECTING 2 46 | #define CLIENT_PRECONNECTED 3 47 | #define CLIENT_CONNECTED 4 48 | byte currentApMode = 0; 49 | byte requestApMode = WIFI_STA; 50 | byte clientConnectionState = CLIENT_NOTCONNECTED; 51 | bool clientReconnect = false; 52 | 53 | //--------------------------------- IP connection 54 | WiFiServer server(80); 55 | WiFiClient client; 56 | Settings _settings; 57 | TickCounter _tickCounter; 58 | int WIFI_COUNT = 0; 59 | 60 | extern QpigsMessage _qpigsMessage; 61 | extern P003GSMessage _P003GSMessage; 62 | extern P003PSMessage _P003PSMessage; 63 | extern P006FPADJMessage _P006FPADJMessage; 64 | extern String _nextCommandNeeded; 65 | extern String _setCommand; 66 | extern String _otherBuffer; 67 | extern String _rawCommand; 68 | 69 | //---------------------- MQTT 70 | PubSubClient mqttclient(client); 71 | 72 | // Interface types that can be used. 73 | const byte MPI = 1; 74 | const byte PCM = 0; 75 | const byte PIP = 2; 76 | bool crcCheck = false; // Default CRC is off. 77 | byte inverterType = MPI; //And defaults in case... 78 | String topic = "solar/"; //Default first part of topic. We will add device ID in setup 79 | String st = ""; 80 | 81 | unsigned long mqtttimer = 0; 82 | extern bool _allMessagesUpdated; 83 | extern bool _otherMessagesUpdated; 84 | //---------- LEDS 85 | int Led_Red = 5; //D1 86 | int Led_Green = 4; //D2 87 | 88 | StaticJsonDocument<300> doc; 89 | //---------------------------------------------------------------------- 90 | void setup() 91 | { 92 | 93 | initHardware(); 94 | _settings.load(); 95 | serviceWifiMode(); 96 | delay(2500); 97 | 98 | server.begin(); 99 | delay(50); 100 | 101 | if (String(_settings._deviceType) == "MPI") { 102 | inverterType = MPI; 103 | } 104 | else if (String(_settings._deviceType) == "PIP"){ 105 | inverterType = PIP; 106 | crcCheck = true; // Enable CRC 107 | } 108 | else { 109 | inverterType = PCM; 110 | crcCheck = true; //Enable CRC 111 | } 112 | 113 | 114 | 115 | //dev = _settings._deviceName.c_str(); 116 | topic = topic + String(_settings._deviceName.c_str()); 117 | 118 | mqttclient.setServer(_settings._mqttServer.c_str(), _settings._mqttPort); 119 | mqttclient.setCallback(callback); 120 | 121 | pinMode(Led_Red, OUTPUT); 122 | pinMode(Led_Green, OUTPUT); 123 | digitalWrite(Led_Red, HIGH); 124 | digitalWrite(Led_Green, LOW); 125 | 126 | /*if (_settings._deviceName.length() > 0) 127 | ArduinoOTA.setHostname(String("ESP-" + _settings._deviceName).c_str()); 128 | ArduinoOTA.onStart([]() { 129 | String type; 130 | if (ArduinoOTA.getCommand() == U_FLASH) 131 | type = "sketch"; 132 | else // U_SPIFFS 133 | type = "filesystem"; 134 | 135 | // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end() 136 | Serial1.println("Start updating " + type); 137 | }); 138 | ArduinoOTA.onEnd([]() { 139 | Serial1.println("\nEnd"); 140 | }); 141 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 142 | Serial1.printf("Progress: %u%%\r", (progress / (total / 100))); 143 | }); 144 | ArduinoOTA.onError([](ota_error_t error) { 145 | Serial1.printf("Error[%u]: ", error); 146 | if (error == OTA_AUTH_ERROR) Serial1.println("Auth Failed"); 147 | else if (error == OTA_BEGIN_ERROR) Serial1.println("Begin Failed"); 148 | else if (error == OTA_CONNECT_ERROR) Serial1.println("Connect Failed"); 149 | else if (error == OTA_RECEIVE_ERROR) Serial1.println("Receive Failed"); 150 | else if (error == OTA_END_ERROR) Serial1.println("End Failed"); 151 | }); 152 | ArduinoOTA.begin();*/ 153 | } 154 | 155 | 156 | 157 | 158 | //---------------------------------------------------------------------- 159 | //---------------------------------------------------------------------- 160 | //---------------------------------------------------------------------- 161 | void loop() 162 | { 163 | delay(10); 164 | 165 | // Make sure wifi is in the right mode 166 | serviceWifiMode(); 167 | if (WiFi.status() == WL_CONNECTED) { //No use going to next step unless WIFI is up and running. 168 | 169 | 170 | //ArduinoOTA.handle(); //Handle any OTA requests DISABLED DUE TO BUG 171 | 172 | //If we have pending data to send send it first! 173 | sendRaw(); 174 | 175 | // Comms with inverter 176 | serviceInverter(); // Check if we recieved data or should send data 177 | sendtoMQTT(); // Update data to MQTT server if we should 178 | 179 | // Check if we have something to read from MQTT 180 | mqttclient.loop(); 181 | } 182 | // Check if a client towards port 80 has connected 183 | WiFiClient client = server.available(); 184 | if (!client) { 185 | return; 186 | } 187 | 188 | // Read the first line of the request 189 | String req = client.readStringUntil('\r'); 190 | Serial1.println(req); 191 | client.flush(); 192 | 193 | if (req.indexOf("/wifi") != -1) { 194 | serveWifiSetupPage(client); 195 | } else if (req.indexOf("/aplist") != -1) { 196 | serveWifiApList(client); 197 | } else if (req.indexOf("/setap") != -1) { 198 | serveWifiSetAp(client, req); 199 | } else if (req.indexOf("/mqtt") != -1) { 200 | serveMqtt(client, req); 201 | } else if (req.indexOf("/setmqtt") != -1) { 202 | serveSetMqtt(client, req); 203 | } else if (req.indexOf("/reboot") != -1) { 204 | serve404(client); 205 | delay(100); 206 | ESP.restart(); 207 | } else { 208 | servePage(client, req); 209 | //serve404(client); 210 | } 211 | client.flush(); 212 | } 213 | 214 | void initHardware() 215 | { 216 | 217 | delay(100); 218 | Wire.begin(4, 5); 219 | 220 | Serial1.begin(115200); // Debugging towards UART1 221 | Serial.begin(2400); // Using UART0 for comm with inverter. IE cant be connected during flashing 222 | 223 | } 224 | 225 | int WifiGetRssiAsQuality(int rssi) // THis part borrowed from Tasmota code 226 | { 227 | int quality = 0; 228 | 229 | if (rssi <= -100) { 230 | quality = 0; 231 | } else if (rssi >= -50) { 232 | quality = 100; 233 | } else { 234 | quality = 2 * (rssi + 100); 235 | } 236 | return quality; 237 | } 238 | 239 | 240 | bool sendtoMQTT() { 241 | if (millis() < (mqtttimer + 3000)) { 242 | return false; 243 | } 244 | mqtttimer = millis(); 245 | if (!mqttclient.connected()) { 246 | if (mqttclient.connect((String("ESP-" +_settings._deviceName)).c_str(), _settings._mqttUser.c_str(), _settings._mqttPassword.c_str() )) { 247 | 248 | Serial1.println(F("Reconnected to MQTT SERVER")); 249 | mqttclient.publish((topic + String("/Info")).c_str(), ("{\"Status\":\"Im alive!\", \"DeviceType\": \"" + _settings._deviceType + "\",\"IP \":\"" + WiFi.localIP().toString() + "\"}" ).c_str()); 250 | mqttclient.subscribe((topic + String("/code")).c_str()); 251 | mqttclient.subscribe((topic + String("/code")).c_str()); 252 | } else { 253 | Serial1.println(F("CANT CONNECT TO MQTT")); 254 | Serial1.println(_settings._mqttUser.c_str()); 255 | digitalWrite(Led_Green, LOW); 256 | //delay(50); 257 | return false; // Exit if we couldnt connect to MQTT brooker 258 | } 259 | } 260 | 261 | mqttclient.publish((topic + String("/uptime")).c_str(), String("{\"human\":\"" + String(uptime_formatter::getUptime()) + "\", \"seconds\":" + String(millis()/1000) + "}").c_str() ); 262 | mqttclient.publish((topic + String("/wifi")).c_str() , (String("{ \"FreeRam\": ") + String(ESP.getFreeHeap()) + String(", \"rssi\": ") + String(WiFi.RSSI()) + String(", \"dbm\": ") + String(WifiGetRssiAsQuality(WiFi.RSSI())) + String("}")).c_str()); 263 | 264 | 265 | 266 | Serial1.print(F("Data sent to MQTT SERver")); 267 | Serial1.print(F(" - up: ")); 268 | Serial1.println(uptime_formatter::getUptime()); 269 | digitalWrite(Led_Green, HIGH); 270 | 271 | 272 | 273 | if (!_allMessagesUpdated) return false; 274 | 275 | _allMessagesUpdated = false; // Lets reset messages and process them 276 | 277 | if (inverterType == PCM) { //PCM 278 | mqttclient.publish((String(topic) + String("/battv")).c_str(), String(_qpigsMessage.battV).c_str()); 279 | mqttclient.publish((String(topic) + String("/solarv")).c_str(), String(_qpigsMessage.solarV).c_str()); 280 | mqttclient.publish((String(topic) + String("/batta")).c_str(), String(_qpigsMessage.battChargeA).c_str()); 281 | mqttclient.publish((String(topic) + String("/wattage")).c_str(), String(_qpigsMessage.wattage).c_str()); 282 | mqttclient.publish((String(topic) + String("/solara")).c_str(), String(_qpigsMessage.solarA).c_str()); 283 | 284 | doc.clear(); 285 | doc["battv"] = _qpigsMessage.battV; 286 | doc["solarv"] = _qpigsMessage.solarV; 287 | doc["batta"] = _qpigsMessage.battChargeA; 288 | doc["wattage"] =_qpigsMessage.wattage; 289 | doc["solara"] = _qpigsMessage.solarA; 290 | st = ""; 291 | serializeJson(doc,st); 292 | mqttclient.publish((String(topic) + String("/status")).c_str(), st.c_str() ); 293 | 294 | 295 | } 296 | if (inverterType == PIP) { //PIP 297 | 298 | mqttclient.publish((String(topic) + String("/GridV")).c_str(), String(_qpigsMessage.GridV).c_str()); 299 | mqttclient.publish((String(topic) + String("/GridF")).c_str(), String(_qpigsMessage.GridF).c_str()); 300 | mqttclient.publish((String(topic) + String("/AcV")).c_str(), String(_qpigsMessage.AcV).c_str()); 301 | mqttclient.publish((String(topic) + String("/AcF")).c_str(), String(_qpigsMessage.AcF).c_str()); 302 | mqttclient.publish((String(topic) + String("/AcVA")).c_str(), String(_qpigsMessage.AcVA).c_str()); 303 | mqttclient.publish((String(topic) + String("/AcW")).c_str(), String(_qpigsMessage.AcW).c_str()); 304 | mqttclient.publish((String(topic) + String("/Load")).c_str(), String(_qpigsMessage.Load).c_str()); 305 | //mqttclient.publish((String(topic) + String("/BusV")).c_str(), String(_qpigsMessage.BusV).c_str()); 306 | mqttclient.publish((String(topic) + String("/BattV")).c_str(), String(_qpigsMessage.BattV).c_str()); 307 | mqttclient.publish((String(topic) + String("/ChgeA")).c_str(), String(_qpigsMessage.ChgeA).c_str()); 308 | mqttclient.publish((String(topic) + String("/BattC")).c_str(), String(_qpigsMessage.BattC).c_str()); 309 | mqttclient.publish((String(topic) + String("/Temp")).c_str(), String(_qpigsMessage.Temp).c_str()); 310 | mqttclient.publish((String(topic) + String("/PVbattA")).c_str(), String(_qpigsMessage.PVbattA).c_str()); 311 | mqttclient.publish((String(topic) + String("/PVV")).c_str(), String(_qpigsMessage.PVV).c_str()); 312 | //mqttclient.publish((String(topic) + String("/BattSCC")).c_str(), String(_qpigsMessage.BattSCC).c_str()); 313 | mqttclient.publish((String(topic) + String("/BattDisA")).c_str(), String(_qpigsMessage.BattDisA).c_str()); 314 | mqttclient.publish((String(topic) + String("/Stat")).c_str(), String(_qpigsMessage.Stat).c_str()); 315 | //mqttclient.publish((String(topic) + String("/BattOffs")).c_str(), String(_qpigsMessage.BattOffs).c_str()); 316 | //mqttclient.publish((String(topic) + String("/Eeprom")).c_str(), String(_qpigsMessage.Eeprom).c_str()); 317 | mqttclient.publish((String(topic) + String("/PVchW")).c_str(), String(_qpigsMessage.PVchW).c_str()); 318 | //mqttclient.publish((String(topic) + String("/b10")).c_str(), String(_qpigsMessage.b10).c_str()); 319 | //mqttclient.publish((String(topic) + String("/b9")).c_str(), String(_qpigsMessage.b9).c_str()); 320 | //mqttclient.publish((String(topic) + String("/b8")).c_str(), String(_qpigsMessage.b8).c_str()); 321 | //mqttclient.publish((String(topic) + String("/reserved")).c_str(), String(_qpigsMessage.reserved).c_str()); 322 | 323 | doc.clear(); 324 | doc["GridV"] = _qpigsMessage.GridV; 325 | doc["GridF"] = _qpigsMessage.GridF; 326 | doc["AcV"] = _qpigsMessage.AcV; 327 | doc["AcF"] =_qpigsMessage.AcF; 328 | doc["AcVA"] = _qpigsMessage.AcVA; 329 | doc["AcW"] = _qpigsMessage.AcW; 330 | doc["Load"] = _qpigsMessage.Load; 331 | //doc["BusV"] = _qpigsMessage.BusV; 332 | doc["BattV"] = _qpigsMessage.BattV; 333 | doc["ChgeA"] = _qpigsMessage.ChgeA; 334 | doc["BattC"] = _qpigsMessage.BattC; 335 | doc["Temp"] = _qpigsMessage.Temp; 336 | doc["PVbattA"] = _qpigsMessage.PVbattA; 337 | doc["PVV"] = _qpigsMessage.PVV; 338 | //doc["BattSCC"] = _qpigsMessage.BattSCC; 339 | doc["BattDisA"] = _qpigsMessage.BattDisA; 340 | doc["Stat"] = _qpigsMessage.Stat; 341 | //doc["BattOffs"] = _qpigsMessage.BattOffs; 342 | //doc["Eeprom"] = _qpigsMessage.Eeprom; 343 | doc["PVchW"] = _qpigsMessage.PVchW; 344 | //doc["b10"] = _qpigsMessage.b10; 345 | //doc["b9"] = _qpigsMessage.b9; 346 | //doc["b8"] = _qpigsMessage.b8; 347 | //doc["reserved"] = _qpigsMessage.reserved; 348 | st = ""; 349 | serializeJson(doc,st); 350 | mqttclient.publish((String(topic) + String("/status")).c_str(), st.c_str() ); 351 | } 352 | 353 | if (inverterType == MPI) { //IF MPI 354 | mqttclient.publish((String(topic) + String("/solar1w")).c_str(), String(_P003GSMessage.solarInputV1*_P003GSMessage.solarInputA1).c_str()); 355 | mqttclient.publish((String(topic) + String("/solar2w")).c_str(), String(_P003GSMessage.solarInputV2*_P003GSMessage.solarInputA2).c_str()); 356 | mqttclient.publish((String(topic) + String("/solarInputV1")).c_str(), String(_P003GSMessage.solarInputV1).c_str()); 357 | mqttclient.publish((String(topic) + String("/solarInputV2")).c_str(), String(_P003GSMessage.solarInputV2).c_str()); 358 | mqttclient.publish((String(topic) + String("/solarInputA1")).c_str(), String(_P003GSMessage.solarInputA1).c_str()); 359 | mqttclient.publish((String(topic) + String("/solarInputA2")).c_str(), String(_P003GSMessage.solarInputA2).c_str()); 360 | mqttclient.publish((String(topic) + String("/battV")).c_str(), String(_P003GSMessage.battV).c_str()); 361 | mqttclient.publish((String(topic) + String("/battA")).c_str(), String(_P003GSMessage.battA).c_str()); 362 | 363 | mqttclient.publish((String(topic) + String("/acInputVoltageR")).c_str(), String(_P003GSMessage.acInputVoltageR).c_str()); 364 | mqttclient.publish((String(topic) + String("/acInputVoltageS")).c_str(), String(_P003GSMessage.acInputVoltageS).c_str()); 365 | mqttclient.publish((String(topic) + String("/acInputVoltageT")).c_str(), String(_P003GSMessage.acInputVoltageT).c_str()); 366 | 367 | mqttclient.publish((String(topic) + String("/acInputCurrentR")).c_str(), String(_P003GSMessage.acInputCurrentR).c_str()); 368 | mqttclient.publish((String(topic) + String("/acInputCurrentS")).c_str(), String(_P003GSMessage.acInputCurrentS).c_str()); 369 | mqttclient.publish((String(topic) + String("/acInputCurrentT")).c_str(), String(_P003GSMessage.acInputCurrentT).c_str()); 370 | 371 | mqttclient.publish((String(topic) + String("/acOutputCurrentR")).c_str(), String(_P003GSMessage.acOutputCurrentR).c_str()); 372 | mqttclient.publish((String(topic) + String("/acOutputCurrentS")).c_str(), String(_P003GSMessage.acOutputCurrentS).c_str()); 373 | mqttclient.publish((String(topic) + String("/acOutputCurrentT")).c_str(), String(_P003GSMessage.acOutputCurrentT).c_str()); 374 | 375 | mqttclient.publish((String(topic) + String("/innerTemp")).c_str(), String(_P003GSMessage.innerTemp).c_str()); 376 | mqttclient.publish((String(topic) + String("/componentTemp")).c_str(), String(_P003GSMessage.componentTemp).c_str()); 377 | 378 | mqttclient.publish((String(topic) + String("/acWattageR")).c_str(), String(_P003PSMessage.w_r).c_str()); 379 | mqttclient.publish((String(topic) + String("/acWattageS")).c_str(), String(_P003PSMessage.w_s).c_str()); 380 | mqttclient.publish((String(topic) + String("/acWattageT")).c_str(), String(_P003PSMessage.w_t).c_str()); 381 | mqttclient.publish((String(topic) + String("/acWattageTotal")).c_str(), String(_P003PSMessage.w_total).c_str()); 382 | mqttclient.publish((String(topic) + String("/ac_output_procent")).c_str(), String(_P003PSMessage.ac_output_procent).c_str()); 383 | 384 | mqttclient.publish((String(topic) + String("/feedingGridDirectionR")).c_str(), String(_P006FPADJMessage.feedingGridDirectionR).c_str()); 385 | mqttclient.publish((String(topic) + String("/calibrationWattR")).c_str(), String(_P006FPADJMessage.calibrationWattR).c_str()); 386 | mqttclient.publish((String(topic) + String("/feedingGridDirectionS")).c_str(), String(_P006FPADJMessage.feedingGridDirectionS).c_str()); 387 | mqttclient.publish((String(topic) + String("/calibrationWattS")).c_str(), String(_P006FPADJMessage.calibrationWattS).c_str()); 388 | mqttclient.publish((String(topic) + String("/feedingGridDirectionT")).c_str(), String(_P006FPADJMessage.feedingGridDirectionT).c_str()); 389 | mqttclient.publish((String(topic) + String("/calibrationWattT")).c_str(), String(_P006FPADJMessage.calibrationWattT).c_str()); 390 | 391 | doc.clear(); 392 | doc["solar1w"] = _P003GSMessage.solarInputV1*_P003GSMessage.solarInputA1; 393 | doc["solar2w"] = _P003GSMessage.solarInputV2*_P003GSMessage.solarInputA2; 394 | doc["solarInputV1"] = _P003GSMessage.solarInputV1; 395 | doc["solarInputV2"] = _P003GSMessage.solarInputV2; 396 | doc["solarInputA1"] = _P003GSMessage.solarInputA1; 397 | doc["solarInputA2"] = _P003GSMessage.solarInputA2; 398 | doc["battV"] = _P003GSMessage.battV; 399 | doc["battA"] = _P003GSMessage.battA; 400 | doc["acInputVoltageR"] = _P003GSMessage.acInputVoltageR; 401 | doc["acInputVoltageS"] = _P003GSMessage.acInputVoltageS; 402 | doc["acInputVoltageT"] = _P003GSMessage.acInputVoltageT; 403 | doc["acInputCurrentR"] = _P003GSMessage.acInputCurrentR; 404 | doc["acInputCurrentS"] = _P003GSMessage.acInputCurrentS; 405 | doc["acInputCurrentT"] = _P003GSMessage.acInputCurrentT; 406 | doc["acOutputCurrentR"] = _P003GSMessage.acOutputCurrentR; 407 | doc["acOutputCurrentS"] = _P003GSMessage.acOutputCurrentS; 408 | doc["acOutputCurrentT"] = _P003GSMessage.acOutputCurrentT; 409 | doc["acWattageR"] = _P003PSMessage.w_r; 410 | doc["acWattageS"] = _P003PSMessage.w_s; 411 | doc["acWattageT"] = _P003PSMessage.w_t; 412 | doc["acWattageTotal"] = _P003PSMessage.w_total; 413 | doc["acOutputProcentage"] = _P003PSMessage.ac_output_procent; 414 | doc["feedingGridDirectionR"] = _P006FPADJMessage.feedingGridDirectionR; 415 | doc["feedingGridDirectionS"] = _P006FPADJMessage.feedingGridDirectionS; 416 | doc["feedingGridDirectionT"] = _P006FPADJMessage.feedingGridDirectionT; 417 | doc["calibrationWattR"] = _P006FPADJMessage.calibrationWattR; 418 | doc["calibrationWattS"] = _P006FPADJMessage.calibrationWattS; 419 | doc["calibrationWattT"] = _P006FPADJMessage.calibrationWattT; 420 | doc["innerTemp"] = _P003GSMessage.innerTemp; 421 | doc["componentTemp"] = _P003GSMessage.componentTemp; 422 | st = ""; 423 | serializeJson(doc,st); 424 | mqttclient.publish((String(topic) + String("/status")).c_str(), st.c_str() ); 425 | } 426 | 427 | return true; 428 | } 429 | 430 | 431 | // Check if we have pending raw messages to send to MQTT. Then send it. 432 | void sendRaw() { 433 | if (_otherMessagesUpdated) { 434 | _otherMessagesUpdated = false; 435 | Serial1.print(F("MQTT RAW SEND: Command sent: ")); 436 | Serial1.print(_rawCommand); 437 | Serial1.print(" Raw data recieved: "); 438 | Serial1.println(_otherBuffer); 439 | mqttclient.publish((String(topic) + String("/debug/recieved")).c_str(), String(_otherBuffer).c_str() ); 440 | 441 | doc.clear(); 442 | doc["sentMessage"] = _rawCommand; 443 | doc["recievedMessage"] = _otherBuffer; 444 | st = ""; 445 | serializeJson(doc,st); 446 | mqttclient.publish((String(topic) + String("/debug/json")).c_str(), st.c_str() ); 447 | _rawCommand = ""; 448 | } 449 | } 450 | /// TESTING MQTT SEND 451 | 452 | void callback(char* top, byte* payload, unsigned int length) { 453 | Serial1.println(F("Callback done")); 454 | if (strcmp(top,"pir1Status")==0){ 455 | // whatever you want for this topic 456 | } 457 | 458 | String st =""; 459 | for (int i = 0; i < length; i++) { 460 | st += String((char)payload[i]); 461 | } 462 | st.trim(); // Remove any whitespaces!! 463 | 464 | 465 | mqttclient.publish((String(topic) + String("/debug/sent")).c_str(), String(st).c_str() ); 466 | Serial1.print(F("Current command: ")); 467 | Serial1.print(_nextCommandNeeded); 468 | Serial1.print(F(" Setting next command to : ")); 469 | Serial1.println(st); 470 | _setCommand = st; 471 | _rawCommand = st; 472 | // Add code to put the call into the queue but verify it firstly. Then send the result back to debug/new window? 473 | } 474 | --------------------------------------------------------------------------------