David Smart / NWSWeather

NWSWeather.cpp

Committer:
WiredHome
Date:
2016-01-26
Revision:
14:bcc80874e824
Parent:
13:a9ac9dde4f7f
Child:
15:c353545f9f13

File content as of revision 14:bcc80874e824:

// 
// This file contains the interface for gathering forecast from NWS.
//
// attention: This program is copyright (c) 2014 - 2015 by Smartware Computing, all 
// rights reserved.
// 
// This software, and/or material is the property of Smartware Computing. All
// use, disclosure, and/or reproduction not specifically authorized by Smartware
// Computing is prohibited.
//
// This software may be freely used for non-commercial purposes, and any 
// derivative work shall carry the original copyright. The author of
// a derivative work shall make clear that it is a derivative work.
//
// author David Smart, Smartware Computing
//
#include "NWSWeather.h"

#include "Utility.h"            // private memory manager
#ifndef UTILITY_H
#define swMalloc malloc         // use the standard 
#define swFree free
#endif

#define DEBUG "NWS "
// ...
// INFO("Stuff to show %d", var); // new-line is automatically appended
//
#if (defined(DEBUG) && !defined(TARGET_LPC11U24))
#define INFO(x, ...) std::printf("[INF %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__);
#else
#define INFO(x, ...)
#define WARN(x, ...)
#define ERR(x, ...)
#endif

// 20151126 - Not sure when, but the NWS site stopped accepting requests unless a
//            User-Agent was provided. It doesn't seem to matter the contents of
//            the U-A string.
static const char * hdrs[] = {
    "Connection", "keep-alive",
    "Accept", "text/html",
    "User-Agent", "SW_Client"
};
#define HDR_PAIRS 3

static NWSWeather::XMLItem_T WeatherData[] = {
    {"observation_time_rfc822", NWSWeather::isString},
    {"suggested_pickup", NWSWeather::isInt},
    {"suggested_pickup_period", NWSWeather::isInt},
    {"location", NWSWeather::isString},
    {"weather", NWSWeather::isString},
    {"temp_f",  NWSWeather::isFloat},
    {"temp_c",  NWSWeather::isFloat},
    {"relative_humidity", NWSWeather::isInt},
    {"wind_degrees", NWSWeather::isInt},
    {"wind_mph", NWSWeather::isFloat},
    {"pressure_mb", NWSWeather::isFloat},
    {"pressure_in", NWSWeather::isFloat},
    {"dewpoint_f", NWSWeather::isFloat},
    {"dewpoint_c", NWSWeather::isFloat},
    {"windchill_f", NWSWeather::isFloat},
    {"windchill_c", NWSWeather::isFloat},
    {"visibility_mi", NWSWeather::isFloat},
    {"icon_url_base", NWSWeather::isString},
    {"icon_url_name", NWSWeather::isString},
    {"latitude", NWSWeather::isFloat},
    {"longitude", NWSWeather::isFloat},
};

#define MAXPARAMLEN 23

#define WeatherItemCount (sizeof(WeatherData)/sizeof(WeatherData[0]))

NWSWeather::NWSWeather(const char * baseURL) : m_baseurl(0)
{
    setAlternateURL(baseURL);
}

NWSWeather::~NWSWeather()
{
    ClearWeatherRecords();
}

NWSWeather::NWSReturnCode_T NWSWeather::setAlternateURL(const char * baseURL)
{
    int n = strlen(baseURL)+1;
    
    if (m_baseurl)
        swFree(m_baseurl);
    m_baseurl = (char *)swMalloc(n);
    if (m_baseurl)
        strncpy(m_baseurl, baseURL, n);
    else {
        ERR("failed to swMalloc(%d)", n);
        return nomemory;
    }
    ClearWeatherRecords();
    return noerror;
}

uint16_t NWSWeather::count(void)
{
    return WeatherItemCount;
}

NWSWeather::NWSReturnCode_T NWSWeather::get(const char * site, int responseSize)
{
    HTTPClient http;
    NWSReturnCode_T retCode = nomemory;
    char *url = NULL;
    char *message = (char *)swMalloc(responseSize);
    
    INFO("get(%s)", site);
    if (!message)
        ERR("failed to swMalloc(%d)", responseSize);
    while (message) {
        int n = strlen(m_baseurl) + strlen(site) + 5;
        
        url = (char *)swMalloc(n);
        if (url) {
            strcpy(url, m_baseurl);
            strcat(url, site);
            strcat(url, ".xml");
            INFO("  url: %s", url);
            http.setMaxRedirections(3);
            http.customHeaders(hdrs, HDR_PAIRS);
            int ret = http.get(url, message, responseSize);
            if (!ret) {
                INFO("ret is %d", ret);
                if (http.getHTTPResponseCode() >= 300 && http.getHTTPResponseCode() < 400) {
                    INFO("  http.getHTTPResponseCode(): %d", http.getHTTPResponseCode());
                    retCode = noerror;         // redirection that was not satisfied.
                    break;
                } else {
                    INFO("wx get %d bytes.\r\n", strlen(message));
                    ParseWeatherXML(message);
                    INFO("wx parse complete.\r\n");
                    retCode = noerror;
                    break;
                }
            } else {
                WARN("get returned %d, no response?", ret);
                retCode = noresponse;
                break;
            }
        } else {
            ERR("failed to swMalloc(%d)", n);
            retCode = nomemory;
            break;
        }
    } // while(...) but configured with break for only 1 pass.
    INFO("  ret is %d", retCode);
    if (url)
        swFree(url);
    if (message)
        swFree(message);
    INFO("  mem freed.");
    return retCode;
}


NWSWeather::NWSReturnCode_T NWSWeather::isUpdated(uint16_t i)
{
    if (i < WeatherItemCount) {
        if (WeatherData[i].updated)
            return noerror;
        else
            return noupdate;
    } else {
        return badparameter;
    }
}


NWSWeather::NWSReturnCode_T NWSWeather::isUpdated(const char * name)
{
    if (name) {
        for (int i=0; i < WeatherItemCount; i++) {
            if (strcmp(name, WeatherData[i].name) == 0) {
                if (WeatherData[i].updated)
                    return noerror;
                else
                    return noupdate;
            }
        }
    }
    return badparameter;
}


NWSWeather::NWSReturnCode_T NWSWeather::getParam(uint16_t i, char *name, Value_T *value, TypeOf_T *typeis)
{
    if (i < WeatherItemCount) {
        *name = *WeatherData[i].name;
        *value = WeatherData[i].value;
        if (typeis)
            *typeis = WeatherData[i].typeis;
        return noerror;
    } else {
        return badparameter;
    }
}


NWSWeather::NWSReturnCode_T NWSWeather::getParam(const char *name, Value_T *value, TypeOf_T *typeis)
{
    for (int i=0; i < WeatherItemCount; i++) {
        if (strcmp(name, WeatherData[i].name) == 0) {
            *value = WeatherData[i].value;
            if (typeis)
                *typeis = WeatherData[i].typeis;
            return noerror;
        }
    }
    return badparameter;
}


void NWSWeather::ClearWeatherRecords(void)
{
    int i;
    int count = WeatherItemCount;

    for (i=0; i<count; i++) {
        switch(WeatherData[i].typeis) {
            case isFloat:
                WeatherData[i].value.fValue = 0.0;
                WeatherData[i].updated = false;
                break;
            case isInt:
                WeatherData[i].value.iValue = 0;
                WeatherData[i].updated = false;
                break;
            case isString:
                if (WeatherData[i].value.sValue) {
                    swFree(WeatherData[i].value.sValue);
                    WeatherData[i].value.sValue = NULL;
                }
                WeatherData[i].updated = false;
                break;
            default:
                break;
        }
    }
}

void NWSWeather::PrintWeatherRecord(uint16_t i)
{
    if (i < WeatherItemCount) {
        switch(WeatherData[i].typeis) {
            case isFloat:
                printf("%23s = %f\r\n", WeatherData[i].name, WeatherData[i].value.fValue);
                break;
            case isInt:
                printf("%23s = %d\r\n", WeatherData[i].name, WeatherData[i].value.iValue);
                break;
            case isString:
                printf("%23s = %s\r\n", WeatherData[i].name, WeatherData[i].value.sValue);
                break;
            default:
                break;
        }
    }
}

void NWSWeather::PrintAllWeatherRecords(void)
{
    for (int i=0; i<WeatherItemCount; i++) {
        PrintWeatherRecord(i);
    }
}

NWSWeather::NWSReturnCode_T NWSWeather::ParseWeatherRecord(char * p)
{
    // <tag_name>value</tag_name>
    //  p1       p2   p3
    char *p1, *p2, *p3;
    int i;
    int n;
    int count = WeatherItemCount;

    INFO("ParseWeatherRecord(%s)", p);
    p1 = strchr(p, '<');        // Pattern Matching <key>value</key>
    if (p1++) {
        p2 = strchr(p1+1, '>');
        if (p2) {
            p3 = strchr(p2+1, '<');
            if (p3) {
                *p2++ = '\0';
                *p3 = '\0';
                for (i=0; i<count; i++) {
                    if (strcmp(p1, WeatherData[i].name) == 0) {
                        switch(WeatherData[i].typeis) {
                            case isFloat:
                                WeatherData[i].value.fValue = atof(p2);
                                WeatherData[i].updated = true;
                                break;
                            case isInt:
                                WeatherData[i].value.iValue = atoi(p2);
                                WeatherData[i].updated = true;
                                break;
                            case isString:
                                if (WeatherData[i].value.sValue)
                                    swFree(WeatherData[i].value.sValue);
                                n = strlen(p2)+1;
                                INFO("  pre- swMalloc(%d)", n);
                                WeatherData[i].value.sValue = (char *)swMalloc(n);
                                INFO("  post-swMalloc");
                                if (WeatherData[i].value.sValue) {
                                    strcpy(WeatherData[i].value.sValue, p2);
                                    WeatherData[i].updated = true;
                                } else {
                                    ERR("failed to swMalloc(%d)", n);
                                    break;
                                }
                                break;
                            default:
                                ERR("unknown type");
                                return badparameter;
                                //break;
                        }
                        //INFO("pw end");
                        return noerror;
                    }
                }
            }
        }
    }
    //INFO("pw end");
    return noparamfound;
}

void NWSWeather::ParseWeatherXML(char * message)
{
    char * p = message;

    INFO("ParseWeatherXML: %s", p);
    ClearWeatherRecords();
    INFO("cleared old");
    while (*p) {
        char * n = strchr(p, '\n');
        if (*n) {
            *n = '\0';
            ParseWeatherRecord(p);
            p = n + 1;
        } else {
            ParseWeatherRecord(p);
            break;
        }
    }
}