emoncmsr: R interface to the emonCMS API

R build status Coverage Status

This package provides the tools to create, delete, and manage inputs and feeds in the open source energy, temperature and environmental monitoring sytem from OpenEnergyMonitor.

emonCMS has two flavors, a self-hosted version and a hosted solution at emoncms.org. The two APIs are similar but differ in a number of areas. This package only supports the self-hosted flavor, though there are several unsupported functions for the hosted version.

This package takes a slightly opinionated view towards the API, returning normalized values from many of the API calls. The native API has several non- standard and inconsistent responses to both success and failure. emoncmsr provides an interface consistent with data analysis and a tidy pipeline.

Installation

devtools::install_github("davidski/emoncmsr")

Usage

emoncmsr requires two environment variables to locate the emoncms host and the proper API key for authentication. These can be set globally (e.g. via .bashrc, the Windows control panel, or other OS-specific mechanism) or you can place them in your ~.Renviron file for a portable solution.

Environment variables used:

Applications

Simulating a beverage sensor that monitors the level of coffee, tea, and water available. First, we'll list the inputs currently configured.

library(emoncmsr)
suppressPackageStartupMessages(library(tidyverse))  # use the tidyverse

# Inputs
list_inputs()

Now we'll create some simulated data, post it to emonCMS as inputs using a node identifier of emoncmsr, read back the value of the coffee level we just posted, store the ID of this new coffee input for future use, and set a useful description for the new input. Whew! Let's get to it!

# create some beverage data
dat <- list(coffee = 42, tea = 6, water = 42)
post_data_to_input(dat)
list_inputs() %>% filter(nodeid == "emoncmsr", name == "coffee")
# store the id of the new input
inputid <- list_inputs() %>% 
  filter(nodeid == "emoncmsr", name == "coffee") %>% 
  pull(id)
inputid

# set a friendly description for our new input
set_input_field(inputid, "description", "cups of coffee remaining in pot")
list_inputs() %>% filter(id == inputid)

That wasn't so bad!

While we're now accepting levels of coffee, tea, and water in emonCMS, those values aren't being stored or processed in any way. We'd like to monitor the levels of coffee over time. We need to create a feed, then configure the coffee input to send its data to that feed.

# Create a feed for the coffee level
feed_response <- create_feed("coffeelevel", "emoncmsr")
feed_response

# Show that the feed exists
list_feeds() %>% filter(id == feed_response$feedid)

# Hook up the coffee monitor to the feed
set_input_process(inputid, paste(1, feed_response$feedid, sep = ":"))
get_input_processes(inputid)

Now that we have our feed set up, let's send some updated beverage level sensor data. We'll first send a single timepoint set of values, then demonstrate using the bulk data interface to several days of simulated data in a single call.

After sending the readings we'll read the feed metadata to demonstrate that data has flowed from the input to the feed.

# Post a single set of readings to all three new inputs
dat <- list(coffee = 86, tea = 100, water = 4)
post_data_to_input(dat)

# show the values we just posted appeared in the inputs
list_inputs() %>% filter(nodeid == "emoncmsr")

# We can also use the bulk data input format for sending a dataframe 
# worth of data, all at different offsets to an optional timestamp
interval <- get_feed_metadata(feed_response$feedid)$interval
end_time <- lubridate::now() %>% as.integer()
end_time <- end_time - (end_time %% interval)
start_time <- (lubridate::now() - lubridate::ddays(3)) %>% as.integer()
start_time <- start_time - (start_time %% interval)

times <- seq(start_time, end_time, by = interval)
dat <- tibble(offset = times, nodeid = "emoncmsr")
dat <- bind_cols(dat, tibble(value = map(times, ~list("coffee" = sample(1:10, size=1)))))
post_bulk_data_to_input(dat, reference_time = 0)
# this large a bulk post can take a moment to process, sleep for a few seconds
Sys.sleep(5)

# we can also post with a specific reference time, though we don't demonstrate
# that here...
# reference_time <- lubridate::as_datetime("2017-03-27 01:30:00") %>% as.integer()
# post_bulk_data_to_input(dat, reference_time)


# show the feed's info
get_feed_metadata(feed_response$feedid)

Beverage monitoring systems are GO! Let's pull a set of data from our feed and plot that data over time, adding a smoothed curve for grins.

dat <- get_feed_data(feed_response$feedid)
gg <- ggplot(dat, aes(x = date, y = value)) + 
  geom_line() + geom_smooth(method = 'loess') +
  labs(title = "Coffee Levels", 
       subtitle = "Seven day historical with smoothed overlay",
       caption = "Demonstration plot for emoncmsr",
       y = "Watts", 
       x = NULL) +
  scale_y_continuous(labels = scales::pretty_breaks()) +
  theme_minimal()
gg

Finally, clean up the test inputs and feeds we created.

list_feeds() %>% filter(tag == "emoncmsr") %>% pull(id) %>% 
  map(~ delete_feed(.x))
list_inputs() %>% filter(nodeid == "emoncmsr") %>% pull(id) %>% 
  map(~ delete_input(.x))

Test results

library(emoncmsr)
library(testthat)

date()

test_dir("tests/")

Contributing

This project is governed by a Code of Conduct. By participating in this project you agree to abide by these terms.

License

The MIT License applies.



davidski/emoncmsr documentation built on April 20, 2022, 6:40 p.m.