diff --git a/Arduino-ESP/Pylon-MQTTeZ-Broker/Pylon-MQTTeZ-Broker.ino b/Arduino-ESP/Pylon-MQTTeZ-Broker/Pylon-MQTTeZ-Broker.ino new file mode 100644 index 0000000..99e6620 --- /dev/null +++ b/Arduino-ESP/Pylon-MQTTeZ-Broker/Pylon-MQTTeZ-Broker.ino @@ -0,0 +1,134 @@ +// MQTT server + client for ESP32 +#include"sMQTTBroker.h" +#include +#include +#include +#include + +const char* ssid = "NOS-3B26"; // Setubal +const char* password = "RMKSX2GL"; // Setubal +//const char* ssid = "MEO-AA9030"; // Andre +//const char* password = "81070ce635"; // Andre + +// Set your Static IP address +IPAddress local_IP(192, 168, 1, 123); +// Set your Gateway IP address +IPAddress gateway(192, 168, 1, 1); + +IPAddress subnet(255, 255, 0, 0); +IPAddress primaryDNS(8, 8, 8, 8); // optional +IPAddress secondaryDNS(8, 8, 4, 4); // optional + +sMQTTBroker broker; + +WiFiClient net; +//WiFiClientSecure net; +MQTTClient mqtt_client(1024); + +//WiFiClient espClient; +//PubSubClient client(espClient); +//long lastMsg = 0; +//char msg[50]; +//int value = 0; + +unsigned long lastMillis = 0; + +////////////////////////////////// +void WiFiconnect(){ + int nn = 0; + while (WiFi.status() != WL_CONNECTED) { // Wait for the Wi-Fi to connect + delay(500); + if(nn<9){ + Serial.print("."); + nn++; + }else{ + nn=0; + Serial.println("."); + } + } + + if (!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) { + Serial.println("STA Failed to configure"); + } + + Serial.println("Connection established!"); + Serial.print("IP address:\t"); + Serial.println(WiFi.localIP()); + + + +// Serial.print("\nconnecting..."); +//// while (!mqtt_client.connect("bsidebotham", "", "")) { +// while (!mqtt_client.connect("tezmqlientx2323", "", "")) { +// Serial.print("."); +// delay(1000); +// } +// +// Serial.println("\nconnected!"); +// +// mqtt_client.subscribe("/hello"); +// +// // MQTT brokers usually use port 8883 for secure connections. +//// client.begin("broker.shiftr.io", 8883, net); +// mqtt_client.begin("192.168.1.123", 1883, net); +// mqtt_client.onMessage(messageReceived); + +} + +////////////////////////////// +void messageReceived(String &topic, String &payload) { + Serial.println("incoming: " + topic + " - " + payload); + + // Note: Do not use the client in the callback to publish, subscribe or + // unsubscribe as it may cause deadlocks when other things arrive while + // sending and receiving acknowledgments. Instead, change a global variable, + // or push to a queue and handle it in the loop after calling `client.loop()`. +} + +////////////////////////////// +void setup() +{ + Serial.begin(115200); +// const char* ssid = "MEO-AA9030"; // The SSID (name) of the Wi-Fi network you want to connect to +// const char* password = "81070ce635"; // The password of the Wi-Fi network + + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, password); + + const unsigned short mqttPort=1883; + broker.init(mqttPort); + // all done + + + WiFiconnect(); + + + + + + +} + +////////////////////////////// +void loop() +{ + broker.update(); + +// mqtt_client.loop(); + + delay(10); // <- fixes some issues with WiFi stability + + // publish a message roughly every second. +// if (millis() - lastMillis > 1000) { +// if (!mqtt_client.connected()) { +// Serial.print("lastError: "); +// Serial.println(mqtt_client.lastError()); +// WiFiconnect(); +// } +// lastMillis = millis(); +// int rnum = random(1,23); +// mqtt_client.publish("/hello", "ciccio" + String(rnum)); +// } +// +} diff --git a/Arduino-ESP/PylontechMonitoring/PylontechMonitoring.ino b/Arduino-ESP/PylontechMonitoring/PylontechMonitoring.ino new file mode 100644 index 0000000..6934dcf --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/PylontechMonitoring.ino @@ -0,0 +1,818 @@ +#include +#include +#include +#include +#include +#include //https://github.com/PaulStoffregen/Time +#include +#include + + +//IMPORTANT: Specify your WIFI settings: +//#define WIFI_SSID "NOS-3B26" +//#define WIFI_PASS "RMKSX2GL" +#define WIFI_SSID "MEO-AA9030" +#define WIFI_PASS "81070ce635" + + +//IMPORTANT: Uncomment this line if you want to enable MQTT (and fill correct MQTT_ values below): +//#define ENABLE_MQTT + +#ifdef ENABLE_MQTT +//NOTE 1: if you want to change what is pushed via MQTT - edit function: pushBatteryDataToMqtt. +//NOTE 2: MQTT_TOPIC_ROOT is where battery will push MQTT topics. For example "soc" will be pushed to: "home/grid_battery/soc" +#define MQTT_SERVER "192.168.1.123" +#define MQTT_PORT 1883 +#define MQTT_USER "" +#define MQTT_PASSWORD "" +#define MQTT_TOPIC_ROOT "home/grid_battery/" //this is where mqtt data will be pushed +#define MQTT_PUSH_FREQ_SEC 2 //maximum mqtt update frequency in seconds + +#include +WiFiClient espClient; +PubSubClient mqttClient(espClient); +#endif //ENABLE_MQTT + +char g_szRecvBuff[7000]; + +IPAddress thisip; + +ESP8266WebServer server(80); +SimpleTimer timer; +circular_log<7000> g_log; +bool ntpTimeReceived = false; +int g_baudRate = 0; + +void Log(const char* msg) +{ + g_log.Log(msg); +} + + +///////////////////////////////// +void goWiFi(){ + // connect to WiFi + WiFi.mode(WIFI_STA); + WiFi.persistent(false); //our credentials are hardcoded, so we don't need ESP saving those each boot (will save on flash wear) + WiFi.hostname("PylonBattery"); + Serial.println(); + Serial.print("connecting to "); + Serial.println(WIFI_SSID); + + WiFi.begin(WIFI_SSID, WIFI_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println(""); + for(int i=1; i<=3; i++){ + LedBlink(); + } + +} + + +////////////////////////////////////////////////// +void LedBlink(){ + digitalWrite(LED_BUILTIN, LOW); + delay(150); + digitalWrite(LED_BUILTIN, HIGH); // high = off + delay(150); +} + + + +////////////////////// +void setup() { + + Serial.begin(115200); + + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH);//high is off + + // connect to WiFi + goWiFi(); + +// // original wifi code +// WiFi.mode(WIFI_STA); +// WiFi.persistent(false); //our credentialss are hardcoded, so we don't need ESP saving those each boot (will save on flash wear) +// WiFi.hostname("PylonBattery"); +// WiFi.begin(WIFI_SSID, WIFI_PASS); +// +// for(int ix=0; ix<10; ix++) +// { +// if(WiFi.status() == WL_CONNECTED) +// { +// break; +// } +// +// delay(1000); +// } + +// Serial.println(""); +// Serial.println("WiFi connected"); +// Serial.println("IP address: "); +// thisip = WiFi.localIP(); +// Serial.println( thisip ); + + + ArduinoOTA.setHostname("AndrePylon"); + ArduinoOTA.begin(); + server.on("/", handleRoot); + server.on("/log", handleLog); + server.on("/req", handleReq); + server.on("/jsonOut", handleJsonOut); + server.on("/reboot", [](){ + ESP.restart(); + }); + + server.begin(); + + syncTime(); + +#ifdef ENABLE_MQTT + mqttClient.setServer(MQTT_SERVER, MQTT_PORT); +#endif + + Log("Boot event"); +} + +void handleLog() +{ + server.send(200, "text/html", g_log.c_str()); +} + +void switchBaud(int newRate) +{ + if(g_baudRate == newRate) + { + return; + } + + if(g_baudRate != 0) + { + Serial.flush(); + delay(20); + Serial.end(); + delay(20); + } + + char szMsg[50]; + snprintf(szMsg, sizeof(szMsg)-1, "New baud: %d", newRate); + Log(szMsg); + + Serial.begin(newRate); + g_baudRate = newRate; + + delay(20); +} + +void waitForSerial() +{ + for(int ix=0; ix<150;ix++) + { + if(Serial.available()) break; + delay(10); + } +} + +int readFromSerial() +{ + memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff)); + int recvBuffLen = 0; + bool foundTerminator = true; + + waitForSerial(); + + while(Serial.available()) + { + char szResponse[256] = ""; + const int readNow = Serial.readBytesUntil('>', szResponse, sizeof(szResponse)-1); //all commands terminate with "$$\r\n\rpylon>" (no new line at the end) + if(readNow > 0 && + szResponse[0] != '\0') + { + if(readNow + recvBuffLen + 1 >= (int)(sizeof(g_szRecvBuff))) + { + Log("WARNING: Read too much data on the console!"); + break; + } + + strcat(g_szRecvBuff, szResponse); + recvBuffLen += readNow; + + if(strstr(g_szRecvBuff, "$$\r\n\rpylon")) + { + strcat(g_szRecvBuff, ">"); //readBytesUntil will skip this, so re-add + foundTerminator = true; + break; //found end of the string + } + + if(strstr(g_szRecvBuff, "Press [Enter] to be continued,other key to exit")) + { + //we need to send new line character so battery continues the output + Serial.write("\r"); + } + + waitForSerial(); + } + } + + if(recvBuffLen > 0 ) + { + if(foundTerminator == false) + { + Log("Failed to find pylon> terminator"); + } + } + + return recvBuffLen; +} + +bool readFromSerialAndSendResponse() +{ + const int recvBuffLen = readFromSerial(); + if(recvBuffLen > 0) + { + server.sendContent(g_szRecvBuff); + return true; + } + + return false; +} + +bool sendCommandAndReadSerialResponse(const char* pszCommand) +{ + switchBaud(115200); + + if(pszCommand[0] != '\0') + { + Serial.write(pszCommand); + } + Serial.write("\n"); + + const int recvBuffLen = readFromSerial(); + if(recvBuffLen > 0) + { + return true; + } + + //wake up console and try again: + wakeUpConsole(); + + if(pszCommand[0] != '\0') + { + Serial.write(pszCommand); + } + Serial.write("\n"); + + return readFromSerial() > 0; +} + +void handleReq() +{ + bool respOK; + if(server.hasArg("code") == false) + { + respOK = sendCommandAndReadSerialResponse(""); + } + else + { + respOK = sendCommandAndReadSerialResponse(server.arg("code").c_str()); + } + + if(respOK) + { + server.send(200, "text/plain", g_szRecvBuff); + } + else + { + server.send(500, "text/plain", "????"); + } +} + +void handleJsonOut() +{ + if(sendCommandAndReadSerialResponse("pwr") == false) + { + server.send(500, "text/plain", "Failed to get response to 'pwr' command"); + return; + } + + parsePwrResponse(g_szRecvBuff); + prepareJsonOutput(g_szRecvBuff, sizeof(g_szRecvBuff)); + server.send(200, "application/json", g_szRecvBuff); +} + +void handleRoot() { + unsigned long days = 0, hours = 0, minutes = 0; + unsigned long val = os_getCurrentTimeSec(); + + days = val / (3600*24); + val -= days * (3600*24); + + hours = val / 3600; + val -= hours * 3600; + + minutes = val / 60; + val -= minutes*60; + + static char szTmp[2500] = ""; + snprintf(szTmp, sizeof(szTmp)-1, "Garage Battery
Time GMT: %d/%02d/%02d %02d:%02d:%02d (%s)
Uptime: %02d:%02d:%02d.%02d

free heap: %u
Wifi RSSI: %d
Wifi SSID: %s", + year(), month(), day(), hour(), minute(), second(), "GMT", + (int)days, (int)hours, (int)minutes, (int)val, + ESP.getFreeHeap(), WiFi.RSSI(), WiFi.SSID().c_str()); + + + strncat(szTmp, "
Runtime log
", sizeof(szTmp)-1); + strncat(szTmp, "
Command:
Power | Help | Event Log | Time", sizeof(szTmp)-1); + strncat(szTmp, "", sizeof(szTmp)-1); + + server.send(200, "text/html", szTmp); +} + +unsigned long os_getCurrentTimeSec() +{ + static unsigned int wrapCnt = 0; + static unsigned long lastVal = 0; + unsigned long currentVal = millis(); + + if(currentVal < lastVal) + { + wrapCnt++; + } + + lastVal = currentVal; + unsigned long seconds = currentVal/1000; + + //millis will wrap each 50 days, as we are interested only in seconds, let's keep the wrap counter + return (wrapCnt*4294967) + seconds; +} + +void syncTime() +{ + //get time from NTP + time_t currentTimeGMT = getNtpTime(); + if(currentTimeGMT) + { + ntpTimeReceived = true; + setTime(currentTimeGMT); + } + else + { + timer.setTimeout(5000, syncTime); //try again in 5 seconds + } +} + +void wakeUpConsole() +{ + switchBaud(1200); + + //byte wakeUpBuff[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D}; + //Serial.write(wakeUpBuff, sizeof(wakeUpBuff)); + Serial.write("~20014682C0048520FCC3\r"); + delay(1000); + + byte newLineBuff[] = {0x0E, 0x0A}; + switchBaud(115200); + + for(int ix=0; ix<10; ix++) + { + Serial.write(newLineBuff, sizeof(newLineBuff)); + delay(1000); + + if(Serial.available()) + { + while(Serial.available()) + { + Serial.read(); + } + + break; + } + } +} + +#define MAX_PYLON_BATTERIES 8 + +struct pylonBattery +{ + bool isPresent; + long soc; //Coulomb in % + long voltage; //in mW + long current; //in mA, negative value is discharge + long tempr; //temp of case or BMS? + long cellTempLow; + long cellTempHigh; + long cellVoltLow; + long cellVoltHigh; + char baseState[9]; //Charge | Dischg | Idle + char voltageState[9]; //Normal + char currentState[9]; //Normal + char tempState[9]; //Normal + char time[20]; //2019-06-08 04:00:29 + char b_v_st[9]; //Normal (battery voltage?) + char b_t_st[9]; //Normal (battery temperature?) + + bool isCharging() const { return strcmp(baseState, "Charge") == 0; } + bool isDischarging() const { return strcmp(baseState, "Dischg") == 0; } + bool isIdle() const { return strcmp(baseState, "Idle") == 0; } + bool isBalancing() const { return strcmp(baseState, "Balance") == 0; } + + + bool isNormal() const + { + if(isCharging() == false && + isDischarging() == false && + isIdle() == false && + isBalancing() == false) + { + return false; //base state looks wrong! + } + + return strcmp(voltageState, "Normal") == 0 && + strcmp(currentState, "Normal") == 0 && + strcmp(tempState, "Normal") == 0 && + strcmp(b_v_st, "Normal") == 0 && + strcmp(b_t_st, "Normal") == 0 ; + } +}; + +struct batteryStack +{ + int batteryCount; + int soc; //in %, if charging: average SOC, otherwise: lowest SOC + int temp; //in mC, if highest temp is > 15C, this will show the highest temp, otherwise the lowest + long currentDC; //mAh current going in or out of the battery + long avgVoltage; //in mV + char baseState[9]; //Charge | Dischg | Idle | Balance | Alarm! + + pylonBattery batts[MAX_PYLON_BATTERIES]; + + bool isNormal() const + { + for(int ix=0; ix 1000) + { + return (long)(powerDC*1.06); + } + else if(powerDC > 600) + { + return (long)(powerDC*1.1); + } + else + { + return (long)(powerDC*1.13); + } + } + } +}; + +batteryStack g_stack; + + +long extractInt(const char* pStr, int pos) +{ + return atol(pStr+pos); +} + +void extractStr(const char* pStr, int pos, char* strOut, int strOutSize) +{ + strOut[strOutSize-1] = '\0'; + strncpy(strOut, pStr+pos, strOutSize-1); + strOutSize--; + + + //trim right + while(strOutSize > 0) + { + if(isspace(strOut[strOutSize-1])) + { + strOut[strOutSize-1] = '\0'; + } + else + { + break; + } + + strOutSize--; + } +} + +/* Output has mixed \r and \r\n +pwr + +@ + +Power Volt Curr Tempr Tlow Thigh Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time B.V.St B.T.St + +1 49735 -1440 22000 19000 19000 3315 3317 Dischg Normal Normal Normal 93% 2019-06-08 04:00:30 Normal Normal + +.... + +8 - - - - - - - Absent - - - - - - - + +Command completed successfully + +$$ + +pylon +*/ +bool parsePwrResponse(const char* pStr) +{ + if(strstr(pStr, "Command completed successfully") == NULL) + { + return false; + } + + int chargeCnt = 0; + int dischargeCnt = 0; + int idleCnt = 0; + int alarmCnt = 0; + int socAvg = 0; + int socLow = 0; + int tempHigh = 0; + int tempLow = 0; + + memset(&g_stack, 0, sizeof(g_stack)); + + for(int ix=0; ix g_stack.batts[ix].soc){socLow = g_stack.batts[ix].soc;} + if(tempHigh < g_stack.batts[ix].cellTempHigh){tempHigh = g_stack.batts[ix].cellTempHigh;} + if(tempLow > g_stack.batts[ix].cellTempLow){tempLow = g_stack.batts[ix].cellTempLow;} + } + + } + } + + //now update stack state: + g_stack.avgVoltage /= g_stack.batteryCount; + g_stack.soc = socLow; + + if(tempHigh > 15000) //15C + { + g_stack.temp = tempHigh; //in the summer we highlight the warmest cell + } + else + { + g_stack.temp = tempLow; //in the winter we focus on coldest cell + } + + if(alarmCnt > 0) + { + strcpy(g_stack.baseState, "Alarm!"); + } + else if(chargeCnt == g_stack.batteryCount) + { + strcpy(g_stack.baseState, "Charge"); + g_stack.soc = (int)(socAvg / g_stack.batteryCount); + } + else if(dischargeCnt == g_stack.batteryCount) + { + strcpy(g_stack.baseState, "Dischg"); + } + else if(idleCnt == g_stack.batteryCount) + { + strcpy(g_stack.baseState, "Idle"); + } + else + { + strcpy(g_stack.baseState, "Balance"); + } + + + return true; +} + +void prepareJsonOutput(char* pBuff, int buffSize) +{ + memset(pBuff, 0, buffSize); + snprintf(pBuff, buffSize-1, "{\"soc\": %d, \"temp\": %d, \"currentDC\": %ld, \"avgVoltage\": %ld, \"baseState\": \"%s\", \"batteryCount\": %d, \"powerDC\": %ld, \"estPowerAC\": %ld, \"isNormal\": %s}", g_stack.soc, + g_stack.temp, + g_stack.currentDC, + g_stack.avgVoltage, + g_stack.baseState, + g_stack.batteryCount, + g_stack.getPowerDC(), + g_stack.getEstPowerAc(), + g_stack.isNormal() ? "true" : "false"); +} + +void loop() { +#ifdef ENABLE_MQTT + mqttLoop(); +#endif + + ArduinoOTA.handle(); + server.handleClient(); + timer.run(); + + //if there are bytes availbe on serial here - it's unexpected + //when we send a command to battery, we read whole response + //if we get anything here anyways - we will log it + int bytesAv = Serial.available(); + if(bytesAv > 0) + { + if(bytesAv > 63) + { + bytesAv = 63; + } + + char buff[64+4] = "RCV:"; + if(Serial.readBytes(buff+4, bytesAv) > 0) + { + digitalWrite(LED_BUILTIN, LOW); + delay(5); + digitalWrite(LED_BUILTIN, HIGH);//high is off + + Log(buff); + } + } +} + +#ifdef ENABLE_MQTT +#define ABS_DIFF(a, b) (a > b ? a-b : b-a) +void mqtt_publish_f(const char* topic, float newValue, float oldValue, float minDiff, bool force) +{ + char szTmp[16] = ""; + snprintf(szTmp, 15, "%.2f", newValue); + if(force || ABS_DIFF(newValue, oldValue) > minDiff) + { + mqttClient.publish(topic, szTmp, false); + } +} + +void mqtt_publish_i(const char* topic, int newValue, int oldValue, int minDiff, bool force) +{ + char szTmp[16] = ""; + snprintf(szTmp, 15, "%d", newValue); + if(force || ABS_DIFF(newValue, oldValue) > minDiff) + { + mqttClient.publish(topic, szTmp, false); + } +} + +void mqtt_publish_s(const char* topic, const char* newValue, const char* oldValue, bool force) +{ + if(force || strcmp(newValue, oldValue) != 0) + { + mqttClient.publish(topic, newValue, false); + } +} + +void pushBatteryDataToMqtt(const batteryStack& lastSentData, bool forceUpdate /* if true - we will send all data regardless if it's the same */) +{ + mqtt_publish_f(MQTT_TOPIC_ROOT "soc", g_stack.soc, lastSentData.soc, 0, forceUpdate); + mqtt_publish_f(MQTT_TOPIC_ROOT "temp", (float)g_stack.temp/1000.0, (float)lastSentData.temp/1000.0, 0, forceUpdate); + mqtt_publish_i(MQTT_TOPIC_ROOT "estPowerAC", g_stack.getEstPowerAc(), lastSentData.getEstPowerAc(), 10, forceUpdate); + mqtt_publish_i(MQTT_TOPIC_ROOT "battery_count",g_stack.batteryCount, lastSentData.batteryCount, 0, forceUpdate); + mqtt_publish_s(MQTT_TOPIC_ROOT "base_state", g_stack.baseState, lastSentData.baseState , forceUpdate); + mqtt_publish_i(MQTT_TOPIC_ROOT "is_normal", g_stack.isNormal() ? 1:0, lastSentData.isNormal() ? 1:0, 0, forceUpdate); +} + +void mqttLoop() +{ + //if we have problems with connecting to mqtt server, we will attempt to re-estabish connection each 1minute (not more than that) + static unsigned long g_lastConnectionAttempt = 0; + + //first: let's make sure we are connected to mqtt + const char* topicLastWill = MQTT_TOPIC_ROOT "availability"; + if (!mqttClient.connected() && (g_lastConnectionAttempt == 0 || os_getCurrentTimeSec() - g_lastConnectionAttempt > 60)) { + if(mqttClient.connect("GarageBattery", MQTT_USER, MQTT_PASSWORD, topicLastWill, 1, true, "offline")) + { + Log("Connected to MQTT server: " MQTT_SERVER); + mqttClient.publish(topicLastWill, "online", true); + } + else + { + Log("Failed to connect to MQTT server."); + } + + g_lastConnectionAttempt = os_getCurrentTimeSec(); + } + + //next: read data from battery and send via MQTT (but only once per MQTT_PUSH_FREQ_SEC seconds) + static unsigned long g_lastDataSent = 0; + if(mqttClient.connected() && + os_getCurrentTimeSec() - g_lastDataSent > MQTT_PUSH_FREQ_SEC && + sendCommandAndReadSerialResponse("pwr") == true) + { + static batteryStack lastSentData; //this is the last state we sent to MQTT, used to prevent sending the same data over and over again + static unsigned int callCnt = 0; + + parsePwrResponse(g_szRecvBuff); + + bool forceUpdate = (callCnt % 20 == 0); //push all the data every 20th call + pushBatteryDataToMqtt(lastSentData, forceUpdate); + + callCnt++; + g_lastDataSent = os_getCurrentTimeSec(); + memcpy(&lastSentData, &g_stack, sizeof(batteryStack)); + } + + mqttClient.loop(); +} + +#endif //ENABLE_MQTT diff --git a/Arduino-ESP/PylontechMonitoring/README.md b/Arduino-ESP/PylontechMonitoring/README.md new file mode 100755 index 0000000..2ed85d3 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/README.md @@ -0,0 +1,52 @@ +# Pylontech Battery Monitoring via WiFi + +This project allows you to control and monitor Pylontech US2000B and US2000C batteries via console port over WiFi. +It it's a great starting point to integrate battery with your home automation. + +**I ACCEPT NO RESPONSIBILTY FOR ANY DAMAGE CAUSED, PROCEED AT YOUR OWN RISK** + +# Features: + * Low cost (around 20$ in total). + * Adds WiFi capability to your Pylontech US2000B/C battery. + * Device exposes web interface that allows to: + * send console commands and read response over WiFi (no PC needed) + * battery information can be retrevied also in JSON format for easy parsing + * MQTT support: + * device pushes basic battery data like SOC, temperature, state, etc to selected MQTT server + * Easy to modify code using Arduino IDE and flash new firmware over WiFi (no need to disconnect from the battery). + +See the project in action on [Youtube](https://youtu.be/7VyQjKU3MsU):
+See the project in action on YouTube + + +# Parts needed and schematics: + * [Wemos D1 mini microcontroller](https://www.amazon.co.uk/Makerfire-NodeMcu-Development-ESP8266-Compatible/dp/B071S8MWTY/). + * [SparkFun MAX3232 Transceiver](https://www.sparkfun.com/products/11189). + * US2000B: Cable with RJ10 connector (some RJ10 cables have only two wires, make sure to buy one that has all four wires present). + * US2000C: Cable with RJ45 connector (see below for more details). + * Capacitors C1: 10uF, C2: 0.1uF (this is not strictly required, but recommended as Wemos D1 can have large current spikes). + +![Schematics](Schemetics.png) + +# US2000C notes: +This battery uses RJ45 cable instead of RJ10. Schematics is the same only plug differs: + * RJ45 Pin 3 (white-green) = R1IN + * RJ45 Pin 6 (green) = T1OUT + * RJ45 Pin 8 (brown) = GND +![image](https://user-images.githubusercontent.com/19826327/146428324-29e3f9bf-6cc3-415c-9d60-fa5ee3d65613.png) + + +# How to get going: + * Get Wemos D1 mini + * Install arduino IDE and ESP8266 libraries as [described here](https://averagemaker.com/2018/03/wemos-d1-mini-setup.html) + * Open [PylontechMonitoring.ino](PylontechMonitoring.ino) in arduino IDE + * Make sure to copy content of [libraries subdirectory](libraries) to [libraries of your Arduino IDE](https://forum.arduino.cc/index.php?topic=88380.0). + * Specify your WiFi login and password at the top of the file (line 13-14) + * If you want MQTT support, uncomment line 17 and fill details in lines 21-24 + * Upload project to your device + * Connect Wemos D1 mini to the MAX3232 transreceiver + * Connect transreceiver to RJ10/RJ45 as descibed in the schematics (all three lines need to be connected) + * Connect RJ10/RJ45 to the serial port of the Pylontech US2000 battery. If you have multiple batteries - connect to the master one. + * Connect Wemos D1 to the power via USB + * Find what IP address was assigned to your Wemos by your router and open it in the web-browser + * You should be able now to connunicate with the battery via WiFi diff --git a/Arduino-ESP/PylontechMonitoring/Schematics.png b/Arduino-ESP/PylontechMonitoring/Schematics.png new file mode 100755 index 0000000..d30f35b Binary files /dev/null and b/Arduino-ESP/PylontechMonitoring/Schematics.png differ diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Misc/circular_buffer.h b/Arduino-ESP/PylontechMonitoring/libraries/Misc/circular_buffer.h new file mode 100755 index 0000000..7440a7c --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Misc/circular_buffer.h @@ -0,0 +1,113 @@ +#ifndef circ_buffer_h +#define circ_buffer_h + +#include + +/// +/// This class allows a fixed size circular buffer. +/// When push_back is called, oldest data is overwritten. +/// Does not use any dynamic allocators. +/// +template class circular_buffer +{ +private: + ItemType m_arr[elementCnt]; + int m_writePos; + int m_size; + + void advanceWritePos() + { + if(m_size < elementCnt) + { + m_size++; + } + + m_writePos++; + if(m_writePos >= elementCnt) + { + m_writePos = 0; + } + } + + circular_buffer(const circular_buffer& rhs); +public: + circular_buffer() + { + clear(); + } + + void operator=(const circular_buffer& rhs) + { + memcpy(m_arr, rhs.m_arr, sizeof(m_arr)); + m_size = rhs.m_size; + m_writePos = rhs.m_writePos; + } + + void push_back(const ItemType& item) + { + m_arr[m_writePos] = item; + advanceWritePos(); + } + + void clear() + { + memset(m_arr,0,sizeof(m_arr)); + m_size = m_writePos = 0; + } + + void sort() + { + if (size() < 2) + return; + + bool swapped; + do + { + swapped = false; + for(int ix=0; ix at(ix+1)) + { + ItemType tmp = at(ix); + at(ix) = at(ix+1); + at(ix+1) = tmp; + swapped = true; + } + } + + }while(swapped); + } + + int size() const { return m_size; } + bool isFull() const { return size() == elementCnt; } + ItemType& operator[](int pos) {return at(pos);} + + ItemType& at(int pos) + { + if(m_size < elementCnt) + { + return m_arr[pos]; + } + + int readPos = m_writePos + pos; + if(readPos >= elementCnt) + { + readPos -= elementCnt; + } + + return m_arr[readPos]; + } + +#if _DEBUG_ENABLED + void print() + { + printf("---\n"); + for(int i=0; i //https://github.com/PaulStoffregen/Time +#include + +template class circular_log +{ +private: + char m_log[elementCnt]; + + bool removeLastFromLog() + { + char* nextLine = strstr(m_log+1, "
"); + if(nextLine == NULL) + { + return false; + } + + int newLineLen = strlen(nextLine); + memmove(m_log, nextLine, newLineLen); + m_log[newLineLen] = '\0'; + + return true; + } + +public: + circular_log() + { + memset(m_log, 0, sizeof(m_log)); + } + + const char* c_str() const { return m_log; } + int freeSpace() const { return elementCnt - strlen(m_log) - 1; } + + void LogXml(const char* msg) + { + char szNew[256] = ""; + snprintf(szNew, sizeof(szNew)-1, "
%02d - %02d:%02d | ", day(), hour(), minute()); + + int ix = strlen(szNew); + while(*msg != '\0' && ix < 250) + { + if(*msg == '<') + { + szNew[ix++] = '&'; + szNew[ix++] = 'l'; + szNew[ix++] = 't'; + szNew[ix++] = ';'; + } + else if(*msg == '>') + { + szNew[ix++] = '&'; + szNew[ix++] = 'g'; + szNew[ix++] = 't'; + szNew[ix++] = ';'; + } + else + { + szNew[ix++] = *msg; + } + + msg++; + } + + const int newLen = strlen(szNew); + while(freeSpace() < newLen) + { + if(removeLastFromLog() == false) + { + return; + } + } + + strcat(m_log, szNew); + } + + void Log(const char* msg) + { + char szNew[256] = ""; + snprintf(szNew, sizeof(szNew)-1, "
%02d - %02d:%02d | %s", day(), hour(), minute(), msg); + + const int newLen = strlen(szNew); + while(freeSpace() < newLen) + { + if(removeLastFromLog() == false) + { + return; + } + } + + strcat(m_log, szNew); + } +}; + +#endif //circular_log_h \ No newline at end of file diff --git a/Arduino-ESP/PylontechMonitoring/libraries/NtpTime/ntp_time.h b/Arduino-ESP/PylontechMonitoring/libraries/NtpTime/ntp_time.h new file mode 100755 index 0000000..5d01fed --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/NtpTime/ntp_time.h @@ -0,0 +1,81 @@ +#ifndef ntp_time_h +#define ntp_time_h + +#include + +// NTP Servers: +static const char ntpServerName[] = "0.uk.pool.ntp.org"; +const int timeZone = 0; +unsigned int localPort = 8888; // local port to listen for UDP packets + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets +WiFiUDP udpNtp; + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + udpNtp.beginPacket(address, 123); //NTP requests are to port 123 + udpNtp.write(packetBuffer, NTP_PACKET_SIZE); + udpNtp.endPacket(); +} + + +time_t getNtpTime() +{ + if(WiFi.status() != WL_CONNECTED) + { + return 0; + } + + static bool udpStarted = false; + if(udpStarted == false) + { + udpStarted = true; + udpNtp.begin(localPort); + } + + IPAddress ntpServerIP; // NTP server's ip address + + while (udpNtp.parsePacket() > 0) ; // discard any previously received packets + // get a random server from the pool + WiFi.hostByName(ntpServerName, ntpServerIP); + sendNTPpacket(ntpServerIP); + delay(100); + + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = udpNtp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + udpNtp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + + delay(10); + } + return 0; // return 0 if unable to get the time +} +#endif //ntp_time_h \ No newline at end of file diff --git a/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/README b/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/README new file mode 100755 index 0000000..c224b02 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/README @@ -0,0 +1,2 @@ +Visit this page for more information: +http://playground.arduino.cc/Code/SimpleTimer \ No newline at end of file diff --git a/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.cpp b/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.cpp new file mode 100755 index 0000000..274606c --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.cpp @@ -0,0 +1,250 @@ +/* + * SimpleTimer.cpp + * + * SimpleTimer - A timer library for Arduino. + * Author: mromani@ottotecnica.com + * Copyright (c) 2010 OTTOTECNICA Italy + * + * This library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser + * General Public License as published by the Free Software + * Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser + * General Public License along with this library; if not, + * write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#include "SimpleTimer.h" + + +// Select time function: +//static inline unsigned long elapsed() { return micros(); } +static inline unsigned long elapsed() { return millis(); } + + +SimpleTimer::SimpleTimer() + : numTimers (-1) +{ +} + +void SimpleTimer::init() { + unsigned long current_millis = elapsed(); + + for (int i = 0; i < MAX_TIMERS; i++) { + enabled[i] = false; + callbacks[i] = 0; // if the callback pointer is zero, the slot is free, i.e. doesn't "contain" any timer + prev_millis[i] = current_millis; + numRuns[i] = 0; + } + + numTimers = 0; +} + + +void SimpleTimer::run() { + int i; + unsigned long current_millis; + + // get current time + current_millis = elapsed(); + + for (i = 0; i < MAX_TIMERS; i++) { + + toBeCalled[i] = DEFCALL_DONTRUN; + + // no callback == no timer, i.e. jump over empty slots + if (callbacks[i]) { + + // is it time to process this timer ? + // see http://arduino.cc/forum/index.php/topic,124048.msg932592.html#msg932592 + + if (current_millis - prev_millis[i] >= delays[i]) { + + // update time + //prev_millis[i] = current_millis; + prev_millis[i] += delays[i]; + + // check if the timer callback has to be executed + if (enabled[i]) { + + // "run forever" timers must always be executed + if (maxNumRuns[i] == RUN_FOREVER) { + toBeCalled[i] = DEFCALL_RUNONLY; + } + // other timers get executed the specified number of times + else if (numRuns[i] < maxNumRuns[i]) { + toBeCalled[i] = DEFCALL_RUNONLY; + numRuns[i]++; + + // after the last run, delete the timer + if (numRuns[i] >= maxNumRuns[i]) { + toBeCalled[i] = DEFCALL_RUNANDDEL; + } + } + } + } + } + } + + for (i = 0; i < MAX_TIMERS; i++) { + switch(toBeCalled[i]) { + case DEFCALL_DONTRUN: + break; + + case DEFCALL_RUNONLY: + (*callbacks[i])(); + break; + + case DEFCALL_RUNANDDEL: + (*callbacks[i])(); + deleteTimer(i); + break; + } + } +} + + +// find the first available slot +// return -1 if none found +int SimpleTimer::findFirstFreeSlot() { + int i; + + // all slots are used + if (numTimers >= MAX_TIMERS) { + return -1; + } + + // return the first slot with no callback (i.e. free) + for (i = 0; i < MAX_TIMERS; i++) { + if (callbacks[i] == 0) { + return i; + } + } + + // no free slots found + return -1; +} + + +int SimpleTimer::setTimer(long d, timer_callback f, int n) { + int freeTimer; + + if (numTimers < 0) { + init(); + } + + freeTimer = findFirstFreeSlot(); + if (freeTimer < 0) { + return -1; + } + + if (f == NULL) { + return -1; + } + + delays[freeTimer] = d; + callbacks[freeTimer] = f; + maxNumRuns[freeTimer] = n; + enabled[freeTimer] = true; + prev_millis[freeTimer] = elapsed(); + + numTimers++; + + return freeTimer; +} + + +int SimpleTimer::setInterval(long d, timer_callback f) { + return setTimer(d, f, RUN_FOREVER); +} + + +int SimpleTimer::setTimeout(long d, timer_callback f) { + return setTimer(d, f, RUN_ONCE); +} + + +void SimpleTimer::deleteTimer(int timerId) { + if (timerId >= MAX_TIMERS) { + return; + } + + // nothing to delete if no timers are in use + if (numTimers == 0) { + return; + } + + // don't decrease the number of timers if the + // specified slot is already empty + if (callbacks[timerId] != NULL) { + callbacks[timerId] = 0; + enabled[timerId] = false; + toBeCalled[timerId] = DEFCALL_DONTRUN; + delays[timerId] = 0; + numRuns[timerId] = 0; + + // update number of timers + numTimers--; + } +} + + +// function contributed by code@rowansimms.com +void SimpleTimer::restartTimer(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + prev_millis[numTimer] = elapsed(); +} + + +boolean SimpleTimer::isEnabled(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return false; + } + + return enabled[numTimer]; +} + + +void SimpleTimer::enable(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + enabled[numTimer] = true; +} + + +void SimpleTimer::disable(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + enabled[numTimer] = false; +} + + +void SimpleTimer::toggle(int numTimer) { + if (numTimer >= MAX_TIMERS) { + return; + } + + enabled[numTimer] = !enabled[numTimer]; +} + + +int SimpleTimer::getNumTimers() { + return numTimers; +} diff --git a/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.h b/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.h new file mode 100755 index 0000000..9cdb395 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/SimpleTimer/SimpleTimer.h @@ -0,0 +1,126 @@ +/* + * SimpleTimer.h + * + * SimpleTimer - A timer library for Arduino. + * Author: mromani@ottotecnica.com + * Copyright (c) 2010 OTTOTECNICA Italy + * + * This library is free software; you can redistribute it + * and/or modify it under the terms of the GNU Lesser + * General Public License as published by the Free Software + * Foundation; either version 2.1 of the License, or (at + * your option) any later version. + * + * This library is distributed in the hope that it will + * be useful, but WITHOUT ANY WARRANTY; without even the + * implied warranty of MERCHANTABILITY or FITNESS FOR A + * PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser + * General Public License along with this library; if not, + * write to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + + +#ifndef SIMPLETIMER_H +#define SIMPLETIMER_H + +#if defined(ARDUINO) && ARDUINO >= 100 +#include +#else +#include +#endif + +typedef void (*timer_callback)(void); + +class SimpleTimer { + +public: + // maximum number of timers + const static int MAX_TIMERS = 10; + + // setTimer() constants + const static int RUN_FOREVER = 0; + const static int RUN_ONCE = 1; + + // constructor + SimpleTimer(); + + void init(); + + // this function must be called inside loop() + void run(); + + // call function f every d milliseconds + int setInterval(long d, timer_callback f); + + // call function f once after d milliseconds + int setTimeout(long d, timer_callback f); + + // call function f every d milliseconds for n times + int setTimer(long d, timer_callback f, int n); + + // destroy the specified timer + void deleteTimer(int numTimer); + + // restart the specified timer + void restartTimer(int numTimer); + + // returns true if the specified timer is enabled + boolean isEnabled(int numTimer); + + // enables the specified timer + void enable(int numTimer); + + // disables the specified timer + void disable(int numTimer); + + // enables the specified timer if it's currently disabled, + // and vice-versa + void toggle(int numTimer); + + // returns the number of used timers + int getNumTimers(); + + // returns the number of available timers + int getNumAvailableTimers() { return MAX_TIMERS - numTimers; }; + +private: + // deferred call constants + const static int DEFCALL_DONTRUN = 0; // don't call the callback function + const static int DEFCALL_RUNONLY = 1; // call the callback function but don't delete the timer + const static int DEFCALL_RUNANDDEL = 2; // call the callback function and delete the timer + + // find the first available slot + int findFirstFreeSlot(); + + // value returned by the millis() function + // in the previous run() call + unsigned long prev_millis[MAX_TIMERS]; + + // pointers to the callback functions + timer_callback callbacks[MAX_TIMERS]; + + // delay values + long delays[MAX_TIMERS]; + + // number of runs to be executed for each timer + int maxNumRuns[MAX_TIMERS]; + + // number of executed runs for each timer + int numRuns[MAX_TIMERS]; + + // which timers are enabled + boolean enabled[MAX_TIMERS]; + + // deferred function call (sort of) - N.B.: this array is only used in run() + int toBeCalled[MAX_TIMERS]; + + // actual number of timers in use + int numTimers; +}; + +#endif \ No newline at end of file diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/DateStrings.cpp b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/DateStrings.cpp new file mode 100755 index 0000000..3eccff3 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/DateStrings.cpp @@ -0,0 +1,97 @@ +/* DateStrings.cpp + * Definitions for date strings for use with the Time library + * + * Updated for Arduino 1.5.7 18 July 2014 + * + * No memory is consumed in the sketch if your code does not call any of the string methods + * You can change the text of the strings, make sure the short strings are each exactly 3 characters + * the long strings can be any length up to the constant dt_MAX_STRING_LEN defined in TimeLib.h + * + */ + +#if defined(__AVR__) +#include +#else +// for compatiblity with Arduino Due and Teensy 3.0 and maybe others? +#define PROGMEM +#define PGM_P const char * +#define pgm_read_byte(addr) (*(const unsigned char *)(addr)) +#define pgm_read_word(addr) (*(const unsigned char **)(addr)) +#define strcpy_P(dest, src) strcpy((dest), (src)) +#endif +#include // for strcpy_P or strcpy +#include "TimeLib.h" + +// the short strings for each day or month must be exactly dt_SHORT_STR_LEN +#define dt_SHORT_STR_LEN 3 // the length of short strings + +static char buffer[dt_MAX_STRING_LEN+1]; // must be big enough for longest string and the terminating null + +const char monthStr0[] PROGMEM = ""; +const char monthStr1[] PROGMEM = "January"; +const char monthStr2[] PROGMEM = "February"; +const char monthStr3[] PROGMEM = "March"; +const char monthStr4[] PROGMEM = "April"; +const char monthStr5[] PROGMEM = "May"; +const char monthStr6[] PROGMEM = "June"; +const char monthStr7[] PROGMEM = "July"; +const char monthStr8[] PROGMEM = "August"; +const char monthStr9[] PROGMEM = "September"; +const char monthStr10[] PROGMEM = "October"; +const char monthStr11[] PROGMEM = "November"; +const char monthStr12[] PROGMEM = "December"; + +const PROGMEM char * const PROGMEM monthNames_P[] = +{ + monthStr0,monthStr1,monthStr2,monthStr3,monthStr4,monthStr5,monthStr6, + monthStr7,monthStr8,monthStr9,monthStr10,monthStr11,monthStr12 +}; + +const char monthShortNames_P[] PROGMEM = "ErrJanFebMarAprMayJunJulAugSepOctNovDec"; + +const char dayStr0[] PROGMEM = "Err"; +const char dayStr1[] PROGMEM = "Sunday"; +const char dayStr2[] PROGMEM = "Monday"; +const char dayStr3[] PROGMEM = "Tuesday"; +const char dayStr4[] PROGMEM = "Wednesday"; +const char dayStr5[] PROGMEM = "Thursday"; +const char dayStr6[] PROGMEM = "Friday"; +const char dayStr7[] PROGMEM = "Saturday"; + +const PROGMEM char * const PROGMEM dayNames_P[] = +{ + dayStr0,dayStr1,dayStr2,dayStr3,dayStr4,dayStr5,dayStr6,dayStr7 +}; + +const char dayShortNames_P[] PROGMEM = "ErrSunMonTueWedThuFriSat"; + +/* functions to return date strings */ + +char* monthStr(uint8_t month) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(monthNames_P[month]))); + return buffer; +} + +char* monthShortStr(uint8_t month) +{ + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(monthShortNames_P[i+ (month*dt_SHORT_STR_LEN)])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} + +char* dayStr(uint8_t day) +{ + strcpy_P(buffer, (PGM_P)pgm_read_word(&(dayNames_P[day]))); + return buffer; +} + +char* dayShortStr(uint8_t day) +{ + uint8_t index = day*dt_SHORT_STR_LEN; + for (int i=0; i < dt_SHORT_STR_LEN; i++) + buffer[i] = pgm_read_byte(&(dayShortNames_P[index + i])); + buffer[dt_SHORT_STR_LEN] = 0; + return buffer; +} diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Readme.txt b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Readme.txt new file mode 100755 index 0000000..67b148e --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Readme.txt @@ -0,0 +1,131 @@ +Readme file for Arduino Time Library + +Time is a library that provides timekeeping functionality for Arduino. + +The code is derived from the Playground DateTime library but is updated +to provide an API that is more flexable and easier to use. + +A primary goal was to enable date and time functionality that can be used with +a variety of external time sources with minimum differences required in sketch logic. + +Example sketches illustrate how similar sketch code can be used with: a Real Time Clock, +internet NTP time service, GPS time data, and Serial time messages from a computer +for time synchronization. + +The functions available in the library include: + +hour(); // the hour now (0-23) +minute(); // the minute now (0-59) +second(); // the second now (0-59) +day(); // the day now (1-31) +weekday(); // day of the week, Sunday is day 0 +month(); // the month now (1-12) +year(); // the full four digit year: (2009, 2010 etc) + +there are also functions to return the hour in 12 hour format +hourFormat12(); // the hour now in 12 hour format +isAM(); // returns true if time now is AM +isPM(); // returns true if time now is PM + +now(); // returns the current time as seconds since Jan 1 1970 + +The time and date functions can take an optional parameter for the time. This prevents +errors if the time rolls over between elements. For example, if a new minute begins +between getting the minute and second, the values will be inconsistent. Using the +following functions eliminates this probglem + time_t t = now(); // store the current time in time variable t + hour(t); // returns the hour for the given time t + minute(t); // returns the minute for the given time t + second(t); // returns the second for the given time t + day(t); // the day for the given time t + weekday(t); // day of the week for the given time t + month(t); // the month for the given time t + year(t); // the year for the given time t + + +Functions for managing the timer services are: +setTime(t); // set the system time to the give time t +setTime(hr,min,sec,day,mnth,yr); // alternative to above, yr is 2 or 4 digit yr (2010 or 10 sets year to 2010) +adjustTime(adjustment); // adjust system time by adding the adjustment value + +timeStatus(); // indicates if time has been set and recently synchronized + // returns one of the following enumerations: + timeNotSet // the time has never been set, the clock started at Jan 1 1970 + timeNeedsSync // the time had been set but a sync attempt did not succeed + timeSet // the time is set and is synced +Time and Date values are not valid if the status is timeNotSet. Otherwise values can be used but +the returned time may have drifted if the status is timeNeedsSync. + +setSyncProvider(getTimeFunction); // set the external time provider +setSyncInterval(interval); // set the number of seconds between re-sync + + +There are many convenience macros in the time.h file for time constants and conversion of time units. + +To use the library, copy the download to the Library directory. + +The Time directory contains the Time library and some example sketches +illustrating how the library can be used with various time sources: + +- TimeSerial.pde shows Arduino as a clock without external hardware. + It is synchronized by time messages sent over the serial port. + A companion Processing sketch will automatically provide these messages + if it is running and connected to the Arduino serial port. + +- TimeSerialDateStrings.pde adds day and month name strings to the sketch above + Short (3 character) and long strings are available to print the days of + the week and names of the months. + +- TimeRTC uses a DS1307 real time clock to provide time synchronization. + A basic RTC library named DS1307RTC is included in the download. + To run this sketch the DS1307RTC library must be installed. + +- TimeRTCSet is similar to the above and adds the ability to set the Real Time Clock + +- TimeRTCLog demonstrates how to calculate the difference between times. + It is a vary simple logger application that monitors events on digtial pins + and prints (to the serial port) the time of an event and the time period since the previous event. + +- TimeNTP uses the Arduino Ethernet shield to access time using the internet NTP time service. + The NTP protocol uses UDP and the UdpBytewise library is required, see: + http://bitbucket.org/bjoern/arduino_osc/src/14667490521f/libraries/Ethernet/ + +- TimeGPS gets time from a GPS + This requires the TinyGPS library from Mikal Hart: + http://arduiniana.org/libraries/TinyGPS + +Differences between this code and the playground DateTime library +although the Time library is based on the DateTime codebase, the API has changed. +Changes in the Time library API: +- time elements are functions returning int (they are variables in DateTime) +- Years start from 1970 +- days of the week and months start from 1 (they start from 0 in DateTime) +- DateStrings do not require a seperate library +- time elements can be accessed non-atomically (in DateTime they are always atomic) +- function added to automatically sync time with extrnal source +- localTime and maketime parameters changed, localTime renamed to breakTime + +Technical notes: + +Internal system time is based on the standard Unix time_t. +The value is the number of seconds since Jan 1 1970. +System time begins at zero when the sketch starts. + +The internal time can be automatically synchronized at regular intervals to an external time source. +This is enabled by calling the setSyncProvider(provider) function - the provider argument is +the address of a function that returns the current time as a time_t. +See the sketches in the examples directory for usage. + +The default interval for re-syncing the time is 5 minutes but can be changed by calling the +setSyncInterval( interval) method to set the number of seconds between re-sync attempts. + +The Time library defines a structure for holding time elements that is a compact version of the C tm structure. +All the members of the Arduino tm structure are bytes and the year is offset from 1970. +Convenience macros provide conversion to and from the Arduino format. + +Low level functions to convert between system time and individual time elements are provided: + breakTime( time, &tm); // break time_t into elements stored in tm struct + makeTime( &tm); // return time_t from elements stored in tm struct + +The DS1307RTC library included in the download provides an example of how a time provider +can use the low level functions to interface with the Time library. diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.cpp b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.cpp new file mode 100755 index 0000000..5ca1327 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.cpp @@ -0,0 +1,321 @@ +/* + time.c - low level time and date functions + Copyright (c) Michael Margolis 2009-2014 + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + 1.0 6 Jan 2010 - initial release + 1.1 12 Feb 2010 - fixed leap year calculation error + 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) + 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update + status, updated examples for Arduino 1.0, fixed ARM + compatibility issues, added TimeArduinoDue and TimeTeensy3 + examples, add error checking and messages to RTC examples, + add examples to DS1307RTC library. + 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 +*/ + +#if ARDUINO >= 100 +#include +#else +#include +#endif + +#include "TimeLib.h" + +static tmElements_t tm; // a cache of time elements +static time_t cacheTime; // the time the cache was updated +static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds + +void refreshCache(time_t t) { + if (t != cacheTime) { + breakTime(t, tm); + cacheTime = t; + } +} + +int hour() { // the hour now + return hour(now()); +} + +int hour(time_t t) { // the hour for the given time + refreshCache(t); + return tm.Hour; +} + +int hourFormat12() { // the hour now in 12 hour format + return hourFormat12(now()); +} + +int hourFormat12(time_t t) { // the hour for the given time in 12 hour format + refreshCache(t); + if( tm.Hour == 0 ) + return 12; // 12 midnight + else if( tm.Hour > 12) + return tm.Hour - 12 ; + else + return tm.Hour ; +} + +uint8_t isAM() { // returns true if time now is AM + return !isPM(now()); +} + +uint8_t isAM(time_t t) { // returns true if given time is AM + return !isPM(t); +} + +uint8_t isPM() { // returns true if PM + return isPM(now()); +} + +uint8_t isPM(time_t t) { // returns true if PM + return (hour(t) >= 12); +} + +int minute() { + return minute(now()); +} + +int minute(time_t t) { // the minute for the given time + refreshCache(t); + return tm.Minute; +} + +int second() { + return second(now()); +} + +int second(time_t t) { // the second for the given time + refreshCache(t); + return tm.Second; +} + +int day(){ + return(day(now())); +} + +int day(time_t t) { // the day for the given time (0-6) + refreshCache(t); + return tm.Day; +} + +int weekday() { // Sunday is day 1 + return weekday(now()); +} + +int weekday(time_t t) { + refreshCache(t); + return tm.Wday; +} + +int month(){ + return month(now()); +} + +int month(time_t t) { // the month for the given time + refreshCache(t); + return tm.Month; +} + +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); +} + +int year(time_t t) { // the year for the given time + refreshCache(t); + return tmYearToCalendar(tm.Year); +} + +/*============================================================================*/ +/* functions to convert to and from system time */ +/* These are for interfacing with time serivces and are not normally needed in a sketch */ + +// leap year calulator expects year argument as years offset from 1970 +#define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) + +static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 + +void breakTime(time_t timeInput, tmElements_t &tm){ +// break the given time_t into time components +// this is a more compact version of the C library localtime function +// note that year is offset from 1970 !!! + + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + + time = (uint32_t)timeInput; + tm.Second = time % 60; + time /= 60; // now it is minutes + tm.Minute = time % 60; + time /= 60; // now it is hours + tm.Hour = time % 24; + time /= 24; // now it is days + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { + year++; + } + tm.Year = year; // year is offset from 1970 + + days -= LEAP_YEAR(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days=0; + month=0; + monthLength=0; + for (month=0; month<12; month++) { + if (month==1) { // february + if (LEAP_YEAR(year)) { + monthLength=29; + } else { + monthLength=28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month +} + +time_t makeTime(tmElements_t &tm){ +// assemble time elements into time_t +// note year argument is offset from 1970 (see macros in time.h to convert to other formats) +// previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds= tm.Year*(SECS_PER_DAY * 365); + for (i = 0; i < tm.Year; i++) { + if (LEAP_YEAR(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.Month; i++) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 + } + } + seconds+= (tm.Day-1) * SECS_PER_DAY; + seconds+= tm.Hour * SECS_PER_HOUR; + seconds+= tm.Minute * SECS_PER_MIN; + seconds+= tm.Second; + return (time_t)seconds; +} +/*=====================================================*/ +/* Low level system time functions */ + +static uint32_t sysTime = 0; +static uint32_t prevMillis = 0; +static uint32_t nextSyncTime = 0; +static timeStatus_t Status = timeNotSet; + +getExternalTime getTimePtr; // pointer to external sync function +//setExternalTime setTimePtr; // not used in this version + +#ifdef TIME_DRIFT_INFO // define this to get drift data +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +#endif + + +time_t now() { + // calculate number of seconds passed since last call to now() + while (millis() - prevMillis >= 1000) { + // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference + sysTime++; + prevMillis += 1000; +#ifdef TIME_DRIFT_INFO + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift +#endif + } + if (nextSyncTime <= sysTime) { + if (getTimePtr != 0) { + time_t t = getTimePtr(); + if (t != 0) { + setTime(t); + } else { + nextSyncTime = sysTime + syncInterval; + Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; + } + } + } + return (time_t)sysTime; +} + +void setTime(time_t t) { +#ifdef TIME_DRIFT_INFO + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time +#endif + + sysTime = (uint32_t)t; + nextSyncTime = (uint32_t)t + syncInterval; + Status = timeSet; + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} + +void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ + // year can be given as full four digit year or two digts (2010 or 10 for 2010); + //it is converted to years since 1970 + if( yr > 99) + yr = yr - 1970; + else + yr += 30; + tm.Year = yr; + tm.Month = mnth; + tm.Day = dy; + tm.Hour = hr; + tm.Minute = min; + tm.Second = sec; + setTime(makeTime(tm)); +} + +void adjustTime(long adjustment) { + sysTime += adjustment; +} + +// indicates if time has been set and recently synchronized +timeStatus_t timeStatus() { + now(); // required to actually update the status + return Status; +} + +void setSyncProvider( getExternalTime getTimeFunction){ + getTimePtr = getTimeFunction; + nextSyncTime = sysTime; + now(); // this will sync the clock +} + +void setSyncInterval(time_t interval){ // set the number of seconds between re-sync + syncInterval = (uint32_t)interval; + nextSyncTime = sysTime + syncInterval; +} diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.h b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.h new file mode 100755 index 0000000..a79b080 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/Time.h @@ -0,0 +1 @@ +#include "TimeLib.h" diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/TimeLib.h b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/TimeLib.h new file mode 100755 index 0000000..ddb1668 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/TimeLib.h @@ -0,0 +1,144 @@ +/* + time.h - low level time and date functions +*/ + +/* + July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) + - fixed daysToTime_t macro (thanks maniacbug) +*/ + +#ifndef _Time_h +#ifdef __cplusplus +#define _Time_h + +#include +#ifndef __AVR__ +#include // for __time_t_defined, but avr libc lacks sys/types.h +#endif + + +#if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc +typedef unsigned long time_t; +#endif + + +// This ugly hack allows us to define C++ overloaded functions, when included +// from within an extern "C", as newlib's sys/stat.h does. Actually it is +// intended to include "time.h" from the C library (on ARM, but AVR does not +// have that file at all). On Mac and Windows, the compiler will find this +// "Time.h" instead of the C library "time.h", so we may cause other weird +// and unpredictable effects by conflicting with the C library header "time.h", +// but at least this hack lets us define C++ functions as intended. Hopefully +// nothing too terrible will result from overriding the C library header?! +extern "C++" { +typedef enum {timeNotSet, timeNeedsSync, timeSet +} timeStatus_t ; + +typedef enum { + dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday +} timeDayOfWeek_t; + +typedef enum { + tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields +} tmByteFields; + +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; + uint8_t Wday; // day of week, sunday is day 1 + uint8_t Day; + uint8_t Month; + uint8_t Year; // offset from 1970; +} tmElements_t, TimeElements, *tmElementsPtr_t; + +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +#define CalendarYrToTm(Y) ((Y) - 1970) +#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 +#define y2kYearToTm(Y) ((Y) + 30) + +typedef time_t(*getExternalTime)(); +//typedef void (*setExternalTime)(const time_t); // not used in this version + + +/*==============================================================================*/ +/* Useful Constants */ +#define SECS_PER_MIN ((time_t)(60UL)) +#define SECS_PER_HOUR ((time_t)(3600UL)) +#define SECS_PER_DAY ((time_t)(SECS_PER_HOUR * 24UL)) +#define DAYS_PER_WEEK ((time_t)(7UL)) +#define SECS_PER_WEEK ((time_t)(SECS_PER_DAY * DAYS_PER_WEEK)) +#define SECS_PER_YEAR ((time_t)(SECS_PER_WEEK * 52UL)) +#define SECS_YR_2000 ((time_t)(946684800UL)) // the time at the start of y2k + +/* Useful Macros for getting elapsed time */ +#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) +#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) +#define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday +#define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 +#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight +// The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 +// Always set the correct time before settting alarms +#define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day +#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 +#define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time +#define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time + + +/* Useful Macros for converting elapsed time to a time_t */ +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) + +/*============================================================================*/ +/* time and date functions */ +int hour(); // the hour now +int hour(time_t t); // the hour for the given time +int hourFormat12(); // the hour now in 12 hour format +int hourFormat12(time_t t); // the hour for the given time in 12 hour format +uint8_t isAM(); // returns true if time now is AM +uint8_t isAM(time_t t); // returns true the given time is AM +uint8_t isPM(); // returns true if time now is PM +uint8_t isPM(time_t t); // returns true the given time is PM +int minute(); // the minute now +int minute(time_t t); // the minute for the given time +int second(); // the second now +int second(time_t t); // the second for the given time +int day(); // the day now +int day(time_t t); // the day for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time +int month(); // the month now (Jan is month 1) +int month(time_t t); // the month for the given time +int year(); // the full four digit year: (2009, 2010 etc) +int year(time_t t); // the year for the given time + +time_t now(); // return the current time as seconds since Jan 1 1970 +void setTime(time_t t); +void setTime(int hr,int min,int sec,int day, int month, int yr); +void adjustTime(long adjustment); + +/* date strings */ +#define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) +char* monthStr(uint8_t month); +char* dayStr(uint8_t day); +char* monthShortStr(uint8_t month); +char* dayShortStr(uint8_t day); + +/* time sync functions */ +timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized +void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider +void setSyncInterval(time_t interval); // set the number of seconds between re-sync + +/* low level functions to convert to and from system time */ +void breakTime(time_t time, tmElements_t &tm); // break time_t into elements +time_t makeTime(tmElements_t &tm); // convert time elements into time_t + +} // extern "C++" +#endif // __cplusplus +#endif /* _Time_h */ + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde new file mode 100755 index 0000000..4313be3 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/SyncArduinoClock.pde @@ -0,0 +1,78 @@ +/** + * SyncArduinoClock. + * + * portIndex must be set to the port connected to the Arduino + * + * The current time is sent in response to request message from Arduino + * or by clicking the display window + * + * The time message is 11 ASCII text characters; a header (the letter 'T') + * followed by the ten digit system time (unix time) + */ + + +import processing.serial.*; +import java.util.Date; +import java.util.Calendar; +import java.util.GregorianCalendar; + +public static final short portIndex = 0; // select the com port, 0 is the first port +public static final String TIME_HEADER = "T"; //header for arduino serial time message +public static final char TIME_REQUEST = 7; // ASCII bell character +public static final char LF = 10; // ASCII linefeed +public static final char CR = 13; // ASCII linefeed +Serial myPort; // Create object from Serial class + +void setup() { + size(200, 200); + println(Serial.list()); + println(" Connecting to -> " + Serial.list()[portIndex]); + myPort = new Serial(this,Serial.list()[portIndex], 9600); + println(getTimeNow()); +} + +void draw() +{ + textSize(20); + textAlign(CENTER); + fill(0); + text("Click to send\nTime Sync", 0, 75, 200, 175); + if ( myPort.available() > 0) { // If data is available, + char val = char(myPort.read()); // read it and store it in val + if(val == TIME_REQUEST){ + long t = getTimeNow(); + sendTimeMessage(TIME_HEADER, t); + } + else + { + if(val == LF) + ; //igonore + else if(val == CR) + println(); + else + print(val); // echo everying but time request + } + } +} + +void mousePressed() { + sendTimeMessage( TIME_HEADER, getTimeNow()); +} + + +void sendTimeMessage(String header, long time) { + String timeStr = String.valueOf(time); + myPort.write(header); // send header and time to arduino + myPort.write(timeStr); + myPort.write('\n'); +} + +long getTimeNow(){ + // java time is in ms, we want secs + Date d = new Date(); + Calendar cal = new GregorianCalendar(); + long current = d.getTime()/1000; + long timezone = cal.get(cal.ZONE_OFFSET)/1000; + long daylight = cal.get(cal.DST_OFFSET)/1000; + return current + timezone + daylight; +} diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/readme.txt b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/readme.txt new file mode 100755 index 0000000..da9721d --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/Processing/SyncArduinoClock/readme.txt @@ -0,0 +1,9 @@ +SyncArduinoClock is a Processing sketch that responds to Arduino requests for +time synchronization messages. + +The portIndex must be set the Serial port connected to Arduino. + +Download TimeSerial.pde onto Arduino and you should see the time +message displayed when you run SyncArduinoClock in Processing. +The Arduino time is set from the time on your computer through the +Processing sketch. diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeArduinoDue/TimeArduinoDue.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeArduinoDue/TimeArduinoDue.ino new file mode 100755 index 0000000..f0a9a95 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeArduinoDue/TimeArduinoDue.ino @@ -0,0 +1,71 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + * This example requires Markus Lange's Arduino Due RTC Library + * https://github.com/MarkusLange/Arduino-Due-RTC-Library + */ + +#include +#include + +// Select the Slowclock source +//RTC_clock rtc_clock(RC); +RTC_clock rtc_clock(XTAL); + +void setup() { + Serial.begin(9600); + rtc_clock.init(); + if (rtc_clock.date_already_set() == 0) { + // Unfortunately, the Arduino Due hardware does not seem to + // be designed to maintain the RTC clock state when the + // board resets. Markus described it thusly: "Uhh the Due + // does reset with the NRSTB pin. This resets the full chip + // with all backup regions including RTC, RTT and SC. Only + // if the reset is done with the NRST pin will these regions + // stay with their old values." + rtc_clock.set_time(__TIME__); + rtc_clock.set_date(__DATE__); + // However, this might work on other unofficial SAM3X boards + // with different reset circuitry than Arduino Due? + } + setSyncProvider(getArduinoDueTime); + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +time_t getArduinoDueTime() +{ + return rtc_clock.unixtime(); +} + +void loop() +{ + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeGPS/TimeGPS.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeGPS/TimeGPS.ino new file mode 100755 index 0000000..fea9698 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeGPS/TimeGPS.ino @@ -0,0 +1,87 @@ +/* + * TimeGPS.pde + * example code illustrating time synced from a GPS + * + */ + +#include +#include // http://arduiniana.org/libraries/TinyGPS/ +#include +// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart + +SoftwareSerial SerialGPS = SoftwareSerial(10, 11); // receive on pin 10 +TinyGPS gps; + +// To use a hardware serial port, which is far more efficient than +// SoftwareSerial, uncomment this line and remove SoftwareSerial +//#define SerialGPS Serial1 + +// Offset hours from gps time (UTC) +const int offset = 1; // Central European Time +//const int offset = -5; // Eastern Standard Time (USA) +//const int offset = -4; // Eastern Daylight Time (USA) +//const int offset = -8; // Pacific Standard Time (USA) +//const int offset = -7; // Pacific Daylight Time (USA) + +// Ideally, it should be possible to learn the time zone +// based on the GPS position data. However, that would +// require a complex library, probably incorporating some +// sort of database using Eric Muller's time zone shape +// maps, at http://efele.net/maps/tz/ + +time_t prevDisplay = 0; // when the digital clock was displayed + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + SerialGPS.begin(4800); + Serial.println("Waiting for GPS time ... "); +} + +void loop() +{ + while (SerialGPS.available()) { + if (gps.encode(SerialGPS.read())) { // process gps messages + // when TinyGPS reports new data... + unsigned long age; + int Year; + byte Month, Day, Hour, Minute, Second; + gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, NULL, &age); + if (age < 500) { + // set the Time to the latest GPS reading + setTime(Hour, Minute, Second, Day, Month, Year); + adjustTime(offset * SECS_PER_HOUR); + } + } + } + if (timeStatus()!= timeNotSet) { + if (now() != prevDisplay) { //update the display only if the time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP/TimeNTP.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP/TimeNTP.ino new file mode 100755 index 0000000..17a908f --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP/TimeNTP.ino @@ -0,0 +1,135 @@ +/* + * Time_NTP.pde + * Example showing time sync to NTP time source + * + * This sketch uses the Ethernet library + */ + +#include +#include +#include +#include + +byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; +// NTP Servers: +IPAddress timeServer(132, 163, 4, 101); // time-a.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 102); // time-b.timefreq.bldrdoc.gov +// IPAddress timeServer(132, 163, 4, 103); // time-c.timefreq.bldrdoc.gov + + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +EthernetUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + if (Ethernet.begin(mac) == 0) { + // no point in carrying on, so do nothing forevermore: + while (1) { + Serial.println("Failed to configure Ethernet using DHCP"); + delay(10000); + } + } + Serial.print("IP number assigned by DHCP is "); + Serial.println(Ethernet.localIP()); + Udp.begin(localPort); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + sendNTPpacket(timeServer); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino new file mode 100755 index 0000000..1aeb2d7 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeNTP_ESP8266WiFi/TimeNTP_ESP8266WiFi.ino @@ -0,0 +1,156 @@ +/* + * TimeNTP_ESP8266WiFi.ino + * Example showing time sync to NTP time source + * + * This sketch uses the ESP8266WiFi library + */ + +#include +#include +#include + +const char ssid[] = "*************"; // your network SSID (name) +const char pass[] = "********"; // your network password + +// NTP Servers: +static const char ntpServerName[] = "us.pool.ntp.org"; +//static const char ntpServerName[] = "time.nist.gov"; +//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov"; +//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov"; +//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov"; + +const int timeZone = 1; // Central European Time +//const int timeZone = -5; // Eastern Standard Time (USA) +//const int timeZone = -4; // Eastern Daylight Time (USA) +//const int timeZone = -8; // Pacific Standard Time (USA) +//const int timeZone = -7; // Pacific Daylight Time (USA) + + +WiFiUDP Udp; +unsigned int localPort = 8888; // local port to listen for UDP packets + +time_t getNtpTime(); +void digitalClockDisplay(); +void printDigits(int digits); +void sendNTPpacket(IPAddress &address); + +void setup() +{ + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + delay(250); + Serial.println("TimeNTP Example"); + Serial.print("Connecting to "); + Serial.println(ssid); + WiFi.begin(ssid, pass); + + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + + Serial.print("IP number assigned by DHCP is "); + Serial.println(WiFi.localIP()); + Serial.println("Starting UDP"); + Udp.begin(localPort); + Serial.print("Local port: "); + Serial.println(Udp.localPort()); + Serial.println("waiting for sync"); + setSyncProvider(getNtpTime); + setSyncInterval(300); +} + +time_t prevDisplay = 0; // when the digital clock was displayed + +void loop() +{ + if (timeStatus() != timeNotSet) { + if (now() != prevDisplay) { //update the display only if time has changed + prevDisplay = now(); + digitalClockDisplay(); + } + } +} + +void digitalClockDisplay() +{ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print("."); + Serial.print(month()); + Serial.print("."); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) +{ + // utility for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if (digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/*-------- NTP code ----------*/ + +const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message +byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets + +time_t getNtpTime() +{ + IPAddress ntpServerIP; // NTP server's ip address + + while (Udp.parsePacket() > 0) ; // discard any previously received packets + Serial.println("Transmit NTP Request"); + // get a random server from the pool + WiFi.hostByName(ntpServerName, ntpServerIP); + Serial.print(ntpServerName); + Serial.print(": "); + Serial.println(ntpServerIP); + sendNTPpacket(ntpServerIP); + uint32_t beginWait = millis(); + while (millis() - beginWait < 1500) { + int size = Udp.parsePacket(); + if (size >= NTP_PACKET_SIZE) { + Serial.println("Receive NTP Response"); + Udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + unsigned long secsSince1900; + // convert four bytes starting at location 40 to a long integer + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; + } + } + Serial.println("No NTP Response :-("); + return 0; // return 0 if unable to get the time +} + +// send an NTP request to the time server at the given address +void sendNTPpacket(IPAddress &address) +{ + // set all bytes in the buffer to 0 + memset(packetBuffer, 0, NTP_PACKET_SIZE); + // Initialize values needed to form NTP request + // (see URL above for details on the packets) + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + // 8 bytes of zero for Root Delay & Root Dispersion + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // all NTP fields have been given values, now + // you can send a packet requesting a timestamp: + Udp.beginPacket(address, 123); //NTP requests are to port 123 + Udp.write(packetBuffer, NTP_PACKET_SIZE); + Udp.endPacket(); +} diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTC/TimeRTC.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTC/TimeRTC.ino new file mode 100755 index 0000000..fa10ff6 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTC/TimeRTC.ino @@ -0,0 +1,55 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + +void setup() { + Serial.begin(9600); + while (!Serial) ; // wait until Arduino Serial Monitor opens + setSyncProvider(RTC.get); // the function to get the time from the RTC + if(timeStatus()!= timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (timeStatus() == timeSet) { + digitalClockDisplay(); + } else { + Serial.println("The time has not been set. Please run the Time"); + Serial.println("TimeRTCSet example, or DS1307RTC SetTime example."); + Serial.println(); + delay(4000); + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCLog/TimeRTCLog.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCLog/TimeRTCLog.ino new file mode 100755 index 0000000..0b250c2 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCLog/TimeRTCLog.ino @@ -0,0 +1,107 @@ +/* + * TimeRTCLogger.pde + * example code illustrating adding and subtracting Time. + * + * this sketch logs pin state change events + * the time of the event and time since the previous event is calculated and sent to the serial port. + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + +const int nbrInputPins = 6; // monitor 6 digital pins +const int inputPins[nbrInputPins] = {2,3,4,5,6,7}; // pins to monitor +boolean state[nbrInputPins] ; // the state of the monitored pins +time_t prevEventTime[nbrInputPins] ; // the time of the previous event + +void setup() { + Serial.begin(9600); + setSyncProvider(RTC.get); // the function to sync the time from the RTC + for(int i=0; i < nbrInputPins; i++){ + pinMode( inputPins[i], INPUT); + // uncomment these lines if pull-up resistors are wanted + // pinMode( inputPins[i], INPUT_PULLUP); + // state[i] = HIGH; + } +} + +void loop() +{ + for(int i=0; i < nbrInputPins; i++) + { + boolean val = digitalRead(inputPins[i]); + if(val != state[i]) + { + time_t duration = 0; // the time since the previous event + state[i] = val; + time_t timeNow = now(); + if(prevEventTime[i] > 0) + // if this was not the first state change, calculate the time from the previous change + duration = duration = timeNow - prevEventTime[i]; + logEvent(inputPins[i], val, timeNow, duration ); // log the event + prevEventTime[i] = timeNow; // store the time for this event + } + } +} + +void logEvent( int pin, boolean state, time_t timeNow, time_t duration) +{ + Serial.print("Pin "); + Serial.print(pin); + if( state == HIGH) + Serial.print(" went High at "); + else + Serial.print(" went Low at "); + showTime(timeNow); + if(duration > 0){ + // only display duration if greater than 0 + Serial.print(", Duration was "); + showDuration(duration); + } + Serial.println(); +} + + +void showTime(time_t t){ + // display the given time + Serial.print(hour(t)); + printDigits(minute(t)); + printDigits(second(t)); + Serial.print(" "); + Serial.print(day(t)); + Serial.print(" "); + Serial.print(month(t)); + Serial.print(" "); + Serial.print(year(t)); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void showDuration(time_t duration){ +// prints the duration in days, hours, minutes and seconds + if(duration >= SECS_PER_DAY){ + Serial.print(duration / SECS_PER_DAY); + Serial.print(" day(s) "); + duration = duration % SECS_PER_DAY; + } + if(duration >= SECS_PER_HOUR){ + Serial.print(duration / SECS_PER_HOUR); + Serial.print(" hour(s) "); + duration = duration % SECS_PER_HOUR; + } + if(duration >= SECS_PER_MIN){ + Serial.print(duration / SECS_PER_MIN); + Serial.print(" minute(s) "); + duration = duration % SECS_PER_MIN; + } + Serial.print(duration); + Serial.print(" second(s) "); +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCSet/TimeRTCSet.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCSet/TimeRTCSet.ino new file mode 100755 index 0000000..49c49f3 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeRTCSet/TimeRTCSet.ino @@ -0,0 +1,80 @@ +/* + * TimeRTCSet.pde + * example code illustrating Time library with Real Time Clock. + * + * RTC clock is set in response to serial port time message + * A Processing example sketch to set the time is included in the download + * On Linux, you can use "date +T%s > /dev/ttyACM0" (UTC time zone) + */ + +#include +#include +#include // a basic DS1307 library that returns time as a time_t + + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider(RTC.get); // the function to get the time from the RTC + if (timeStatus() != timeSet) + Serial.println("Unable to sync with the RTC"); + else + Serial.println("RTC has set the system time"); +} + +void loop() +{ + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + RTC.set(t); // set the RTC and the system time to the received value + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + + + + + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerial/TimeSerial.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerial/TimeSerial.ino new file mode 100755 index 0000000..07e609f --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerial/TimeSerial.ino @@ -0,0 +1,81 @@ +/* + * TimeSerial.pde + * example code illustrating Time library set through serial port messages. + * + * Messages consist of the letter T followed by ten digit time (as seconds since Jan 1 1970) + * you can send the text on the next line using Serial Monitor to set the clock to noon Jan 1 2013 + T1357041600 + * + * A Processing example sketch to automatically send the messages is included in the download + * On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) + */ + +#include + +#define TIME_HEADER "T" // Header tag for serial time sync message +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + pinMode(13, OUTPUT); + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available()) { + processSyncMessage(); + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + if (timeStatus() == timeSet) { + digitalWrite(13, HIGH); // LED on if synced + } else { + digitalWrite(13, LOW); // LED off if needs refresh + } + delay(1000); +} + +void digitalClockDisplay(){ + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } + } +} + +time_t requestSync() +{ + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino new file mode 100755 index 0000000..95d2568 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeSerialDateStrings/TimeSerialDateStrings.ino @@ -0,0 +1,108 @@ +/* + * TimeSerialDateStrings.pde + * example code illustrating Time library date strings + * + * This sketch adds date string functionality to TimeSerial sketch + * Also shows how to handle different messages + * + * A message starting with a time header sets the time + * A Processing example sketch to automatically send the messages is inclided in the download + * On Linux, you can use "date +T%s\n > /dev/ttyACM0" (UTC time zone) + * + * A message starting with a format header sets the date format + + * send: Fs\n for short date format + * send: Fl\n for long date format + */ + +#include + +// single character message tags +#define TIME_HEADER 'T' // Header tag for serial time sync message +#define FORMAT_HEADER 'F' // Header tag indicating a date format message +#define FORMAT_SHORT 's' // short month and day strings +#define FORMAT_LONG 'l' // (lower case l) long month and day strings + +#define TIME_REQUEST 7 // ASCII bell character requests a time sync message + +static boolean isLongFormat = true; + +void setup() { + Serial.begin(9600); + while (!Serial) ; // Needed for Leonardo only + setSyncProvider( requestSync); //set function to call when sync required + Serial.println("Waiting for sync message"); +} + +void loop(){ + if (Serial.available() > 1) { // wait for at least two characters + char c = Serial.read(); + if( c == TIME_HEADER) { + processSyncMessage(); + } + else if( c== FORMAT_HEADER) { + processFormatMessage(); + } + } + if (timeStatus()!= timeNotSet) { + digitalClockDisplay(); + } + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + if(isLongFormat) + Serial.print(dayStr(weekday())); + else + Serial.print(dayShortStr(weekday())); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + if(isLongFormat) + Serial.print(monthStr(month())); + else + Serial.print(monthShortStr(month())); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +void printDigits(int digits) { + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + +void processFormatMessage() { + char c = Serial.read(); + if( c == FORMAT_LONG){ + isLongFormat = true; + Serial.println(F("Setting long format")); + } + else if( c == FORMAT_SHORT) { + isLongFormat = false; + Serial.println(F("Setting short format")); + } +} + +void processSyncMessage() { + unsigned long pctime; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 - paul, perhaps we define in time.h? + + pctime = Serial.parseInt(); + if( pctime >= DEFAULT_TIME) { // check the integer is a valid time (greater than Jan 1 2013) + setTime(pctime); // Sync Arduino clock to the time received on the serial port + } +} + +time_t requestSync() { + Serial.write(TIME_REQUEST); + return 0; // the time will be sent later in response to serial mesg +} diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeTeensy3/TimeTeensy3.ino b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeTeensy3/TimeTeensy3.ino new file mode 100755 index 0000000..f68dd8c --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/examples/TimeTeensy3/TimeTeensy3.ino @@ -0,0 +1,78 @@ +/* + * TimeRTC.pde + * example code illustrating Time library with Real Time Clock. + * + */ + +#include + +void setup() { + // set the Time library to use Teensy 3.0's RTC to keep time + setSyncProvider(getTeensy3Time); + + Serial.begin(115200); + while (!Serial); // Wait for Arduino Serial Monitor to open + delay(100); + if (timeStatus()!= timeSet) { + Serial.println("Unable to sync with the RTC"); + } else { + Serial.println("RTC has set the system time"); + } +} + +void loop() { + if (Serial.available()) { + time_t t = processSyncMessage(); + if (t != 0) { + Teensy3Clock.set(t); // set the RTC + setTime(t); + } + } + digitalClockDisplay(); + delay(1000); +} + +void digitalClockDisplay() { + // digital clock display of the time + Serial.print(hour()); + printDigits(minute()); + printDigits(second()); + Serial.print(" "); + Serial.print(day()); + Serial.print(" "); + Serial.print(month()); + Serial.print(" "); + Serial.print(year()); + Serial.println(); +} + +time_t getTeensy3Time() +{ + return Teensy3Clock.get(); +} + +/* code to process time sync messages from the serial port */ +#define TIME_HEADER "T" // Header tag for serial time sync message + +unsigned long processSyncMessage() { + unsigned long pctime = 0L; + const unsigned long DEFAULT_TIME = 1357041600; // Jan 1 2013 + + if(Serial.find(TIME_HEADER)) { + pctime = Serial.parseInt(); + return pctime; + if( pctime < DEFAULT_TIME) { // check the value is a valid time (greater than Jan 1 2013) + pctime = 0L; // return 0 to indicate that the time is not valid + } + } + return pctime; +} + +void printDigits(int digits){ + // utility function for digital clock display: prints preceding colon and leading 0 + Serial.print(":"); + if(digits < 10) + Serial.print('0'); + Serial.print(digits); +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/keywords.txt b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/keywords.txt new file mode 100755 index 0000000..073f8f8 --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/keywords.txt @@ -0,0 +1,34 @@ +####################################### +# Syntax Coloring Map For Time +####################################### + +####################################### +# Datatypes (KEYWORD1) +####################################### +time_t KEYWORD1 +####################################### +# Methods and Functions (KEYWORD2) +####################################### +now KEYWORD2 +second KEYWORD2 +minute KEYWORD2 +hour KEYWORD2 +day KEYWORD2 +month KEYWORD2 +year KEYWORD2 +isAM KEYWORD2 +isPM KEYWORD2 +weekday KEYWORD2 +setTime KEYWORD2 +adjustTime KEYWORD2 +setSyncProvider KEYWORD2 +setSyncInterval KEYWORD2 +timeStatus KEYWORD2 +TimeLib KEYWORD2 +####################################### +# Instances (KEYWORD2) +####################################### + +####################################### +# Constants (LITERAL1) +####################################### diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.json b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.json new file mode 100755 index 0000000..071e74c --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.json @@ -0,0 +1,22 @@ +{ +"name": "Time", +"frameworks": "Arduino", +"keywords": "Time, date, hour, minute, second, day, week, month, year, RTC", +"description": "Time keeping library", +"url": "http://playground.arduino.cc/Code/Time", +"authors": +[ +{ + "name": "Michael Margolis" +}, +{ + "name": "Paul Stoffregen" +} +], +"repository": +{ + "type": "git", + "url": "https://github.com/PaulStoffregen/Time" +} +} + diff --git a/Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.properties b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.properties new file mode 100755 index 0000000..49b1e2a --- /dev/null +++ b/Arduino-ESP/PylontechMonitoring/libraries/Time-master/library.properties @@ -0,0 +1,10 @@ +name=Time +version=1.5 +author=Michael Margolis +maintainer=Paul Stoffregen +sentence=Timekeeping functionality for Arduino +paragraph=Date and Time functions, with provisions to synchronize to external time sources like GPS and NTP (Internet). This library is often used together with TimeAlarms and DS1307RTC. +category=Timing +url=http://playground.arduino.cc/code/time +architectures=* + diff --git a/Arduino-ESP/Pylontez-MQTT/CACert.ino b/Arduino-ESP/Pylontez-MQTT/CACert.ino new file mode 100644 index 0000000..2a976ee --- /dev/null +++ b/Arduino-ESP/Pylontez-MQTT/CACert.ino @@ -0,0 +1,103 @@ +// rootca1.cer +const unsigned char caCert[] PROGMEM = { + +0x30, 0x82, 0x04, 0x92, 0x30, 0x82, 0x03, 0x7a, 0xa0, 0x03, 0x02, 0x01, +0x02, 0x02, 0x13, 0x06, 0x7f, 0x94, 0x4a, 0x2a, 0x27, 0xcd, 0xf3, 0xfa, +0xc2, 0xae, 0x2b, 0x01, 0xf9, 0x08, 0xee, 0xb9, 0xc4, 0xc6, 0x30, 0x0d, +0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, +0x00, 0x30, 0x81, 0x98, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, +0x06, 0x13, 0x02, 0x55, 0x53, 0x31, 0x10, 0x30, 0x0e, 0x06, 0x03, 0x55, +0x04, 0x08, 0x13, 0x07, 0x41, 0x72, 0x69, 0x7a, 0x6f, 0x6e, 0x61, 0x31, +0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x07, 0x13, 0x0a, 0x53, 0x63, +0x6f, 0x74, 0x74, 0x73, 0x64, 0x61, 0x6c, 0x65, 0x31, 0x25, 0x30, 0x23, +0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x1c, 0x53, 0x74, 0x61, 0x72, 0x66, +0x69, 0x65, 0x6c, 0x64, 0x20, 0x54, 0x65, 0x63, 0x68, 0x6e, 0x6f, 0x6c, +0x6f, 0x67, 0x69, 0x65, 0x73, 0x2c, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x31, +0x3b, 0x30, 0x39, 0x06, 0x03, 0x55, 0x04, 0x03, 0x13, 0x32, 0x53, 0x74, +0x61, 0x72, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x20, 0x53, 0x65, 0x72, 0x76, +0x69, 0x63, 0x65, 0x73, 0x20, 0x52, 0x6f, 0x6f, 0x74, 0x20, 0x43, 0x65, +0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x20, 0x41, 0x75, +0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x2d, 0x20, 0x47, 0x32, +0x30, 0x1e, 0x17, 0x0d, 0x31, 0x35, 0x30, 0x35, 0x32, 0x35, 0x31, 0x32, +0x30, 0x30, 0x30, 0x30, 0x5a, 0x17, 0x0d, 0x33, 0x37, 0x31, 0x32, 0x33, +0x31, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x30, 0x39, 0x31, 0x0b, +0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x53, 0x31, +0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x13, 0x06, 0x41, 0x6d, +0x61, 0x7a, 0x6f, 0x6e, 0x31, 0x19, 0x30, 0x17, 0x06, 0x03, 0x55, 0x04, +0x03, 0x13, 0x10, 0x41, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x20, 0x52, 0x6f, +0x6f, 0x74, 0x20, 0x43, 0x41, 0x20, 0x31, 0x30, 0x82, 0x01, 0x22, 0x30, +0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01, +0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, 0x02, +0x82, 0x01, 0x01, 0x00, 0xb2, 0x78, 0x80, 0x71, 0xca, 0x78, 0xd5, 0xe3, +0x71, 0xaf, 0x47, 0x80, 0x50, 0x74, 0x7d, 0x6e, 0xd8, 0xd7, 0x88, 0x76, +0xf4, 0x99, 0x68, 0xf7, 0x58, 0x21, 0x60, 0xf9, 0x74, 0x84, 0x01, 0x2f, +0xac, 0x02, 0x2d, 0x86, 0xd3, 0xa0, 0x43, 0x7a, 0x4e, 0xb2, 0xa4, 0xd0, +0x36, 0xba, 0x01, 0xbe, 0x8d, 0xdb, 0x48, 0xc8, 0x07, 0x17, 0x36, 0x4c, +0xf4, 0xee, 0x88, 0x23, 0xc7, 0x3e, 0xeb, 0x37, 0xf5, 0xb5, 0x19, 0xf8, +0x49, 0x68, 0xb0, 0xde, 0xd7, 0xb9, 0x76, 0x38, 0x1d, 0x61, 0x9e, 0xa4, +0xfe, 0x82, 0x36, 0xa5, 0xe5, 0x4a, 0x56, 0xe4, 0x45, 0xe1, 0xf9, 0xfd, +0xb4, 0x16, 0xfa, 0x74, 0xda, 0x9c, 0x9b, 0x35, 0x39, 0x2f, 0xfa, 0xb0, +0x20, 0x50, 0x06, 0x6c, 0x7a, 0xd0, 0x80, 0xb2, 0xa6, 0xf9, 0xaf, 0xec, +0x47, 0x19, 0x8f, 0x50, 0x38, 0x07, 0xdc, 0xa2, 0x87, 0x39, 0x58, 0xf8, +0xba, 0xd5, 0xa9, 0xf9, 0x48, 0x67, 0x30, 0x96, 0xee, 0x94, 0x78, 0x5e, +0x6f, 0x89, 0xa3, 0x51, 0xc0, 0x30, 0x86, 0x66, 0xa1, 0x45, 0x66, 0xba, +0x54, 0xeb, 0xa3, 0xc3, 0x91, 0xf9, 0x48, 0xdc, 0xff, 0xd1, 0xe8, 0x30, +0x2d, 0x7d, 0x2d, 0x74, 0x70, 0x35, 0xd7, 0x88, 0x24, 0xf7, 0x9e, 0xc4, +0x59, 0x6e, 0xbb, 0x73, 0x87, 0x17, 0xf2, 0x32, 0x46, 0x28, 0xb8, 0x43, +0xfa, 0xb7, 0x1d, 0xaa, 0xca, 0xb4, 0xf2, 0x9f, 0x24, 0x0e, 0x2d, 0x4b, +0xf7, 0x71, 0x5c, 0x5e, 0x69, 0xff, 0xea, 0x95, 0x02, 0xcb, 0x38, 0x8a, +0xae, 0x50, 0x38, 0x6f, 0xdb, 0xfb, 0x2d, 0x62, 0x1b, 0xc5, 0xc7, 0x1e, +0x54, 0xe1, 0x77, 0xe0, 0x67, 0xc8, 0x0f, 0x9c, 0x87, 0x23, 0xd6, 0x3f, +0x40, 0x20, 0x7f, 0x20, 0x80, 0xc4, 0x80, 0x4c, 0x3e, 0x3b, 0x24, 0x26, +0x8e, 0x04, 0xae, 0x6c, 0x9a, 0xc8, 0xaa, 0x0d, 0x02, 0x03, 0x01, 0x00, +0x01, 0xa3, 0x82, 0x01, 0x31, 0x30, 0x82, 0x01, 0x2d, 0x30, 0x0f, 0x06, +0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, +0x01, 0xff, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, +0x04, 0x04, 0x03, 0x02, 0x01, 0x86, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, +0x0e, 0x04, 0x16, 0x04, 0x14, 0x84, 0x18, 0xcc, 0x85, 0x34, 0xec, 0xbc, +0x0c, 0x94, 0x94, 0x2e, 0x08, 0x59, 0x9c, 0xc7, 0xb2, 0x10, 0x4e, 0x0a, +0x08, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, +0x80, 0x14, 0x9c, 0x5f, 0x00, 0xdf, 0xaa, 0x01, 0xd7, 0x30, 0x2b, 0x38, +0x88, 0xa2, 0xb8, 0x6d, 0x4a, 0x9c, 0xf2, 0x11, 0x91, 0x83, 0x30, 0x78, +0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x01, 0x01, 0x04, 0x6c, +0x30, 0x6a, 0x30, 0x2e, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, +0x30, 0x01, 0x86, 0x22, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x6f, +0x63, 0x73, 0x70, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x61, +0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, +0x6f, 0x6d, 0x30, 0x38, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, +0x30, 0x02, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x63, +0x72, 0x74, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x61, 0x6d, +0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, 0x2e, 0x63, 0x6f, +0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, 0x63, 0x65, 0x72, +0x30, 0x3d, 0x06, 0x03, 0x55, 0x1d, 0x1f, 0x04, 0x36, 0x30, 0x34, 0x30, +0x32, 0xa0, 0x30, 0xa0, 0x2e, 0x86, 0x2c, 0x68, 0x74, 0x74, 0x70, 0x3a, +0x2f, 0x2f, 0x63, 0x72, 0x6c, 0x2e, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, +0x2e, 0x61, 0x6d, 0x61, 0x7a, 0x6f, 0x6e, 0x74, 0x72, 0x75, 0x73, 0x74, +0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x72, 0x6f, 0x6f, 0x74, 0x67, 0x32, 0x2e, +0x63, 0x72, 0x6c, 0x30, 0x11, 0x06, 0x03, 0x55, 0x1d, 0x20, 0x04, 0x0a, +0x30, 0x08, 0x30, 0x06, 0x06, 0x04, 0x55, 0x1d, 0x20, 0x00, 0x30, 0x0d, +0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, +0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x62, 0x37, 0x42, 0x5c, 0xbc, 0x10, +0xb5, 0x3e, 0x8b, 0x2c, 0xe9, 0x0c, 0x9b, 0x6c, 0x45, 0xe2, 0x07, 0x00, +0x7a, 0xf9, 0xc5, 0x58, 0x0b, 0xb9, 0x08, 0x8c, 0x3e, 0xed, 0xb3, 0x25, +0x3c, 0xb5, 0x6f, 0x50, 0xe4, 0xcd, 0x35, 0x6a, 0xa7, 0x93, 0x34, 0x96, +0x32, 0x21, 0xa9, 0x48, 0x44, 0xab, 0x9c, 0xed, 0x3d, 0xb4, 0xaa, 0x73, +0x6d, 0xe4, 0x7f, 0x16, 0x80, 0x89, 0x6c, 0xcf, 0x28, 0x03, 0x18, 0x83, +0x47, 0x79, 0xa3, 0x10, 0x7e, 0x30, 0x5b, 0xac, 0x3b, 0xb0, 0x60, 0xe0, +0x77, 0xd4, 0x08, 0xa6, 0xe1, 0x1d, 0x7c, 0x5e, 0xc0, 0xbb, 0xf9, 0x9a, +0x7b, 0x22, 0x9d, 0xa7, 0x00, 0x09, 0x7e, 0xac, 0x46, 0x17, 0x83, 0xdc, +0x9c, 0x26, 0x57, 0x99, 0x30, 0x39, 0x62, 0x96, 0x8f, 0xed, 0xda, 0xde, +0xaa, 0xc5, 0xcc, 0x1b, 0x3e, 0xca, 0x43, 0x68, 0x6c, 0x57, 0x16, 0xbc, +0xd5, 0x0e, 0x20, 0x2e, 0xfe, 0xff, 0xc2, 0x6a, 0x5d, 0x2e, 0xa0, 0x4a, +0x6d, 0x14, 0x58, 0x87, 0x94, 0xe6, 0x39, 0x31, 0x5f, 0x7c, 0x73, 0xcb, +0x90, 0x88, 0x6a, 0x84, 0x11, 0x96, 0x27, 0xa6, 0xed, 0xd9, 0x81, 0x46, +0xa6, 0x7e, 0xa3, 0x72, 0x00, 0x0a, 0x52, 0x3e, 0x83, 0x88, 0x07, 0x63, +0x77, 0x89, 0x69, 0x17, 0x0f, 0x39, 0x85, 0xd2, 0xab, 0x08, 0x45, 0x4d, +0xd0, 0x51, 0x3a, 0xfd, 0x5d, 0x5d, 0x37, 0x64, 0x4c, 0x7e, 0x30, 0xb2, +0x55, 0x24, 0x42, 0x9d, 0x36, 0xb0, 0x5d, 0x9c, 0x17, 0x81, 0x61, 0xf1, +0xca, 0xf9, 0x10, 0x02, 0x24, 0xab, 0xeb, 0x0d, 0x74, 0x91, 0x8d, 0x7b, +0x45, 0x29, 0x50, 0x39, 0x88, 0xb2, 0xa6, 0x89, 0x35, 0x25, 0x1e, 0x14, +0x6a, 0x47, 0x23, 0x31, 0x2f, 0x5c, 0x9a, 0xfa, 0xad, 0x9a, 0x0e, 0x62, +0x51, 0xa4, 0x2a, 0xa9, 0xc4, 0xf9, 0x34, 0x9d, 0x21, 0x18}; + +const unsigned int caCertLen = 1174; diff --git a/Arduino-ESP/Pylontez-MQTT/Pylontez-MQTT.ino b/Arduino-ESP/Pylontez-MQTT/Pylontez-MQTT.ino new file mode 100644 index 0000000..1d81110 --- /dev/null +++ b/Arduino-ESP/Pylontez-MQTT/Pylontez-MQTT.ino @@ -0,0 +1,898 @@ +#include +#include +#include +#include +#include +#include //https://github.com/PaulStoffregen/Time +#include +#include +#include +#include + +extern const unsigned char caCert[] PROGMEM; +extern const unsigned int caCertLen; + +//IMPORTANT: Specify your WIFI settings: +//#define WIFI_SSID "NOS-3B26" // setubal +//#define WIFI_PASS "RMKSX2GL" // Setubal +#define WIFI_SSID "MEO-AA9030"// Andre +#define WIFI_PASS "81070ce635" // andre + +//IMPORTANT: Uncomment this line if you want to enable MQTT (and fill correct MQTT_ values below): +//#define ENABLE_MQTT + +#ifdef ENABLE_MQTT +//NOTE 1: if you want to change what is pushed via MQTT - edit function: pushBatteryDataToMqtt. +//NOTE 2: MQTT_TOPIC_ROOT is where battery will push MQTT topics. For example "soc" will be pushed to: "home/grid_battery/soc" +//#define MQTT_SERVER "cc42fcb4f1eb492fa3c2e07a9e617830.s2.eu.hivemq.cloud" //"192.168.1.123" +#define MQTT_SERVER "192.168.1.123" +#define MQTT_PORT 1883 // 8883 +#define MQTT_USER "tezmqtt" +#define MQTT_PASSWORD "mqtTez_123" +#define MQTT_TOPIC_ROOT "home/grid_battery/" //this is where mqtt data will be pushed +#define MQTT_PUSH_FREQ_SEC 2 //maximum mqtt update frequency in seconds +#endif ENABLE_MQTT + + +WiFiClient espClient; +//WiFiClientSecure espClient; +PubSubClient mqttClient(espClient); +IPAddress thisip; +ESP8266WebServer server(80); + +SimpleTimer timer; + +char g_szRecvBuff[7000]; +circular_log<7000> g_log; +bool ntpTimeReceived = false; +int g_baudRate = 0; + +void Log(const char* msg) +{ + g_log.Log(msg); +} + +int LEDPIN = 2; // The on-board Wemos D1 mini LED + +////////////////////////////////////// +void setup() { + Serial.begin(115200); + Serial.println("---"); + Serial.println("Serial started"); + + pinMode(LED_BUILTIN, OUTPUT); + + digitalWrite(LED_BUILTIN, LOW); // LOW = on + delay(2000); + digitalWrite(LED_BUILTIN, HIGH); // HIGH = off + + + + // connect to WiFi + WiFi.mode(WIFI_STA); + WiFi.persistent(false); //our credentials are hardcoded, so we don't need ESP saving those each boot (will save on flash wear) + WiFi.hostname("PylonBattery"); + Serial.println(); + Serial.print("connecting to "); + Serial.println(WIFI_SSID); + + WiFi.begin(WIFI_SSID, WIFI_PASS); + while (WiFi.status() != WL_CONNECTED) { + delay(500); + Serial.print("."); + } + Serial.println(""); + Serial.println("WiFi connected"); + Serial.println("IP address: "); + Serial.println(WiFi.localIP()); + Serial.println(""); + for(int i=1; i<=3; i++){ + LedBlink(); + } + + + ArduinoOTA.setHostname("AndrePylon"); + ArduinoOTA.begin(); + server.on("/", handleRoot); + server.on("/log", handleLog); + server.on("/req", handleReq); + server.on("/jsonOut", handleJsonOut); + server.on("/reboot", [](){ + ESP.restart(); + }); + + server.begin(); + Serial.println("web server started"); + Serial.println(""); + + delay(1000); + + syncTime(); + Serial.println("SNTP time synced"); + Serial.println(""); + + for(int i=1; i<=3; i++){ + LedBlink(); + } + +/* COMMENTED BY TEZ */ +/* +// https://links2004.github.io/Arduino/d2/d2f/class_wi_fi_client_secure.html +// Load root certificate in DER format into WiFiClientSecure object +// bool res = espClient.setCACert_P(caCert, caCertLen); + bool res = espClient.setCertificate(caCert, caCertLen); + if (!res) { + Serial.println("Failed to load root CA certificate!"); + while (true) { + yield(); + } + } + Serial.println("local load root CA certificate OK!"); + + + // VERIFY SSL FINGERPRINT + if (!espClient.verify( "49 7E 82 A3 DB B4 19 1E 73 E5 19 A6 D3 C6 C5 31 DE D9 DE 97 BA B1 D8 19 80 A3 88 96 70 8D 94 3D", "websocketclient.hivemq.cloud") ) { + Serial.println( "Fingerprint certificate NOT verified sorry!" ); +// mqttClient.disconnect(); +// return false; + }else{ + Serial.println( "Fingerprint verified OK!"); + } + +*/ +// END COMMENTED BY TEZ + +#ifdef ENABLE_MQTT + mqttClient.setServer(MQTT_SERVER, MQTT_PORT); +#endif + + Log("Boot event"); +} + + +////////////////////////////////////////////////// +void LedBlink(){ + digitalWrite(LED_BUILTIN, LOW); + delay(150); + digitalWrite(LED_BUILTIN, HIGH); // high = off + delay(150); +} + +////////////////////////////////////////////////// +void handleLog() +{ + server.send(200, "text/html", g_log.c_str()); +} + +////////////////////////////////////////////////// +void switchBaud(int newRate) +{ + if(g_baudRate == newRate) + { + return; + } + + if(g_baudRate != 0) + { + Serial.flush(); + delay(20); + Serial.end(); + delay(20); + } + + char szMsg[50]; + snprintf(szMsg, sizeof(szMsg)-1, "New baud: %d", newRate); + Log(szMsg); + + Serial.begin(newRate); + g_baudRate = newRate; + + delay(20); +} + +////////////////////////////////////////////////// +void waitForSerial() +{ + for(int ix=0; ix<150;ix++) + { + if(Serial.available()) break; + delay(10); + } +} + +////////////////////////////////////////////////// +int readFromSerial() +{ + memset(g_szRecvBuff, 0, sizeof(g_szRecvBuff)); + int recvBuffLen = 0; + bool foundTerminator = true; + + waitForSerial(); + + while(Serial.available()) + { + char szResponse[256] = ""; + const int readNow = Serial.readBytesUntil('>', szResponse, sizeof(szResponse)-1); //all commands terminate with "$$\r\n\rpylon>" (no new line at the end) + if(readNow > 0 && + szResponse[0] != '\0') + { + if(readNow + recvBuffLen + 1 >= (int)(sizeof(g_szRecvBuff))) + { + Log("WARNING: Read too much data on the console!"); + break; + } + + strcat(g_szRecvBuff, szResponse); + recvBuffLen += readNow; + + if(strstr(g_szRecvBuff, "$$\r\n\rpylon")) + { + strcat(g_szRecvBuff, ">"); //readBytesUntil will skip this, so re-add + foundTerminator = true; + break; //found end of the string + } + + if(strstr(g_szRecvBuff, "Press [Enter] to be continued,other key to exit")) + { + //we need to send new line character so battery continues the output + Serial.write("\r"); + } + + waitForSerial(); + } + } + + if(recvBuffLen > 0 ) + { + if(foundTerminator == false) + { + Log("Failed to find pylon> terminator"); + } + } + + return recvBuffLen; +} + +////////////////////////////////////////////////// +bool readFromSerialAndSendResponse() +{ + const int recvBuffLen = readFromSerial(); + if(recvBuffLen > 0) + { + server.sendContent(g_szRecvBuff); + return true; + } + + return false; +} + +////////////////////////////////////////////////// +bool sendCommandAndReadSerialResponse(const char* pszCommand) +{ + switchBaud(115200); + + if(pszCommand[0] != '\0') + { + Serial.write(pszCommand); + } + Serial.write("\n"); + + const int recvBuffLen = readFromSerial(); + if(recvBuffLen > 0) + { + return true; + } + + //wake up console and try again: + wakeUpConsole(); + + if(pszCommand[0] != '\0') + { + Serial.write(pszCommand); + } + Serial.write("\n"); + + return readFromSerial() > 0; +} + +////////////////////////////////////////////////// +void handleReq() +{ + bool respOK; + if(server.hasArg("code") == false) + { + respOK = sendCommandAndReadSerialResponse(""); + } + else + { + respOK = sendCommandAndReadSerialResponse(server.arg("code").c_str()); + } + + if(respOK) + { + server.send(200, "text/plain", g_szRecvBuff); + } + else + { + server.send(500, "text/plain", "????"); + } +} + +////////////////////////////////////////////////// +void handleJsonOut() +{ + if(sendCommandAndReadSerialResponse("pwr") == false) + { + server.send(500, "text/plain", "Failed to get response to 'pwr' command"); + return; + } + + parsePwrResponse(g_szRecvBuff); + prepareJsonOutput(g_szRecvBuff, sizeof(g_szRecvBuff)); + server.send(200, "application/json", g_szRecvBuff); +} + +////////////////////////////////////////////////// +void handleRoot() { + unsigned long days = 0, hours = 0, minutes = 0; + unsigned long val = os_getCurrentTimeSec(); + + days = val / (3600*24); + val -= days * (3600*24); + + hours = val / 3600; + val -= hours * 3600; + + minutes = val / 60; + val -= minutes*60; + + static char szTmp[2500] = ""; + snprintf(szTmp, sizeof(szTmp)-1, "Garage Battery
Time GMT: %d/%02d/%02d %02d:%02d:%02d (%s)
Uptime: %02d:%02d:%02d.%02d

free heap: %u
Wifi RSSI: %d
Wifi SSID: %s", + year(), month(), day(), hour(), minute(), second(), "GMT", + (int)days, (int)hours, (int)minutes, (int)val, + ESP.getFreeHeap(), WiFi.RSSI(), WiFi.SSID().c_str()); + + + strncat(szTmp, "
Runtime log
", sizeof(szTmp)-1); + strncat(szTmp, "
Command:
Power | Help | Event Log | Time", sizeof(szTmp)-1); + strncat(szTmp, "", sizeof(szTmp)-1); + + server.send(200, "text/html", szTmp); +} + +unsigned long os_getCurrentTimeSec() +{ + static unsigned int wrapCnt = 0; + static unsigned long lastVal = 0; + unsigned long currentVal = millis(); + + if(currentVal < lastVal) + { + wrapCnt++; + } + + lastVal = currentVal; + unsigned long seconds = currentVal/1000; + + //millis will wrap each 50 days, as we are interested only in seconds, let's keep the wrap counter + return (wrapCnt*4294967) + seconds; +} + +////////////////////////////////////////////////// +void syncTime() +{ + +// configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); + + //get time from NTP + time_t currentTimeGMT = getNtpTime(); + if(currentTimeGMT) + { + ntpTimeReceived = true; + setTime(currentTimeGMT); + } + else + { + timer.setTimeout(3000, syncTime); //try again in 5 seconds + } + + + struct tm timeinfo; + gmtime_r(¤tTimeGMT, &timeinfo); + Serial.print("Current time: "); + Serial.print(asctime(&timeinfo)); + +Serial.println("------------------"); + + + // Synchronize time using SNTP. This is necessary to verify that + // the TLS certificates offered by the server are currently valid. +// Serial.print("Setting time using SNTP"); +// configTime(8 * 3600, 0, "pool.ntp.org", "time.nist.gov"); +// time_t now = time(nullptr); +// while (now < 8 * 3600 * 2) { +// delay(500); +// Serial.print("."); +// now = time(nullptr); +// } +// +// setTime(now); +// +// Serial.println(""); +// struct tm timeinfo; +// gmtime_r(&now, &timeinfo); +// Serial.print("Current time: "); +// Serial.print(asctime(&timeinfo)); + + +} + +////////////////////////////////////////////////// +void wakeUpConsole() +{ + switchBaud(1200); + + //byte wakeUpBuff[] = {0x7E, 0x32, 0x30, 0x30, 0x31, 0x34, 0x36, 0x38, 0x32, 0x43, 0x30, 0x30, 0x34, 0x38, 0x35, 0x32, 0x30, 0x46, 0x43, 0x43, 0x33, 0x0D}; + //Serial.write(wakeUpBuff, sizeof(wakeUpBuff)); + Serial.write("~20014682C0048520FCC3\r"); + delay(1000); + + byte newLineBuff[] = {0x0E, 0x0A}; + switchBaud(115200); + + for(int ix=0; ix<10; ix++) + { + Serial.write(newLineBuff, sizeof(newLineBuff)); + delay(1000); + + if(Serial.available()) + { + while(Serial.available()) + { + Serial.read(); + } + + break; + } + } +} + +#define MAX_PYLON_BATTERIES 2 + +struct pylonBattery +{ + bool isPresent; + long soc; //Coulomb in % + long voltage; //in mW + long current; //in mA, negative value is discharge + long tempr; //temp of case or BMS? + long cellTempLow; + long cellTempHigh; + long cellVoltLow; + long cellVoltHigh; + char baseState[9]; //Charge | Dischg | Idle + char voltageState[9]; //Normal + char currentState[9]; //Normal + char tempState[9]; //Normal + char time[20]; //2019-06-08 04:00:29 + char b_v_st[9]; //Normal (battery voltage?) + char b_t_st[9]; //Normal (battery temperature?) + + bool isCharging() const { return strcmp(baseState, "Charge") == 0; } + bool isDischarging() const { return strcmp(baseState, "Dischg") == 0; } + bool isIdle() const { return strcmp(baseState, "Idle") == 0; } + bool isBalancing() const { return strcmp(baseState, "Balance") == 0; } + + + bool isNormal() const + { + if(isCharging() == false && + isDischarging() == false && + isIdle() == false && + isBalancing() == false) + { + return false; //base state looks wrong! + } + + return strcmp(voltageState, "Normal") == 0 && + strcmp(currentState, "Normal") == 0 && + strcmp(tempState, "Normal") == 0 && + strcmp(b_v_st, "Normal") == 0 && + strcmp(b_t_st, "Normal") == 0 ; + } +}; + +struct batteryStack +{ + int batteryCount; + int soc; //in %, if charging: average SOC, otherwise: lowest SOC + int temp; //in mC, if highest temp is > 15C, this will show the highest temp, otherwise the lowest + long currentDC; //mAh current going in or out of the battery + long avgVoltage; //in mV + char baseState[9]; //Charge | Dischg | Idle | Balance | Alarm! + + pylonBattery batts[MAX_PYLON_BATTERIES]; + + bool isNormal() const + { + for(int ix=0; ix 1000) + { + return (long)(powerDC*1.06); + } + else if(powerDC > 600) + { + return (long)(powerDC*1.1); + } + else + { + return (long)(powerDC*1.13); + } + } + } +}; + +batteryStack g_stack; + +////////////////////////////////////////////////// +long extractInt(const char* pStr, int pos) +{ + return atol(pStr+pos); +} + +////////////////////////////////////////////////// +void extractStr(const char* pStr, int pos, char* strOut, int strOutSize) +{ + strOut[strOutSize-1] = '\0'; + strncpy(strOut, pStr+pos, strOutSize-1); + strOutSize--; + + + //trim right + while(strOutSize > 0) + { + if(isspace(strOut[strOutSize-1])) + { + strOut[strOutSize-1] = '\0'; + } + else + { + break; + } + + strOutSize--; + } +} + +/* Output has mixed \r and \r\n +pwr + +@ + +Power Volt Curr Tempr Tlow Thigh Vlow Vhigh Base.St Volt.St Curr.St Temp.St Coulomb Time B.V.St B.T.St + +1 49735 -1440 22000 19000 19000 3315 3317 Dischg Normal Normal Normal 93% 2019-06-08 04:00:30 Normal Normal + +.... + +8 - - - - - - - Absent - - - - - - - + +Command completed successfully + +$$ + +pylon +*/ + +////////////////////////////////////////////////// +bool parsePwrResponse(const char* pStr) +{ + if(strstr(pStr, "Command completed successfully") == NULL) + { + return false; + } + + int chargeCnt = 0; + int dischargeCnt = 0; + int idleCnt = 0; + int alarmCnt = 0; + int socAvg = 0; + int socLow = 0; + int tempHigh = 0; + int tempLow = 0; + + memset(&g_stack, 0, sizeof(g_stack)); + + for(int ix=0; ix g_stack.batts[ix].soc){socLow = g_stack.batts[ix].soc;} + if(tempHigh < g_stack.batts[ix].cellTempHigh){tempHigh = g_stack.batts[ix].cellTempHigh;} + if(tempLow > g_stack.batts[ix].cellTempLow){tempLow = g_stack.batts[ix].cellTempLow;} + } + + } + } + + //now update stack state: + g_stack.avgVoltage /= g_stack.batteryCount; + g_stack.soc = socLow; + + if(tempHigh > 15000) //15C + { + g_stack.temp = tempHigh; //in the summer we highlight the warmest cell + } + else + { + g_stack.temp = tempLow; //in the winter we focus on coldest cell + } + + if(alarmCnt > 0) + { + strcpy(g_stack.baseState, "Alarm!"); + } + else if(chargeCnt == g_stack.batteryCount) + { + strcpy(g_stack.baseState, "Charge"); + g_stack.soc = (int)(socAvg / g_stack.batteryCount); + } + else if(dischargeCnt == g_stack.batteryCount) + { + strcpy(g_stack.baseState, "Dischg"); + } + else if(idleCnt == g_stack.batteryCount) + { + strcpy(g_stack.baseState, "Idle"); + } + else + { + strcpy(g_stack.baseState, "Balance"); + } + + + return true; +} + +////////////////////////////////////////////////// +void prepareJsonOutput(char* pBuff, int buffSize) +{ + memset(pBuff, 0, buffSize); + snprintf(pBuff, buffSize-1, "{\"soc\": %d, \"temp\": %d, \"currentDC\": %ld, \"avgVoltage\": %ld, \"baseState\": \"%s\", \"batteryCount\": %d, \"powerDC\": %ld, \"estPowerAC\": %ld, \"isNormal\": %s}", g_stack.soc, + g_stack.temp, g_stack.isNormal() ? "true" : "false"); +} + + +////////////////////////////////////////////////// +void loop() { +#ifdef ENABLE_MQTT + mqttLoop(); +#endif + + ArduinoOTA.handle(); + server.handleClient(); + timer.run(); + + //if there are bytes availbe on serial here - it's unexpected + //when we send a command to battery, we read whole response + //if we get anything here anyways - we will log it + int bytesAv = Serial.available(); + if(bytesAv > 0) + { + if(bytesAv > 63) + { + bytesAv = 63; + } + + char buff[64+4] = "RCV:"; + if(Serial.readBytes(buff+4, bytesAv) > 0) + { + digitalWrite(LED_BUILTIN, LOW); + delay(5); + digitalWrite(LED_BUILTIN, HIGH);//high is off + + Log(buff); + } + } +} + +////////////////////////////////////////////////// +#ifdef ENABLE_MQTT +#define ABS_DIFF(a, b) (a > b ? a-b : b-a) + +////////////////////////////////////////////////// +void mqtt_publish_f(const char* topic, float newValue, float oldValue, float minDiff, bool force) +{ + char szTmp[16] = ""; + snprintf(szTmp, 15, "%.2f", newValue); + if(force || ABS_DIFF(newValue, oldValue) > minDiff) + { + mqttClient.publish(topic, szTmp, false); + } +} + +////////////////////////////////////////////////// +void mqtt_publish_i(const char* topic, int newValue, int oldValue, int minDiff, bool force) +{ + char szTmp[16] = ""; + snprintf(szTmp, 15, "%d", newValue); + if(force || ABS_DIFF(newValue, oldValue) > minDiff) + { + mqttClient.publish(topic, szTmp, false); + } +} + +////////////////////////////////////////////////// +void mqtt_publish_s(const char* topic, const char* newValue, const char* oldValue, bool force) +{ + if(force || strcmp(newValue, oldValue) != 0) + { + mqttClient.publish(topic, newValue, false); + } +} + +////////////////////////////////////////////////// +void pushBatteryDataToMqtt(const batteryStack& lastSentData, bool forceUpdate /* if true - we will send all data regardless if it's the same */) +{ + Serial.println("entered publish"); + mqtt_publish_f(MQTT_TOPIC_ROOT "soc", g_stack.soc, lastSentData.soc, 0, forceUpdate); + mqtt_publish_f(MQTT_TOPIC_ROOT "temp", (float)g_stack.temp/1000.0, (float)lastSentData.temp/1000.0, 0, forceUpdate); + mqtt_publish_i(MQTT_TOPIC_ROOT "estPowerAC", g_stack.getEstPowerAc(), lastSentData.getEstPowerAc(), 10, forceUpdate); + mqtt_publish_i(MQTT_TOPIC_ROOT "battery_count",g_stack.batteryCount, lastSentData.batteryCount, 0, forceUpdate); + mqtt_publish_s(MQTT_TOPIC_ROOT "base_state", g_stack.baseState, lastSentData.baseState , forceUpdate); + mqtt_publish_i(MQTT_TOPIC_ROOT "is_normal", g_stack.isNormal() ? 1:0, lastSentData.isNormal() ? 1:0, 0, forceUpdate); + Serial.println("finished publish"); +} + +////////////////////////////////////////////////// +void mqttLoop() +{ + //if we have problems with connecting to mqtt server, we will attempt to re-estabish connection each 1minute (not more than that) + static unsigned long g_lastConnectionAttempt = 0; + + //first: let's make sure we are connected to mqtt + const char* topicLastWill = MQTT_TOPIC_ROOT "availability"; + + if (!mqttClient.connected() && (g_lastConnectionAttempt == 0 || os_getCurrentTimeSec() - g_lastConnectionAttempt > 60)) { + if(mqttClient.connect("GarageBattery", MQTT_USER, MQTT_PASSWORD, topicLastWill, 1, true, "offline")) + { + Log("Connected to MQTT server: " MQTT_SERVER); + Serial.println("Connected to MQTT server!!!"); + mqttClient.publish(topicLastWill, "online", true); + // test by TeZ + mqttClient.publish("home/grid_battery/tez", "cicciobello", true); + mqttClient.publish("testbytez", "232323", true); + + } + else + { + Log("Failed to connect to MQTT server."); + Serial.println("Failed to connect to MQTT server."); + } + + g_lastConnectionAttempt = os_getCurrentTimeSec(); + } + + + + //next: read data from battery and send via MQTT (but only once per MQTT_PUSH_FREQ_SEC seconds) + static unsigned long g_lastDataSent = 0; + if(mqttClient.connected() && + os_getCurrentTimeSec() - g_lastDataSent > MQTT_PUSH_FREQ_SEC && + sendCommandAndReadSerialResponse("pwr") == true) + { + Serial.println("Connected and sending to MQTT server!!!"); + + static batteryStack lastSentData; //this is the last state we sent to MQTT, used to prevent sending the same data over and over again + static unsigned int callCnt = 0; + + parsePwrResponse(g_szRecvBuff); + + bool forceUpdate = (callCnt % 20 == 0); //push all the data every 20th call + pushBatteryDataToMqtt(lastSentData, forceUpdate); + + callCnt++; + g_lastDataSent = os_getCurrentTimeSec(); + memcpy(&lastSentData, &g_stack, sizeof(batteryStack)); + } + + mqttClient.loop(); +} + +#endif //ENABLE_MQTT