Skip to content

joannarousseau/weather

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

23 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Weather

Description:

A command line program that prints out the current temperature when given a city and country code.

'Weather' makes use of the 'OpenWeatherMap' API to display the current temperature for the city specified. The temperature is displayed in 'Celsius' by default, however this can be optionally changed to : 'imperial'(°F), 'metric'(°C) or 'standard' (Kelvin).

Usage: weather [-u, --units] city iso3166_country_code

Example:
$ weather -u imperial 'New York' USA
The temperature in New York is 79.65 °F

'OpenWeatherMap' requires an API key which is freely available for personal use from openweathermap.org. This key must be set as the 'API_KEY_WEATHER' environment variable for 'weather' to function.

Design Rationale

'libcurl' was chosen as a tool to talk to the 'OpenWeatherMap' server as it had extensive documentation and was well-established. 'libcurl' also optionally produced code generated by the curl command line tools. This was really helpful for this project as making use of a library saved on development hours (as opposed to writing form scratch).

A web API was used for this project as different scenarios could be easily tested in the browser and the various different API options available could be explored. 'OpenWeatherMap' was chosen as it was open-source, free and adaptable as it returned different formats such as JSON, XML,etc.

OpenWeatherMap requires the use of an API key to access the service. The use of the web API is rate-limited on the free tier. As such, each end-user will require their own API key (rather than using a compiled-in global key for all users). An environment variable was used to keep track of this. This was picked instead of a command line argument as the key needed to be typed precisely and it was best to limit user input in the command line.

As JSON was the easiest and fastest format to parse, this was chosen for the project. The search for good libraries to parse JSON focused on libraries already packaged for Ubuntu as this was the development environment being used. 'json-c' was chosen for its simple and intuitive API and data model. Additionally, there were no dependencies on other libraries and json-c was easy to embed directly into a project like this.

'City' and 'country' are supplied as separate arguments to the program in order to follow the same convention as the 'OpenWeatherMap' API. This decision may be revisited in future. To offer flexibility, the user is able to override the default units that are used (Celsius). This argument is passed directly to the OpenWeatherMap API, although conversion could be performed locally if this was not an option.

weather.c

main()

main() first validates the API key by checking if the environment variable is set. It also checks the length of the key and that all the digits are hexadecimal. Length and hex digits are checked within the same loop (rather than using strlen) to avoid having to visit each character twice. An extra check was also needed to ensure that the API key was sufficiently long. There are limitations to the extent to which an API key can be checked as only 'OpenWeatherMap' can ultimately determine if a key is valid. The project currently does not differentiate between errors so specific messaging could be introduced in future to communicate OpenWeatherMap API key errors to the user.

Errors are typically indicated by returning standard exit codes (EXIT_SUCCESS/EXIT_FAILURE) and printing error messages to 'stderr'. This separates error messages from the standard program output allowing errors to be seen if stdout is redirected elsewhere.

'getopt' was then used to allow the units the temperature was displayed in to be overridden. long_options were used so that '-u' or '--units' could be supplied by the user. To allow users to input units in a case-insensitive manner, 'tolower' was used on the user input and compared with 'strcmp' against three strings: 'standard', 'metric', and 'imperial'. These three strings were chosen as they were the same as those used by the 'OpenWeatherMAP' API; this avoided the use of a look-up-table.

Once the optional command line arguments were parsed, main() then checks that the two required arguments (city and country code) were present. All optional/required arguments are then substituted into the 'url_template' and a check is made to ensure that it does not exceed the 'MAX_URL_LENGTH'. This is done to maximise compatibility as URLs under 2000 characters should work with virtually all combinations of client/server software.

curl_easy_init() follows in main() which initialises a libcurl easy environment and returns a CURL easy handle that must be used as input to other functions in the easy interface (in this case *hnd). Curl options are then set (including set callback for writing received data (received_data_callback())). libcurl documents this callback function as follows:

This callback function gets called by libcurl as soon as there is data received that needs to be saved. For most transfers, this callback gets called many times and each invoke delivers another chunk of data.

This project assumes that all data arrives in full; however, libcurl does not guarantee this. A suggestion for future improvement would be to research what json-c suggests for this issue and refactor the code appropriately.

received_data()

'received_data' is a callback function (set with the CURLOPT_WRITEFUNCTION option) that is called by libcurl as soon as there is data received that needs to be processed. 'ptr' points to the delivered data, and the size of that data is nmemb; size is always 1 as 'ptr' is always 'char *'. 'received_data' was marked as 'static' so that this function was encapsulated and not available outside this compilation unit.

'size' & 'userp' were cast to void to suppress compiler warnings relating to unused variables. 'nmemb' is verified as non-zero as libcurl may call 'received_data' in this manner if the transferred file is empty. The received data was verified as null-terminated as the JSON parser operates on C-strings.

A limitation of this implementation is that, if all data is not received in one go, (which is not guaranteed by libcurl) the program will abort. This is partly why the 'null_char_found' check was made. If the null character is not found as the last character, it is assumed that parts of the data are missing and thus can't be parsed.

Once these basic sanity checks had been made, json_tokener_parse() was called. json_tokener_parse() returns a pointer which can then be used to traverse the tree. A check for NULL was made to ensure that a valid JSON value was parsed successfully.

A simplified (and formatted) example of the JSON received from the OpenWeather API is shown below.

{
    "main":{
        "temp":292.57,
        "feels_like":292.26,
        "temp_min":291.01,
        "temp_max":294.8,
        "pressure":1029,
        "humidity":65
    },
    "name":"London"
}

The code makes use of json_object_object_get_ex() to retrieve both 'name' and 'temp'. This function gets the json_object associated with a given object field which can be used to descend the tree. It does not retrieve values so, for example, json_object_get_string() is used to fetch the string value of 'name' in order to print out the name of the city. Because the data is hierarchical, the function must be called multiple times in in order to access 'temp', as a child of 'main'.

All data is encoded as strings in JSON, but the decision was made that 'temp' should be converted to a double; as it allows the data to be manipulated mathematically should that be desired in future (i.e. averaging lots of temperatures). It was also a good excuse for the programmer to explore the API further. 'json_object_is_type()' was first called to check if the 'temp' could be interpreted as a double. Should this succeed, then 'json_object_get_double()' is then called to perform the conversion. Alternatively 'json_object_get_double(temperature)' could be called directly followed by checking errno's value. Whilst this code would be easier to read, the use of 'errno' could cause thread-safety issues if this code was expanded upon in future.

CMakeLists.txt

This project makes use of multiple open-source libraries. As such, it would be non-trivial to ensure that all the dependencies are satisfied and to build the project from source manually. CMake is a build tool that helps solve these problems by ensuring that the necessary libraries and header files are present so the project may compile. CMake is a cross-platform tool that can generate many different types of makefile allowing the project to be compiled in different environments.

'find_package()' can be used to search for 'CURL' as CMake ships with a 'FindCURL.cmake' file (as CURL is a very common library). However, this is not available for 'json-c'. Instead, the FindPkgConfig package (which makes use of the pkg-config tool) was used.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published