mirror of
https://codeberg.org/dnkl/yambar.git
synced 2025-04-19 19:25:41 +02:00
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.
This commit is contained in:
parent
082ab41598
commit
11f7f38a3c
12 changed files with 1187 additions and 0 deletions
|
@ -27,6 +27,7 @@ packages:
|
||||||
- py3-pip
|
- py3-pip
|
||||||
- flex
|
- flex
|
||||||
- bison
|
- bison
|
||||||
|
- curl-dev
|
||||||
|
|
||||||
sources:
|
sources:
|
||||||
- https://git.sr.ht/~dnkl/yambar
|
- https://git.sr.ht/~dnkl/yambar
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
* pipewire: added `spacing`, `left-spacing` and `right-spacing`
|
* pipewire: added `spacing`, `left-spacing` and `right-spacing`
|
||||||
attributes.
|
attributes.
|
||||||
* mpris: new module ([#53][53]).
|
* mpris: new module ([#53][53]).
|
||||||
|
* weather: new module ([#448][448]).
|
||||||
|
|
||||||
[96]: https://codeberg.org/dnkl/yambar/issues/96
|
[96]: https://codeberg.org/dnkl/yambar/issues/96
|
||||||
[380]: https://codeberg.org/dnkl/yambar/issues/380
|
[380]: https://codeberg.org/dnkl/yambar/issues/380
|
||||||
|
@ -40,6 +41,7 @@
|
||||||
[428]: https://codeberg.org/dnkl/yambar/pulls/428
|
[428]: https://codeberg.org/dnkl/yambar/pulls/428
|
||||||
[404]: https://codeberg.org/dnkl/yambar/issues/404
|
[404]: https://codeberg.org/dnkl/yambar/issues/404
|
||||||
[53]: https://codeberg.org/dnkl/yambar/issues/53
|
[53]: https://codeberg.org/dnkl/yambar/issues/53
|
||||||
|
[448]: https://codeberg.org/dnkl/yambar/pulls/448
|
||||||
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -53,6 +53,9 @@ endif
|
||||||
if plugin_niri_workspaces_enabled
|
if plugin_niri_workspaces_enabled
|
||||||
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
|
plugin_pages += ['yambar-modules-niri-workspaces.5.scd']
|
||||||
endif
|
endif
|
||||||
|
if plugin_weather_enabled
|
||||||
|
plugin_pages += ['yambar-modules-weather.5.scd']
|
||||||
|
endif
|
||||||
if plugin_pipewire_enabled
|
if plugin_pipewire_enabled
|
||||||
plugin_pages += ['yambar-modules-pipewire.5.scd']
|
plugin_pages += ['yambar-modules-pipewire.5.scd']
|
||||||
endif
|
endif
|
||||||
|
|
128
doc/yambar-modules-weather.5.scd
Normal file
128
doc/yambar-modules-weather.5.scd
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
yambar-modules-weather(5)
|
||||||
|
|
||||||
|
# NAME
|
||||||
|
weather - This module monitors weather station reports
|
||||||
|
|
||||||
|
# DESCRIPTION
|
||||||
|
|
||||||
|
This module monitors weather reports in NOAA's format from weather
|
||||||
|
stations around the world. It instantiates the provided _content_
|
||||||
|
particle for the station specified.
|
||||||
|
|
||||||
|
# TAGS
|
||||||
|
|
||||||
|
[[ *Name*
|
||||||
|
:[ *Type*
|
||||||
|
:< *Description*
|
||||||
|
| station_town
|
||||||
|
: string
|
||||||
|
: Town name of the station.
|
||||||
|
| station_state
|
||||||
|
: string
|
||||||
|
: Region name of the station.
|
||||||
|
| year
|
||||||
|
: int
|
||||||
|
: Year of the weather report.
|
||||||
|
| month
|
||||||
|
: int
|
||||||
|
: Month of the weather report.
|
||||||
|
| day
|
||||||
|
: int
|
||||||
|
: Day of the weather report.
|
||||||
|
| hour
|
||||||
|
: int
|
||||||
|
: Hour of the weather report.
|
||||||
|
| minute
|
||||||
|
: int
|
||||||
|
: Minute of the weather report.
|
||||||
|
| wind_direction
|
||||||
|
: string
|
||||||
|
: Direction of the wind or *μ* if calm or variable.
|
||||||
|
| wind_azimuth
|
||||||
|
: int
|
||||||
|
: The azimuth of the wind or *-1* if calm or variable.
|
||||||
|
| wind_mph
|
||||||
|
: int
|
||||||
|
: The speed of the wind in miles per hour (m/h).
|
||||||
|
| wind_knots
|
||||||
|
: int
|
||||||
|
: The speed of the wind in nautical knots.
|
||||||
|
| wind_kmph
|
||||||
|
: int
|
||||||
|
: The speed of the wind in kilometers per hour (km/h).
|
||||||
|
| wind_mps
|
||||||
|
: int
|
||||||
|
: The speed of the wind in meters per second (m/s).
|
||||||
|
| visibility
|
||||||
|
: string
|
||||||
|
: Visibility report.
|
||||||
|
| sky_condition
|
||||||
|
: string
|
||||||
|
: Description of the sky condition (e.g., cloudy).
|
||||||
|
| weather
|
||||||
|
: string
|
||||||
|
: Description of the weather (not always available).
|
||||||
|
| temp_c
|
||||||
|
: float
|
||||||
|
: Temperature in Celsius.
|
||||||
|
| temp_f
|
||||||
|
: float
|
||||||
|
: Temperature in Fahrenheit.
|
||||||
|
| heat_index_c
|
||||||
|
: float
|
||||||
|
: Heat index in Celsius.
|
||||||
|
| heat_index_f
|
||||||
|
: float
|
||||||
|
: Heat index in Fahrenheit.
|
||||||
|
| dew_point_c
|
||||||
|
: float
|
||||||
|
: Dew point in Celsius.
|
||||||
|
| dew_point_f
|
||||||
|
: float
|
||||||
|
: Dew point in Fahrenheit.
|
||||||
|
| humidity
|
||||||
|
: int
|
||||||
|
: Relative humidity (0-100).
|
||||||
|
| pressure_mmhg
|
||||||
|
: float
|
||||||
|
: Pressure in millimeters of Mercury (mmHg).
|
||||||
|
| pressure_hpa
|
||||||
|
: int
|
||||||
|
: Pressure in hectapascals (hPa).
|
||||||
|
|
||||||
|
|
||||||
|
# CONFIGURATION
|
||||||
|
|
||||||
|
[[ *Name*
|
||||||
|
:[ *Type*
|
||||||
|
:[ *Req*
|
||||||
|
:< *Description*
|
||||||
|
| station
|
||||||
|
: string
|
||||||
|
: yes
|
||||||
|
: Name of the station to report weather.
|
||||||
|
| host
|
||||||
|
: string
|
||||||
|
: no
|
||||||
|
: Website to fetch the weather report from. Defaults to NOAA's
|
||||||
|
website which uses ICAO names for stations.
|
||||||
|
|
||||||
|
|
||||||
|
# EXAMPLES
|
||||||
|
|
||||||
|
Display weather report for KIAD (Washington Dulles Airport).
|
||||||
|
|
||||||
|
```
|
||||||
|
bar:
|
||||||
|
left:
|
||||||
|
- weather:
|
||||||
|
station: KIAD
|
||||||
|
content:
|
||||||
|
map:
|
||||||
|
TODO
|
||||||
|
```
|
||||||
|
|
||||||
|
# SEE ALSO
|
||||||
|
|
||||||
|
*yambar-modules*(5), *yambar-particles*(5), *yambar-tags*(5), *yambar-decorations*(5)
|
||||||
|
|
|
@ -195,6 +195,7 @@ summary(
|
||||||
'Sway XKB keyboard': plugin_sway_xkb_enabled,
|
'Sway XKB keyboard': plugin_sway_xkb_enabled,
|
||||||
'Niri language': plugin_niri_language_enabled,
|
'Niri language': plugin_niri_language_enabled,
|
||||||
'Niri workspaces': plugin_niri_workspaces_enabled,
|
'Niri workspaces': plugin_niri_workspaces_enabled,
|
||||||
|
'Weather': plugin_weather_enabled,
|
||||||
'XKB keyboard (for X11)': plugin_xkb_enabled,
|
'XKB keyboard (for X11)': plugin_xkb_enabled,
|
||||||
'XWindow (window tracking for X11)': plugin_xwindow_enabled,
|
'XWindow (window tracking for X11)': plugin_xwindow_enabled,
|
||||||
},
|
},
|
||||||
|
|
|
@ -50,6 +50,8 @@ option('plugin-niri-language', type: 'feature', value: 'auto',
|
||||||
description: 'language support for Niri')
|
description: 'language support for Niri')
|
||||||
option('plugin-niri-workspaces', type: 'feature', value: 'auto',
|
option('plugin-niri-workspaces', type: 'feature', value: 'auto',
|
||||||
description: 'workspaces support for Niri')
|
description: 'workspaces support for Niri')
|
||||||
|
option('plugin-weather', type: 'feature', value: 'auto',
|
||||||
|
description: 'Weather polling support')
|
||||||
option('plugin-xkb', type: 'feature', value: 'auto',
|
option('plugin-xkb', type: 'feature', value: 'auto',
|
||||||
description: 'keyboard support for X11')
|
description: 'keyboard support for X11')
|
||||||
option('plugin-xwindow', type: 'feature', value: 'auto',
|
option('plugin-xwindow', type: 'feature', value: 'auto',
|
||||||
|
|
|
@ -60,6 +60,9 @@ plugin_xkb_enabled = backend_x11 and xcb_xkb.found()
|
||||||
|
|
||||||
plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed()
|
plugin_xwindow_enabled = backend_x11 and get_option('plugin-xwindow').allowed()
|
||||||
|
|
||||||
|
curl = dependency('libcurl', required: get_option('plugin-weather'))
|
||||||
|
plugin_weather_enabled = curl.found() and get_option('plugin-weather').allowed()
|
||||||
|
|
||||||
# Module name -> (source-list, dep-list)
|
# Module name -> (source-list, dep-list)
|
||||||
mod_data = {}
|
mod_data = {}
|
||||||
|
|
||||||
|
@ -144,6 +147,14 @@ if plugin_niri_workspaces_enabled
|
||||||
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
|
mod_data += {'niri-workspaces': [['niri-common.c', 'niri-common.h'], [dynlist, json_niri_workspaces]]}
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if plugin_weather_enabled
|
||||||
|
mod_data += {'weather': [['weather-parse.c'], [curl]]}
|
||||||
|
|
||||||
|
e = executable('test-weather', ['test-weather.c', 'weather-parse.c'],
|
||||||
|
dependencies: [pixman, fcft, tllist, curl])
|
||||||
|
test('module-weather', e)
|
||||||
|
endif
|
||||||
|
|
||||||
if plugin_xkb_enabled
|
if plugin_xkb_enabled
|
||||||
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
|
mod_data += {'xkb': [[], [xcb_stuff, xcb_xkb]]}
|
||||||
endif
|
endif
|
||||||
|
|
341
modules/test-weather.c
Normal file
341
modules/test-weather.c
Normal file
|
@ -0,0 +1,341 @@
|
||||||
|
#include "weather.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define PARSE_SUCCEED(name, marker, info, lit) \
|
||||||
|
do { \
|
||||||
|
if (0) { \
|
||||||
|
puts("string literal assertion: " lit); \
|
||||||
|
} \
|
||||||
|
const struct weather_curl_buffer buf = { \
|
||||||
|
.buffer = lit, \
|
||||||
|
.capacity = 0, \
|
||||||
|
.size = sizeof(lit), \
|
||||||
|
}; \
|
||||||
|
fprintf(stderr, "========================================\n"); \
|
||||||
|
fprintf(stderr, "Testing " name "\n"); \
|
||||||
|
int res = parse_weather_info(&buf, &info); \
|
||||||
|
if (res) { \
|
||||||
|
fprintf(stderr, "unexpected failure to parse test '" name "'\n"); \
|
||||||
|
marker = 0; \
|
||||||
|
overall = EXIT_FAILURE; \
|
||||||
|
} else { \
|
||||||
|
fprintf(stderr, "succeeded in parsing\n"); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define PARSE_FAIL(name, lit) \
|
||||||
|
do { \
|
||||||
|
if (0) { \
|
||||||
|
puts("string literal assertion: " lit); \
|
||||||
|
} \
|
||||||
|
const struct weather_curl_buffer buf = { \
|
||||||
|
.buffer = lit, \
|
||||||
|
.capacity = 0, \
|
||||||
|
.size = sizeof(lit), \
|
||||||
|
}; \
|
||||||
|
struct weather_info info = {0}; \
|
||||||
|
fprintf(stderr, "========================================\n"); \
|
||||||
|
fprintf(stderr, "Testing " name " (expected to fail)\n"); \
|
||||||
|
int res = parse_weather_info(&buf, &info); \
|
||||||
|
if (!res) { \
|
||||||
|
fprintf(stderr, "unexpected successful parsing of '" name "'\n"); \
|
||||||
|
overall = EXIT_FAILURE; \
|
||||||
|
} else { \
|
||||||
|
fprintf(stderr, "succeeded in detecting bad parsing\n"); \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define CHECK_STR(info, field, expect) \
|
||||||
|
do { \
|
||||||
|
if (!info.field) { \
|
||||||
|
fprintf(stderr, "Unexpected `" #field "`: NULL\n"); \
|
||||||
|
} else if (strcmp(info.field, expect)) { \
|
||||||
|
fprintf(stderr, "Unexpected `" #field "`: '%s', expected '" expect "'\n", info.field); \
|
||||||
|
overall = EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
#define CHECK_DOUBLE(info, field, expect) \
|
||||||
|
do { \
|
||||||
|
if (info.field != expect) { \
|
||||||
|
fprintf(stderr, "Unexpected `" #field "`: '%f', expected '" #expect "'\n", info.field); \
|
||||||
|
overall = EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
#define CHECK_INT(info, field, expect) \
|
||||||
|
do { \
|
||||||
|
if (info.field != expect) { \
|
||||||
|
fprintf(stderr, "Unexpected `" #field "`: '%d', expected '" #expect "'\n", info.field); \
|
||||||
|
overall = EXIT_FAILURE; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
|
||||||
|
#define DEFAULT_STATION \
|
||||||
|
"Tirstrup, Denmark (EKAH) 56-18N 010-37E 25M\n"
|
||||||
|
#define CHECK_DEFAULT_STATION(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_STR(info, station_town, "Tirstrup"); \
|
||||||
|
CHECK_STR(info, station_state, "Denmark"); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_TIME \
|
||||||
|
"Dec 22, 2024 - 04:20 PM EST / 2024.12.22 2120 UTC\n"
|
||||||
|
#define CHECK_DEFAULT_TIME(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_INT(info, year, 2024); \
|
||||||
|
CHECK_INT(info, month, 12); \
|
||||||
|
CHECK_INT(info, day, 22); \
|
||||||
|
CHECK_INT(info, hour, 21); \
|
||||||
|
CHECK_INT(info, minute, 20); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_WIND \
|
||||||
|
"Wind: from the SSW (200 degrees) at 5 MPH (4 KT) (direction variable):0\n"
|
||||||
|
#define CHECK_DEFAULT_WIND(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_STR(info, wind_direction, "SSW"); \
|
||||||
|
CHECK_INT(info, wind_azimuth, 200); \
|
||||||
|
CHECK_INT(info, wind_mph, 5); \
|
||||||
|
CHECK_INT(info, wind_knots, 4); \
|
||||||
|
CHECK_INT(info, wind_kmph, 7); \
|
||||||
|
CHECK_INT(info, wind_mps, 2); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_VISIBILITY \
|
||||||
|
"Visibility: greater than 7 mile(s):0\n"
|
||||||
|
#define CHECK_DEFAULT_VISIBILITY(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_STR(info, visibility, "greater than 7 mile(s)"); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_SKY_CONDITIONS \
|
||||||
|
"Sky conditions: mostly clear\n"
|
||||||
|
#define CHECK_DEFAULT_SKY_CONDITIONS(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_STR(info, sky_condition, "mostly clear"); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_WEATHER \
|
||||||
|
"Weather: rain\n"
|
||||||
|
#define CHECK_DEFAULT_WEATHER(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_STR(info, weather, "rain"); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_TEMPERATURE \
|
||||||
|
"Temperature: 37 F (3 C)\n"
|
||||||
|
#define CHECK_DEFAULT_TEMPERATURE(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_DOUBLE(info, temp_f, 37); \
|
||||||
|
CHECK_DOUBLE(info, temp_c, 3); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_HEAT_INDEX \
|
||||||
|
"Heat index: 35 F (2 C):0\n"
|
||||||
|
#define CHECK_DEFAULT_HEAT_INDEX(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_DOUBLE(info, heat_index_f, 35); \
|
||||||
|
CHECK_DOUBLE(info, heat_index_c, 2); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_DEW_POINT \
|
||||||
|
"Dew Point: 35 F (2 C)\n"
|
||||||
|
#define CHECK_DEFAULT_DEW_POINT(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_DOUBLE(info, dew_point_f, 35); \
|
||||||
|
CHECK_DOUBLE(info, dew_point_c, 2); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_RELATIVE_HUMIDITY \
|
||||||
|
"Relative Humidity: 93%\n"
|
||||||
|
#define CHECK_DEFAULT_RELATIVE_HUMIDITY(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_INT(info, humidity, 93); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_PRESSURE \
|
||||||
|
"Pressure (altimeter): 29.26 in. Hg (0991 hPa)\n"
|
||||||
|
#define CHECK_DEFAULT_PRESSURE(info) \
|
||||||
|
do { \
|
||||||
|
CHECK_DOUBLE(info, pressure_mmhg, 29.26); \
|
||||||
|
CHECK_INT(info, pressure_hpa, 991); \
|
||||||
|
} while (0)
|
||||||
|
#define DEFAULT_FOOTER \
|
||||||
|
"ob: EKAH 222120Z AUTO 20004KT 150V250 9999 FEW140/// 03/02 Q0991\n" \
|
||||||
|
"cycle: 21\n"
|
||||||
|
|
||||||
|
int main(int argc, char* argv[])
|
||||||
|
{
|
||||||
|
int overall = EXIT_SUCCESS;
|
||||||
|
|
||||||
|
PARSE_FAIL("empty string", "");
|
||||||
|
|
||||||
|
// Default contents parsing
|
||||||
|
{
|
||||||
|
int parse_ok = 1;
|
||||||
|
struct weather_info info = {0};
|
||||||
|
PARSE_SUCCEED("default values",
|
||||||
|
parse_ok, info,
|
||||||
|
DEFAULT_STATION
|
||||||
|
DEFAULT_TIME
|
||||||
|
DEFAULT_WIND
|
||||||
|
DEFAULT_VISIBILITY
|
||||||
|
DEFAULT_SKY_CONDITIONS
|
||||||
|
DEFAULT_WEATHER
|
||||||
|
DEFAULT_TEMPERATURE
|
||||||
|
DEFAULT_HEAT_INDEX
|
||||||
|
DEFAULT_DEW_POINT
|
||||||
|
DEFAULT_RELATIVE_HUMIDITY
|
||||||
|
DEFAULT_PRESSURE
|
||||||
|
DEFAULT_FOOTER);
|
||||||
|
if (parse_ok) {
|
||||||
|
CHECK_DEFAULT_STATION(info);
|
||||||
|
CHECK_DEFAULT_TIME(info);
|
||||||
|
CHECK_DEFAULT_WIND(info);
|
||||||
|
CHECK_DEFAULT_VISIBILITY(info);
|
||||||
|
CHECK_DEFAULT_WEATHER(info);
|
||||||
|
CHECK_DEFAULT_SKY_CONDITIONS(info);
|
||||||
|
CHECK_DEFAULT_TEMPERATURE(info);
|
||||||
|
CHECK_DEFAULT_HEAT_INDEX(info);
|
||||||
|
CHECK_DEFAULT_DEW_POINT(info);
|
||||||
|
CHECK_DEFAULT_RELATIVE_HUMIDITY(info);
|
||||||
|
CHECK_DEFAULT_PRESSURE(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown station parsing
|
||||||
|
{
|
||||||
|
int parse_ok = 1;
|
||||||
|
struct weather_info info = {0};
|
||||||
|
PARSE_SUCCEED("unknown station",
|
||||||
|
parse_ok, info,
|
||||||
|
"Station name not available\n"
|
||||||
|
DEFAULT_TIME
|
||||||
|
DEFAULT_WIND
|
||||||
|
DEFAULT_VISIBILITY
|
||||||
|
DEFAULT_SKY_CONDITIONS
|
||||||
|
DEFAULT_WEATHER
|
||||||
|
DEFAULT_TEMPERATURE
|
||||||
|
DEFAULT_HEAT_INDEX
|
||||||
|
DEFAULT_DEW_POINT
|
||||||
|
DEFAULT_RELATIVE_HUMIDITY
|
||||||
|
DEFAULT_PRESSURE
|
||||||
|
DEFAULT_FOOTER);
|
||||||
|
if (parse_ok) {
|
||||||
|
CHECK_STR(info, station_town, "?");
|
||||||
|
CHECK_STR(info, station_state, "?");
|
||||||
|
CHECK_DEFAULT_TIME(info);
|
||||||
|
CHECK_DEFAULT_WIND(info);
|
||||||
|
CHECK_DEFAULT_VISIBILITY(info);
|
||||||
|
CHECK_DEFAULT_WEATHER(info);
|
||||||
|
CHECK_DEFAULT_SKY_CONDITIONS(info);
|
||||||
|
CHECK_DEFAULT_TEMPERATURE(info);
|
||||||
|
CHECK_DEFAULT_HEAT_INDEX(info);
|
||||||
|
CHECK_DEFAULT_DEW_POINT(info);
|
||||||
|
CHECK_DEFAULT_RELATIVE_HUMIDITY(info);
|
||||||
|
CHECK_DEFAULT_PRESSURE(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Missing weather parsing.
|
||||||
|
{
|
||||||
|
int parse_ok = 1;
|
||||||
|
struct weather_info info = {0};
|
||||||
|
PARSE_SUCCEED("missing_weather",
|
||||||
|
parse_ok, info,
|
||||||
|
DEFAULT_STATION
|
||||||
|
DEFAULT_TIME
|
||||||
|
DEFAULT_WIND
|
||||||
|
DEFAULT_VISIBILITY
|
||||||
|
DEFAULT_SKY_CONDITIONS
|
||||||
|
DEFAULT_TEMPERATURE
|
||||||
|
DEFAULT_HEAT_INDEX
|
||||||
|
DEFAULT_DEW_POINT
|
||||||
|
DEFAULT_RELATIVE_HUMIDITY
|
||||||
|
DEFAULT_PRESSURE
|
||||||
|
DEFAULT_FOOTER);
|
||||||
|
if (parse_ok) {
|
||||||
|
CHECK_DEFAULT_STATION(info);
|
||||||
|
CHECK_DEFAULT_TIME(info);
|
||||||
|
CHECK_DEFAULT_WIND(info);
|
||||||
|
CHECK_DEFAULT_VISIBILITY(info);
|
||||||
|
CHECK_STR(info, weather, "<unknown>");
|
||||||
|
CHECK_DEFAULT_SKY_CONDITIONS(info);
|
||||||
|
CHECK_DEFAULT_TEMPERATURE(info);
|
||||||
|
CHECK_DEFAULT_HEAT_INDEX(info);
|
||||||
|
CHECK_DEFAULT_DEW_POINT(info);
|
||||||
|
CHECK_DEFAULT_RELATIVE_HUMIDITY(info);
|
||||||
|
CHECK_DEFAULT_PRESSURE(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calm wind parsing
|
||||||
|
{
|
||||||
|
int parse_ok = 1;
|
||||||
|
struct weather_info info = {0};
|
||||||
|
PARSE_SUCCEED("calm wind",
|
||||||
|
parse_ok, info,
|
||||||
|
DEFAULT_STATION
|
||||||
|
DEFAULT_TIME
|
||||||
|
"Wind: Calm:0\n"
|
||||||
|
DEFAULT_VISIBILITY
|
||||||
|
DEFAULT_SKY_CONDITIONS
|
||||||
|
DEFAULT_WEATHER
|
||||||
|
DEFAULT_TEMPERATURE
|
||||||
|
DEFAULT_HEAT_INDEX
|
||||||
|
DEFAULT_DEW_POINT
|
||||||
|
DEFAULT_RELATIVE_HUMIDITY
|
||||||
|
DEFAULT_PRESSURE
|
||||||
|
DEFAULT_FOOTER);
|
||||||
|
if (parse_ok) {
|
||||||
|
CHECK_DEFAULT_STATION(info);
|
||||||
|
CHECK_DEFAULT_TIME(info);
|
||||||
|
CHECK_STR(info, wind_direction, "μ");
|
||||||
|
CHECK_INT(info, wind_azimuth, -1);
|
||||||
|
CHECK_INT(info, wind_mph, 0);
|
||||||
|
CHECK_INT(info, wind_knots, 0);
|
||||||
|
CHECK_INT(info, wind_kmph, 0);
|
||||||
|
CHECK_INT(info, wind_mps, 0);
|
||||||
|
CHECK_DEFAULT_VISIBILITY(info);
|
||||||
|
CHECK_DEFAULT_WEATHER(info);
|
||||||
|
CHECK_DEFAULT_SKY_CONDITIONS(info);
|
||||||
|
CHECK_DEFAULT_TEMPERATURE(info);
|
||||||
|
CHECK_DEFAULT_HEAT_INDEX(info);
|
||||||
|
CHECK_DEFAULT_DEW_POINT(info);
|
||||||
|
CHECK_DEFAULT_RELATIVE_HUMIDITY(info);
|
||||||
|
CHECK_DEFAULT_PRESSURE(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variable wind parsing
|
||||||
|
{
|
||||||
|
int parse_ok = 1;
|
||||||
|
struct weather_info info = {0};
|
||||||
|
PARSE_SUCCEED("variable wind",
|
||||||
|
parse_ok, info,
|
||||||
|
DEFAULT_STATION
|
||||||
|
DEFAULT_TIME
|
||||||
|
"Wind: Variable at 5 MPH (4 KT)\n"
|
||||||
|
DEFAULT_VISIBILITY
|
||||||
|
DEFAULT_SKY_CONDITIONS
|
||||||
|
DEFAULT_WEATHER
|
||||||
|
DEFAULT_TEMPERATURE
|
||||||
|
DEFAULT_HEAT_INDEX
|
||||||
|
DEFAULT_DEW_POINT
|
||||||
|
DEFAULT_RELATIVE_HUMIDITY
|
||||||
|
DEFAULT_PRESSURE
|
||||||
|
DEFAULT_FOOTER);
|
||||||
|
if (parse_ok) {
|
||||||
|
CHECK_DEFAULT_STATION(info);
|
||||||
|
CHECK_DEFAULT_TIME(info);
|
||||||
|
CHECK_STR(info, wind_direction, "μ");
|
||||||
|
CHECK_INT(info, wind_azimuth, -1);
|
||||||
|
CHECK_INT(info, wind_mph, 5);
|
||||||
|
CHECK_INT(info, wind_knots, 4);
|
||||||
|
CHECK_INT(info, wind_kmph, 7);
|
||||||
|
CHECK_INT(info, wind_mps, 2);
|
||||||
|
CHECK_DEFAULT_VISIBILITY(info);
|
||||||
|
CHECK_DEFAULT_WEATHER(info);
|
||||||
|
CHECK_DEFAULT_SKY_CONDITIONS(info);
|
||||||
|
CHECK_DEFAULT_TEMPERATURE(info);
|
||||||
|
CHECK_DEFAULT_HEAT_INDEX(info);
|
||||||
|
CHECK_DEFAULT_DEW_POINT(info);
|
||||||
|
CHECK_DEFAULT_RELATIVE_HUMIDITY(info);
|
||||||
|
CHECK_DEFAULT_PRESSURE(info);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return overall;
|
||||||
|
}
|
408
modules/weather-parse.c
Normal file
408
modules/weather-parse.c
Normal file
|
@ -0,0 +1,408 @@
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <float.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "weather.h"
|
||||||
|
|
||||||
|
#define check_pos_impl(p, op, m) \
|
||||||
|
do { \
|
||||||
|
if ((p) op (m)) { \
|
||||||
|
fprintf(stderr, "failed to parse at %d\n", __LINE__); \
|
||||||
|
parse_fail = 1; \
|
||||||
|
} \
|
||||||
|
} while (0)
|
||||||
|
#define check_parse(end, pos) check_pos_impl(end, !=, pos)
|
||||||
|
|
||||||
|
static int
|
||||||
|
parse_line(struct weather_info *info, const char* line, size_t len)
|
||||||
|
{
|
||||||
|
const char wind_prefix[] = "Wind: ";
|
||||||
|
const char visibility_prefix[] = "Visibility: ";
|
||||||
|
const char sky_conditions_prefix[] = "Sky conditions: ";
|
||||||
|
const char weather_prefix[] = "Weather: ";
|
||||||
|
const char temperature_prefix[] = "Temperature: ";
|
||||||
|
const char heat_index_prefix[] = "Heat index: ";
|
||||||
|
const char dew_point_prefix[] = "Dew Point: ";
|
||||||
|
const char relative_humidity_prefix[] = "Relative Humidity: ";
|
||||||
|
const char pressure_prefix[] = "Pressure (altimeter): ";
|
||||||
|
|
||||||
|
const char* lend = line + len;
|
||||||
|
int parse_fail = 0;
|
||||||
|
|
||||||
|
#define has_prefix(l, s, p) ((sizeof(p) - 1) <= s && !strncmp(l, p, sizeof(p) - 1))
|
||||||
|
|
||||||
|
// Wind: ...
|
||||||
|
if (has_prefix(line, len, wind_prefix)) {
|
||||||
|
line += sizeof(wind_prefix) - 1;
|
||||||
|
len -= sizeof(wind_prefix) - 1;
|
||||||
|
|
||||||
|
const char calm_indicator[] = "Calm:0";
|
||||||
|
const char variable_indicator[] = "Variable at ";
|
||||||
|
const char normal_indicator[] = "from the ";
|
||||||
|
|
||||||
|
// Wind: Calm:0
|
||||||
|
if (has_prefix(line, len, calm_indicator)) {
|
||||||
|
info->wind_direction = strdup("μ");
|
||||||
|
info->wind_azimuth = -1;
|
||||||
|
info->wind_mph = 0;
|
||||||
|
info->wind_knots = 0;
|
||||||
|
info->wind_kmph = 0;
|
||||||
|
info->wind_mps = 0;
|
||||||
|
// Wind: Variable at N MPH (N KT)
|
||||||
|
} else if (has_prefix(line, len, variable_indicator)) {
|
||||||
|
line += sizeof(variable_indicator) - 1;
|
||||||
|
len -= sizeof(variable_indicator) - 1;
|
||||||
|
|
||||||
|
info->wind_direction = strdup("μ");
|
||||||
|
info->wind_azimuth = -1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, " ");
|
||||||
|
char* end = NULL;
|
||||||
|
long long int_parse = strtoll(line, &end, 10);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
info->wind_mph = int_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "(");
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
int_parse = strtoll(line, &end, 10);
|
||||||
|
line = end;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
info->wind_knots = int_parse;
|
||||||
|
info->wind_kmph = int_parse * 1.852;
|
||||||
|
info->wind_mps = int_parse * 0.514;
|
||||||
|
// Wind: from the DIR (AZI degrees) at N MPH (N KT) ...
|
||||||
|
} else if (has_prefix(line, len, normal_indicator)) {
|
||||||
|
line += sizeof(normal_indicator) - 1;
|
||||||
|
len -= sizeof(normal_indicator) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, " ");
|
||||||
|
check_pos_impl(line + spn + 2, >=, lend);
|
||||||
|
info->wind_direction = strndup(line, spn);
|
||||||
|
|
||||||
|
line += spn + 2;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
char* end = NULL;
|
||||||
|
long long int_parse = strtoll(line, &end, 10);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->wind_azimuth = int_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "t");
|
||||||
|
line += spn + 2;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
int_parse = strtoll(line, &end, 10);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->wind_mph = int_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "(");
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
int_parse = strtoll(line, &end, 10);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->wind_knots = int_parse;
|
||||||
|
info->wind_kmph = int_parse * 1.852;
|
||||||
|
info->wind_mps = int_parse * 0.514;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "Unknown 'Wind' format: '%.*s'\n", (int)len, line);
|
||||||
|
parse_fail = 1;
|
||||||
|
}
|
||||||
|
// Visibility: VIS
|
||||||
|
} else if (has_prefix(line, len, visibility_prefix)) {
|
||||||
|
line += sizeof(visibility_prefix) - 1;
|
||||||
|
len -= sizeof(visibility_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, ":");
|
||||||
|
check_pos_impl(line + spn, >=, lend);
|
||||||
|
|
||||||
|
free(info->visibility);
|
||||||
|
info->visibility = strndup(line, spn);
|
||||||
|
// Sky conditions: SKY
|
||||||
|
} else if (has_prefix(line, len, sky_conditions_prefix)) {
|
||||||
|
line += sizeof(sky_conditions_prefix) - 1;
|
||||||
|
len -= sizeof(sky_conditions_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, "\n");
|
||||||
|
check_pos_impl(line + spn, >, lend);
|
||||||
|
|
||||||
|
free(info->sky_condition);
|
||||||
|
info->sky_condition = strndup(line, spn);
|
||||||
|
// Weather: WEATHER
|
||||||
|
} else if (has_prefix(line, len, weather_prefix)) {
|
||||||
|
line += sizeof(weather_prefix) - 1;
|
||||||
|
len -= sizeof(weather_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, "\n");
|
||||||
|
check_pos_impl(line + spn, >, lend);
|
||||||
|
|
||||||
|
free(info->weather);
|
||||||
|
info->weather = strndup(line, spn);
|
||||||
|
// Temperature: T F (T C)
|
||||||
|
} else if (has_prefix(line, len, temperature_prefix)) {
|
||||||
|
line += sizeof(temperature_prefix) - 1;
|
||||||
|
len -= sizeof(temperature_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, " ");
|
||||||
|
char* end = NULL;
|
||||||
|
double double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
info->temp_f = double_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "(");
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->temp_c = double_parse;
|
||||||
|
// Heat index: T F (T C):N
|
||||||
|
} else if (has_prefix(line, len, heat_index_prefix)) {
|
||||||
|
line += sizeof(heat_index_prefix) - 1;
|
||||||
|
len -= sizeof(heat_index_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, " ");
|
||||||
|
char* end = NULL;
|
||||||
|
double double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
info->heat_index_f = double_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "(");
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->heat_index_c = double_parse;
|
||||||
|
// Dew Point: T F (T C)
|
||||||
|
} else if (has_prefix(line, len, dew_point_prefix)) {
|
||||||
|
line += sizeof(dew_point_prefix) - 1;
|
||||||
|
len -= sizeof(dew_point_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, " ");
|
||||||
|
char* end = NULL;
|
||||||
|
double double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
info->dew_point_f = double_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "(");
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->dew_point_c = double_parse;
|
||||||
|
// Relative Humidity: H%
|
||||||
|
} else if (has_prefix(line, len, relative_humidity_prefix)) {
|
||||||
|
line += sizeof(relative_humidity_prefix) - 1;
|
||||||
|
len -= sizeof(relative_humidity_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, "%");
|
||||||
|
char* end = NULL;
|
||||||
|
long long int_parse = strtoll(line, &end, 10);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >, lend);
|
||||||
|
|
||||||
|
info->humidity = int_parse;
|
||||||
|
// Pressure (altimeter): X.Y in. Hg (N hPa)
|
||||||
|
} else if (has_prefix(line, len, pressure_prefix)) {
|
||||||
|
line += sizeof(pressure_prefix) - 1;
|
||||||
|
len -= sizeof(pressure_prefix) - 1;
|
||||||
|
|
||||||
|
size_t spn = strcspn(line, " ");
|
||||||
|
char* end = NULL;
|
||||||
|
double double_parse = strtod(line, &end);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >, lend);
|
||||||
|
|
||||||
|
info->pressure_mmhg = double_parse;
|
||||||
|
|
||||||
|
spn = strcspn(line, "(");
|
||||||
|
line += spn + 1;
|
||||||
|
check_pos_impl(line, >=, lend);
|
||||||
|
|
||||||
|
spn = strcspn(line, " ");
|
||||||
|
long long int_parse = strtoll(line, &end, 10);
|
||||||
|
check_parse(end, line + spn);
|
||||||
|
|
||||||
|
info->pressure_hpa = int_parse;
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
parse_weather_info(const struct weather_curl_buffer *buf, struct weather_info *info)
|
||||||
|
{
|
||||||
|
// Extract station information.
|
||||||
|
const char* pos = buf->buffer;
|
||||||
|
int parse_fail = 0;
|
||||||
|
#define check_pos() check_pos_impl(pos, >, buf->buffer + buf->size)
|
||||||
|
|
||||||
|
// Fill in invalid data.
|
||||||
|
free(info->station_town);
|
||||||
|
info->station_town = strdup("<unknown>");
|
||||||
|
free(info->station_state);
|
||||||
|
info->station_state = strdup("<unknown>");
|
||||||
|
info->year = 0;
|
||||||
|
info->month = 0;
|
||||||
|
info->day = 0;
|
||||||
|
info->hour = -1;
|
||||||
|
info->minute = -1;
|
||||||
|
free(info->wind_direction);
|
||||||
|
info->wind_direction = strdup("<unknown>");
|
||||||
|
info->wind_azimuth = -1;
|
||||||
|
info->wind_mph = -1;
|
||||||
|
info->wind_knots = -1;
|
||||||
|
info->wind_kmph = -1;
|
||||||
|
info->wind_mps = -1;
|
||||||
|
free(info->visibility);
|
||||||
|
info->visibility = strdup("<unknown>");
|
||||||
|
free(info->sky_condition);
|
||||||
|
info->sky_condition = strdup("<unknown>");
|
||||||
|
free(info->weather);
|
||||||
|
info->weather = strdup("<unknown>");
|
||||||
|
info->temp_c = -1000;
|
||||||
|
info->temp_f = -1000;
|
||||||
|
info->heat_index_c = -1000;
|
||||||
|
info->heat_index_f = -1000;
|
||||||
|
info->dew_point_c = -1000;
|
||||||
|
info->dew_point_f = -1000;
|
||||||
|
info->humidity = -1;
|
||||||
|
info->pressure_mmhg = -1;
|
||||||
|
info->pressure_hpa = -1;
|
||||||
|
|
||||||
|
// TOWN, STATE (...
|
||||||
|
if (!parse_fail) {
|
||||||
|
size_t spn;
|
||||||
|
|
||||||
|
const char *unknown_station = "Station name not available";
|
||||||
|
|
||||||
|
if (!strncmp(pos, unknown_station, sizeof(unknown_station) - 1)) {
|
||||||
|
free(info->station_town);
|
||||||
|
info->station_town = strdup("?");
|
||||||
|
free(info->station_state);
|
||||||
|
info->station_state = strdup("?");
|
||||||
|
} else {
|
||||||
|
spn = strcspn(pos, ",");
|
||||||
|
free(info->station_town);
|
||||||
|
info->station_town = strndup(pos, spn);
|
||||||
|
pos += spn + 1;
|
||||||
|
check_pos();
|
||||||
|
|
||||||
|
if (!parse_fail) {
|
||||||
|
spn = strcspn(pos, "(");
|
||||||
|
// Trim trailing space
|
||||||
|
while (isspace(pos[spn - 1])) {
|
||||||
|
--spn;
|
||||||
|
}
|
||||||
|
// Skip leading space
|
||||||
|
while (isspace(*pos)) {
|
||||||
|
++pos;
|
||||||
|
--spn;
|
||||||
|
}
|
||||||
|
free(info->station_state);
|
||||||
|
info->station_state = strndup(pos, spn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the end of the line.
|
||||||
|
spn = strcspn(pos, "\n");
|
||||||
|
pos += spn + 1;
|
||||||
|
check_pos();
|
||||||
|
}
|
||||||
|
// Dec 22, 2024 - 04:20 PM EST / 2024.12.22 2120 UTC
|
||||||
|
if (!parse_fail) {
|
||||||
|
size_t spn;
|
||||||
|
const char *lpos = pos;
|
||||||
|
char *end;
|
||||||
|
long int_parse;
|
||||||
|
|
||||||
|
// Work on the entire line.
|
||||||
|
spn = strcspn(pos, "\n");
|
||||||
|
pos += spn + 1;
|
||||||
|
check_pos();
|
||||||
|
|
||||||
|
spn = strcspn(lpos, "/");
|
||||||
|
lpos += spn + 2;
|
||||||
|
check_pos_impl(lpos, >=, pos);
|
||||||
|
|
||||||
|
int_parse = strtoll(lpos, &end, 10);
|
||||||
|
check_parse(end, lpos + 4);
|
||||||
|
lpos = end + 1;
|
||||||
|
check_pos_impl(lpos, >=, pos);
|
||||||
|
|
||||||
|
info->year = int_parse;
|
||||||
|
|
||||||
|
int_parse = strtoll(lpos, &end, 10);
|
||||||
|
check_parse(end, lpos + 2);
|
||||||
|
lpos = end + 1;
|
||||||
|
check_pos_impl(lpos, >=, pos);
|
||||||
|
|
||||||
|
info->month = int_parse;
|
||||||
|
|
||||||
|
int_parse = strtoll(lpos, &end, 10);
|
||||||
|
check_parse(end, lpos + 2);
|
||||||
|
lpos = end + 1;
|
||||||
|
check_pos_impl(lpos, >=, pos);
|
||||||
|
|
||||||
|
info->day = int_parse;
|
||||||
|
|
||||||
|
info->hour = (*lpos - '0') * 10 + lpos[1] - '0';
|
||||||
|
lpos += 2;
|
||||||
|
check_pos_impl(lpos, >=, pos);
|
||||||
|
info->minute = (*lpos - '0') * 10 + lpos[1] - '0';
|
||||||
|
}
|
||||||
|
|
||||||
|
while (*pos && pos < buf->buffer + buf->size) {
|
||||||
|
size_t spn;
|
||||||
|
const char *lpos = pos;
|
||||||
|
|
||||||
|
// Work on the entire line.
|
||||||
|
spn = strcspn(pos, "\n");
|
||||||
|
pos += spn;
|
||||||
|
// Handle contents without newlines at the end of the file.
|
||||||
|
if (*pos == '\n') {
|
||||||
|
++pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parse_line(info, lpos, spn)) {
|
||||||
|
fprintf(stderr, "failed to parse line '%.*s'\n", (int)spn, lpos);
|
||||||
|
parse_fail = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parse_fail;
|
||||||
|
}
|
243
modules/weather.c
Normal file
243
modules/weather.c
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
#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
|
41
modules/weather.h
Normal file
41
modules/weather.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
#pragma once
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
struct weather_info
|
||||||
|
{
|
||||||
|
char* station_town;
|
||||||
|
char* station_state;
|
||||||
|
int year;
|
||||||
|
int month;
|
||||||
|
int day;
|
||||||
|
int hour;
|
||||||
|
int minute;
|
||||||
|
char* wind_direction;
|
||||||
|
int wind_azimuth;
|
||||||
|
int wind_mph;
|
||||||
|
int wind_knots;
|
||||||
|
int wind_kmph;
|
||||||
|
int wind_mps;
|
||||||
|
char* visibility;
|
||||||
|
char* sky_condition;
|
||||||
|
char* weather;
|
||||||
|
double temp_c;
|
||||||
|
double temp_f;
|
||||||
|
double heat_index_c;
|
||||||
|
double heat_index_f;
|
||||||
|
double dew_point_c;
|
||||||
|
double dew_point_f;
|
||||||
|
int humidity;
|
||||||
|
double pressure_mmhg;
|
||||||
|
int pressure_hpa;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct weather_curl_buffer
|
||||||
|
{
|
||||||
|
char *buffer;
|
||||||
|
size_t size;
|
||||||
|
size_t capacity;
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
parse_weather_info(const struct weather_curl_buffer *buf, struct weather_info *info);
|
6
plugin.c
6
plugin.c
|
@ -93,6 +93,9 @@ EXTERN_MODULE(niri_language);
|
||||||
#if defined(HAVE_PLUGIN_niri_workspaces)
|
#if defined(HAVE_PLUGIN_niri_workspaces)
|
||||||
EXTERN_MODULE(niri_workspaces);
|
EXTERN_MODULE(niri_workspaces);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(HAVE_PLUGIN_weather)
|
||||||
|
EXTERN_MODULE(weather);
|
||||||
|
#endif
|
||||||
#if defined(HAVE_PLUGIN_xkb)
|
#if defined(HAVE_PLUGIN_xkb)
|
||||||
EXTERN_MODULE(xkb);
|
EXTERN_MODULE(xkb);
|
||||||
#endif
|
#endif
|
||||||
|
@ -232,6 +235,9 @@ static void __attribute__((constructor)) init(void)
|
||||||
#if defined(HAVE_PLUGIN_niri_workspaces)
|
#if defined(HAVE_PLUGIN_niri_workspaces)
|
||||||
REGISTER_CORE_MODULE(niri-workspaces, niri_workspaces);
|
REGISTER_CORE_MODULE(niri-workspaces, niri_workspaces);
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(HAVE_PLUGIN_weather)
|
||||||
|
REGISTER_CORE_MODULE(weather, weather);
|
||||||
|
#endif
|
||||||
#if defined(HAVE_PLUGIN_xkb)
|
#if defined(HAVE_PLUGIN_xkb)
|
||||||
REGISTER_CORE_MODULE(xkb, xkb);
|
REGISTER_CORE_MODULE(xkb, xkb);
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Add table
Reference in a new issue