Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
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
Show file tree
Hide file tree
Showing 18 changed files with 653 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
^.*\.Rproj$
^\.Rproj\.user$
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.Rproj.user
.Rhistory
.RData
.Ruserdata
20 changes: 20 additions & 0 deletions AotClient.Rproj
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
13 changes: 13 additions & 0 deletions DESCRIPTION
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
13 changes: 13 additions & 0 deletions LICENSE
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.
10 changes: 10 additions & 0 deletions NAMESPACE
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)
276 changes: 276 additions & 0 deletions R/AotClient.R
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)
}
43 changes: 43 additions & 0 deletions README.md
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"
))
```
Loading

0 comments on commit 48bccca

Please sign in to comment.