From af3142174e390ae52f226e05d69a81f5067657e7 Mon Sep 17 00:00:00 2001 From: irekzielinski Date: Mon, 26 Aug 2019 12:25:39 +0100 Subject: [PATCH] initial files checked in --- PylontechMonitoring.ino | 648 ++++++++++++++++++++++++++ libraries/Misc/circular_buffer.h | 113 +++++ libraries/Misc/circular_log.h | 96 ++++ libraries/NtpTime/ntp_time.h | 81 ++++ libraries/SimpleTimer/README | 2 + libraries/SimpleTimer/SimpleTimer.cpp | 250 ++++++++++ libraries/SimpleTimer/SimpleTimer.h | 126 +++++ 7 files changed, 1316 insertions(+) create mode 100644 PylontechMonitoring.ino create mode 100644 libraries/Misc/circular_buffer.h create mode 100644 libraries/Misc/circular_log.h create mode 100644 libraries/NtpTime/ntp_time.h create mode 100644 libraries/SimpleTimer/README create mode 100644 libraries/SimpleTimer/SimpleTimer.cpp create mode 100644 libraries/SimpleTimer/SimpleTimer.h diff --git a/PylontechMonitoring.ino b/PylontechMonitoring.ino new file mode 100644 index 0000000..ad3fc2b --- /dev/null +++ b/PylontechMonitoring.ino @@ -0,0 +1,648 @@ +#include +#include +#include +#include +#include +#include +#include //https://github.com/PaulStoffregen/Time +#include +#include + + +//Specify your WIFI settings: +#define WIFI_SSID "--YOUR SSID HERE --" +#define WIFI_PASS "-- YOUR PASSWORD HERE --" + +char g_szRecvBuff[7000]; + +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 setup() { + pinMode(LED_BUILTIN, OUTPUT); + digitalWrite(LED_BUILTIN, HIGH);//high is off + + // put your setup code here, to run once: + 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("PylontechBattery"); + WiFi.begin(WIFI_SSID, WIFI_PASS); + + for(int ix=0; ix<10; ix++) + { + if(WiFi.status() == WL_CONNECTED) + { + break; + } + + delay(1000); + } + + ArduinoOTA.setHostname("GarageBattery"); + 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(); + + 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 isNormal() const + { + if(isCharging() == false && + isDischarging() == false && + isIdle() == 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 | Ballance | 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, "Ballance"); + } + + + 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() { + 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); + } + } +} diff --git a/libraries/Misc/circular_buffer.h b/libraries/Misc/circular_buffer.h new file mode 100644 index 0000000..7440a7c --- /dev/null +++ b/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/libraries/NtpTime/ntp_time.h b/libraries/NtpTime/ntp_time.h new file mode 100644 index 0000000..5d01fed --- /dev/null +++ b/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/libraries/SimpleTimer/README b/libraries/SimpleTimer/README new file mode 100644 index 0000000..c224b02 --- /dev/null +++ b/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/libraries/SimpleTimer/SimpleTimer.cpp b/libraries/SimpleTimer/SimpleTimer.cpp new file mode 100644 index 0000000..274606c --- /dev/null +++ b/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/libraries/SimpleTimer/SimpleTimer.h b/libraries/SimpleTimer/SimpleTimer.h new file mode 100644 index 0000000..9cdb395 --- /dev/null +++ b/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