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

Embed: (wiki syntax)

« Back to documentation index

Show/hide line numbers main.cpp Source File

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 }