mbed based IoT Gateway More details http://blog.thiseldo.co.uk/wp-filez/IoTGateway.pdf

Dependencies:   NetServices FatFileSystem csv_parser mbed MQTTClient RF12B DNSResolver SDFileSystem

main.cpp

Committer:
SomeRandomBloke
Date:
2012-05-09
Revision:
5:0dbc27a7af55
Parent:
4:d460406ac780

File content as of revision 5:0dbc27a7af55:

/** IoT Gateway main code
 *
 * @author Andrew Lindsay
 *
 * @section LICENSE
 *
 * Copyright (c) 2012 Andrew Lindsay (andrew [at] thiseldo [dot] co [dot] uk)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:

 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 * @section DESCRIPTION
 *
 * Code is to be classed as beta. There is a lot of debug code still includes
 * to dump heap sizes and other values. It is still a work in progress and
 * should be treated as such.
 *
 * Further documentation available from
 * http://blog.thiseldo.co.uk/wp-filez/IoTGateway.pdf
 *
 * Sample configuration file IOTSETUP.TXT
 *
 * @code
 * ip.mode=fixed
 * ip.address=192.168.1.99
 * ip.netmask=255.255.255.0
 * ip.gateway=192.168.1.1
 * ip.dns=192.168.1.1
 * rfm12b.band=868
 * rfm12b.id=30
 * rfm12b.group=212
 * time.host=0.uk.pool.ntp.org
 * time.timezone=GMT
 * time.dst=yes
 * pachube.key=***your Pachube api key***
 * pachube.apiurl=http://api.pachube.com/v2/feeds/%d.csv?_method=PUT
 * emoncms.key=***your emonCms api key***
 * emoncms.apiurl=http://your.host.com/emoncms/api/post?apikey=%s&json=
 * sense.key=***your Sen.se api key***
 * sense.apiurl=http://api.sen.se/events/?sense_key=%s
 * mqtt.host=
 * mqtt.port=1883
 * mqtt.username=
 * mqtt.password=
 * @endcode
 *
 * Some values are not yet used
 *
 */

#include "iotgateway.h"
#include "mbed.h"
#include <ctype.h>
#include "SDFileSystem.h"
#include "EthernetNetIf.h"
#include "NTPClient.h"
#include "dnsresolve.h"
#include "RF12B.h"
#include "IoTRouting.h"

using std::string;

#undef DEBUG 

#define VERSION_INFO "IoT Gateway Basic - Version 0.8"

DigitalOut heartbeatLED(LED1, "heartbeatLED");
DigitalOut led2(LED2, "led2");
DigitalOut led3(LED3, "led3");
//DigitalOut led4(LED4, "led4");
DigitalOut linkLED(p30, "linkLED");
DigitalOut statusLED(p25, "statusLED");

// Put as much as we can into RAM bank AHBSRAM0 which is reserved for USB that we are not using

// Setup which filesystem to use, Local or uSD card, both use same name
//__attribute((section("AHBSRAM0"))) LocalFileSystem fs("iotfs");
__attribute((section("AHBSRAM0"))) SDFileSystem sd(p5, p6, p7, p8, "iotfs");

__attribute((section("AHBSRAM0"))) EthernetNetIf *eth;
__attribute((section("AHBSRAM0"))) NTPClient ntp;
__attribute((section("AHBSRAM0"))) Ethernet ethernet;
__attribute((section("AHBSRAM0"))) DNSResolver resolver;

// Configuration values loaded from file iotsetup.dat
__attribute((section("AHBSRAM0"))) IpAddr ipAddress(0, 0, 0, 0);
__attribute((section("AHBSRAM0"))) IpAddr netMask(255, 255, 255, 0);
__attribute((section("AHBSRAM0"))) IpAddr gwAddress(0, 0, 0, 0);
__attribute((section("AHBSRAM0"))) IpAddr dnsAddress(0, 0, 0, 0);
__attribute((section("AHBSRAM0"))) bool useDHCP = false;

// Static buffers
__attribute((section("AHBSRAM0"))) static char lineBuf[MAX_LINE_LENGTH];
__attribute((section("AHBSRAM0"))) static OutputPachube outPachube;
__attribute((section("AHBSRAM0"))) static OutputEmonCms outEmonCms;
__attribute((section("AHBSRAM0"))) static OutputSenSe outSenSe;
__attribute((section("AHBSRAM0"))) static IoTRouting rtr;

// Pachube config
__attribute((section("AHBSRAM0"))) char pachubeApiUrl[API_URL_LENGTH];
__attribute((section("AHBSRAM0"))) char pachubeApiKey[API_KEY_LENGTH];
__attribute((section("AHBSRAM0"))) char pachubeDataBuffer[DATABUF_SIZE];

// MQTT config
__attribute((section("AHBSRAM0"))) IpAddr mqttHostAddress( 0, 0, 0, 0);
__attribute((section("AHBSRAM0"))) short mqttPort;
__attribute((section("AHBSRAM0"))) char mqttUsername[MAX_LINE_LENGTH];
__attribute((section("AHBSRAM0"))) char mqttPassword[MAX_LINE_LENGTH];
__attribute((section("AHBSRAM0"))) bool useMQTT = false;

//char mqttHostName[65];

// Open energy Monitor emonCMS config
__attribute((section("AHBSRAM0"))) char emonCmsApiUrl[API_URL_LENGTH];
__attribute((section("AHBSRAM0"))) char emonCmsApiKey[API_KEY_LENGTH];
__attribute((section("AHBSRAM0"))) char emonCmsDataBuffer[DATABUF_SIZE];

// Open energy Monitor Sen.Se config
__attribute((section("AHBSRAM0"))) char senSeApiUrl[API_URL_LENGTH];
__attribute((section("AHBSRAM0"))) char senSeApiKey[API_KEY_LENGTH];
__attribute((section("AHBSRAM0"))) char senSeDataBuffer[DATABUF_SIZE];

// Time server config
__attribute((section("AHBSRAM0"))) char ntpHost[MAX_LINE_LENGTH] = "0.uk.pool.ntp.org";

// RFM12B config
__attribute((section("AHBSRAM0"))) static string rfm12bBands[4] = {"xxx", "433", "868", "915" };
__attribute((section("AHBSRAM0"))) static uint8_t rfm12bId;
__attribute((section("AHBSRAM0"))) static uint8_t rfm12bBand;
__attribute((section("AHBSRAM0"))) static uint8_t rfm12bGroup;

#define iotConfigFile "/iotfs/IOTSETUP.TXT"

__attribute((section("AHBSRAM0"))) RF12B rfm12b(p11, p12, p13, p14, p18);
__attribute((section("AHBSRAM0"))) Serial pc(USBTX, USBRX); // tx, rx

// Utility functions

/** convert string to IP address
 *
 * @param ipAddrInt  IP address as single 32bit integer
 * @param value      The IP address as a string
 */
void setIpAddress( int *ipAddrInt, char *value ) {
    sscanf(value, "%d.%d.%d.%d", &ipAddrInt[0],&ipAddrInt[1],&ipAddrInt[2],&ipAddrInt[3]);
}

/** Get the value from a name=value pair
 *
 * @param buf  The name/value pair character buffer
 * @returns pointer to value
 */
char *getConfigValue( char *buf ) {
    char *ptr = strchr(buf, '=');
    if ( ptr == NULL )
        return NULL;
    ptr++;
    return ptr;
}

/** Copy zero or CR/LF terminated string to new zero terminated string
 * Used when reading config values from files to remove CR/LF endings
 *
 * @param to   Char pointer to destination
 * @param from Char pointer to source
 */
void strcpynull(char *to, char *from ) {
    while ( *from != '\n' && *from != '\r' && *from != '\0' ) {
        *to++ = *from++;
    }
    *to = '\0';
}

/** Trim a string in buffer, remove leading and trailing spaces
 *
 */
char *trim(char *str) {
    size_t len = 0;
    char *frontp = str - 1;
    char *endp = NULL;

    if ( str == NULL )
        return NULL;

    if ( str[0] == '\0' )
        return str;

    len = strlen(str);
    endp = str + len;

    /* Move the front and back pointers to address
     * the first non-whitespace characters from
     * each end.
     */
    while ( isspace(*(++frontp)) );
    while ( isspace(*(--endp)) && endp != frontp );

//    while( *(++frontp) == ' ' );
//    while( (*(--endp) == ' ') && endp != frontp );

    if ( str + len - 1 != endp )
        *(endp + 1) = '\0';
    else if ( frontp != str &&  endp == frontp )
        *str = '\0';

    /* Shift the string so that it starts at str so
     * that if it's dynamically allocated, we can
     * still free it on the returned pointer.  Note
     * the reuse of endp to mean the front of the
     * string buffer now.
     */
    endp = str;
    if ( frontp != str ) {
        while ( *frontp ) *endp++ = *frontp++;
        *endp = '\0';
    }


    return str;
}

/** Read IoT gateway configuration file
 * File is of format name=value
 *
 */
bool readConfig() {

    FILE *fp = fopen(iotConfigFile, "r");
    if (fp == NULL) {
        printf("Could not open file %s for read\n", iotConfigFile);
        return false;
    }
    // read file
    while (!feof( fp )) {
        fgets(lineBuf,MAX_LINE_LENGTH,fp);
//        printf("[%s] ",lineBuf);
        trim( lineBuf );
//        printf("[%s]\n", lineBuf);

        // Need to read each entry and update config
        char *nameStr;
        char *valueStr;
        nameStr = lineBuf;
        valueStr = strchr( lineBuf, '=' );
        if ( valueStr != NULL ) {
            *valueStr++ = '\0';
            int tmpAddress[4] = {0,0,0,0};
            if ( strcmp( nameStr, "ip.address" ) == 0 ) {
                setIpAddress( &tmpAddress[0], valueStr );
                ipAddress = IpAddr( tmpAddress[0],tmpAddress[1],tmpAddress[2],tmpAddress[3]);

            } else if ( strcmp( nameStr, "ip.netmask" ) == 0 ) {
                setIpAddress( &tmpAddress[0], valueStr );
                netMask = IpAddr( tmpAddress[0],tmpAddress[1],tmpAddress[2],tmpAddress[3]);

            } else if ( strcmp( nameStr, "ip.gateway" ) == 0 ) {
                setIpAddress( &tmpAddress[0], valueStr );
                gwAddress = IpAddr( tmpAddress[0],tmpAddress[1],tmpAddress[2],tmpAddress[3]);

            } else if ( strcmp( nameStr, "ip.dns" ) == 0 ) {
                setIpAddress( &tmpAddress[0], valueStr );
                dnsAddress = IpAddr( tmpAddress[0],tmpAddress[1],tmpAddress[2],tmpAddress[3]);

            }  else if ( strcmp( nameStr, "ip.mode" ) == 0 ) {
                useDHCP = (strncmp( valueStr, "dhcp", 4) == 0 ? true : false);

            }  else if ( strcmp( nameStr, "time.host" ) == 0 ) {
                strcpynull(ntpHost, valueStr );

            }  else if ( strcmp( nameStr, "pachube.apiurl" ) == 0 ) {
                strcpynull(pachubeApiUrl, valueStr );

            }  else if ( strcmp( nameStr, "pachube.key" ) == 0 ) {
                strcpynull(pachubeApiKey, valueStr );

            }  else if ( strcmp( nameStr, "emoncms.apiurl" ) == 0 ) {
                strcpynull(emonCmsApiUrl, valueStr );

            }  else if ( strcmp( nameStr, "emoncms.key" ) == 0 ) {
                strcpynull(emonCmsApiKey, valueStr );

            }  else if ( strcmp( nameStr, "sense.apiurl" ) == 0 ) {
                strcpynull(senSeApiUrl, valueStr );

            }  else if ( strcmp( nameStr, "sense.key" ) == 0 ) {
                strcpynull(senSeApiKey, valueStr );
            } else if ( strcmp( nameStr, "mqtt.host" ) == 0 ) {
                setIpAddress( &tmpAddress[0], valueStr );
                mqttHostAddress = IpAddr( tmpAddress[0],tmpAddress[1],tmpAddress[2],tmpAddress[3]);

            } else if ( strcmp( nameStr, "mqtt.port" ) == 0 ) {
                mqttPort = atoi( valueStr );

            }  else if ( strcmp( nameStr, "mqtt.username" ) == 0 ) {
                strcpynull(mqttUsername, valueStr );

            }  else if ( strcmp( nameStr, "mqtt.password" ) == 0 ) {
                strcpynull(mqttPassword, valueStr );

            }  else if ( strcmp( nameStr, "rfm12b.band" ) == 0 ) {
                if ( strncmp( valueStr, "433", 3 ) == 0 )
                    rfm12bBand = RF12_433MHZ;
                else if ( strncmp( valueStr, "868", 3 ) == 0 )
                    rfm12bBand = RF12_868MHZ;
                else if ( strncmp( valueStr, "915", 3 ) == 0 )
                    rfm12bBand = RF12_915MHZ;

            }  else if ( strcmp( nameStr, "rfm12b.id" ) == 0 ) {
                rfm12bId = atoi( valueStr );
            }  else if ( strcmp( nameStr, "rfm12b.group" ) == 0 ) {
                rfm12bGroup = atoi( valueStr );
            }
        }
    }
    fclose(fp);

    return true;
}

/** Write the current config to file
 */
bool writeConfig() {

// Doesnt work?
    // Rename original file
//    if( rename ( iotConfigFile.c_str(), iotConfigFileBak.c_str() ) != 0 ) {
//        printf("Could not rename file %s\n", iotConfigFile);
//        return false;
//    }

    FILE *fp = fopen(iotConfigFile, "w");
    if (fp == NULL) {
        printf("Could not open file %s for write\n", iotConfigFile);
        return false;
    }

    time_t ctTime;
    ctTime = time(NULL);

    fprintf(fp, "# iotsetup created (UTC) %s\n", ctime(&ctTime));
    fprintf(fp, "ip.mode=%s\n", (useDHCP ? "dhcp" : "fixed") );
    // Add msg to say net config being ignored
    if ( useDHCP )
        fprintf(fp, "# Following ip.* parameters not used in DHCP mode\n");

    fprintf(fp, "ip.address=%hhu.%hhu.%hhu.%hhu\n", ipAddress[0], ipAddress[1],ipAddress[2],ipAddress[3]);
    fprintf(fp, "ip.netmask=%hhu.%hhu.%hhu.%hhu\n",netMask[0], netMask[1], netMask[2], netMask[3]);
    fprintf(fp, "ip.gateway=%hhu.%hhu.%hhu.%hhu\n",gwAddress[0],gwAddress[1],gwAddress[2],gwAddress[3]);
    fprintf(fp, "ip.dns=%hhu.%hhu.%hhu.%hhu\n",dnsAddress[0],dnsAddress[1],dnsAddress[2],dnsAddress[3]);

    fprintf(fp, "rfm12b.band=%s\n",rfm12bBands[rfm12bBand].c_str());
    fprintf(fp, "rfm12b.id=%d\n",rfm12bId);
    fprintf(fp, "rfm12b.group=%d\n",rfm12bGroup);

    fprintf(fp, "time.timezone=GMT\n");
    fprintf(fp, "time.dst=yes\n");

    fprintf(fp, "pachube.key=%s\n", pachubeApiKey );

    fprintf(fp, "mqtt.host=%hhu.%hhu.%hhu.%hhu\n",mqttHostAddress[0],mqttHostAddress[1],mqttHostAddress[2],mqttHostAddress[3]);
    fprintf(fp, "mqtt.port=%d\n",mqttPort);
    fprintf(fp, "mqtt.username=%s\n", mqttUsername );
    fprintf(fp, "mqtt.password=%s\n", mqttPassword );

    fclose(fp);

    return true;
}


// These external symbols are maintained by the linker to indicate the
// location of various regions in the device's memory.  They will be used by
// DisplayRAMBanks() to dump the size of each RAM bank to stdout.
extern unsigned int Image$$RW_IRAM1$$Base;
extern unsigned int Image$$RW_IRAM1$$ZI$$Limit;
extern unsigned int Image$$RW_IRAM2$$Base;
extern unsigned int Image$$RW_IRAM2$$ZI$$Limit;
extern unsigned int Image$$RW_IRAM3$$Base;
extern unsigned int Image$$RW_IRAM3$$ZI$$Limit;

//#ifdef DEBUG
// Displays the size of static allocations for each RAM bank as indicated by
// ARM linker to stdout.
static void DisplayRAMBanks(void) {
    printf("Static RAM bank allocations\r\n");
    printf("  Main RAM = %u\r\n", (unsigned int)&Image$$RW_IRAM1$$ZI$$Limit -
           (unsigned int)&Image$$RW_IRAM1$$Base);
    printf("  RAM0     = %u\r\n", (unsigned int)&Image$$RW_IRAM2$$ZI$$Limit -
           (unsigned int)&Image$$RW_IRAM2$$Base);
    printf("  RAM1     = %u\r\n", (unsigned int)&Image$$RW_IRAM3$$ZI$$Limit -
           (unsigned int)&Image$$RW_IRAM3$$Base);
}
//#endif

/** Main function, where all the magic starts
 */
int main() {
    linkLED = 1;
    statusLED = 0;
    pc.baud(115200);

    printf(VERSION_INFO);
    printf("\n");
    DisplayRAMBanks();
#ifdef DEBUG

    printf("Setting up...\n");
    printf("\nHEAP STATS\n");
    __heapstats((__heapprt)fprintf,stderr);
#endif

    if ( !readConfig() ) {
        error("Setup failed");
    }

    if (useDHCP) {
        printf("Using DHCP\n");
        eth = new EthernetNetIf( "IoTGateway" );
    } else {
//        printf("Using Fixed addressing\n");
        eth = new EthernetNetIf( ipAddress, netMask, gwAddress, dnsAddress );
    }
    EthernetErr ethErr = eth->setup();
    if (ethErr) {
        printf("Error %d in setup.\n", ethErr);
        return -1;
    }

    linkLED = !ethernet.link();

    if (useDHCP) {
        // We are using dhcp so get IP Address
        ipAddress = eth->getIp();
    }

    // Get Current time
    Host server(IpAddr(), 123, ntpHost);    // "0.uk.pool.ntp.org");
    ntp.setTime(server);

    time_t ctTime = time(NULL);
    printf("\nTime is now (UTC): %s\n", ctime(&ctTime));

    // If Pachube API Key defined, set up output module
    if ( strlen(pachubeApiKey) > 0 ) {
        outPachube = OutputPachube( pachubeDataBuffer, pachubeApiUrl, pachubeApiKey );
//        outPachube.setApiKey( pachubeApiKey );
//        printf("MAIN: outPachube = %ld\n",(int)&outPachube);
        rtr.addOutput( (OutputDef*)&outPachube , OUTPUT_TYPE_PACHUBE);
    }

    // If emonCms API Key defined, set up output module
    if ( strlen(emonCmsApiKey) > 0 ) {
        outEmonCms = OutputEmonCms( emonCmsDataBuffer, emonCmsApiUrl, emonCmsApiKey );
        rtr.addOutput( (OutputDef*)&outEmonCms , OUTPUT_TYPE_EMONCMS);
    }

    // If Sen.se API Key defined, set up output module
    if ( strlen(senSeApiKey) > 0 ) {
        outSenSe = OutputSenSe( senSeDataBuffer, senSeApiUrl, senSeApiKey );
        rtr.addOutput( (OutputDef*)&outSenSe , OUTPUT_TYPE_SENSE);
    }

    //mqttHostAddress = IpAddr(192,168,1,77);
    OutputMqtt outMqtt = OutputMqtt();
//    mqttHostAddress = resolver.resolveName("api.pachube.com");
    // Only use MQTT is we have a host IP address
    if ( mqttHostAddress[0] > 0 && mqttHostAddress[3] > 0 ) {
        outMqtt.initConnection( &mqttHostAddress, mqttPort, mqttUsername, mqttPassword );
        //( &serverIpAddr );
        useMQTT = outMqtt.init();

        if ( useMQTT )
            rtr.addOutput( (OutputDef*)&outMqtt , OUTPUT_TYPE_MQTT);
    }

    rtr.initRouting();
#ifdef DEBUG
    printf("Setup OK\n");

    printf( "Setting RFM12B ID %d, Band %d Group %d\n",rfm12bId, rfm12bBand, rfm12bGroup);
#endif

    rfm12b.init(rfm12bId, rfm12bBand, rfm12bGroup );   //id = 2, band 866, group 5

//    printf("Listening...\n");

    Timer tm;
    tm.start();
#ifdef DEBUG
    printf("\nHEAP STATS\n");
    __heapstats((__heapprt)fprintf,stderr);
#endif

    short dataLen = 0;
    uint8_t *rf12Buf;

    // Start receiving data
    rfm12b.rf12_recvStart();
    while (true) {
        Net::poll();

        // This is RFM12B specific
        if ( rfm12b.available() > 0 ) {
            statusLED = 1;
            rf12Buf =  rfm12b.get_data();
            dataLen = rfm12b.length();

            if ( rtr.routePayload( rf12Buf, dataLen + 3 ) ) {
                // Successfully dealt with a packet

                // Do something different if routed

            }

            // For now, acknowledge everything

            // Send an ack if required
            if ((rf12Buf[1] & ~RF12_HDR_MASK) == RF12_HDR_ACK // &&
                    //(config.nodeId & 0x20) == 0
               ) {
#ifdef DEBUG
                printf("RFM12B -> ack\n");
#endif
                byte addr = rf12Buf[1] & RF12_HDR_MASK;
                rfm12b.rf12_sendStart(RF12_HDR_CTL | RF12_HDR_DST | addr, 0, 1);
            }

            statusLED = 0;
        }

        if (tm.read()>.5) {
            heartbeatLED=!heartbeatLED; //Show that we are alive
            tm.start();
        }

        // If using mqtt then send heartbeat
        if (useMQTT) {
            outMqtt.send();     // Used for heartbeat/keepalive packet
        }

        linkLED = !ethernet.link();

    }

}

// The End!