├── LICENSE.md ├── README.md ├── VE.Direct2MQTT ├── VE.Direct2MQTT.ino ├── VEDirect.h ├── config.h ├── vedirectEEPROM.h ├── vedirectMQTT.h ├── vedirectONEWIRE.h ├── vedirectOTA.h ├── vedirectSerial.h └── vedirectWiFi.h └── pictures ├── MyVEDirectHardware.jpg └── esp32.gif /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ralf Lehmann 4 | 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | Victron is building wonderful devices for Solar systems. 3 | Some of the devices have a VE.Direct interface and Victron disclosed the protocol. 4 | 5 | - See also: https://www.victronenergy.com/live/vedirect_protocol:faq 6 | 7 | I do own 2 MPTT75/15 Solar charger and all that I was missing was a MQTT interface for monitoring. 8 | I do NOT plan or intend to augment or replace the, in my eyes, perfect bluetooth app. 9 | 10 | So here it is 11 | 12 | # VE.Direct2MQTT 13 | 14 | VE.Direct2MQTT is using an ESP32 developers board and the Arduino IDE to send all ASCII data coming from a VE.Direct device to a MQTT server. 15 | 16 | With the help of the MQTT server you can integrate the monitoring data to virtually any Home Automation System. 17 | 18 | ## Features 19 | - Listen to VE.Direct messages and publish a block (consisting of several key-value pairs) to a MQTT broker
Every key from the device will be appended to the MQTT_PREFIX and build a topic. e.g. MQTT_PREFIX="/MPPT"; Topic /MPPT/V will contain the Battery Voltage
so please see the VE.Direct protocol for the meaning of topics 20 | - SSL enabled
If you are sending messages over the internet or using User/Passwords over the Internet you have to use SSL.
If you are using a locally protected network you can disable the Usage of SSL in the config file 21 | - Have several WiFi SSID's to connect to, in case one or the other is not reachable from your position 22 | - Have sveral MQTT Servers in case one is down.
The system will only be bound to one MQTT server at a time 23 | - Have several OneWire temperature sensors
So you can see the temperature of e.g. the MPPT Solracharger or the batteries or your inverter, ... 24 | - Timing parameters can be changed via MQTT
E.g. you can set that VE.Direct blocks are only transmitted every 10 seconds. The last received block will be transmitted, all other blocks are lost 25 | - OTA (Over The Air Update)
If you have a webserver where you can put binary files on and run php scripts you can use that server to install new VE.Direct2MQTT software on your ESP32
Please make sure that you use SSL and User/Password 26 | - One config file to enable/disable features and configure serial port or MQTT Topics 27 | 28 | 29 | ## Limitations 30 | - VE.Direct2MQTT is only listening to messages of the VE.Direct device
It understands only the "ASCII" part of the protocol that is only good to receive a set of values. You can't request any special data or change any parameters of the VE.Direct device.
This is intentionally because this is monitoring only 31 | - If you transmit a block only every 10 seconds, 9 previous blocks will be lost, because VE.Direct devices publish every second
This is normally not a problem. 32 | - During sending OneWire Data or OTA checking, no VE.Direct blocks will be sent.
This is a limitation of the ESP32, having several tasks tranmitting in parallel caused crashes of the device 33 | 34 | ## Hardware Installation 35 | ![Please see the wiki](https://github.com/RalfJL/VE.Direct2MQTT/wiki/Hardware) 36 | 37 | ## Software configuration 38 | ![Please see the wiki](https://github.com/RalfJL/VE.Direct2MQTT/wiki/Software) 39 | 40 | ## Debugging 41 | ![Please see the wiki](https://github.com/RalfJL/VE.Direct2MQTT/wiki/Debugging) 42 | 43 | ## Disclaimer 44 | I WILL NOT BE HELD LIABLE FOR ANY DAMAGE THAT YOU DO TO YOU OR ONE OF YOUR DEVICES. 45 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/VE.Direct2MQTT.ino: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct to MQTT Gateway using a ESP32 Board 3 | Collect Data from VE.Direct device like Victron MPPT 75/15 and send it 4 | to a MQTT gateway. From there you can integrate the data into any 5 | Home Automation Software like ioBroker annd make graphs. 6 | 7 | The ESP32 will read data from the VE.Direct interface and transmit the 8 | data via WiFi to a MQTT broker 9 | 10 | GITHUB Link 11 | 12 | MIT License 13 | 14 | Copyright (c) 2020 Ralf Lehmann 15 | 16 | 17 | Permission is hereby granted, free of charge, to any person obtaining a copy 18 | of this software and associated documentation files (the "Software"), to deal 19 | in the Software without restriction, including without limitation the rights 20 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 21 | copies of the Software, and to permit persons to whom the Software is 22 | furnished to do so, subject to the following conditions: 23 | 24 | The above copyright notice and this permission notice shall be included in all 25 | copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 28 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 29 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 30 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 31 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 32 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 33 | SOFTWARE. 34 | */ 35 | 36 | 37 | /* 38 | All configuration comes from config.h 39 | So please see there for WiFi, MQTT and OTA configuration 40 | */ 41 | #include "config.h" 42 | 43 | time_t last_boot; 44 | 45 | #include "TimeLib.h" 46 | #include "vedirectEEPROM.h" 47 | mEEPROM pref; 48 | #include "VEDirect.h" 49 | #include "vedirectWiFi.h" 50 | #include "vedirectMQTT.h" 51 | #ifdef USE_ONEWIRE 52 | #include "vedirectONEWIRE.h" 53 | #endif 54 | #ifdef USE_OTA 55 | #include "vedirectOTA.h" 56 | #endif 57 | 58 | 59 | 60 | VEDirect ve; 61 | time_t last_vedirect; 62 | 63 | 64 | void setup() { 65 | Serial.begin(115200); 66 | // fetch previously stored parameters from EEPROM 67 | VE_WAIT_TIME = pref.getInt("VE_WAIT_TIME", VE_WAIT_TIME); 68 | #ifdef USE_OTA 69 | OTA_WAIT_TIME = pref.getInt("OTA_WAIT_TIME", OTA_WAIT_TIME); 70 | #endif 71 | #ifdef USE_ONEWIRE 72 | OW_WAIT_TIME = pref.getInt("OW_WAIT_TIME", OW_WAIT_TIME); 73 | #endif 74 | if ( startWiFi()) { 75 | setClock(); 76 | last_boot = time(nullptr); 77 | if ( startMQTT()) { 78 | ve.begin(); 79 | // looking good; moving to loop 80 | return; 81 | } 82 | } 83 | // oh oh, we did not get WiFi or MQTT, that is bad; we can't continue 84 | // wait a while and reboot to try again 85 | delay(20000); 86 | ESP.restart(); 87 | } 88 | 89 | void loop() { 90 | VEDirectBlock_t block; 91 | time_t t = time(nullptr); 92 | #ifdef USE_ONEWIRE 93 | if ( abs(t - last_ow) >= OW_WAIT_TIME) { 94 | if ( checkWiFi()) { 95 | sendOneWireMQTT(); 96 | last_ow = t; 97 | sendOPInfo(); 98 | } 99 | } 100 | #endif 101 | #ifdef USE_OTA 102 | if ( abs(t - last_ota) >= OTA_WAIT_TIME) { 103 | if ( checkWiFi()) { 104 | startOTA(SKETCH_NAME); 105 | last_ota = t; 106 | } 107 | } 108 | #endif 109 | if ( abs( t - last_vedirect) >= VE_WAIT_TIME) { 110 | if ( ve.getNewestBlock(&block)) { 111 | last_vedirect = t; 112 | log_i("New block arrived; Value count: %d, serial %d", block.kvCount, block.serial); 113 | if ( checkWiFi()) { 114 | sendASCII2MQTT(&block); 115 | // sendOPInfo(); 116 | } 117 | } 118 | } 119 | if ( ! espMQTT.loop()){ 120 | log_i("MQTT connection lost restart"); 121 | startMQTT(); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/VEDirect.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct Protocol. 3 | 4 | GITHUB Link 5 | 6 | MIT License 7 | 8 | Copyright (c) 2020 Ralf Lehmann 9 | 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | #ifndef VEDIRECT_H 31 | #define VEDIRECT_H 32 | 33 | #include 34 | #include "vedirectSerial.h" 35 | 36 | typedef struct VEDirectKeyValue_t { 37 | String key; 38 | String value; 39 | }; 40 | 41 | typedef struct VEDirectBlock_t { 42 | VEDirectKeyValue_t b[MAX_KEY_VALUE_COUNT]; 43 | int kvCount; 44 | int serial; 45 | }; 46 | 47 | 48 | 49 | 50 | class VEDirect { 51 | public: 52 | void begin(); 53 | boolean addToASCIIBlock(String s); 54 | boolean getNewestBlock(VEDirectBlock_t *b); 55 | 56 | 57 | private: 58 | 59 | int _serial = 0; // Serial number of the block 60 | boolean _endOfASCIIBlock(StringSplitter *s); 61 | void _increaseNewestBlock(); 62 | int _getNewestBlock(); 63 | VEDirectBlock_t block[MAX_BLOCK_COUNT]; 64 | portMUX_TYPE _new_block_mutex = portMUX_INITIALIZER_UNLOCKED; // mutex to protect _newest_block 65 | volatile int _newest_block = -1; // newest complete block; ready for consumption 66 | volatile boolean _has_new_block = false; 67 | int _incoming_block = 0; // block currently filled; candidate for next newest block 68 | int _incoming_keyValueCount = 0; 69 | TaskHandle_t tHandle = NULL; 70 | 71 | }; 72 | 73 | /* 74 | This task will collect the data from Serial1 and place them in a block buffer 75 | The task is independant from the main task so it should not loose data because 76 | of MQTT reconnects or other time consuming duties in the main task 77 | */ 78 | void serialTask(void * pointer) { 79 | VEDirect *ve = (VEDirect *) pointer; 80 | String data; 81 | startVEDirectSerial(); 82 | while ( true ) { 83 | if ( Serial1.available()) { 84 | // char s = Serial1.read(); 85 | // log_d("Read: %c:%d", s, s); 86 | // if ( s == '\n') { 87 | // log_d("received start"); 88 | // begin of a datafield or frame 89 | data = Serial1.readStringUntil('\n'); // read label and value 90 | //log_d("Received Data: \"%s\"", data.c_str()); 91 | if ( data.length() > 0) { 92 | data.replace("\r\n", ""); // Strip carriage return newline; not part of the data 93 | ve->addToASCIIBlock(data); 94 | log_d("Stack free: %5d", uxTaskGetStackHighWaterMark(NULL)); 95 | } 96 | //} 97 | } else { 98 | // no serial data available; have a nap 99 | delay(1); 100 | } 101 | } 102 | } 103 | 104 | 105 | 106 | 107 | void VEDirect::begin() { 108 | _newest_block = -1; 109 | _incoming_block = _incoming_keyValueCount = 0; 110 | // create a task to handle the serial input 111 | xTaskCreate( 112 | serialTask, /* Task function. */ 113 | "serialTask", /* String with name of task. */ 114 | 10000, /* Stack size in bytes. */ 115 | this, /* Parameter passed as input of the task */ 116 | 1, /* Priority of the task. */ 117 | &tHandle); /* Task handle. */ 118 | } 119 | 120 | void VEDirect::_increaseNewestBlock() { 121 | taskENTER_CRITICAL(&_new_block_mutex); 122 | _newest_block++; 123 | _newest_block %= MAX_BLOCK_COUNT; 124 | taskEXIT_CRITICAL(&_new_block_mutex); 125 | _has_new_block = true; 126 | } 127 | 128 | int VEDirect::_getNewestBlock() { 129 | int n; 130 | taskENTER_CRITICAL(&_new_block_mutex); 131 | n = _newest_block; 132 | taskEXIT_CRITICAL(&_new_block_mutex); 133 | return n; 134 | } 135 | 136 | boolean VEDirect::_endOfASCIIBlock(StringSplitter *s) { 137 | if ( s->getItemAtIndex(0).equals("Checksum")) { 138 | // To Do: checksum test 139 | return true; 140 | } 141 | return false; 142 | } 143 | 144 | boolean VEDirect::addToASCIIBlock(String s) { 145 | StringSplitter sp = StringSplitter(s, '\t', 2); 146 | String historical = ""; 147 | if ( sp.getItemCount() == 2) { // sometime checksum has historical data attached 148 | log_d("Received Key/value: \"%s\":\"%s\"", sp.getItemAtIndex(0).c_str(), sp.getItemAtIndex(1).c_str()); 149 | if ( _incoming_keyValueCount < MAX_KEY_VALUE_COUNT) { 150 | block[_incoming_block].b[_incoming_keyValueCount].key = sp.getItemAtIndex(0); 151 | if ( sp.getItemAtIndex(0).equals("Checksum")) { 152 | char s[10]; 153 | sprintf(s, "%02x", sp.getItemAtIndex(1).charAt(0)); 154 | block[_incoming_block].b[_incoming_keyValueCount].value = String(s); 155 | historical = sp.getItemAtIndex(1).substring(1); 156 | } else { 157 | block[_incoming_block].b[_incoming_keyValueCount].value = sp.getItemAtIndex(1); 158 | } 159 | block[_incoming_block].kvCount = ++_incoming_keyValueCount; 160 | if ( historical.length() > 1) { 161 | // historical data 162 | block[_incoming_block].b[_incoming_keyValueCount].key = "Historical"; 163 | block[_incoming_block].b[_incoming_keyValueCount].value = historical; 164 | log_d("Historical data:\"%s\"", historical.c_str()); 165 | block[_incoming_block].kvCount = ++_incoming_keyValueCount; 166 | } 167 | } else { 168 | // buffer full but not end of frame 169 | // so this is not a good frame -> delete frame 170 | _incoming_keyValueCount = 0; 171 | return false; // buffer full 172 | } 173 | if ( _endOfASCIIBlock(&sp)) { 174 | // good frame; increase newest frame pointer 175 | block[_incoming_block].serial = _serial++; 176 | _increaseNewestBlock(); 177 | _incoming_keyValueCount = 0; 178 | _incoming_block++; 179 | _incoming_block %= MAX_BLOCK_COUNT; 180 | return true; 181 | } 182 | // not the end of a frame yet; continue 183 | return false; 184 | } else { 185 | log_e("Receviced Data not correct: \"%s\"", s.c_str()); 186 | //delete splitter; 187 | return false; 188 | } 189 | } 190 | 191 | boolean VEDirect::getNewestBlock(VEDirectBlock_t *b) { 192 | if ( ! _has_new_block) { 193 | return false; 194 | } 195 | _has_new_block = false; 196 | int block_num = _getNewestBlock(); 197 | int kv_count = block[block_num].kvCount; 198 | b->kvCount = kv_count; 199 | int ser = block[block_num].serial; 200 | b->serial = ser; 201 | for (int i = 0; i < kv_count; i++) { 202 | b->b[i] = block[block_num].b[i]; 203 | } 204 | return true; 205 | } 206 | 207 | 208 | 209 | #endif 210 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/config.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct config file. 3 | Cconfiguration parameters for 4 | VE.Direct2MQTT gateway 5 | 6 | GITHUB Link 7 | 8 | MIT License 9 | 10 | Copyright (c) 2020 Ralf Lehmann 11 | 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to deal 15 | in the Software without restriction, including without limitation the rights 16 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | SOFTWARE. 30 | */ 31 | 32 | /* 33 | Defines that activate features like OTA (over the air update) 34 | or Acitve/Passive mode 35 | */ 36 | // use SSL to connect to MQTT Server or OTA Server 37 | // it is strongly recommended to use SSL if you send any password over the net 38 | // connectiong to MQTT might need a password; the same for OTA 39 | // if you do not have SSL activated on your servers rename it to NO_USE_SSL 40 | #define NO_USE_SSL 41 | 42 | // Activate Over The Air Update of firmware 43 | // rename to NO_USE_OTA if you do not have a webserver that can server new firmware 44 | #define USE_OTA 45 | 46 | // 47 | // Use OneWire temperature sensors 48 | // 49 | #define USE_ONEWIRE 50 | 51 | #ifdef USE_ONEWIRE 52 | #define ONEWIRE_PIN 22 53 | /* 54 | define the wait time between 2 attempts to send one wire data 55 | 300000 = every 5 minutes 56 | */ 57 | int OW_WAIT_TIME = 10; // in s 58 | time_t last_ow; 59 | #endif 60 | 61 | 62 | 63 | #ifdef USE_SSL 64 | /* 65 | SSL certificate 66 | This is good for all let's encrypt certificates for MQTT or OTA servers 67 | */ 68 | /* 69 | This is lets-encrypt-x3-cross-signed.pem 70 | */ 71 | const char* rootCACertificate = \ 72 | "-----BEGIN CERTIFICATE-----\n" \ 73 | "MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/\n" \ 74 | "MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT\n" \ 75 | "DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow\n" \ 76 | "SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT\n" \ 77 | "GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC\n" \ 78 | "AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF\n" \ 79 | "q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8\n" \ 80 | "SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0\n" \ 81 | "Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA\n" \ 82 | "a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj\n" \ 83 | "/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T\n" \ 84 | "AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG\n" \ 85 | "CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv\n" \ 86 | "bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k\n" \ 87 | "c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw\n" \ 88 | "VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC\n" \ 89 | "ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz\n" \ 90 | "MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu\n" \ 91 | "Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF\n" \ 92 | "AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo\n" \ 93 | "uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/\n" \ 94 | "wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu\n" \ 95 | "X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG\n" \ 96 | "PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6\n" \ 97 | "KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==\n" \ 98 | "-----END CERTIFICATE-----\n"; 99 | 100 | #endif 101 | 102 | 103 | /* 104 | WiFi parameters 105 | */ 106 | 107 | // WiFi SSID'S and passwords 108 | // the strongest WiFi station will be used 109 | const char* ssid[] = {"SSID1", "SSID2", "SSID3"}; 110 | const char* pw[] = {"PW_SSID1", "PW_SSID2", "PW_SSID3"}; 111 | 112 | /* 113 | MQTT parameters 114 | you can have more than one MQTT server, the first one that answers will have the connection 115 | it is strongly recommended to use SSL if you send a username and password over the internet 116 | ATTENTION: use a unique client id to connect to MQTT or you will be kicked out by another device 117 | using your id 118 | */ 119 | #define MQTT_MAX_RETRIES 3 // maximum retires to reach a MQTT broker 120 | const char* mqtt_server[] = {"IP_MQTT1", "IP_MQTT2"}; 121 | // no SSL ports 122 | const uint16_t mqtt_port[] = {1883, 1883}; 123 | // SSL ports 124 | //const uint16_t mqtt_port[] = {8883, 8883}; 125 | const char* mqtt_clientID[] = {"clientID", "clientID"}; 126 | const char* mqtt_username[] = {"mqttUserName", "mqttUserName"}; 127 | const char* mqtt_pw[] = {"mqttUserPW", "mqttUserPW"}; 128 | int mqtt_server_count = sizeof(mqtt_server) / sizeof(mqtt_server[0]); 129 | 130 | // this is the MQTT prefix; below that we use the string from VE.Direct 131 | // e.g. /MPPT75-15/PID for Product ID 132 | String MQTT_PREFIX = "/MPPT75-15NOSSL"; 133 | String MQTT_PARAMETER = "/MPPT75-15NOSSL/Parameter"; 134 | #ifdef USE_ONEWIRE 135 | String MQTT_ONEWIRE = "/MPPT75-15NOSSL/OneWire"; 136 | #endif 137 | 138 | 139 | #ifdef USE_OTA 140 | /* 141 | the binary file to look for 142 | */ 143 | #define SKETCH_NAME "VE.Direct2MQTT.ino.esp32.bin" // sketch name and binary 144 | /* 145 | the URL of he update 146 | Note: the php script file.php returns a 302 "not modified" if the checksum of the current sketch and the binary 147 | is the same 148 | URL: 149 | http://username:pw@servername/bin/file.php? 150 | */ 151 | const char* ota_server_string[] = {"http://user:pw@serverIP/bin/file.php?", "http://user:pw@serverName/bin/file.php?"}; 152 | int ota_server_count = sizeof(ota_server_string) / sizeof(ota_server_string[0]); 153 | 154 | /* 155 | define the wait time between 2 attempts to update the firmware 156 | 300000 = every 5 minutes 157 | */ 158 | int OTA_WAIT_TIME = 300; // in s 159 | time_t last_ota; 160 | #endif 161 | 162 | /* 163 | Software serial parameter 164 | These are the pins for the VE.Direct connection 165 | WARNING: if your VE.Direct device uses 5V please use a 1kOhm/2kOhm divider for the receive line 166 | The sending line does not need any modification. The ESP uses 3.3V and that's it. A 5V device 167 | should be able to read that voltage as input 168 | */ 169 | #ifndef VEDIRECT_RX 170 | #define VEDIRECT_RX 33 // connected to TX of the VE.Direct device; ATTENTION divider may be needed, see abowe 171 | #endif 172 | #ifndef VEDIRECT_TX 173 | #define VEDIRECT_TX 32 // connected to RX of the VE:DIRECT device 174 | #endif 175 | 176 | /* 177 | Depending on the DE.Direct device there will be several Key/Value pairs; 178 | Define the maximum count of key/value pairs 179 | */ 180 | #define MAX_KEY_VALUE_COUNT 30 181 | 182 | /* 183 | Number of Key-Value blocks we can buffer 184 | MQTT may be slower than one second, especially when we have to reconnect 185 | this is the number of buffers we can keep 186 | */ 187 | #define MAX_BLOCK_COUNT 5 188 | 189 | /* 190 | Wait time in Loop 191 | this determines how many frames are send to MQTT 192 | if wait time is e.g. 10 minutes, we will send only every 10 minutes to MQTT 193 | Note: only the last incoming block will be send; all previous blocks will be discarded 194 | Wait time is in seconds 195 | Waittime of 1 or 0 means every received packet will be transmitted to MQTT 196 | Packets during OTA or OneWire will be discarded 197 | */ 198 | int VE_WAIT_TIME = 1; // in s 199 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/vedirectEEPROM.h: -------------------------------------------------------------------------------- 1 | /* 2 | Store alarms and preferences in NVRAM 3 | for Bewässerung 4 | 5 | Ralf Lehmann 6 | 12.2019 7 | */ 8 | 9 | #ifndef VEDIRECTEEPROM_H 10 | #define VEDIRECTEEPROM_H 11 | 12 | 13 | // Constants 14 | // no key larger than 15 chars 15 | 16 | 17 | 18 | #include 19 | #define PREF_NAME_SPACE "VE2MQTT" // 20 | 21 | class mEEPROM { 22 | public: 23 | mEEPROM(); 24 | String getString(String key, String devault_value); 25 | String getString(int key, String devault_value); 26 | boolean setString(String key, String value); 27 | boolean setString(int key, String value); 28 | int32_t getInt(int key, int default_value); 29 | int32_t getInt(String key, int default_value); 30 | boolean setInt(int key, int32_t value); 31 | boolean setInt(String key, int32_t value); 32 | 33 | 34 | private: 35 | Preferences _preferences; 36 | }; 37 | 38 | mEEPROM::mEEPROM() { 39 | // Open Preferences with my-app namespace. Each application module, library, etc 40 | // has to use a namespace name to prevent key name collisions. We will open storage in 41 | // RW-mode (second parameter has to be false). 42 | // Note: Namespace name is limited to 15 chars. 43 | 44 | } 45 | 46 | int32_t mEEPROM::getInt(String key, int default_value = 0) { 47 | _preferences.begin(PREF_NAME_SPACE, false); 48 | int32_t ret = _preferences.getInt(key.c_str(), default_value); 49 | log_d("PrefGetInt; \'%s\' = \'%d\'", key.c_str(), ret); 50 | _preferences.end(); 51 | return ret; 52 | } 53 | 54 | boolean mEEPROM::setInt(String key, int32_t value) { 55 | _preferences.begin(PREF_NAME_SPACE, false); 56 | _preferences.putInt(key.c_str(), value); 57 | log_d("PrefPutInt; \'%s\' = \'%d\'", key.c_str(), value); 58 | _preferences.end(); 59 | return true; 60 | } 61 | 62 | int32_t mEEPROM::getInt(int key, int default_value = 0) { 63 | return mEEPROM::getInt(String(key), default_value); 64 | } 65 | 66 | boolean mEEPROM::setInt(int key, int32_t value = 0) { 67 | return mEEPROM::setInt(String(key), value); 68 | } 69 | 70 | String mEEPROM::getString(int key, String default_value = ""){ 71 | return getString(String(key), default_value); 72 | } 73 | 74 | String mEEPROM::getString(String key, String default_value = "") { 75 | _preferences.begin(PREF_NAME_SPACE, false); 76 | String ret = _preferences.getString(key.c_str(), default_value.c_str()); 77 | log_d("PrefGetStr: \'%s\' = \'%s\'", key.c_str(), ret.c_str()); 78 | _preferences.end(); 79 | return ret; 80 | } 81 | 82 | boolean mEEPROM::setString(int key, String value){ 83 | return setString(String(key), value); 84 | } 85 | 86 | boolean mEEPROM::setString(String key, String value) { 87 | _preferences.begin(PREF_NAME_SPACE, false); 88 | _preferences.putString(key.c_str(), value.c_str()); 89 | log_d("PrefSetStr: \'%s\' = \'%s\'", key.c_str(), value.c_str()); 90 | _preferences.end(); 91 | return true; 92 | } 93 | 94 | 95 | #endif 96 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/vedirectMQTT.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct MQTT code. 3 | 4 | GITHUB Link 5 | 6 | MIT License 7 | 8 | Copyright (c) 2020 Ralf Lehmann 9 | 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | #ifndef vedirectMQTT_H 31 | #define vedirectMQTT_H 32 | 33 | #include 34 | #include "PubSubClient.h" 35 | 36 | volatile boolean mqtt_param_rec = false; // we received a parameter via MQTT; remove it or it will be received over and over again 37 | 38 | PubSubClient espMQTT(espClient); 39 | 40 | boolean reconnect(const char* server, uint16_t port, const char* id, const char* user, const char* pw) { 41 | espMQTT.setServer(server, port); 42 | // Loop until we're reconnected 43 | int j = MQTT_MAX_RETRIES; 44 | while (!espMQTT.connected() && j-- > 0) { 45 | log_i("Attempting MQTT connection to server: %s, try: %d", server, j); 46 | // Attempt to connect 47 | if (espMQTT.connect(id, user, pw)) { 48 | log_i("connected"); 49 | log_i("Subscribing to: %s", MQTT_PARAMETER.c_str()); 50 | espMQTT.subscribe(MQTT_PARAMETER.c_str(), 1); 51 | return true; 52 | } else { 53 | log_e("failed, rc= %d", espMQTT.state()); 54 | log_e(" try again in 5 seconds"); 55 | // Wait 5 seconds before retrying 56 | delay(5000); 57 | } 58 | } 59 | return false; 60 | } 61 | 62 | // 63 | // Callback function; Receiver 64 | // currently only as dummy 65 | // 66 | void callback(const char* topic, byte * payload, unsigned int length) { 67 | mqtt_param_rec = false; // did we receive a parameter? 68 | char s[length + 1]; strncpy(s, (char *)payload, length); s[length] = '\0'; 69 | log_d("MQTT callback received: %s - \"%s\"", topic, s); 70 | String t = String(topic); 71 | if ( t.equals(MQTT_PARAMETER)) { 72 | log_d("MQTT received new parameters"); 73 | 74 | const int capacity = JSON_OBJECT_SIZE(10); // no clue how much data is coming in 75 | StaticJsonDocument doc; 76 | DeserializationError err = deserializeJson(doc, s, length); 77 | if ( err ) { 78 | log_d("ERROR: deserializing MQTT message: %s", err.c_str()); 79 | return; 80 | } 81 | JsonObject obj = doc.as(); 82 | #ifdef DEBUG 83 | for ( JsonPair kv : obj) { 84 | log_d("key: %s, Value %s", kv.key().c_str(), kv.value().as().c_str()); 85 | } 86 | #endif 87 | 88 | JsonVariant sleeptime = obj["VE_WAIT_TIME"]; 89 | if ( sleeptime ) { 90 | mqtt_param_rec = true; // this will cause the parameter to be erased from queue 91 | int st = sleeptime.as(); 92 | log_d("Found member VEDIRECT_WAIT_TIME: %d", st); 93 | if ( VE_WAIT_TIME != st) { 94 | VE_WAIT_TIME = st; 95 | // store in EEPROM 96 | pref.setInt("VE_WAIT_TIME", st); 97 | } 98 | } 99 | 100 | #ifdef USE_OTA 101 | JsonVariant ota_sleeptime = obj["OTA_WAIT_TIME"]; 102 | if ( ota_sleeptime ) { 103 | mqtt_param_rec = true; // this will cause the parameter to be erased from queue 104 | int st = ota_sleeptime.as(); 105 | log_d("Found member OTA_WAIT_TIME: %d", st); 106 | if ( OTA_WAIT_TIME != st) { 107 | OTA_WAIT_TIME = st; 108 | // store in EEPROM 109 | pref.setInt("OTA_WAIT_TIME", st); 110 | } 111 | } 112 | #endif 113 | #ifdef USE_ONEWIRE 114 | JsonVariant ow_sleeptime = obj["OW_WAIT_TIME"]; 115 | if ( ow_sleeptime ) { 116 | mqtt_param_rec = true; // this will cause the parameter to be erased from queue 117 | int st = ow_sleeptime.as(); 118 | log_d("Found member OW_WAIT_TIME: %d", st); 119 | if ( OW_WAIT_TIME != st) { 120 | OW_WAIT_TIME = st; 121 | // store in EEPROM 122 | pref.setInt("OW_WAIT_TIME", st); 123 | } 124 | } 125 | #endif 126 | } 127 | } 128 | 129 | // 130 | // Startup 131 | // 132 | boolean startMQTT() { 133 | #ifdef USE_SSL 134 | espClient.setCACert(rootCACertificate); 135 | #endif 136 | // receive parameter via MQTT 137 | log_d("MQTT callback setting"); 138 | espMQTT.setCallback(callback); 139 | for ( int i = 0; i < mqtt_server_count; i++) { 140 | if (! espMQTT.connected()) { 141 | log_d("Connecting to MQTT server: %s port: %d", mqtt_server[i], mqtt_port[i]); 142 | if ( reconnect(mqtt_server[i], mqtt_port[i], mqtt_clientID[i], mqtt_username[i], mqtt_pw[i])) { 143 | return true; // done got the server 144 | } 145 | } 146 | } 147 | return false; // no server available 148 | } 149 | 150 | // 151 | // end MQTT 152 | // 153 | boolean endMQTT() { 154 | espMQTT.loop(); 155 | log_d("MQTT disconnect"); 156 | espMQTT.disconnect(); 157 | return true; 158 | } 159 | 160 | // 161 | // Send ASCII data from passive mode to MQTT 162 | // 163 | boolean sendASCII2MQTT(VEDirectBlock_t * block) { 164 | for (int i = 0; i < block->kvCount; i++) { 165 | String key = block->b[i].key; 166 | String value = block->b[i].value; 167 | String topic = MQTT_PREFIX + "/" + key; 168 | if ( !espMQTT.connected()) { 169 | startMQTT(); 170 | } 171 | espMQTT.loop(); 172 | topic.replace("#", ""); // # in a topic is a no go for MQTT 173 | value.replace("\r\n", ""); 174 | if ( espMQTT.publish(topic.c_str(), value.c_str())) { 175 | log_i("MQTT message sent succesfully: %s: \"%s\"", topic.c_str(), value.c_str()); 176 | } else { 177 | log_e("Sending MQTT message failed: %s: %s", topic.c_str(), value.c_str()); 178 | } 179 | 180 | if ( mqtt_param_rec ) { 181 | // avoid loops by sending only if we received a valid parameter 182 | log_i("Removing parameter from Queue: %s", MQTT_PARAMETER.c_str()); 183 | espMQTT.publish(MQTT_PARAMETER.c_str(), "", true); 184 | } 185 | } 186 | return true; 187 | } 188 | 189 | // 190 | // send operation info 191 | // 192 | boolean sendOPInfo() { 193 | if ( !espMQTT.connected()) { 194 | startMQTT(); 195 | } 196 | espMQTT.loop(); 197 | String topic = MQTT_PREFIX + "/" + "UTCBootTime"; 198 | if ( espMQTT.publish(topic.c_str(), asctime(localtime(&last_boot)))) { 199 | log_i("MQTT message sent succesfully: %s: \"%s\"", topic.c_str(), asctime(localtime(&last_boot))); 200 | } else { 201 | log_e("Sending MQTT message failed: %s: %s", topic.c_str(), asctime(localtime(&last_boot))); 202 | } 203 | return true; 204 | } 205 | 206 | 207 | 208 | #endif 209 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/vedirectONEWIRE.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct OneWire temperature sensors code. 3 | 4 | GITHUB Link 5 | 6 | MIT License 7 | 8 | Copyright (c) 2020 Ralf Lehmann 9 | 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | #ifndef VEDIRECTONEWIRE_H 31 | #define VEDIRECTONEWIRE_H 32 | 33 | #include 34 | #include 35 | 36 | #define MAX_DS18SENSORS 3 37 | #define MAX_MEASSUREMENTS 3 38 | 39 | OneWire oneWire(ONEWIRE_PIN); 40 | DallasTemperature sensors(&oneWire); 41 | int deviceCount = 0; 42 | boolean onewire_good_values = false; 43 | 44 | String addr2String(DeviceAddress addr) { 45 | String s; 46 | for (int i = 0; i < 7; i++) { 47 | s += String(addr[i]) + ":"; 48 | } 49 | return s += addr[7]; 50 | } 51 | 52 | boolean meassureOneWire() { 53 | log_d("Start"); 54 | int m_count = MAX_MEASSUREMENTS; 55 | do { 56 | m_count --; 57 | onewire_good_values = true; 58 | sensors.begin(); 59 | sensors.setWaitForConversion(true); 60 | delay(1000); 61 | sensors.requestTemperatures(); 62 | delay(1000); 63 | deviceCount = sensors.getDeviceCount(); 64 | log_d("Found %d devices", deviceCount); 65 | for (int i = 0; i < deviceCount; i++) { 66 | float temp = sensors.getTempCByIndex(i); 67 | if ( temp > 200 || temp < -80 ) { 68 | onewire_good_values = false; 69 | } 70 | } 71 | } while (m_count >= 0 && !onewire_good_values); 72 | log_d("End"); 73 | return true; 74 | } 75 | 76 | boolean sendOneWireMQTT() { 77 | log_d("Start"); 78 | meassureOneWire(); 79 | 80 | log_d("Found %d One wire temp sensors", deviceCount); 81 | float c[deviceCount]; 82 | 83 | if ( !onewire_good_values || deviceCount <= 0) { 84 | log_d("No ONE Wire temp sensors found; END"); 85 | return false; 86 | } 87 | 88 | for (int i = 0; i < deviceCount; i++) { 89 | DeviceAddress addr; 90 | sensors.getAddress((uint8_t *)&addr, (uint8_t) i); 91 | c[i] = sensors.getTempCByIndex(i); 92 | log_d("DS18: %s, Temp: %f", addr2String(addr).c_str(), c[i]); 93 | } 94 | 95 | if ( !espMQTT.connected()) { 96 | startMQTT(); 97 | } 98 | 99 | StaticJsonDocument<300> doc; 100 | // Add values in the document 101 | // 102 | int count = deviceCount > MAX_DS18SENSORS ? MAX_DS18SENSORS : deviceCount; 103 | log_d("Sending: %d Devices", count); 104 | for (int i = 0; i < count; i++) { 105 | DeviceAddress addr; 106 | sensors.getAddress((uint8_t *)&addr, (uint8_t) i); 107 | String s = addr2String(addr); 108 | doc[s] = c[i]; 109 | } char s[300]; 110 | serializeJson(doc, s); 111 | if ( espMQTT.publish(MQTT_ONEWIRE.c_str(), s)) { 112 | log_d("Sending OneWire Data: %s - OK", s); 113 | } else { 114 | log_d("Sending OneWire %s Data: %s - ERROR", MQTT_ONEWIRE.c_str(), s); 115 | } 116 | espMQTT.loop(); 117 | if ( mqtt_param_rec ) { 118 | // avoid loops by sending only if we received a valid parameter 119 | log_i("Removing parameter from Queue: %s", MQTT_PARAMETER.c_str()); 120 | espMQTT.publish(MQTT_PARAMETER.c_str(), "", true); 121 | } 122 | log_d("End"); 123 | return true; 124 | } 125 | 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/vedirectOTA.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct Over The Air update code. 3 | 4 | GITHUB Link 5 | 6 | MIT License 7 | 8 | Copyright (c) 2020 Ralf Lehmann 9 | 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | #ifndef VEDIRECTOTA_H 31 | #define VEDIRECTOTA_H 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | 39 | 40 | //// Utility to extract header value from headers 41 | //String getHeaderValue(String header, String headerName) { 42 | // return header.substring(strlen(headerName.c_str())); 43 | //} 44 | 45 | //void printSSLError() { 46 | // char s[100]; 47 | // int e = espClient.lastError(s, 100); 48 | // D("SSL Error: " + String(e) + " "); Dln(s); 49 | // // int err; 50 | // // br_ssl_client_context sc; 51 | // // err = br_ssl_engine_last_error(&sc.eng); 52 | //} 53 | 54 | boolean splitURL(String * result, String url) { 55 | int col_pos = url.indexOf(':'); 56 | if ( col_pos < 0 ) { 57 | return false; 58 | } 59 | result[0] = url.substring(0, col_pos); 60 | // Dln("Proto: " + result[0]); 61 | if ( url.charAt(col_pos + 1) != '/' || url.charAt(col_pos + 2) != '/') { 62 | return false; 63 | } 64 | String rest = url.substring(col_pos + 3 ); // strip // immediately 65 | // Dln("Rest:" + rest); 66 | // check if we have a user name and password 67 | int at_pos = rest.indexOf('@'); 68 | if ( at_pos > 0 ) { 69 | // process user:password 70 | result[3] = rest.substring(0, at_pos); 71 | rest = rest.substring(at_pos + 1); 72 | } 73 | int slash_pos = rest.indexOf('/'); 74 | if ( slash_pos < 0 ) { 75 | return false; 76 | } 77 | result[1] = rest.substring(0, slash_pos); 78 | result[2] = rest.substring(slash_pos); 79 | 80 | return true; 81 | } 82 | 83 | 84 | 85 | // 86 | // make sure that there is plenty of memory, otherwise SSL will fail 87 | // because bin files contain passwords SSL is needed 88 | // e.g. stop BLE and release the memory 89 | // 90 | bool startOTA(String sketch_name) { 91 | log_d("Free Mem: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT)); 92 | log_d("Starting OTA with sketch: %s", sketch_name.c_str()); 93 | 94 | // The line below is optional. It can be used to blink the LED on the board during flashing 95 | // The LED will be on during download of one buffer of data from the network. The LED will 96 | // be off during writing that buffer to flash 97 | // On a good connection the LED should flash regularly. On a bad connection the LED will be 98 | // on much longer than it will be off. Other pins than LED_BUILTIN may be used. The second 99 | // value is used to put the LED on. If the LED is on with HIGH, that value should be passed 100 | // httpUpdate.setLedPin(LED_BUILTIN, HIGH); 101 | 102 | for (int i = 0; i < ota_server_count; i++) { 103 | t_httpUpdate_return ret = httpUpdate.update(espClient, String(ota_server_string[i]) + sketch_name); 104 | // Or: 105 | //t_httpUpdate_return ret = httpUpdate.update(client, "server", 443, "file.bin"); 106 | 107 | switch (ret) { 108 | case HTTP_UPDATE_FAILED: 109 | log_d("HTTP_UPDATE_FAILED Error %d - %s", httpUpdate.getLastError(), httpUpdate.getLastErrorString().c_str()); 110 | break; 111 | 112 | case HTTP_UPDATE_NO_UPDATES: 113 | log_d("HTTP_UPDATE_NO_UPDATES"); 114 | return true; 115 | break; 116 | 117 | case HTTP_UPDATE_OK: 118 | log_d("HTTP_UPDATE_OK"); 119 | return true; 120 | break; 121 | } 122 | } 123 | 124 | return true; 125 | } 126 | 127 | 128 | #endif 129 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/vedirectSerial.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct Serial code. 3 | 4 | GITHUB Link 5 | 6 | MIT License 7 | 8 | Copyright (c) 2020 Ralf Lehmann 9 | 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | #ifndef VEDIRECTSERIAL_H 31 | #define VEDIRECTSERIAL_H 32 | 33 | void startVEDirectSerial() { 34 | Serial1.begin(19200, SERIAL_8N1, VEDIRECT_RX, VEDIRECT_TX); 35 | } 36 | 37 | #endif 38 | -------------------------------------------------------------------------------- /VE.Direct2MQTT/vedirectWiFi.h: -------------------------------------------------------------------------------- 1 | /* 2 | VE.Direct WiFi code. 3 | 4 | GITHUB Link 5 | 6 | MIT License 7 | 8 | Copyright (c) 2020 Ralf Lehmann 9 | 10 | 11 | Permission is hereby granted, free of charge, to any person obtaining a copy 12 | of this software and associated documentation files (the "Software"), to deal 13 | in the Software without restriction, including without limitation the rights 14 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | copies of the Software, and to permit persons to whom the Software is 16 | furnished to do so, subject to the following conditions: 17 | 18 | The above copyright notice and this permission notice shall be included in all 19 | copies or substantial portions of the Software. 20 | 21 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 22 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 23 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 24 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 25 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 26 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 27 | SOFTWARE. 28 | */ 29 | 30 | #ifndef VERDIRECTWIFI_H 31 | #define VERDIRECTWIFI_H 32 | 33 | #include 34 | #include 35 | 36 | #ifdef USE_SSL 37 | #include 38 | #else 39 | #include 40 | #endif 41 | 42 | WiFiMulti WiFiMulti; 43 | #ifdef USE_SSL 44 | WiFiClientSecure espClient; 45 | #else 46 | WiFiClient espClient; 47 | #endif 48 | 49 | int ssid_count = sizeof(ssid) / sizeof(ssid[0]); 50 | 51 | // Set time via NTP, as required for x.509 validation 52 | void setClock() { 53 | configTime(0, 0, "pool.ntp.org", "time.nist.gov"); // UTC 54 | 55 | log_d("Waiting for NTP time sync: "); 56 | time_t now = time(nullptr); 57 | while (now < 8 * 3600 * 2) { 58 | yield(); 59 | delay(500); 60 | now = time(nullptr); 61 | } 62 | 63 | log_d(""); 64 | struct tm timeinfo; 65 | gmtime_r(&now, &timeinfo); 66 | log_d("NTP time %s", asctime(&timeinfo)); 67 | } 68 | 69 | boolean startWiFi() { 70 | log_d("Number of ssid's: %d", ssid_count); 71 | // add all ssid's to WiFiMulti 72 | for (int i = 0; i < ssid_count; i++) { 73 | WiFiMulti.addAP(ssid[i], pw[i]); 74 | } 75 | if ((WiFiMulti.run() == WL_CONNECTED)) { 76 | log_i("WiFi connected"); 77 | 78 | #ifdef USE_SSL 79 | //WiFiClientSecure client; 80 | espClient.setCACert(rootCACertificate); 81 | #endif 82 | 83 | // Reading data over SSL may be slow, use an adequate timeout 84 | espClient.setTimeout(24000 / 1000); // timeout argument is defined in seconds for setTimeout 85 | return true; 86 | } 87 | log_e("WiFi could not be started"); 88 | return false; 89 | } 90 | 91 | // check if WiFi is still connected 92 | // if not, try to reconnect 93 | boolean checkWiFi(){ 94 | if ( WiFi.status() != WL_CONNECTED ){ 95 | return startWiFi(); 96 | } 97 | return true; 98 | } 99 | 100 | boolean endWiFi() { 101 | log_d("Stopping WiFi ... "); 102 | boolean r = WiFi.disconnect(); 103 | delay(1000); 104 | if ( WiFi.status() == WL_CONNECTED) { 105 | log_e("ERROR: unable to disconnect"); 106 | return false; 107 | } 108 | log_i("disconnected successfully"); 109 | return true; 110 | } 111 | 112 | #endif 113 | -------------------------------------------------------------------------------- /pictures/MyVEDirectHardware.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RalfJL/VE.Direct2MQTT/ddc6ef74595b5781b02319c9dba2e744061a3a67/pictures/MyVEDirectHardware.jpg -------------------------------------------------------------------------------- /pictures/esp32.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RalfJL/VE.Direct2MQTT/ddc6ef74595b5781b02319c9dba2e744061a3a67/pictures/esp32.gif --------------------------------------------------------------------------------