The goal of netatmo.weather is to provide access to Netatmo measurements and station metadata making use of the Netatmo Weather API.
You can install the development version of netatmo.weather with:
# install.packages("devtools")
devtools::install_github("dimfalk/netatmo.weather")
and load the package via
library(netatmo.weather)
#> 0.5.27
In order to be able to access data, please follow the subsequent steps:
-
Register a user account at auth.netatmo.com and login at dev.netatmo.com.
-
Click on your username in the upper right corner and create a new application. Provide mandatory information (*) and save.
-
Credentials will be stored making use of
{keyring}
. In order to securely encrypt your secrets stored, it is necessary to define a vault password in your user-level.Renviron
, which can be edited viafile.edit("~/.Renviron")
or by runningusethis::edit_r_environ()
. Create a new environment variable calledKEYRING_PASSWORD
.
The new line added now looks something like
KEYRING_PASSWORD = "<insert_your_strong_password_here_123!>"
. You can
also create a password using e.g. {sodium}
with
keygen() |> bin2hex()
. Restart R to see changes. Eventually inspect
the key (privately) via Sys.getenv("KEYRING_PASSWORD")
.
- Run
set_credentials()
and copy & paste the information necessary (id/secret), as supplied in 2) .
set_credentials()
#> Note: Keyring 'netatmo' successfully created.
- Run
fetch_token()
to cache Oauth 2.0 access credentials based on the specifications provided in 4). When asked, whether you want to use a local file to cache OAuth access credentials between R sessions, choose “1: Yes”. You’ll be redirected to your browser to grant access to your application. Accept and close the browser tab.
fetch_token()
#> Note: OAuth 2.0 access credentials successfully cached in file `.httr-oauth`.
Successful authentication is confirmed in the browser. Your token is now
stored to disk as .httr-oauth
in your working directory.
In case you wanted to execute /getpublicdata and /getmeasure API
calls from your browser (for debugging reasons or whatever), you’ll need
to append your access token to your URL: "&access_token=xxx"
. You’ll
also be notified if you try to execute requests with your access token
missing.
You can access your tokens consisting of a key and secret making use of little helpers provided:
print_at()
#> "&access_token=62361e03ca18e13802546z20|5dt2091f1693dbff35f0428f2386b492"
print_rt()
#> "&refresh_token=62361e03ca18e13802546z20|6ce2fb2490a615d58b16e874fz4eb579"
Tokens expire after 3 hours and have to be refreshed again in order to
be used. {netatmo.weather}
does so in the background automatically
without the user noticing. But if you’re using your access token in a
browser session temporarily, make use of these little helpers provided:
is_expired()
#> TRUE
refresh_at()
#> <Token>
#> <oauth_endpoint>
#> authorize: https://api.netatmo.net/oauth2/authorize
#> access: https://api.netatmo.net/oauth2/token
#> <oauth_app> netatmo.weather
#> key: 62361e03ca18e13802546z20
#> secret: <hidden>
#> <credentials> scope, access_token, expires_in, expire_in, refresh_token
is_expired()
#> FALSE
Queries via get_publicdata()
to obtain station locations and metadata
require a user-defined bounding box as the main function argument. In
order to facilitate this, get_extent()
was implemented to help you
out:
# using coordinates (xmin, ymin, xmax, ymax)
e1 <- get_extent(c(6.89, 51.34, 7.13, 51.53), crs = "epsg:4326")
e1
#> Geometry set for 1 feature
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 6.89 ymin: 51.34 xmax: 7.13 ymax: 51.53
#> Geodetic CRS: WGS 84
#> POLYGON ((6.89 51.34, 7.13 51.34, 7.13 51.53, 6...
# using municipality names
e2 <- get_extent("Essen")
e2
#> Geometry set for 1 feature
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 6.891972 ymin: 51.34647 xmax: 7.139793 ymax: 51.53627
#> Geodetic CRS: WGS 84
#> POLYGON ((6.891972 51.34647, 7.139793 51.34647,...
# using postal codes
e3 <- get_extent("45145")
e3
#> Geometry set for 1 feature
#> Geometry type: POLYGON
#> Dimension: XY
#> Bounding box: xmin: 6.952605 ymin: 51.44062 xmax: 7.001576 ymax: 51.45272
#> Geodetic CRS: WGS 84
#> POLYGON ((6.952605 51.44062, 7.001576 51.44062,...
This information can now be used to list stations located in this area (at the time of the query):
stations <- get_publicdata(ext = e1)
#> /getpublicdata: Fetching stations from the following area: 6.89, 51.34, 7.13, 51.53 ...
stations
#> Simple feature collection with 306 features and 13 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: 6.890067 ymin: 51.34127 xmax: 7.129903 ymax: 51.52755
#> Geodetic CRS: WGS 84
#> # A tibble: 306 × 14
#> status time_server NAMain timezone country altitude city street
#> * <chr> <dttm> <chr> <chr> <chr> <int> <chr> <chr>
#> 1 ok 2024-04-17 21:53:39 70:ee:50:0… Europe/… DE 111 Essen A 52
#> 2 ok 2024-04-17 21:53:39 70:ee:50:6… Europe/… DE 112 Essen Steel…
#> 3 ok 2024-04-17 21:53:39 70:ee:50:0… Europe/… DE 114 Essen Steel…
#> 4 ok 2024-04-17 21:53:39 70:ee:50:a… Europe/… DE 108 Essen Laure…
#> 5 ok 2024-04-17 21:53:39 70:ee:50:a… Europe/… DE 108 Essen Eligi…
#> 6 ok 2024-04-17 21:53:39 70:ee:50:0… Europe/… DE 60 Essen Hengl…
#> 7 ok 2024-04-17 21:53:39 70:ee:50:a… Europe/… DE 69 Essen Notte…
#> 8 ok 2024-04-17 21:53:39 70:ee:50:0… Europe/… DE 69 Essen Notte…
#> 9 ok 2024-04-17 21:53:39 70:ee:50:3… Europe/… DE 77 Essen Lohmü…
#> 10 ok 2024-04-17 21:53:39 70:ee:50:a… Europe/… DE 101 Essen Bochu…
#> # ℹ 296 more rows
#> # ℹ 6 more variables: mark <int>, n_modules <int>, NAModule1 <chr>,
#> # NAModule2 <chr>, NAModule3 <chr>, geometry <POINT [°]>
However, since the number of stations returned by /getpublicdata
seems to be influenced by the size of the area queried, the logical
argument tiles
was implemented, slicing your area of interest in tiles
à 0.05 degrees per default to be queried separately in order to ensure
the maximum number of available stations.
stations_tiles <- get_publicdata(ext = e1, tiles = TRUE)
#> /getpublicdata: Fetching stations from the following area: 6.89, 51.34, 7.13, 51.53 ...
stations_tiles
#> Simple feature collection with 627 features and 13 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: 6.890067 ymin: 51.34127 xmax: 7.129903 ymax: 51.52922
#> Geodetic CRS: WGS 84
#> # A tibble: 627 × 14
#> status time_server NAMain timezone country altitude city street
#> <chr> <dttm> <chr> <chr> <chr> <int> <chr> <chr>
#> 1 ok 2024-04-17 21:54:41 70:ee:50:8… Europe/… DE 45 Essen Volck…
#> 2 ok 2024-04-17 21:54:41 70:ee:50:7… Europe/… DE 46 Essen Eva-H…
#> 3 ok 2024-04-17 21:54:41 70:ee:50:a… Europe/… DE 63 Essen Am Mö…
#> 4 ok 2024-04-17 21:54:41 70:ee:50:a… Europe/… DE 93 Essen Senge…
#> 5 ok 2024-04-17 21:54:41 70:ee:50:1… Europe/… DE 48 Essen Johan…
#> 6 ok 2024-04-17 21:54:41 70:ee:50:a… Europe/… DE 57 Essen Marti…
#> 7 ok 2024-04-17 21:54:41 70:ee:50:1… Europe/… DE 81 Essen Akade…
#> 8 ok 2024-04-17 21:54:41 70:ee:50:7… Europe/… DE 61 Essen Haupt…
#> 9 ok 2024-04-17 21:54:41 70:ee:50:7… Europe/… DE 61 Essen Haupt…
#> 10 ok 2024-04-17 21:54:41 70:ee:50:7… Europe/… DE 66 Essen Eiche…
#> # ℹ 617 more rows
#> # ℹ 6 more variables: mark <int>, n_modules <int>, NAModule1 <chr>,
#> # NAModule2 <chr>, NAModule3 <chr>, geometry <POINT [°]>
Queries via get_measure()
to obtain station observations basically
require a base station MAC address to be queried (included in stations
returned by get_publicdata()
), the parameter to be queried
(e.g. temperature
, humudity
, sum_rain
, ...
), the measurement
interval in minutes (e.g. 5
, 30
, 60
) and a period encompassing the
timestamp of the first and last observation to retrieve in form of UNIX
time (seconds since 1970-01-01 00:00 UTC).
To assist you with the latter going backwards from
lubridate::now("UTC")
, get_period()
exists:
# per default (with x = NULL) returning the maximum number of observations (1024) as a function of `res` chosen
# 5-minutely data
p1 <- get_period(res = 5)
as.POSIXct(p1, tz = "UTC")
#> [1] "2024-04-14 07:40:00 UTC" "2024-04-17 21:00:00 UTC"
# hourly data
p2 <- get_period(res = 60)
as.POSIXct(p2, tz = "UTC")
#> [1] "2024-03-06 05:00:00 UTC" "2024-04-17 21:00:00 UTC"
# querying the last 24 hours, maybe convenient for scheduled jobs
p3 <- get_period("recent")
as.POSIXct(p3, tz = "UTC")
#> [1] "2024-04-16 21:00:00 UTC" "2024-04-17 21:00:00 UTC"
# self-defined periods
p4 <- get_period("2024-03-01/2024-04-01")
as.POSIXct(p4, tz = "UTC")
#> [1] "2024-03-01 UTC" "2024-04-01 UTC"
p5 <- get_period("2024-03-01 18:00/2024-03-15 18:00")
as.POSIXct(p5, tz = "UTC")
#> [1] "2024-03-01 18:00:00 UTC" "2024-03-15 18:00:00 UTC"
Just to ensure you do not run into HTTP 429 because of API guideline violations, you can use another little helper in advance to make sure the number of your requests fired does not exceed 500 at one time.
# 10 stations, hourly data
get_n_queries(d = 10, res = 60, p = p4)
#> [1] 10
# 20 stations, 5-minutely data
get_n_queries(d = 20, res = 5, p = p5)
#> [1] 80
This last piece of information can now be used to finally acquire
observations (iterating over previously identified stations) in form of
listed xts
objects. This might take some time to finish.
# get subset of data for demonstration purposes
obs <- get_measure(devices = stations_tiles[1:10, ],
period = p2,
par = "temperature",
res = 60)
#> /getmeasure: Fetching temperature measurements (60 min) from 2024-02-26 07:00 UTC to 2024-04-08 23:00 UTC for 10 station(s) ...
class(obs)
#> [1] "list"
length(obs)
#> [1] 10
names(obs)
#> [1] "70:ee:50:84:48:5a" "70:ee:50:7a:81:24" "70:ee:50:af:62:5e"
#> [4] "70:ee:50:af:86:16" "70:ee:50:12:f8:62" "70:ee:50:a4:68:a2"
#> [7] "70:ee:50:16:0e:06" "70:ee:50:7f:f6:c8" "70:ee:50:7a:94:9a"
#> [10] "70:ee:50:7b:12:86"
# subset to individual xts object
xts <- obs[[1]]
class(xts)
#> [1] "xts" "zoo"
# inspect index range
zoo::index(xts) |> range()
#> [1] "2024-03-06 05:00:00 UTC" "2024-04-17 20:00:00 UTC"
# inspect index/coredata
head(xts)
#> Warning: object timezone ('UTC') is different from system timezone ('')
#> NOTE: set 'options(xts_check_TZ = FALSE)' to disable this warning
#> This note is displayed once per session
#> temperature
#> 2024-03-06 05:00:00 2.6
#> 2024-03-06 06:00:00 2.9
#> 2024-03-06 07:00:00 3.9
#> 2024-03-06 08:00:00 6.1
#> 2024-03-06 09:00:00 8.3
#> 2024-03-06 10:00:00 10.0
# inspect attribute names appended
attributes(xts) |> names() |> tail(-4)
#> [1] "STAT_ID" "X" "Y"
#> [4] "Z" "CRS_EPSG" "TZONE"
#> [7] "OPERATOR" "SENS_ID" "PARAMETER"
#> [10] "TS_START" "TS_END" "TS_TYPE"
#> [13] "MEAS_INTERVALTYPE" "MEAS_BLOCKING" "MEAS_RESOLUTION"
#> [16] "MEAS_UNIT" "MEAS_STATEMENT" "CREATED_WITH"
#> [19] "CREATED_AT" "REMARKS"
plot(xts, main = "hourly air temperatures", col = "red")