├── LICENSE ├── README.md ├── SerialRead.ino └── config.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 PHYSEE 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Victron.Arduino-ESP8266 2 | Code to read the VE.Direct-Protocol from serial into a value array. Uses a non-blocking read loop and does checksum verification before adding the data. Extra care has been taken to not used `readByteUntil()` or any other blocking serial command that can mess with background services, especially on the ESP8266. 3 | 4 | Extend this code with MQTT or any other protocol or service to send and/or extract the data. 5 | 6 | ## Config 7 | At the moment the MPPT 75/10 and the 100/20 are configured in the `config.h`. 8 | 9 | ## Usage 10 | Make sure the RX and TX of the VE.Direct-Protocol are connected to the corresponding pins in the setup, `victronSerial`. On the NodeMCU, pins D7/D8 are used. 11 | 12 | Every second the MPPT sends out data, this is put into the `value` array. As per code the `PrintValues()` function loops of the array and prints the values and keys. 13 | 14 | The values can also be used with the macros defined, eg. `value[VPV]` or `value[ERR]`. 15 | 16 | Note that the values are stored as chars, so convert to suitable types with functions like: `atof()` or `atoi()` etc. 17 | -------------------------------------------------------------------------------- /SerialRead.ino: -------------------------------------------------------------------------------- 1 | /* 2 | Victron.Arduino-ESP8266 3 | A:Pim Rutgers 4 | E:pim@physee.eu 5 | 6 | Code to grab data from the VE.Direct-Protocol on Arduino / ESP8266. 7 | Tested on NodeMCU v1.0 8 | 9 | The fields of the serial commands are configured in "config.h" 10 | 11 | */ 12 | #include 13 | #include "config.h" 14 | 15 | // Serial variables 16 | #define rxPin D7 17 | #define txPin D8 // TX Not used 18 | SoftwareSerial victronSerial(rxPin, txPin); // RX, TX Using Software Serial so we can use the hardware serial to check the ouput 19 | // via the USB serial provided by the NodeMCU. 20 | char receivedChars[buffsize]; // an array to store the received data 21 | char tempChars[buffsize]; // an array to manipulate the received data 22 | char recv_label[num_keywords][label_bytes] = {0}; // {0} tells the compiler to initalize it with 0. 23 | char recv_value[num_keywords][value_bytes] = {0}; // That does not mean it is filled with 0's 24 | char value[num_keywords][value_bytes] = {0}; // The array that holds the verified data 25 | static byte blockindex = 0; 26 | bool new_data = false; 27 | bool blockend = false; 28 | 29 | void setup() { 30 | // Open serial communications and wait for port to open: 31 | Serial.begin(19200); 32 | victronSerial.begin(19200); 33 | } 34 | 35 | void loop() { 36 | // Receive information on Serial from MPPT 37 | RecvWithEndMarker(); 38 | HandleNewData(); 39 | 40 | // Just print the values every second, 41 | // Add your own code here to use the data. 42 | // Make sure to not used delay(X)s of bigger than 50ms, 43 | // so make use of the same principle used in PrintEverySecond() 44 | // or use some sort of Alarm/Timer Library 45 | PrintEverySecond(); 46 | } 47 | 48 | // Serial Handling 49 | // --- 50 | // This block handles the serial reception of the data in a 51 | // non blocking way. It checks the Serial line for characters and 52 | // parses them in fields. If a block of data is send, which always ends 53 | // with "Checksum" field, the whole block is checked and if deemed correct 54 | // copied to the 'value' array. 55 | 56 | void RecvWithEndMarker() { 57 | static byte ndx = 0; 58 | char endMarker = '\n'; 59 | char rc; 60 | 61 | while (victronSerial.available() > 0 && new_data == false) { 62 | rc = victronSerial.read(); 63 | if (rc != endMarker) { 64 | receivedChars[ndx] = rc; 65 | ndx++; 66 | if (ndx >= buffsize) { 67 | ndx = buffsize - 1; 68 | } 69 | } 70 | else { 71 | receivedChars[ndx] = '\0'; // terminate the string 72 | ndx = 0; 73 | new_data = true; 74 | } 75 | yield(); 76 | } 77 | } 78 | 79 | void HandleNewData() { 80 | // We have gotten a field of data 81 | if (new_data == true) { 82 | //Copy it to the temp array because parseData will alter it. 83 | strcpy(tempChars, receivedChars); 84 | ParseData(); 85 | new_data = false; 86 | } 87 | } 88 | 89 | void ParseData() { 90 | char * strtokIndx; // this is used by strtok() as an index 91 | strtokIndx = strtok(tempChars,"\t"); // get the first part - the label 92 | // The last field of a block is always the Checksum 93 | if (strcmp(strtokIndx, "Checksum") == 0) { 94 | blockend = true; 95 | } 96 | strcpy(recv_label[blockindex], strtokIndx); // copy it to label 97 | 98 | // Now get the value 99 | strtokIndx = strtok(NULL, "\r"); // This continues where the previous call left off until '/r'. 100 | if (strtokIndx != NULL) { // We need to check here if we don't receive NULL. 101 | strcpy(recv_value[blockindex], strtokIndx); 102 | } 103 | blockindex++; 104 | 105 | if (blockend) { 106 | // We got a whole block into the received data. 107 | // Check if the data received is not corrupted. 108 | // Sum off all received bytes should be 0; 109 | byte checksum = 0; 110 | for (int x = 0; x < blockindex; x++) { 111 | // Loop over the labels and value gotten and add them. 112 | // Using a byte so the the % 256 is integrated. 113 | char *v = recv_value[x]; 114 | char *l = recv_label[x]; 115 | while (*v) { 116 | checksum += *v; 117 | v++; 118 | } 119 | while (*l) { 120 | checksum+= *l; 121 | l++; 122 | } 123 | // Because we strip the new line(10), the carriage return(13) and 124 | // the horizontal tab(9) we add them here again. 125 | checksum += 32; 126 | } 127 | // Checksum should be 0, so if !0 we have correct data. 128 | if (!checksum) { 129 | // Since we are getting blocks that are part of a 130 | // keyword chain, but are not certain where it starts 131 | // we look for the corresponding label. This loop has a trick 132 | // that will start searching for the next label at the start of the last 133 | // hit, which should optimize it. 134 | int start = 0; 135 | for (int i = 0; i < blockindex; i++) { 136 | for (int j = start; (j - start) < num_keywords; j++) { 137 | if (strcmp(recv_label[i], keywords[j % num_keywords]) == 0) { 138 | // found the label, copy it to the value array 139 | strcpy(value[j], recv_value[i]); 140 | start = (j + 1) % num_keywords; // start searching the next one at this hit +1 141 | break; 142 | } 143 | } 144 | } 145 | } 146 | // Reset the block index, and make sure we clear blockend. 147 | blockindex = 0; 148 | blockend = false; 149 | } 150 | } 151 | 152 | void PrintEverySecond() { 153 | static unsigned long prev_millis; 154 | if (millis() - prev_millis > 1000) { 155 | PrintValues(); 156 | prev_millis = millis(); 157 | } 158 | } 159 | 160 | 161 | void PrintValues() { 162 | for (int i = 0; i < num_keywords; i++){ 163 | Serial.print(keywords[i]); 164 | Serial.print(","); 165 | Serial.println(value[i]); 166 | } 167 | } -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | config.h - config the keywords for Victron.DIRECT 3 | */ 4 | 5 | #define MPPT_75_10 6 | // MPPT 75 | 10 7 | #ifdef MPPT_75_10 8 | 9 | const byte buffsize = 32; 10 | const byte value_bytes = 33; 11 | const byte label_bytes = 9; 12 | const byte num_keywords = 18; 13 | 14 | char keywords[num_keywords][label_bytes] = { 15 | "PID", 16 | "FW", 17 | "SER#", 18 | "V", 19 | "I", 20 | "VPV", 21 | "PPV", 22 | "CS", 23 | "ERR", 24 | "LOAD", 25 | "IL", 26 | "H19", 27 | "H20", 28 | "H21", 29 | "H22", 30 | "H23", 31 | "HSDS", 32 | "Checksum" 33 | }; 34 | #define PID 0 35 | #define FW 1 36 | #define SER 2 // Offically SER# but # does not play that well as macro 37 | #define V 3 // ScV 38 | #define I 4 // ScI 39 | #define VPV 5 // PVV 40 | #define PPV 6 // PVI = PVV / VPV 41 | #define CS 7 // ScS 42 | #define ERR 8 // ScERR 43 | #define LOAD 9 // SLs 44 | #define IL 10 // SLI 45 | #define H19 11 46 | #define H20 12 47 | #define H21 13 48 | #define H22 14 49 | #define H23 15 50 | #define HSDS 16 51 | #define CHECKSUM 17 52 | #endif 53 | 54 | // MPPT 75 | 15 55 | #ifdef MPPT_75_15 56 | const byte buffsize = 32; 57 | const byte value_bytes = 33; 58 | const byte label_bytes = 9; 59 | const byte num_keywords = 19; 60 | 61 | char keywords[num_keywords][label_bytes] = { 62 | "PID", 63 | "FW", 64 | "SER#", 65 | "V", 66 | "I", 67 | "VPV", 68 | "PPV", 69 | "CS", 70 | "MPPT", 71 | "ERR", 72 | "LOAD", 73 | "IL", 74 | "H19", 75 | "H20", 76 | "H21", 77 | "H22", 78 | "H23", 79 | "HSDS", 80 | "Checksum" 81 | }; 82 | #define PID 0 83 | #define FW 1 84 | #define SER 2 // Offically SER# but # does not play that well as macro 85 | #define V 3 // ScV 86 | #define I 4 // ScI 87 | #define VPV 5 // PVV 88 | #define PPV 6 // PVI = PVV / VPV 89 | #define CS 7 // ScS 90 | #define MPPT 8 91 | #define ERR 9 // ScERR 92 | #define LOAD 10 // SLs 93 | #define IL 11 // SLI 94 | #define H19 12 95 | #define H20 13 96 | #define H21 14 97 | #define H22 15 98 | #define H23 16 99 | #define HSDS 17 100 | #define CHECKSUM 18 101 | #endif 102 | 103 | // MPPT 100 | 20 104 | #ifdef MPPT_100_20 105 | 106 | const byte buffsize = 32; 107 | const byte value_bytes = 33; 108 | const byte label_bytes = 9; 109 | const byte num_keywords = 20; 110 | 111 | char keywords[num_keywords][label_bytes] = { 112 | "PID", 113 | "FW", 114 | "SER#", 115 | "V", 116 | "I", 117 | "VPV", 118 | "PPV", 119 | "CS", 120 | "MPPT", 121 | "OR", 122 | "ERR", 123 | "LOAD", 124 | "IL", 125 | "H19", 126 | "H20", 127 | "H21", 128 | "H22", 129 | "H23", 130 | "HSDS", 131 | "Checksum" 132 | }; 133 | #define PID 0 134 | #define FW 1 135 | #define SER 2 136 | #define V 3 137 | #define I 4 138 | #define VPV 5 139 | #define PPV 6 140 | #define MPPT 7 141 | #define OR 8 142 | #define CS 9 143 | #define ERR 10 144 | #define LOAD 11 145 | #define IL 12 146 | #define H19 13 147 | #define H20 14 148 | #define H21 15 149 | #define H22 16 150 | #define H23 17 151 | #define HSDS 18 152 | #define CHECKSUM 19 153 | #endif 154 | --------------------------------------------------------------------------------