Add statusweather.
Get weather info from openweathermap.org for use with i3blocks.
This commit is contained in:
parent
803761f680
commit
3cad575f7c
|
@ -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
|
||||
)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
#include "helpers.hpp"
|
||||
|
||||
#include <cstdlib>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
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
|
|
@ -0,0 +1,14 @@
|
|||
#ifndef CPPSCRIPTS_HELPERS_HPP
|
||||
#define CPPSCRIPTS_HELPERS_HPP
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace helpers
|
||||
{
|
||||
|
||||
std::string get_env(std::string_view name);
|
||||
|
||||
} // namespace helpers
|
||||
|
||||
#endif // CPPSCRIPTS_HELPERS_HPP
|
|
@ -0,0 +1,200 @@
|
|||
// Print weather information for i3 status bar.
|
||||
/* i3blocks config:
|
||||
* [weather]
|
||||
* command=statusweather
|
||||
* interval=persist
|
||||
* markup=pango
|
||||
*/
|
||||
|
||||
#include "helpers.hpp"
|
||||
|
||||
#include <boost/program_options/options_description.hpp>
|
||||
#include <boost/program_options/parsers.hpp>
|
||||
#include <boost/program_options/variables_map.hpp>
|
||||
#include <fmt/core.h>
|
||||
#include <nlohmann/json.hpp>
|
||||
#include <restclient-cpp/connection.h>
|
||||
#include <restclient-cpp/restclient.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <future>
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
|
||||
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)
|
||||
{
|
||||
// <https://openweathermap.org/weather-conditions>
|
||||
const std::map<std::uint8_t, std::string> 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<std::string, std::string> get_options()
|
||||
{
|
||||
namespace po = boost::program_options;
|
||||
|
||||
po::options_description options("Options");
|
||||
// clang-format off
|
||||
options.add_options()
|
||||
("api_key", po::value<std::string>()->required()->value_name("API key"),
|
||||
"API key for openweathermap.org.")
|
||||
("city", po::value<std::string>()->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<std::string>(),
|
||||
vm["city"].as<std::string>());
|
||||
}
|
||||
|
||||
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"(<span color="red">)" << e.what() << "</span>"
|
||||
<< std::endl;
|
||||
return false;
|
||||
}
|
||||
|
||||
RestClient::init();
|
||||
RestClient::Connection conn(
|
||||
"http://api.openweathermap.org/data/2.5/weather");
|
||||
conn.FollowRedirects(true, 5);
|
||||
conn.SetTimeout(10);
|
||||
// <https://openweathermap.org/current>
|
||||
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<std::mutex> guard(mutex_weather);
|
||||
const auto json{nlohmann::json::parse(response.body)};
|
||||
weather.temperature = json[0]["main"]["temp"].get<float>();
|
||||
weather.icon = map_icon(
|
||||
json[0]["weather"][0]["icon"].get<std::string_view>());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void print_weather()
|
||||
{
|
||||
using fmt::format;
|
||||
|
||||
std::lock_guard<std::mutex> 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} <span color="{1:s}">{2:.1f}°C</span>)",
|
||||
weather.icon, color, weather.temperature)
|
||||
<< std::endl;
|
||||
}
|
||||
|
||||
void update(std::atomic<bool> &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<bool> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue