yambar/modules/weather.c
Ben Boeckel 11f7f38a3c weather: add plugin
This plugin fetches weather information in the same fashion as
Xmobar's Weather plugin. It fetches from NOAA's website (by default)
using their standard format for reporting data from weather stations
around the world.

Also enable in CI.

TODO:

Fill out the example configuration in the docfile.
2025-03-10 00:23:04 +01:00

243 lines
6.6 KiB
C

#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <poll.h>
#include <curl/curl.h>
#define LOG_MODULE "weather"
#define LOG_ENABLE_DBG 0
#include "../bar/bar.h"
#include "../config-verify.h"
#include "../config.h"
#include "../log.h"
#include "../plugin.h"
#include "version.h"
#include "weather.h"
#define DEFAULT_HOST "https://tgftp.nws.noaa.gov/data/observations/metar/decoded/"
static void
free_weather_info(struct weather_info *info)
{
if (!info) {
return;
}
free(info->station_town);
free(info->station_state);
free(info->wind_direction);
free(info->visibility);
free(info->sky_condition);
free(info->weather);
free(info);
}
struct private
{
struct particle *label;
char *host;
char *station;
struct weather_info *info;
};
static void
destroy(struct module *mod)
{
struct private *m = mod->private;
m->label->destroy(m->label);
free(m->host);
free(m->station);
free_weather_info(m->info);
free(m);
module_default_destroy(mod);
}
static const char *
description(const struct module *mod)
{
return "weather";
}
static struct exposable *
content(struct module *mod)
{
const struct private *m = mod->private;
mtx_lock(&mod->lock);
struct tag_set tags = {
.tags = (struct tag *[]){
tag_new_string(mod, "station_town", m->info->station_town),
tag_new_string(mod, "station_state", m->info->station_state),
tag_new_int(mod, "year", m->info->year),
tag_new_int(mod, "month", m->info->month),
tag_new_int(mod, "day", m->info->day),
tag_new_int(mod, "hour", m->info->hour),
tag_new_int(mod, "minute", m->info->minute),
tag_new_string(mod, "wind_direction", m->info->wind_direction),
tag_new_int(mod, "wind_azimuth", m->info->wind_azimuth),
tag_new_int(mod, "wind_mph", m->info->wind_mph),
tag_new_int(mod, "wind_knots", m->info->wind_knots),
tag_new_int(mod, "wind_kmph", m->info->wind_kmph),
tag_new_int(mod, "wind_mps", m->info->wind_mps),
tag_new_string(mod, "visibility", m->info->visibility),
tag_new_string(mod, "sky_condition", m->info->sky_condition),
tag_new_string(mod, "weather", m->info->weather),
tag_new_float(mod, "temp_c", m->info->temp_c),
tag_new_float(mod, "temp_f", m->info->temp_f),
tag_new_float(mod, "heat_index_c", m->info->heat_index_c),
tag_new_float(mod, "heat_index_f", m->info->heat_index_f),
tag_new_float(mod, "dew_point_c", m->info->dew_point_c),
tag_new_float(mod, "dew_point_f", m->info->dew_point_f),
tag_new_int(mod, "humidity", m->info->humidity),
tag_new_float(mod, "pressure_mmhg", m->info->pressure_mmhg),
tag_new_int(mod, "pressure_hpa", m->info->pressure_hpa),
},
.count = 25,
};
mtx_unlock(&mod->lock);
struct exposable *exposable = m->label->instantiate(m->label, &tags);
tag_set_destroy(&tags);
return exposable;
}
static size_t
weather_curl_buffer_write(void *contents, size_t size, size_t n, void *data)
{
size_t new_size = size * n;
struct weather_curl_buffer *buf = (struct weather_curl_buffer *)data;
// Grow the buffer if necessary.
size_t needed = buf->size + new_size + 1;
if (buf->capacity < needed) {
size_t new_cap = buf->capacity;
while (new_cap < needed) {
new_cap <<= 1;
}
char *newmem = realloc(buf->buffer, new_cap);
if (!newmem) {
// TODO: log
return 0;
}
buf->buffer = newmem;
buf->capacity = new_cap;
}
memcpy(buf->buffer + buf->size, contents, new_size);
buf->size += new_size;
buf->buffer[buf->size] = '\0';
return new_size;
};
struct weather_curl_buffer *
weather_curl_buffer_new()
{
struct weather_curl_buffer *buf = (struct weather_curl_buffer*)calloc(1, sizeof(*buf));
// Start with one page of memory.
buf->buffer = calloc(1, 4096);
buf->capacity = buf->buffer ? 4096 : 0;
return buf;
}
void
free_weather_curl_buffer(struct weather_curl_buffer *buf)
{
if (!buf) {
return;
}
free(buf->buffer);
free(buf);
}
static int
run(struct module *mod)
{
struct private *m = mod->private;
free_weather_info(m->info);
m->info = calloc(1, sizeof(*m->info));
static char url[4096];
snprintf(url, sizeof(url), "%s%s.TXT", m->host, m->station);
CURL *curl = curl_easy_init();
struct weather_curl_buffer *buf = weather_curl_buffer_new();
if (curl) {
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, weather_curl_buffer_write);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "yambar/" YAMBAR_VERSION);
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
free_weather_curl_buffer(buf);
buf = NULL;
}
}
curl_easy_cleanup(curl);
if (!buf) {
return 1;
}
int ret = parse_weather_info(buf, m->info);
free_weather_curl_buffer(buf);
return ret;
}
static struct module *
weather_new(struct particle *label, const char* host, const char *station)
{
struct private *m = calloc(1, sizeof(*m));
m->label = label;
m->host = strdup(host);
m->station = strdup(station);
struct module *mod = module_common_new();
mod->private = m;
mod->run = &run;
mod->destroy = &destroy;
mod->content = &content;
mod->description = &description;
return mod;
}
static struct module *
from_conf(const struct yml_node *node, struct conf_inherit inherited)
{
const struct yml_node *c = yml_get_value(node, "content");
const struct yml_node *station = yml_get_value(node, "station");
const struct yml_node *host = yml_get_value(node, "host");
return weather_new(conf_to_particle(c, inherited), yml_value_as_string(station), host == NULL ? DEFAULT_HOST : yml_value_as_string(host));
}
static bool
verify_conf(keychain_t *chain, const struct yml_node *node)
{
static const struct attr_info attrs[] = {
{"station", true, &conf_verify_string},
{"host", false, &conf_verify_string},
MODULE_COMMON_ATTRS,
};
return conf_verify_dict(chain, node, attrs);
}
const struct module_iface module_weather_iface = {
.verify_conf = &verify_conf,
.from_conf = &from_conf,
};
#if defined(CORE_PLUGINS_AS_SHARED_LIBRARIES)
extern const struct module_iface iface __attribute__((weak, alias("module_weather_iface")));
#endif