├── AAAA_EVENTS.ino ├── AAA_DECODE.ino ├── AAA_HOMEPAGE.ino ├── AAA_INVERTERS.ino ├── AAA_LOG.ino ├── AAA_MENUPAGE.h ├── AA_CONSOLE.ino ├── ABOUT.ino ├── ASYSERVER.ino ├── Async_TCP.h ├── CONFIG_BASIS.ino ├── CONFIG_GEO.ino ├── DETAILSPAGE.h ├── ESP32-ECU_v0-9a.bin ├── ESP32_ECU_v1_3c.bin ├── ESP32_ECU_v1_4.ino ├── ESP32_ECU_v1_4b.bin ├── ESP32_ECU_v1_4c.bin ├── EXTERNAL.ino ├── FORCE.ino ├── HELPERS.ino ├── HTML.h ├── INFOPAGE.ino ├── ISR.ino ├── LICENSE ├── LOGPAGE.ino ├── MQTT.ino ├── MQTT_CONFIG.ino ├── OTA.H ├── OTA.h ├── PORTAL_HTML.h ├── PORTAL_WIFI.ino ├── README.md ├── Reboot__AP.ino ├── SERIAL.ino ├── SETPOWER.ino ├── SPIFFS_RW.ino ├── Start_WiFi.ino ├── TIJD_CALC.ino ├── TIJD_GET.ino ├── ZIGBEE_COORDINATOR.ino ├── ZIGBEE_HEALTH.ino ├── ZIGBEE_HELPERS.ino ├── ZIGBEE_PAIR.ino ├── ZIGBEE_POLLING.ino ├── ZIGBEE_QUERY.ino ├── ZIGBEE_QUERYING.ino ├── eventSource.h ├── handeforms.ino ├── handledata.ino ├── legende.ino ├── nmap_result.zip ├── result.xml ├── sniffs_100to500.txt └── test.ino /AAAA_EVENTS.ino: -------------------------------------------------------------------------------- 1 | /* we can push data to the website using an event for that 2 | * question is when is this needed. 3 | * if the webpage is opened or gets focus 4 | * when state has changed 5 | * when there is new data 6 | * 7 | * say there is new data but no client. should we send? no 8 | * So we only send when there is a client 9 | */ 10 | 11 | 12 | //void generalEvent () 13 | //{ 14 | //// this link provides the general data on the frontpage 15 | // char temp[15]={0}; 16 | // //char * temp; 17 | // char json[200]={0}; 18 | // uint8_t remote = 0; 19 | // //if(checkRemote( request->client()->remoteIP().toString()) ) remote = 1; // for the menu link 20 | // uint8_t night = 0; 21 | // if(!dayTime) { night = 1; } 22 | // 23 | //// {"ps":"05:27 hr","pe":"21:53 hr","cnt":3,"rm":0,"st":1,"sl":1} length 62 24 | // 25 | // snprintf(json, sizeof(json), "{\"count\":\"%d\",\"remote\":\"%d\",\"state\":\"%d\",\"sleep\":\"%d\"," ,inverterCount, remote, zigbeeUp, night ); 26 | // // add the polling times 27 | // if(minute(switchonTime) < 10 ) { 28 | // sprintf(temp,"\"ps\":\"0%d:0%d hr\",", hour(switchonTime), minute(switchonTime) ); 29 | // } else { 30 | // sprintf(temp,"\"ps\":\"0%d:%d hr\",", hour(switchonTime), minute(switchonTime) ); 31 | // } 32 | // strcat(json, temp); 33 | // if( minute(switchoffTime) < 10 ) { 34 | // sprintf(temp,"\"pe\":\"0%d:0%d hr\",", hour(switchoffTime), minute(switchoffTime) ); 35 | // } else { 36 | // sprintf(temp,"\"pe\":\"0%d:%d hr\",", hour(switchoffTime), minute(switchoffTime) ); 37 | // } 38 | // strcat(json, temp); 39 | // 40 | // Serial.println("length temp = " + String(strlen(temp))); 41 | // Serial.println("length json = " + String(strlen(json))); 42 | // Serial.println("json = " + String(json)); 43 | // //request->send(200, "text/json", json); 44 | // events.send( json, "general" ); 45 | //} 46 | // 47 | -------------------------------------------------------------------------------- /AAA_HOMEPAGE.ino: -------------------------------------------------------------------------------- 1 | 2 | const char ECU_HOMEPAGE [] PROGMEM = R"=====( 3 | 4 | ESP32-ECU 5 | 6 | 20 | 21 | 24 |
25 | 26 |
27 | 30 | ESP32 ECU

31 | 32 |

POWER / ENERGY @

waiting for output

33 |

Powered by Hansiart

checking / initialyzing zigbee network...

46 |
48 | )====="; 49 | 50 | 51 | // this is the original javascript. Made compact via https://www.digitalocean.com/community/tools/minify 52 | /* 53 | const char JAVA_SCRIPT[] PROGMEM = R"=====( 54 | window.addEventListener('visibilitychange', () =>{ 55 | if (document.visibilityState === 'visible') { 56 | //window.location.reload();} 57 | getGeneral(); 58 | setTimeout(getAll, 300); 59 | } 60 | }) 61 | 62 | var term; 63 | var table_row; 64 | var cnt = 0; 65 | var totalEn = 0; 66 | 67 | function loadScript() { 68 | getGeneral(); 69 | //console.log("getGeneral done, now getAll()"); 70 | setTimeout(getAll, 300); 71 | } 72 | function celbgc(cel) { 73 | if(cel.startsWith("e") || cel.startsWith("i")) { 74 | document.getElementById(cel).style = "background-color:#c6ff1a"; } 75 | else {document.getElementById(cel).style = "background-color:#a6a6a6"; 76 | } 77 | } 78 | 79 | function getAll() { 80 | totalEn = 0; 81 | //console.log("getAll count = " + cnt); 82 | for (let i = 0; i < cnt; i++) { 83 | term = "get.Power?inv=" + i; 84 | table_row = "inv" + i; 85 | getData(i); 86 | } 87 | 88 | } 89 | 90 | function getData(invnr) { 91 | var xhttp = new XMLHttpRequest(); 92 | xhttp.onreadystatechange = function() { 93 | if (this.readyState == 4 && this.status == 200) { 94 | var antwoord = this.responseText; 95 | //console.log("inverter = " + invnr); 96 | var obj = JSON.parse(antwoord); 97 | //cnt = obj.cnt; 98 | //console.log("cnt = " + cnt); 99 | var eN = obj.eN; 100 | 101 | var regel = "r" + String(invnr); 102 | document.getElementById(regel).style.display="table-row"; 103 | var cel = "i" + String(invnr); 104 | //document.getElementById(cel).innerHTML = String(invnr); 105 | document.getElementById(cel).innerHTML = ""; 43 | // 44 | //String zt = "summertime"; 45 | //switch (dst) { 46 | // case 2: zt="wintertime"; break; 47 | // case 0: zt="no dst set"; break; 48 | //} 49 | // 50 | //pageSend += "



system time = hr.   " + zt + "
"; 51 | // 52 | //pageSend += "firmware version : " + String(VERSION) + "
"; 53 | // 54 | //pageSend += "time retrieved today : "; if ( timeRetrieved ) { pageSend += "yes
"; } else { pageSend += "no
"; } 55 | // 56 | //// 57 | //long rssi = WiFi.RSSI(); 58 | //pageSend += "the wifi signalstrength (rssi) = " + String(rssi) + "
"; 59 | // 60 | //if ( Mqtt_Format != 0 ) { //bool == y en er is een mqtt adres, ja kijk dan of er een sensor is ingesteld 61 | //// check if connected 62 | // //String clientId = "ESPClient#"; 63 | // //String clientId = "ESP32-ECU"; 64 | // String clientId = getChipId() ; 65 | // pageSend += "de mqtt clientId = : " + clientId + "
"; 66 | // if ( MQTT_Client.connected() ) { 67 | // pageSend += "status mqtt : connected to " + Mqtt_Broker + "
"; 68 | // } else { 69 | // pageSend += "status mqtt : not connected
"; 70 | // } 71 | // } else { 72 | // pageSend += "mosquitto not configured
"; 73 | // pageSend += "check the mosquitto settings
"; 74 | //} 75 | // 76 | // int minutens = millis()/60000; 77 | // int urens = minutens/60; 78 | // int dagen = urens/24; 79 | // pageSend += "system up time: " + String(dagen) + " days " + String(urens-dagen*24) + " hrs " + String(minutens - urens*60) + " min.
"; 80 | // pageSend += "current errorCode = " + String(errorCode) + "
"; 81 | // 82 | // 83 | // 84 | //pageSend += "

"; 85 | //pageSend += ""; 87 | //pageSend += "
ESP INFORMATION
ESP CHIP ID nr: " + getChipId().substring(10); 86 | //pageSend += "min. heap size" + String(esp_get_minimum_free_heap_size()) + " bytes
Free heap" + String(esp_get_free_heap_size()) + " bytesremote IP" + request->client()->remoteIP().toString() + "
"; 88 | // 89 | //pageSend += "

variables dump

"; 90 | //pageSend += "value=" + String(value) + " inverterCount=" + String(inverterCount) + " zigbeeUp=" + String(zigbeeUp) + "
"; 91 | //pageSend += "switchonTime=" + String(switchonTime) + " switchoffTime=" + String(switchoffTime)+ "
"; 92 | //pageSend += "unixtime=" + String(now()) + "
"; 93 | //pageSend += "polled = " + String(polled[0]) + String(polled[1]) + String(polled[2]) + String(polled[3]) + String(polled[4]) + String(polled[5]) + String(polled[6]) + String(polled[7]) + String(polled[8]) + "
"; 94 | //pageSend += "ZB resetCounter = " + String(resetCounter); 95 | //pageSend += " pollOffset = " + String(pollOffset) + " Mqtt_Format = " + String(Mqtt_Format) + "
"; 96 | //pageSend += " Polling = " + String(Polling) + "
"; 97 | //#ifdef TEST 98 | //pageSend += " testCounter = " + String(testCounter) + " inv0 type = " + String(Inv_Prop[0].invType); 99 | //#endif 100 | //pageSend += "

Content filesystem :

"; 101 | // 102 | //File root = SPIFFS.open("/"); 103 | //File file = root.openNextFile(); 104 | //while (file) { 105 | // pageSend += String(file.name()) + " size "; 106 | // pageSend += String(file.size()) + "
"; 107 | // file = root.openNextFile(); 108 | //} 109 | // 110 | // //DebugPrintln("end infopage "); 111 | // //DebugPrint("de lengte van pageSend na de infopage = "); //DebugPrintln( pageSend.length() ); 112 | // request->send(200, "text/html", pageSend); //send the html code to the client 113 | //} 114 | -------------------------------------------------------------------------------- /ISR.ino: -------------------------------------------------------------------------------- 1 | // ******************************************************************************************* 2 | // interrupt service routine 3 | // ******************************************************************************************* 4 | // this routine is called when the button is pressed 5 | // the first part is a debouncer and takes care for the case of grid failure 6 | // when the program would think that a button has been pressed. 7 | // next we check if the button was short or long pressed. 8 | 9 | // ******************************************************************************************* 10 | // interrupt service routine 11 | // ******************************************************************************************* 12 | // this routine is called when the button is pressed 13 | // the first part is a debouncer and takes care for the case of grid failure 14 | // when the program would think that a button has been pressed. 15 | // next we check if the button was short or long pressed. 16 | 17 | IRAM_ATTR void isr() { 18 | actionFlag = 15; 19 | } 20 | 21 | void buttonPressed() { 22 | int val = 0; 23 | detachInterrupt(knop); // prevent interrupts during the isr 24 | Serial.println("interrupt"); 25 | //DebugPrintln(value); 26 | //we came here because the button was pressed. Now we check asfer a few 27 | //miliseconds if it still is, otherwise it was a not a manual press 28 | unsigned long starttime = millis(); // save the current time in currentMillis 29 | unsigned long endtime = millis(); 30 | while (endtime - starttime <= 50) 31 | { //2000 millis = 2 sec 32 | endtime = millis(); 33 | } 34 | val = digitalRead(knop); 35 | if (val == 1) { // means the button is released 36 | //DebugPrintln("button was pressed too short, accidental spike "); 37 | attachInterrupt(digitalPinToInterrupt(knop), isr, FALLING); 38 | return; // jump out, the button is high so it was a coincidence 39 | } 40 | //if we are here we passed the debounce 41 | digitalWrite(led_onb, HIGH); // led on 42 | //we wait again but longer 43 | Serial.println("button still pressed, so not accidentally"); 44 | // first the led is flashed 45 | while (endtime - starttime <= 500) { //2000 millis = 2 sec 46 | endtime = millis(); 47 | } 48 | digitalWrite(led_onb, LOW); // led off 49 | while (endtime - starttime <= 1500) 50 | { //2000 millis = 2 sec 51 | endtime = millis(); 52 | } 53 | 54 | // Now test the button again 55 | val = digitalRead(knop); 56 | if (val == 0) // still pressed 57 | { 58 | // we wait another time, somewhat longer 59 | Serial.println("button still pressed"); 60 | while (endtime - starttime <= 6000) //2000 millis = 2 sec 61 | { 62 | endtime = millis(); 63 | } 64 | // wdt_disable(); //othersise it's going to reset 65 | //first test if the button is still pressed 66 | //otherwise it's going to reset after a clumpsy operation 67 | val = digitalRead(knop); 68 | if (val == 1) // means the button is released 69 | { 70 | attachInterrupt(digitalPinToInterrupt(knop), isr, FALLING); 71 | return; // jump out, the button is high so it was a clumpsiness 72 | } 73 | digitalWrite(led_onb, HIGH); //de onboard led aan 74 | //Serial.println(F("button still pressed, onboard led on")); 75 | //next we wait some time again 76 | while (endtime - starttime <= 9000) //2000 millis = 2 sec 77 | { 78 | endtime = millis(); 79 | } 80 | 81 | digitalWrite(led_onb, LOW); //the onboard led off 82 | // we test the button again, 83 | val = digitalRead(knop); 84 | if (val == 0) // still pressed 85 | { 86 | Serial.println(F("button still pressed, open accesspoint")); 87 | actionFlag = 11; 88 | //attachInterrupt(digitalPinToInterrupt(knop), isr, FALLING); 89 | return; 90 | 91 | } 92 | else 93 | { 94 | Serial.println("button finally released, reboot"); 95 | ESP.restart(); // reboot 96 | } 97 | } 98 | else 99 | { // 100 | // the button was release after the seccond evaluation 101 | // so it was a switch command 102 | // if the switch is off it should go on or reversed 103 | // put here your switch logic 104 | // if(some evaluation){ 105 | // // here we can put our switch off command 106 | // ledsOffNow(true, true, "button"); 107 | // } 108 | // else 109 | // { // ledState = was 0 so switched off 110 | // // here we can put our switch on command 111 | // ledsOnNow(true, false, "button"); 112 | // } 113 | // attach the interrupt again 114 | attachInterrupt(digitalPinToInterrupt(knop), isr, FALLING); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 patience4711 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 | -------------------------------------------------------------------------------- /LOGPAGE.ino: -------------------------------------------------------------------------------- 1 | //const char HTML_LOGPAGE[] PROGMEM = R"=====( 2 | // 3 | //ESP32-ECU 4 | // 5 | // 6 | // 7 | // 36 | // 37 | // 38 | // 39 | //
40 | // 45 | //
ECU LOG 46 | //
47 | //
Last refresh : !@@! 48 | // 49 | //

50 | // 51 | // 52 | // 53 | // 54 | // 55 | // 56 | // 57 | //
TimeTypeCommand
58 | // 59 | // 60 | // 61 | //)====="; 62 | ////date system192.168.0.aaa.sss.ddd 63 | // 64 | //void zendPageLog( AsyncWebServerRequest *request ) { 65 | // 66 | // String uur = String(hour()); 67 | // if(hour() < 10) { 68 | // uur = "0" + String(hour()); 69 | // } 70 | // String minuten = String(minute()); 71 | // if(minute() < 10) { 72 | // minuten = "0" + String(minute()); 73 | // } 74 | // 75 | //String cont = ""; 76 | //cont += uur + " : " + minuten + " hr."; 77 | // char page[1536] = {0}; 78 | // char temp[100]={0}; 79 | // strcpy_P(page, ABOUT); 80 | //char toSEND=FPSTR(HTML_LOGPAGE); 81 | // 82 | ////toSEND.replace("HANSIART" , String(swName)); 83 | // 84 | //toSEND.replace("!@@!", cont); 85 | // 86 | ////DebugPrintln(" zendlogpage :build eventlist"); 87 | // byte Log_Count = 0; 88 | // Log_MaxReached ? Log_Count = Log_MaxEvents : Log_Count = logNr; // determine if the max number of event is already reached 89 | // 90 | // int j = logNr; 91 | // String content = ""; 92 | // for ( int i = 1; i <= Log_Count; i++ ) { 93 | // //Serial.println("een regel van de lijst, nummer i = "); //Serial.println(i); 94 | // j--; // we get the index of the last record in the array 95 | // //Serial.println("een regel van de lijst, nummer j = "); //Serial.println(j); 96 | // if (j ==-1) j = Log_MaxEvents - 1; // if we are under the first index of the array ,we go to the last 97 | // ////////////////// One table line /////////////////// 98 | //sprintf(temp,"%s%s%s", Log_list[j].Log_date, Log_list[j].Log_kind, Log_list[j].Log_message); 99 | // 100 | // ////////////////// One table line /////////////////// 101 | // } 102 | // //Serial.print(content); 103 | // toSEND.replace("", content); 104 | // request->send(200, "text/html", toSEND); 105 | //} 106 | // 107 | // void //Update_log(String what, String message) { 108 | // String nu; 109 | // //DebugPrintln("updating the log"); 110 | // if(what != "clear") { 111 | // nu = String(day()) + "-" + String(hour()) + ":" + String(minute()) + ":" + String(second()); 112 | // } else { 113 | // nu = ""; 114 | // what = "";} 115 | // //DebugPrint("nu = "); DebugPrintln(nu); 116 | // Log_list[logNr].Log_date = nu; 117 | // Log_list[logNr].Log_kind = what; 118 | // //Log_list[logNr].Log_issued = who; 119 | // Log_list[logNr].Log_message = message; 120 | // logNr++; 121 | // if (logNr >= Log_MaxEvents) 122 | // { 123 | // logNr = 0;//start again 124 | // Log_MaxReached = true; 125 | // } 126 | //} 127 | ////void //Update_log(String what, String message) { 128 | //// String nu; 129 | //// //DebugPrintln("updating the log"); 130 | //// if(what != "clear") { 131 | //// nu = String(day()) + "-" + String(hour()) + ":" + String(minute()) + ":" + String(second()); 132 | //// } else { 133 | //// nu = ""; 134 | //// what = "";} 135 | //// //DebugPrint("nu = "); DebugPrintln(nu); 136 | //// Log_list[logNr].Log_date = nu; 137 | //// Log_list[logNr].Log_kind = what; 138 | //// //Log_list[logNr].Log_issued = who; 139 | //// Log_list[logNr].Log_message = message; 140 | //// logNr++; 141 | //// if (logNr >= Log_MaxEvents) 142 | //// { 143 | //// logNr = 0;//start again 144 | //// Log_MaxReached = true; 145 | //// } 146 | ////} 147 | // 148 | //void Clear_Log() { 149 | // //Serial.println("clearing the log"); 150 | // if(logNr != 0) { 151 | // String nu=""; 152 | // String what=""; 153 | // String message=""; 154 | // for (int i=0; i <= Log_MaxEvents; i++) { 155 | // Update_Log("clear", ""); 156 | // } 157 | // logNr = 0;//start again 158 | // Log_MaxReached = false; 159 | // //Serial.println("log cleared"); 160 | // } 161 | //} 162 | -------------------------------------------------------------------------------- /MQTT.ino: -------------------------------------------------------------------------------- 1 | bool mqttConnect() { // 2 | /* this function checks if we are connected to the broker, if not connect anyway */ 3 | if( MQTT_Client.connected() ) { 4 | consoleOut("mqtt was connected"); 5 | return true; 6 | } 7 | // we are here because w'r not connected. Signal with the LED 8 | consoleOut("mqtt connecting"); 9 | ledblink(2,70); 10 | 11 | if (Mqtt_Port[0] == '\0' ) strcpy(Mqtt_Port, "1883"); // just in case .... 12 | uint8_t retry = 3; 13 | 14 | //String Clientid = getChipId(false); 15 | 16 | while (!MQTT_Client.connected()) { 17 | 18 | if ( MQTT_Client.connect( getChipId(false).c_str(), Mqtt_Username, Mqtt_Password) ) 19 | { 20 | //connected, so subscribe to inTopic (not for thingspeak) 21 | if(Mqtt_Format != 5 ) { 22 | String clientid = getChipId(false) + "/in"; 23 | if( MQTT_Client.subscribe ( clientid.c_str() ) ) { 24 | //if( MQTT_Client.subscribe ( Mqtt_inTopic ) ) { 25 | //if(diagNose) ws.textAll("subscribed to " + String(Mqtt_inTopic )); 26 | consoleOut("subscribed to " + clientid); 27 | } 28 | } 29 | consoleOut(F("mqtt connected")); 30 | Update_Log(3, "connected"); 31 | 32 | return true; 33 | 34 | } else { 35 | //String term = "connection failed state: " + String(MQTT_Client.state()); 36 | Update_Log(3, "failed"); 37 | if (!--retry) break; // stop when tried 3 times 38 | delay(500); 39 | } 40 | } 41 | // if we are here , no connection was made. 42 | 43 | consoleOut(F("mqtt connection failed")); 44 | return false; 45 | } 46 | 47 | // ************************************************************************* 48 | // process received mqtt 49 | // ************************************************************************* 50 | 51 | void MQTT_Receive_Callback(char *topic, byte *payload, unsigned int length) 52 | { 53 | 54 | // String Payload = ""; // convert the payload to a String... 55 | // for (int i = 0; i < length; i++) 56 | // { 57 | // Payload += (char)payload[i]; // convert to char, needed??? 58 | // } 59 | 60 | // ws.textAll("mqtt received " + Payload); 61 | 62 | JsonDocument doc; // We use json library to parse the payload 63 | // The function deserializeJson() parses a JSON input and puts the result in a JsonDocument. 64 | // DeserializationError error = deserializeJson(doc, Payload); // Deserialize the JSON document 65 | DeserializationError error = deserializeJson(doc, payload); // Deserialize the JSON document 66 | if (error) // Test if parsing succeeds. 67 | { 68 | consoleOut("mqtt no valid json "); 69 | return; 70 | } 71 | consoleOut("Deserialized JSON:"); 72 | serializeJson(doc, Serial); // Print in one line 73 | Serial.println(); 74 | // We check the kind of command format received with MQTT 75 | // if( doc["throttle"] != 0 ) 76 | //if(doc.containsKey("throttle")) 77 | if (!doc["throttle"].isNull()) 78 | { 79 | int Invert = doc["throttle"].as(); 80 | int throtVal = doc["val"].as(); 81 | String term = "mqtt got message {\"throttle\":" + String(Invert) + ",\"val\":" + String(throtVal) + "}"; 82 | consoleOut(term); 83 | if(Invert > inverterCount || Invert < 0 || throtVal > 700 || throtVal < 20 ) 84 | { 85 | consoleOut("invalid value(s), skipping"); 86 | return; 87 | } 88 | // write the desired throtval 89 | desiredThrottle[Invert] = throtVal; 90 | //Inv_Prop[invert].maxPower = throtVal; 91 | actionFlag = 240 + Invert; 92 | } 93 | 94 | if (!doc["poll"].isNull()) 95 | { 96 | //now we have a payload like {"poll",1} 97 | int inv = doc["poll"].as(); 98 | consoleOut( "got message {\"poll\":" + String(inv) + "}" ); 99 | 100 | if( !Polling && dayTime ) 101 | { 102 | 103 | //ws.textAll( "found {\"poll\" " + String(inv) + "}\"" ); 104 | 105 | iKeuze = inv; 106 | if(iKeuze == 99) { 107 | actionFlag = 48; // takes care for the polling of all inverters 108 | return; 109 | } 110 | 111 | if ( iKeuze < inverterCount ) 112 | { 113 | actionFlag = 220+inv; // takes care for the polling 114 | return; 115 | } else { 116 | consoleOut("mqtt error no inv " + String(iKeuze)); 117 | return; 118 | } 119 | } 120 | else 121 | { 122 | consoleOut("autopolling set or nightTime, skipping"); 123 | } 124 | consoleOut("nothing familiair found in mqtt"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /MQTT_CONFIG.ino: -------------------------------------------------------------------------------- 1 | const char MQTTCONFIG[] PROGMEM = R"=====( 2 | 3 | 8 |
9 | 14 | MOSQUITTO CONFIGURATION 15 |
16 |
17 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
format:  25 |
address
port
state idx: 
outtopic: 
intopic: 
username: 
password: 
client id: 
35 |

36 |
37 | 38 | 39 | )====="; 40 | //
  • test 41 | 42 | void zendPageMQTTconfig(AsyncWebServerRequest *request) { 43 | //DebugPrintln("we are at zendPageMQTTconfig"); 44 | //webPage = FPSTR(HTML_HEAD); 45 | //webPage.replace("tieTel", swname ); 46 | String webPage; 47 | webPage = FPSTR(HTML_HEAD); 48 | webPage += FPSTR(MQTTCONFIG); 49 | 50 | //altijd de mqtt gegevens terugzetten 51 | String Mqtt_inTopic=getChipId(false) + "/in"; 52 | webPage.replace("{mqttAdres}", String(Mqtt_Broker) ); 53 | webPage.replace("{mqttPort}", String(Mqtt_Port) ); 54 | //webPage.replace("{mqttinTopic}", String(Mqtt_inTopic) ); 55 | webPage.replace("{mqttoutTopic}", String(Mqtt_outTopic) ); 56 | webPage.replace("{mqttinTopic}", String(Mqtt_inTopic) ); 57 | webPage.replace("{mqtu}", String(Mqtt_Username) ); 58 | webPage.replace("{mqtp}", String(Mqtt_Password) ); 59 | webPage.replace("{idx}" , String(Mqtt_stateIDX) ); 60 | 61 | //String Mqtt_Clientid = getChipid(false); 62 | webPage.replace("{mqtc}" , getChipId(false)); 63 | switch (Mqtt_Format) { 64 | case 0: 65 | webPage.replace("fm_0", "selected"); 66 | break; 67 | case 1: 68 | webPage.replace("fm_1", "selected"); 69 | break; 70 | case 2: 71 | webPage.replace("fm_2", "selected"); 72 | break; 73 | case 3: 74 | webPage.replace("fm_3", "selected"); 75 | break; 76 | case 4: 77 | webPage.replace("fm_4", "selected"); 78 | break; 79 | case 5: 80 | webPage.replace("fm_5", "selected"); 81 | break; 82 | } 83 | request->send(200, "text/html", webPage); 84 | webPage=""; 85 | } 86 | 87 | //void handleMQTTconfig(AsyncWebServerRequest *request) { 88 | // //collect serverarguments 89 | // strcpy( Mqtt_Broker , request->getParam("mqtAdres") ->value().c_str() ); 90 | // strcpy( Mqtt_Port , request->getParam("mqtPort") ->value().c_str() ); 91 | // strcpy( Mqtt_outTopic, request->getParam("mqtoutTopic")->value().c_str() ); 92 | // strcpy( Mqtt_Username, request->getParam("mqtUser") ->value().c_str() ); 93 | // strcpy( Mqtt_Password, request->getParam("mqtPas") ->value().c_str() ); 94 | // strcpy( Mqtt_Clientid, request->getParam("mqtCi") ->value().c_str() ); 95 | // Mqtt_stateIDX = request->arg("mqidx").toInt(); //values are 0 1 2 96 | // Mqtt_Format = request->arg("fm").toInt(); //values are 0 1 2 3 4 5 97 | // 98 | // //DebugPrintln("saved mqttconfig"); 99 | // mqttConfigsave(); // 100 | // actionFlag=24; // reconnect with these settings 101 | // 102 | //} 103 | -------------------------------------------------------------------------------- /OTA.H: -------------------------------------------------------------------------------- 1 | const char otaIndex[] PROGMEM = R"=====( 2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 |

    ESP32 ECU FIRMWARE UPDATE



    19 | if the upload was successful the reboot button will appear.


    20 |
    21 | 22 | 23 |



    24 |
    progress: 0%



    25 |

    26 |


    27 | CANCEL

    28 | 29 | 59 | 60 | )====="; 61 | 62 | ////void esp32ota() { 63 | // 64 | // 65 | ////WebServer server(80); 66 | // 67 | ///* 68 | // * Login page 69 | // */ 70 | // 71 | ////const char* loginIndex = 72 | //// "
    " 73 | //// "" 74 | //// "" 75 | //// "" 79 | //// "
    " 80 | //// "
    " 81 | //// "" 82 | //// "" 83 | //// "" 84 | //// "" 85 | //// "" 86 | //// "
    " 87 | //// "
    " 88 | //// "" 89 | //// "" 90 | //// "" 91 | //// "
    " 92 | //// "
    " 93 | //// "" 94 | //// "" 95 | //// "" 96 | //// "" 97 | //// "
    " 76 | //// "
    ESP32 Login Page
    " 77 | //// "
    " 78 | //// "
    Username:
    Password:
    " 98 | ////"
    " 99 | ////""; 112 | // 113 | ///* 114 | // * Server Index Page 115 | // */ 116 | // 117 | // 118 | // 119 | /////* 120 | //// * setup function 121 | //// */ 122 | ////void setup(void) { 123 | //// Serial.begin(115200); 124 | //// 125 | //// // Connect to WiFi network 126 | //// WiFi.begin(ssid, password); 127 | //// Serial.println(""); 128 | //// 129 | //// // Wait for connection 130 | //// while (WiFi.status() != WL_CONNECTED) { 131 | //// delay(500); 132 | //// Serial.print("."); 133 | //// } 134 | //// Serial.println(""); 135 | //// Serial.print("Connected to "); 136 | //// Serial.println(ssid); 137 | //// Serial.print("IP address: "); 138 | //// Serial.println(WiFi.localIP()); 139 | //// 140 | //// /*use mdns for host name resolution*/ 141 | //// if (!MDNS.begin(host)) { //http://esp32.local 142 | //// Serial.println("Error setting up MDNS responder!"); 143 | //// while (1) { 144 | //// delay(1000); 145 | //// } 146 | //// } 147 | //// Serial.println("mDNS responder started"); 148 | //// /*return index page which is stored in serverIndex */ 149 | //// server.on("/", HTTP_GET, []() { 150 | //// server.sendHeader("Connection", "close"); 151 | //// server.send(200, "text/html", loginIndex); 152 | //// }); 153 | //// server.on("/serverIndex", HTTP_GET, []() { 154 | //// server.sendHeader("Connection", "close"); 155 | //// server.send(200, "text/html", serverIndex); 156 | //// }); 157 | //// /*handling uploading firmware file */ 158 | //// server.on("/update", HTTP_POST, []() { 159 | //// server.sendHeader("Connection", "close"); 160 | //// server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); 161 | //// ESP.restart(); 162 | //// }, []() { 163 | //// HTTPUpload& upload = server.upload(); 164 | //// if (upload.status == UPLOAD_FILE_START) { 165 | //// Serial.printf("Update: %s\n", upload.filename.c_str()); 166 | //// if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size 167 | //// Update.printError(Serial); 168 | //// } 169 | //// } else if (upload.status == UPLOAD_FILE_WRITE) { 170 | //// /* flashing firmware to ESP*/ 171 | //// if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 172 | //// Update.printError(Serial); 173 | //// } 174 | //// } else if (upload.status == UPLOAD_FILE_END) { 175 | //// if (Update.end(true)) { //true to set the size to the current progress 176 | //// Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); 177 | //// } else { 178 | //// Update.printError(Serial); 179 | //// } 180 | //// } 181 | //// }); 182 | //// server.begin(); 183 | ////} 184 | // 185 | ////void otaloop(void) { 186 | //// server.handleClient(); 187 | //// delay(1); 188 | ////} 189 | -------------------------------------------------------------------------------- /OTA.h: -------------------------------------------------------------------------------- 1 | const char otaIndex[] PROGMEM = R"=====( 2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 |

    ESP32 ECU FIRMWARE UPDATE



    19 | if the upload was successful the reboot button will appear.


    20 |
    21 | 22 | 23 |



    24 |
    progress: 0%



    25 |

    26 |


    27 | CANCEL

    28 | 29 | 59 | 60 | )====="; 61 | 62 | ////void esp32ota() { 63 | // 64 | // 65 | ////WebServer server(80); 66 | // 67 | ///* 68 | // * Login page 69 | // */ 70 | // 71 | ////const char* loginIndex = 72 | //// "
    " 73 | //// "" 74 | //// "" 75 | //// "" 79 | //// "
    " 80 | //// "
    " 81 | //// "" 82 | //// "" 83 | //// "" 84 | //// "" 85 | //// "" 86 | //// "
    " 87 | //// "
    " 88 | //// "" 89 | //// "" 90 | //// "" 91 | //// "
    " 92 | //// "
    " 93 | //// "" 94 | //// "" 95 | //// "" 96 | //// "" 97 | //// "
    " 76 | //// "
    ESP32 Login Page
    " 77 | //// "
    " 78 | //// "
    Username:
    Password:
    " 98 | ////"
    " 99 | ////""; 112 | // 113 | ///* 114 | // * Server Index Page 115 | // */ 116 | // 117 | // 118 | // 119 | /////* 120 | //// * setup function 121 | //// */ 122 | ////void setup(void) { 123 | //// Serial.begin(115200); 124 | //// 125 | //// // Connect to WiFi network 126 | //// WiFi.begin(ssid, password); 127 | //// Serial.println(""); 128 | //// 129 | //// // Wait for connection 130 | //// while (WiFi.status() != WL_CONNECTED) { 131 | //// delay(500); 132 | //// Serial.print("."); 133 | //// } 134 | //// Serial.println(""); 135 | //// Serial.print("Connected to "); 136 | //// Serial.println(ssid); 137 | //// Serial.print("IP address: "); 138 | //// Serial.println(WiFi.localIP()); 139 | //// 140 | //// /*use mdns for host name resolution*/ 141 | //// if (!MDNS.begin(host)) { //http://esp32.local 142 | //// Serial.println("Error setting up MDNS responder!"); 143 | //// while (1) { 144 | //// delay(1000); 145 | //// } 146 | //// } 147 | //// Serial.println("mDNS responder started"); 148 | //// /*return index page which is stored in serverIndex */ 149 | //// server.on("/", HTTP_GET, []() { 150 | //// server.sendHeader("Connection", "close"); 151 | //// server.send(200, "text/html", loginIndex); 152 | //// }); 153 | //// server.on("/serverIndex", HTTP_GET, []() { 154 | //// server.sendHeader("Connection", "close"); 155 | //// server.send(200, "text/html", serverIndex); 156 | //// }); 157 | //// /*handling uploading firmware file */ 158 | //// server.on("/update", HTTP_POST, []() { 159 | //// server.sendHeader("Connection", "close"); 160 | //// server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK"); 161 | //// ESP.restart(); 162 | //// }, []() { 163 | //// HTTPUpload& upload = server.upload(); 164 | //// if (upload.status == UPLOAD_FILE_START) { 165 | //// Serial.printf("Update: %s\n", upload.filename.c_str()); 166 | //// if (!Update.begin(UPDATE_SIZE_UNKNOWN)) { //start with max available size 167 | //// Update.printError(Serial); 168 | //// } 169 | //// } else if (upload.status == UPLOAD_FILE_WRITE) { 170 | //// /* flashing firmware to ESP*/ 171 | //// if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) { 172 | //// Update.printError(Serial); 173 | //// } 174 | //// } else if (upload.status == UPLOAD_FILE_END) { 175 | //// if (Update.end(true)) { //true to set the size to the current progress 176 | //// Serial.printf("Update Success: %u\nRebooting...\n", upload.totalSize); 177 | //// } else { 178 | //// Update.printError(Serial); 179 | //// } 180 | //// } 181 | //// }); 182 | //// server.begin(); 183 | ////} 184 | // 185 | ////void otaloop(void) { 186 | //// server.handleClient(); 187 | //// delay(1); 188 | ////} 189 | -------------------------------------------------------------------------------- /PORTAL_HTML.h: -------------------------------------------------------------------------------- 1 | 2 | /* the portal consists of 2 pages starting with portal homepage 3 | * this page has 3 buttons that are hidden or shown when applicable 4 | * it depends om event 101 or 100 how this page looks 5 | * the 2th page is the wifiform, when filled in and saved, we get the 6 | * 1st page, the ok buttom is shown 7 | * when we click it we return to the 1st page with the close button shown 8 | * and the ip info 9 | * after clicking the close button, we get the last page, the confirmation that 10 | * a restart is taking place. 11 | * 12 | */ 13 | 14 | const char PORTAL_HOMEPAGE[] PROGMEM = R"=====( 15 | 20 | 25 |
    26 |

    HANSIART WIFI CONFIG

    27 |
    28 |

    device mac adres : {ma}

    29 |
    30 |
    31 |


    32 | 33 | 36 |

    37 | 38 |

    39 | 40 |
    41 | 42 | 43 | )====="; 44 | 45 | 46 | 47 | 48 | 49 | // below here was tested allright 50 | 51 | // ****************************************************************************************** 52 | // THE WIFI FORM 53 | // ****************************************************************************************** 54 | const char PORTAL_WIFIFORM[] PROGMEM = R"=====( 55 | 56 | 63 | 70 |
    71 |
    72 | 75 | 76 |

    HANSIART CONFIG PORTAL

    77 |
    78 |
    79 |
    80 | aplijst 81 |
    82 |
    83 |
    84 |
    85 | 86 | 87 | 88 |
    wifi network
    wifi passwd
    admin passwd
    security level

    89 | 90 | 91 |
    92 |
    93 | )====="; 94 | 95 | const char PORTAL_CONFIRM[] PROGMEM = R"=====( 96 | 97 |
    98 |

    CONNECT TO WIFI




    99 | please wait until the led has flashed and is on again, than click OK

    100 |

    In case of troubles you can disconnect and reconnect from the AP now.

    101 |
    102 |

    103 | 104 | )====="; 105 | 106 | const char PORTAL_LIST[] PROGMEM = R"=====( 107 |
    {v} {r}dBm
    108 | )====="; 109 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESP32 read APS inverters 2 | This project is intended to read out APS micro inverters, (YC600, DS3, QS1). 3 | Tt is a continuation of the project [ESP-ECU](https://github.com/patience4711/read-APSystems-YC600-QS1-DS3). The reason to run this on a more powerfull platform is that the ESP8266 has not enough program space for necessary program extensions. 4 |
    5 | The ESP32 has another big advantage over the ESP8266, the presence of a second uart. We can use one uart for the zigbee moduele and we have the other UART available for debugging on the serial monitor via usb. This way it is easyer to debug in the development stage. 6 | There is also much more heap. Where the ESP8266 software can't be extended because there is not enough space for OTA, there is no limitation for the ESP32 so far. 7 | 8 | See it in action on [YouTube](https://youtu.be/WKFVQ6d8KhQ) 9 | 10 | ## status ## 11 | The system has been tested in practice with a YC600, QS1 and DS3 inverters and it works fine. 12 | 13 | ## purpose ## 14 | The system is intended for reading APS Systems inverters. The program can pair and poll YC600 QS1 and DS3 inverters, up to 9 pieces. The read values are displayed on a web page and sent via mosquitto in a Json format. 15 | 16 | Please see the WIKI for information on building it, the working, etc. 17 | 18 | ## downloads 19 | Sept 10 2025 : There is a new version [esp32-ecu_V1-4b](https://github.com/patience4711/ESP32-read-APS-inverters/blob/main/ESP32_ECU_v1_4b.bin)
    20 | Please pay attention: Due to a new value (calibration) you should check all settings, especially the inverter settings, calibration. Some values may get corrupted because the different spiffs usage. 21 | Aug 14 2025 : There is a new version [esp32-ecu_v1_3_expb](https://github.com/patience4711/ESP32-read-APS-inverters/blob/main/ESP32_ECU_v1_3_expb.bin) 22 | Please see changelog. 23 | 24 |

    25 | The frontpage:
    26 | ![frontpage](https://user-images.githubusercontent.com/12282915/229239150-05f6d29d-7620-4363-94fc-787b09d11fad.jpg) 27 |

    The details page:
    28 | ![details](https://github.com/user-attachments/assets/db17c692-f8a6-420a-94f1-a9f16b8cd3de) 29 | 30 | 31 | ## features 32 | - Simply to connect to your wifi 33 | - Easy add, delete and pair inverters 34 | - automatic polling or on demand via mqtt or http 35 | - data can be requested via http and mosquitto 36 | - There are 5 different mqtt json formats 37 | - Fast asyc webserver 38 | - a serial- and a web console to send commands and debugging 39 | - Smart timekeeping 40 | - A lot of system info on the webpage 41 | - Easy firmware update "Over The Air" 42 | - We can set a max output for inverters (throttling) 43 | 44 | ## the hardware 45 | It is nothing more than an esp32 device and a prepared cc2530, cc2531 zigbee module. And a powersupply. 46 | The zigbeemodule should be flashed with a firmware that is developped by kadsol : [CC25xx_firmware](https://github.com/Koenkk/zigbee2mqtt/files/10193677/discord-09-12-2022.zip). The firmware is also available [here](https://github.com/patience4711/read-APSystems-YC600-QS1-DS3/blob/main/cc25xx_firmware.zip). Much more info as to the development of this software can be found here https://github.com/Koenkk/zigbee2mqtt/issues/4221. 47 | 48 | For info on how to build and use it, please see the [wiki](https://github.com/patience4711/ESP32-read-APS-inverters/wiki) 49 | 50 | ## how does it work 51 | APS works with their own zigbee implementation. The ESP-ECU sends zigbee commands (wireless) to the inverters and analyzes the answers, extracting the values. 52 | The ESP communicates with the zigbee module through the alternative serial port (wired). 53 | The ESP-ECU starts a coordinator (an entity that can start a zigbee network). The coordinator binds the inverters and sends the poll requests to them. 54 | The interesting values are send via mqtt and displayed on the main page. The ecu sends a message that there is new data and the webpage reacts by requesting 55 | the new data. 56 |

    example of a sensor in Domoticz:
    57 | ![graph2](https://user-images.githubusercontent.com/12282915/139062602-71e92216-9703-4fc4-acc6-fabf544c4ffd.jpg) 58 | 59 | 60 | ## changelog ## 61 | version ESP32-ECU_V1_4 : 62 | * improved inverter throttling (works for YC600 and DS3) 63 | * added an inverter query command via console 64 | * added a mosquitto intopic based on chipId 65 | * Throttling is possible via ui, http request or mosquitto 66 | * API's can have a debug argument to show debug info. 67 | 68 | version ESP32-ECU_V1_2: 69 | * added options to throttle inverter (tested on YC600 and DS3) 70 | 71 | version ESP32-ECU_V1_1: 72 | * adapted to a modern ArduinoIDE(2.3.4) and board definitions(2.0.18 arduino5) 73 | * changed the SPIFFS save functions 74 | * changed the wifi connection Portal 75 | 76 | version ESP32-ECU_V0_9: 77 | * fixed a bug in the html of the inverterspage 78 | * introduced an improved debugging method 79 | 80 | version ESP32-ECU_V0_8: 81 | * fixed a bug related to the working of the button 82 | 83 | version ESP32-ECU_V0_7: 84 | * more efficient communication browser/server (events driven) 85 | * minimized all webpages and javascripts 86 | * improved menu and browsing on the ecu website 87 | 88 | version ESP32-ECU_V0_5: 89 | * more efficient use of the memory 90 | * Use of arduinoJson 91 | 92 | version ESP32-ECU_V0_4: 93 | * Banned all string operations in main processes 94 | * Some webpages improved. 95 | 96 | version ESP32-ECU_V0_3b: 97 | * some security updates (maintenance from outside the own network) 98 | * fine-tuned the pairing process 99 | * redesigned the important processes to gain more free heap. 100 | * some cosmetics and small bugs 101 | 102 | version ESP32-ECU_V0_1d: 103 | * replaced elegantOta with my own implentation 104 | * fixed a bug in the pairing proces 105 | * solved system crashed due to string operations 106 | * fixed a bug in the pairing proces 107 | 108 | version ESP32-ECU_V0_1a: 109 | Relative to the esp8266: 110 | * new frontpage with buttons to inverter details 111 | * removed the websocket console to relieve the webserver 112 | * added a serial console to issue commands 113 | -------------------------------------------------------------------------------- /Reboot__AP.ino: -------------------------------------------------------------------------------- 1 | 2 | // void zendPageReboot() { 3 | // // als niet ingelogd als admin dan terug 4 | // if(!server.authenticate("admin", pswd)) { return server.requestAuthentication(); } 5 | // 6 | // toSend = FPSTR(HTML_HEAD); 7 | // toSend += FPSTR(CONFIRM); 8 | // 9 | // server.send(200, "text/html", toSend); //send the html code to the client 10 | // delay(500);//wait half a second after sending the dataconfigSave(); 11 | // DebugPrintln("basisconfig saved"); 12 | // ESP.restart(); 13 | //} 14 | 15 | //void startAP() { 16 | // // als niet ingelogd als admin dan terug 17 | // if(!server.authenticate("admin", pswd)) { return server.requestAuthentication(); } 18 | // DebugPrintln("Erasing the wifisettings"); 19 | // String teZenden = F(""); 20 | // teZenden += F("

    OK the accesspoint is started.

    Wait until the led lights up.

    then go to wifi-settings on your device and connect to ESP-"); 21 | // teZenden += String(ESP.getChipId()) + " !"; 22 | // server.send ( 200, "text/html", teZenden ); //zend bevestiging 23 | // delay(500);//wait half a second after sending the data 24 | // delay(1); 25 | // 26 | // 27 | // WiFi.disconnect(true); 28 | // delay(1000); // ruim de tijd om de knop los te laten anders komt ie in programmeermode 29 | // 30 | // 31 | // ESP.restart(); 32 | //} 33 | 34 | void loginAdmin(AsyncWebServerRequest *request) { 35 | String authFailResponse = "

    login failed click here

    "; 36 | const char* www_realm = "login as administrator."; 37 | if (!request->authenticate("admin", pswd)) return request->requestAuthentication(); 38 | } 39 | -------------------------------------------------------------------------------- /SETPOWER.ino: -------------------------------------------------------------------------------- 1 | int setMaxPower(int which) 2 | { 3 | // if there is no coordinator, this command fails 4 | // this function tries to program a throttle value in the inverter 5 | // the value is in desiredThrottle[Inv] 6 | // after that the inverter is queried ti determine the programmed value 7 | // when the query succeeds this value is always written in preferences 8 | 9 | int calibratedVal; 10 | int Scaled; 11 | char powCommand[85]; 12 | char ecu_id_reverse[13]; 13 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 14 | 15 | // calculate a checkval to determine whether the throttle succeeds 16 | // can be used for both inverters 17 | // calibratedVal = static_cast( 18 | // ceil(desiredThrottle[which] * (1.0 + Inv_Prop[which].calib / 100.0)) 19 | // ); 20 | calibratedVal = desiredThrottle[which] + Inv_Prop[which].calib; 21 | consoleOut ("user input calibrated = " + String(calibratedVal)); 22 | 23 | if(Inv_Prop[which].invType == 2) // DS3 24 | { 25 | consoleOut("sending the throttle command for DS3"); 26 | // first we convert the scaled maxPower value to hex, ( msb and lsb ) 27 | // and calculate the validation byte 28 | Scaled = (calibratedVal * 1659 + 50) / 100; // this rounds up instead of down 29 | //Scaled = calibratedVal * 16.59; 30 | uint8_t msb = (Scaled >> 8) & 0xFF; // 0x02 31 | uint8_t lsb = Scaled & 0xFF; // 0x58 32 | // Compute validation byte 33 | uint8_t s = (msb + lsb) & 0xFF; 34 | uint8_t vv = (s - 0x29) & 0xFF; 35 | //Serial.println("for maxpower = " + String(desiredThrottle[which]) + " * 16.59, the values are "); 36 | //Serial.printf("MSB: %02X, LSB: %02X, VV: %02X\n", msb, lsb, vv); 37 | snprintf(powCommand, sizeof(powCommand), "2401%s1414060001000F13%sFBFB06AA270000%02X%02X01%02XFEFE", Inv_Prop[which].invID, ecu_id_reverse, msb, lsb, vv); 38 | consoleOut("The raw powCommand for DS3 = " + String(powCommand)); 39 | 40 | } else 41 | { 42 | consoleOut("sending the throttle command for YC600 / QS1"); 43 | Scaled = (calibratedVal * 2889 + 50) / 100; // this rounds up instead of down 44 | //Scaled = calibratedVal * 28.89; // 45 | char hexStr[5]; // Enough for "FFFFFFFF" + null terminator 46 | // Convert Scaled to hexadecimal string 47 | // %04X ensures it's always 4 hex digits, uppercase, zero-padded 48 | //Serial.println("Scaled = " + String(Scaled)); 49 | snprintf(hexStr, sizeof(hexStr), "%04X", Scaled); 50 | //cout << "hexStr = " << hexStr << "\n
    "<< endl; 51 | //Serial.println("hexStr = " + String(hexStr)); 52 | //construct command 53 | powCommand[0]='\0'; // make sure its empty 54 | snprintf(powCommand, sizeof(powCommand), "2401%s1414060001000F13%sFBFB061C8C02%s00", Inv_Prop[which].invID, ecu_id_reverse, hexStr); 55 | 56 | // now we calculate the checksum( the last 2 bytes) 57 | int getal=0; 58 | char bytes2summerize[15]={"061C8C02"}; 59 | // add the trottle value 60 | strncat(bytes2summerize, hexStr, sizeof(bytes2summerize) - strlen(bytes2summerize)); 61 | strncat(bytes2summerize, "00", sizeof(bytes2summerize) - strlen(bytes2summerize)); 62 | // 63 | Serial.println("the last 7 bytes are " + String(bytes2summerize)); 64 | for(int x=0; x < strlen(bytes2summerize); x += 2) { 65 | // Convert two hex chars into a byte 66 | char hexByte[3] = { bytes2summerize[x], bytes2summerize[x + 1], '\0' }; 67 | //cout <<"hexByte = " << hexByte << "\n
    " << endl; 68 | int value = strtol(hexByte, NULL, 16); // convert hex string to int 69 | getal += value; 70 | } 71 | char hexString[10]; // Enough space for hex + null terminator 72 | // Convert int to hex and store in hexString 73 | snprintf(hexString, sizeof(hexString), "%04X", getal); 74 | //append to sendCmd 75 | strncat(powCommand, hexString, sizeof(powCommand) - strlen(powCommand) - 1); 76 | strncat(powCommand, "FEFE", sizeof(powCommand) - strlen(powCommand) - 1); 77 | consoleOut("The raw powCommand for YC600 / QS1 = " + String(powCommand)); 78 | 79 | } 80 | // if we are here. powCommand contains the raw throttle command 81 | // specific to each inverter type 82 | // we are going to send it and discard the response 83 | if(zigbeeUp == 1) sendZB(powCommand); 84 | //we discard the inMessage 85 | if( waitSerial2Available() ) { 86 | empty_serial2(); // clear the incoming data 87 | } 88 | // now we are going to send the dummy which is equal for each invertertype 89 | snprintf(powCommand, sizeof(powCommand), "2401%s1414060001000F13%sFBFB06DE00000000000000FEFE", Inv_Prop[which].invID, ecu_id_reverse); 90 | consoleOut("the raw dummy command = " + String(powCommand)); 91 | if(zigbeeUp == 1) sendZB(powCommand); 92 | //we also discard this response if there is any 93 | if( waitSerial2Available() ) { 94 | empty_serial2(); // clear the incoming data 95 | } 96 | // now we send the query comand which is also the same for both inverters 97 | snprintf(powCommand, sizeof(powCommand), "2401%s1414060001000F13%sFBFB06DE000000000000E4FEFE", Inv_Prop[which].invID, ecu_id_reverse); 98 | consoleOut("the raw query command = " + String(powCommand)); 99 | // consoleOut("\nzigbeeUp value = " + String(zigbeeUp)); 100 | #ifndef TEST 101 | if(zigbeeUp != 1) { 102 | Serial.println("no zigbee coordinator, fail"); 103 | return 15; 104 | } 105 | sendZB(powCommand); 106 | #endif 107 | //we decode amd analize the query answer 108 | errorCode = decodeQueryAnswer(which, true); // true means after a throttle 109 | return errorCode; 110 | //if(errorCode == 0 || errorCode == 15 ) return true; else return false; 111 | } 112 | -------------------------------------------------------------------------------- /SPIFFS_RW.ino: -------------------------------------------------------------------------------- 1 | // ****************** spiffs lezen ************************* 2 | 3 | // als er geen spiffs bestand is dan moet hij eigenlijk altijd een ap openenen 4 | void SPIFFS_read() { 5 | //DebugPrintln("mounting FS..."); 6 | if (SPIFFS.begin(true)) { 7 | Serial.println("mounted file system"); 8 | 9 | if( file_open_for_read("/wificonfig.json") ) { 10 | Serial.println("read wificonfig\n"); 11 | } else { 12 | Serial.println("wificonfig.json not opened\n"); 13 | } 14 | 15 | if( file_open_for_read("/basisconfig.json") ) { 16 | Serial.println("read basisconfig\n"); 17 | } else { 18 | Serial.println("basisconfig.json not opened\n"); 19 | } 20 | if( file_open_for_read("/mqttconfig.json") ) { 21 | Serial.println("mqttconfig read"); 22 | } else { 23 | Serial.println("mqttconfig.json not opened"); 24 | } 25 | } else { 26 | Serial.println("failed to mount FS"); 27 | } 28 | // einde spiffs lezen 5 bestanden 29 | 30 | } 31 | 32 | void writeStruct( String whichfile, int nummer) { 33 | File configFile = SPIFFS.open(whichfile, "w"); 34 | //input=/Inv_Prop2.str 35 | //int nummer=whichfile.substring(9,10).toInt(); 36 | //Serial.println("nummer = "+ String(nummer)); 37 | if (!configFile) 38 | { 39 | Serial.print(F("Failed open for write : ")); Serial.println(whichfile); 40 | } 41 | 42 | Serial.print(F("Opened for write....")); Serial.println(whichfile); 43 | configFile.write( (unsigned char *)&Inv_Prop[nummer], sizeof(Inv_Prop[nummer]) ); 44 | configFile.close(); 45 | 46 | } 47 | 48 | bool readStruct(String whichfile) { 49 | consoleOut("readStruct whichfile = " + whichfile); 50 | if (!SPIFFS.exists(whichfile)) { 51 | consoleOut("Failed to open for read" + whichfile); 52 | return false; 53 | } 54 | // the file exists so we can open it for read 55 | File configFile = SPIFFS.open(whichfile, "r"); 56 | int ivn = whichfile.substring(9,10).toInt(); 57 | 58 | consoleOut("readStruct ivn = " + String(ivn) ); 59 | consoleOut("reading " + whichfile); 60 | configFile.read( (unsigned char *)&Inv_Prop[ivn], sizeof(Inv_Prop[ivn]) ); 61 | configFile.close(); 62 | return true; 63 | } 64 | 65 | 66 | 67 | // **************************************************************************** 68 | // save the data in SPIFFS * 69 | // **************************************************************************** 70 | void wifiConfigsave() { 71 | Serial.println("saving config"); 72 | 73 | JsonDocument doc; 74 | JsonObject json = doc.to(); 75 | //json["ip"] = static_ip; 76 | json["pswd"] = pswd; 77 | json["longi"] = longi; 78 | json["lati"] = lati; 79 | 80 | json["gmtOffset"] = gmtOffset; 81 | json["zomerTijd"] = zomerTijd; 82 | json["securityLevel"] = securityLevel; 83 | File configFile = SPIFFS.open("/wificonfig.json", "w"); 84 | if (!configFile) { 85 | Serial.println("open file for writing failed!"); 86 | } 87 | //DebugPrintln("wificonfig.json written"); 88 | #ifdef DEBUG 89 | // json.printTo(Serial); 90 | serializeJson(json, Serial); 91 | Serial.println(F("")); 92 | #endif 93 | serializeJson(json, configFile); 94 | configFile.close(); 95 | } 96 | 97 | 98 | void basisConfigsave() { 99 | Serial.println("saving basis config"); 100 | JsonDocument doc; 101 | JsonObject json = doc.to(); 102 | json["ECU_ID"] = ECU_ID; 103 | json["userPwd"] = userPwd; 104 | json["inverterCount"] = inverterCount; 105 | json["Polling"] = Polling; 106 | json["pollOffset"] = pollOffset; 107 | 108 | File configFile = SPIFFS.open("/basisconfig.json", "w"); 109 | if (!configFile) { 110 | //DebugPrintln("open file for writing failed"); 111 | } 112 | Serial.println("inverterconfig.json written"); 113 | #ifdef DEBUG 114 | serializeJson(json, Serial); 115 | Serial.println(F("")); 116 | #endif 117 | serializeJson(json, configFile); 118 | configFile.close(); 119 | } 120 | 121 | void mqttConfigsave() { 122 | //DebugPrintln("saving mqtt config"); 123 | JsonDocument doc; 124 | JsonObject json = doc.to(); 125 | // 126 | // json["Mqtt_Enabled"] = Mqtt_Enabled; 127 | json["Mqtt_Broker"] = Mqtt_Broker; 128 | json["Mqtt_Port"] = Mqtt_Port; 129 | json["Mqtt_stateIDX"] = Mqtt_stateIDX; 130 | //json["Mqtt_inTopic"] = Mqtt_inTopic; 131 | json["Mqtt_outTopic"] = Mqtt_outTopic; 132 | json["Mqtt_Username"] = Mqtt_Username; 133 | json["Mqtt_Password"] = Mqtt_Password; 134 | // json["Mqtt_Idx"] = Mqtt_Idx; 135 | json["Mqtt_Format"] = Mqtt_Format; 136 | File configFile = SPIFFS.open("/mqttconfig.json", "w"); 137 | if (!configFile) { 138 | //DebugPrintln("open file for writing failed"); 139 | } 140 | Serial.println("mqttconfig.json written"); 141 | #ifdef DEBUG 142 | serializeJson(json, Serial); 143 | Serial.println(F("")); 144 | #endif 145 | serializeJson(json, configFile); 146 | configFile.close(); 147 | } 148 | 149 | 150 | bool file_open_for_read(const char* bestand) 151 | { 152 | String Output = ""; 153 | consoleOut("we are in file_open_for_read, file = " + String(bestand)); //DebugPrintln(bestand); 154 | JsonDocument doc; 155 | File configFile = SPIFFS.open(bestand, "r"); 156 | if (configFile) { 157 | DeserializationError error = deserializeJson(doc, configFile); 158 | configFile.close(); 159 | if (error) { 160 | Serial.print(F("Failed to parse config file: ")); 161 | Serial.println(error.c_str()); 162 | // Continue with fallback values 163 | } else { 164 | // no error so we can print the file 165 | //serializeJson(doc, Serial); // always print 166 | serializeJson(doc, Output); // always print 167 | consoleOut(Output); 168 | } 169 | } else { 170 | Serial.print(F("Cannot open config file: ")); 171 | Serial.println(bestand); 172 | // Continue with empty doc -> all fallbacks will be used 173 | } 174 | // we read the file even if it doesn't exist, so that variables are initialized 175 | // we read every variable with a fall back value to prevent crashes 176 | 177 | //serializeJson(doc, jsonStr); 178 | if (strcmp(bestand, "/wificonfig.json") == 0) { 179 | //strcpy(static_ip, doc["ip"] | "000.000.000.000"); 180 | strcpy(pswd, doc["pswd"] | "0000"); 181 | longi = doc["longi"] | 5.432; 182 | lati = doc["lati"] | 51.743; 183 | strcpy(gmtOffset, doc["gmtOffset"] | "+120"); 184 | zomerTijd = doc["zomerTijd"].as() | true; 185 | securityLevel = doc["securityLevel"].as() | 6; 186 | } 187 | 188 | if ( strcmp(bestand, "/basisconfig.json") == 0) { 189 | strcpy (ECU_ID, doc["ECU_ID"] | "D8A3011B9780"); 190 | strcpy (userPwd, doc["userPwd"] | "1111" ); 191 | pollOffset = doc["pollOffset"].as() | 0; 192 | Polling = doc["Polling"].as() | false; 193 | } 194 | 195 | if ( strcmp(bestand, "/mqttconfig.json") == 0) { 196 | strcpy(Mqtt_Broker, doc["Mqtt_Broker"] | "192.168.0.100"); 197 | strcpy(Mqtt_Port, doc["Mqtt_Port"] | "1883"); 198 | strcpy(Mqtt_outTopic, doc["Mqtt_outTopic"] | "domoticz/in"); 199 | strcpy(Mqtt_Username, doc["Mqtt_Username"] | "n/a"); 200 | strcpy(Mqtt_Password, doc["Mqtt_Password"] | "n/a"); 201 | Mqtt_Format = doc["Mqtt_Format"].as() | 0; 202 | Mqtt_stateIDX = doc["Mqtt_stateIDX"].as() | 123; 203 | } 204 | return true; 205 | } 206 | void printStruct( String bestand ) { 207 | //input String bestand = "/Inv_Prop" + String(x) + ".str"; 208 | //String bestand = bestand + String(i) + ".str" 209 | //leesStruct(bestand); is done at boottime 210 | int ivn = bestand.substring(9,10).toInt(); 211 | consoleOut("Inv_Prop[" + String(ivn) + "].invLocation = " + String(Inv_Prop[ivn].invLocation)); 212 | consoleOut("Inv_Prop[" + String(ivn) + "].invSerial = " + String(Inv_Prop[ivn].invSerial)); 213 | consoleOut("Inv_Prop[" + String(ivn) + "].invID = " + String(Inv_Prop[ivn].invID)); 214 | consoleOut("Inv_Prop[" + String(ivn) + "].invType = " + String(Inv_Prop[ivn].invType)); 215 | consoleOut("Inv_Prop[" + String(ivn) + "].invIdx = " + String(Inv_Prop[ivn].invIdx)); 216 | consoleOut("Inv_Prop[" + String(ivn) + "].calib = " + String(Inv_Prop[ivn].calib)); 217 | consoleOut("Inv_Prop[" + String(ivn) + "].conPanels = " + String(Inv_Prop[ivn].conPanels[0]) + String(Inv_Prop[ivn].conPanels[1]) + String(Inv_Prop[ivn].conPanels[2]) + String(Inv_Prop[ivn].conPanels[3])); 218 | //Serial.println("Inv_Prop[" + String(ivn) + "].throttled = " + String(Inv_Prop[ivn].throttled)); 219 | //Serial.println("Inv_Prop[" + String(ivn) + "].maxPower = " + String(Inv_Prop[ivn].maxPower)); 220 | consoleOut(""); 221 | consoleOut("****************************************"); 222 | } 223 | 224 | // void printStruct( String bestand ) { 225 | // //input String bestand = "/Inv_Prop" + String(x) + ".str"; 226 | // //String bestand = bestand + String(i) + ".str" 227 | // //leesStruct(bestand); is done at boottime 228 | // int ivn = bestand.substring(9,10).toInt(); 229 | // Serial.println("Inv_Prop[" + String(ivn) + "].invLocation = " + String(Inv_Prop[ivn].invLocation)); 230 | // Serial.println("Inv_Prop[" + String(ivn) + "].invSerial = " + String(Inv_Prop[ivn].invSerial)); 231 | // Serial.println("Inv_Prop[" + String(ivn) + "].invID = " + String(Inv_Prop[ivn].invID)); 232 | // Serial.println("Inv_Prop[" + String(ivn) + "].invType = " + String(Inv_Prop[ivn].invType)); 233 | // Serial.println("Inv_Prop[" + String(ivn) + "].invIdx = " + String(Inv_Prop[ivn].invIdx)); 234 | // Serial.println("Inv_Prop[" + String(ivn) + "].conPanels = " + String(Inv_Prop[ivn].conPanels[0]) + String(Inv_Prop[ivn].conPanels[1]) + String(Inv_Prop[ivn].conPanels[2]) + String(Inv_Prop[ivn].conPanels[3])); 235 | // //Serial.println("Inv_Prop[" + String(ivn) + "].throttled = " + String(Inv_Prop[ivn].throttled)); 236 | // //Serial.println("Inv_Prop[" + String(ivn) + "].maxPower = " + String(Inv_Prop[ivn].maxPower)); 237 | // Serial.println(""); 238 | // Serial.println("****************************************"); 239 | // } 240 | -------------------------------------------------------------------------------- /Start_WiFi.ino: -------------------------------------------------------------------------------- 1 | // ************************************************************************************ 2 | // * START wifi 3 | // ************************************************************************************ 4 | void start_wifi() { 5 | WiFi.softAPdisconnect(true); 6 | WiFi.mode(WIFI_STA); 7 | Serial.println("starting wifi "); 8 | delay(1000); 9 | //Serial.println("start wifi 3"); 10 | WiFi.setHostname(getChipId(false).c_str()); 11 | 12 | //Serial.println("start wifi 4"); 13 | 14 | // WiFi.mode(WIFI_STA); // geen ap op dit moment 15 | 16 | // we gaan 10 pogingen doen om te verbinden 17 | // met de laatst gebruikte credentials 18 | while (WiFi.status() != WL_CONNECTED) { 19 | delay(500); 20 | Serial.print("*"); 21 | WiFi.begin(); 22 | event+=1; 23 | if (event==10) {break;} 24 | } 25 | // als het verbinden is mislukt gaan we naar het configportal 26 | if (event>9) { 27 | event=0; 28 | Serial.println("\nWARNING connection failed"); 29 | start_portal(); 30 | } else { 31 | Serial.print("\nconnection success, ip = "); 32 | Serial.println(WiFi.localIP()); 33 | } 34 | Serial.print("# connection attempts = "); //Serial.println(event); 35 | event=0; // we kunnen door naar de rest 36 | //checkFixed(); 37 | 38 | start_server(); 39 | } 40 | //// ************************************************************************* 41 | //// START THE SERVER 42 | 43 | 44 | // // ******************************************************************** 45 | // // check if there must come a static ip 46 | // // ******************************************************************** 47 | // void checkFixed() { 48 | // // we come here only when wifi connected 49 | // char GATE_WAY[16]=""; 50 | // IPAddress gat=WiFi.gatewayIP(); 51 | // sprintf(GATE_WAY, "%d.%d.%d.%d", gat[0], gat[1], gat[2], gat[3]); 52 | // //DebugPrint("GATE_WAY in checkFixed = nu: "); //DebugPrintln(String(GATE_WAY)); 53 | // //DebugPrint("static_ip in checkFixed = nu: "); //DebugPrintln(String(static_ip)); 54 | 55 | // if (static_ip[0] != '\0' && static_ip[0] != '0') { 56 | // //DebugPrintln("we need s static ip Custom STA IP/GW/Subnet"); 57 | // IPAddress _ip,_gw,_sn(255,255,255,0); // declare 58 | // _ip.fromString(static_ip); 59 | // _gw.fromString(GATE_WAY);// if (ssid != "") { 60 | // WiFi.config(_ip, _gw, _sn); 61 | // //DebugPrintln(WiFi.localIP()); 62 | // } else { 63 | // //DebugPrintln("trying to get rid of wificonfig"); 64 | // WiFi.config(0u, 0u, 0u); 65 | // } 66 | // } 67 | 68 | void loginBoth(AsyncWebServerRequest *request, String who) { 69 | String authFailResponse = "

    login failed click here

    "; 70 | if (who == "admin" ){ 71 | const char* www_realm = "login as administrator."; 72 | if (!request->authenticate("admin", pswd)) return request->requestAuthentication(); 73 | } 74 | if (who == "both" ){ 75 | const char* www_realm = "login as administrator or user."; 76 | if (!request->authenticate("admin", pswd) && !request->authenticate("user", userPwd)) return request->requestAuthentication(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /TIJD_CALC.ino: -------------------------------------------------------------------------------- 1 | // deze functie berekent de zonsopkomst en zonsondergangstijden 2 | void sun_setrise() { 3 | 4 | //float OUR_longtitude = atof(lengte); 5 | //float OUR_latitude = atof(breedte); 6 | float OUR_longtitude = longi; 7 | float OUR_latitude = lati; 8 | float OUR_timezone = atof(gmtOffset); // 120 // localtime with UTC difference in minutes 9 | 10 | sunMoon sm; 11 | 12 | // tmElements_t tm; // specific time 13 | // tm.Second = 0; 14 | // tm.Minute = 12; 15 | // tm.Hour = 12; 16 | // tm.Day = 3; 17 | // tm.Month = 8; 18 | // tm.Year = 2016 - 1970; 19 | // time_t s_date = makeTime(tm); 20 | // Serial.println("RTC has set the system time"); 21 | sm.init(OUR_timezone, OUR_latitude, OUR_longtitude); 22 | 23 | 24 | // uint32_t jDay = sm.julianDay(); // Optional call 25 | // mDay = sm.moonDay(); 26 | // if (mDay < 13){ maan = "cresc. moon";} //crescent moon" 27 | // if (mDay > 15){maan = "waning moon";} 28 | // if (mDay == 13 || mDay == 14 || mDay == 15){maan = "full moon";} 29 | // if (mDay == 0 || mDay == 1 || mDay == 28){maan = "new moon";} 30 | 31 | time_t sunrise = sm.sunRise(); 32 | time_t sunset = sm.sunSet(); 33 | 34 | if ( zomerTijd ) { // er is zomertijd ingesteld 35 | if (zomertijd() == true) { // we kijken of het zomertijd is 36 | sunrise = sunrise + 3600; // seconden 37 | sunset = sunset + 3600; 38 | dst = 1; //summer time 39 | } else { 40 | dst = 2; // winter time 41 | } 42 | } else { 43 | dst = 0; 44 | } 45 | switchonTime = sunrise + pollOffset*60; // was -900 46 | switchoffTime = sunset - pollOffset*60; // was +900 47 | 48 | } 49 | 50 | 51 | // ************************* deze functie bepaalt of zomertijd van toepassing is 52 | bool zomertijd() { 53 | //DebugPrint(" the date is "); 54 | //DebugPrint(day()); 55 | //DebugPrint(" "); 56 | //DebugPrint(month()); 57 | //DebugPrint(" "); 58 | //DebugPrint(year()); 59 | //DebugPrintln(" "); 60 | 61 | int eerstemrt = dow(year(), 3 ,1); 62 | int zdmrt; 63 | 64 | if (eerstemrt == 0) { 65 | zdmrt = 1; 66 | } else { 67 | zdmrt = 1 + (7 - eerstemrt); 68 | } 69 | 70 | while(zdmrt <= 24){ 71 | zdmrt = zdmrt + 7; 72 | } 73 | //DebugPrint("the last sunday of march is "); 74 | //DebugPrint(zdmrt); 75 | //DebugPrintln(""); 76 | 77 | //Serial.print("de eerste dag van oktober is dag "); 78 | int eersteoct = dow(year(), 10 ,1); 79 | int zdoct ; 80 | //Serial.print(eersteoct); 81 | //Serial.println(""); 82 | // dow gaat van 0 naar 6, zondag is 0 83 | //Serial.print("de eerste zondag van zondag van oct is dag "); 84 | if (zdoct == 0) { 85 | zdoct = 1; 86 | } else { 87 | zdoct = 1+(7-eersteoct); 88 | } 89 | //Serial.print(zdoct); 90 | //Serial.println(""); 91 | 92 | while(zdoct <= 24){ 93 | zdoct = zdoct + 7; 94 | } 95 | //DebugPrint("de laatste zondag van october is dag "); 96 | //DebugPrint(zdoct); 97 | //DebugPrintln(""); 98 | 99 | if(((month() == 3 and day() >= zdmrt) or month() > 3) and ((month() == 10 and day() < zdoct) or month() < 10)) { 100 | //DebugPrintln("het is zomertijd"); 101 | return true; 102 | } else { 103 | //DebugPrintln("het is geen zomertijd"); 104 | return false; 105 | } 106 | } 107 | 108 | int dow(int y, int m, int d) { 109 | static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4}; 110 | y -= m < 3; 111 | return (y + y/4 - y/100 + y/400 + t[m-1] + d) % 7; 112 | } 113 | 114 | void tijd_convert () { 115 | 116 | } 117 | 118 | 119 | // een universele funtie om het aantal uren en minuten uit de char in een string te zetten 120 | // als wat true dan geeft ie de uren en anders de minuut 121 | int tijd_cvrt(char TIJD[6], bool wat) { 122 | int uur = 0; 123 | for (int i = 0; i < 2; i++) { 124 | char c = TIJD[i]; 125 | if (c < '0' || c > '9') break; 126 | uur *= 10; 127 | uur += (c - '0'); 128 | } 129 | int minuut = 0; 130 | for (int i = 3; i < 5; i++) { // 2 = de : 131 | char c = TIJD[i]; 132 | if (c < '0' || c > '9') break; 133 | minuut *= 10; 134 | minuut += (c - '0'); 135 | } 136 | if (wat) { return uur; } else { return minuut;} 137 | } 138 | -------------------------------------------------------------------------------- /TIJD_GET.ino: -------------------------------------------------------------------------------- 1 | void getTijd() { 2 | 3 | timeRetrieved = false; // stays false until time is retrieved 4 | timeClient.begin(); 5 | //unsigned long epochTime = 0; 6 | //get the time, if fails we try again during healthcheck 7 | 8 | timeClient.update(); 9 | unsigned long epochTime = timeClient.getEpochTime(); 10 | 11 | 12 | //Serial.print("Epoch Time: "); 13 | //Serial.println(epochTime); 14 | 15 | // now convert NTP time into unix tijd: 16 | // Unix time starts on Jan 1 1970. In seconds, that's 2208988800: 17 | //const unsigned long seventyYears = 2208988800UL; 18 | // subtract seventy years: 19 | // unsigned long epoch = secsSince1900 - seventyYears + atof(timezone) * 60; // * 60 weggehaald omdat timezone in minuten is 20 | // unsigned long epochTime = timeClient.getEpochTime; 21 | // we have to do this conditional, if time retrieving failed 22 | if (epochTime < 1000) { 23 | ntpUDP.stop(); 24 | return; 25 | } else { 26 | 27 | epochTime += atoi(gmtOffset) * 60; 28 | setTime(epochTime); // dit moeten we doen omdat anders zomertijd() niet werkt 29 | //Serial.print("epoch gecorrigeerd voor timezone = "); Serial.println(epochTime); 30 | if ( zomerTijd == true ) { 31 | //Serial.print("zomerTijd[0] = een o dus on "); Serial.println(String(zomerTijd)); 32 | if (zomertijd() == true) { 33 | epochTime += 3600; // een uur erbij 34 | setTime(epochTime); 35 | //DebugPrint("epoch corrected with dts = "); //DebugPrintln(epochTime); 36 | } 37 | } 38 | timeRetrieved=true; 39 | Update_Log(1, "got time"); 40 | } 41 | //DebugPrint(" Unix time epoch = "); 42 | //DebugPrintln(epochTime); 43 | 44 | ntpUDP.stop(); 45 | // 46 | // // de tijd is nu opgehaald en in setTime gestopt 47 | // // dus met de tijden die met setTime zijn opgeslagen gaan we alle berekeningen doen 48 | // 49 | //DebugPrint("het uur is "); //DebugPrint(hour()); 50 | //DebugPrint(" aantal minuten "); //DebugPrintln(minute()); 51 | datum = day(); 52 | // 53 | //yield(); 54 | delay(10); 55 | sun_setrise(); //to calulate moonshape sunrise etc. and the switchtimes 56 | 57 | // switchonTime = sunrise - 900; 58 | // switchoffTime = sunset + 900; // nightmode starts at 15 min after sunset 59 | } 60 | -------------------------------------------------------------------------------- /ZIGBEE_COORDINATOR.ino: -------------------------------------------------------------------------------- 1 | /* this is a tricky process as it is hard to check if it does what it should. 2 | * if brought up with sprintf constructed commands, it seems we can pair but 3 | * get no polling answers. 4 | * This is not caused by only the last NO command. It seems that if we feed sendZB() with a 5 | * char that contains empty space, it goes wrong. 6 | * To send the relative commands i tried something like: 7 | * for ( int y=0; y < 8; Y++ ) { 8 | * snprintf(command, "A123B456C%sA1A2B3B4C5C6", invID); // or similar 9 | * sendZB(command); 10 | * etc; 11 | * } 12 | * This works with pairing ( at least we get the invID ) but maybe it wasn't paired at all. 13 | * Anyway the polling fails constantly, also after rebooting the inverter. 14 | * command 2700 gives us the answer that the coordinator is up. So that proves nothing. 15 | * I haven't investigate this further, for now only the method used here works 16 | */ 17 | 18 | bool coordinator(bool normal) 19 | { // if true we send the extra command for normal operations 20 | consoleOut(F("starting coordinator")); 21 | //} else 22 | //if(diagNose == 2) ws.textAll(F("starting coordinator")); 23 | coordinator_init(); 24 | if(normal) sendNO(); 25 | // now check if running 26 | delay(1000); // to give the ZB the time to start 27 | empty_serial2(); //otherwise the check fails 28 | if ( checkCoordinator() == 0 ) // can be 0 1 or 2 29 | { 30 | Update_Log(2, "started"); 31 | consoleOut(F("ZB coordinator started")); 32 | //if(diagNose == 2) ws.textAll(F("ZB coordinator started")); 33 | 34 | ledblink(5,100); 35 | return true; 36 | 37 | } else { 38 | Update_Log( 2 , "failed"); 39 | consoleOut(F("starting ZB coordinator failed")); 40 | return false; 41 | } 42 | } 43 | 44 | void coordinator_init() { 45 | 46 | /* 47 | * init the coordinator takes the following procedure 48 | * 1st we send a resetcommand 4 times Sent=FE0141000040 49 | * then we send the following commands 50 | * 0 Sent=FE03260503010321 51 | * Received=FE0166050062 52 | * 1 Sent=FE0141000040 53 | * Received=FE064180020202020702C2 54 | * 2 Sent=FE0A26050108FFFF80971B01A3D856 55 | * Received=FE0166050062 56 | * 3 Sent=FE032605870100A6 57 | * Received=FE0166050062 58 | * 4 Sent=FE 04 26058302 D8A3 DD should be ecu_id the fst 2 bytes 59 | * Received=FE0166050062 60 | * 5 Sent=FE062605840400000100A4 61 | * Received=FE0166050062 62 | * 6 Sent=FE0D240014050F0001010002000015000020 63 | * Received=FE0164000065 64 | * 7 Sent=FE00260026 65 | * 8 Sent=FE00670067 66 | * Received=FE0145C0098D 67 | * received FE00660066 FE0145C0088C FE0145C0098D F0F8FE0E670000FFFF80971B01A3D8000007090011 68 | * now we can pair if we want to or else an extra command for retrieving data (normal operation) 69 | * 9 for normal operation we send cmd 9 70 | * Finished. Heap=26712 71 | * 72 | */ 73 | //Serial.println("coordinator init 1"); 74 | consoleOut("init zb coordinator"); 75 | zigbeeUp = 11; //initial it is initializing 11, 0=down 1=up 76 | yield(); 77 | char ecu_id_reverse[13]; //= {ECU_REVERSE()}; 78 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 79 | char initCmd[254]={0}; 80 | char s_d[254]={0}; // provide a buffer for the call to readZB 81 | // commands for setting up coordinater 82 | char initBaseCommand[][254] = { 83 | "2605030103", // ok this is a ZB_WRITE_CONFIGURATION CMD //changed to 01 84 | "410000", // ok ZB_SYS_RESET_REQ 85 | "26050108FFFF", // + ecu_id_reverse, this is a ZB_WRITE_CONFIGURATION CMD 86 | "2605870100", //ok 87 | "26058302", // + ecu_id.substring(0,2) + ecu_id.substring(2,4), 88 | "2605840400000100", //ok 89 | "240014050F00010100020000150000", //AF_REGISTER register an application’s endpoint description 90 | "2600", //ok ZB_START_REQUEST 91 | }; 92 | 93 | // we start with a hard reset of the zb module 94 | ZBhardReset(); 95 | delay(500); 96 | 97 | // construct some of the commands 98 | // ***************************** command 2 ******************************************** 99 | // command 2 this is 26050108FFFF we add ecu_id reversed 100 | strncat(initBaseCommand[2], ecu_id_reverse, sizeof(ecu_id_reverse)); 101 | delayMicroseconds(250); 102 | //DebugPrintln("initBaseCmd 2 constructed = " + String(initBaseCommand[2])); // ok 103 | 104 | // ***************************** command 4 ******************************************** 105 | // command 4 this is 26058302 + ecu_id_short 106 | strncat(initBaseCommand[4], ECU_ID, 2); 107 | strncat(initBaseCommand[4], ECU_ID + 2, 2); 108 | delayMicroseconds(250); 109 | //DebugPrintln("initBaseCmd 4 constructed = " + String(initBaseCommand[4])); 110 | 111 | // send the commands from 0 to 7 112 | for (int y = 0; y < 8; y++) 113 | { 114 | //cmd 0 tm / 9 alles ok 115 | //strcpy(initCmd, initBaseCommand[y]); 116 | consoleOut("cmd : " + String(y)); 117 | 118 | //Serial.println("comMand ex len ex checkSum = " + String(initBaseCommand[y])); 119 | delayMicroseconds(250); 120 | if( diagNose != 0 ) consoleOut("zb send cmd " + String(y)); 121 | 122 | sendZB( initBaseCommand[y] ); 123 | ledblink(1,50); 124 | 125 | //check if anything was received 126 | readZB(s_d); // we read but flush the answer 127 | 128 | //DebugPrintln("inMessage = " + String(inMessage) + " rc = " + String(readCounter)); 129 | } 130 | // now all the commands are send 131 | //first clean (zero out) initCmd 132 | 133 | //memset(&initCmd, 0, sizeof(initCmd)); //zero out all buffers we could work with "messageToDecode" 134 | //delayMicroseconds(250); 135 | memset(&initBaseCommand, 0, sizeof(&initBaseCommand)); //zero out all buffers we could work with "messageToDecode" 136 | delayMicroseconds(250); 137 | } 138 | 139 | 140 | // ************************************************************************************** 141 | // the extra command for normal operations 142 | // ************************************************************************************** 143 | void sendNO() { 144 | char noCmd[49] ={0} ; // this buffer must have the right length 145 | char ecu_id_reverse[13]; //= {ECU_REVERSE()}; 146 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 147 | char s_d[254]={0}; // provide a buffer for the call to readZB 148 | 149 | snprintf(noCmd, sizeof(noCmd), "2401FFFF1414060001000F1E%sFBFB1100000D6030FBD3000000000000000004010281FEFE", ecu_id_reverse); 150 | //lenth=36+12+1 151 | //Serial.println("noCmd = " + String(noCmd)); 152 | 153 | //add sln at the beginning 154 | //char comMand[100]; 155 | //sprintf(comMand, "%02X", (strlen(noCmd) / 2 - 2)); 156 | //strcat(comMand, noCmd); 157 | 158 | //add the CRC at the end of the command is done by sendZigbee 159 | String term = "send normal ops initCmd = " + String(noCmd); 160 | consoleOut(term); 161 | sendZB( noCmd ); 162 | 163 | //check if anything was received 164 | //waitSerial2Available(); 165 | readZB(s_d);//do nothing with the returned value 166 | 167 | //if(readCounter == 0) Serial.println("no answer"); 168 | 169 | consoleOut(F("zb initializing ready, now check running")); 170 | //zero out 171 | //memset(&comMand, 0, sizeof(comMand)); //zero out 172 | //delayMicroseconds(250); 173 | memset(&noCmd, 0, sizeof(noCmd)); //zero out 174 | delayMicroseconds(250); 175 | 176 | } 177 | -------------------------------------------------------------------------------- /ZIGBEE_HEALTH.ino: -------------------------------------------------------------------------------- 1 | // ************************************************************************* 2 | // system healtcheck 3 | //************************************************************************** 4 | 5 | void healthCheck() { 6 | #ifdef TEST 7 | return; 8 | #endif 9 | if(Mqtt_Format != 0 && Mqtt_Format != 5 && Mqtt_stateIDX != 0) { 10 | if (mqttConnect() ) { 11 | char Mqtt_send[26]; 12 | strcpy( Mqtt_send, Mqtt_outTopic) ; 13 | if( Mqtt_send[strlen(Mqtt_send)-1] == '/' ) { 14 | strcat(Mqtt_send, "heap"); 15 | } 16 | 17 | char toMQTT[50]={0}; // when i make this bigger a crash occures 18 | 19 | sprintf( toMQTT, "{\"idx\":%d,\"svalue\":\"%ld\",\"heap\":%ld}", Mqtt_stateIDX, esp_get_free_heap_size(), esp_get_free_heap_size() ); 20 | //must be like {"idx":901,"svalue":"22968", "heap":22968} 21 | 22 | consoleOut("mqtt publish heap, mess is : " + String(toMQTT) ); 23 | MQTT_Client.publish ( Mqtt_send, toMQTT, false); 24 | 25 | memset(&toMQTT, 0, sizeof(&toMQTT)); //zero out 26 | delayMicroseconds(250); 27 | } 28 | } 29 | 30 | 31 | if(!timeRetrieved) { 32 | getTijd(); 33 | eventSend(1); 34 | } 35 | 36 | // reset the errorcode so that polling errors remain 37 | if(errorCode >= 3000) errorCode -= 3000; 38 | if(errorCode >= 200) errorCode -= 200; 39 | if(errorCode >= 100) errorCode -= 100; 40 | 41 | // if there are no inverters, we don't start the coordinator 42 | if( inverterCount < 1 ) { 43 | if (diagNose) consoleOut(F("skipping, no inverters")); 44 | //Update_log( 2, "no inverter"); 45 | zigbeeUp = 0; 46 | return; 47 | } 48 | 49 | switch(checkCoordinator() ) // send the 2700 command 50 | { 51 | case 0: 52 | zigbeeUp = 1; // all oke 53 | diagNose = 0; // reset diagNose as this costs cpu 54 | break; 55 | 56 | case 2: 57 | //zigbeeUp = 0; 58 | //String term = "zb down"; 59 | //Update_log("zigbee", "zb down" ); 60 | consoleOut("zb down"); 61 | resetCounter += 1; 62 | resetValues(false, false); // reset all values, no mqtt 63 | // try to start the coordinator 64 | Serial.println("hc starting coordinator"); 65 | if (coordinator(true) ) zigbeeUp = 1; else zigbeeUp = 0; 66 | //events.send( "reload", "message"); 67 | eventSend(1); 68 | } 69 | 70 | } 71 | 72 | int checkCoordinator() { 73 | // this is basically the 2700 command 74 | // the answer can mean that the coordinator is up, not yet started or no answer 75 | // we evaluate that 76 | // first empty serial2, comming from coordinator this is necessary; 77 | //empty_serial2(); is done in the loop 78 | 79 | char ecu_id_reverse[13]; 80 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 81 | char * tail; 82 | 83 | // the response = 67 00, status 1 bt, IEEEAddr 8bt, ShortAddr 2bt, DeviceType 1bt, Device State 1bt 84 | // FE0E 67 00 00 FFFF 80971B01A3D8 0000 0709001 85 | // status = 00 means succes, IEEEAddress= FFFF80971B01A3D8, ShortAdr = 0000, devicetype=07 bits 0 to 2 86 | 87 | //Device State 09 started as zigbeecoordinator 88 | 89 | consoleOut("checkZigbeeRadio"); 90 | //if(log) Update_Log("zigbee", "checking zb module"); 91 | //check the radio, send FE00670067 92 | // when ok the returned string = FE0E670000FFFF + ECU_ID REVERSE + 00000709001 93 | // so we check if we have this 94 | 95 | char checkCommand[10]; // we send 2700 to the zb 96 | strncpy(checkCommand, "2700", 5); 97 | 98 | char reCeived[254]={0}; // a buffer for the received message 99 | char s_d[100]={0}; 100 | // now we do this 3 times 101 | for (int x=1; x<4; x++) 102 | { 103 | 104 | sendZB( checkCommand ); 105 | 106 | s_d[0]='\0'; 107 | // now read the answer if there is one 108 | strcpy(reCeived, readZB(s_d)); 109 | delayMicroseconds(250); 110 | 111 | //if ( waitSerial2Available() ) { readZigbee(); } else { readCounter = 0;} // when nothing available we don't read 112 | //if(diagNose == 1) Serial.println("cc inMessage = " + String(inMessage) + " rc = " + String(readCounter)); 113 | 114 | 115 | // we get this : FE0E670000 FFFF80971B01A3D8 0000 07090011 or 116 | // received : FE0E670000 FFFF80971B01A3D6 0000 0709001F when ok 117 | 118 | //check if ecu_id_reverse is in the string, then split it there + 2 bytes 119 | if( strstr(reCeived, ecu_id_reverse) ) 120 | { 121 | tail = split(reCeived, ecu_id_reverse + 4); 122 | // the tail should contain 0709 123 | if( strstr(tail, "0709") ) 124 | { 125 | consoleOut("found 0709, running oke"); 126 | zigbeeUp = 1; 127 | return 0; 128 | } 129 | } 130 | delay(700); 131 | reCeived[0] = '\0'; 132 | 133 | if( diagNose != 0 ) consoleOut("retrying.."); 134 | } 135 | // if we come here 3 attempts failed 136 | return 2; 137 | } 138 | 139 | 140 | //void ZigbeePing() { 141 | // // if the ping command failed then we have to restart the coordinator 142 | // //Update_Log("zigbee", "check serial loopback"); 143 | // // these commands already have the length 00 and checksum 20 resp 26 144 | // char pingCmd[5]={"2101"}; // ping 145 | // char s_d[200] = {0}; 146 | // if(diagNose !=0 ) consoleOut(F("send zb ping")); 147 | // 148 | // sendZB( pingCmd ); // answer should be FE02 6101 79 07 1C 149 | //// if ( waitSerial2Available() ) { readZigbee(); } else { readCounter = 0;} // when nothing available we don't read 150 | //// DebugPrintln("inMessage = " + String(inMessage) + " rc = " + String(readCounter)); 151 | // char reCeived[254]={0}; 152 | // strcpy(reCeived, readZB(s_d)); 153 | // 154 | // 155 | // if (strstr(reCeived, "FE026101" ) == NULL) 156 | // { 157 | // consoleOut(F("no ping answer")); 158 | // } else { 159 | // consoleOut(F("ping ok")); 160 | // } 161 | // // we ignore the answer 162 | //} 163 | 164 | //bool ZigbeeLoopBack() { 165 | // // if the ping command failed then we have to restart the coordinator 166 | // //Update_Log("zigbee", "check serial loopback"); 167 | // // these commands already have the length 00 and checksum 20 resp 26 168 | // char loopCmd[11]={"2710AABBCC"}; // ping 169 | // char s_d[200] = {0}; 170 | // if(diagNose !=0 ) consoleOut(F("send zb loopback")); 171 | // 172 | // sendZB( loopCmd ); // answer should be FE02 6101 79 07 1C 173 | //// if ( waitSerial2Available() ) { readZigbee(); } else { readCounter = 0;} // when nothing available we don't read 174 | //// DebugPrintln("inMessage = " + String(inMessage) + " rc = " + String(readCounter)); 175 | // 176 | // char reCeived[254]={0}; 177 | // strcpy(reCeived, readZB(s_d)); 178 | // 179 | // if(readCounter > 10) return false; 180 | // 181 | // if (strstr(reCeived, "AABBCC" ) == NULL) 182 | // { 183 | // consoleOut(F("no loopback answer")); 184 | // return false; 185 | // } else { 186 | // consoleOut(F("loopback ok")); 187 | // return true; 188 | // } 189 | //} 190 | // ************************************************************************* 191 | // hard reset the cc25xx 192 | // ************************************************************************* 193 | void ZBhardReset() 194 | { 195 | digitalWrite(ZB_RESET, LOW); 196 | delay(500); 197 | digitalWrite(ZB_RESET, HIGH); 198 | //char term[20] = {"ZBmodule hard reset"} ; 199 | Update_Log(2, "hard reset"); 200 | consoleOut("ZB module hard reset"); 201 | delay(2000); //wait for the cc2530 to reboot 202 | } 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /ZIGBEE_HELPERS.ino: -------------------------------------------------------------------------------- 1 | // ***************************************************************************** 2 | // read zigbee 3 | // ***************************************************************************** 4 | 5 | char * readZB( char inMess[] ) { 6 | readCounter = 0; 7 | String term=""; 8 | //wait a while untill something available 9 | //if not within the reactiontime we return NULL 10 | if( !waitSerial2Available() ) { 11 | inMess[0]='\0'; 12 | consoleOut(F(" readZB: nothing to read")); 13 | return inMess; 14 | } 15 | 16 | char oneChar[10] = {0}; 17 | //fullIncomingMessage[0] = '\0'; //terminate otherwise all is appended 18 | //memset( &inMessage, 0, sizeof(inMessage) ); //zero out the 19 | //delayMicroseconds(250); 20 | 21 | while (Serial2.available()) 22 | { 23 | //Serial.print("#"); 24 | // here we have the danger that when readcounter reaches 512, there are 1024 bytes processed 25 | // the length of a poll answer is usually not more than 223 26 | if (readCounter < CC2530_MAX_SERIAL_BUFFER_SIZE/2) 27 | { 28 | sprintf( oneChar, "%02X", Serial2.read() ); // always uppercase 29 | strncat(inMess, oneChar, 2); // append 30 | readCounter += 1; 31 | } 32 | else 33 | { 34 | empty_serial2(); // remove all excess data in the buffer at once 35 | } 36 | if (Serial2.available() == 0) delay(120); // we wait if there comes more data 37 | } 38 | // now we should have catched inMessage 39 | if(readCounter == 0) inMess[0]='\0'; 40 | // with swaps we get F8 sometimes, this removes it 41 | if(inMess[0] == 'F' && inMess[1] == '8') { 42 | Serial.println("found F8"); 43 | strcpy(inMess, &inMess[2]); 44 | } 45 | 46 | consoleOut("readZB " + String(inMess) + " rc=" + String(readCounter) + "\n"); 47 | 48 | //if(diagNose == 1) Serial.println(term); else if(diagNose == 2) ws.textAll(term); 49 | //} 50 | delayMicroseconds(250); // give it some time 51 | return inMess; 52 | } 53 | 54 | // ***************************************************************************** 55 | // send to zigbee radio 56 | // ***************************************************************************** 57 | void sendZB( char printString[] ) 58 | { 59 | char bufferSend[254]={0}; 60 | char byteSend[3]; // never more than 2 bytes 61 | sprintf(bufferSend, "%02X", (strlen(printString) / 2 - 2)); //now contains a hex representation of the length 62 | //first add length and the checksum 63 | strcat(bufferSend, printString); // now put slen and the rest together 64 | delayMicroseconds(250); 65 | 66 | strcat(bufferSend,checkSumString(bufferSend).c_str()) ; 67 | 68 | //until here this works! 69 | empty_serial2(); 70 | if (Serial2.availableForWrite() > (uint8_t)strlen(bufferSend)) 71 | { 72 | Serial2.write(0xFE); //we have to send "FE" at start of each command 73 | for (uint8_t i = 0; i <= strlen(bufferSend) / 2 - 1; i++) 74 | { 75 | // we use 2 characters to make a byte 76 | strncpy(byteSend, bufferSend + i * 2, 2); 77 | delayMicroseconds(250); // 78 | 79 | Serial2.write(StrToHex(byteSend)); //turn the two chars to a byte and send this 80 | } 81 | 82 | Serial2.flush(); //wait till the full command was sent 83 | 84 | } 85 | 86 | consoleOut("sendZB FE" + String(bufferSend)); 87 | 88 | //else if (diagNose == 2) ws.textAll("sendZB FE" + String(bufferSend)); 89 | } 90 | 91 | // ***************************************************************************** 92 | // return checksum as a string 93 | // ***************************************************************************** 94 | String checkSumString(char Command[]) 95 | { 96 | char bufferCRC[254] = {0}; 97 | char bufferCRC_2[254] = {0}; 98 | 99 | strncpy(bufferCRC, Command, 2); //as starting point perhaps called "seed" use the first two chars from "Command" 100 | delayMicroseconds(250); //give memset a little bit of time to empty all the buffers 101 | 102 | for (uint8_t i = 1; i <= (strlen(Command) / 2 - 1); i++) 103 | { 104 | strncpy(bufferCRC_2, Command + i * 2, 2); //use every iteration the next two chars starting with char 2+3 105 | delayMicroseconds(250); //give memset a little bit of time to empty all the buffers 106 | sprintf(bufferCRC, "%02X", StrToHex(bufferCRC) ^ StrToHex(bufferCRC_2)); 107 | delayMicroseconds(250); //give memset a little bit of time to empty all the buffers 108 | } 109 | return String(bufferCRC); 110 | } 111 | 112 | 113 | // ************************************************************************** 114 | // data converters 115 | // ************************************************************************** 116 | 117 | // calculate and return the length of the message 118 | char *sLen(const char Command[]) 119 | { 120 | static char bufferSln[8]; // why is this so big 254 121 | sprintf(bufferSln, "%02X", (strlen(Command) / 2 - 2)); 122 | delayMicroseconds(250); //give memset a little bit of time to empty all the buffers 123 | return bufferSln; 124 | } 125 | 126 | 127 | // convert a char to Hex ****************************************************** 128 | int StrToHex(char str[]) 129 | { 130 | return (int)strtol(str, 0, 16); 131 | } 132 | 133 | // reverse the ecu id ********************************************************** 134 | String ECU_REVERSE() { 135 | String ecu_id = String(ECU_ID); 136 | String reverse = ecu_id.substring(10,12) + ecu_id.substring(8,10) + ecu_id.substring(6,8) + ecu_id.substring(4,6) + ecu_id.substring(2,4) + ecu_id.substring(0,2); 137 | return reverse; 138 | } 139 | 140 | // ****************************************************************************** 141 | // reboot an inverter 142 | // ******************************************************************************* 143 | void inverterReboot(int which) { 144 | char ecu_id_reverse[13]; 145 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 146 | if(zigbeeUp != 1) { 147 | ws.textAll("skip inverter reboot, zigbee down"); 148 | return; 149 | } 150 | 151 | 152 | char rebootCmd[57]={0}; 153 | char s_d[200]={0}; 154 | 155 | char command[][50] = { 156 | "2401", 157 | "1414060001000F13", 158 | "FBFB06C1000000000000A6FEFE", 159 | }; 160 | //Serial.println("constructing command"); 161 | strncpy( rebootCmd, command[0], sizeof(command[0]) ); 162 | strncat( rebootCmd, Inv_Prop[which].invID, 4 ); // ad the 2nd byte of inv_id 163 | strncat( rebootCmd, command[1], sizeof(command[1]) ); 164 | strncat( rebootCmd, ecu_id_reverse, sizeof(ecu_id_reverse) ); 165 | strncat( rebootCmd, command[2], sizeof(command[2]) ); 166 | //Serial.println("command: " + String(rebootCmd)); 167 | ws.textAll("the rebootCmd = " + String(rebootCmd)); 168 | 169 | //2401 BBAA 1414060001000F13 80971B01A3D6 FBFB06C1000000000000A6FEFE 170 | //should be 2401 103A 1414060001000F13 80 97 1B 01 A3 D6 FBFB06C1000000000000A6FEFE 171 | // got 1414060001000F1380971B01A3D6FBFB06C1000000000000A6FEFE 172 | // 2401 BBAA 1414060001000F13 80971B01A3D6 1414060001000F13 173 | // 2401 3A10 1414060001000F13 80971B01B3D6 FBFB06C1000000000000A6FEFE 174 | // put sln at the beginning and CRC at the end done by sendZigbee() 175 | //swap_to_zb(); 176 | 177 | sendZB(rebootCmd); 178 | delay(1000); 179 | //char s_d[150]={0}; 180 | readZB(s_d); 181 | 182 | // if(readCounter == 0) { 183 | // } 184 | // ws.textAll("received : " + String(inMess) ); 185 | } 186 | 187 | // ****************************************************************************** 188 | // reset all values and send mqtt 189 | // ****************************************************************************** 190 | void resetValues(bool energy, bool mustSend) { 191 | for(int z=0; z 2000) return false; // return false after 2000 milis time out 212 | } 213 | // if we are here there is something available 214 | delay(500); 215 | return true; 216 | } 217 | 218 | char *split(char *str, const char *delim) 219 | { 220 | char *p = strstr(str, delim); 221 | 222 | if (p == NULL) 223 | return NULL; // delimiter not found 224 | 225 | *p = '\0'; // terminate string after head 226 | return p + strlen(delim); // return tail substring 227 | } 228 | -------------------------------------------------------------------------------- /ZIGBEE_PAIR.ino: -------------------------------------------------------------------------------- 1 | void pairOnActionflag() { 2 | //start with setup the coordinator 3 | //can we pair when the radio is up for normal operation 4 | 5 | char term[20]; 6 | sprintf(term, "inverter %d" , Inv_Prop[iKeuze].invSerial); 7 | Update_Log(4, term); 8 | if( !coordinator(false) ) { 9 | //term="pairing failed, zb down"; 10 | Update_Log(4, "failed"); 11 | consoleOut("pairing failed, zb down"); 12 | return; 13 | } 14 | 15 | consoleOut("trying pair inv " + String(iKeuze)); 16 | // now that we know that the radio is up, we don't need to test this in the pairing routine 17 | 18 | if( pairing(iKeuze) ) { 19 | //DebugPrintln("pairing success, saving configfile"); 20 | String term = "success, inverter got id " + String(Inv_Prop[iKeuze].invID); 21 | Update_Log(2, "success"); 22 | consoleOut(term); 23 | //} else if(diagNose==2){ws.textAll(term);} 24 | 25 | } else { 26 | strncpy(Inv_Prop[iKeuze].invID, "0000", 6); 27 | String term = "failed, inverter got id " + String(Inv_Prop[iKeuze].invID); 28 | Update_Log(4, "failed"); 29 | consoleOut(term); 30 | 31 | } 32 | String bestand = "/Inv_Prop" + String(iKeuze) + ".str"; // /Inv_Prop0.str 33 | writeStruct(bestand, iKeuze); // alles opslaan in SPIFFS 34 | 35 | //after successfull pairing we issue the command for normal ops 36 | sendNO(); 37 | checkCoordinator(); // updates the log 38 | } 39 | 40 | void handlePair(AsyncWebServerRequest *request) { 41 | 42 | strncpy(Inv_Prop[iKeuze].invID, "1111", 4); // this value makes the pairing page visable 43 | 44 | actionFlag = 60; // we do this because no delay is alowed within an async request 45 | toSend=FPSTR(WAIT_PAIR); 46 | toSend.replace("{#}", String(iKeuze)); 47 | request->send(200, "text/html", toSend); //send the html code to the client 48 | } 49 | 50 | 51 | bool pairing(int which) { 52 | 53 | //the pairing process consistst of 4 commands sent to the coordinator 54 | char pairCmd[254]={0}; 55 | char s_d[250]={0}; 56 | char ecu_id_reverse[13]; 57 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 58 | char ecu_short[5]={0}; 59 | strncat(ecu_short, ECU_ID + 2, 2); // D8A3011B9780 should be A3D8 60 | strncat(ecu_short, ECU_ID, 2); 61 | //ecu_short[5]='\0'; no need for as strncat terminates with \0 62 | String term = ""; 63 | bool success=false; 64 | for ( int y = 0; y < 4; y++) { 65 | switch (y) { 66 | case 0:// command 0 67 | // build command 0 this is "24020FFFFFFFFFFFFFFFFF14FFFF14" + "0D0200000F1100" + String(invSerial) + "FFFF10FFFF" + ecu_id_reverse 68 | snprintf(pairCmd, sizeof(pairCmd), "24020FFFFFFFFFFFFFFFFF14FFFF140D0200000F1100%sFFFF10FFFF%s", Inv_Prop[which].invSerial , ecu_id_reverse); 69 | break; 70 | case 1: 71 | // build command 1 this is "24020FFFFFFFFFFFFFFFFF14FFFF14" + "0C0201000F0600" + inv serial, 72 | snprintf(pairCmd, sizeof(pairCmd), "24020FFFFFFFFFFFFFFFFF14FFFF140C0201000F0600%s", Inv_Prop[which].invSerial ); 73 | break; 74 | case 2: 75 | // build command 2 this is "24020FFFFFFFFFFFFFFFFF14FFFF14" + "0F0102000F1100" + invSerial + short ecu_id_reverse, + 10FFF + ecu_id_reverse 76 | 77 | snprintf(pairCmd, sizeof(pairCmd), "24020FFFFFFFFFFFFFFFFF14FFFF140F0102000F1100%s%s10FFFF%s", Inv_Prop[which].invSerial, ecu_short, ecu_id_reverse); 78 | break; 79 | case 3: 80 | // now build command 3 this is "24020FFFFFFFFFFFFFFFFF14FFFF14" + "010103000F0600" + ecu_id_reverse, 81 | snprintf(pairCmd, sizeof(pairCmd), "24020FFFFFFFFFFFFFFFFF14FFFF14010103000F0600%s", ecu_id_reverse); 82 | } 83 | delayMicroseconds(250); 84 | // send 85 | term = "pair command " + String(y) + " = " + String(pairCmd); 86 | consoleOut(term); 87 | //else if(diagNose == 2) ws.textAll(term); 88 | 89 | sendZB(pairCmd); 90 | delay(1500); // give the inverter the chance to answer 91 | //check if anything was received 92 | // after sending cmd 1 or 2 we can expect an answer to decode 93 | // we let decodePairMessage retrieve the answer then. 94 | // the answers on cmd0 or cmd3 are flushed 95 | if(y == 1 || y == 2) { 96 | // if y 1 or 2 we catch and decode the answer 97 | if ( decodePairMessage(which) ) 98 | { 99 | success = true; // if at least one of these 2 where true we had success 100 | } 101 | } else { 102 | // if not y == 1 or y == 2 we waste the received message 103 | readZB(s_d); 104 | } 105 | } 106 | //now all 4 commands have been sent 107 | if(success) {return true; } else { return false;} // when paired 0x103A 108 | } 109 | 110 | 111 | bool decodePairMessage(int which) 112 | { 113 | char messageToDecode[CC2530_MAX_SERIAL_BUFFER_SIZE] = {0}; 114 | char _CC2530_answer_string[] = "44810000"; 115 | char _noAnswerFromInverter[32] = "FE0164010064FE034480CD14011F"; 116 | char * result; 117 | char temp[13]; 118 | char s_d[250]={0}; 119 | String term = ""; 120 | 121 | strcpy(messageToDecode, readZB(s_d)); 122 | //Serial.println("messageToDecode = " + String(messageToDecode)); 123 | consoleOut("decoding : " + String(messageToDecode)); 124 | if (readCounter == 0 || strlen(messageToDecode) < 6 ) // invalid message 125 | { 126 | consoleOut("no usable code, returning.."); 127 | messageToDecode[0]='\0'; 128 | return false; 129 | } 130 | // can we conclude that a valid pair answer cannot be less than 60 bytes 131 | if (strlen(messageToDecode) > 222 || readCounter < 60 || strlen(messageToDecode) < 6) 132 | { 133 | //term = "no pairing code, returning..."; 134 | consoleOut(F("no valid pairing code, returning...")); 135 | messageToDecode[0]='\0'; 136 | return false; 137 | } 138 | // the message is shorter but not too short so continueing 139 | 140 | if (!strstr(messageToDecode, Inv_Prop[which].invSerial)) { 141 | consoleOut("not found serialnr, returning"); 142 | //if(diagNose==1) Serial.println(term); else if(diagNose==2) ws.textAll(term); 143 | messageToDecode[0]='\0'; 144 | return false; 145 | } 146 | 147 | if ( strstr(messageToDecode, Inv_Prop[which].invSerial) ) { 148 | result = split(messageToDecode, Inv_Prop[which].invSerial); 149 | } 150 | consoleOut("result after 1st splitting = " + String(result)); 151 | 152 | // now we keep splitting as long as result contains the serial nr 153 | while ( strstr(result, Inv_Prop[which].invSerial) ) 154 | { 155 | result = split(result, Inv_Prop[which].invSerial); 156 | } 157 | consoleOut("result after splitting = " + String(result)); 158 | // result are the bytes behind the serialnr 159 | // now we know that it is what we expect, a string right behind the last occurence of the serialnr 160 | 161 | memset(&Inv_Prop[which].invID, 0, sizeof(Inv_Prop[which].invID)); //zero out the 162 | delayMicroseconds(250); 163 | strncpy(Inv_Prop[which].invID, result, 4); // take the 1st 4 bytes 164 | 165 | term = "found invID = " + String(Inv_Prop[which].invID); 166 | consoleOut(term); 167 | // why is this? Can it get this value? 168 | if ( String(Inv_Prop[which].invID) == "0000" ) { 169 | return false; 170 | } 171 | return true; 172 | } 173 | -------------------------------------------------------------------------------- /ZIGBEE_POLLING.ino: -------------------------------------------------------------------------------- 1 | void polling(int which) { 2 | polled[which]=false; //nothing is displayed on webpage 3 | if(zigbeeUp != 1) 4 | { 5 | consoleOut(F("skipping poll, coordinator down!")); // 6 | return; 7 | } 8 | 9 | char pollCommand[65] = {0}; 10 | char ecu_id_reverse[13]; 11 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 12 | 13 | snprintf(pollCommand, sizeof(pollCommand), "2401%s1414060001000F13%sFBFB06BB000000000000C1FEFE", Inv_Prop[which].invID, ecu_id_reverse); 14 | delayMicroseconds(250); 15 | // put in the CRC at the end of the command done in sendZigbee 16 | consoleOut("pollCommand ex checksum:" + String(pollCommand)); 17 | //} else 18 | //if(diagNose == 2) ws.textAll ("pollCommand ex checksum:" + String(pollCommand)); 19 | 20 | sendZB(pollCommand); 21 | 22 | // decodePollAnswer will read and analyze the answer 23 | errorCode = decodePollAnswer(which); 24 | switch( errorCode ) 25 | { 26 | case 0: 27 | polled[which] = true; 28 | yield(); 29 | mqttPoll(which); // 30 | yield(); 31 | break; 32 | default: 33 | consoleOut("polling failed with errorcode " + String(errorCode)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ZIGBEE_QUERY.ino: -------------------------------------------------------------------------------- 1 | void querying(int which) { 2 | //polled[which]=false; //nothing is displayed on webpage 3 | 4 | consoleOut("query inverter " + String(which)); 5 | char queryCommand[65] = {0}; 6 | char ecu_id_reverse[13]; 7 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 8 | // 2401 1414060000100F13 FBFB06DE000000000000E4FEFE 9 | snprintf(queryCommand, sizeof(queryCommand), "2401%s1414060001000F13%sFBFB06DE000000000000E4FEFE", Inv_Prop[which].invID, ecu_id_reverse); 10 | // put in the CRC at the end of the command done in sendZigbee 11 | consoleOut("\nraw queryCommand :" + String(queryCommand)); 12 | #ifndef TEST 13 | if(zigbeeUp != 1) 14 | { 15 | consoleOut(F("\nskipping query, coordinator down!")); // 16 | return; 17 | } 18 | #endif 19 | sendZB(queryCommand); 20 | 21 | // decodeQueryAnswer will read and analyze the answer 22 | // the second arg tells that we query without throttle 23 | errorCode = decodeQueryAnswer(which, false); 24 | // in case of error there are 3 possibilities 25 | // the throttle and query succeeded, the actual throttle value is in preferences 26 | // only the query succeeded , the actual throttle value is in preferences 27 | // the throttle and query both failed, (coodinator down, inverter down or no answer) program -1 28 | 29 | 30 | switch( errorCode ) 31 | { 32 | case 0: 33 | yield(); 34 | break; 35 | case 15: 36 | consoleOut("Throttle failed error 15"); 37 | yield(); 38 | break; 39 | default: 40 | String key = "maxPwr" + String(which); 41 | preferences.putUInt(key.c_str(), -1 ); 42 | consoleOut("query failed with errorcode " + String(errorCode)); 43 | } 44 | } 45 | 46 | 47 | // ****************************************************************** 48 | // decode query zigbee answer 49 | // ****************************************************************** 50 | int decodeQueryAnswer(int welke, bool throTTle) 51 | { 52 | // if throttle == true we do this query after a throttle attempt 53 | // which mens that we need to determine desired and programmed 54 | char messageToDecode[CC2530_MAX_SERIAL_BUFFER_SIZE] = {0}; 55 | char s_d[CC2530_MAX_SERIAL_BUFFER_SIZE] = {0}; 56 | uint8_t Message_begin_offset = 0; 57 | char *payload; 58 | int fault=0; 59 | int programmedVal; 60 | //float calibrateFactor; 61 | consoleOut("decoding inverter " + String(welke)); 62 | //retrieve the answer 63 | strcpy(messageToDecode, readZB(s_d)); 64 | // for testing we used these fake answers 65 | #ifdef TEST 66 | if(Inv_Prop[welke].invType != 2) { 67 | strcpy(messageToDecode, "FE0164010064FE0345C43A1000A8FE034480001400D3FE0345C43A1000A8FE6E4481000006013A101414006900C3B77000005A408000158215FBFB4DDE041105440FE5020F32B003CF05440FE5020F32B0066604CC0EA3A804D70214050C100FD80ED07A0F32B0056A054F019000641F3FE480068ACE8ACE000130103030190604001D21DB3B6600000000FEFE3A100E00"); 68 | readCounter = 120; 69 | } else { 70 | readCounter = 120; 71 | strcpy(messageToDecode, "FE0164010064FE0345C4A2F600D6FE034480001400D3FE0345C4A2F600D6FE0345C4A2F600D6FE7D448100000601A2F61414008000CC73F7000069704000202594FBFB5CDDDE010426E20013BA14B413EC000A032000500003DD03A403200003E80000000000640003DD03A503350304012C060D03FF045F0E93140E3204890258001374136F125C0014032007D023A6031401BF03D9FFFFFFFFFFFF23A6C8FF1C01FEFEA2F6734E"); 72 | } 73 | #endif 74 | 75 | if (readCounter == 0) { 76 | consoleOut(F("decodeQueryAnswer: no answer on request")); 77 | return 50; //no answer 78 | } 79 | 80 | if (strstr(messageToDecode, "4481") == NULL) 81 | { 82 | consoleOut("no AF_INCOMING_MSG"); // this is the real answer 83 | fault=13; 84 | } 85 | if(fault > 9 ) { 86 | memset(&messageToDecode, 0, sizeof(messageToDecode)); //zero out 87 | delayMicroseconds(250); 88 | return fault; 89 | } 90 | 91 | // if we are here we should have a string containing 4481, we analyse it defferently for YC600 an DS3 92 | payload = split(messageToDecode, "FBFB"); // remove the 0000 as well 93 | // */ 94 | // for test we gave payload a value 95 | consoleOut("payload " + String(payload) ); 96 | 97 | // we must handle the DS3 and YC600 differently 98 | 99 | //float calibrateFactor = 1.0 + Inv_Prop[welke].calib / 100.0; //eg 1.12 100 | String key = "maxPwr" + String(welke); 101 | consoleOut("preferences key = " + key); 102 | // ************************************************************** 103 | // DECODING YC600 / QS1 104 | //*************************************************************** 105 | if(Inv_Prop[welke].invType != 2) 106 | { 107 | consoleOut("decoding a YC600 / QS1 "); 108 | // we know the tail is 109 | //FBFB4DDE041105440FE5020F32B003CF05440FE5020F32B0066604CC0EA3A804D70214050C100FD80ED07A0F32B0056A054F0> 110 | //FE480068ACE8ACE000130103030190604001D1B153B6600000000FEFE3A100E5D 111 | // the powervalue is right behind 2B66 112 | char target[5] = {"3B66"}; 113 | 114 | char *ptr = strstr(payload, target); // find "3B66" 115 | if (ptr != NULL && ptr - payload >= 4) { 116 | char before[5]; 117 | strncpy(before, ptr - 4, 4); 118 | before[4] = '\0'; 119 | // before is the hex value of the throttle setting 120 | // convert from hex string to int 121 | //int decimalValue = (int)strtol(before, NULL, 16) / 28.89; 122 | int decimalValue = (strtol(before, NULL, 16) * 100 + 2889/2) / 2889; 123 | // we calculate the original value 124 | //double result = decimalValue / 28.89; 125 | programmedVal = decimalValue - Inv_Prop[welke].calib; 126 | //programmedVal = floor(decimalValue / calibrateFactor); 127 | #ifdef TEST 128 | programmedVal = 100 * testCounter - Inv_Prop[welke].calib; 129 | #endif 130 | consoleOut("power value in YC600 = " + String(programmedVal)); 131 | // we always write this value in preferences 132 | preferences.putInt(key.c_str(), programmedVal ); 133 | consoleOut("preference written = " + String(preferences.getInt(key.c_str(), 0))); 134 | // if we are here, a valid query is done and we have the throtvalue written 135 | // if throttled, we determine wheter the throttle succeeded, this is only the 136 | // case when desired and programmed match 137 | if(throTTle) 138 | { 139 | consoleOut("the desired throttle = " + String(desiredThrottle[welke])); 140 | if(abs(programmedVal - desiredThrottle[welke]) > 2) 141 | { 142 | consoleOut("desired and programmed throttlevalue mismatch"); 143 | // to indicate that there was a failure 144 | return 15; 145 | } 146 | } 147 | 148 | } 149 | } else {// end if(invType != 2) 150 | // ******************************************************************************* 151 | // DECODING DS3 152 | // *************************************************************** 153 | consoleOut("decoding a DS3 "); 154 | //the payload looks like FBFB5CDDDE010426E20013BA14B413EC000A032000500003DD03A403200003E80000000000640003DD03A503350304012C060D03FF045F0E93140E3204890258001374136F125C0014032007D023A6031401BF03D9FFFFFFFFFFFF23A6C8FF1C01FEFEA2F6734E 155 | // for test we give payload a conten 156 | //strcpy(payload, "FBFB5CDDDE0104 26E2 0013BA14B413EC000A032000500003DD03A403200003E80000000000640003DD03A503350304012C060D03FF045F0E93140E3204890258001374136F125C0014032007D023A6031401BF03D9FFFFFFFFFFFF23A6C8FF1C01FEFEA2F6734E"); 157 | 158 | char powval[5]; // 4 chars + null terminator 159 | memcpy(powval, payload + 10, 4); // copy "26E2" 160 | //int decimalValue = (int)strtol(powval, NULL, 16) / 16.59; 161 | int decimalValue = (strtol(powval, NULL, 16) * 100 + 1659/2) / 1659; 162 | 163 | programmedVal = decimalValue - Inv_Prop[welke].calib; 164 | #ifdef TEST 165 | programmedVal = 100 * testCounter; 166 | #endif 167 | 168 | consoleOut("throttle value in DS3 = " + String(programmedVal)) ; 169 | // we always write this value in preferences 170 | preferences.putInt(key.c_str(), programmedVal ); 171 | consoleOut("preference written = " + String(preferences.getInt(key.c_str(), 0))); 172 | // if we are here, a valid query is done and we have the throtvalue written 173 | // if throttled, we determine wheter the throttle succeeded, this is only the 174 | // case when desired and programmed match 175 | 176 | if(throTTle) 177 | { 178 | consoleOut("the desired throttle = " + String(desiredThrottle[welke])); 179 | if(abs(programmedVal - desiredThrottle[welke]) > 2) { 180 | consoleOut("desired and programmed throttlevalue mismatch"); 181 | // to indicate that there was a failure 182 | return 15; 183 | } 184 | } 185 | // if we are here the desired is equal to the present so all oke 186 | 187 | } 188 | 189 | return 0; 190 | } -------------------------------------------------------------------------------- /ZIGBEE_QUERYING.ino: -------------------------------------------------------------------------------- 1 | void querying(int which) { 2 | //polled[which]=false; //nothing is displayed on webpage 3 | 4 | consoleOut("query inverter " + String(which)); 5 | char queryCommand[65] = {0}; 6 | char ecu_id_reverse[13]; 7 | ECU_REVERSE().toCharArray(ecu_id_reverse, 13); 8 | // 2401 1414060000100F13 FBFB06DE000000000000E4FEFE 9 | snprintf(queryCommand, sizeof(queryCommand), "2401%s1414060001000F13%sFBFB06DE000000000000E4FEFE", Inv_Prop[which].invID, ecu_id_reverse); 10 | // put in the CRC at the end of the command done in sendZigbee 11 | consoleOut("raw queryCommand :" + String(queryCommand)); 12 | 13 | if(zigbeeUp != 1) 14 | { 15 | consoleOut(F("skipping query, coordinator down!")); // 16 | return; 17 | } 18 | 19 | sendZB(queryCommand); 20 | 21 | // decodeQueryAnswer will read and analyze the answer 22 | errorCode = decodeQueryAnswer(which); 23 | switch( errorCode ) 24 | { 25 | case 0: 26 | //polled[which] = true; 27 | //yield(); 28 | //mqttPoll(which); // 29 | yield(); 30 | break; 31 | default: 32 | consoleOut("query failed with errorcode " + String(errorCode)); 33 | } 34 | } 35 | 36 | 37 | // ****************************************************************** 38 | // decode query zigbee answer 39 | // ****************************************************************** 40 | int decodeQueryAnswer(int welke) 41 | { 42 | char messageToDecode[CC2530_MAX_SERIAL_BUFFER_SIZE] = {0}; 43 | char s_d[CC2530_MAX_SERIAL_BUFFER_SIZE] = {0}; 44 | uint8_t Message_begin_offset = 0; 45 | char *payload; 46 | int fault=0; 47 | consoleOut("decoding inverter " + String(welke)); 48 | //retrieve the answer 49 | strcpy(messageToDecode, readZB(s_d)); 50 | // for testing we used these fake answers 51 | #ifdef TEST 52 | if(Inv_Prop[welke].invType != 2) { 53 | strcpy(messageToDecode, "FE0164010064FE0345C43A1000A8FE034480001400D3FE0345C43A1000A8FE6E4481000006013A101414006900C3B77000005A408000158215FBFB4DDE041105440FE5020F32B003CF05440FE5020F32B0066604CC0EA3A804D70214050C100FD80ED07A0F32B0056A054F019000641F3FE480068ACE8ACE000130103030190604001D21DB3B6600000000FEFE3A100E00"); 54 | readCounter = 120; 55 | } else { 56 | readCounter = 120; 57 | strcpy(messageToDecode, "FE0164010064FE0345C4A2F600D6FE034480001400D3FE0345C4A2F600D6FE0345C4A2F600D6FE7D448100000601A2F61414008000CC73F7000069704000202594FBFB5CDDDE010426E20013BA14B413EC000A032000500003DD03A403200003E80000000000640003DD03A503350304012C060D03FF045F0E93140E3204890258001374136F125C0014032007D023A6031401BF03D9FFFFFFFFFFFF23A6C8FF1C01FEFEA2F6734E"); 58 | } 59 | #endif 60 | 61 | if (readCounter == 0) { 62 | consoleOut(F("decodeQueryAnswer: no answer on request")); 63 | return 50; //no answer 64 | } 65 | // if(strstr(messageToDecode, "FE01640100") == NULL) // answer to AF_DATA_REQUEST 00=success 66 | // { 67 | // consoleOut( "AF_DATA_REQUEST failed" ); 68 | // fault = 10; 69 | // } else 70 | // if (strstr(messageToDecode, "FE03448000") == NULL) // AF_DATA_CONFIRM the 00 byte = success 71 | // { 72 | // consoleOut("no AF_DATA_CONFIRM"); 73 | // fault = 11; 74 | // } else 75 | // if (strstr(messageToDecode, "FE0345C4") == NULL) // ZDO_SRC_RTG_IND 76 | // { 77 | // consoleOut("no route receipt"); 78 | // //return 12; // this command seems facultative 79 | // } else 80 | if (strstr(messageToDecode, "4481") == NULL) 81 | { 82 | consoleOut("no AF_INCOMING_MSG"); // this is the real answer 83 | fault=13; 84 | } 85 | if(fault > 9 ) { 86 | memset(&messageToDecode, 0, sizeof(messageToDecode)); //zero out 87 | delayMicroseconds(250); 88 | return fault; 89 | } 90 | 91 | // if we are here we should have a string containing 4481, we analyse it defferently for YC600 an DS3 92 | payload = split(messageToDecode, "FBFB"); // remove the 0000 as well 93 | // */ 94 | // for test we give payload a value 95 | consoleOut("payload " + String(payload) ); 96 | 97 | // we must handle the DS3 and YC600 differently 98 | 99 | if(Inv_Prop[welke].invType != 2) { 100 | // for test we give payload a content 101 | consoleOut("decoding a YC600 / QS1 "); 102 | // we know the tail is 103 | //FBFB4DDE041105440FE5020F32B003CF05440FE5020F32B0066604CC0EA3A804D70214050C100FD80ED07A0F32B0056A054F0> 104 | //FE480068ACE8ACE000130103030190604001D1B153B6600000000FEFE3A100E5D 105 | // the powervalue is right behind 2B66 106 | char target[5] = {"3B66"}; 107 | 108 | char *ptr = strstr(payload, target); // find "3B66" 109 | if (ptr != NULL && ptr - payload >= 4) { 110 | char before[5]; 111 | strncpy(before, ptr - 4, 4); 112 | before[4] = '\0'; 113 | int decimalValue = (int)strtol(before, NULL, 16) / 28.89; // convert from hex string to int 114 | //double result = decimalValue / 28.89; 115 | consoleOut("power value YC600 = " + String(decimalValue)); 116 | // this should match with the set throttle value which is 117 | consoleOut("Inv_Prop[welke].maxPower = " + String(Inv_Prop[welke].maxPower)); 118 | if(abs(decimalValue - Inv_Prop[welke].maxPower) > 2) return 15; 119 | } else { 120 | consoleOut("0x3B66 not found or not enough characters before it."); 121 | return 15; 122 | } 123 | } else {// end if(invType != 2) 124 | 125 | // this is ds3 126 | consoleOut("decoding a DS3 "); 127 | //the payload looks like FBFB5CDDDE010426E20013BA14B413EC000A032000500003DD03A403200003E80000000000640003DD03A503350304012C060D03FF045F0E93140E3204890258001374136F125C0014032007D023A6031401BF03D9FFFFFFFFFFFF23A6C8FF1C01FEFEA2F6734E 128 | // for test we give payload a conten 129 | //strcpy(payload, "FBFB5CDDDE0104 26E2 0013BA14B413EC000A032000500003DD03A403200003E80000000000640003DD03A503350304012C060D03FF045F0E93140E3204890258001374136F125C0014032007D023A6031401BF03D9FFFFFFFFFFFF23A6C8FF1C01FEFEA2F6734E"); 130 | 131 | char powval[5]; // 4 chars + null terminator 132 | memcpy(powval, payload + 10, 4); // copy "26E2" 133 | int decimalValue = (int)strtol(powval, NULL, 16) / 16.59; 134 | String term="power value DS3 = " + String(powval) + " this is dec. " + String(decimalValue); 135 | consoleOut(term); 136 | consoleOut("Inv_Prop[welke].maxPower = " + String(Inv_Prop[welke].maxPower)); 137 | if(abs(decimalValue - Inv_Prop[welke].maxPower) > 2) return 15; 138 | } 139 | 140 | return 0; 141 | } -------------------------------------------------------------------------------- /eventSource.h: -------------------------------------------------------------------------------- 1 | 2 | const char ECU_EVENTS[] PROGMEM = R"=====( 3 | 4 | ESP Web Server 5 | 6 | 7 | 42 | 43 | 44 |
    45 |

    BME280 WEB SERVER (SSE)

    46 |
    47 |
    48 |
    49 |
    50 |

    p0

    51 |

    W

    52 |
    53 |
    54 |

    p1

    55 |

    W

    56 |
    57 |
    58 |

    p2

    59 |

    W

    60 |
    61 |
    62 |
    63 | 100 | 101 | 102 | )====="; 103 | -------------------------------------------------------------------------------- /handeforms.ino: -------------------------------------------------------------------------------- 1 | void handleForms(AsyncWebServerRequest *request) 2 | { 3 | //every form submission is handled here 4 | // we find out which form with a parameter present 5 | String serverUrl = request->url().c_str(); 6 | Serial.println("serverUrl = " + serverUrl); // this is /submitform 7 | 8 | if(request->hasParam("ecuid")) { 9 | // received form basisconfig 10 | strcpy(ECU_ID, request->arg("ecuid").c_str()); 11 | strcpy(userPwd, request->arg("pw1").c_str()); 12 | pollOffset = request->arg("offs").toInt(); 13 | 14 | //BEWARE CHECKBOX 15 | if(request->hasParam("pL")) { Polling = true; } else { Polling = false;} 16 | basisConfigsave(); // alles opslaan 17 | return; 18 | } else 19 | 20 | if(request->hasParam("longi")) { 21 | // received the geoconfig form 22 | longi = request->getParam("longi")->value().toFloat(); 23 | lati = request->getParam("be")->value().toFloat(); 24 | strcpy(gmtOffset, request->getParam("tz")->value().c_str()); 25 | // a checkbox has only a parameter when checked 26 | if(request->hasParam("ts")) zomerTijd = true; else zomerTijd = false; 27 | wifiConfigsave(); 28 | actionFlag=25; // recalculate with these settings 29 | return; 30 | 31 | } else 32 | if(request->hasParam("mqtAdres")) { 33 | // form mosquitto received 34 | strcpy( Mqtt_Broker , request->getParam("mqtAdres") ->value().c_str() ); 35 | strcpy( Mqtt_Port , request->getParam("mqtPort") ->value().c_str() ); 36 | strcpy( Mqtt_outTopic, request->getParam("mqtoutTopic")->value().c_str() ); 37 | strcpy( Mqtt_Username, request->getParam("mqtUser") ->value().c_str() ); 38 | strcpy( Mqtt_Password, request->getParam("mqtPas") ->value().c_str() ); 39 | //strcpy( Mqtt_Clientid, request->getParam("mqtCi") ->value().c_str() ); 40 | Mqtt_stateIDX = request->arg("mqidx").toInt(); //values are 0 1 2 41 | Mqtt_Format = request->arg("fm").toInt(); //values are 0 1 2 3 4 5 42 | mqttConfigsave(); // 43 | actionFlag=24; // reconnect with these settings 44 | return; 45 | } else 46 | // the request is something like pMax=200 MPW=0 47 | if(request->hasParam("pMax")) // name of the hidden input 48 | { // because of the hidden input named maxPower we know we received the setPower form 49 | Serial.println("found pMax"); 50 | procesId = 3; 51 | // check that the form has the right params ( should be pMax and MPW) 52 | int params = request->params(); 53 | Serial.print("Number of params: "); 54 | Serial.println(params); 55 | for (int i = 0; i < params; i++) { 56 | AsyncWebParameter* p = request->getParam(i); 57 | Serial.print("Param name: "); 58 | Serial.println(p->name()); 59 | Serial.print("Param value: "); 60 | Serial.println(p->value()); 61 | } 62 | 63 | int Inv = request->arg("INV").toInt(); 64 | Serial.println("the form is for inverter " + String(Inv)); 65 | desiredThrottle[Inv] = request->getParam("pMax")->value().toInt(); 66 | // write the desired throttlevalue in preferences 67 | //String key = "maxPwr" + String(welke); 68 | //preferences.putUInt(key.c_str(), maxPw); 69 | //desiredThrottle = 70 | Serial.println("throttle set to = " + String(desiredThrottle[Inv])); 71 | actionFlag=240 + Inv; // save the settings and send zigbee to inverter 72 | Serial.println("actionFlag set to " + String(actionFlag)); 73 | //Serial.println("setting the return url to /details?inv="); 74 | String toReturn = "/details?inv=" + String(Inv); 75 | strcpy(requestUrl, toReturn.c_str() ); 76 | Serial.println("requestUrl = " + String(requestUrl)); 77 | return; 78 | 79 | } 80 | 81 | // if we are here something was wrong, no parameters found 82 | request->send(200, "text/html", "no valid form found"); 83 | } 84 | -------------------------------------------------------------------------------- /handledata.ino: -------------------------------------------------------------------------------- 1 | void handleDataRequests(AsyncWebServerRequest *request) 2 | 3 | { 4 | //consoleOut"handleDataRequest"); 5 | //Serial.println("handleDataRequest the request is " + String(requestUrl)); 6 | if( request->hasArg("Power") ) 7 | { 8 | // consoleOut("found POWER"); 9 | int i = atoi(request->arg("inv").c_str()) ; 10 | AsyncResponseStream *response = request->beginResponseStream("application/json"); 11 | JsonDocument root; 12 | if(Inv_Data[i].en_total > 0) { // only possible when was polled this day 13 | root["eN"] = round2(Inv_Data[i].en_total/(float)1000); // rounded 14 | } else { 15 | root["eN"] = "n/a" ; 16 | } 17 | //now populate the powervalues in an array "p":[p0, p1, p2, p3] 18 | for(int z = 0; z < 4; z++ ) 19 | { 20 | //is the panel connected? if not put n/e 21 | if( ! Inv_Prop[i].conPanels[z] ) { root["p"][z] = "n/e"; } 22 | // so the panel is connected, is the inverter polled? 23 | else if (polled[i]) 24 | { 25 | //polled, we put a value 26 | root["p"][z] = round1(Inv_Data[i].power[z]) ; 27 | } else { 28 | // not polled, we put n/a 29 | root["p"][z] = "n/a"; 30 | } 31 | } 32 | 33 | serializeJson(root, * response); 34 | request->send(response); 35 | return; 36 | } else 37 | 38 | if( request->hasArg("General") ) 39 | { 40 | //consoleOut("found arg General"); 41 | char temp[15]={0}; 42 | uint8_t remote = 0; 43 | if(checkRemote( request->client()->remoteIP().toString()) ) remote = 1; // for the menu link 44 | uint8_t night = 0; 45 | if(!dayTime) { night = 1; } 46 | 47 | AsyncResponseStream *response = request->beginResponseStream("application/json"); 48 | JsonDocument root; //(160); 49 | //JsonObject root = doc.to(); 50 | root["cnt"] = inverterCount; 51 | root["rm"] = remote; 52 | root["st"] = zigbeeUp; 53 | root["sl"] = night; 54 | serializeJson(root, * response); 55 | request->send(response); 56 | return; 57 | } else 58 | // the get.Data request, send by detailspage 59 | if(request->hasArg("Inverter")) // this is the get.Data 60 | { 61 | //consoleOut("found arg Inverter"); 62 | // this is used by the detailspage and for http requests 63 | // set the array into a json object 64 | AsyncResponseStream *response = request->beginResponseStream("application/json"); 65 | JsonDocument doc; // size was 768 66 | JsonObject root = doc.to(); 67 | int i; 68 | i = (request->arg("Inverter").toInt()) | iKeuze; 69 | consoleOut("inverter = " + String(i)); 70 | if( i < inverterCount) { // check that this is a valid value 71 | root["inv"] = i; 72 | root["name"] = Inv_Prop[i].invLocation; 73 | root["polled"] = polled[i]; 74 | root["type"] = Inv_Prop[i].invType; 75 | root["serial"] = Inv_Prop[i].invSerial; 76 | root["sid"] = Inv_Prop[i].invID; 77 | root["freq"] = round1(Inv_Data[i].freq); 78 | root["temp"] = round1(Inv_Data[i].heath); 79 | root["acv"] = round1(Inv_Data[i].acv); 80 | root["sq"] = round1(Inv_Data[i].sigQ); 81 | root["pw_total"] = round1(Inv_Data[i].pw_total); 82 | root["en_total"] = round2(Inv_Data[i].en_total/(float)1000); // rounded 83 | String key = "maxPwr" + String(i); 84 | root["pwMax"] = preferences.getInt(key.c_str(), 0); 85 | 86 | //root["pwMax"] = Inv_Prop[i].maxPower; 87 | //if(Inv_Prop[i].throttled == true) root["throttled"] = 1; else root["throttled"] = 0; 88 | 89 | for(int z = 0; z < 4; z++ ) 90 | { 91 | if(Inv_Prop[i].conPanels[z]) // is this panel connected? 92 | { 93 | root["dcv"][z] = round1(Inv_Data[i].dcv[z]); 94 | root["dcc"][z] = round1(Inv_Data[i].dcc[z]); 95 | root["pow"][z] = round1(Inv_Data[i].power[z]); 96 | root["en"][z] = round2(en_saved[i][z]); //rounded 97 | } else { 98 | root["dcv"][z] = "n/e"; 99 | root["dcc"][z] = "n/e"; 100 | root["pow"][z] = "n/e"; 101 | root["en"][z] = "n/e"; 102 | } 103 | } 104 | serializeJson(root, * response); 105 | request->send(response); 106 | } else { 107 | String term = "unknown inverter " + String(i); 108 | request->send(200, "text/plain", term); 109 | } 110 | return; 111 | } 112 | // if we are here no maching request was found 113 | String term = "invalid request"; 114 | request->send(200, "text/plain", term); 115 | } 116 | -------------------------------------------------------------------------------- /legende.ino: -------------------------------------------------------------------------------- 1 | // nodemcu pins 2 | // 16 D0 3 | // 5 D1 4 | // 4 D2 5 | // 0 D3 6 | // 2 D4 7 | // GND 8 | // 3,3v 9 | // 14 D5 10 | // 12 D6 11 | // 13 D7 -> P02 12 | // 15 D8 -> P03 13 | // 3 D9 14 | // 1 D10 15 | 16 | // Serial of the APS inverter 17 | // serial of my inverter 408000158215 18 | // made the homepage to include the scripts so it uses less memory 19 | // the inverterpage is served form progmem with processor for the form in 3 parts 20 | // otherwise it is too large for the server. 21 | // started making some global string vars in to chars freq heath acv dcc dcv power 22 | // added an inverterquery get.Inverter?inv=x 23 | // changed the reading procedure, 24 | // added mqtt check to healthcheck and removed it from loop 25 | // added a hardware reset command to use via serial 26 | // in the healthcheck, if the zigbee not responsive it will be reset. 27 | // added the console so that there are no swaps needed anymore. 28 | 29 | 30 | /* 31 | from the readme from kadszol 32 | Configuration ID: 0x0003; Size: 1 byte; Default value: 0 33 | 34 | 35: Sent=FE03 26 05 03 01 03 21 ZB_WRITE_CONFIGURATION ZCD_NV_STARTUP_OPTION value=03 35 | 35: Received=FE01 66 05 00 62 SRSP 36 | 37 | 40: Sent=FE01 410000 40 RESET 38 | 40: Received=FE064180020202020702C2 39 | 40 | 45: Sent=FE0A 2605 01 08 FFFF80971B01A3D8 56 ZB_WRITE_CONFIGURATION 41 | 45: Received=FE01 66 05 00 62 SRSP status = 00 success 42 | 43 | 50: Sent=FE03 2605 87 01 00 A6 ZB_WRITE_CONFIGURATION ZCD_NV_LOGICAL_TYPE 00=coordinator 44 | 50: Received=FE01 66 05 00 62 SRSP status = 00 45 | 46 | 55: Sent=FE04 2605 83 02 D8 A3 DD ZB_WRITE_CONFIGURATION ZCD_NV_PANID 0x0083 id 0x02 size D8A3 identifies the network 47 | 55: Received=FE 01 66 05 00 62 SRSP status = 00 48 | 49 | 60: Sent=FE06 2605 84 04 00000100 A4 ZB_WRITE_CONFIGURATION ZCD_NV_CHANLIST on which channel it operates 50 | 60: Received=FE 01 66 05 00 62 SRSP status = 00 51 | 52 | 65: Sent=FE0D 240014050F0001010002000015000020 53 | 65: Received=FE 01 64 00 00 65 54 | 55 | The latest idea is to send messages to the webpage that can cause it to refresh or laod data 56 | this works, so when do we reload? 57 | if something changes in the zigbee health. This happens after a boot so after the initial healthcheck and 58 | when there was an attempt to (re)start the coordinator to reflect the status in the frontpage 59 | */ 60 | 61 | // compiled with board version 1.0.6 62 | -------------------------------------------------------------------------------- /nmap_result.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patience4711/ESP32-read-APS-inverters/77020fe28af890070bfc76cb23d4c04ad3dd0cf8/nmap_result.zip -------------------------------------------------------------------------------- /result.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
    11 |
    12 | 13 | 14 | 15 | 16 |
    17 |
    18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /sniffs_100to500.txt: -------------------------------------------------------------------------------- 1 | Snffs are made by sending a set maxpower command from ecu-c eth0 mac 80:97:1B:0B:BD:E8 to inverter 408000158215 2 | 3 | These are sniffs made with rf packet sniffer, converted from psd to ncap, only those with length 59 from 0x9c9c to 0x16cf 4 | snif 1 100w 5 | 61884d 5a2b 9c9ccf1648029c9cc74d1d3728408e3d06c82c3c0b01881700005b22b5b21d81c8009cd3ada497dd28215e7146f853a08b782c7cefb4 6 | snif 2 7 | 61884e5a2b 9c9ccf1648029c9ccf161ef728418e3d06c82c3c0b018817000026e5c01435b0cf6af15481c94696fb04cd13842f73c05d2066fb9601 8 | snif 3 9 | 6188525a2b 9c9ccf1648029c9ccf161efb28458e3d06c82c3c0b0188170000b4da548638754eea7b2174bf895fe37e7103e8b49e6f8fa544b724e6 10 | snif 4 11 | 6188585a2b 9c9ccf1648029c9cc74d1d43284b8e3d06c82c3c0b01881700005729287f84831f8767fa4d4bb1eacb0d645748f05ea92ae3efa4c835 12 | snif 5 13 | 6188595a2b 9c9ccf1648029c9ccf161e02284c8e3d06c82c3c0b0188170000dae519a79a2174f7b93174a37da63718da901f1ba5ada0ade1930680 14 | sniff 6 15 | 6188655a2b 9c9ccf1648029c9ccf161e0e28588e3d06c82c3c0b0188170000438f011f5e9f4136e75f60d8d68cdea822db78e4fc7356ffccd98d26 16 | sniff 7 17 | 6188705a2b9c9ccf1648029c9ccf161e1728638e3d06c82c3c0b0188170000159fff3bfb6c805836e25cc29a90c241d5267f6c97ba078ebb30b225 18 | 19 | ecu-3 eth0 mac 80:97:1B:0B:BD:E8 20 | Zboss sniff 100w 6188c9 e8bd3a10000048003a1000000f 1e 00140600050f14 01 80971b0bbde8 fbfb061c8c02 0b48000103 fefe 21 | zboss sniff 200w 6188dd e8bd3a10000048003a1000000f 30 00140600050f14 01 80971b0bbde8 fbfb061c8c02 1691000157 fefe 22 | zboss sniff 300w 618895 e8bd3a10000048003a1000000f 49 00140600050f14 03 80971b0bbde8 fbfb061c8c02 21da0001ab fefe 23 | zboss sniff 400w 61889e e8bd3a10000048003a1000000f 52 00140600050f14 05 80971b0bbde8 fbfb061c8c02 2d220000ff fefe 24 | zboss sniff 500w 618813 e8bd3a10000048003a1000000f 5b 00140600050f1401 80971b0bbde8 fbfb061c8c02 386b000153 fefe 25 | 26 | at every set maxpower there are 3 frames with equal length 27 | sniff maxpower 100 3 messages 28 | 618821e8bd3a10000048003a1000000f0300140600050f140180971b0bbde8 fbfb061c8c020b48000103fefe 29 | 61882ae8bd00003a10480000003a100f0200140601050f1485408000158215fbfb06de00000000000000fefe 30 | 618822e8bd3a10000048003a1000000f0400140600050f140280971b0bbde8fbfb06de000000000000e4fefe 31 | 32 | sniff maxpower 200 3messages 33 | 6188f4e8bd3a10000048003a1000000f1800140600050f140380971b0bbde8fbfb061c8c021691000157fefe 34 | 618842e8bd00003a10480000003a100f1300140601050f148a408000158215fbfb06de00000000000000fefe 35 | 6188f5e8bd3a10000048003a1000000f1900140600050f140480971b0bbde8fbfb06de000000000000e4fefe 36 | 37 | sniff maxpower 300 3 messages 38 | 6188fce8bd3a10000048003a1000000f2000140600050f140580971b0bbde8fbfb061c8c0221da0001abfefe 39 | 61884be8bd00003a10480000003a100f1a00140601050f148c408000158215fbfb06de00000000000000fefe 40 | 6188fde8bd3a10000048003a1000000f2100140600050f140680971b0bbde8fbfb06de000000000000e4fefe 41 | 42 | sniff maxpower 400 3 messages 43 | 6188ebe8bd3a10000048003a1000000f2d00140600050f140180971b0bbde8fbfb061c8c022d220000fffefe 44 | 61885be8bd00003a10480000003a100f2500140601050f148f408000158215fbfb06de00000000000000fefe 45 | 6188ece8bd3a10000048003a1000000f2e00140600050f140280971b0bbde8fbfb06de000000000000e4fefe 46 | 47 | sniff maxpower 500 3 messages 48 | 618813e8bd3a10000048003a1000000f5b00140600050f140180971b0bbde8 fbfb061c8c02 386b000153 fefe 49 | 618872e8bd00003a10480000003a100f8a00140601050f147a408000158215fbfb06de00000000000000fefe 50 | 618814e8bd3a10000048003a1000000f5c00140600050f140280971b0bbde8fbfb06de000000000000e4fefe 51 | 52 | 53 | only the payload 54 | Zboss sniff 100w fbfb 061c8c02 0b 48 00 01 03 fefe 0b84 2948 / 28.89 = 102 55 | zboss sniff 200w fbfb 061c8c02 16 91 00 01 57 fefe 1691 5777 / 28.89 = 200 (199,9) 56 | zboss sniff 300w fbfb 061c8c02 21 da 00 01 ab fefe 21da 8666 / 28.89 = 300 57 | zboss sniff 400w fbfb 061c8c02 2d 22 00 00 ff fefe 2d22 11554 / 28.89 = 400 58 | zboss sniff 500w fbfb 061c8c02 38 6b 00 01 53 fefe 386b 14443 / 28.89 = 500 59 | check: 60 | zboss sniff 280w fbfb 061c8c02 1f 98 00 01 67 fefe 1f98=8088 / 28.89 = 279.9 = 280 61 | zboss sniff 180w fbfb 061c8c02 1447=5199 /28,89 = 179.9 = 180 62 | 63 | 64 | inverter responses 65 | maxpower 100: 66 | 61889ae8bd00003a10480000003a100f4200140601050f1442408000158215fbfb4dde041105440fe5020f32b003cf05440fe5020f32b0066604cc0ea3a804d70214050c100fd80ed07a0f32b0056a054f019000641f3fe480068ace8ace000130103030190604001d0b483b6600000000fefe 67 | maxpower 200: 68 | 6188a3e8bd00003a10480000003a100f4900140601050f1444408000158215fbfb4dde041105440fe5020f32b003cf05440fe5020f32b0066604cc0ea3a804d70214050c100fd80ed07a0f32b0056a054f019000641f3fe480068ace8ace000130103030190604001d16913b6600000000fefe 69 | maxpower 300: 70 | 6188b5e8bd00003a10480000003a100f5500140601050f1447408000158215fbfb4dde041105440fe5020f32b003cf05440fe5020f32b0066604cc0ea3a804d70214050c100fd80ed07a0f32b0056a054f019000641f3fe480068ace8ace000130103030190604001d21da3b6600000000fefe 71 | maxpower 400: 72 | 6188bee8bd00003a10480000003a100f5c00140601050f1449408000158215fbfb4dde041105440fe5020f32b003cf05440fe5020f32b0066604cc0ea3a804d70214050c100fd80ed07a0f32b0056a054f019000641f3fe480068ace8ace000130103030190604001d2d223b6600000000fefe 73 | maxpower 500: 74 | 6188cde8bd00003a10480000003a100f6800140601050f144e408000158215fbfb4dde041105440fe5020f32b003cf05440fe5020f32b0066604cc0ea3a804d70214050c100fd80ed07a0f32b0056a054f019000641f3fe480068ace8ace000130103030190604001d386b3b6600000000fefe -------------------------------------------------------------------------------- /test.ino: -------------------------------------------------------------------------------- 1 | // we can send a zigbee message via the API 2 | void testMessage() 3 | { 4 | char sendCmd[100]={0}; 5 | 6 | // if(console) diagNose = 1; else diagNose = 2; 7 | int len; 8 | len = strlen( txBuffer ); //else len = strlen( InputBuffer_Serial ); 9 | 10 | ////put all the bytes of inputBuffer_Serial ( or txBuffer) in sendCmd, starting at pos 7 11 | //for(int i=0; i 9 ) { 129 | memset(&messageToDecode, 0, sizeof(messageToDecode)); //zero out 130 | delayMicroseconds(250); 131 | return fault; 132 | } 133 | 134 | //consoleOut("received response " + String(messageToDecode) ); 135 | 136 | // if we are here we have a message containing 44810000 137 | // shorten the message by removing everything before 4481 138 | if(throttle) return 0; 139 | 140 | tail = split(messageToDecode, "44810000"); // remove the 0000 as well 141 | //tail = after removing the 1st part 142 | // in tail at offset 14, 2 bytes with signalQuality reside 143 | consoleOut("tail " + String(tail) ); 144 | 145 | return 0; 146 | } 147 | 148 | 149 | 150 | #ifdef TEST 151 | void testDecode() { 152 | // we feed the decoding of a polling answer with an artificial polling answer 153 | // we always test inverter 0, depending on the type we test the right string 154 | // So if we want to test a ds3 we have to define inverter0 as ds3 155 | int type = Inv_Prop[0].invType; // 156 | // we define an inmessage first 157 | 158 | switch(type) { 159 | case 0: // yc600 160 | strncpy(inMessage, "FE0164010064FE034480001401D2FE0345C43A1000A8FE724481000006013A101414007100B57CFA00005E408000158215FBFB51B103D40F4117000074CF00000076706A73D06B0496000000000000000172072D88017862E8201F00030555073F0303030100000100000000000000000000000000000000000000000000000000000000000000FEFE3A100E76",300); 161 | break; 162 | case 1: // test qs1 johan 163 | strncpy(inMessage, "FE0164010064FE034480001401D2FE72448100000601C0051414005E00905D5B00005E801000085070FBFB51B103EB0F419300CAF069D9F068C7C068C1206804B868E0000006A80001BB38134D01CCE90E0A01FD1E052201D967D0641F0003055400000000000000000000000000000000000000000000000000002B2A0000FEFEC0050E55",300); 164 | break; 165 | if(testCounter == 0) { 166 | // from npeters tracefile at 9:53 and 9:57 167 | strncpy(inMessage, "FE0164010064FE034480001401D2FE0345C4226C00CCFE0345C4226C00CCFE0345C43A1000A8FE724481000006013A101414007100B57CFA00005E703000021300fbfb5cbbbb2000fc0001ffff000000000000000006e506ee00E200EA036e13882D5F01480026ffff052508430049F8C40048C77C00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3896fefe",300); 168 | // > 26 tttt 100 108 169 | } else { //we increased energy values to be able to calc the power 170 | strncpy(inMessage, "FE0164010064FE034480001401D2FE0345C4226C00CCFE0345C4226C00CCFE0345C43A1000A8FE724481000006013A101414007100B57CFA00005E703000021300fbfb5cbbbb2000fc0001ffff000000000000000006e506ee015900EF010313882E8B01480026ffff052508430050BF39004F849C00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3896fefe",300); 171 | // > 26 tttt 100 108 172 | } 173 | break; 174 | } 175 | //703000021300 FB FB 5C BB BB 20 00 FC 00 01 FF FF 00 00 00 00 00 00 00 00 06 83 06 8A 02 31 02 3A 03 68 13 87 2C 9B 01 176 | //703000021300 fb fb 5c bb bb 20 00 02 00 e6 ff ff 00 00 00 00 00 00 00 00 06 f5 06 f9 00 2e 00 34 03 60 13 8a | 17 a7 |00 177 | //0 6 7 8 9 10 11 12 13 14 15 21 22 30 | time |40 50 53 54 55 178 | 179 | //BB 00 2A FF FF 05 6B 08 D8 01 1C 4E 79 01 1D BD 26 00 FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF3830FEFE226C70E 180 | //24 00 1f ff ff 05 42 06 90 00 16 f6 2b 00 18 e4 51 ff ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff3969fefe 181 | //41 45 50 55 182 | 183 | //DebugPrintln("going to test for invType: " + String(type) + "\n"); 184 | decodePollAnswer(0); 185 | 186 | polled[0]=true; 187 | testCounter += 1; // for the next round we have new string 188 | mqttPoll(0); 189 | } 190 | #endif 191 | --------------------------------------------------------------------------------