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

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(&eth);
+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(&eth);
+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);