July 5, 2017
We, Alicia Schep and Miles
McBain, drove the webrockets
project
at #runconf17.
To make progress we solicited code, advice, and entertaining anecdotes
from a host of other attendees, whom we humbly thank for helping to make
our project possible.
This post is divided into two sections: First up we’ll relate our
experiences, prompted by some questions we wrote for
one another. Second, we’ll put the webrockets
package into context and walk you
through a fun example where you can live plot streaming sensor data from
a mobile device.
webrockets
?
I first learned about rOpenSci last summer, while doing an internship at Genentech. Jenny Bryan came to give a seminar and also participated in a “women in computing” group discussion during her visit. During that discussion, she talked about how she thought organizations like rOpenSci that are welcoming and inclusive are great for helping diversify programming (and other) communities. It sounded like a really cool organization, and I started exploring some of the rOpenSci packages and reading the blog but wasn’t sure of how to take that next step and become more actively involved. When I saw the blog post about the unconf, it seemed like a great opportunity to take that next step so I went ahead and applied, although I didn’t think I had much chance of being selected — it was a very welcome surprise!
When I got to the unconf, I didn’t know what I wanted to work on, as a
lot of the projects sounded interesting. I had proposed a project, but
it seemed like someone else was keen to work on a similar idea after the
unconf, so I ended up circling the room trying to figure out what other
project I might want to work on. I chose the webrockets
project for
two main reasons: 1) the use cases that you proposed (making
visualizations that updated based on streaming data!) seemed really
neat, and 2) I wanted to push myself to learn something really new, and
I didn’t know much about web sockets! This type of project was something
that I would be very hesitant about jumping into on my own, so the
unconf seemed like a great time for it. I figured you could teach me all
I needed to know about web sockets :D (and if those reading this
don’t know what web sockets are, don’t worry… later in this post we’ll
explain it to you too!)
I’m pretty shy and introverted, so I was nervous about being around so
many new people and the packed schedule. Luckily, everyone I met was
extremely friendly! I did still ended up being pretty exhausted by the
end of each day, but had a great time working with you on webrockets
and talking about a wide range of topics (R, food, dogs, politics,
life…) with various smaller groups. It was also really incredible to
be able to get help on our project from some of the most knowledgeable
people possible!
I thing the big thing that will stay with me is knowing how kind and helpful people in the R community are. I’m often nervous about asking for help or contributing to a new project, but I feel much more comfortable now about doing so in the future!
webrockets
package?
It wasn’t my first choice, that was actually your suggestion for roxygen-like tagging for makefile generation, which I still think is an amazing idea. But I went into the unconf with handful of issues earmarked and websockets was definitely one of them.
The main attraction to websockets for me is about connecting R to new kinds of data sources. They could be Internet of Things devices or sources that reside in Virtual Realities. Websockets provide the base layer for the protocols you need to navigate to stream data from these sources. Since I’m starting to encounter these types of sources in my work, I had a feel for a basic unconf-sized use case to attack.
So as the project room was rapidly clearing on Thursday morning, I was
starting to get a bit disheartened that I might have to move to a
project I hadn’t really registered an interest in. Then almost at the
last moment Alicia Schep rocks up, keen and backing her C++ skills,
convincing me we might be able to get something done. That as much as
anything inspired me to work on webrockets
.
In basically all the ways. I usually work independently and I tend to bang my head against something for a while before reaching for help. The unconf encourages just the opposite approach and it works amazingly well. We had web experts like Bob Rudis and Joe Cheng sitting literally metres away from us, and they were only too happy to point us in the right directions to short cut a lot of time consuming trial and error.
Also, You and I were pair programming for a good portion of the time, that’s something I rarely do. I was surprised it worked so well given we had only just met. I think you can’t overstate the effect of the tone set by the rOpenSci team in facilitating this. I can’t speak for You, but I didn’t feel like the self-consciousness that can sometimes occur when coding in front of strangers was a noticeable presence. We were too focussed on getting something happening!
webrockets
related!)
Wow. That’s hard. I mean there’s the little known but dedicated scene inhabited by the R Karaoke People group, the fact that there exists someone who takes more unadulterated joy in streaming sensor data than me (hey Bob!), the hypnotic power of sing song southern accents, Karthik’s magic Dolittle-level powers over dogs…
But my absolute favorite thing learned would be aspects of all these inspiring people who were just previously names or static 2d avatars and text on the interwebz. As a far-flung Aussie, it’s easy to feel on the outer of the R community. Post unconf I feel like the online community is so much more alive, and that has to be a result of having had the opportunity to commune with some of its brightest inhabitants.
Websockets are communication end-points that occupy the same place in the network stack as HTTP. To shoehorn the protocols needed by modern web apps into the request-response model of HTTP has required a lot of fancy footwork involving polling schemes. These schemes are expensive in bandwidth due to HTTP header information that has to be included in the most basic of messages. Websockets strip back and abstract away that machinery, allowing for application level protocol development without concern for HTTP headers or URL strings!
You can read a great explanation here.
We’ve discussed our personal motivations, but the project
issue was actually
proposed by Bob Rudis. He has been using websockets to control headless
Chrome browsers in the context of automated testing. Check out his repo:
decapitated
.
Bob had already hunted down a lightweight C++ websocket library that compiled on Windows, and he was kind enough to share that with us. So using the wrapper he created for easywsclient as a template, we set about creating a general API to target our unconf challenge.
That challenge was: Can we create a shiny
app that makes live animated
plots of streaming data received over a websocket connection? To do this
successfully requires a client interface (inbound) for websockets that
is compatible with the reactive programming paradigm in shiny
. It also
requires a well understood test source of streaming data to verify the
app is working properly.
A server interface (outbound) for websockets in R has been available for
some time through RStudio’s
httpuv
package. We were able to
use this to implement our test data
source.
We assume that as the client, it is our role to initiate a data streaming session with the server. So we have a way to initialize a connection:
con <- ws_connect(url = "ws://localhost:5006/")
con
is a handle to our websocket connection. It is passed to read
methods to get data.
With easywsclient
there is no concept of a buffer messages piling up
for us. A new received message overwrites the one received prior. If we
have not read it, it is lost. This creates some considerations for us as
consumers of websocket data:
Good answers to these questions depend on the characteristics of the data stream as well as our application. We have to consider how fast and how predictably data are arriving, and what data we really need (e.g. latest vs all history). So some degrees of freedom are required, which is why we created a few methods to read from websockets:
msg <- ws_receive(ws_ptr = con, timeout = 10) # Check for a message waiting up to 10 milliseconds to receive.
msg <- ws_receive_one(ws_ptr = con, frequency = 5) #Return 1 message, check every 5 milliseconds until it arrives.
msg <- ws_receive_multiple(ws_ptr = con, eventlimit = 10) #Do not return until you have 10 messages.
To continually receive messages, we could potentially put calls to
receive messages within a while loop. However, that wouldn’t work in the
context of shiny
since the app would spend all of its time inside the
while
loop, ignoring other inputs/outputs. We got a very entertaining
introduction to the future
package and how it might
help us get messages asynchronously from Henrik
Bengtsson, but ultimately Carl
Ganz and Joe
Cheng set us on the right path using some
of shiny
's lesser known classes and functions.
To receive websocket messages in a shiny
application, we can place our
call to get the message and the handling of the response in an
observeEvent
expression. The event we are observing here isn’t a real
event; we use the invalidateLater
function to specify that we actually
just want to run the code every X milliseconds.
con <- ws_connect(url = "ws://localhost:5006/")
ui <- fluidPage(
plotOutput('plot')
)
server <- function(input, output) {
values <- reactiveValues(x = NULL, y = NULL)
observeEvent(invalidateLater(100), {
new_response <- ws_receive(con, 0)
if (new_response != ""){
new_point <- fromJSON(new_response)
values$x <- c(values$x, new_point$x)
values$y <- c(values$y, new_point$y)
}
}, ignoreNULL = FALSE)
output$plot <- renderPlot({
ggplot(data.frame(xval = values$x, yval = values$y),
aes(x = xval, y=yval)) + geom_point()
})
}
shinyApp(ui = ui, server = server)
Here’s what the app looks like as new points are received from the server:
We were pretty happy to get the proof of concept shiny
app that
updates based on a test server up and running — lots of high fives and
cheering when we saw the first few points getting added to the
plot! We then set our sights higher — could we plot some real sensor
data? Miles set up an app on his phone that would start a websockets
server that would send out messages containing accelerometer data from
the phone. We modified our shiny
app to read in and plot that real
(live!) data.
We used an app
from the Android app store to stream our sensor data. Hot tip: be sure
to be running your shiny
plotting app on the same wifi network as your
phone. To begin sensor data transmission, hit “start” in the sensor data
app interface and make a note of the websocket port and phone ip
address.
The call to establish the connection is:
con <- ws_connect(url = "ws://<PHONE_IP_ADDRESS>:<APP_WEBSOCKET_PORT>")
.
The code for our accelerometer shiny app required only small modifications from above.
We made styling change in ui
to hide the “greying-out” effect of the
plot while it was re-rendered with each new datapoint. It makes the plot
seem more fluid:
tags$style(type="text/css",
".recalculating {opacity: 1.0;}"),
In server
introduced a call to gather
so we could create a time
series plot facetted by accelerometer axis (x,y,z). It would be better
not to do this at each timestep. Individual plots could alleviate this.
The plot changes to:
output$plot <- renderPlot({
data.frame(xval = values$x, yval = values$y,
zval = values$z, time = values$time) %>%
gather("variable", "value", -time) %>%
ggplot(aes(x = time, y=value)) + geom_path() + facet_grid(~variable)
})
Voila! Now you can create a demo like this:
The plot updates with the acceleration of the phones in the x, y, an z directions in real time as Miles moves the phone around!
In this video you can hear Nick Tierney losing his mind as he watches the plot update in real time, but your mileage may vary with, you know… non-R people.
The project is about to get some real world use in an upcoming project
so expect a bunch of issues and design considerations to shake out of
that. Constructive feedback on our effort and/or contributions are very
welcome! Feel free to engage us on Twitter
(@aliciaschep,
@milesmcbain), rOpenSci slack
(check out the #webrockets
channel), or the issues on the
webrockets
Github repo.