DataScience+ An online community for showcasing R & Python tutorials. It operates as a networking platform for data scientists to promote their talent and get hired. Our mission is to empower data scientists by bridging the gap between talent and opportunity.
Programming

Email and Text Message Alerts Based on Streaming Sensor Data

  • Published on February 8, 2018 at 8:00 am
  • Updated on February 12, 2018 at 11:42 pm

This is a video-tutorial on how we can get email or text alerts based on streaming real world data. The question I answer through this post at DataScience+ is how can we get email and text message alerts when sensors either fail or transmit abnormal reading. Watch the video below and let me know if you have any questions.

If we have a dashboard that is built based on dynamic data and we want alerts when some conditions are met, how can we do that? One option is using R-shiny. You can also check the code on my GitHub or read the code below.

generate_data.R

longitude=c(-110,-90,-100,-85)
latitude=c(33,40,37,34)

sensorID=c("SN100","SN200","SN300","SN400")


i=0
while(i<100){
dat=data.frame(timestamp=as.numeric(Sys.time()),sensorID=sensorID,longitude=longitude,latitude=latitude,
               temperature=rnorm(n=4,mean=30,sd=1))
write.csv(dat,paste0("sensorData",gsub("[^0-9]","",Sys.time()),".csv"),
          row.names = FALSE)
i=i+1
Sys.sleep(2)
}

while(TRUE){
        dat=data.frame(timestamp=as.numeric(Sys.time()),sensorID=sensorID[1:3],longitude=longitude[1:3],latitude=latitude[1:3],
                       temperature=rnorm(n=3,mean=30,sd=1))
        write.csv(dat,paste0("sensorData",gsub("[^0-9]","",Sys.time()),".csv"),
                  row.names = FALSE)
        Sys.sleep(2)
}

server.R

library(dplyr)
library(data.table)
library(ggplot2)
library(mailR)

filenames <- list.files(pattern="*.csv", full.names=TRUE)

data_at_start <- rbindlist(lapply(filenames, fread))

ids_at_start <- unique(data_at_start$sensorID)



IsThereNewFile <- function(){  #  cheap function whose values over time will be tested for equality;
        #  inequality indicates that the underlying value has changed and needs to be 
        #  invalidated and re-read using ReadAllData
        
        filenames <- list.files(pattern="*.csv", full.names=TRUE)
        length(filenames)
}

ReadAllData=function(){ # A function that calculates the underlying value
        
        filenames <- list.files(pattern="*.csv", full.names=TRUE)
       temp= rbindlist(lapply(filenames, fread))
       temp$timestamp =as.POSIXct(as.numeric(as.character(temp$timestamp)),origin="1970-01-01",tz="GMT")
       temp
}



shinyServer(function(input, output,session) {
        
        
     alldata <- reactivePoll(10, session,IsThereNewFile, ReadAllData)    
                   # 10: number of milliseconds to wait between calls to IsThereNewFile
        
           
        output$myleaflet %fitBounds(right,bottom,left,top)%>%
                          addTiles()%>%
                          addMarkers(
                                  data=latslons,
                                  label=~as.character(sensorID),
                                  labelOptions = labelOptions(noHide = T,textOnly = T,textsize = "16px",offset = c(12, -10))
                          )%>%
                          addMarkers(
                                  data=latslons,
                                  label=~as.character(latlon),
                                  labelOptions = labelOptions(noHide = F,textOnly = T,textsize = "12px",offset = c(12, 10))
                          )
          })
          
        output$timeseries_all = renderPlot({
                  dat=alldata()
                  end=nrow(dat)
                  start=1#end-100
                  
                  if(nrow(dat)>=1){
                          dat[start:end,]%>%ggplot(aes(x=timestamp,y=temperature))+
                                  geom_line(aes(color=sensorID))+ylim(26, 34)+
                                  geom_hline(yintercept = 33,linetype="dotted",color="darkblue")+
                                  labs(x="",y="Temperature",color="Sensor IDs")+
                                  theme(axis.title.x = element_blank(),
                                        axis.title.y = element_text(colour="blue",size=14),
                                        axis.text = element_text(colour="darkred",size=12),
                                        plot.title = element_blank())
                  }
          })
          
          
          ids_too_high_reading=reactive({
                    dat=alldata()
                    temp=filter(dat,timestamp==max(dat$timestamp))
                    ids=temp$sensorID[temp$temperature>100]
                    ids
                    
                }) 
            
          ids_failed_sensors=reactive({
                    dat=alldata()
                    temp=filter(dat,timestamp==max(dat$timestamp))
                    ids=unique(temp$sensorID)
                    previous_ids=new_ids$ids
                    previous_ids[!(previous_ids %in% ids)]
                    
            }) 
           
       
           observe({
                    if(length(ids_too_high_reading())>0){
                            
                            # Verizon: [email protected]
                            # AT&T: [email protected]
                            # other carriers: https://20somethingfinance.com/how-to-send-text-messages-sms-via-email-for-free/
                            
                            send.mail(from = "sender email",
                                      to = "recipient emails and phone numbers",
                                      subject="Sensor Alert: Abnormal reading",
                                      body =paste("Abnormal reading! Sensor IDs: ",ids_too_high_reading()),
                                      smtp = list(host.name = "smtp.gmail.com", port = 465,
                                                  user.name="sender", passwd="password", ssl=TRUE),
                                      authenticate = TRUE,
                                      send = TRUE)
                    }
            })
           
            
           new_ids=reactiveValues(ids=ids_at_start)
           
            observe({
                    if(length(ids_failed_sensors())>0){
                            new_ids$ids=new_ids$ids[!(new_ids$ids %in% ids_failed_sensors())]
                            
                            
                            send.mail(from = "sender email",
                                      to = "recipient emails and phone numbers",
                                      subject="Sensor Alert:Failed Sensors",
                                      body =paste("Failed Sensors! Sensor IDs: ",ids_failed_sensors()),
                                      smtp = list(host.name = "smtp.gmail.com", port = 465,
                                                  user.name="sender email", passwd="password", ssl=TRUE),
                                      authenticate = TRUE,
                                      send = TRUE)
                    }
            })
        
                output$Too_High <-  renderText({
                        
                        paste("Sensors with too high Readings: ",length(ids_too_high_reading()))
                })
                
                
                output$Failed_Sensors <-  renderText({
                        
                        paste("Number of Failed Sensors: ",length(ids_failed_sensors()))
                })
        
                  
        })

ui.R

library(shiny)
library(leaflet)

shinyUI(fluidPage(

  tags$h2("Email and Text Message Allerts Based on Streaming Sensor Data",style="text-align:center;color:blue"),
     br(),
     br(),
  fluidRow(
        column(width=5,
                leafletOutput("myleaflet",height = "500px")
        ),
        column(width=7,
                 plotOutput("timeseries_all",height = "500px"),
                  column(width=6, offset=5,
                         br(),
                         br(),
                 textOutput("Too_High")
                  ),
               column(width=6, offset=5,
                      textOutput("Failed_Sensors")
               )
               
        ))))