//ethernet_monitor_v2 //jonathan.engel@gmail.com //Code to enable power monitoring shield to collect current transformer //readings, compute real power values, and transmit to Pachube. //Basic energy monitoring sketch - by Trystan Lea //Licenced under GNU General Public Licence more details here // openenergymonitor.org //Additional code work by Eric Sandeen //http://sandeen.net/ //Sketch measures mains voltage and branch current //and then calculates useful values like real power, //apparent power, powerfactor, Vrms, Irms. //Remaining code work by Jonathan Engel //Open up to measure full 6 analog channels of real power for transmission //to Pachube feed. //Modifications are required to the Ethernet Shield for this code to //function. The pass through pins on the Ethernet Shield for A0 and A1 //must be isloated from the 10K pullup resistors on the shield by cutting //the traces near the pins. See build documentation for photos. //v2 changes: //Upgrade to reset ethernet shield if more than 10 failed connections //to pachube, requires mods to the shield so that the ethernet shield //reset pin is connected to pin D9. This is accomplished with an "airwire" //from Pin D9 to the reset pin on the Ethernet Shield, by bending out //the Ethernet Shield rest pin so that it does not interact with the Arduino //reset circuit, and by clipping the SPI bus reset pin on the Ethernet Shield. //See build documentation for photos of mods. #include #include byte debug_time = 1; byte debug_power_values = 1; byte debug_http = 0; unsigned int successes = 0; unsigned int failures = 0; // Setup variables int numberOfSamples = 3000; //digital out int resetPin = 9; //reset pin to manually reset the ethernet shield // Number of CTs to be monitored #define NUM_CTS 5 #define MAX_CTS 5 // Set Voltage and current input pins int inPinV = 0; int inPinI[MAX_CTS] = {1, 2, 3, 4, 5}; // Initial Calibration // These need to be set in order to obtain accurate results // Voltage is reduced both by xfmr & voltage divider // Measure wall & adapter output, calculate voltage divider ratio #define AC_WALL_VOLTAGE 120 #define AC_ADAPTER_VOLTAGE 3.516 #define AC_VOLTAGE_DIV_RATIO 3.01 // CT: Voltage depends on current, burden resistor, and turns // Enter values for each total burden resistance, and each CT's turns double ct_burden[MAX_CTS] = {10, 49, 60.5, 100, 100}; int ct_turns[MAX_CTS] = {1450, 2016, 920, 1840, 1840}; // Calibration coefficients // VCAL and ICAL are small-scale tweaks to improve accuracy double VCAL = 1.025; // For efergy CT, 68 ohm, 1700 turns ICAL is 1.05755 double ICAL[MAX_CTS] = {1.17, 0.945, 0.933, 1.05, 1.06}; double PHASECAL = 1.4; // Initial guesses for ratios double V_RATIO = ((long double)AC_WALL_VOLTAGE / AC_ADAPTER_VOLTAGE) * AC_VOLTAGE_DIV_RATIO * 5 / 1024 * VCAL; double I_RATIO[MAX_CTS]; // we set these up in setup() // Sample variables int lastSampleV, lastSampleI, sampleV, sampleI; //Filter variables double lastFilteredV, lastFilteredI, filteredV, filteredI; // Stores the phase calibrated instantaneous voltage. double shiftedV; // Power calculation variables double sqV, sqI, instP; double sumV, sumI, sumP; // Useful value variables double realPower[MAX_CTS], sumRealPower[MAX_CTS], apparentPower, powerFactor, Vrms, Irms; int powerReadings = 0, powerGarage, powerUtil, powerAC, power1, power2; // timing long start; //insert your own API key and feed ID below. #define PACHUBE_API_KEY "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" #define PACHUBE_FEED_ID "xxxxx" #define PACHUBE_UPDATE_INTERVAL (30L * 1000L) long updateTime = 0; // next time to post data // assign a MAC address for the ethernet controller. // fill in your address here: // 00:16:3e:5f:bc:d0 (xensource) //byte mac[] = { 0x00, 0x16, 0x3E, 0x5F, 0xBC, 0xD1}; byte mac[] = { 0xCC, 0xAC, 0xBE, 0xEF, 0xFE, 0x91}; // assign an IP address for the controller: //byte ip[] = { 10,0,0,188 }; byte ip[] = { 192,168,0,144 }; //byte gateway[] = { 10,0,0,1}; byte gateway[] = { 192,168,0,254}; byte subnet[] = { 255, 255, 255, 0 }; // The address of the server you want to connect to (pachube.com): byte server[] = { 173,203,98,29 }; // initialize the library instance: Client client(server, 80); boolean lastConnected = false; // state of the connection last time through the main loop void setup() { // setup ethernet reset pin pinMode(resetPin,OUTPUT); // manually reset the ethernet shield before using it digitalWrite(resetPin,LOW); // put reset pin to low ==> reset the ethernet shield delay(200); digitalWrite(resetPin,HIGH); // set it back to high delay(2000); Serial.begin(57600); Serial.print("Initializing Network... "); Ethernet.begin(mac, ip); // give the ethernet module time to boot up: delay(1000); Serial.println("done."); Serial.println("Initializing variables\n"); for (int i = 0; i < NUM_CTS; i++) { I_RATIO[i] = (long double)ct_turns[i] / ct_burden[i] * 5 / 1024 * ICAL[i]; } // First update PACHUBE_UPDATE_INTERVAL from now updateTime = millis() + PACHUBE_UPDATE_INTERVAL; Serial.println("Setup is done\n"); } void show_ct_details(int i) { Serial.print("CT: "); Serial.print(i); Serial.print(" RP: "); Serial.print(realPower[i]); Serial.print(" AP: "); Serial.print(apparentPower); Serial.print(" PF: "); Serial.print(powerFactor); Serial.print(" Vrms: "); Serial.print(Vrms); Serial.print(" Irms "); Serial.println(Irms); } // this method makes a HTTP connection to the server: void sendData(void) { // if there's a successful connection: if (!client.connected()) { client.connect(); } if (client.connected()) { char dataBuf[70]; sprintf(dataBuf, "%d,%d,%d,%d,%d", powerUtil, powerGarage, powerAC, power1, power2); successes++; Serial.println("connecting..."); // send the HTTP PUT request. // fill in your feed address here: client.print("PUT /api/" PACHUBE_FEED_ID ".csv HTTP/1.1\n"); client.print("Host: www.pachube.com\n"); // fill in your Pachube API key here: client.print("X-PachubeApiKey: " PACHUBE_API_KEY "\n"); client.print("Content-Length: "); // calculate the length of the sensor reading in bytes: int thisLength = strlen(dataBuf); client.println(thisLength, DEC); // last pieces of the HTTP PUT request: client.print("Content-Type: text/csv\n"); client.print("Connection: close\n\n"); // here's the actual content of the PUT request: client.print(dataBuf); client.print("\n"); } else { // if you couldn't make a connection: Serial.print("connection failed"); Serial.println(++failures); if (failures > 9) { // manually reset the ethernet shield before using it digitalWrite(resetPin,LOW); // put reset pin to low ==> reset the ethernet shield delay(200); digitalWrite(resetPin,HIGH); // set it back to high delay(2000); Ethernet.begin(mac, ip); // give the ethernet module time to boot up: delay(1000); failures = 0; } } } void loop() { start = millis(); for (int i = 0; i < NUM_CTS; i++) { for (int n = 0; n < numberOfSamples; n++) { // Set up initial conditions for digital filter if (n == 0) { sampleV = analogRead(inPinV); sampleI = analogRead(inPinI[i]); // Best initial guess at offset removal, half of 0-1023 range filteredV = sampleV - 511; filteredI = sampleI - 511; } // Used for offset removal lastSampleV = sampleV; lastSampleI = sampleI; // Read in voltage and current samples. // Sampling I after V makes us measure I about 0.000112 sec or 2.5 degrees late // This tends to -advance- the I waveform relative to V sampleV = analogRead(inPinV); sampleI = analogRead(inPinI[i]); // Used for 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 goes here. // This starts w/ the -last- voltage sample and adds a fraction of our new difference. // Effectively shifts the voltage waveform along the slope // Inductive circuits have pf < 1, and current lags voltage. // Phasecal of 0 takes last sample; 1 takes current, 2 takes approx. "next" sample // SO > 1 shifts things which way? shiftedV = lastFilteredV + PHASECAL * (filteredV - lastFilteredV); // Root-mean-square method voltage // 1) square voltage values sqV = filteredV * filteredV; // 2) sum sumV += sqV; // Root-mean-square method current // 1) square current values sqI = filteredI * filteredI; //2) sum sumI += sqI; // Instantaneous Power instP = shiftedV * filteredI; // Sum sumP += instP; } // End of V/I samples loop // Calculation of the root of the mean of the voltage and current squared (rms) // Calibration coefficients applied here as well. Vrms = V_RATIO * sqrt(sumV / numberOfSamples); Irms = I_RATIO[i] * sqrt(sumI / numberOfSamples); // Calculation of power values realPower[i] = V_RATIO * I_RATIO[i] * sumP / numberOfSamples; sumRealPower[i] += realPower[i]; // for averaging over report interval apparentPower = Vrms * Irms; powerFactor = realPower[i] / apparentPower; if (debug_power_values) show_ct_details(i); // Reset accumulators for this CT sumV = sumI = sumP = 0; } // End of CTs loop if (debug_time) { Serial.print("Time to sample all CTs: "); Serial.print(millis() - start); Serial.print("Connected: "); Serial.println(client.connected(),DEC); Serial.print("Lastconnected: "); Serial.println(lastConnected,DEC); } powerReadings++; // if there's incoming data from the net connection. // send it out the serial port. This is for debugging // purposes only: if (debug_http && client.available()) { char c = client.read(); Serial.print(c); } // if there's no net connection, but there was one last time // through the loop, then stop the client: if (!client.connected() && lastConnected) { Serial.println(); Serial.println("disconnecting."); client.stop(); lastConnected = false; } // Check if it's time to send an external update of averages // if you're not connected, and ten seconds have passed since // your last connection, then connect again and send data: //if (!client.connected() && millis() >= updateTime) { if (millis() >= updateTime) { Serial.println("Updating pachube"); // Do another update PACHUBE_UPDATE_INTERVAL from now updateTime += PACHUBE_UPDATE_INTERVAL; Serial.println(updateTime); powerUtil=sumRealPower[0]/powerReadings; powerGarage=sumRealPower[1]/powerReadings; powerAC = sumRealPower[2]/powerReadings; power1 = sumRealPower[3]/powerReadings; //power2 = Vrms; power2 = sumRealPower[4]/powerReadings; sendData(); // store the state of the connection for next time through // the loop: lastConnected = client.connected(); // Reset values for next sample loop powerReadings = 0; for (int i = 0; i < NUM_CTS; i++) { sumRealPower[i] = 0; } Serial.print("Average: "); } else { // Compute & print power info we are interested in after this sample instance powerGarage = realPower[1]; powerUtil = realPower[0]; powerAC = realPower[2]; power1 = realPower[3]; power2 = realPower[4]; Serial.println(millis()); Serial.print("Sample: "); } Serial.print("Util: "); Serial.print(powerUtil); Serial.print(" Garage: "); Serial.print(powerGarage); Serial.print(" AC: "); Serial.print(powerAC); Serial.print(" Cir1: "); Serial.print(power1); Serial.print(" Cir2: "); Serial.println(power2); }