// 7/22/13 changed Ical to .86
// 7/18/13 added temp code
// 7/17/13 correct sd read loop, added SRAM code
// 7/14/13 Added second CT code
// 7/12/13 added SD card to store kilowatts
// 7/4/13 added cost
// 7/2/13 final ver put on line
// 6/30/13 added emoncms code to monitor code
// 6/28/13 add all 3 boards together
// 6/25/13 power monitor program
#include <SD.h>
#include <SPI.h>
#include <Ethernet.h>
#include <JeeLib.h> // https://github.com/jcw/jeelib
#include <OneWire.h> //for one wire temp sensor
#include <DallasTemperature.h>
#define ONE_WIRE_BUS 7 // Data wire is digital pin 7 on the Arduino
#define TEMPERATURE_PRECISION 12
// Setup a oneWire instance w/any OneWire devices
OneWire oneWire(ONE_WIRE_BUS);
// Pass our oneWire reference to Dallas Temperature.
DallasTemperature sensors(&oneWire);
// arrays to hold device addresses – replace with your sensors addresses
//DeviceAddress insideThermometer = { 0x28, 0x37, 0x38, 0xB7, 0x03,
0x00, 0x00, 0x3F};
DeviceAddress outsideThermometer = { 0x28, 0x45, 0x1F, 0x61, 0x03, 0x00,
0x00, 0x68};
// code used to show free sram
/*
int freeRam () {
extern int __heap_start, *__brkval;
int v;
return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval);
} */
File myFile; // used to store total kilowatt reading
int bc = 0; // used to bypass first time thru loop
int j; //loop counter
char kwh[10]; //temp array used to convert string to float
byte mac[] = { }; // my second ethernet mac address
byte ip[] = {
192,168,1,99 }; //I am using ip=99, port 1234
typedef struct { int power1, power2, power3, voltage; }
PayloadTX;
PayloadTX emontx;
// Enter your apiurl here including apikey:
char apiurl[] = "http://emoncms.org/api/post.json?apikey=YOURAPIKEY&json=";
//char timeurl[] = "http://emoncms.org/time/local.json?apikey=YOURAPIKEY";
// For posting to emoncms server with host name, (DNS lookup) comment
out if using static IP address below
// emoncms.org is the public emoncms server. Emoncms can also be
downloaded and run on any server.
//char server[] = "emoncms.org";
//byte server[] = { 213.138.101.177 }; // emnocms.org ip
IPAddress server(213,138,101,177); // emoncms server IP for posting to
server without a host name, can be used for posting to local emoncms
server
//------------------------------------------------------------------------------------------------------
// The PacketBuffer class is used to generate the json string that is
send via ethernet - JeeLabs
//------------------------------------------------------------------------------------------------------
class PacketBuffer :
public Print {
public:
PacketBuffer () :
fill (0) {
}
const char* buffer() {
return buf;
}
byte length() {
return fill;
}
void reset()
{
memset(buf,NULL,sizeof(buf));
fill = 0;
}
virtual size_t write (uint8_t ch)
{
if (fill < sizeof buf) buf[fill++] = ch;
}
byte fill;
char buf[150];
private:
};
PacketBuffer str;
//--------------------------------------------------------------------------------------------------------
//--------------------------------------------------------------------------
//datastreams
char kilowatt[] = "Kilowatt";
char RealPower[] = "Power";
char RMSCurrent[] = "RMSCurrent";
const int voltageSensor = A0;
const int currentSensor = A1; //CT #1
const int currentSensor2 = A2; //CT #2
const int numberOfSamples = 3000;
// Calibration constants
const float AC_WALL_VOLTAGE = 122.0;
const float AC_ADAPTER_VOLTAGE = 7.40;
const float AC_VOLTAGE_DIV_VOUT = 1.23;
const float CT_BURDEN_RESISTOR = 55;
const float CT_TURNS = 6060.6;
const float CT_BURDEN_RESISTOR2 = 55;
// Calibration coefficients
const float VCAL = 1.00;
const float ICAL = 0.86;
const float PHASECAL = 0.9;
const float ICAL2 = 0.86;
// Calculated ratio constants, modified by VCAL/ICAL
const float AC_ADAPTER_RATIO = AC_WALL_VOLTAGE / AC_ADAPTER_VOLTAGE;
const float AC_VOLTAGE_DIV_RATIO = AC_ADAPTER_VOLTAGE /
AC_VOLTAGE_DIV_VOUT;
const float V_RATIO = AC_ADAPTER_RATIO * AC_VOLTAGE_DIV_RATIO * 5 / 1024
* VCAL;
const float I_RATIO = CT_TURNS / CT_BURDEN_RESISTOR * 5 / 1024 * ICAL;
const float I_RATIO2 = CT_TURNS / CT_BURDEN_RESISTOR2 * 5 / 1024 *
ICAL2;
// Sample variables
int lastSampleV, lastSampleI, sampleV, sampleI;
int lastSampleI2, sampleI2; //using same V for both CTs
// Filter variables
float lastFilteredV, lastFilteredI, filteredV, filteredI;
float lastFilteredI2, filteredI2; //using same V for both CTs
// Power sample totals
float sumI, sumV, sumP, sumI2, sumV2, sumP2;
float instantcost, kilowattcost, tempF, tempC;
const float crate = 0.0544; // current electric rate per kilowatt/hour
// Phase calibrated instantaneous voltage
float calibratedV;
float calibratedV2;
// Calculated power variables
float realPower, apparentPower, powerFactor, voltageRMS, currentRMS;
float realPower2, realPowert, apparentPower2, powerFactor2, currentRMS2,
currentRMSt;
unsigned long last_kWhTime, kWhTime;
float kilowattHour = 0.0;
float kilowattHour2 = 0.0;
float kilowattHourt = 0.0;
EthernetClient client;
void setup() {
Serial.begin(9600);
//Serial.println("Begin");
sensors.begin(); // Start up the library for temp sensor
delay(1000);
// set the resolution
sensors.setResolution(outsideThermometer, TEMPERATURE_PRECISION);
delay(1000);
Ethernet.begin(mac, ip);
// On the Ethernet Shield, CS is pin 4. It's set as an output by
default.
// Note that even if it's not used as the CS pin, the hardware SS pin
// (10 on most Arduino boards, 53 on the Mega) must be left as an output
// or the SD library functions will not work.
pinMode(10, OUTPUT);
if (!SD.begin(4)) { // SD card uses pin 4 as CS because ethernet uses
pin 10
Serial.println("SD fail");
return; }
}
void loop() {
//Serial.print(F("free SRAM "));
//Serial.println(freeRam());
//Serial.println(F("Loop"));
sensors.requestTemperatures();
tempC = sensors.getTempC(outsideThermometer); //get outside temp
tempF = (DallasTemperature::toFahrenheit(tempC));
calculatePower1();
calculatePower2();
realPowert = realPower + realPower2;
kilowattHourt = kilowattHour + kilowattHour2;
currentRMSt = currentRMS + currentRMS2;
//Serial.print(F("KWHT is "));
//Serial.println(kilowattHourt);
instantcost = realPowert * (crate / 1000); //instant power cost
myFile = SD.open("killog.txt");
if (myFile) { // check to see if killlog.txt exists and open
//Serial.println(F("Open killog"));
if (kilowattHourt < 2.0) { // either just starting or Arduino has reset
//myFile = SD.open("killog.txt"); //killog.txt holds kilowatt hour
running total
j = 0;
do {
// test for kwh full
if (j == sizeof(kwh)) {
Serial.println(F("line too long"));
break; }
kwh[j] = myFile.read(); }
while (kwh[j++] != '\r');
kilowattHourt = atof(&kwh[0]); //convert read string to float
//Serial.print(F("KWH from SD "));
//Serial.println(kilowattHourt);
myFile.close();}
else {
myFile = SD.open("killog.txt", FILE_WRITE); //open file for writing
//Serial.print(F("Write KWH "));
//Serial.println(kilowattHourt);
myFile.seek(0); //set to write to first byte of file
myFile.println(kilowattHourt);
myFile.close(); } //save kilowattHour to file
} // end of if myfile code
else {
// if the file didn't open, print an error:
Serial.println(F("error killog")); }
kilowattcost = kilowattHourt * crate;
//Serial.print(F("V "));
//Serial.println(voltageRMS);
//Serial.print(F("I "));
//Serial.println(currentRMSt);
//Serial.print(tempF);
//Serial.println(F(" F"));
/*Serial.print("Instant $ ");
Serial.println(instantcost);
Serial.print("Total KW $ ");
Serial.println(kilowattcost); */
if (bc > 0) {
str.reset(); // Reset json string
str.print("{kilowattHour:");
str.print(kilowattHourt); // Add power reading
str.print("},{realPower:");
str.print(realPowert); // Add power reading
str.print("},{currentRMS:");
str.print(currentRMSt); // Add power reading
//str.print("}"); //comment out this line when adding cost lines
str.print("},{InstantCost:");
str.print(instantcost); // Add power reading
str.print("},{TotalCost:");
str.print(kilowattcost);
str.print("},{OusideTemp:");
str.print(tempF);
str.print("}"); //end string
if (client.connect(server, 80)) {
str.print("}\0");
//Serial.println();
//Serial.print(F("Send "));
//Serial.println(str.buf);
client.print(F("GET "));
client.print(apiurl);
client.print(str.buf);
client.println();
delay(1000);
}
else {
//Serial.println(F("Done"));
delay(500);
client.stop();
}
}
//wdt_reset(); //watchdog timer
delay(60000);
bc = 1;
} // LOOP end
//=========================================================================
void calculatePower1() { // calculate line 2 of 240v
for (int i = 0; i < numberOfSamples; i++) {
// Used for voltage offset removal
lastSampleV = sampleV;
lastSampleI = sampleI;
// Read voltage and current values
sampleV = analogRead(voltageSensor);
sampleI = analogRead(currentSensor);
// Used for voltage offset removal
lastFilteredV = filteredV;
lastFilteredI = filteredI;
// Digital high pass filters to remove 2.5V DC offset
filteredV = 0.996 * (lastFilteredV + sampleV - lastSampleV);
filteredI = 0.996 * (lastFilteredI + sampleI - lastSampleI);
// Phase calibration
calibratedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);
// Root-mean-square voltage
sumV += calibratedV * calibratedV;
// Root-mean-square current
sumI += filteredI * filteredI;
// Instantaneous Power
sumP += abs(calibratedV * filteredI);
}
// Calculation of the root of the mean of the voltage and current
squared (rms)
// Calibration coeficients applied
voltageRMS = V_RATIO * sqrt(sumV / numberOfSamples);
currentRMS = I_RATIO * sqrt(sumI / numberOfSamples);
//Serial.println(voltageRMS);
//Serial.println(currentRMS);
// Calculate power values
realPower = V_RATIO * I_RATIO * sumP / numberOfSamples;
apparentPower = voltageRMS * currentRMS;
powerFactor = realPower / apparentPower;
// Calculate running total kilowatt hours
// This value will reset in 50 days
last_kWhTime = kWhTime;
kWhTime = millis();
// Convert watts into kilowatts and multiply by the time since the last
reading in ms
kilowattHour += (realPower / 1000) * ((kWhTime - last_kWhTime) /
3600000.0);
// Reset sample totals
sumV = 0;
sumI = 0;
sumP = 0;
} // end of CT#1 calculations
//============================================================
void calculatePower2() { // calculate line 2 of 240v
for (int i = 0; i < numberOfSamples; i++) {
// Used for voltage offset removal
lastSampleV = sampleV;
lastSampleI2 = sampleI2;
// Read voltage and current values
sampleV = analogRead(voltageSensor);
sampleI2 = analogRead(currentSensor2);
// Used for voltage offset removal
lastFilteredV = filteredV;
lastFilteredI2 = filteredI2;
// Digital high pass filters to remove 2.5V DC offset
filteredV = 0.996 * (lastFilteredV + sampleV - lastSampleV);
filteredI2 = 0.996 * (lastFilteredI2 + sampleI2 - lastSampleI2);
// Phase calibration
calibratedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV);
// Root-mean-square voltage
sumV += calibratedV * calibratedV;
// Root-mean-square current
sumI2 += filteredI2 * filteredI2;
// Instantaneous Power
sumP2 += abs(calibratedV * filteredI2);
}
// Calculation of the root of the mean of the voltage and current
squared (rms)
// Calibration coeficients applied
voltageRMS = V_RATIO * sqrt(sumV / numberOfSamples);
currentRMS2 = I_RATIO2 * sqrt(sumI2 / numberOfSamples);
//Serial.println(voltageRMS);
//Serial.println(currentRMS);
// Calculate power values
realPower2 = V_RATIO * I_RATIO2 * sumP2 / numberOfSamples;
apparentPower2 = voltageRMS * currentRMS2;
powerFactor2 = realPower2 / apparentPower2;
// Calculate running total kilowatt hours
// This value will reset in 50 days
last_kWhTime = kWhTime;
kWhTime = millis();
// Convert watts into kilowatts and multiply by the time since the last
reading in ms
kilowattHour2 += (realPower2 / 1000) * ((kWhTime - last_kWhTime) /
3600000.0);
// Reset sample totals
sumV = 0;
sumI2 = 0;
sumP2 = 0;
}
|