A class to gather weather data from an NWS server.

NWSWeather.h

Committer:
WiredHome
Date:
2019-02-23
Revision:
20:29257dc4c7aa
Parent:
18:699590fd8856

File content as of revision 20:29257dc4c7aa:

/// @file NWSWeather.h
/// 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
///
#ifndef NWSWEATHER_H
#define NWSWEATHER_H
#include "mbed.h"
#include "HTTPClient.h"

// Set one of these to a 1, depending on your data source.
#define WEATHER_GOV 0    // www.weather.gov (which went from http to https Jan 2019
#define OPEN_WEATHER 1   // openweathermap.org (which so far remains http)


#if WEATHER_GOV == 1
static const char DEF_URL[] = "http://www.weather.gov/data/current_obs/";
#elif OPEN_WEATHER == 1
static const char DEF_URL[] = "http://api.openweathermap.org/data/2.5/weather?mode=xml&units=imperial&";
#endif

/// Weather Service
///
/// This class will fetch and parse weather data from an XML feed
/// using a URL such as 
///     + https://www.weather.gov/data/current_obs/KALO.xml
///     + http://api.openweathermap.org/data/2.5/weather?mode=xml&units=imperial&id=<Loc_ID>&APPID=<User_ID>
///
/// @attention Proper use of this should follow the guideline,
///     which is to not request this data from the server more often than
///     necessary, and if available to recognize and use the suggested_pickup
///     parameter for subsequent updates of the weather information. This
///     class does not monitor the RTC so is unaware of the time of day. It
///     is therefore up to the user to manage this recommended practice.
///
/// Only the essential parameters are gathered, which is generally those
/// from which you can derive others. It turns out this is also most of the
/// parameters.
///
/// From weather.gov
/// \li observation_time_rfc822 - "Sun, 16 Mar 2014 09:54:00 -0500"
/// \li        suggested_pickup - "15 minutes after the hour"
/// \li suggested_pickup_period - 60 (minutes)
/// \li                location
/// \li                 weather
/// \li                  temp_f
/// \li                  temp_c
/// \li       relative_humidity
/// \li            wind_degrees
/// \li                wind_mph
/// \li             pressure_mb
/// \li             pressure_in
/// \li              dewpoint_f
/// \li              dewpoint_c
/// \li             windchill_f
/// \li             windchill_c
/// \li           visibility_mi
/// \li           icon_url_base - http://forecast.weather.gov/images/wtf/small/
/// \li           icon_url_name - ovc.png (changes based on forecast)
/// \li                latitude
/// \li               longitude
///
/// From openweathermap.org
/// \li <current>
/// \li <city id="4880889" name="Waterloo">
/// \li <coord lat="42.49" lon="-92.34"/>
/// \li <country>US</country>
/// \li <sun set="2019-01-08T22:54:13" rise="2019-01-08T13:38:34"/>
/// \li </city>
/// \li <temperature unit="fahrenheit" max="36.68" min="33.26" value="34.92"/>
/// \li <humidity unit="%" value="93"/>
/// \li <pressure unit="hPa" value="1009"/>
/// \li <wind>
/// \li <speed name="Gale" value="18.34"/>
/// \li <gusts value="10.8"/>
/// \li <direction name="North-northeast" value="340" code="NNW"/>
/// \li </wind>
/// \li <clouds name="scattered clouds" value="40"/>
/// \li <visibility value="16093"/>
/// \li <precipitation mode="no"/>
/// \li <weather value="scattered clouds" icon="03n" number="802"/>
/// \li <lastupdate value="2019-01-08T12:15:00"/>
/// \li </current>
/// 
class NWSWeather
{
public:
    /// Return code interpretation.
    typedef enum
    {
        noerror,        ///< no error, or parameter has been updated.
        nomemory,       ///< insufficient memory to complete operation.
        noresponse,     ///< no response from server.
        badparameter,   ///< bad parameter to function (typically out of range value).
        noupdate,       ///< no update available.
        noparamfound    ///< no parameter found.
    } NWSReturnCode_T;
    
    /// Identifies the type of a parameter.
    typedef enum
    {
        isFloat,        ///< this parameter is a floating point value.
        isInt,          ///< this parameter is an integer.
        isString        ///< this parameter is a string value.
    } TypeOf_T;
    
    /// The union of available types.
    typedef union
    {
        float  fValue;  ///< the floating point value
        int    iValue;  ///< the integer value
        char * sValue;  ///< the string value
    } Value_T;
    
    /// The array that defines what XML information to extract.
    ///
    /// It will not honor heirarchical XML tagging, each parsed
    /// tag must be in the same text line.
    ///
    /// Given two types of XML structure:
    /// 1) <sunrise>6:45 am</sunrise>
    /// 2) <sun rise="6:45 am" set="4:52 pm">
    /// 
    /// The key-value pairs can be defines to support either:
    /// "sunrise", NULL, NWSWeather::isString
    /// "sun", "rise", NWSWeather::isString
    ///
    /// @code
    /// static NWSWeather::XMLItem_T WeatherData[] = {
    ///     {"weather", "value", NWSWeather::isString},                  //<weather number="802" value="scattered clouds" icon="03n"/>
    ///     {"temperature", "value", NWSWeather::isFloat},              //<temperature value="27.3" min="21.2" max="32" unit="fahrenheit"/>
    ///     {"sun", "rise", NWSWeather::isString},
    ///     {"sun", "set", NWSWeather::isString},
    /// };
    /// @endcode    
    typedef struct 
    {
        const char * key;  ///< pointer to the XML key of interest
        const char * param; ///< pointer to the parameter, or NULL if the key defines it
        TypeOf_T typeis;    ///< the type of data
        bool updated;
        Value_T value;
    } XMLItem_T;


    /// Constructor.
    ///
    /// Create the object to acquire the weather information from NOAA.
    /// The full form of a valid url to acquire the data from is:
    /// "http://www.weather.gov/data/current_obs/SITE.xml", where SITE
    /// is replaced by the proper site location code.
    ///
    /// location codes are available at http://weather.noaa.gov/data/nsd_bbsss.txt,
    /// and you can use the http://forecast.weather.gov/zipcity.php search tool
    /// to search based on either zip code or city, state.
    ///
    /// It is possible to construct the NWSWeather object with an alternate
    /// base url, but this is only useful if the alternate site is xml format
    /// compatible.
    ///
    /// 
    /// @code
    /// NWSWeather wx;
    /// #if WEATHER_GOV == 1
    /// wx.get("KALO"};
    /// #elif OPEN_WEATHER == 1
    /// wx.get(4880889, "user_id");
    /// #endif
    /// wx.PrintAllWeatherRecords();
    /// @endcode
    ///
    /// @code
    /// NWSWeather wx("http://some.alternate.site/path/");
    /// wx.get("KALO"};
    /// wx.PrintAllWeatherRecords();
    /// @endcode
    /// 
    /// @param[in] baseurl is an optional parameter to set the base url. If this
    ///     is not set a default is used.
    ///     If it is set, that alternate base url is used.
    /// #ifdef WEATHER_GOV
    ///     http://www.weather.gov/data/current_obs/
    ///     appended are two parameters
    ///     <loc_code>
    ///     ".xml"
    /// #elif OPEN_WEATHER == 1
    ///     http://api.openweathermap.org/data/2.5/weather?mode=xml&units=imperial
    ///     appended are two parameters
    ///     &id=<loc_code>
    ///     &APPID=<user_id>
    /// #endif
    /// for future forecast
    ///     http://forecast.weather.gov/MapClick.php?lat=42.47508&lon=-92.36704926700929
    //          &unit=0&lg=english&FcstType=dwml
    ///
    NWSWeather(XMLItem_T * pList, int count);
    
    /// Destructor.
    ~NWSWeather();

    /// set an alternate base url after construction of the NWSWeather object.
    ///
    /// @param[in] alternateurl is the new url to replace the baseurl.
    /// @returns success/failure code. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T setAlternateURL(const char * alternateurl);
    

    #if WEATHER_GOV == 1
    /// get the current conditions weather data from the specified site.
    ///
    /// This does the work to fetch the weather data from weatherdata.gov,
    /// for the site of interest.
    ///
    /// @param[in] site is the site/location code of the site of interest.
    /// @param[in] responseSize is optional but important. It defaults to 2500
    ///         and is intended to tell this method the size of a buffer it
    ///         should temporarily allocate to receive and process the data.
    /// @returns success/failure code. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T get(const char * site, int responseSize = 2500);
    #elif OPEN_WEATHER == 1
    /// get the current conditions weather data from the specified site.
    ///
    /// This does the work to fetch the weather data from openweathermap.org,
    /// for the site of interest.
    ///
    /// @param[in] site is the site/location code string of the site of interest.
    /// @param[in] userid is the code string registered with the site to access the data.
    /// @param[in] responseSize is optional but important. It defaults to 2500
    ///         and is intended to tell this method the size of a buffer it
    ///         should temporarily allocate to receive and process the data.
    /// @returns success/failure code. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T get(const char * site, const char * userid, int responseSize = 1000);
    #endif

    /// get the count of the number of weather parameters.
    ///
    /// @returns the count of the number of parameters for which 
    ///     data is expected.
    uint16_t count(void);

    /// determines if a specific parameter was updated from the last
    /// acquisition.
    ///
    /// @param[in] i is the item of interest.
    /// @returns noerror if the parameter is up to date. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T isUpdated(uint16_t i);
    
    /// determines if a specific parameter was updated from the last
    /// acquisition.
    ///
    /// @param[in] name is the item of interest.
    /// @returns noerror if the parameter is up to date. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T isUpdated(const char * name);
    
    /// get one of the weather parameters.
    ///
    /// This fetches one of the available weather parameters by setting
    /// user supplied pointers to reference the parameter.
    ///
    /// @code
    /// // Iterate over each of the parameters
    /// for (i=0; i<wx.count(); i++) {
    ///     char * name; TypeOf_T type; char * value;
    ///     if (wx.getParam(i, name, &type, value)) {
    ///         // print the values
    ///     }
    /// }
    /// @endcode
    ///
    /// @code
    /// // Get the names of the available parameters.
    /// for (i=0; i<wx.count(); i++) {
    ///     char *name;
    ///     if (wx.getParam(i, name) == noerror) {
    ///         // print the names of the parameters
    ///     }
    /// }
    /// @endcode
    ///
    /// @param[in] i is index of the parameter of interest.
    /// @param[in] name is a pointer that is set to point to the name of parameter i.
    /// @param[out] value is an optional pointer that is then set to point to the value of parameter i.
    /// @param[out] typeis is an optional pointer that is set to indicate the type of the value.
    /// @returns success/failure code. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T getParam(uint16_t i, char *name, Value_T *value = NULL, TypeOf_T *typeis = NULL);
    
    
    /// get one of the weather parameters.
    ///
    /// This searchs the parameter set for the named parameter, and if found
    /// it will set the user supplied pointers to the value and type information
    /// for that parameter.
    ///
    /// @code
    /// char sunrise[30]; TypeOf_T type;
    /// if (wx.getParam("sun", "rise", &sunrise, &type) == noerror) {
    ///     // print the values
    /// }
    /// @endcode
    ///
    /// @param[in] name is a const pointer to a string naming the parameter of interest.
    /// @param[in] param is the secondary part of the parameter.
    /// @param[out] value is a pointer that is set to point to the value of parameter i.
    /// @param[out] typeis is a pointer that is set to indicate the type of the value.
    /// @returns success/failure code. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T getParam(const char *name, const char * param, Value_T *value, TypeOf_T *typeis = NULL);

    /// get one of the weather parameters.
    ///
    /// This searchs the parameter set for the named parameter, and if found
    /// it will set the user supplied pointers to the value and type information
    /// for that parameter.
    ///
    /// @code
    /// float value; TypeOf_T type;
    /// if (wx.getParam("temp_f", &value, &type) == noerror) {
    ///     // print the values
    /// }
    /// @endcode
    ///
    /// @param[in] name is a const pointer to a string naming the parameter of interest.
    /// @param[out] value is a pointer that is set to point to the value of parameter i.
    /// @param[out] typeis is a pointer that is set to indicate the type of the value.
    /// @returns success/failure code. @see NWSReturnCode_T.
    ///
    NWSReturnCode_T getParam(const char *name, Value_T *value, TypeOf_T *typeis = NULL);
    
    /// Print to stdout the specified weather record.
    ///
    /// Prints the specified record as a "Parmeter = value\r\n" string, formatted
    /// so that subsequent prints line up at the '=' sign.
    ///
    /// @param[in] i specifies the record.
    ///
    void PrintWeatherRecord(uint16_t i);

    /// Print all the records to stdout.
    ///
    /// calls PrintWeatherRecord for each available parameter.
    ///
    void PrintAllWeatherRecords(void);

    /// Clear all the data and free allocated memory.
    ///
    void ClearWeatherRecords(void);

private:
    char * m_baseurl;
    NWSReturnCode_T ParseWeatherRecord(char * p);
    NWSReturnCode_T ExtractParam(int i, char * pValue, char * pEnd);
    void ParseWeatherXML(char * message);

    XMLItem_T * WeatherData;
    int WeatherItemCount;
    
};
#endif // NWSWEATHER_H