├── .gitignore ├── LICENSE ├── README.md ├── Schematic ├── copper.pdf ├── esp8266-VoltronicSolar-COMP.jpg ├── esp8266-VoltronicSolar-Sol.jpg └── sch.pdf ├── include ├── passwd.h └── solarmon.h ├── platformio.ini └── src ├── communication.cpp ├── qmisc.cpp ├── qpigs.cpp ├── qpiri.cpp └── solarmon.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | .pio 3 | .vscode/.browse.c_cpp.db* 4 | .vscode/c_cpp_properties.json 5 | .vscode/launch.json 6 | .vscode/ipch 7 | temp/posix_sockets.h 8 | temp/qmisc.c 9 | temp/qpiri.c 10 | temp/solarmon.c 11 | Solarmon-HA-MQTT.code-workspace 12 | .vscode/arduino.json 13 | .vscode/extensions.json 14 | include/passwd.h 15 | include/README 16 | lib/README 17 | src/main.ino 18 | include/passwd.h 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Amish Vishwakarma 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 | # voltronic_ESP8266_MQTT 2 | Home Assistant addon using MQTT to get data from Voltronic Solar Inverters using ESP8266 3 | 4 | This project is intended to interface with the Voltronics 'InfiniSolar V' Solar inverters https://voltronicpower.com/en-US/Product/Detail/InfiniSolar-V-1K-5K, sold in India by Flin Energy https://flinenergy.com/solar_inverters/_FlinInfini_Lite_Smart_Hybrid_Inverter. 5 | 6 | This inverter follows protocol version 1.8 that starts with ^ (^P005PI) unlike previous starting with Q (QPI). 7 | ___________________________________________________________________________________________ 8 | Comman Format: ^TnnnXXXX,XXXX,XXXX,, 9 | ___________________________________________________________________________________________ 10 | Character Description Remark 11 | 12 | ^ Start bit 13 | 14 | T Type P: PC Query command, S: Set command, D: Device Response 15 | 16 | nnn Data length Include CRC and ending character, except"^Tnnn" 17 | 18 | XXXXX Data If the data is reserved, they will be filled nothing, 19 | so you would see double "," connected. 20 | 21 | , Seperator Separate each data, please use "," to recognize the 22 | length of data. If double "," continuing, that means this data is reserved. 23 | 24 | Two byte of CRC result, the first byte is high 8 bits, second byte is low 25 | 8 bits. 26 | ___________________________________________________________________________________________ 27 | 28 | Before compiling be sure to change the SSID, password, aqnd MQTT server IP in include/passwd.h 29 | The binaries can be built by importing the project in PlatformIO editor or Arduino. 30 | -------------------------------------------------------------------------------- /Schematic/copper.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amishv/voltronic_ESP8266_MQTT/d6ffb53ee562e59289bbfdd153f0c7885bcf2b92/Schematic/copper.pdf -------------------------------------------------------------------------------- /Schematic/esp8266-VoltronicSolar-COMP.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amishv/voltronic_ESP8266_MQTT/d6ffb53ee562e59289bbfdd153f0c7885bcf2b92/Schematic/esp8266-VoltronicSolar-COMP.jpg -------------------------------------------------------------------------------- /Schematic/esp8266-VoltronicSolar-Sol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amishv/voltronic_ESP8266_MQTT/d6ffb53ee562e59289bbfdd153f0c7885bcf2b92/Schematic/esp8266-VoltronicSolar-Sol.jpg -------------------------------------------------------------------------------- /Schematic/sch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amishv/voltronic_ESP8266_MQTT/d6ffb53ee562e59289bbfdd153f0c7885bcf2b92/Schematic/sch.pdf -------------------------------------------------------------------------------- /include/passwd.h: -------------------------------------------------------------------------------- 1 | #define SSID "SSID" 2 | #define WiFi_KEY "passwd" 3 | #define SSID2 "SSID2" 4 | #define WiFi_KEY2 "passwd2" 5 | #define MQTT_HOST "you.rMQ.TTh.ost" //MQTT Host IP 6 | #define API_PASS "" 7 | #define CLIENTID "ESP-Solarmon" 8 | -------------------------------------------------------------------------------- /include/solarmon.h: -------------------------------------------------------------------------------- 1 | 2 | // #solarmon.h 3 | //srarted on 5/3/2019 4 | #ifndef SOLARMON_H__ 5 | #define SOLARMON_H__ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "WiFiClientSecureBearSSL.h" 13 | #include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug 14 | #include 15 | 16 | 17 | #define debugPort Debug 18 | //#define debugPort Serial1 19 | #define InverterPort Serial 20 | //#define _POSIX_SOURCE 1 /* POSIX compliant source */ 21 | #define MQTT_RAW_CMD_TOPIC "homeassistant/raw_command" 22 | #define MQTT_RAW_REPLY_TOPIC "homeassistant/raw_command/reply" 23 | #define HA_SENSOR_TOPIC "homeassistant/sensor" 24 | 25 | const int SIZE_OF_REPLY = 130; 26 | static RemoteDebug Debug; 27 | static const int enDay = 1; 28 | static const int enMon = 2; 29 | static const int enYear = 3; 30 | 31 | static const int TRUE = 1; 32 | static const int FALSE = -1; 33 | 34 | static const char command[][10] = { 35 | "^P005PI", 36 | "^P005GS", 37 | "^P005ET", 38 | "^P009EY", 39 | "^P011EM", 40 | "^P013ED"}; 41 | 42 | uint16_t cal_crc_half(uint8_t *pin, uint8_t len); 43 | int sendCommand(const char *cmd); 44 | int openport(void); 45 | int readport(uint8_t *recv_buf); 46 | int sendMQTTmessage(const char *topic_str, const char *msg); 47 | int sensorInit(int init_uninit); 48 | int getInverterMode(void); 49 | int getInverterStatus(void); 50 | int getInverterTime(void); 51 | int generalStatusDisplay(void); 52 | float energyGenerated(struct tm *tm, const int type); 53 | int ratedInformation(); 54 | //static void debugPrint(const char *format, ...){}; 55 | 56 | #endif //SOLARMON_H__ -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ; PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | default_envs = esp01_1m 13 | 14 | [env:esp01_1m] 15 | platform = espressif8266 16 | board = esp01_1m 17 | framework = arduino 18 | ;upload_port = /dev/ttyUSB1 19 | ;upload_protocol = esptool 20 | lib_deps = 21 | RemoteDebug @ 2.1.2 ;^3.0.5 22 | PubSubClient @ ^2.8 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/communication.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 amish 2 | // 3 | // This software is released under the MIT License. 4 | // https://opensource.org/licenses/MIT 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug 17 | #include "solarmon.h" 18 | 19 | extern WiFiClient wifiClient; 20 | extern PubSubClient client; 21 | const long TIMEOUT = 10000L; 22 | int debugFlag = 0; 23 | static RemoteDebug *commDebug; // static -> private - for only this file 24 | 25 | void communication_SetRemoteDebug(RemoteDebug *Debug) { 26 | commDebug = Debug; 27 | } 28 | int readport(uint8_t *recv_buf) 29 | { 30 | uint8_t inByte; 31 | int byteCnt = 0; 32 | long time_now = millis(); 33 | long time_prev = time_now; 34 | memset(recv_buf, 0, SIZE_OF_REPLY); 35 | while ((InverterPort.available() == 0)&&((time_now - time_prev) < 5000l)){ 36 | //wait for one second for inverter to respond 37 | time_now = millis(); 38 | }; 39 | commDebug->printf("Reading from Port \n"); 40 | do 41 | { 42 | InverterPort.readBytes(&inByte,1); 43 | //if (InverterPort.readBytes(&inByte,1) = 1) // read byte by byte: Serial.read returns int :( 44 | //commDebug->printf("readport: %03d: %02X >>> (%c)\n",byteCnt, inByte, inByte); 45 | recv_buf[byteCnt++] = inByte; 46 | } while ( (inByte != '\n') && (inByte != '\r') && (byteCnt < 150)); 47 | 48 | if ((recv_buf[0] == '^') && (recv_buf[1] == '0')) 49 | { 50 | commDebug->println("Command Refused \n"); 51 | strcpy((char *) recv_buf, "Command Refused"); 52 | return (-2); 53 | } 54 | if ((recv_buf[0] != '^') || (recv_buf[1] != 'D')) 55 | { 56 | commDebug->printf("Response corrupted: %s\n", recv_buf); 57 | strcpy((char *) recv_buf, "Response corrupted "); 58 | return (-1); 59 | } 60 | //commDebug->printf("%d --->>%s\n", n, recv_buf); 61 | uint16_t recCRC = (recv_buf[byteCnt-3] << 8) + recv_buf[byteCnt-2] ; 62 | uint32_t caclCRC = cal_crc_half(recv_buf, byteCnt-3); 63 | if (caclCRC != recCRC) // TODO: this work around as CRC is not addind up for some commands 64 | { 65 | commDebug->printf(" CRC (%04X) Failed in Received message len %d %02X%02X :::: %04X\n", 66 | caclCRC,byteCnt, recv_buf[byteCnt-3], recv_buf[byteCnt-2], recCRC ); 67 | commDebug->printf("CRC : %s\n", recv_buf); //return FALSE; 68 | } 69 | recv_buf[byteCnt-3]= '\0'; //terminate the string before CRC 70 | return TRUE; 71 | } 72 | // Sending byte by byte as Inverter DSP does not like stream 73 | int sendport(uint8_t *tmpbuffer) 74 | { 75 | int n, j, i = 0; 76 | int status = FALSE; 77 | int len = strlen((char *)tmpbuffer); 78 | uint8_t t; 79 | commDebug->printf("enter write %s with len %d\n", tmpbuffer, len); 80 | 81 | do // send one char at a time for slow CPU on inverter 82 | { 83 | t = tmpbuffer[i++]; 84 | n = InverterPort.write(&t, 1); 85 | for (j = 0; j < 300; j++) 86 | ; //need to adjust for best response 87 | } while ((t != '\r') && (i < len + 1)); // 88 | if (n < 0){ 89 | commDebug->printf("writeof (%d) bytes failed!\n", len + 1); 90 | commDebug->println("write failed"); 91 | } 92 | else 93 | { 94 | status = TRUE; 95 | commDebug->printf(" %d Bytes Sent successfully to staus %d\n", i, n); 96 | commDebug->println("write completed"); 97 | } 98 | //InverterPort.flush(); // clean any remaining data and wait for transmission to complete 99 | return status; 100 | } 101 | int sendCommand(const char *cmd) 102 | { 103 | uint8_t sendStr[30]; 104 | uint8_t cmdLen = strlen((char *)cmd); 105 | uint16_t cmdCrc = cal_crc_half((uint8_t *)cmd, cmdLen); 106 | while (InverterPort.available()>0) 107 | InverterPort.read(); //arduino has no defined method to clear incomming buffer 108 | // So manually clean it 109 | memset(sendStr, 0, 30); 110 | sprintf((char *)sendStr, "%s%c%c\r", cmd, (uint8_t)(cmdCrc >> 8), (uint8_t)(cmdCrc & 0xFF)); 111 | commDebug->printf("sendCommand: %02u bytes %s to send---CRC %04X\n", strlen((char *)sendStr), sendStr, cmdCrc); 112 | return (sendport(sendStr)); 113 | } 114 | uint16_t cal_crc_half(uint8_t *pin, uint8_t len) 115 | { 116 | uint16_t crc; 117 | uint8_t da; 118 | uint8_t *ptr; 119 | uint8_t bCRCHign; 120 | uint8_t bCRCLow; 121 | uint16_t crc_ta[16] = 122 | { 123 | 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 124 | 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef}; 125 | ptr = pin; 126 | crc = 0; 127 | while (len-- != 0) 128 | { 129 | da = ((uint8_t)(crc >> 8)) >> 4; 130 | crc <<= 4; 131 | crc ^= crc_ta[da ^ (*ptr >> 4)]; 132 | da = ((uint8_t)(crc >> 8)) >> 4; 133 | crc <<= 4; 134 | crc ^= crc_ta[da ^ (*ptr & 0x0f)]; 135 | ptr++; 136 | } 137 | bCRCLow = crc; 138 | bCRCHign = (uint8_t)(crc >> 8); 139 | if (bCRCLow == 0x28 || bCRCLow == 0x0d || bCRCLow == 0x0a) 140 | { 141 | bCRCLow++; 142 | } 143 | if (bCRCHign == 0x28 || bCRCHign == 0x0d || bCRCHign == 0x0a) 144 | { 145 | bCRCHign++; 146 | } 147 | crc = ((uint16_t)bCRCHign) << 8; 148 | crc += bCRCLow; 149 | return (crc); 150 | } 151 | int sendMQTTmessage(const char *topic_str, const char *msg) 152 | { 153 | int mqttStatus = 5; 154 | char topic[100]; 155 | sprintf(topic, "%s/%s/state",HA_SENSOR_TOPIC, topic_str); 156 | //int qos = 1; 157 | //int retained = 0; 158 | mqttStatus = client.publish(topic, msg); 159 | if (mqttStatus == false) 160 | { 161 | commDebug->printf("Failed to publish message \'%s\':\'%s\', return code %d\n", topic, msg, mqttStatus); 162 | //perror ("sendMQTT"); 163 | } 164 | return mqttStatus; 165 | } 166 | /* 167 | Registering in Hass https://www.home-assistant.io/docs/mqtt/discovery/ 168 | 169 | Configuration Parameters: 170 | 171 | topic: homeassistant/sensor/voltronics_/config 172 | Payload: 173 | { 174 | "name": "", 175 | "unit_of_measurement": "", 176 | "unique_id": "toppic_str", 177 | "state_topic": "homeassistant/sensor//state", 178 | "icon": "mdi:", 179 | "retain": true 180 | } 181 | */ 182 | int registerTopic(const char *topic_str, const char *unit_name, const char *icon_name, int isStop) 183 | { 184 | int mqttStatus; 185 | char topic[100]; 186 | char message[500]; 187 | sprintf(topic, "%s/voltronic_%s/config",HA_SENSOR_TOPIC, topic_str); 188 | if (isStop == 1) 189 | sprintf(message, "{ \n\ 190 | \"name\": \"voltronic_%s\",\n\ 191 | \"unit_of_measurement\": \"%s\",\n\ 192 | \"unique_id\": \"%s\",\n\ 193 | \"state_topic\": \"%s/%s/state\",\n\ 194 | \"icon\": \"mdi:%s\",\n\ 195 | \"retain\": false\n\ 196 | }\n", 197 | topic_str, unit_name, topic_str, HA_SENSOR_TOPIC, topic_str,icon_name); 198 | else 199 | sprintf(message, " "); 200 | //commDebug->printf("topic: %s\n", topic); 201 | //commDebug->printf("message: %s\n", message); 202 | //int qos = 1; 203 | int retained = 0; 204 | //int msglen = strlen(message); 205 | mqttStatus = client.publish(topic, message, retained); 206 | if (!mqttStatus) 207 | { 208 | commDebug->printf("%d Failed to publish message \'%s\': \'%s\', return code %d\n", strlen(message), topic, message, mqttStatus); 209 | //perror ("sendMQTT"); 210 | } 211 | return mqttStatus; 212 | } 213 | 214 | int sensorInit(int isStop) 215 | { 216 | int status = FALSE; 217 | status = registerTopic( "Inverter_mode", "", "solar-power", isStop); 218 | status = registerTopic( "AC_grid_voltage","V","power-plug", isStop); 219 | status = registerTopic( "AC_grid_frequency", "Hz", "current-ac", isStop); 220 | status = registerTopic( "AC_out_voltage","V","power-plug", isStop); 221 | status = registerTopic( "AC_out_frequency","Hz","current-ac", isStop); 222 | status = registerTopic( "PV_in_voltage","V","solar-panel-large", isStop); 223 | status = registerTopic( "PV_in_power","W","solar-panel-large", isStop); 224 | status = registerTopic( "SCC_voltage","V","current-dc", isStop); 225 | status = registerTopic( "Load_pct","%","brightness-percent", isStop); 226 | status = registerTopic( "Load_watt","W","chart-bell-curve", isStop); 227 | status = registerTopic( "Load_va","VA","chart-bell-curve", isStop); 228 | status = registerTopic( "Heatsink_temperature","C","details", isStop); 229 | status = registerTopic( "MPPT_temperature","C","details", isStop); 230 | status = registerTopic( "Battery_capacity","%","battery-outline", isStop); 231 | status = registerTopic( "Battery_voltage","V","battery-outline", isStop); 232 | status = registerTopic( "Battery_charge_current","A","current-dc", isStop); 233 | status = registerTopic( "Battery_discharge_current","A","current-dc", isStop); 234 | status = registerTopic( "Load_status_on","","power", isStop); 235 | status = registerTopic( "AC_Power_dir","","power", isStop); 236 | status = registerTopic( "DC_AC_dir","","power", isStop); 237 | status = registerTopic( "SCC_charge_on","","power", isStop); 238 | /* 239 | status = registerTopic( "AC_charge_on","","power", isStop); 240 | status = registerTopic( "Battery_recharge_voltage","V","current-dc", isStop); 241 | status = registerTopic( "Battery_under_voltage","V","current-dc", isStop); 242 | status = registerTopic( "Battery_bulk_voltage","V","current-dc", isStop); 243 | status = registerTopic( "Battery_float_voltage","V","current-dc", isStop); 244 | status = registerTopic( "Max_grid_charge_current","A","current-ac", isStop); 245 | status = registerTopic( "Max_charge_current","A","current-ac", isStop); 246 | */ 247 | status = registerTopic( "Out_source_priority","","grid",isStop); 248 | status = registerTopic( "Charger_source_priority","","solar-power", isStop); 249 | status = registerTopic( "wifi_strength","dBM","wifi", isStop); 250 | status = registerTopic( "raw_command","","", isStop); 251 | return status; 252 | } -------------------------------------------------------------------------------- /src/qmisc.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 amish 2 | // 3 | // This software is released under the MIT License. 4 | // https://opensource.org/licenses/MIT 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "solarmon.h" 13 | const char *inverterStatus[] = { 14 | "Power on mode", 15 | "Standby mode", 16 | "Bypass mode", 17 | "Battery mode", 18 | "Fault mode", 19 | "Hybrid mode" }; 20 | 21 | int getInverterTime(void) 22 | { 23 | char tmpBuf[30], protocol[3]; 24 | //int year, mon, day, hr, min, sec; 25 | if (!sendCommand("^P005PI")) 26 | return FALSE; 27 | if (!readport((uint8_t *)tmpBuf)) 28 | return FALSE; 29 | debugPort.printf("%s\n", tmpBuf); 30 | sscanf(tmpBuf, "^D005%2s", protocol); 31 | if (!sendCommand("^P004T")) 32 | return FALSE; 33 | if (!readport((uint8_t *)tmpBuf)) 34 | return FALSE; 35 | /* 36 | debugPort.printf("%s\n", tmpBuf); 37 | sscanf(tmpBuf,"^D017%04d%02d%02d%02d%02d%02d",&year,&mon,&day,&hr,&min,&sec); 38 | printf("\n\tInverter Time : %04d/%02d/%02d %02d:%02d:%02d\t\t\t\tProtocol:%s\n",year,mon,day,hr,min,sec, protocol); 39 | */ 40 | return true; 41 | } 42 | int getInverterStatus(void) 43 | { 44 | char tmpBuf[SIZE_OF_REPLY]; 45 | if (sendCommand("^P006MOD") != TRUE) 46 | return FALSE; 47 | debugPort.println("sent ^P006MOD"); 48 | if (readport((uint8_t *)tmpBuf) != TRUE) 49 | return FALSE; 50 | debugPort.printf("%s\n", tmpBuf); 51 | if (!strstr(tmpBuf, "^D005")) 52 | return FALSE; 53 | sendMQTTmessage( "Inverter_mode", inverterStatus[tmpBuf[6]-'0']); 54 | return TRUE; 55 | } -------------------------------------------------------------------------------- /src/qpigs.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 amish 2 | // 3 | // This software is released under the MIT License. 4 | // https://opensource.org/licenses/MIT 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "solarmon.h" 13 | 14 | const char *mpptChargerStatus[] = { 15 | "Abnormal", 16 | "Not Charging", 17 | "Charging"}; 18 | 19 | const char *PowerDirection[] = { 20 | "None", 21 | "Charging", 22 | "Discharging"}; 23 | const char *LineDirection[] = { 24 | "None ---", 25 | "Input <<<", 26 | "Output >>>"}; 27 | 28 | int generalStatusDisplay(void) 29 | { 30 | int value_grid_voltage_, value_grid_frequency_, value_ac_output_voltage_, 31 | value_ac_output_frequency_, value_ac_output_apparent_power_, 32 | value_ac_output_active_power_, value_output_load_percent_, 33 | value_battery_voltage_, value_battery_voltage_scc1_, 34 | value_battery_voltage_scc2_, value_battery_discharge_current_, 35 | valuebattery_charging_current_, value_battery_capacity_percent_, 36 | value_inverter_heat_sink_temperature_, value_mppt1_charger_temperature_, 37 | value_mppt2_charger_temperature_, value_pv1_input_power_, 38 | value_pv2_input_power_, value_pv1_input_voltage_, value_pv2_input_voltage_, 39 | value_setting_value_configuration_state_, value_mppt1_charger_status_, 40 | mppt2_charger_status_, value_load_connection_, value_battery_power_direction_, 41 | value_dc_ac_power_direction_, value_line_power_direction_, value_local_parallel_id_; 42 | char tmpBuf[254]; //sometimes the device sends long junk causing the system to crash 43 | debugPort.println("sending ^P005GS"); 44 | if (sendCommand("^P005GS") != TRUE) 45 | return FALSE; 46 | debugPort.println("sent ^P005GS"); 47 | if (readport((uint8_t *)tmpBuf) != TRUE) 48 | return FALSE; 49 | debugPort.printf("%s\n", tmpBuf); 50 | if (!strstr(tmpBuf, "^D106")) 51 | return FALSE; 52 | 53 | sscanf(tmpBuf, // NOLINT 54 | "^D106%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", // NOLINT 55 | &value_grid_voltage_, &value_grid_frequency_, &value_ac_output_voltage_, // NOLINT 56 | &value_ac_output_frequency_, &value_ac_output_apparent_power_, // NOLINT 57 | &value_ac_output_active_power_, &value_output_load_percent_, // NOLINT 58 | &value_battery_voltage_, &value_battery_voltage_scc1_, &value_battery_voltage_scc2_, // NOLINT 59 | &value_battery_discharge_current_, &valuebattery_charging_current_, // NOLINT 60 | &value_battery_capacity_percent_, &value_inverter_heat_sink_temperature_, // NOLINT 61 | &value_mppt1_charger_temperature_, &value_mppt2_charger_temperature_, // NOLINT 62 | &value_pv1_input_power_, &value_pv2_input_power_, &value_pv1_input_voltage_, // NOLINT 63 | &value_pv2_input_voltage_, &value_setting_value_configuration_state_, // NOLINT 64 | &value_mppt1_charger_status_, &mppt2_charger_status_, &value_load_connection_, // NOLINT 65 | &value_battery_power_direction_, &value_dc_ac_power_direction_, 66 | &value_line_power_direction_, &value_local_parallel_id_); 67 | //convert to ascii and send MQTT messages 68 | sprintf(tmpBuf, "%5.1f", (float)value_grid_voltage_ / 10); 69 | sendMQTTmessage("AC_grid_voltage", tmpBuf); 70 | sprintf(tmpBuf, "%5.1f", (float)value_grid_frequency_ / 10); 71 | sendMQTTmessage("AC_grid_frequency", tmpBuf); 72 | sprintf(tmpBuf, "%5.1f", (float)value_ac_output_voltage_ / 10); 73 | sendMQTTmessage("AC_out_voltage", tmpBuf); 74 | sprintf(tmpBuf, "%5.1f", (float)value_ac_output_frequency_ / 10); 75 | sendMQTTmessage("AC_out_frequency", tmpBuf); 76 | sprintf(tmpBuf, "%5.1f", (float)value_pv1_input_voltage_ / 10); 77 | sendMQTTmessage("PV_in_voltage", tmpBuf); 78 | sprintf(tmpBuf, "%d", value_pv1_input_power_); 79 | sendMQTTmessage("PV_in_power", tmpBuf); 80 | sprintf(tmpBuf, "%5.1f", (float)value_battery_voltage_scc1_ / 10); 81 | sendMQTTmessage("SCC_voltage", tmpBuf); 82 | sprintf(tmpBuf, "%d", value_output_load_percent_); 83 | sendMQTTmessage("Load_pct", tmpBuf); 84 | sprintf(tmpBuf, "%d", value_ac_output_active_power_); 85 | sendMQTTmessage("Load_watt", tmpBuf); 86 | sprintf(tmpBuf, "%d", value_ac_output_apparent_power_); 87 | sendMQTTmessage("Load_va", tmpBuf); 88 | sprintf(tmpBuf, "%d", value_inverter_heat_sink_temperature_); 89 | sendMQTTmessage("Heatsink_temperature", tmpBuf); 90 | sprintf(tmpBuf, "%d", value_mppt1_charger_temperature_); 91 | sendMQTTmessage("MPPT_temperature", tmpBuf); 92 | sprintf(tmpBuf, "%d", value_battery_capacity_percent_); 93 | sendMQTTmessage("Battery_capacity", tmpBuf); 94 | sprintf(tmpBuf, "%5.1f", (float)value_battery_voltage_ / 10); 95 | sendMQTTmessage("Battery_voltage", tmpBuf); 96 | sprintf(tmpBuf, "%d", valuebattery_charging_current_); 97 | sendMQTTmessage("Battery_charge_current", tmpBuf); 98 | sprintf(tmpBuf, "%d", value_battery_discharge_current_); 99 | sendMQTTmessage("Battery_discharge_current", tmpBuf); 100 | sendMQTTmessage("Load_status_on", (value_load_connection_)?"Connected":"Disconnected"); 101 | sendMQTTmessage("SCC_charge_on", mpptChargerStatus[value_mppt1_charger_status_]); 102 | sendMQTTmessage("AC_Power_dir", LineDirection[value_line_power_direction_]); 103 | sendMQTTmessage("DC_AC_dir", PowerDirection[value_dc_ac_power_direction_]); 104 | return true; 105 | } 106 | -------------------------------------------------------------------------------- /src/qpiri.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 amish 2 | // 3 | // This software is released under the MIT License. 4 | // https://opensource.org/licenses/MIT 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "solarmon.h" 13 | 14 | 15 | static const char *ChargeSource[] = { 16 | "Solar first", 17 | "Solar & Utility", 18 | "Only solar" 19 | }; 20 | static const char *OutputPriority[] = { 21 | "S-U-B", //Solar-Utility-Battery", 22 | "S-B-U" //Solar-Battery-Utility" 23 | }; 24 | #if 0 //for future use 25 | static const char *BatteryType[] = { 26 | "AGM", 27 | "Flooded", 28 | "User" 29 | }; 30 | static const char *OutputSetting[] = { 31 | " Single", 32 | "Parallel", 33 | " Phase 1", 34 | " Phase 2", 35 | " Phase 3" 36 | }; 37 | #endif //for future use 38 | int ratedInformation(void) 39 | { 40 | int value_AC_input_rating_voltage_, value_AC_input_rating_current_, value_AC_output_rating_voltage_, 41 | value_AC_output_rating_frequency_, value_AC_output_rating_current_, 42 | value_AC_output_rating_apparent_power_, value_AC_output_rating_active_power_, 43 | value_Battery_rating_voltage_, value_Battery_recharge_voltage_, value_Battery_redischarge_voltage_, 44 | value_Battery_under_voltage_, value_Battery_bulk_voltage_, value_Battery_float_voltage_, 45 | value_Battery_type_, value_Max_AC_charging_current_, value_Max_charging_current_, 46 | value_Input_voltage_range_, value_Output_source_priority_, value_Charger_source_priority_, 47 | value_Parallel_max_num_, value_Machine_type_, value_Topology_, value_Output_model_setting_, 48 | value_Solar_power_priority_, value_MPPT_string_; 49 | 50 | char tmpBuf[255]; //sometimes the device sends long junk causing the system to crash 51 | if (sendCommand("^P007PIRI")!=TRUE) 52 | return FALSE; 53 | if (readport((uint8_t *)tmpBuf)!=TRUE) 54 | return FALSE; 55 | debugPort.printf("%s\n", tmpBuf); 56 | if (!strstr( tmpBuf, "^D088")) 57 | return FALSE; 58 | 59 | sscanf( tmpBuf, // NOLINT 60 | "^D088%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", 61 | &value_AC_input_rating_voltage_, &value_AC_input_rating_current_, &value_AC_output_rating_voltage_, 62 | &value_AC_output_rating_frequency_, &value_AC_output_rating_current_, 63 | &value_AC_output_rating_apparent_power_, &value_AC_output_rating_active_power_, 64 | &value_Battery_rating_voltage_, &value_Battery_recharge_voltage_, &value_Battery_redischarge_voltage_, 65 | &value_Battery_under_voltage_, &value_Battery_bulk_voltage_, &value_Battery_float_voltage_, 66 | &value_Battery_type_, &value_Max_AC_charging_current_, &value_Max_charging_current_, 67 | &value_Input_voltage_range_, &value_Output_source_priority_, &value_Charger_source_priority_, 68 | &value_Parallel_max_num_, &value_Machine_type_, &value_Topology_, &value_Output_model_setting_, 69 | &value_Solar_power_priority_, &value_MPPT_string_); 70 | 71 | sendMQTTmessage( "Out_source_priority", OutputPriority[value_Output_source_priority_]); 72 | sendMQTTmessage("Charger_source_priority", ChargeSource[value_Charger_source_priority_]); 73 | 74 | return TRUE; 75 | } -------------------------------------------------------------------------------- /src/solarmon.cpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 amish 2 | // 3 | // This software is released under the MIT License. 4 | // https://opensource.org/licenses/MIT 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "RemoteDebug.h" //https://github.com/JoaoLopesF/RemoteDebug 13 | #include "solarmon.h" 14 | #include "passwd.h" 15 | 16 | ESP8266WiFiMulti wifiMulti; 17 | WiFiClient wifiClient; 18 | PubSubClient client; 19 | 20 | const char *ssid = SSID; 21 | const char *password = WiFi_KEY; 22 | const char *ssid2 = SSID2; 23 | const char *password2 = WiFi_KEY2; 24 | const uint32_t connectTimeoutMs = 5000; 25 | const char *hostname = CLIENTID; 26 | const char *api_passwd = API_PASS; 27 | const char mqtt_hostname[] = MQTT_HOST; // replace this with IP address of machine 28 | const uint8_t startingDebugLevel = debugPort.ANY; 29 | void communication_SetRemoteDebug(RemoteDebug *Debug); 30 | // HardwareSerial debugPort = Serial; 31 | // HardwareSerial InverterPort = Serial1; 32 | // PubSub call back crashes on calling external/library functions 33 | static char mqttCmdBuf[SIZE_OF_REPLY]; // avoiding dynamic allocation in the ESP processor 34 | static char mqttRecBuf[SIZE_OF_REPLY]; // avoiding malloc/calloc in the ESP processor 35 | static int isMqttCmd = false; 36 | 37 | void callback(const char *topic, byte *payload, unsigned int length) 38 | { 39 | if (!isMqttCmd) 40 | { 41 | debugPort.println("Message Arrived on"); 42 | debugPort.println(topic); 43 | 44 | debugPort.print("Message:"); 45 | unsigned int i; 46 | for (i = 0; i < length; i++) 47 | { 48 | debugPort.print((char)payload[i]); 49 | mqttCmdBuf[i] = (char)payload[i]; 50 | } 51 | // mqttCmdBuf[i] = '\r'; 52 | isMqttCmd = true; // set flag for further processing of signal 53 | debugPort.println(); 54 | debugPort.println("-----------------------"); 55 | } 56 | } 57 | void setupOTA() 58 | { 59 | ArduinoOTA.onStart([]() 60 | { 61 | String type; 62 | if (ArduinoOTA.getCommand() == U_FLASH) 63 | { 64 | type = "sketch"; 65 | } 66 | else 67 | { // U_FS 68 | type = "filesystem"; 69 | } 70 | 71 | // NOTE: if updating FS this would be the place to unmount FS using FS.end() 72 | debugPort.println("Start updating " + type); }); 73 | ArduinoOTA.onEnd([]() 74 | { debugPort.println("\nEnd"); }); 75 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) 76 | { debugPort.printf("Progress: %u%%\r", (progress / (total / 100))); }); 77 | ArduinoOTA.onError([](ota_error_t error) 78 | { 79 | debugPort.printf("Error[%u]: ", error); 80 | if (error == OTA_AUTH_ERROR) 81 | { 82 | debugPort.println("Auth Failed"); 83 | } 84 | else if (error == OTA_BEGIN_ERROR) 85 | { 86 | debugPort.println("Begin Failed"); 87 | } 88 | else if (error == OTA_CONNECT_ERROR) 89 | { 90 | debugPort.println("Connect Failed"); 91 | } 92 | else if (error == OTA_RECEIVE_ERROR) 93 | { 94 | debugPort.println("Receive Failed"); 95 | } 96 | else if (error == OTA_END_ERROR) 97 | { 98 | debugPort.println("End Failed"); 99 | } }); 100 | ArduinoOTA.begin(); 101 | } 102 | 103 | void setup() 104 | { 105 | // debugPort.begin(9600); // For Logger, enable if using serial logging 106 | debugPort.println("Booting"); 107 | InverterPort.begin(2400); // for Inverter 108 | // Register multi WiFi networks 109 | wifiMulti.addAP(ssid, password); 110 | wifiMulti.addAP(ssid2, password2); 111 | client.setClient(wifiClient); 112 | WiFi.mode(WIFI_STA); 113 | // WiFi.begin(ssid, password); 114 | while (wifiMulti.run(connectTimeoutMs) != WL_CONNECTED) 115 | { 116 | debugPort.println("Connection Failed! Rebooting..."); 117 | delay(5000); 118 | ESP.restart(); 119 | } 120 | setupOTA(); 121 | // Hostname defaults to esp8266-[ChipID] 122 | WiFi.setHostname(hostname); 123 | // starting over wifi 124 | debugPort.begin(hostname, startingDebugLevel); 125 | 126 | debugPort.println("Ready"); 127 | debugPort.print(hostname); 128 | debugPort.print("IP address: "); 129 | debugPort.println(WiFi.localIP()); 130 | // implement rest of the code 131 | client.setServer(mqtt_hostname, 1883); // default port for mqtt is 1883 132 | client.setBufferSize(500); // for large mqtt messages 133 | client.setCallback(callback); 134 | while (!client.connected()) 135 | { 136 | debugPort.println("Connecting to MQTT..."); 137 | 138 | if (client.connect(CLIENTID)) 139 | { 140 | debugPort.println("connected"); 141 | } 142 | else 143 | { 144 | 145 | debugPort.print("failed with state "); 146 | debugPort.print(client.state()); 147 | delay(10000); // wait for watchdog reset 148 | } 149 | } 150 | client.publish("homeassistant/test", "Hello from ESP-solarmon"); 151 | if (client.subscribe(MQTT_RAW_CMD_TOPIC, 0)) 152 | debugPort.println("Subscribed to topic"); 153 | // required to enable WIFI remotedebug in communication.h 154 | communication_SetRemoteDebug(&debugPort); 155 | // Register the senor on HA MQTT 156 | sensorInit(1); 157 | } 158 | 159 | // this function reconnect wifi as well as broker if connection gets disconnected. 160 | void reconnect() 161 | { 162 | int status; 163 | while (!client.connected()) 164 | { 165 | status = WiFi.status(); 166 | if (status != WL_CONNECTED) 167 | { 168 | wifiMulti.run(connectTimeoutMs); 169 | while (WiFi.status() != WL_CONNECTED) 170 | { 171 | delay(500); 172 | debugPort.println("."); 173 | } 174 | debugPort.println("Connected to AP"); 175 | } 176 | debugPort.print("Connecting to Broker …"); 177 | debugPort.print(mqtt_hostname); 178 | 179 | if (client.connect(CLIENTID, NULL, NULL)) 180 | { 181 | client.subscribe(MQTT_RAW_CMD_TOPIC, 0); 182 | debugPort.println("[DONE]"); 183 | } 184 | else 185 | { 186 | debugPort.println(" : retrying in 5 seconds]"); 187 | delay(5000); 188 | } 189 | } 190 | } 191 | 192 | void loop() 193 | { 194 | static long time_now = millis(); 195 | static long time_prev = 0l; 196 | static int time_count = 0; 197 | static bool polling = true; 198 | int status; 199 | reconnect(); 200 | ArduinoOTA.handle(); 201 | client.loop(); 202 | if (isMqttCmd) // if raw command is available on MQTT process it first 203 | { 204 | if (sendCommand(mqttCmdBuf) > 0) 205 | { 206 | status = readport((uint8_t *)mqttRecBuf); 207 | if (status != TRUE) 208 | { 209 | debugPort.println("Bad Response form inverter"); 210 | debugPort.println(status); 211 | debugPort.println(mqttRecBuf); 212 | } 213 | else if (status == -2) // command refused 214 | { 215 | debugPort.println(mqttRecBuf); 216 | debugPort.println("Message to be published to MQTT"); 217 | debugPort.printf("(%d): %s", strlen(mqttRecBuf), mqttRecBuf); 218 | } 219 | else 220 | { 221 | debugPort.println(mqttRecBuf); 222 | debugPort.println("Message to be published to MQTT"); 223 | debugPort.printf("(%d): %s", strlen(mqttRecBuf), mqttRecBuf); 224 | } 225 | } 226 | else 227 | { 228 | debugPort.println("Unable to send command to inverter"); 229 | } 230 | client.publish(MQTT_RAW_REPLY_TOPIC, mqttRecBuf); 231 | isMqttCmd = false; 232 | memset(mqttCmdBuf, 0, SIZE_OF_REPLY); 233 | } // no other commands if raw cammand is present 234 | else if ((time_now - time_prev) > 15000l) 235 | { 236 | time_prev = time_now; 237 | if (polling) 238 | { 239 | generalStatusDisplay(); 240 | sprintf(mqttCmdBuf, "%d", WiFi.RSSI()); // reuse mqtt buffer as it will ne empty in single thread 241 | sendMQTTmessage("wifi_strength", mqttCmdBuf); 242 | } 243 | else 244 | { 245 | getInverterStatus(); 246 | ratedInformation(); 247 | } 248 | polling = !polling; 249 | time_count++; 250 | if (time_count % 20 == 0) 251 | { 252 | sensorInit(1); //configure sensors every 5 min 253 | } 254 | debugPort.printf("in the debug loop -Amish\n"); 255 | } 256 | time_now = millis(); 257 | debugPort.handle(); 258 | } --------------------------------------------------------------------------------