X10 Server - IOT device to leverage a collection of old X10 devices for home automation and lighting control.
Dependencies: IniManager mbed HTTPClient SWUpdate mbed-rtos Watchdog X10 SW_HTTPServer SW_String EthernetInterface TimeInterface SSDP
X10 Server
See the X10 Server Nodebook page
main.cpp
- Committer:
- WiredHome
- Date:
- 2019-03-03
- Revision:
- 10:ca0c1db6d933
- Parent:
- 9:2c96e69b6035
File content as of revision 10:ca0c1db6d933:
// // WattEye - Version 2 // /// WattEye monitors a simple input pin, measures the time between pulses, and translates /// that into a unit of power. The pulses are generated by the whole-house electric meter /// and may be generated by an IR Emitter. /// #include "mbed.h" // ver 120; mbed-rtos ver 111 #include "EthernetInterface.h" // ver 56 - https://os.mbed.com/users/WiredHome/code/EthernetInterface/ #include "SW_HTTPServer.h" // ver 58 #include "TimeInterface.h" // ver 25 #include "SWUpdate.h" // ver 26 #include "SW_String.h" // ver 2 #include "SSDP.h" // ver 7 #include "Watchdog.h" // ver 6 #include "IniManager.h" // v20 #include "StatisticQueue.h" #include "PowerData.h" #include "WebPages.h" // Private handler for web queries #include "SignOfLife.h" // LED effects extern "C" void mbed_reset(); //#define DEBUG "MAIN" #include <cstdio> #if (defined(DEBUG) && !defined(TARGET_LPC11U24)) #define DBG(x, ...) std::printf("[DBG %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define WARN(x, ...) std::printf("[WRN %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define ERR(x, ...) std::printf("[ERR %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #define INFO(x, ...) std::printf("[INF %s %3d] "x"\r\n", DEBUG, __LINE__, ##__VA_ARGS__); #else #define DBG(x, ...) #define WARN(x, ...) #define ERR(x, ...) #define INFO(x, ...) #endif RawSerial pc(USBTX, USBRX); EthernetInterface eth; DigitalOut linkup(p26); DigitalOut linkdata(p25); Watchdog wd; bool WDEventOccurred = false; TimeInterface ntp(ð); time_t ntpSyncd; // time of the last sync bool ntpUpdateCheck = true; // scheduled check on startup time_t lastboottime; bool swUpdateCheck = true; // scheduled check on startup INI ini; LocalFileSystem local("local"); // some place to hold settings and maybe the static web pages const char * Server_Root = "/local"; // public for the WebPages handler to see // const char * BUILD_DATE = __DATE__ " " __TIME__; const char * PROG_NAME = "WattEye-2"; char * My_Name = "WattEye-2"; const char * PROG_INFO = "WattEye-2 Build " __DATE__ " " __TIME__; const char * My_SerialNum = "0000001"; int Server_Port = 80; // end public information const char * iniFile = "/local/WattEye.ini"; HTTPClient http; DigitalOut PulseIndicator(LED1); DigitalOut UDPSendIndicator(LED2); DigitalOut URLSendIndicator(LED3); // Keep a sample every 5 s for 5 minutes // 12 samples / min * 5 min => 60 samples #define SampleInterval_Sec 5 #define SampleHistory_5m (60) StatisticQueue stats5s(SampleHistory_5m); // Keep 5 minute data for 1 day // 12 samples / hour * 24 hours => 288 #define SampleInterval_Min 5 #define SampleHistory_1d 288 StatisticQueue stats5m(SampleHistory_1d); char url[100], dest[20], port[8]; char myID[50]; InterruptIn event(p15); Timer timer; Timeout flash; typedef struct { time_t todClock; uint32_t tLastStart; uint32_t tLastRise; uint16_t Samples10s[30]; // Every 10s for 5 min uint16_t Samples10sIndex; uint16_t Samples5m[12*24]; // Every 5m for 1 day uint16_t Samples5mIndex; uint16_t Samples1d[365]; // Every uint16_t Samples1dIndex; } WattData; Atomic_t PowerSnapshot; typedef struct { bool init; time_t startTimestamp; uint64_t tStart; uint64_t tLastRise; uint64_t tStartSample; uint32_t cycles; } RawSample_t; RawSample_t RawPowerSample; void PulseRisingISR(void); void RunPulseTask(void); void TransmitEnergy(bool sendNow, float iKW, float min5s, float avg5s, float max5s, float min5m, float avg5m, float max5m); void ShowRawSample(); void ShowRawSample() { printf("Sample:\r\n"); printf(" Sample Start: %s\r\n", ntp.ctime(&RawPowerSample.startTimestamp)); printf(" tStart: %llu\r\n", RawPowerSample.tStart); printf(" tLastRise: %llu\r\n", RawPowerSample.tLastRise); printf(" tStartSample: %llu\r\n", RawPowerSample.tStartSample); printf(" cycles: %ul\r\n", RawPowerSample.cycles); } void SoftwareUpdateCheck(bool force = false) { char url[100], name[10]; static time_t tstart = ntp.timelocal(); time_t tNow = ntp.timelocal(); //eth_mutex.lock(); #define ONE_DAY (24 * 60 * 60) if (force || (tNow - tstart) > ONE_DAY) { pc.printf(" SoftwareUpdateCheck: %s\r\n", ntp.ctime(&tNow)); tstart = tNow; swUpdateCheck = true; if (INI::INI_SUCCESS == ini.ReadString("SWUpdate", "url", url, sizeof(url)) && INI::INI_SUCCESS == ini.ReadString("SWUpdate", "name", name, sizeof(name))) { linkdata = true; pc.printf(" url: %s\r\n", url); pc.printf(" name: %s\r\n", name); SWUpdate_T su = SoftwareUpdate(url, name, DEFER_REBOOT); if (SWUP_OK == su) { pc.printf(" update installed, rebooting...\r\n"); Thread::wait(3000); mbed_reset(); } else if (SWUP_SAME_VER == su) { pc.printf(" no update available.\r\n"); swUpdateCheck = false; } else { pc.printf(" update failed %04X - %s\r\n", su, SoftwareUpdateGetHTTPErrorMsg(SoftwareUpdateGetHTTPErrorCode())); Thread::wait(1000); swUpdateCheck = false; } linkdata = false; } else { pc.printf(" can't get info from ini file.\r\n"); swUpdateCheck = false; } //eth_mutex.unlock(); } } void ShowIPAddress(bool show = true) { char buf[16]; if (show) sprintf(buf, "%15s", eth.getIPAddress()); else sprintf(buf, "%15s", "---.---.---.---"); pc.printf("Ethernet connected as %s\r\n", buf); } /// This function syncs the node to a timeserver, if one /// is configured in the .ini file. /// void SyncToNTPServer(bool force) { char url[100]; char tzone[10]; char dstFlag[5]; // off,on,auto char dstStart[12]; // mm/dd,hh:mm char dstStop[12]; // mm/dd,hh:mm static time_t tlast = 0; time_t tnow = ntp.timelocal(); if (((tnow - tlast) > (60*60*24)) || force) { printf("SyncToNTPServer\r\n"); if (INI::INI_SUCCESS == ini.ReadString("Clock", "timeserver", url, sizeof(url))) { ini.ReadString("Clock", "tzoffsetmin", tzone, sizeof(tzone), "0"); ini.ReadString("Clock", "dst", dstFlag, sizeof(dstFlag), "0"); ini.ReadString("Clock", "dststart", dstStart, sizeof(dstStart), ""); ini.ReadString("Clock", "dststop", dstStop, sizeof(dstStop), ""); printf("NTP update time from (%s)\r\n", url); int32_t tzo_min = atoi(tzone); if (strcmp(dstFlag,"on") == 0) { ntp.set_dst(1); } else if (strcmp(dstFlag, "off") == 0) { ntp.set_dst(0); } else { /* if (strcmp(dstFlag, "auto") == 0) */ ntp.set_dst(dstStart,dstStop); } ntp.set_tzo_min(tzo_min); linkdata = true; int res = ntp.setTime(url); //printf(" NTP (release ethernet)\r\n"); if (res == 0) { time_t ctTime; ctTime = ntp.timelocal(); ntpSyncd = ntp.get_timelastset();; tlast = ctTime; printf(" Time set to (UTC): %s\r\n", ntp.ctime(&ctTime)); printf(" ntpSyncd: %s (UTC)\r\n", ntp.ctime(&ntpSyncd)); ntpUpdateCheck = false; } else { ntpSyncd = 0; printf("Error return from setTime(%s) %d\r\n", url, res); } linkdata = false; } else { ntpSyncd = 0; printf("no time server was set\r\n"); } } } /// This function monitors the USB serial interface for activity /// from a connected user. /// /// It offers a tiny bit of help if an unrecognized command is entered. /// void CheckConsoleInput(void) { static Timer timer; if (pc.readable()) { int c = pc.getc(); switch (c) { case 'r': mbed_reset(); break; case 's': swUpdateCheck = true; break; case 't': ntpUpdateCheck = true; break; case '@': pc.printf("Sample '%s' file.\r\n", iniFile); pc.printf("[SWUpdate]\r\n"); pc.printf("url=http://192.168.1.201/mbed/\r\n"); pc.printf("name=WattEye\r\n"); pc.printf("[Clock]\r\n"); pc.printf("timeserver=time.nist.gov\r\n"); pc.printf("tzoffsetmin=-300\r\n"); pc.printf("[IP]\r\n"); pc.printf("ip=192.168.1.204\r\n"); pc.printf("nm=255.255.254.0\r\n"); pc.printf("gw=192.168.1.1\r\n"); pc.printf("[Node]\r\n"); pc.printf("id=WattEye-01\r\n"); pc.printf(";hint: Use either the fixed IP or the Node\r\n"); break; case '?': ShowRawSample(); // break; default: pc.printf("\r\n\r\n"); if (c > ' ' && c != '?') pc.printf("unknown command '%c'\r\n", c); pc.printf("Commands:\r\n" " r = reset\r\n" " s = software update check\r\n" " t = time sync to NTP server\r\n" ); ShowIPAddress(); break; } } } int main() { char ip[20],nm[20],gw[20]; bool SensorStarted = false; pc.baud(460800); pc.printf("\r\n%s Build %s\r\n", PROG_NAME, BUILD_DATE); lastboottime = ntp.timelocal(); if (wd.WatchdogCausedReset()) { pc.printf("**** Watchdog Event caused reset at %s ****\r\n", ntp.ctime(&lastboottime)); WDEventOccurred = true; } wd.Configure(25); // very generous, but this is a network appliance, so a bit less deterministic. ini.SetFile(iniFile, 2); pc.printf("Initializing network interface...\r\n"); int initResult; if (INI::INI_SUCCESS == ini.ReadString("IP", "ip", ip, sizeof(ip)) && INI::INI_SUCCESS == ini.ReadString("IP", "nm", nm, sizeof(nm)) && INI::INI_SUCCESS == ini.ReadString("IP", "gw", gw, sizeof(gw))) { initResult = eth.init(ip,nm,gw); // use Fixed } else { initResult = eth.init(); // use DHCP } if (initResult) { // Failed to init ... pc.printf(" ... failed to initialize, rebooting...\r\n"); wait_ms(5000); mbed_reset(); } else { bool bEU = ini.ReadString("Energy", "url", url, sizeof(url)); bool bDS = ini.ReadString("Energy", "dest", dest, sizeof(dest)); bool bPO = ini.ReadString("Energy", "port", port, sizeof(port)); bool bID = ini.ReadString("Node", "id", myID, sizeof(myID)); if (INI::INI_SUCCESS == bEU && INI::INI_SUCCESS == bDS && INI::INI_SUCCESS == bPO && INI::INI_SUCCESS == bID) { pc.printf("Node %s\r\n", myID); pc.printf("URL %s\r\n", url); pc.printf("port %s\r\n", port); pc.printf("dest %s\r\n", dest); Server_Port = atoi(port); } eth.setName(myID); do { pc.printf("Connecting to network...\r\n"); if (0 == eth.connect()) { wd.Service(); linkup = true; time_t tstart = ntp.timelocal(); int speed = eth.get_connection_speed(); pc.printf("Ethernet Connected at: %d Mb/s\r\n", speed); pc.printf(" IP: %15s\r\n", eth.getIPAddress()); HTTPServer svr(Server_Port, Server_Root); svr.RegisterHandler("/", RootPage); svr.RegisterHandler("/info", InfoPage); svr.RegisterHandler("/software", SoftwarePage); svr.RegisterHandler("/reboot", RebootPage); svr.RegisterHandler("/setup.xml", Setup_xml); SSDP ssdp(myID, eth.getMACAddress(), eth.getIPAddress(), Server_Port); wait(5); if (!SensorStarted) { timer.start(); timer.reset(); event.rise(&PulseRisingISR); SensorStarted = true; printf("Sensor started\r\n"); } while (eth.is_connected()) { wd.Service(); time_t tNow = ntp.timelocal(); CheckConsoleInput(); RunPulseTask(); svr.Poll(); // Web Server: non-blocking, but also not deterministic ShowSignOfLife(); SyncToNTPServer(ntpUpdateCheck); SoftwareUpdateCheck(swUpdateCheck); // Any other work can happen here // ... Thread::yield(); } linkup = false; linkdata = false; pc.printf("lost connection.\r\n"); ShowIPAddress(false); eth.disconnect(); } else { pc.printf(" ... failed to connect.\r\n"); } } while (1); } } void LedOff(void) { PulseIndicator = 0; } void PulseRisingISR(void) { uint64_t tNow = timer.read_us(); __disable_irq(); if (!RawPowerSample.init) { RawPowerSample.init = true; RawPowerSample.cycles = (uint32_t)-1; RawPowerSample.tStart = tNow; RawPowerSample.tLastRise = tNow; RawPowerSample.startTimestamp = ntp.timelocal(); } RawPowerSample.cycles++; RawPowerSample.tStartSample = RawPowerSample.tLastRise; RawPowerSample.tLastRise = tNow; __enable_irq(); PulseIndicator = 1; flash.attach_us(&LedOff, 25000); } void RunPulseTask(void) { static time_t timeFor5s = 0; static time_t timeFor5m = 0; //static uint32_t lastCount = 0; time_t timenow = ntp.timelocal(); float iKW = 0.0f; bool sendToWeb = false; __disable_irq(); uint32_t elapsed = RawPowerSample.tLastRise - RawPowerSample.tStartSample; //uint32_t count = RawPowerSample.cycles; __enable_irq(); if (elapsed) { // instantaneous, from this exact sample iKW = (float)3600 * 1000 / elapsed; } if (timeFor5s == 0 || timenow < timeFor5s) // startup or if something goes really bad timeFor5s = timenow; if (timeFor5m == 0 || timenow < timeFor5m) // startup or if something goes really bad timeFor5m = timenow; if ((timenow - timeFor5m) >= 60) { // 300) { //pc.printf(" tnow: %d, t5m: %d\r\n", timenow, timeFor5m); sendToWeb = true; timeFor5s = timeFor5m = timenow; stats5s.EnterItem(iKW); stats5m.EnterItem(stats5s.Average()); TransmitEnergy(true, iKW, stats5s.Min(), stats5s.Average(), stats5s.Max(), stats5m.Min(), stats5m.Average(), stats5m.Max()); } else if ((timenow - timeFor5s) >= 5) { sendToWeb = true; timeFor5s = timenow; stats5s.EnterItem(iKW); TransmitEnergy(false, iKW, stats5s.Min(), stats5s.Average(), stats5s.Max(), stats5m.Min(), stats5m.Average(), stats5m.Max()); } if (sendToWeb) { // count != lastCount) { //lastCount = count; pc.printf("%8.3fs => %4.3f (%4.3f,%4.3f,%4.3f) iKW, (%4.3f,%4.3f,%4.3f) KW 5m\r\n", (float)elapsed/1000000, iKW, stats5s.Min(), stats5s.Average(), stats5s.Max(), stats5m.Min(), stats5m.Average(), stats5m.Max()); } } void TransmitEnergy(bool sendNow, float iKW, float min5s, float avg5s, float max5s, float min5m, float avg5m, float max5m) { char data[150]; char fullurl[250]; if (*url) { 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", myID, iKW, min5s, avg5s, max5s, min5m, avg5m, max5m); //eth_mutex.lock(); linkdata = true; // Send the UDP Broadcast, picked up by a listener UDPSendIndicator = true; UDPSocket bcast; Endpoint ep; int h = ep.set_address(dest, Server_Port); int i = bcast.bind(Server_Port); int j = bcast.set_broadcasting(true); int k = bcast.sendTo(ep, data, strlen(data)); bcast.close(); UDPSendIndicator = false; // On the 5-minute interval, post the data to a specified web server if (sendNow && *url) { //HTTPClient http; char buf[50]; URLSendIndicator = true; snprintf(fullurl, 250, "%s?%s", url, data); pc.printf("Contacting %s\r\n", fullurl); http.setMaxRedirections(3); int x = http.get(fullurl, buf, sizeof(buf)); URLSendIndicator = false; pc.printf(" return: %d\r\n", x); } linkdata = false; //eth_mutex.unlock(); } }