Watt Eye has a simple purpose - monitor pulses that comes from the home electric meter, measure the interval between the pulses and compute the real-time energy being consumed, broadcast that onto the network using UDP packets so that CouchCalendar has something to do and display, and publish the data to a web server, where it can be used (graphed or placed into a db).
Dependencies: IniManager mbed HTTPClient SWUpdate StatisticQueue mbed-rtos NTPClient Watchdog SW_HTTPServer EthernetInterface TimeInterface
main.cpp
00001 #include "mbed.h" // v83, RTOS 38 00002 #include "RawSerial.h" // ? 00003 00004 // My libs 00005 #include "TimeInterface.h" // ver 3 00006 #include "HTTPClient.h" // ver 0 00007 #include "IniManager.h" // ver 9 00008 #include "SWUpdate.h" // ver 17 00009 #include "Watchdog.h" // ver 2 00010 #include "StatisticQueue.h" 00011 00012 //#define WIFLY 00013 #define HW_ADAPTER SMART_BOARD /* Which board are we compiling against? */ 00014 00015 #ifdef WIFLY 00016 #include "WiflyInterface.h" 00017 #else 00018 #include "EthernetInterface.h" // ver 51 00019 #endif 00020 00021 #include "SW_HTTPServer.h" 00022 00023 extern "C" void mbed_reset(); 00024 00025 //#define DEBUG "MAIN" 00026 #include <cstdio> 00027 #if (defined(DEBUG) && !defined(TARGET_LPC11U24)) 00028 #define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); 00029 #define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); 00030 #define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); 00031 #define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); 00032 #else 00033 #define DBG(x, ...) 00034 #define WARN(x, ...) 00035 #define ERR(x, ...) 00036 #define INFO(x, ...) 00037 #endif 00038 00039 #define TIME_TO_CHECK_SW_UPDATE (60*60) /* once per hour */ 00040 00041 EthernetInterface eth; 00042 Mutex eth_mutex; 00043 00044 Watchdog wd; 00045 00046 RawSerial pc(USBTX, USBRX); 00047 LocalFileSystem local("local"); 00048 INI ini; 00049 00050 DigitalOut linkup(p26); 00051 DigitalOut linkdata(p25); 00052 00053 TimeInterface ntp; 00054 HTTPClient http; 00055 00056 // Keep a sample every 5 s for 5 minutes 00057 // 12 samples / min * 5 min => 60 samples 00058 #define SampleInterval_Sec 5 00059 #define SampleHistory_5m (60) 00060 StatisticQueue stats5s(SampleHistory_5m); 00061 00062 // Keep 5 minute data for 1 day 00063 // 12 samples / hour * 24 hours => 288 00064 #define SampleInterval_Min 5 00065 #define SampleHistory_1d 288 00066 StatisticQueue stats5m(SampleHistory_1d); 00067 00068 const char * PROG_INFO = "Watt Eye: " __DATE__ ", " __TIME__; 00069 const char * iniFile = "/local/WattEye.ini"; 00070 00071 00072 DigitalOut PulseIndicator(LED1); 00073 DigitalOut UDPSendIndicator(LED2); 00074 DigitalOut URLSendIndicator(LED3); 00075 PwmOut signOfLife(LED4); 00076 00077 InterruptIn event(p15); 00078 Timer timer; 00079 Timeout flash; 00080 00081 typedef struct 00082 { 00083 time_t todClock; 00084 uint32_t tLastStart; 00085 uint32_t tLastRise; 00086 uint16_t Samples10s[30]; // Every 10s for 5 min 00087 uint16_t Samples10sIndex; 00088 uint16_t Samples5m[12*24]; // Every 5m for 1 day 00089 uint16_t Samples5mIndex; 00090 uint16_t Samples1d[365]; // Every 00091 uint16_t Samples1dIndex; 00092 } WattData; 00093 00094 typedef struct 00095 { 00096 float instantKW; 00097 float averageKW; 00098 uint32_t measuredCycles; 00099 } Atomic_t; 00100 00101 Atomic_t PowerSnapshot; 00102 00103 typedef struct 00104 { 00105 bool init; 00106 time_t startTimestamp; 00107 uint64_t tStart; 00108 uint64_t tLastRise; 00109 uint64_t tStartSample; 00110 uint32_t cycles; 00111 } RawSample_t; 00112 00113 RawSample_t RawPowerSample; 00114 00115 //uint64_t tElapsedFive; 00116 //uint32_t cycleFive; 00117 00118 00119 00120 void SoftwareUpdateCheck(bool force = false) 00121 { 00122 static time_t tLastCheck; 00123 char url[100], name[10]; 00124 time_t tCheck = ntp.time(); 00125 00126 if (tCheck < tLastCheck) 00127 force = true; // guard against bad stuff that would prevent updates 00128 00129 if ((tCheck - tLastCheck > TIME_TO_CHECK_SW_UPDATE) || force) { 00130 tLastCheck = tCheck; 00131 eth_mutex.lock(); 00132 pc.printf("SoftwareUpdateCheck\r\n"); 00133 if (ini.ReadString("SWUpdate", "url", url, sizeof(url)) 00134 && ini.ReadString("SWUpdate", "name", name, sizeof(name))) { 00135 //pc.printf("SW Check(%s,%s)\r\n", url, name); 00136 SWUpdate_T su = SoftwareUpdate(url, name, DEFER_REBOOT); 00137 if (SWUP_OK == su) { 00138 eth_mutex.unlock(); 00139 pc.printf(" new software installed, restarting...\r\n"); 00140 Thread::wait(3000); 00141 mbed_reset(); 00142 } else if (SWUP_SAME_VER == su) { 00143 pc.printf(" no update available.\r\n"); 00144 } else { 00145 pc.printf(" update failed %04X, http %d\r\n", su, SoftwareUpdateGetHTTPErrorCode()); 00146 } 00147 } else { 00148 pc.printf(" can't get info from ini file.\r\n"); 00149 eth_mutex.unlock(); 00150 } 00151 } 00152 } 00153 00154 void ShowIPAddress(bool show = true) 00155 { 00156 char buf[16]; 00157 00158 if (show) 00159 sprintf(buf, "%15s", eth.getIPAddress()); 00160 else 00161 sprintf(buf, "%15s", "---.---.---.---"); 00162 pc.printf("Ethernet connected as %s\r\n", buf); 00163 } 00164 00165 00166 00167 bool SyncToNTPServer(void) 00168 { 00169 char url[100]; 00170 char tzone[10]; 00171 00172 if (ini.ReadString("Clock", "timeserver", url, sizeof(url))) { 00173 ini.ReadString("Clock", "tzoffsetmin", tzone, sizeof(tzone), "0"); 00174 00175 time_t tls = ntp.get_timelastset(); 00176 //time_t tnow = ntp.time(); 00177 //int32_t tcr = ntp.get_cal(); 00178 eth_mutex.lock(); 00179 pc.printf("NTP update time from (%s)\r\n", url); 00180 linkdata = true; 00181 int32_t tzo_min = atoi(tzone); 00182 ntp.set_tzo_min(tzo_min); 00183 int res = ntp.setTime(url); 00184 eth_mutex.unlock(); 00185 linkdata = false; 00186 if (res == 0) { 00187 time_t ctTime; 00188 ctTime = ntp.timelocal(); 00189 pc.printf(" Time set to (UTC): %s\r\n", ntp.ctime(&ctTime)); 00190 return true; 00191 } else { 00192 pc.printf("Error %d\r\n", res); 00193 } 00194 } else { 00195 pc.printf("no time server was set\r\n"); 00196 } 00197 return false; 00198 } 00199 00200 void TransmitEnergy(bool sendNow, float iKW, float min5s, float avg5s, float max5s, float min5m, float avg5m, float max5m) 00201 { 00202 char url[100], dest[20], port[8]; 00203 char data[150]; 00204 char myID[50]; 00205 char fullurl[250]; 00206 bool bEU = ini.ReadString("Energy", "url", url, sizeof(url)); 00207 bool bDS = ini.ReadString("Energy", "dest", dest, sizeof(dest)); 00208 bool bPO = ini.ReadString("Energy", "port", port, sizeof(port)); 00209 bool bID = ini.ReadString("Node", "id", myID, sizeof(myID)); 00210 00211 if (bEU && bDS && bPO && bID) { 00212 snprintf(data, 150, "ID=%s&iKW=%5.3f&min5s=%5.3f&avg5s=%5.3f&max5s=%5.3f&min5m=%5.3f&avg5m=%5.3f&max5m=%5.3f", 00213 myID, iKW, min5s, avg5s, max5s, min5m, avg5m, max5m); 00214 eth_mutex.lock(); 00215 // Send the UDP Broadcast, picked up by a listener 00216 UDPSendIndicator = true; 00217 UDPSocket bcast; 00218 Endpoint ep; 00219 int h = ep.set_address(dest, atoi(port)); 00220 int i = bcast.bind(atoi(port)); 00221 int j = bcast.set_broadcasting(true); 00222 int k = bcast.sendTo(ep, data, strlen(data)); 00223 bcast.close(); 00224 UDPSendIndicator = false; 00225 // On the 5-minute interval, post the data to a specified web server 00226 if (sendNow && *url) { 00227 //HTTPClient http; 00228 char buf[50]; 00229 URLSendIndicator = true; 00230 snprintf(fullurl, 250, "%s?%s", url, data); 00231 pc.printf("Contacting %s\r\n", fullurl); 00232 http.setMaxRedirections(3); 00233 int x = http.get(fullurl, buf, sizeof(buf)); 00234 URLSendIndicator = false; 00235 pc.printf(" return: %d\r\n", x); 00236 } 00237 eth_mutex.unlock(); 00238 } 00239 } 00240 00241 00242 /// ShowSignOfLife 00243 /// 00244 /// Pulse an LED to indicate a sign of life of the program. 00245 /// This also has some moderate entertainment value. 00246 /// 00247 void ShowSignOfLife() 00248 { 00249 #define PI 3.14159265359 00250 static Timer activityTimer; 00251 static unsigned int activityStart; 00252 static bool init; 00253 static int degrees = 0; 00254 float v; 00255 00256 if (!init) { 00257 activityTimer.start(); 00258 activityStart = (unsigned int) activityTimer.read_ms(); 00259 init = true; 00260 } 00261 if ((unsigned int)activityTimer.read_ms() - activityStart > 20) { 00262 00263 v = sin(degrees * PI / 180); 00264 if (v < 0) 00265 v = 0; 00266 signOfLife = v; 00267 degrees += 5; 00268 activityStart = (unsigned int) activityTimer.read_ms(); 00269 } 00270 } 00271 00272 void LedOff(void) 00273 { 00274 PulseIndicator = 0; 00275 } 00276 00277 void CheckConsoleInput(void) 00278 { 00279 if (pc.readable()) { 00280 int c = pc.getc(); 00281 switch (c) { 00282 case 'r': 00283 mbed_reset(); 00284 break; 00285 case 's': 00286 SoftwareUpdateCheck(true); 00287 break; 00288 case 't': 00289 SyncToNTPServer(); 00290 break; 00291 default: 00292 pc.printf("unknown command '%c'\r\n", c); 00293 case ' ': 00294 case '\r': 00295 case '\n': 00296 pc.printf("Commands:\r\n" 00297 " r = reset\r\n" 00298 " s = software update check\r\n" 00299 " t = time sync to NTP server\r\n" 00300 ); 00301 ShowIPAddress(); 00302 00303 break; 00304 } 00305 } 00306 } 00307 00308 bool NetworkIsConnected(void) 00309 { 00310 return eth.is_connected(); 00311 } 00312 00313 void PulseRisingISR(void) 00314 { 00315 uint64_t tNow = timer.read_us(); 00316 00317 __disable_irq(); 00318 if (!RawPowerSample.init) { 00319 RawPowerSample.init = true; 00320 RawPowerSample.cycles = (uint32_t)-1; 00321 RawPowerSample.tStart = tNow; 00322 RawPowerSample.tLastRise = tNow; 00323 RawPowerSample.startTimestamp = ntp.time(); 00324 } 00325 RawPowerSample.cycles++; 00326 RawPowerSample.tStartSample = RawPowerSample.tLastRise; 00327 RawPowerSample.tLastRise = tNow; 00328 __enable_irq(); 00329 PulseIndicator = 1; 00330 flash.attach_us(&LedOff, 25000); 00331 } 00332 00333 void RunPulseTask(void) 00334 { 00335 static time_t timeFor5s = 0; 00336 static time_t timeFor5m = 0; 00337 static uint32_t lastCount = 0; 00338 time_t timenow = ntp.time(); 00339 float iKW = 0.0f; 00340 bool sendToWeb = false; 00341 00342 __disable_irq(); 00343 uint32_t elapsed = RawPowerSample.tLastRise - RawPowerSample.tStartSample; 00344 uint32_t count = RawPowerSample.cycles; 00345 __enable_irq(); 00346 00347 if (elapsed) { 00348 // instantaneous, from this exact sample 00349 iKW = (float)3600 * 1000 / elapsed; 00350 } 00351 if (timeFor5s == 0 || timenow < timeFor5s) // startup or if something goes really bad 00352 timeFor5s = timenow; 00353 if (timeFor5m == 0 || timenow < timeFor5m) // startup or if something goes really bad 00354 timeFor5m = timenow; 00355 00356 if ((timenow - timeFor5m) >= 60) { // 300) { 00357 //pc.printf(" tnow: %d, t5m: %d\r\n", timenow, timeFor5m); 00358 sendToWeb = true; 00359 timeFor5s = timeFor5m = timenow; 00360 stats5s.EnterItem(iKW); 00361 stats5m.EnterItem(stats5s.Average()); 00362 TransmitEnergy(true, iKW, stats5s.Min(), stats5s.Average(), stats5s.Max(), 00363 stats5m.Min(), stats5m.Average(), stats5m.Max()); 00364 } else if ((timenow - timeFor5s) >= 5) { 00365 sendToWeb = true; 00366 timeFor5s = timenow; 00367 stats5s.EnterItem(iKW); 00368 TransmitEnergy(false, iKW, stats5s.Min(), stats5s.Average(), stats5s.Max(), 00369 stats5m.Min(), stats5m.Average(), stats5m.Max()); 00370 } 00371 if (sendToWeb) { // count != lastCount) { 00372 lastCount = count; 00373 pc.printf("%8.3fs => %4.3f (%4.3f,%4.3f,%4.3f) iKW, (%4.3f,%4.3f,%4.3f) KW 5m\r\n", 00374 (float)elapsed/1000000, 00375 iKW, 00376 stats5s.Min(), stats5s.Average(), stats5s.Max(), 00377 stats5m.Min(), stats5m.Average(), stats5m.Max()); 00378 } 00379 } 00380 00381 /// SimplyDynamicPage1 00382 /// 00383 /// This web page is generated dynamically as a kind of "bare minimum". 00384 /// It doesn't do much. 00385 /// 00386 /// You can see in main how this page was registered. 00387 /// 00388 HTTPServer::CallBackResults SuperSimpleDynamicPage(HTTPServer *svr, HTTPServer::CallBackType type, 00389 const char * path, const HTTPServer::namevalue *params, int paramcount) 00390 { 00391 HTTPServer::CallBackResults ret = HTTPServer::ACCEPT_ERROR; 00392 char contentlen[30]; 00393 char buf[500]; 00394 char linebuf[100]; 00395 00396 switch (type) { 00397 case HTTPServer::SEND_PAGE: 00398 // This sample drops it all into a local buffer, computes the length, 00399 // and passes that along as well. This can help the other end with efficiency. 00400 strcpy(buf, "<html><head><title>Smart WattEye/title></head>\r\n"); 00401 strcat(buf, "<body>\r\n"); 00402 strcat(buf, "<h1>Smart WattEye</h1>\r\n"); 00403 strcat(buf, "<table>"); 00404 snprintf(linebuf, sizeof(linebuf), "<tr><td>Instantaneous</td><td align='right'>%5.3f.</td></tr>\r\n", 00405 PowerSnapshot.instantKW); 00406 strcat(buf, linebuf); 00407 snprintf(linebuf, sizeof(linebuf), "<tr><td>Average</td><td align='right'>%5.3f</td></tr>\r\n", 00408 PowerSnapshot.averageKW); 00409 strcat(buf, linebuf); 00410 snprintf(linebuf, sizeof(linebuf), "<tr><td>Total Cycles</td><td align='right'>%10u</td></tr>\r\n", 00411 PowerSnapshot.measuredCycles); 00412 strcat(buf, linebuf); 00413 strcat(buf, "</table>"); 00414 strcat(buf, "<a href='/'>back to main</a></body></html>\r\n"); 00415 sprintf(contentlen, "Content-Length: %d\r\n", strlen(buf)); 00416 // Now the actual header response 00417 svr->header(HTTPServer::OK, "OK", "Content-Type: text/html\r\n", contentlen); 00418 // and data are sent 00419 svr->send(buf); 00420 ret = HTTPServer::ACCEPT_COMPLETE; 00421 break; 00422 case HTTPServer::CONTENT_LENGTH_REQUEST: 00423 ret = HTTPServer::ACCEPT_COMPLETE; 00424 break; 00425 case HTTPServer::DATA_TRANSFER: 00426 ret = HTTPServer::ACCEPT_COMPLETE; 00427 break; 00428 default: 00429 ret = HTTPServer::ACCEPT_ERROR; 00430 break; 00431 } 00432 return ret; 00433 } 00434 00435 00436 int main() 00437 { 00438 bool SensorStarted = false; 00439 pc.baud(460800); 00440 pc.printf("\r\n%s\r\n", PROG_INFO); 00441 00442 if (wd.WatchdogCausedReset()) { 00443 pc.printf("**** Watchdog Event caused reset ****\r\n"); 00444 } 00445 wd.Configure(30.0); // nothing should take more than 30 s we hope. 00446 ini.SetFile(iniFile); 00447 // Thread bcThread(Scheduler_thread, NULL, osPriorityHigh); 00448 00449 // Now let's instantiate the web server - along with a few settings: 00450 // the Wifly object, the port of interest (typically 80), 00451 // file system path to the static pages, 00452 // the maximum parameters per transaction (in the query string), 00453 // the maximum number of dynamic pages that can be registered, 00454 // the serial port back thru USB (for development/logging) 00455 //HTTPServer svr(NULL, 80, "/Local/", 15, 30, 10, &pc); 00456 00457 // But for even more fun, I'm registering a few dynamic pages 00458 // You see the handlers for in DynamicPages.cpp. 00459 // Here you can see the path to place on the URL. 00460 // ex. http://192.168.1.140/dyn 00461 //svr.RegisterHandler("/dyn", SuperSimpleDynamicPage); 00462 00463 00464 pc.printf("***\r\n"); 00465 pc.printf("Initializing network interface...\r\n"); 00466 00467 int res; 00468 char ip[20], mask[20], gw[20]; 00469 bool bIP, bMask, bGW; 00470 bIP = ini.ReadString("Network", "addr", ip, 20, ""); 00471 bMask = ini.ReadString("Network", "mask", mask, 20, ""); 00472 bGW = ini.ReadString("Network", "gate", gw, 20, ""); 00473 00474 if (bIP && bMask && bGW) { 00475 res = eth.init(ip,mask,gw); 00476 } else { 00477 res = eth.init(); 00478 } 00479 if (0 == res) { // Interface set 00480 do { 00481 pc.printf("Connecting to network...\r\n"); 00482 if (0 == eth.connect()) { 00483 linkup = true; 00484 ShowIPAddress(true); 00485 int speed = eth.get_connection_speed(); 00486 pc.printf("Connected at %d Mb/s\r\n", speed); 00487 SoftwareUpdateCheck(true); 00488 SyncToNTPServer(); // we hope to have the right time of day now 00489 wait(5); 00490 if (!SensorStarted) { 00491 timer.start(); 00492 timer.reset(); 00493 event.rise(&PulseRisingISR); 00494 SensorStarted = true; 00495 } 00496 while (NetworkIsConnected()) { 00497 Thread::wait(5); 00498 linkdata = !linkdata; 00499 // Here's the real core of the main loop 00500 RunPulseTask(); 00501 //svr.Poll(); 00502 CheckConsoleInput(); 00503 ShowSignOfLife(); 00504 SoftwareUpdateCheck(); 00505 wd.Service(); 00506 } 00507 linkup = false; 00508 pc.printf("lost connection.\r\n"); 00509 ShowIPAddress(false); 00510 eth.disconnect(); 00511 } 00512 else { 00513 pc.printf(" ... failed to connect.\r\n"); 00514 } 00515 CheckConsoleInput(); 00516 } 00517 while (1); 00518 } 00519 else { 00520 pc.printf(" ... failed to initialize, rebooting...\r\n"); 00521 mbed_reset(); 00522 } 00523 00524 }
Generated on Fri Jul 15 2022 06:44:22 by 1.7.2