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
Diff: main.cpp
- Revision:
- 7:16129d213e6a
- Parent:
- 6:80a97f156128
- Child:
- 8:a45fe77efcc5
--- a/main.cpp Sat Sep 01 01:46:33 2018 +0000 +++ b/main.cpp Wed Feb 27 18:24:32 2019 +0000 @@ -1,119 +1,378 @@ // -// A simple SSDP example +// X10 Server - Version 2 // +/// If you've wired the CM17a to the same mbed pins, you should be able to simply +/// install this application, and then install a properly configured ini file +/// on the local file system. +/// + #include "mbed.h" // ver 120; mbed-rtos ver 111 -#include "EthernetInterface.h" // ver 56 -#include "SW_HTTPServer.h" // ver 57 -#include "TimeInterface.h" // ver 24 +#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 "SW_String.h" // ver 1 -#include "SSDP.h" // ver 4 +#include "IniManager.h" // v20 +#include "X10.h" // v1 +#include "X10Server.h" // v1 + #include "WebPages.h" // Private handler for web queries +#include "SignOfLife.h" // LED effects -// Comment out the next line if you do not have an RA8875 Display -#include "RA8875.h" // ver 154 +extern "C" void mbed_reset(); + +X10Interface cm17a(p5,p12); // cm17a.ParseCommand(buf); RawSerial pc(USBTX, USBRX); EthernetInterface eth; -TimeInterface ntp(ð); +DigitalOut linkup(p26); +DigitalOut linkdata(p25); + Watchdog wd; -time_t lastboottime; +bool WDEventOccurred = false; -#ifdef RA8875_H -#define LCD_W 480 -#define LCD_H 272 -#define LCD_C 16 -RA8875 lcd(p5, p6, p7, p12, NC, p9,p10,p13, "tft"); // SPI:{MOSI,MISO,SCK,/ChipSelect,/reset}, I2C:{SDA,SCL,/IRQ}, name -#endif +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 = "SSDP Server"; -char * My_Name = "MBED SSDP Node"; +const char * PROG_NAME = "X10-Server-2"; +char * My_Name = "X10-Server-2"; const char * My_SerialNum = "0000001"; int Server_Port = 80; // end public information + +const char * iniFile = "/local/X10svr.ini"; + + + +/// This function uses the settings in the .ini file to check for +/// and install, a software update that might be available. +/// +void SoftwareUpdateCheck(bool force) +{ + char url[100], name[10]; + static time_t tstart = ntp.time(); + time_t tNow = ntp.time(); + + //eth_mutex.lock(); + #define ONE_DAY (24 * 60 * 60) + if (force || (tNow - tstart) > ONE_DAY) { + pc.printf("SoftwareUpdateCheck at %s (UTC)\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\r\n", su); + Thread::wait(1000); + } + linkdata = false; + } else { + pc.printf(" can't get info from ini file.\r\n"); + swUpdateCheck = false; + } + //eth_mutex.unlock(); + } +} + + +/// 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.time(); + + 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); + int res = ntp.setTime(url); + //printf(" NTP (release ethernet)\r\n"); + if (res == 0) { + time_t ctTime; + ctTime = ntp.time(); + ntpSyncd = ntp.get_timelastset();; + tlast = ctTime; + printf(" Time set to (UTC): %s\r\n", ntp.ctime(&ctTime)); + printf(" ntpSyncd: %s\r\n", ntp.ctime(&ntpSyncd)); + } else { + ntpSyncd = 0; + printf("Error return from setTime(%s) %d\r\n", url, res); + } + } 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; + static bool test = false; + static bool toggle = false; + static char buf[80]; + static int i; + + if (pc.readable()) { + int c = pc.getc(); + test = false; + switch (c) { + case 'r': + mbed_reset(); + break; + case 's': + swUpdateCheck = true; + break; + case 't': + ntpUpdateCheck = true; + break; + case 'x': + pc.printf("x10>"); + i = 0; + do { + c = pc.getc(); + pc.putc(c); + if (c == '\x08') { // <bs> + if (i < 0) { + pc.printf("\r\n"); + break; + } + } else if (c == '\r') { + buf[i++] = '\0'; + printf("Shell Command: '%s'\r\n", buf); + cm17a.ParseCommand(buf); + break; + } else { + buf[i++] = c; + } + } while(1); + break; + case 'z': + pc.printf("X10 test mode enabled\r\n"); + test = true; + timer.start(); + 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=X10svr\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.203\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=X10Server-01\r\n"); + pc.printf(";hint: Use either the fixed IP or the Node\r\n"); + break; + case '?': + default: + pc.printf("\r\n\r\n"); + if (c > ' ' && c != '?') + pc.printf("unknown command '%c'\r\n", c); + pc.printf("IP: %s\r\n", eth.getIPAddress()); + pc.printf(" Last Boot %s %s\r\n", + ntp.ctime(&lastboottime), + (WDEventOccurred) ? "(WD event)" : ""); + pc.printf("\r\n"); + pc.printf("Valid commands:\r\n"); + pc.printf(" r - reset\r\n"); + pc.printf(" s - software update\r\n"); + pc.printf(" t - time server sync\r\n"); + pc.printf(" x <House><Unit> <cmd> | x # | /XXXX\r\n"); + pc.printf(" a-p House\r\n"); + pc.printf(" 1 - 16 Unit\r\n"); + pc.printf(" 1=On,0=Off,+#=Bright,-#=Dim (#=1 to 6)\r\n"); + pc.printf(" ex: x a1 1 a3 +2\r\n"); + pc.printf(" # = set baud rate\r\n"); + pc.printf(" /XXXX send hex code XXXX\r\n"); + pc.printf(" z - x10 test mode (toggles a1 on/off)\r\n"); + pc.printf(" @ - Show a sample '%s' file.\r\n", iniFile); + pc.printf("\r\n"); + break; + } + } else { + if (test) { + if (timer.read_ms() > 1000) { + timer.reset(); + pc.printf(" Test Mode: Sending a1 %d\r\n", toggle); + if (toggle) { + cm17a.ParseCommand("a1 1"); + } else { + cm17a.ParseCommand("a1 0"); + } + toggle = !toggle; + } + } + } +} + + +/// This handler is registered for callbacks from the X10server. +/// +/// It has only the simple responsibility of passing the command +/// forward to the CM17a driver. As a useful side-effect, it +/// blinks the Network interface data LED. +/// +void x10Handler(char * buffer, int size) +{ + time_t ctTime; + + ctTime = ntp.time(); + linkdata = true; + pc.printf("X10 (%6s) %s (UTC)\r\n", buffer, ntp.ctime(&ctTime)); + cm17a.ParseCommand(buffer); + wait_ms(100); + linkdata = false; +} + + + int main() { + char ip[20],nm[20],gw[20]; 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(45); // very generous, but this is a network appliance, so a bit less deterministic. + wd.Configure(25); // very generous, but this is a network appliance, so a bit less deterministic. + + ini.SetFile(iniFile, 2); -#ifdef RA8875_H - lcd.init(LCD_W,LCD_H,LCD_C, 60); - lcd.Backlight_u8(30); - lcd.foreground(Red); - lcd.printf("Initializing network interface...\r\n"); -#endif pc.printf("Initializing network interface...\r\n"); - if (0 == eth.init()) { -#ifdef RA8875_H - lcd.printf(" Name: %s\r\n", My_Name); -#endif - pc.printf("Name: %s\r\n", My_Name); - eth.setName(My_Name); + 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 { + char * nn = (char *)malloc(33); + if (!nn) + error("no mem for network name"); + ini.ReadString("Node", "id", nn, 32, "Name Me"); + pc.printf("Name: %s\r\n", nn); + eth.setName(nn); + + char * port = (char *)malloc(33); + uint16_t portNum = 10630; // X10 Listener Port + if (!port) + error("no mem for port"); + if (INI::INI_SUCCESS == ini.ReadString("IP", "port", port, sizeof(port))) + portNum = atoi(port); do { -#ifdef RA8875_H - lcd.printf(" Connecting to network...\r\n"); -#endif pc.printf("Connecting to network...\r\n"); if (0 == eth.connect()) { + wd.Service(); + linkup = true; + time_t tstart = ntp.time(); int speed = eth.get_connection_speed(); - pc.printf("Connected at %d Mb/s\r\n", speed); - pc.printf("IP: %15s\r\n", eth.getIPAddress()); -#ifdef RA8875_H - lcd.printf(" Connected at %d Mb/s\r\n", speed); - lcd.printf(" IP: %15s\r\n", eth.getIPAddress()); -#endif + 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, 15, 30, 20, 50, &pc); svr.RegisterHandler("/", RootPage); + svr.RegisterHandler("/info", InfoPage); + svr.RegisterHandler("/software", SoftwarePage); svr.RegisterHandler("/setup.xml", Setup_xml); SSDP ssdp(My_Name, eth.getMACAddress(), eth.getIPAddress(), Server_Port); - ntp.set_dst("3/11,2:00","11/4,2:00"); // mm/dd,hh:mm - ntp.set_tzo_min(-360); - int res = ntp.setTime("pool.ntp.org"); + pc.printf(" X10 Server started %s (UTC)\r\n", ntp.ctime(&tstart)); + X10Server x10svr(&x10Handler, portNum); while (eth.is_connected()) { + static time_t tLastSec; + wd.Service(); - static time_t tLast; time_t tNow = ntp.timelocal(); - if (tNow != tLast) { -#ifdef RA8875_H - lcd.SetTextFontSize(2); - lcd.SetTextCursor(20,120); - lcd.printf("%s", ntp.ctime(&tNow)); - lcd.SetTextFontSize(0); -#endif + CheckConsoleInput(); + x10svr.poll(); + svr.Poll(); // Web Server: non-blocking, but also not deterministic + ShowSignOfLife(1); + if (tNow != tLastSec) { pc.printf("time is %s\r\n", ntp.ctime(&tNow)); - tLast = tNow; + tLastSec = tNow; } + SyncToNTPServer(ntpUpdateCheck); + SoftwareUpdateCheck(swUpdateCheck); // Any other work can happen here + // ... Thread::yield(); } -#ifdef RA8875_H - lcd.cls(); - lcd.printf("lost network.\r\n"); -#endif + linkup = false; + linkdata = false; pc.printf("lost connection.\r\n"); eth.disconnect(); } else { -#ifdef RA8875_H - lcd.cls(); - lcd.printf(" ... failed to connect\r\n"); -#endif pc.printf(" ... failed to connect.\r\n"); } } while (1);