initial files checked in
This commit is contained in:
648
PylontechMonitoring.ino
Normal file
648
PylontechMonitoring.ino
Normal file
@ -0,0 +1,648 @@
|
|||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESP8266mDNS.h>
|
||||||
|
#include <ArduinoOTA.h>
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <ESP8266WebServer.h>
|
||||||
|
#include <SimpleTimer.h>
|
||||||
|
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time
|
||||||
|
#include <ntp_time.h>
|
||||||
|
#include <circular_log.h>
|
||||||
|
|
||||||
|
|
||||||
|
//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, "<html><b>Garage Battery</b><br>Time GMT: %d/%02d/%02d %02d:%02d:%02d (%s)<br>Uptime: %02d:%02d:%02d.%02d<br><br>free heap: %u<br>Wifi RSSI: %d<BR>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, "<BR><a href='/log'>Runtime log</a><HR>", sizeof(szTmp)-1);
|
||||||
|
strncat(szTmp, "<form action='/req' method='get'>Command:<input type='text' name='code'/><input type='submit'></form><a href='/req?code=pwr'>Power</a> | <a href='/req?code=help'>Help</a> | <a href='/req?code=log'>Event Log</a> | <a href='/req?code=time'>Time</a>", sizeof(szTmp)-1);
|
||||||
|
strncat(szTmp, "</html>", 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<MAX_PYLON_BATTERIES; ix++)
|
||||||
|
{
|
||||||
|
if(batts[ix].isPresent &&
|
||||||
|
batts[ix].isNormal() == false)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
//in wH
|
||||||
|
long getPowerDC() const
|
||||||
|
{
|
||||||
|
return (long)(((double)currentDC/1000.0)*((double)avgVoltage/1000.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
//wH estimated current on AC side (taking into account Sofar ME3000SP losses)
|
||||||
|
long getEstPowerAc() const
|
||||||
|
{
|
||||||
|
double powerDC = (double)getPowerDC();
|
||||||
|
if(powerDC == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
else if(powerDC < 0)
|
||||||
|
{
|
||||||
|
//we are discharging, on AC side we will see less power due to losses
|
||||||
|
if(powerDC < -1000)
|
||||||
|
{
|
||||||
|
return (long)(powerDC*0.94);
|
||||||
|
}
|
||||||
|
else if(powerDC < -600)
|
||||||
|
{
|
||||||
|
return (long)(powerDC*0.90);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return (long)(powerDC*0.87);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//we are charging, on AC side we will have more power due to losses
|
||||||
|
if(powerDC > 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<MAX_PYLON_BATTERIES; ix++)
|
||||||
|
{
|
||||||
|
char szToFind[32] = "";
|
||||||
|
snprintf(szToFind, sizeof(szToFind)-1, "\r\r\n%d ", ix+1);
|
||||||
|
|
||||||
|
const char* pLineStart = strstr(pStr, szToFind);
|
||||||
|
if(pLineStart == NULL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pLineStart += 3; //move past \r\r\n
|
||||||
|
|
||||||
|
extractStr(pLineStart, 55, g_stack.batts[ix].baseState, sizeof(g_stack.batts[ix].baseState));
|
||||||
|
if(strcmp(g_stack.batts[ix].baseState, "Absent") == 0)
|
||||||
|
{
|
||||||
|
g_stack.batts[ix].isPresent = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
g_stack.batts[ix].isPresent = true;
|
||||||
|
extractStr(pLineStart, 64, g_stack.batts[ix].voltageState, sizeof(g_stack.batts[ix].voltageState));
|
||||||
|
extractStr(pLineStart, 73, g_stack.batts[ix].currentState, sizeof(g_stack.batts[ix].currentState));
|
||||||
|
extractStr(pLineStart, 82, g_stack.batts[ix].tempState, sizeof(g_stack.batts[ix].tempState));
|
||||||
|
extractStr(pLineStart, 100, g_stack.batts[ix].time, sizeof(g_stack.batts[ix].time));
|
||||||
|
extractStr(pLineStart, 121, g_stack.batts[ix].b_v_st, sizeof(g_stack.batts[ix].b_v_st));
|
||||||
|
extractStr(pLineStart, 130, g_stack.batts[ix].b_t_st, sizeof(g_stack.batts[ix].b_t_st));
|
||||||
|
g_stack.batts[ix].voltage = extractInt(pLineStart, 6);
|
||||||
|
g_stack.batts[ix].current = extractInt(pLineStart, 13);
|
||||||
|
g_stack.batts[ix].tempr = extractInt(pLineStart, 20);
|
||||||
|
g_stack.batts[ix].cellTempLow = extractInt(pLineStart, 27);
|
||||||
|
g_stack.batts[ix].cellTempHigh = extractInt(pLineStart, 34);
|
||||||
|
g_stack.batts[ix].cellVoltLow = extractInt(pLineStart, 41);
|
||||||
|
g_stack.batts[ix].cellVoltHigh = extractInt(pLineStart, 48);
|
||||||
|
g_stack.batts[ix].soc = extractInt(pLineStart, 91);
|
||||||
|
|
||||||
|
//////////////////////////////// Post-process ////////////////////////
|
||||||
|
g_stack.batteryCount++;
|
||||||
|
g_stack.currentDC += g_stack.batts[ix].current;
|
||||||
|
g_stack.avgVoltage += g_stack.batts[ix].voltage;
|
||||||
|
socAvg += g_stack.batts[ix].soc;
|
||||||
|
|
||||||
|
if(g_stack.batts[ix].isNormal() == false){ alarmCnt++; }
|
||||||
|
else if(g_stack.batts[ix].isCharging()){chargeCnt++;}
|
||||||
|
else if(g_stack.batts[ix].isDischarging()){dischargeCnt++;}
|
||||||
|
else if(g_stack.batts[ix].isIdle()){idleCnt++;}
|
||||||
|
else{ alarmCnt++; } //should not really happen!
|
||||||
|
|
||||||
|
if(g_stack.batteryCount == 1)
|
||||||
|
{
|
||||||
|
socLow = g_stack.batts[ix].soc;
|
||||||
|
tempLow = g_stack.batts[ix].cellTempLow;
|
||||||
|
tempHigh = g_stack.batts[ix].cellTempHigh;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if(socLow > 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
113
libraries/Misc/circular_buffer.h
Normal file
113
libraries/Misc/circular_buffer.h
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
#ifndef circ_buffer_h
|
||||||
|
#define circ_buffer_h
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This class allows a fixed size circular buffer.
|
||||||
|
/// When push_back is called, oldest data is overwritten.
|
||||||
|
/// Does not use any dynamic allocators.
|
||||||
|
/// </summary>
|
||||||
|
template <class ItemType, int elementCnt> 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<ItemType, elementCnt>& rhs);
|
||||||
|
public:
|
||||||
|
circular_buffer()
|
||||||
|
{
|
||||||
|
clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(const circular_buffer<ItemType, elementCnt>& 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<size()-1; ix++)
|
||||||
|
{
|
||||||
|
if(at(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<size(); i++)
|
||||||
|
{
|
||||||
|
printf("%d = %d\n", i, at(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //circ_buffer_h
|
||||||
96
libraries/Misc/circular_log.h
Normal file
96
libraries/Misc/circular_log.h
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#ifndef circ_log_h
|
||||||
|
#define circ_log_h
|
||||||
|
|
||||||
|
#include <TimeLib.h> //https://github.com/PaulStoffregen/Time
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
template <int elementCnt> class circular_log
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
char m_log[elementCnt];
|
||||||
|
|
||||||
|
bool removeLastFromLog()
|
||||||
|
{
|
||||||
|
char* nextLine = strstr(m_log+1, "<BR>");
|
||||||
|
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, "<BR>%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, "<BR>%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
|
||||||
81
libraries/NtpTime/ntp_time.h
Normal file
81
libraries/NtpTime/ntp_time.h
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
#ifndef ntp_time_h
|
||||||
|
#define ntp_time_h
|
||||||
|
|
||||||
|
#include <WiFiUdp.h>
|
||||||
|
|
||||||
|
// 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
|
||||||
2
libraries/SimpleTimer/README
Normal file
2
libraries/SimpleTimer/README
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Visit this page for more information:
|
||||||
|
http://playground.arduino.cc/Code/SimpleTimer
|
||||||
250
libraries/SimpleTimer/SimpleTimer.cpp
Normal file
250
libraries/SimpleTimer/SimpleTimer.cpp
Normal file
@ -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;
|
||||||
|
}
|
||||||
126
libraries/SimpleTimer/SimpleTimer.h
Normal file
126
libraries/SimpleTimer/SimpleTimer.h
Normal file
@ -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 <Arduino.h>
|
||||||
|
#else
|
||||||
|
#include <WProgram.h>
|
||||||
|
#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
|
||||||
Reference in New Issue
Block a user