From 3cad575f7c4e6b8bb549d41d2ba1ec72db3163f7 Mon Sep 17 00:00:00 2001 From: tastytea Date: Mon, 23 Aug 2021 16:53:26 +0200 Subject: [PATCH] Add statusweather. Get weather info from openweathermap.org for use with i3blocks. --- CMakeLists.txt | 16 +++- helpers.cpp | 21 +++++ helpers.hpp | 14 ++++ statusweather.cpp | 200 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 helpers.cpp create mode 100644 helpers.hpp create mode 100644 statusweather.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 7477dc6..2dfce06 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,8 +12,18 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) find_package(fmt 7 REQUIRED CONFIG) -# find_package(termcolor CONFIG) -# find_package(Threads REQUIRED) +find_package(restclient-cpp 0.5 REQUIRED CONFIG) +find_package(nlohmann_json 3 REQUIRED CONFIG) +find_package(Threads REQUIRED) +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE) +find_package(Boost 1.65.0 REQUIRED COMPONENTS program_options) add_executable(statustime "statustime.cpp") -target_link_libraries(statustime PUBLIC fmt::fmt) +target_link_libraries(statustime PRIVATE fmt::fmt) + +add_executable(statusweather "statusweather.cpp" "helpers.cpp") +target_link_libraries(statusweather + PRIVATE + fmt::fmt restclient-cpp nlohmann_json::nlohmann_json Threads::Threads + Boost::program_options +) diff --git a/helpers.cpp b/helpers.cpp new file mode 100644 index 0000000..0985a4a --- /dev/null +++ b/helpers.cpp @@ -0,0 +1,21 @@ +#include "helpers.hpp" + +#include +#include +#include + +namespace helpers +{ + +std::string get_env(std::string_view name) +{ + const char *env = std::getenv(name.data()); // NOLINT(concurrency-mt-unsafe) + if (env != nullptr) + { + return env; + } + + return {}; +} + +} // namespace helpers diff --git a/helpers.hpp b/helpers.hpp new file mode 100644 index 0000000..964e9f3 --- /dev/null +++ b/helpers.hpp @@ -0,0 +1,14 @@ +#ifndef CPPSCRIPTS_HELPERS_HPP +#define CPPSCRIPTS_HELPERS_HPP + +#include +#include + +namespace helpers +{ + +std::string get_env(std::string_view name); + +} // namespace helpers + +#endif // CPPSCRIPTS_HELPERS_HPP diff --git a/statusweather.cpp b/statusweather.cpp new file mode 100644 index 0000000..8804342 --- /dev/null +++ b/statusweather.cpp @@ -0,0 +1,200 @@ +// Print weather information for i3 status bar. +/* i3blocks config: + * [weather] + * command=statusweather + * interval=persist + * markup=pango + */ + +#include "helpers.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct weather +{ + float temperature{0.0}; + std::string icon; +} __attribute__((aligned(64))) weather; +std::mutex mutex_weather; + +std::string map_icon(const std::string_view icon_id) +{ + // + const std::map icons{{{1, "🌞"}, + {2, "⛅"}, + {3, "☁"}, + {4, "☁"}, + {9, "🌧"}, + {10, "🌧"}, + {10, "⛈"}, + {13, "🌨"}, + {50, "🌫"}}}; + // TODO: Differenciate between day and night. + auto icon{icons.find(std::stoul(icon_id.data()))}; + if (icon != icons.end()) + { + return icon->second; + } + return {}; +} + +std::tuple get_options() +{ + namespace po = boost::program_options; + + po::options_description options("Options"); + // clang-format off + options.add_options() + ("api_key", po::value()->required()->value_name("API key"), + "API key for openweathermap.org.") + ("city", po::value()->required()->value_name("City"), + "City you want the weather data for.") + ; + // clang-format on + + po::variables_map vm; + const auto path{[]() + { + auto path{helpers::get_env("XDG_CONFIG_HOME")}; + if (path.empty()) + { + path = helpers::get_env("HOME"); + if (!path.empty()) + { + path += "/.config"; + } + } + return path += "/statusweather.cfg"; + }()}; + std::ifstream configfile(path); + po::store(po::parse_config_file(configfile, options, true), vm); + configfile.close(); + if ((vm.count("api_key") == 0) || (vm.count("city") == 0)) + { + throw std::runtime_error{"api_key or city not configured."}; + } + + return std::make_tuple(vm["api_key"].as(), + vm["city"].as()); +} + +bool fetch_weather() +{ + using fmt::format; + + std::string api_key; + std::string city; + try + { + std::tie(api_key, city) = get_options(); + } + catch (std::runtime_error &e) + { + std::cout << R"()" << e.what() << "" + << std::endl; + return false; + } + + RestClient::init(); + RestClient::Connection conn( + "http://api.openweathermap.org/data/2.5/weather"); + conn.FollowRedirects(true, 5); + conn.SetTimeout(10); + // + auto response{ + conn.get(format("?appid={0:s}&q={1:s}&units=metric", api_key, city))}; + RestClient::disable(); + + if (response.code == 200) + { + std::lock_guard guard(mutex_weather); + const auto json{nlohmann::json::parse(response.body)}; + weather.temperature = json[0]["main"]["temp"].get(); + weather.icon = map_icon( + json[0]["weather"][0]["icon"].get()); + return true; + } + return false; +} + +void print_weather() +{ + using fmt::format; + + std::lock_guard guard(mutex_weather); + const std::string color{[] + { + if (weather.temperature > 25.0) + { + return "#ff2200"; + } + if (weather.temperature < 0.0) + { + return "#aaffff"; + } + if (weather.temperature < 10.0) + { + return "#44ddff"; + } + return "#66ff66"; + }()}; + + std::cout << format(R"({0:s} {2:.1f}°C)", + weather.icon, color, weather.temperature) + << std::endl; +} + +void update(std::atomic &cancelled) +{ + using clock = std::chrono::system_clock; + using namespace std::chrono_literals; + + while (!cancelled) + { + if (fetch_weather()) + { + print_weather(); + } + std::this_thread::sleep_until(clock::now() + 30min); + } +} + +int main() +{ + // TODO: Implement clean shutdown. + std::atomic cancelled{false}; + auto future{std::async(std::launch::async, update, std::ref(cancelled))}; + + std::string line; + while (std::getline(std::cin, line)) // Button click is sent to stdin. + { + if (line == "1") // Left mouse button. + { + if (fetch_weather()) + { + print_weather(); + } + } + } +}