knitr::opts_chunk$set( collapse = TRUE, comment = "#>" )
This vignette describes a way to visualize on shiny a 3D animation of an inertial measurement unit (IMU) in real time. The setup requires:
We assume both of these have been established.
The core of the shiny logic consists of three parts:
compUpdate()
compUpdate()
to output a quaternion that represents the new orientationimu_proxy()
and imu_send_data()
to update the imu_object()
widget with the quaternionThe code of a possible implementation is shown in the next section We will explain this implementation in the sections following that.
if (interactive()) { library(shiny) library(serial) library(imuf) library(stringr) getCon <- function(port) { # # set up connection for serial port con <- serial::serialConnection(name = "testcon", port = port, mode = "115200,n,8,1", newline = 1, translation = "crlf" ) if (serial::isOpen(con)) close(con) con } readFromSerial <- function(con) { # # helper - function to convert sensor coord to NED bmi2ned <- function(bmi) { # convert bmi coord to ned coord c(bmi[1], -bmi[2], -bmi[3]) } # # helper - function to convert deg to radian toRad <- function(deg) { deg * pi/180 } # # functions to process and validate data read from serial port isValidLength <- function(x) { minLength <- 32 if (x <= minLength) return(FALSE) return(TRUE) } str2Vec <- function(x) { # # data from the IMU is a row of 6 comma-separated floats: # accx, accy, accz, gyrx, gyry, gyrz y <- stringr::str_split_1(x, ",") %>% trimws() %>% as.numeric() %>% suppressWarnings() y } isValidNumElements <- function(x) { if (length(x) != 6) return(FALSE) return(TRUE) } while (TRUE) { nInQ <- serial::nBytesInQueue(con)["n_in"] if(!isValidLength(nInQ)) next # a <- serial::read.serialConnection(con) %>% str2Vec() if(!isValidNumElements(a)) next # # a is the IMU output we want, exit infinite loop break } # gyr from bmi270 IMU is in deg/sec, need to convert to rad/sec list(acc = bmi2ned(a[1:3]), gyr = bmi2ned(a[4:6]) %>% toRad()) } runshiny <- function(port) { # ui = fluidPage( actionButton("do", "Start animation"), imu_objectOutput("imu1") ) server = function(input, output, session) { # initial orientation quat0 <- c(cos(pi/4), sin(pi/4), 0, 0) observeEvent(input$do, { con <- getCon(port) open(con) quat <- quat0 while (TRUE) { accgyr <- readFromSerial(con) quat <- compUpdate(accgyr$acc, accgyr$gyr, dt = 1/50, initQ = quat, gain = 0.1) imu_proxy("imu1") %>% imu_send_data(data = quat) } }) output$imu1 <- renderImu_object( imu_object(quat0) ) } shinyApp(ui = ui, server = server) } }
The goal of this step is to read the IMU data from a serial port, validate it, and package it so it can be used as input to compUpdate()
.
We first use the serial package to set up a serial port connection. We then enter into a loop to check if there is data and if so, whether it meets certain requirements.
Once we confirm the data is legit, we exit the loop and proceed to package the IMU measurements into a list of two vectors: a numeric vector for the 3 accelerometer readings and another numeric vector for the 3 gyroscope readings. We take care to transform the data so it conforms to what compUpdate()
expects. First, we transform the data from IMU's coordinate system to the north-east-down (NED) convention compUpdate()
expects. Second, we convert the gyroscope readings from deg/sec to rad/sec again expected by compUpdate()
. Whether these transformations are necessary depends on the IMU hardware and firmware you use.
With the IMU readings appropriately packaged, we call compUpdate()
to calculate a new rotation quaternion that represents the latest orientation of the IMU. Besides the accelerometer and gyroscope readings, there are two other inputs worth mentioning. The first is the time duration (in seconds). This should be the inverse of the sampling frequency (in Hz) of the IMU. The second is the initial orientation. This should be the quaternion output of the previous iteration. In other words, the quaternion of the last iteration becomes the initial orientation of the current iteration.
The last step is to update animation with the newly calculated rotation quaternion. We accomplish this by calling imu_proxy()
and imu_send_data()
in succession. Note that the input to imu_proxy()
is the output id of the rendered imu_object()
.
As you move the IMU, the animation shown should reflect the movement of the IMU. The following is an example of what an animation may look like:
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.