//Basic energy monitoring sketch - by Trystan Lea //Licenced under GNU General Public Licence more details here // openenergymonitor.org //Sketch measures voltage and current. //and then calculates useful values like real power, //apparent power, powerfactor, Vrms, Irms. #include byte debug_time = 1; byte debug_power_values = 1; // Setup variables int numberOfSamples = 3000; // Number of CTs to be monitored #define NUM_CTS 4 #define MAX_CTS 4 // Set Voltage and current input pins int inPinV = 0; int inPinI[MAX_CTS] = {1, 2, 3, 4}; // 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 122 #define AC_ADAPTER_VOLTAGE 15.2 #define AC_VOLTAGE_DIV_RATIO 11 // 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] = {137, 137, 32.4, 32.4}; int ct_turns[MAX_CTS] = {1012, 1012, 1500, 1500}; // Calibration coefficients // VCAL and ICAL are small-scale tweaks to improve accuracy double VCAL = 0.9850; // For efergy CT, 68 ohm, 1700 turns ICAL is 1.05755 double ICAL[MAX_CTS] = {1.0, 1.0, 1.03, 1.03}; double PHASECAL = 1; // 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, powerSolar, powerUtil, powerNet; // timing long start; // XXX s/b long for overflow math? unsigned long updateTime = 0; // next time to post data #define PACHUBE_API_KEY "shhh it's a secret!" #define PACHUBE_FEED_ID "12345" #define PACHUBE_UPDATE_INTERVAL (60L * 1000L) // Wireless configuration parameters ---------------------------------------- unsigned char local_ip[] = {10,0,0,82}; // IP address of WiShield unsigned char gateway_ip[] = {10,0,0,1}; // router or gateway IP address unsigned char subnet_mask[] = {255,255,255,0}; // subnet mask for the local network const prog_char ssid[] PROGMEM = {"MY_SSID"}; // max 32 bytes unsigned char security_type = 0; // 0 - open; 1 - WEP; 2 - WPA; 3 - WPA2 // WPA/WPA2 passphrase const prog_char security_passphrase[] PROGMEM = {"S3KR1T!"}; // max 64 characters prog_uchar wep_keys[] PROGMEM = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, // Key 0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 1 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Key 2 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 // Key 3 }; // setup the wireless mode // infrastructure - connect to AP unsigned char wireless_mode = WIRELESS_MODE_INFRA; unsigned char ssid_len; unsigned char security_passphrase_len; // End of wireless configuration parameters ---------------------------------------- // IP Address for api.pachube.com char pachube_ip[] = {173,203,98,29}; //const prog_char client_request[] PROGMEM = {" // POST /api/" PACHUBE_FEED_ID ".csv?_method=put HTTP/1.1 // Host: www.pachube.com // X-PachubeApiKey: " PACHUBE_API_KEY " // Content-Length: 21 // Connection: close // // status=Ready to sleep //"}; // Bigger than we need since we will append char client_request[256] = {"POST /api/" PACHUBE_FEED_ID ".csv?_method=put HTTP/1.1\r\nHost: www.pachube.com\r\nX-PachubeApiKey: " PACHUBE_API_KEY "\r\nConnection: close\r\nContent-Length: "}; void setup() { Serial.begin(57600); Serial.print("Initializing WiFi... "); // Weird, doing this raises readings by a bit - noise? WiFi.init(); Serial.println("done."); // Enable Serial output and ask WiServer to generate log messages (optional) 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); } 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; // Make sure WiServer task runs often WiFi.run(); } // 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; WiFi.run(); } // End of CTs loop if (debug_time) { Serial.print("Time to sample all CTs: "); Serial.println(millis() - start); } powerReadings++; WiFi.run(); // Check if it's time to send an external update of averages if (millis() >= updateTime) { char *appendLoc; char val_buf[32]; unsigned long timeNow = millis(); Serial.println("Updating pachube"); // Do another update PACHUBE_UPDATE_INTERVAL from now updateTime += PACHUBE_UPDATE_INTERVAL; // Get average power info since last external update powerSolar = -(sumRealPower[0] + sumRealPower[1]) / powerReadings; powerUtil = -(sumRealPower[2] + sumRealPower[3]) / powerReadings; if (powerSolar > 0) powerNet = powerSolar + powerUtil; else powerNet = powerUtil; memset(val_buf, 0, 32); sprintf(val_buf, "%d,%d,%d", powerNet, powerUtil, powerSolar); // Get location of "Content-Length:" and update with values appendLoc = strstr(client_request, "Content-Length:"); sprintf(appendLoc, "Content-Length: %d\r\n\r\n%s", strlen(val_buf), val_buf); Serial.println(client_request); webclient_get(pachube_ip, 80, "/"); WiFi.run(); // 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 powerSolar = realPower[0] + realPower[1]; powerUtil = realPower[2] + realPower[3]; if (powerSolar > 0) powerNet = powerSolar + powerUtil; else powerNet = powerUtil; Serial.print("Sample: "); } Serial.print("Net: "); Serial.print(powerNet); Serial.print(" Util: "); Serial.print(powerUtil); Serial.print(" Solar: "); Serial.println(powerSolar); // Run WiServer again for good measure WiFi.run(); }