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-02-27
Revision:
7:16129d213e6a
Parent:
6:80a97f156128
Child:
8:a45fe77efcc5

File content as of revision 7:16129d213e6a:

//
// 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 - 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 "X10.h"                // v1
#include "X10Server.h"          // v1

#include "WebPages.h"           // Private handler for web queries
#include "SignOfLife.h"         // LED effects

extern "C" void mbed_reset();

X10Interface cm17a(p5,p12);    // cm17a.ParseCommand(buf);

RawSerial pc(USBTX, USBRX);
EthernetInterface eth;
DigitalOut linkup(p26);
DigitalOut linkdata(p25);

Watchdog wd;
bool WDEventOccurred = false;

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 = "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(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 {
        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 {
            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("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);

                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();
                    time_t tNow = ntp.timelocal();
                    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));
                        tLastSec = tNow;
                    }
                    SyncToNTPServer(ntpUpdateCheck);
                    SoftwareUpdateCheck(swUpdateCheck);
                    // Any other work can happen here
                    // ...
                    Thread::yield();
                }
                linkup = false;
                linkdata = false;
                pc.printf("lost connection.\r\n");
                eth.disconnect();
            } else {
                pc.printf("  ... failed to connect.\r\n");
            }
        } while (1);
    }
}