Since everyone and their brother that knows how to use an Arduino seems to have done a project to monitor their household power consumption, I figured that I would do one too.
My situation is slightly different than some, as a photovoltaic solar installation from a previous owner had already provided current taps and a LCD rate meter near the power inverters. That interface provided pulse frequency modulated signals for both power generated and consumed. I built a little circuit to buffer and level shift the differential signals and pump them into the Arduino interrupt lines.
As for the software stack, I did a not too dissimilar implementation from what others seem to have done, with the following design choices
1) I used the ethernet shield instead of WiFi for reliability and because I'd like to migrate the solution to be powered over POE over time
2) I used the recently contributed time utility library to have the Arduino connect to an NTP server to set its clock correctly and keep itself in synch with the rest of the world.
3) I collect data once per second, and then do a rolling archive of the last x seconds, the last y minutes and the last z hours of data in the Arduino RAM in case something happens to the client pulling the data. I then implemented a function to dump both historical and real time data to the client via HTTP. Different HTTP query strings will return different types (hours, minutes, seconds) of samples.
4) I implemented delta pulse code modulation to compress the storage requirement in RAM on the Arduino. I needed more than 8 bits of resolution, but really only wanted to use 8 bits per sample. The DPCM algorithm implemented works very nicely for low frequency signals. Basically you get to within 64 Watts of accuracy in 1 second, and to within 1 Watt by the second second.
5) Like others that have done similar projects, I have a client program that polls the Arduino and logs the data in a database as well as doing graphing. Since I had a goal of learning Cocoa, as well, I wrote that client for the Mac, using an SQLLite Data Store under a CoreData framework, and implemented historical and realtime graphs as well.
For those who are interested, the Arduino source code is below. By defining "DPCM", delta pulse code modulation is enabled, which approximately doubles the amount of history that can be stored on the Arduino's onboard RAM. Sorry that it's a little ugly.
The Mac OS/X source code is even more disheveled. It may be posted once it embarrasses me a little less.
#include <UdpBytewise.h>
#include <Time.h>
//*******************************
#include <WString.h>
#include <SPI.h>
#include <Ethernet.h>
#define SCALEFACTORA 1280000
#define SCALEFACTORB 640000
#define RAMSIZE 1024 //must be 960 or larger
#define COUNTSIZE (RAMSIZE/8) //use half the ram, for 2 arrays of 2 byte ints
#define SECONDSIZE 64//(COUNTSIZE/2)
#define MINUTESIZE 64
#define HOURSIZE 32//((COUNTSIZE/2)-MINUTESIZE)
#define MAXCMDLEN 16
#define DEBOUNCEA (SCALEFACTORA/10000) // assume max power consumption of 10K watts
#define DEBOUNCEB (SCALEFACTORB/5000) // assume max power generation of 5K watts
#define HomePin 2
#define SolarPin 3
#define ledPin 4 // LED pin
#define MIN(a,b) ((a<b) ? a:b)
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = {
192, 168, 168, 110 }; // ip in lan
byte gateway[] = {
192, 168, 168, 168 }; // internet access via router
byte subnet[] = {
255, 255, 255, 0 }; //subnet mask
byte SNTP_server_IP[] = {
192, 43, 244, 18}; // time.nist.gov
//byte SNTP_server_IP[] = { 130,149,17,21}; // ntps1-0.cs.tu-berlin.de
//byte SNTP_server_IP[] = { 192,53,103,108}; // ptbtime1.ptb.de
const long timeZoneOffset = 0L; // set this to the offset in seconds to your local time;
Server server(80); //server port
String readString = String(MAXCMDLEN); //string for fetching data from address
boolean LEDON = false; //LED status flag
volatile long channelAval = 0;
volatile long channelBval = 0;
volatile unsigned long lastmillisA =0;
volatile unsigned long lastmillisB =0;
unsigned long myMillis = 0;
unsigned long lastMyMillis=0;
unsigned long safeAval;
unsigned long safeBval;
#define USEBYTES 1
#define DPCM 1
#ifdef USEBYTES
unsigned char aSecArray[SECONDSIZE];
unsigned char bSecArray[SECONDSIZE];
unsigned char aMinArray[MINUTESIZE];
unsigned char bMinArray[MINUTESIZE];
unsigned char aHourArray[HOURSIZE];
unsigned char bHourArray[HOURSIZE];
#if DPCM
#define PRINTSCALE 1
unsigned int startSecsBaseA=0;
unsigned int startSecsBaseB=0;
unsigned int startMinsBaseA=0;
unsigned int startMinsBaseB=0;
unsigned int startHoursBaseA=0;
unsigned int startHoursBaseB=0;
#else
#define PRINTSCALE 32
#endif
#else
unsigned int aSecArray[SECONDSIZE];
unsigned int bSecArray[SECONDSIZE];
unsigned int aMinArray[MINUTESIZE];
unsigned int bMinArray[MINUTESIZE];
unsigned int aHourArray[HOURSIZE];
unsigned int bHourArray[HOURSIZE];
#define PRINTSCALE 1
#endif
unsigned int writecursor = 0;
unsigned int readcursor =0;
unsigned int secondCount=0;
unsigned int minuteCount=0;
unsigned int hourCount=0;
unsigned int secondSamples=0;
unsigned int minuteSamples=0;
unsigned int hourSamples=0;
time_t curSampleTime = 0;
boolean closeConnection=false;
volatile int ic=0;
void myInterruptHandlerA()
{
unsigned long nowsnap = millis();
ic++;
if (lastmillisA==0) {
lastmillisA=nowsnap;
channelAval=0;
ic++;
return;
}
if ((nowsnap-lastmillisA)>DEBOUNCEA)
{
channelAval= nowsnap - lastmillisA;
lastmillisA=nowsnap;
ic++;
}
}
void myInterruptHandlerB()
{
unsigned long nowsnap = millis();
if (lastmillisB==0) {
lastmillisB=nowsnap;
channelBval=0;
ic++;
return;
}
if ((nowsnap-lastmillisB)>DEBOUNCEB)
{
channelBval= nowsnap - lastmillisB;
lastmillisB=nowsnap;
ic++;
}
}
//coding is as follows
// bit 7: 0 if absolute sample, or 1 if a delta
// bit 0-6: Two's complement of the delta if delta coded (-64 to +63)
// bit 0-6: value of sample divided by 64
unsigned char encodeSample( int base,unsigned int sample)
{
int delta = sample-base;
byte retval;
if (base == -1)
{
retval = (delta) | 0b10000000;
return((unsigned char) retval);
}
if ((delta > 63) || (delta < -64))
{
retval = (sample >> 6) & 0b01111111;
}
else
{
retval = (delta) | 0b10000000;
}
// Serial.print("Encoded ");
// Serial.print(base);
// Serial.print(" and ");
// Serial.print(sample);
// Serial.print(" and got ");
// Serial.print((int) retval);
// Serial.print(" which decodes as ");
// Serial.print(decodeSample(base,retval));
// Serial.print("\n");
return((unsigned char) retval);
}
unsigned int decodeSample(unsigned int base, unsigned char code)
{
unsigned int retval;
if (code & 0b10000000) //dealing with a delta
{
retval = base + (char) ((code & 0b01111111) | ((code & 0b01000000) << 1)); //extends the sign byte
}
else
{
retval = code << 6;
}
return(retval);
}
void cleanSeconds()
{
int i;
unsigned long aAcc= 0;
unsigned long bAcc =0;
unsigned long aTot=0;
unsigned long bTot=0;
unsigned int xVal=0;
unsigned int yVal=0;
unsigned int zVal=0;
unsigned long aLow,bLow,aHigh,bHigh;
int j;
#if DPCM
aAcc = startSecsBaseA;
bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>=0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
aTot +=aAcc;
bTot +=bAcc;
#else
aTot += aSecArray[i];
bTot += bSecArray[i];
#endif
}
aLow = aTot/(4*SECONDSIZE);
aHigh =aTot/(SECONDSIZE/4);
bLow = bTot/(4*SECONDSIZE);
bHigh = bTot/(SECONDSIZE/4);
#if DPCM
aAcc = startSecsBaseA;
bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>=0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
if ((aAcc > aHigh) || (aAcc <aLow))
{
aSecArray[i] = encodeSample(-1, aTot/SECONDSIZE); //set to an absolute sample
xVal = aAcc;
yVal=decodeSample(0,aSecArray[i]); //note that the zero doesn't matter because we have an 'absolute sample
for (j=i-1;j>=0;j--)
{
zVal=decodeSample(xVal,aSecArray[j]);
aSecArray[j] = encodeSample(yVal,zVal);
yVal= decodeSample(yVal,aSecArray[j]);
xVal=zVal;
}
}
if ((bAcc > bHigh) || (bAcc < bLow))
{
bSecArray[i] = encodeSample(-1, bTot/SECONDSIZE); //set to an absolute sample
xVal = bAcc;
yVal=decodeSample(0,bSecArray[i]); //note that the zero doesn't matter because we have an 'absolute sample
for (j=i-1;j>=0;j--)
{
zVal=decodeSample(xVal,bSecArray[j]);
bSecArray[j] = encodeSample(yVal,zVal);
yVal= decodeSample(yVal,bSecArray[j]);
xVal=zVal;
}
}
#else
if (aSecArray[i] > aHigh || aSecArray[i] <aLow) {
aSecArray[i] = aTot/SECONDSIZE;
}
if (bSecArray[i] > bHigh || bSecArray[i] <bLow) {
bSecArray[i] = bTot/SECONDSIZE;
}
#endif
}
}
void printTwoDigits(Client C, byte digits)
{
if(digits < 10)
C.print('0');
C.print(digits,DEC);
}
void printOffsetTime(Client client, time_t basetime,long int offset)
{
client.print(year(basetime));
client.print("-");
printTwoDigits(client,month(basetime+offset));
client.print("-");
printTwoDigits(client,day(basetime+offset));
client.print(" ");
printTwoDigits(client,hour(basetime+offset));
client.print(":");
printTwoDigits(client,minute(basetime+offset));
client.print(":");
printTwoDigits(client,second(basetime+offset));
}
void printHeader(Client client)
{
// now output HTML data starting with standart header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
//set background color
client.print("<body style=background-color:white>");
//send first heading
client.println("<font color='black'><h1>Power Used and Produced</font></h1>");
client.println("<hr />");
client.println("<hr />");
}
void printSeconds(Client client)
{
int i;
//output seconds data to browser
client.print("<font color='blue' size='5'> Last ");
client.print(SECONDSIZE);
client.println(" Seconds");
client.println("<br />");//some space between lines
for (i=0;i<SECONDSIZE;i++)
{
client.print(i);
client.print(", ");
client.print(PRINTSCALE*aSecArray[i]);//lets output some data
client.print(", ");
client.print(PRINTSCALE*bSecArray[i]);
client.println("<br />");//some space between lines
}
}
void printBCurrent(Client client)
{
int i;
client.print("1\n");
printOffsetTime(client, curSampleTime,0);
client.print(" 1, ");
#if DPCM
int aAcc = startSecsBaseA;
int bAcc = startSecsBaseB;
for (i=SECONDSIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
}
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aSecArray[0]);
#endif
client.print(", ");
#if DPCM
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bSecArray[0]);
#endif
client.print("\n");
}
void printBHistSecs(Client client)
{
int i;
//output seconds data as text
client.print(MIN(secondSamples,SECONDSIZE));
client.print("\n");
#if DPCM
int aAcc = startSecsBaseA;
int bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
if (i<MIN(secondSamples,SECONDSIZE)) //must do this for hours and mins
{
printOffsetTime(client, curSampleTime,-1*i);
client.print(" 1, ");
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aSecArray[i]);
#endif
client.print(", ");
#if DPCM
bAcc = decodeSample(bAcc,bSecArray[i]);
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bSecArray[0]);
#endif
client.print("\n");
}
else
#if DPCM
{
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
}
#endif
}
}
void printBHistMins(Client client)
{
long int i;
//output seconds data as text
client.print(MIN(minuteSamples,MINUTESIZE));
client.print("\n");
#if DPCM
int aAcc = startMinsBaseA;
int bAcc = startMinsBaseB;
#endif
for (i=MINUTESIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
if (i<MIN(minuteSamples,MINUTESIZE))
{
printOffsetTime(client,curSampleTime,(long) (-second(curSampleTime)-60*i));
client.print(" 60, ");
#if DPCM
aAcc = decodeSample(aAcc,aMinArray[i]);
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aMinArray[i]);
#endif
client.print(", ");
#if DPCM
bAcc = decodeSample(bAcc,bMinArray[i]);
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bMinArray[0]);
#endif
client.print("\n");
}
else
#if DPCM
{
aAcc = decodeSample(aAcc,aMinArray[i]);
bAcc = decodeSample(bAcc,bMinArray[i]);
}
#endif
}
}
void printBHistHours(Client client)
{
long int i;
client.print(MIN(hourSamples,HOURSIZE));
client.print("\n");
#if DPCM
int aAcc = startHoursBaseA;
int bAcc = startHoursBaseB;
#endif
for (i=HOURSIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
if (i<MIN(hourSamples,HOURSIZE))
{
printOffsetTime(client, curSampleTime,(long) (-60*minute(curSampleTime)-second(curSampleTime)-3600*i));
client.print(" 3600, ");
#if DPCM
aAcc = decodeSample(aAcc,aHourArray[i]);
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aHourArray[i]);
#endif
client.print(", ");
#if DPCM
bAcc = decodeSample(bAcc,bHourArray[i]);
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bHourArray[i]);
#endif
client.print("\n");
}
else
#if DPCM
{
aAcc = decodeSample(aAcc,aHourArray[i]);
bAcc = decodeSample(bAcc,bHourArray[i]);
}
#endif
}
}
void printMinutes(Client client)
{
int i;
//output minutes data to browser
client.println("<hr />");
client.println("<hr />");
client.print("<font color='blue' size='5'>Last ");
client.print(MINUTESIZE);
client.println(" Minutes");
client.println("<br />");//some space between lines
for (i=0;i<MINUTESIZE;i++)
{
client.print(i);
client.print(", ");
client.print(PRINTSCALE*aMinArray[i]);//lets output some data
client.print(", ");
client.print(PRINTSCALE*bMinArray[i]);
client.println("<br />");//some space between lines
}
}
void printCheckBox(Client client)
{
//controlling led via checkbox
client.println("<h1>LED control</h1>");
//address will look like http://192.168.168.110/?L=1 when submited
client.println("<form method=get name=LED><input type=checkbox name=L value=1>LED<br><input type=submit value=submit></form>");
client.println("<br />");
}
void printFooter(Client client)
{
client.println("</body></html>\n");
}
void printHours(Client client)
{
int i;
//output hours data to browser
client.println("<hr />");
client.println("<hr />");
client.print("<font color='blue' size='5'>Last ");
client.print(HOURSIZE);
client.println(" Hours");
client.println("<br />");//some space between lines
for (i=0;i<HOURSIZE;i++)
{
client.print(i);
client.print(", ");
client.print(PRINTSCALE*aHourArray[i]);//lets output some data
client.print(", ");
client.print(PRINTSCALE*bHourArray[i]);
client.println("<br />");//some space between lines
}
}
/*-------- NTP code ----------*/
unsigned long getNtpTime()
{
sendNTPpacket(SNTP_server_IP);
delay(1000);
if ( UdpBytewise.available() ) {
for(int i=0; i < 40; i++)
UdpBytewise.read(); // ignore every field except the time
const unsigned long seventy_years = 2208988800UL + timeZoneOffset;
return getUlong() - seventy_years;
}
return 0; // return 0 if unable to get the time
}
unsigned long sendNTPpacket(byte *address)
{
UdpBytewise.begin(123);
UdpBytewise.beginPacket(address, 123);
UdpBytewise.write(B11100011); // LI, Version, Mode
UdpBytewise.write(0); // Stratum
UdpBytewise.write(6); // Polling Interval
UdpBytewise.write(0xEC); // Peer Clock Precision
write_n(0, 8); // Root Delay & Root Dispersion
UdpBytewise.write(49);
UdpBytewise.write(0x4E);
UdpBytewise.write(49);
UdpBytewise.write(52);
write_n(0, 32); //Reference and time stamps
UdpBytewise.endPacket();
}
unsigned long getUlong()
{
unsigned long ulong = (unsigned long)UdpBytewise.read() << 24;
ulong |= (unsigned long)UdpBytewise.read() << 16;
ulong |= (unsigned long)UdpBytewise.read() << 8;
ulong |= (unsigned long)UdpBytewise.read();
return ulong;
}
void write_n(int what, int how_many)
{
for( int i = 0; i < how_many; i++ )
UdpBytewise.write(what);
}
#if 0
void printFreeMem()
{
int i;
char *test;
for (i=500;i>0;i--)
{
test = (char *) malloc(i);
if (test)
{
Serial.print("Free mem is: ");
Serial.print(i);
Serial.print("\n");
free(test);
break;
}
}
}
#endif
/* main loop */
void setup()
{
int i;
int lastic = 0;
int iccount;
char *test;
//Zero Array
for (i=0;i<SECONDSIZE;i++)
{
aSecArray[i]=0;
bSecArray[i]=0;
}
for (i=0;i<MINUTESIZE;i++)
{
aMinArray[i]=0;
bMinArray[i]=0;
}
for (i=0;i<HOURSIZE;i++)
{
aHourArray[i]=0;
bHourArray[i]=0;
}
//start Ethernet
Ethernet.begin(mac, ip, gateway, subnet);
pinMode(HomePin,INPUT);
pinMode(SolarPin,INPUT);
digitalWrite(HomePin,HIGH);
digitalWrite(SolarPin,HIGH);
pinMode(ledPin, OUTPUT);
//enable serial datada print
Serial.begin(115200);
Serial.print("Booted...\n");
setSyncProvider(getNtpTime);
setSyncInterval(3001);
while(timeStatus() == timeNotSet)
{
Serial.print("Synching Clock\n"); // wait until the time is set by the sync provider
}
Serial.print("Time is: ");
Serial.print(now());
Serial.print("\n");
attachInterrupt(0,myInterruptHandlerA,FALLING); //interrupt zero is pin 2
attachInterrupt(1, myInterruptHandlerB,FALLING); //interrupt one is pin 3
iccount = ic;
while (ic < iccount+5)
{ // just wait!
}
}
void parseInput(Client client)
{
//lets check if LED should be lighted
if(readString.indexOf("L=1") != -1)
{
//led has to be turned ON
digitalWrite(ledPin, HIGH); // set the LED on
LEDON = true;
closeConnection =true;
}
else
{
//led has to be turned OFF
digitalWrite(ledPin, LOW); // set the LED OFF
LEDON = false;
closeConnection =true;
}
if (readString.indexOf("BCurrent") != -1)
{
printBCurrent(client);
closeConnection = false;
}
if (readString.indexOf("BHistSecs") != -1)
{
printBHistSecs(client);
closeConnection = false;
}
if (readString.indexOf("BHistMins") != -1)
{
printBHistMins(client);
closeConnection = false;
}
if (readString.indexOf("BHistHrs") != -1)
{
printBHistHours(client);
closeConnection = false;
}
if(readString.indexOf("Seconds") != -1)
{
printHeader(client);
printSeconds(client);
printFooter(client);
closeConnection =true;
}
if(readString.indexOf("Minutes") != -1)
{
printHeader(client);
printMinutes(client);
printFooter(client);
closeConnection =true;
}
if(readString.indexOf("Hours") != -1)
{
printHeader(client);
printHours(client);
printFooter(client);
closeConnection =true;
}
if(readString.indexOf("All") != -1)
{
printHeader(client);
printSeconds(client);
printMinutes(client);
printHours(client);
printCheckBox(client);
printFooter(client);
closeConnection =true;
}
}
void loop()
{
int i;
unsigned int aAcc;
unsigned int bAcc;
unsigned int a2Acc;
unsigned int b2Acc;
unsigned long aTot,bTot;
myMillis = millis();
if (myMillis < 1000+lastMyMillis)
{
//could delay here or do other work
}
else
{
lastMyMillis=lastMyMillis+1000;
secondCount++;
secondSamples++;
secondSamples = MIN(secondSamples,SECONDSIZE);
if (myMillis < 5000+lastmillisA) {
safeAval=channelAval;
}
else {
safeAval=0;
} // no data for 5 seconds means 0
if (myMillis < 5000+lastmillisB) {
safeBval=channelBval;
}
else {
safeBval=0;
} //no data for 5 seconds means 0
if (safeAval && (safeAval < 150)) {
Serial.print("Error Channel A: ");
Serial.print(safeAval);
Serial.print(" \n");
}
if (safeBval && (safeBval < 150)) {
Serial.print("Error Channel B: ");
Serial.print(safeBval);
Serial.print(" \n");
}
curSampleTime =now();
// scrolls the seconds along the array - trading off speed for less code complexity
#if DPCM
startSecsBaseA = decodeSample(startSecsBaseA,aSecArray[SECONDSIZE-1]);
startSecsBaseB = decodeSample(startSecsBaseB,bSecArray[SECONDSIZE-1]);
aAcc = startSecsBaseA;
bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i-1]);
bAcc = decodeSample(bAcc,bSecArray[i-1]);
#endif
aSecArray[i] = aSecArray[i-1];
bSecArray[i]= bSecArray[i-1];
}
#if USEBYTES
#if DPCM
if (safeAval) {
aSecArray[0]=encodeSample(aAcc,SCALEFACTORA/(PRINTSCALE*safeAval));
}
else aSecArray[0]=encodeSample(aAcc,0);
if (safeBval) {
bSecArray[0]=encodeSample(bAcc,SCALEFACTORB/(PRINTSCALE*safeBval));
}
else bSecArray[0]=encodeSample(bAcc,0);
#else
if (safeAval) {
aSecArray[0]=SCALEFACTORA/(PRINTSCALE*safeAval);
}
else aSecArray[0]=0;
if (safeBval) {
bSecArray[0]=SCALEFACTORB/(PRINTSCALE*safeBval);
}
else bSecArray[0]=0;
#endif
#else
if (safeAval) {
aSecArray[0]=SCALEFACTORA/safeAval;
}
else aSecArray[0]=0;
if (safeBval) {
bSecArray[0]=SCALEFACTORB/safeBval;
}
else bSecArray[0]=0;
#endif
//This section handles minutes
// every minute, average the last 60 seconds, and scroll the minutes along
if (second(curSampleTime) == 0) // do this at the top of every minute
{
//first clean out any egregious data points
// cleanSeconds();
//scroll the minutes
#if DPCM
startMinsBaseA = decodeSample(startMinsBaseA,aMinArray[MINUTESIZE-1]);
startMinsBaseB = decodeSample(startMinsBaseB,bMinArray[MINUTESIZE-1]);
a2Acc = startMinsBaseA;
b2Acc = startMinsBaseB;
#endif
for (i=MINUTESIZE-1;i>0;i--)
{
#if DPCM
a2Acc = decodeSample(a2Acc,aMinArray[i-1]);
b2Acc = decodeSample(b2Acc,bMinArray[i-1]);
#endif
aMinArray[i]= aMinArray[i-1];
bMinArray[i]= bMinArray[i-1];
}
// Do the averaging
//although this second pass seems redundant with "cleanSeconds", it actually helps to have the data cleaned in the first pass and the
//re-averaged.
//We could do this with more code complexity in a single pass
aTot=0;
bTot=0;
#if DPCM
aAcc=startSecsBaseA;
bAcc=startSecsBaseB;
#endif
for (i=SECONDSIZE-1; i>=0; i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
#endif
if (i<MIN(60,secondCount))
{
#if DPCM
aTot = aTot+aAcc;
bTot = bTot+bAcc;
#else
aTot = aTot+aSecArray[i];
bTot = bTot+bSecArray[i];
#endif
}
}
#ifdef DPCM
aMinArray[0]= encodeSample(a2Acc,(aTot/MIN(60,secondCount))); // add dpcm here
bMinArray[0]= encodeSample(b2Acc,(bTot/MIN(60,secondCount)));
#else
aMinArray[0]= (unsigned int) (aTot/MIN(60,secondCount));
bMinArray[0]= (unsigned int) (bTot/MIN(60,secondCount));
#endif
minuteCount++;
minuteSamples++;
minuteSamples = MIN(minuteSamples,MINUTESIZE);
secondCount=0;
// Serial.print("Done with minutes\n");
} //closes the once a minute loop
//This section handles hours
// every hour, average the last 60 minutes, and scroll the hours along
if ((minute(curSampleTime) == 0) && (second(curSampleTime) == 0))
{
#if DPCM
startHoursBaseA = decodeSample(startHoursBaseA,aHourArray[SECONDSIZE-1]);
startHoursBaseB = decodeSample(startHoursBaseB,bHourArray[SECONDSIZE-1]);
a2Acc = startHoursBaseA;
b2Acc = startHoursBaseB;
#endif
// do the scrolling
for (i=HOURSIZE-1;i>0;i--)
{
#if DPCM
a2Acc = decodeSample(a2Acc,aHourArray[i-1]);
b2Acc = decodeSample(b2Acc,bHourArray[i-1]);
#endif
aHourArray[i] = aHourArray[i-1];
bHourArray[i] = bHourArray[i-1];
}
aTot=0;
bTot=0;
#if DPCM
aAcc=startMinsBaseA;
bAcc=startMinsBaseB;
#endif
//find the average
for (i=MINUTESIZE-1;i>=0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aMinArray[i]);
bAcc = decodeSample(bAcc,bMinArray[i]);
#endif
if (i<MIN(60,minuteCount))
{
#if DPCM
aTot += aAcc;
bTot += bAcc;
#else
aTot +=aMinArray[i];
bTot +=bMinArray[i];
#endif
}
}
#if DPCM
aHourArray[0]= encodeSample(a2Acc,(aTot/MIN(60,minuteCount)));
bHourArray[0]= encodeSample(b2Acc,(bTot/MIN(60,minuteCount)));
#else
aHourArray[0]= (unsigned int) (aAcc/MIN(60,minuteCount));
bHourArray[0]= (unsigned int) (bAcc/MIN(60,minuteCount));
#endif
minuteCount=0;
hourCount++;
hourSamples++;
hourSamples = MIN(hourSamples,HOURSIZE);
}//closes the "once per hour loop
}//closes the "once per second loop"
// Create a client connection
Client client = server.available();
readString = "";
if (client) {
//Serial.print("Client connecting\n");
while (client.connected())
{
if (client.available())
{
char c = client.read();
//read char by char HTTP request
if (readString.length() < MAXCMDLEN)
{
//store characters to string
readString +=c;
}
//output chars to serial port
Serial.print(c);
//if HTTP request has ended
if (c == '\n')
{
if(readString.indexOf("GET") != -1)
{
Serial.print(readString);
//printFreeMem();
parseInput(client);
//if (closeConnection) client.stop();
client.stop();
}
//clearing string for next read
readString="";
} //closes the branch where we've found a new line
} //closes the if client available
} // closes while client connected
if (client) client.stop();
} //closes if client
} //closes loop()
//*******************************
The Mac OS/X source code is even more disheveled. It may be posted once it embarrasses me a little less.
#include <UdpBytewise.h>
#include <Time.h>
//*******************************
#include <WString.h>
#include <SPI.h>
#include <Ethernet.h>
#define SCALEFACTORA 1280000
#define SCALEFACTORB 640000
#define RAMSIZE 1024 //must be 960 or larger
#define COUNTSIZE (RAMSIZE/8) //use half the ram, for 2 arrays of 2 byte ints
#define SECONDSIZE 64//(COUNTSIZE/2)
#define MINUTESIZE 64
#define HOURSIZE 32//((COUNTSIZE/2)-MINUTESIZE)
#define MAXCMDLEN 16
#define DEBOUNCEA (SCALEFACTORA/10000) // assume max power consumption of 10K watts
#define DEBOUNCEB (SCALEFACTORB/5000) // assume max power generation of 5K watts
#define HomePin 2
#define SolarPin 3
#define ledPin 4 // LED pin
#define MIN(a,b) ((a<b) ? a:b)
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED }; //physical mac address
byte ip[] = {
192, 168, 168, 110 }; // ip in lan
byte gateway[] = {
192, 168, 168, 168 }; // internet access via router
byte subnet[] = {
255, 255, 255, 0 }; //subnet mask
byte SNTP_server_IP[] = {
192, 43, 244, 18}; // time.nist.gov
//byte SNTP_server_IP[] = { 130,149,17,21}; // ntps1-0.cs.tu-berlin.de
//byte SNTP_server_IP[] = { 192,53,103,108}; // ptbtime1.ptb.de
const long timeZoneOffset = 0L; // set this to the offset in seconds to your local time;
Server server(80); //server port
String readString = String(MAXCMDLEN); //string for fetching data from address
boolean LEDON = false; //LED status flag
volatile long channelAval = 0;
volatile long channelBval = 0;
volatile unsigned long lastmillisA =0;
volatile unsigned long lastmillisB =0;
unsigned long myMillis = 0;
unsigned long lastMyMillis=0;
unsigned long safeAval;
unsigned long safeBval;
#define USEBYTES 1
#define DPCM 1
#ifdef USEBYTES
unsigned char aSecArray[SECONDSIZE];
unsigned char bSecArray[SECONDSIZE];
unsigned char aMinArray[MINUTESIZE];
unsigned char bMinArray[MINUTESIZE];
unsigned char aHourArray[HOURSIZE];
unsigned char bHourArray[HOURSIZE];
#if DPCM
#define PRINTSCALE 1
unsigned int startSecsBaseA=0;
unsigned int startSecsBaseB=0;
unsigned int startMinsBaseA=0;
unsigned int startMinsBaseB=0;
unsigned int startHoursBaseA=0;
unsigned int startHoursBaseB=0;
#else
#define PRINTSCALE 32
#endif
#else
unsigned int aSecArray[SECONDSIZE];
unsigned int bSecArray[SECONDSIZE];
unsigned int aMinArray[MINUTESIZE];
unsigned int bMinArray[MINUTESIZE];
unsigned int aHourArray[HOURSIZE];
unsigned int bHourArray[HOURSIZE];
#define PRINTSCALE 1
#endif
unsigned int writecursor = 0;
unsigned int readcursor =0;
unsigned int secondCount=0;
unsigned int minuteCount=0;
unsigned int hourCount=0;
unsigned int secondSamples=0;
unsigned int minuteSamples=0;
unsigned int hourSamples=0;
time_t curSampleTime = 0;
boolean closeConnection=false;
volatile int ic=0;
void myInterruptHandlerA()
{
unsigned long nowsnap = millis();
ic++;
if (lastmillisA==0) {
lastmillisA=nowsnap;
channelAval=0;
ic++;
return;
}
if ((nowsnap-lastmillisA)>DEBOUNCEA)
{
channelAval= nowsnap - lastmillisA;
lastmillisA=nowsnap;
ic++;
}
}
void myInterruptHandlerB()
{
unsigned long nowsnap = millis();
if (lastmillisB==0) {
lastmillisB=nowsnap;
channelBval=0;
ic++;
return;
}
if ((nowsnap-lastmillisB)>DEBOUNCEB)
{
channelBval= nowsnap - lastmillisB;
lastmillisB=nowsnap;
ic++;
}
}
//coding is as follows
// bit 7: 0 if absolute sample, or 1 if a delta
// bit 0-6: Two's complement of the delta if delta coded (-64 to +63)
// bit 0-6: value of sample divided by 64
unsigned char encodeSample( int base,unsigned int sample)
{
int delta = sample-base;
byte retval;
if (base == -1)
{
retval = (delta) | 0b10000000;
return((unsigned char) retval);
}
if ((delta > 63) || (delta < -64))
{
retval = (sample >> 6) & 0b01111111;
}
else
{
retval = (delta) | 0b10000000;
}
// Serial.print("Encoded ");
// Serial.print(base);
// Serial.print(" and ");
// Serial.print(sample);
// Serial.print(" and got ");
// Serial.print((int) retval);
// Serial.print(" which decodes as ");
// Serial.print(decodeSample(base,retval));
// Serial.print("\n");
return((unsigned char) retval);
}
unsigned int decodeSample(unsigned int base, unsigned char code)
{
unsigned int retval;
if (code & 0b10000000) //dealing with a delta
{
retval = base + (char) ((code & 0b01111111) | ((code & 0b01000000) << 1)); //extends the sign byte
}
else
{
retval = code << 6;
}
return(retval);
}
void cleanSeconds()
{
int i;
unsigned long aAcc= 0;
unsigned long bAcc =0;
unsigned long aTot=0;
unsigned long bTot=0;
unsigned int xVal=0;
unsigned int yVal=0;
unsigned int zVal=0;
unsigned long aLow,bLow,aHigh,bHigh;
int j;
#if DPCM
aAcc = startSecsBaseA;
bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>=0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
aTot +=aAcc;
bTot +=bAcc;
#else
aTot += aSecArray[i];
bTot += bSecArray[i];
#endif
}
aLow = aTot/(4*SECONDSIZE);
aHigh =aTot/(SECONDSIZE/4);
bLow = bTot/(4*SECONDSIZE);
bHigh = bTot/(SECONDSIZE/4);
#if DPCM
aAcc = startSecsBaseA;
bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>=0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
if ((aAcc > aHigh) || (aAcc <aLow))
{
aSecArray[i] = encodeSample(-1, aTot/SECONDSIZE); //set to an absolute sample
xVal = aAcc;
yVal=decodeSample(0,aSecArray[i]); //note that the zero doesn't matter because we have an 'absolute sample
for (j=i-1;j>=0;j--)
{
zVal=decodeSample(xVal,aSecArray[j]);
aSecArray[j] = encodeSample(yVal,zVal);
yVal= decodeSample(yVal,aSecArray[j]);
xVal=zVal;
}
}
if ((bAcc > bHigh) || (bAcc < bLow))
{
bSecArray[i] = encodeSample(-1, bTot/SECONDSIZE); //set to an absolute sample
xVal = bAcc;
yVal=decodeSample(0,bSecArray[i]); //note that the zero doesn't matter because we have an 'absolute sample
for (j=i-1;j>=0;j--)
{
zVal=decodeSample(xVal,bSecArray[j]);
bSecArray[j] = encodeSample(yVal,zVal);
yVal= decodeSample(yVal,bSecArray[j]);
xVal=zVal;
}
}
#else
if (aSecArray[i] > aHigh || aSecArray[i] <aLow) {
aSecArray[i] = aTot/SECONDSIZE;
}
if (bSecArray[i] > bHigh || bSecArray[i] <bLow) {
bSecArray[i] = bTot/SECONDSIZE;
}
#endif
}
}
void printTwoDigits(Client C, byte digits)
{
if(digits < 10)
C.print('0');
C.print(digits,DEC);
}
void printOffsetTime(Client client, time_t basetime,long int offset)
{
client.print(year(basetime));
client.print("-");
printTwoDigits(client,month(basetime+offset));
client.print("-");
printTwoDigits(client,day(basetime+offset));
client.print(" ");
printTwoDigits(client,hour(basetime+offset));
client.print(":");
printTwoDigits(client,minute(basetime+offset));
client.print(":");
printTwoDigits(client,second(basetime+offset));
}
void printHeader(Client client)
{
// now output HTML data starting with standart header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println();
//set background color
client.print("<body style=background-color:white>");
//send first heading
client.println("<font color='black'><h1>Power Used and Produced</font></h1>");
client.println("<hr />");
client.println("<hr />");
}
void printSeconds(Client client)
{
int i;
//output seconds data to browser
client.print("<font color='blue' size='5'> Last ");
client.print(SECONDSIZE);
client.println(" Seconds");
client.println("<br />");//some space between lines
for (i=0;i<SECONDSIZE;i++)
{
client.print(i);
client.print(", ");
client.print(PRINTSCALE*aSecArray[i]);//lets output some data
client.print(", ");
client.print(PRINTSCALE*bSecArray[i]);
client.println("<br />");//some space between lines
}
}
void printBCurrent(Client client)
{
int i;
client.print("1\n");
printOffsetTime(client, curSampleTime,0);
client.print(" 1, ");
#if DPCM
int aAcc = startSecsBaseA;
int bAcc = startSecsBaseB;
for (i=SECONDSIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
}
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aSecArray[0]);
#endif
client.print(", ");
#if DPCM
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bSecArray[0]);
#endif
client.print("\n");
}
void printBHistSecs(Client client)
{
int i;
//output seconds data as text
client.print(MIN(secondSamples,SECONDSIZE));
client.print("\n");
#if DPCM
int aAcc = startSecsBaseA;
int bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
if (i<MIN(secondSamples,SECONDSIZE)) //must do this for hours and mins
{
printOffsetTime(client, curSampleTime,-1*i);
client.print(" 1, ");
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aSecArray[i]);
#endif
client.print(", ");
#if DPCM
bAcc = decodeSample(bAcc,bSecArray[i]);
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bSecArray[0]);
#endif
client.print("\n");
}
else
#if DPCM
{
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
}
#endif
}
}
void printBHistMins(Client client)
{
long int i;
//output seconds data as text
client.print(MIN(minuteSamples,MINUTESIZE));
client.print("\n");
#if DPCM
int aAcc = startMinsBaseA;
int bAcc = startMinsBaseB;
#endif
for (i=MINUTESIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
if (i<MIN(minuteSamples,MINUTESIZE))
{
printOffsetTime(client,curSampleTime,(long) (-second(curSampleTime)-60*i));
client.print(" 60, ");
#if DPCM
aAcc = decodeSample(aAcc,aMinArray[i]);
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aMinArray[i]);
#endif
client.print(", ");
#if DPCM
bAcc = decodeSample(bAcc,bMinArray[i]);
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bMinArray[0]);
#endif
client.print("\n");
}
else
#if DPCM
{
aAcc = decodeSample(aAcc,aMinArray[i]);
bAcc = decodeSample(bAcc,bMinArray[i]);
}
#endif
}
}
void printBHistHours(Client client)
{
long int i;
client.print(MIN(hourSamples,HOURSIZE));
client.print("\n");
#if DPCM
int aAcc = startHoursBaseA;
int bAcc = startHoursBaseB;
#endif
for (i=HOURSIZE-1;i>=0;i--) //note that this is in "rolling forward" order
{
if (i<MIN(hourSamples,HOURSIZE))
{
printOffsetTime(client, curSampleTime,(long) (-60*minute(curSampleTime)-second(curSampleTime)-3600*i));
client.print(" 3600, ");
#if DPCM
aAcc = decodeSample(aAcc,aHourArray[i]);
client.print(PRINTSCALE*aAcc);
#else
client.print(PRINTSCALE*aHourArray[i]);
#endif
client.print(", ");
#if DPCM
bAcc = decodeSample(bAcc,bHourArray[i]);
client.print(PRINTSCALE*bAcc);
#else
client.print(PRINTSCALE*bHourArray[i]);
#endif
client.print("\n");
}
else
#if DPCM
{
aAcc = decodeSample(aAcc,aHourArray[i]);
bAcc = decodeSample(bAcc,bHourArray[i]);
}
#endif
}
}
void printMinutes(Client client)
{
int i;
//output minutes data to browser
client.println("<hr />");
client.println("<hr />");
client.print("<font color='blue' size='5'>Last ");
client.print(MINUTESIZE);
client.println(" Minutes");
client.println("<br />");//some space between lines
for (i=0;i<MINUTESIZE;i++)
{
client.print(i);
client.print(", ");
client.print(PRINTSCALE*aMinArray[i]);//lets output some data
client.print(", ");
client.print(PRINTSCALE*bMinArray[i]);
client.println("<br />");//some space between lines
}
}
void printCheckBox(Client client)
{
//controlling led via checkbox
client.println("<h1>LED control</h1>");
//address will look like http://192.168.168.110/?L=1 when submited
client.println("<form method=get name=LED><input type=checkbox name=L value=1>LED<br><input type=submit value=submit></form>");
client.println("<br />");
}
void printFooter(Client client)
{
client.println("</body></html>\n");
}
void printHours(Client client)
{
int i;
//output hours data to browser
client.println("<hr />");
client.println("<hr />");
client.print("<font color='blue' size='5'>Last ");
client.print(HOURSIZE);
client.println(" Hours");
client.println("<br />");//some space between lines
for (i=0;i<HOURSIZE;i++)
{
client.print(i);
client.print(", ");
client.print(PRINTSCALE*aHourArray[i]);//lets output some data
client.print(", ");
client.print(PRINTSCALE*bHourArray[i]);
client.println("<br />");//some space between lines
}
}
/*-------- NTP code ----------*/
unsigned long getNtpTime()
{
sendNTPpacket(SNTP_server_IP);
delay(1000);
if ( UdpBytewise.available() ) {
for(int i=0; i < 40; i++)
UdpBytewise.read(); // ignore every field except the time
const unsigned long seventy_years = 2208988800UL + timeZoneOffset;
return getUlong() - seventy_years;
}
return 0; // return 0 if unable to get the time
}
unsigned long sendNTPpacket(byte *address)
{
UdpBytewise.begin(123);
UdpBytewise.beginPacket(address, 123);
UdpBytewise.write(B11100011); // LI, Version, Mode
UdpBytewise.write(0); // Stratum
UdpBytewise.write(6); // Polling Interval
UdpBytewise.write(0xEC); // Peer Clock Precision
write_n(0, 8); // Root Delay & Root Dispersion
UdpBytewise.write(49);
UdpBytewise.write(0x4E);
UdpBytewise.write(49);
UdpBytewise.write(52);
write_n(0, 32); //Reference and time stamps
UdpBytewise.endPacket();
}
unsigned long getUlong()
{
unsigned long ulong = (unsigned long)UdpBytewise.read() << 24;
ulong |= (unsigned long)UdpBytewise.read() << 16;
ulong |= (unsigned long)UdpBytewise.read() << 8;
ulong |= (unsigned long)UdpBytewise.read();
return ulong;
}
void write_n(int what, int how_many)
{
for( int i = 0; i < how_many; i++ )
UdpBytewise.write(what);
}
#if 0
void printFreeMem()
{
int i;
char *test;
for (i=500;i>0;i--)
{
test = (char *) malloc(i);
if (test)
{
Serial.print("Free mem is: ");
Serial.print(i);
Serial.print("\n");
free(test);
break;
}
}
}
#endif
/* main loop */
void setup()
{
int i;
int lastic = 0;
int iccount;
char *test;
//Zero Array
for (i=0;i<SECONDSIZE;i++)
{
aSecArray[i]=0;
bSecArray[i]=0;
}
for (i=0;i<MINUTESIZE;i++)
{
aMinArray[i]=0;
bMinArray[i]=0;
}
for (i=0;i<HOURSIZE;i++)
{
aHourArray[i]=0;
bHourArray[i]=0;
}
//start Ethernet
Ethernet.begin(mac, ip, gateway, subnet);
pinMode(HomePin,INPUT);
pinMode(SolarPin,INPUT);
digitalWrite(HomePin,HIGH);
digitalWrite(SolarPin,HIGH);
pinMode(ledPin, OUTPUT);
//enable serial datada print
Serial.begin(115200);
Serial.print("Booted...\n");
setSyncProvider(getNtpTime);
setSyncInterval(3001);
while(timeStatus() == timeNotSet)
{
Serial.print("Synching Clock\n"); // wait until the time is set by the sync provider
}
Serial.print("Time is: ");
Serial.print(now());
Serial.print("\n");
attachInterrupt(0,myInterruptHandlerA,FALLING); //interrupt zero is pin 2
attachInterrupt(1, myInterruptHandlerB,FALLING); //interrupt one is pin 3
iccount = ic;
while (ic < iccount+5)
{ // just wait!
}
}
void parseInput(Client client)
{
//lets check if LED should be lighted
if(readString.indexOf("L=1") != -1)
{
//led has to be turned ON
digitalWrite(ledPin, HIGH); // set the LED on
LEDON = true;
closeConnection =true;
}
else
{
//led has to be turned OFF
digitalWrite(ledPin, LOW); // set the LED OFF
LEDON = false;
closeConnection =true;
}
if (readString.indexOf("BCurrent") != -1)
{
printBCurrent(client);
closeConnection = false;
}
if (readString.indexOf("BHistSecs") != -1)
{
printBHistSecs(client);
closeConnection = false;
}
if (readString.indexOf("BHistMins") != -1)
{
printBHistMins(client);
closeConnection = false;
}
if (readString.indexOf("BHistHrs") != -1)
{
printBHistHours(client);
closeConnection = false;
}
if(readString.indexOf("Seconds") != -1)
{
printHeader(client);
printSeconds(client);
printFooter(client);
closeConnection =true;
}
if(readString.indexOf("Minutes") != -1)
{
printHeader(client);
printMinutes(client);
printFooter(client);
closeConnection =true;
}
if(readString.indexOf("Hours") != -1)
{
printHeader(client);
printHours(client);
printFooter(client);
closeConnection =true;
}
if(readString.indexOf("All") != -1)
{
printHeader(client);
printSeconds(client);
printMinutes(client);
printHours(client);
printCheckBox(client);
printFooter(client);
closeConnection =true;
}
}
void loop()
{
int i;
unsigned int aAcc;
unsigned int bAcc;
unsigned int a2Acc;
unsigned int b2Acc;
unsigned long aTot,bTot;
myMillis = millis();
if (myMillis < 1000+lastMyMillis)
{
//could delay here or do other work
}
else
{
lastMyMillis=lastMyMillis+1000;
secondCount++;
secondSamples++;
secondSamples = MIN(secondSamples,SECONDSIZE);
if (myMillis < 5000+lastmillisA) {
safeAval=channelAval;
}
else {
safeAval=0;
} // no data for 5 seconds means 0
if (myMillis < 5000+lastmillisB) {
safeBval=channelBval;
}
else {
safeBval=0;
} //no data for 5 seconds means 0
if (safeAval && (safeAval < 150)) {
Serial.print("Error Channel A: ");
Serial.print(safeAval);
Serial.print(" \n");
}
if (safeBval && (safeBval < 150)) {
Serial.print("Error Channel B: ");
Serial.print(safeBval);
Serial.print(" \n");
}
curSampleTime =now();
// scrolls the seconds along the array - trading off speed for less code complexity
#if DPCM
startSecsBaseA = decodeSample(startSecsBaseA,aSecArray[SECONDSIZE-1]);
startSecsBaseB = decodeSample(startSecsBaseB,bSecArray[SECONDSIZE-1]);
aAcc = startSecsBaseA;
bAcc = startSecsBaseB;
#endif
for (i=SECONDSIZE-1;i>0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i-1]);
bAcc = decodeSample(bAcc,bSecArray[i-1]);
#endif
aSecArray[i] = aSecArray[i-1];
bSecArray[i]= bSecArray[i-1];
}
#if USEBYTES
#if DPCM
if (safeAval) {
aSecArray[0]=encodeSample(aAcc,SCALEFACTORA/(PRINTSCALE*safeAval));
}
else aSecArray[0]=encodeSample(aAcc,0);
if (safeBval) {
bSecArray[0]=encodeSample(bAcc,SCALEFACTORB/(PRINTSCALE*safeBval));
}
else bSecArray[0]=encodeSample(bAcc,0);
#else
if (safeAval) {
aSecArray[0]=SCALEFACTORA/(PRINTSCALE*safeAval);
}
else aSecArray[0]=0;
if (safeBval) {
bSecArray[0]=SCALEFACTORB/(PRINTSCALE*safeBval);
}
else bSecArray[0]=0;
#endif
#else
if (safeAval) {
aSecArray[0]=SCALEFACTORA/safeAval;
}
else aSecArray[0]=0;
if (safeBval) {
bSecArray[0]=SCALEFACTORB/safeBval;
}
else bSecArray[0]=0;
#endif
//This section handles minutes
// every minute, average the last 60 seconds, and scroll the minutes along
if (second(curSampleTime) == 0) // do this at the top of every minute
{
//first clean out any egregious data points
// cleanSeconds();
//scroll the minutes
#if DPCM
startMinsBaseA = decodeSample(startMinsBaseA,aMinArray[MINUTESIZE-1]);
startMinsBaseB = decodeSample(startMinsBaseB,bMinArray[MINUTESIZE-1]);
a2Acc = startMinsBaseA;
b2Acc = startMinsBaseB;
#endif
for (i=MINUTESIZE-1;i>0;i--)
{
#if DPCM
a2Acc = decodeSample(a2Acc,aMinArray[i-1]);
b2Acc = decodeSample(b2Acc,bMinArray[i-1]);
#endif
aMinArray[i]= aMinArray[i-1];
bMinArray[i]= bMinArray[i-1];
}
// Do the averaging
//although this second pass seems redundant with "cleanSeconds", it actually helps to have the data cleaned in the first pass and the
//re-averaged.
//We could do this with more code complexity in a single pass
aTot=0;
bTot=0;
#if DPCM
aAcc=startSecsBaseA;
bAcc=startSecsBaseB;
#endif
for (i=SECONDSIZE-1; i>=0; i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aSecArray[i]);
bAcc = decodeSample(bAcc,bSecArray[i]);
#endif
if (i<MIN(60,secondCount))
{
#if DPCM
aTot = aTot+aAcc;
bTot = bTot+bAcc;
#else
aTot = aTot+aSecArray[i];
bTot = bTot+bSecArray[i];
#endif
}
}
#ifdef DPCM
aMinArray[0]= encodeSample(a2Acc,(aTot/MIN(60,secondCount))); // add dpcm here
bMinArray[0]= encodeSample(b2Acc,(bTot/MIN(60,secondCount)));
#else
aMinArray[0]= (unsigned int) (aTot/MIN(60,secondCount));
bMinArray[0]= (unsigned int) (bTot/MIN(60,secondCount));
#endif
minuteCount++;
minuteSamples++;
minuteSamples = MIN(minuteSamples,MINUTESIZE);
secondCount=0;
// Serial.print("Done with minutes\n");
} //closes the once a minute loop
//This section handles hours
// every hour, average the last 60 minutes, and scroll the hours along
if ((minute(curSampleTime) == 0) && (second(curSampleTime) == 0))
{
#if DPCM
startHoursBaseA = decodeSample(startHoursBaseA,aHourArray[SECONDSIZE-1]);
startHoursBaseB = decodeSample(startHoursBaseB,bHourArray[SECONDSIZE-1]);
a2Acc = startHoursBaseA;
b2Acc = startHoursBaseB;
#endif
// do the scrolling
for (i=HOURSIZE-1;i>0;i--)
{
#if DPCM
a2Acc = decodeSample(a2Acc,aHourArray[i-1]);
b2Acc = decodeSample(b2Acc,bHourArray[i-1]);
#endif
aHourArray[i] = aHourArray[i-1];
bHourArray[i] = bHourArray[i-1];
}
aTot=0;
bTot=0;
#if DPCM
aAcc=startMinsBaseA;
bAcc=startMinsBaseB;
#endif
//find the average
for (i=MINUTESIZE-1;i>=0;i--)
{
#if DPCM
aAcc = decodeSample(aAcc,aMinArray[i]);
bAcc = decodeSample(bAcc,bMinArray[i]);
#endif
if (i<MIN(60,minuteCount))
{
#if DPCM
aTot += aAcc;
bTot += bAcc;
#else
aTot +=aMinArray[i];
bTot +=bMinArray[i];
#endif
}
}
#if DPCM
aHourArray[0]= encodeSample(a2Acc,(aTot/MIN(60,minuteCount)));
bHourArray[0]= encodeSample(b2Acc,(bTot/MIN(60,minuteCount)));
#else
aHourArray[0]= (unsigned int) (aAcc/MIN(60,minuteCount));
bHourArray[0]= (unsigned int) (bAcc/MIN(60,minuteCount));
#endif
minuteCount=0;
hourCount++;
hourSamples++;
hourSamples = MIN(hourSamples,HOURSIZE);
}//closes the "once per hour loop
}//closes the "once per second loop"
// Create a client connection
Client client = server.available();
readString = "";
if (client) {
//Serial.print("Client connecting\n");
while (client.connected())
{
if (client.available())
{
char c = client.read();
//read char by char HTTP request
if (readString.length() < MAXCMDLEN)
{
//store characters to string
readString +=c;
}
//output chars to serial port
Serial.print(c);
//if HTTP request has ended
if (c == '\n')
{
if(readString.indexOf("GET") != -1)
{
Serial.print(readString);
//printFreeMem();
parseInput(client);
//if (closeConnection) client.stop();
client.stop();
}
//clearing string for next read
readString="";
} //closes the branch where we've found a new line
} //closes the if client available
} // closes while client connected
if (client) client.stop();
} //closes if client
} //closes loop()
//*******************************