A class to gather weather data from an NWS server.

NWSWeather.cpp

Committer:
WiredHome
Date:
2019-02-23
Revision:
20:29257dc4c7aa
Parent:
19:00af774c9b72

File content as of revision 20:29257dc4c7aa:

// 
// This file contains the interface for gathering forecast from NWS.
//
// attention: This program is copyright (c) 2014 - 2019 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


NWSWeather::NWSWeather(XMLItem_T * pList, int count) : m_baseurl(0)
{
    WeatherData = pList;
    WeatherItemCount = count;
    setAlternateURL(DEF_URL);
}

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;
}

#if WEATHER_GOV == 1
NWSWeather::NWSReturnCode_T NWSWeather::get(const char * site, int responseSize)
#elif OPEN_WEATHER == 1
NWSWeather::NWSReturnCode_T NWSWeather::get(const char * site, const char * userid, int responseSize)
#endif
{
    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) {
        #if WEATHER_GOV == 1
        //https://www.weather.gov/data/current_obs/<loc_code>.xml
        //                                                   ++++
        int n = strlen(m_baseurl) + strlen(site) + 5;
        #elif OPEN_WEATHER == 1
        //http://api.openweathermap.org/data/2.5/weather?mode=xml&units=imperial&id=<loc_code>&APPID=<user_id>
        //                                                                      ++++          +++++++
        int n = strlen(m_baseurl) + 4 + strlen(site) + 7 + strlen(userid) + 1;
        #endif
        url = (char *)swMalloc(n);
        if (url) {
            #if WEATHER_GOV == 1
            sprintf(url, "%s%s%s.xml", m_baseurl, site);
            #elif OPEN_WEATHER == 1
            sprintf(url, "%s&id=%s&APPID=%s", m_baseurl, site, userid);
            #endif
            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 = (NWSReturnCode_T)ret; // return code from get also indicates type of error // 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].key) == 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].key;
        *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)
{
    return getParam(name, NULL, value, typeis);
}


NWSWeather::NWSReturnCode_T NWSWeather::getParam(const char *name, const char * param, Value_T *value, TypeOf_T *typeis)
{
    for (int i=0; i < WeatherItemCount; i++) {
        if (strcmp(name, WeatherData[i].key) == 0) {
            if (param == NULL || strcmp(param, WeatherData[i].param) == 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) {
        printf("%23s::%-10s = ", WeatherData[i].key, WeatherData[i].param);
        switch(WeatherData[i].typeis) {
            case isFloat:
                printf("%f", WeatherData[i].value.fValue);
                break;
            case isInt:
                printf("%d", WeatherData[i].value.iValue);
                break;
            case isString:
                printf("%s", WeatherData[i].value.sValue);
                break;
            default:
                break;
        }
        printf("\r\n");
    }
}

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

NWSWeather::NWSReturnCode_T NWSWeather::ExtractParam(int i, char * pValue, char * pEnd) {
    int n;
    
    INFO("ExtractParam(%d, ...)", i);
    switch(WeatherData[i].typeis) {
        case isFloat:
            WeatherData[i].value.fValue = (float)atof(pValue);
            WeatherData[i].updated = true;
            INFO("%f", WeatherData[i].value.fValue);
            break;
        case isInt:
            WeatherData[i].value.iValue = atoi(pValue);
            WeatherData[i].updated = true;
            INFO("%d", WeatherData[i].value.iValue);
            break;
        case isString:
            if (WeatherData[i].value.sValue)
                swFree(WeatherData[i].value.sValue);
            n = pEnd - pValue;
            WeatherData[i].value.sValue = (char *)swMalloc(n+1);
            if (WeatherData[i].value.sValue) {
                strncpy(WeatherData[i].value.sValue, pValue, n);
                WeatherData[i].value.sValue[n] = '\0';
                WeatherData[i].updated = true;
                INFO(" info '%s'", WeatherData[i].value.sValue);
            } else {
                ERR("failed to swMalloc(%d)", n);
                return nomemory;
            }
            break;
        default:
            ERR("unknown type");
            return badparameter;
            //break;
    }
    INFO("...");
    return noerror;
}

NWSWeather::NWSReturnCode_T NWSWeather::ParseWeatherRecord(char * p)
{
    #if 1
    // Pattern Matching <thekey dontcare="something" value="thevalue"/>
    //                   |     |                     |------|       | |
    //                   |     |                     |              | +pEnd
    //                   |     +pKeyE                +pVal          +pValE
    //                   +pKey
    //
    //                  <thekey>theValue</thekey>
    //                   |     ||       |       |
    //                   |     ||       |       +pEnd
    //                   |     |+pVal   +pValE
    //                   |     +pKeyE
    //                   +pKey
    //
    char *pKey, *pEnd;
    char *pVal = NULL;
    char *pValE = NULL;

    p = strchr(p, '<');
    while (p) {
        //INFO("ParseWeatherRecord  \n%s\n", p);
        p++;    // Advance past the '<'
        for (int i=0; i<WeatherItemCount; i++) {
            //INFO("\n  check %s::%s", WeatherData[i].key, WeatherData[i].param);
            pKey = strstr(p, WeatherData[i].key);
            if (pKey && pKey == p && *(pKey-1) == '<') {
                //INFO("  key %s", pKey);
                if (WeatherData[i].param && WeatherData[i].param[0]) {
                    // <thekey dontcare="something" value="thevalue"/>
                    pEnd = strchr(pKey, '/');
                    if (pEnd) {
                        pVal = strstr(pKey, WeatherData[i].param);
                        if (pVal && pVal < pEnd) {
                            pVal = strchr(pVal, '"');
                            if (pVal) {
                                pVal++;
                                //INFO("  val %s", pVal);
                                pValE = strchr(pVal, '"');
                                ExtractParam(i, pVal, pValE);
                                INFO("Key %s::%s", WeatherData[i].key, WeatherData[i].param);
                            }
                        }
                    }
                } else {
                    // <thekey>theValue</key>
                    pVal = strchr(pKey, '>');
                    if (pVal) {
                        pVal++;
                        pValE = strchr(pVal, '<');
                        if (pValE && *(pValE+1) == '/') {
                            ExtractParam(i, pVal, pValE);
                            INFO("Key %s", WeatherData[i].key);
                        }
                    }
                }
            }
        }
        p = strchr(p, '<');
    }
    #else
    // <tag_name>value</tag_name>
    //  pKey     pValue
    char *pKey, *pValue, *pX;
    int i;
    int n;
    int count = WeatherItemCount;
    bool parseIt = false;

    INFO("ParseWeatherRecord(%s)", p);
    #if WEATHER_GOV == 1
    // Pattern Matching <key>value</key>
    //                   |   +pValue
    //                   +pKey
    pKey = strchr(p, '<');
    if (pKey++) {
        pValue = strchr(pKey+1, '>');
        if (pValue) {
            p2 = strchr(pValue+1, '<');
            if (p2) {
                *pValue++ = '\0';
                *p2 = '\0';
                parseIt = true;
            }
        }
    }
    #elif OPEN_WEATHER == 1
    pKey = strchr(p, '<');
    // Pattern Matching <key dontcare="something" value="thevalue" dontcare="something"/>
    //                   |                               +pValue
    //                   +pKey
    if (pKey++) {
        pValue = strchr(pKey+1, ' ');
        if (pValue) {
            *pValue++ = '\0';
            pValue = strstr(pValue, "value=\"");
            if (pValue) {
                pValue += 7;
                pX = strchr(pValue, '"');
                if (pX) {
                    *pX = '\0';
                    parseIt = true;
                }
            }
        }
    }
    #endif
    if (parseIt) {
        for (i=0; i<count; i++) {
            if (strcmp(pKey, WeatherData[i].key) == 0) {
                switch(WeatherData[i].typeis) {
                    case isFloat:
                        WeatherData[i].value.fValue = atof(pValue);
                        WeatherData[i].updated = true;
                        break;
                    case isInt:
                        WeatherData[i].value.iValue = atoi(pValue);
                        WeatherData[i].updated = true;
                        break;
                    case isString:
                        if (WeatherData[i].value.sValue)
                            swFree(WeatherData[i].value.sValue);
                        n = strlen(pValue)+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, pValue);
                            WeatherData[i].updated = true;
                        } else {
                            ERR("failed to swMalloc(%d)", n);
                            break;
                        }
                        break;
                    default:
                        ERR("unknown type");
                        return badparameter;
                        //break;
                }
                return noerror;
            }
        }
    }
    #endif
    return noparamfound;
}

void NWSWeather::ParseWeatherXML(char * message)
{
    char * p = strchr(message, '<');

    ClearWeatherRecords();
    ParseWeatherRecord(p);
}