This vignette describes a way to visualize on shiny a 3D animation ofan 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 thatrepresents the new orientationimu_proxy() andimu_send_data() toupdate theimu_object() widget with the quaternionThe code of a possible implementation is shown in the next section Wewill 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 coordc(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<-32if (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 loopbreak }# gyr from bmi270 IMU is in deg/sec, need to convert to rad/seclist(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<- quat0while (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 tocompUpdate().
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, whetherit meets certain requirements.
Once we confirm the data is legit, we exit the loop and proceed topackage the IMU measurements into a list of two vectors: a numericvector for the 3 accelerometer readings and another numeric vector forthe 3 gyroscope readings. We take care to transform the data so itconforms to whatcompUpdate() expects. First, we transformthe data from IMU’s coordinate system to the north-east-down (NED)conventioncompUpdate() expects. Second, we convert thegyroscope readings from deg/sec to rad/sec again expected bycompUpdate(). Whether these transformations are necessarydepends on the IMU hardware and firmware you use.
With the IMU readings appropriately packaged, we callcompUpdate() to calculate a new rotation quaternion thatrepresents the latest orientation of the IMU. Besides the accelerometerand gyroscope readings, there are two other inputs worth mentioning. Thefirst is the time duration (in seconds). This should be the inverse ofthe sampling frequency (in Hz) of the IMU. The second is the initialorientation. This should be the quaternion output of the previousiteration. In other words, the quaternion of the last iteration becomesthe initial orientation of the current iteration.
The last step is to update animation with the newly calculatedrotation quaternion. We accomplish this by callingimu_proxy() andimu_send_data() in succession.Note that the input toimu_proxy() is the output id of therenderedimu_object().
As you move the IMU, the animation shown should reflect the movementof the IMU. The following is an example of what an animation may looklike: