-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Ok, so I've never written a line of R before. This kinda sorta works for really simple use cases. For more advanced stuff (like aggregates) I need to fix some things. There also appears to be a randomly occurring bug parsing timestamps when the response data is empty.
- Loading branch information
Vince Forgione
committed
Oct 23, 2018
0 parents
commit 48bccca
Showing
18 changed files
with
653 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
^.*\.Rproj$ | ||
^\.Rproj\.user$ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.Rproj.user | ||
.Rhistory | ||
.RData | ||
.Ruserdata |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
Version: 1.0 | ||
|
||
RestoreWorkspace: Default | ||
SaveWorkspace: Default | ||
AlwaysSaveHistory: Default | ||
|
||
EnableCodeIndexing: Yes | ||
UseSpacesForTab: Yes | ||
NumSpacesForTab: 2 | ||
Encoding: UTF-8 | ||
|
||
RnwWeave: Sweave | ||
LaTeX: pdfLaTeX | ||
|
||
AutoAppendNewline: Yes | ||
StripTrailingWhitespace: Yes | ||
|
||
BuildType: Package | ||
PackageUseDevtools: Yes | ||
PackageInstallArgs: --no-multiarch --with-keep.source |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Package: AotClient | ||
Type: Package | ||
Title: Office Array of Things API Client | ||
Version: 0.1.0 | ||
Author: Vince Forgione | ||
Maintainer: "Vince Forgione" <[email protected]> | ||
Description: HTTP API Client | ||
License: Apache 2 | ||
Encoding: UTF-8 | ||
LazyData: true | ||
Suggests: | ||
testthat | ||
RoxygenNote: 6.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
Copyright 2018 University of Chicago | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# Generated by roxygen2: do not edit by hand | ||
|
||
export(ls.nodes) | ||
export(ls.observations) | ||
export(ls.projects) | ||
export(ls.raw_observations) | ||
export(ls.sensors) | ||
export(stat.node) | ||
export(stat.project) | ||
export(stat.sensor) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,276 @@ | ||
|
||
#' Timestamped message -- primarily used to push error output to user | ||
#' | ||
#' @param msg - The message to be logged | ||
#' @return None (invisible NULL) as per cat | ||
#' @noRd | ||
log_msg <- function (msg) { | ||
cat(format(Sys.time(), "%Y-%m-%d %H:%M:%OS3 "), ": ", msg, "\n", sep="") | ||
} | ||
|
||
|
||
#' Sends a request to the API, ensures 200 response and returns the response | ||
#' | ||
#' Given a URL and optional filters/query params, this sends an HTTP GET request | ||
#' to the URL. The response"s status is checked -- if it isn"t 200 then an | ||
#' error message is logged and the process halts; it it"s 200 then the entire | ||
#' response object is returned. | ||
#' | ||
#' @param url - The URL to send the request to | ||
#' @param filters - A list of tuples to build filters/query params | ||
#' @return The entire response | ||
#' @noRd | ||
send_request <- function (url, filters = NULL) { | ||
# send request; get response | ||
if (!is.null(filters)) { | ||
resp <- httr::GET(url, query=filters) | ||
} else { | ||
resp <- httr::GET(url) | ||
} | ||
|
||
# if not 200, log error | ||
if (resp$status_code != 200) { | ||
msg <- paste("Error in httr GET:", rep$status_code, rep$headers$statusmessage, url) | ||
if(!is.null(rep$headers$`content-length`) && (rep$headers$`content-length` > 0)) { | ||
details <- httr::content(rep) | ||
msg <- paste(msg, details) | ||
} | ||
log_msg(msg) | ||
} | ||
|
||
# stop or return | ||
httr::stop_for_status(resp) | ||
return(resp) | ||
} | ||
|
||
|
||
#' Parses a response object as JSON and returns the `data` object | ||
#' | ||
#' @param resp - The response object | ||
#' @return The parsed JSON body | ||
#' @noRd | ||
parse_content <- function (resp) { | ||
content <- httr::content(resp, as="text") | ||
json <- jsonlite::fromJSON(content) | ||
data <- json$data | ||
return(data) | ||
} | ||
|
||
|
||
#' Sends a request and parses the result as a single map object | ||
#' | ||
#' Given a URL and optional filters, a request is sent and the response | ||
#' is processed as a single map object -- the response content has a | ||
#' `data` key that maps an object representing details for the metadata | ||
#' record requested. | ||
#' | ||
#' @param url - The URL to send the request to | ||
#' @param filters - A list of tuples to build query params | ||
#' @return The metadata details | ||
#' @noRd | ||
stat <- function (url, filters) { | ||
resp <- send_request(url, filters) | ||
details <- parse_content(resp) | ||
return(details) | ||
} | ||
|
||
|
||
#' Gets a data frame of `project` metadata | ||
#' | ||
#' Projects are the highest entity in the hierarchy of the Array of | ||
#' Things system. They are generally ambiguous geographic regions used | ||
#' to roughly group nodes. | ||
#' | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A data frame of project metadata | ||
#' @export | ||
ls.projects <- function (filters = NULL) { | ||
# build url, send request, get response | ||
url <- "https://api.arrayofthings.org/api/projects" | ||
resp <- send_request(url, filters) | ||
|
||
# build data frame | ||
data <- parse_content(resp) | ||
df <- as.data.frame.list(data) | ||
attr(df, "name") <- data$name | ||
attr(df, "slug") <- data$slug | ||
attr(df, "first_observation") <- as.POSIXlt(data$first_observation) | ||
attr(df, "latest_observation") <- as.POSIXlt(data$latest_observation) | ||
attr(df, "hull") <- data$hull | ||
|
||
# return data frame | ||
return(df) | ||
} | ||
|
||
|
||
#' Gets the details for a single `project` metadata record | ||
#' | ||
#' Projects are the highest entity in the hierarchy of the Array of | ||
#' Things system. They are generally ambiguous geographic regions used | ||
#' to roughly group nodes. | ||
#' | ||
#' @param slug - The project"s unique identifier | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A list representing the project | ||
#' @export | ||
stat.project <- function (slug, filters = NULL) { | ||
# build url, send request, get response | ||
url <- paste("https://api.arrayofthings.org/api/projects/", slug, sep="") | ||
details <- stat(url, filters) | ||
return(details) | ||
} | ||
|
||
|
||
#' Gets a data frame of `node` metadata | ||
#' | ||
#' Nodes are the physical devices deployed to collect observations. | ||
#' The are comprised of multiple sensors and are grouped by | ||
#' projects. | ||
#' | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A data frame of node metadata | ||
#' @export | ||
ls.nodes <- function (filters = NULL) { | ||
# build url, send request, get response | ||
url <- "https://api.arrayofthings.org/api/nodes" | ||
resp <- send_request(url, filters) | ||
|
||
# build data frame | ||
data <- parse_content(resp) | ||
df <- as.data.frame.list(data) | ||
attr(df, "vsn") <- data$vsn | ||
attr(df, "location") <- data$location | ||
attr(df, "human_address") <- data$human_address | ||
attr(df, "description") <- data$description | ||
attr(df, "commissioned_on") <- as.POSIXlt(data$commissioned_on) | ||
attr(df, "decommissioned_on") <- as.POSIXlt(data$decommissioned_on) | ||
|
||
# return data frame | ||
return(df) | ||
} | ||
|
||
|
||
#' Gets the details for a single `node` metadata record | ||
#' | ||
#' Nodes are the physical devices deployed to collect observations. | ||
#' The are comprised of multiple sensors and are grouped by | ||
#' projects. | ||
#' | ||
#' @param vsn - The node"s unique identifier | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A list representing the node | ||
#' @export | ||
stat.node <- function (vsn, filters = NULL) { | ||
url <- paste("https://api.arrayofthings.org/api/nodes/", vsn, sep="") | ||
details <- stat(url, filters) | ||
return(details) | ||
} | ||
|
||
|
||
#' Gets a data frame of `sensor` metadata | ||
#' | ||
#' Sensors are the physical boards inside the nodes that record | ||
#' the observations. Sensors are in various states of being tuned | ||
#' and therefor some of their observations are considered to be | ||
#' experimental. Trustworthy data is listed under the _observations_ | ||
#' endpoint and experimental data is under _raw-observations_. | ||
#' | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A data frame of sensor metadata | ||
#' @export | ||
ls.sensors <- function (filters = NULL) { | ||
# build url, send request, get response | ||
url <- "https://api.arrayofthings.org/api/sensors" | ||
resp <- send_request(url, filters) | ||
|
||
# build data frame | ||
data <- parse_content(resp) | ||
df <- as.data.frame.list(data) | ||
attr(df, "path") <- data$path | ||
attr(df, "subsystem") <- data$subsystem | ||
attr(df, "sensor") <- data$sensor | ||
attr(df, "parameter") <- data$parameter | ||
attr(df, "uom") <- data$uom | ||
attr(df, "min") <- data$min | ||
attr(df, "max") <- data$max | ||
attr(df, "data_sheet") <- data$data_sheet | ||
|
||
# return data frame | ||
return(df) | ||
} | ||
|
||
|
||
#' Gets the details for a single `sensor` metadata record | ||
#' | ||
#' Sensors are the physical boards inside the nodes that record | ||
#' the observations. Sensors are in various states of being tuned | ||
#' and therefor some of their observations are considered to be | ||
#' experimental. Trustworthy data is listed under the _observations_ | ||
#' endpoint and experimental data is under _raw-observations_. | ||
#' | ||
#' @param path - The sensor"s unique identifier | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A list representing the sensor | ||
#' @export | ||
stat.sensor <- function (path, filters = NULL) { | ||
url <- paste("https://api.arrayofthings.org/api/sensors/", path, sep="") | ||
details <- stat(url, filters) | ||
return(details) | ||
} | ||
|
||
|
||
#' Gets a data frame of `obserations` data. | ||
#' | ||
#' Observation data are the environmental measurements made | ||
#' by the sensors. Data listed here is more or less tuned | ||
#' and trustworthy. | ||
#' | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A data frame of observation data | ||
#' @export | ||
ls.observations <- function (filters = NULL) { | ||
# build url, send request, get response | ||
url <- "https://api.arrayofthings.org/api/observations" | ||
resp <- send_request(url, filters) | ||
|
||
# build data frame | ||
data <- parse_content(resp) | ||
df <- as.data.frame.list(data) | ||
attr(df, "node_vsn") <- data$node_vsn | ||
attr(df, "sensor_path") <- data$sensor_path | ||
attr(df, "timestamp") <- as.POSIXlt(data$timestamp) | ||
attr(df, "value") <- data$value | ||
|
||
# return data frame | ||
return(df) | ||
} | ||
|
||
|
||
#' Gets a data frame of `raw observations` data. | ||
#' | ||
#' Raw observation data are the environmental measurements made | ||
#' by the sensors. Data listed here can be both tuned and not-yet | ||
#' tuned data. The values of `raw` are the analog readings made by | ||
#' the sensor; `hrf` (human readable format) are the clean/trusted | ||
#' values -- these are typically null. | ||
#' | ||
#' @param filters - A list of tuples to create filters/query params | ||
#' @return A data frame of project metadata | ||
#' @export | ||
ls.raw_observations <- function (filters = NULL) { | ||
# build url, send request, get response | ||
url <- "https://api.arrayofthings.org/api/raw-observations" | ||
resp <- send_request(url, filters) | ||
|
||
# build data frame | ||
data <- parse_content(resp) | ||
df <- as.data.frame.list(data) | ||
attr(df, "node_vsn") <- data$node_vsn | ||
attr(df, "sensor_path") <- data$sensor_path | ||
attr(df, "timestamp") <- as.POSIXlt(data$timestamp) | ||
attr(df, "hrf") <- data$hrf | ||
attr(df, "raw") <- data$raw | ||
|
||
# return data frame | ||
return(df) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# Array of Things Client | ||
|
||
This library serves as the official R client to the [Array of Things API](https://api.arrayofthings.org/). | ||
|
||
## Using the Library | ||
|
||
This isn't listed with CRAN yet (because it's awful -- I'm not an R developer). You _can_ install it | ||
from GitHub though: | ||
|
||
```R | ||
devtools::install_github("UbranCCD-UChicago/aot-client-r") | ||
``` | ||
|
||
There are two general types of functions presented: _ls_ and _stat_. You should use the ls functions | ||
to work with the list endpoints, and stat is for details: | ||
|
||
- `ls.projects` to get a list of projects | ||
- `ls.nodes` to get a list of nodes | ||
- `ls.sensors` to get a list of sensors | ||
- `ls.observations` to get the observation data | ||
- `ls.raw_observations` to get the raw observation data | ||
- `stat.project` to get details for a single project | ||
- `stat.node` to get details for a single node | ||
- `stat.sensor` to get details for a single sensor | ||
|
||
The _stat_ functions require a unique id for the type of metadata you're looking for -- `slug` for | ||
projects, `vsn` for nodes, and `path` for sensors. | ||
|
||
All of the functions allow you to add arbitrary filters/parameters as well: | ||
|
||
```R | ||
# sensors onboard node 004 | ||
df <- ls.sensors(filters=list(onboard_node="004")) | ||
|
||
# note: this doesn't work quite right but it will soon | ||
# average temperature observations made in august per node | ||
df <- ls.observations(filters=list( | ||
by_sensor="metsense.bmp180.temperature", | ||
timestamp="ge:2018-08-01T00:00:00", | ||
timestamp="lt:2018-09-01T00:00:00", | ||
value="avg:node_vsn" | ||
)) | ||
``` |
Oops, something went wrong.