2014-12-09 03:12:21 +00:00
import kivy
from kivy . app import App
2014-12-10 22:22:52 +00:00
from kivy . clock import Clock
2014-12-11 03:38:40 +00:00
import kivy . core . image
2014-12-13 17:57:33 +00:00
from kivy . core . image import ImageData
from kivy . core . image . img_pil import ImageLoaderPIL
2014-12-11 03:38:40 +00:00
from kivy . core . image . img_pygame import ImageLoaderPygame
import kivy . graphics . texture
2014-12-13 17:57:33 +00:00
from kivy . graphics . texture import Texture
2014-12-10 22:22:52 +00:00
from kivy . logger import Logger
2014-12-13 21:44:05 +00:00
from kivy . properties import DictProperty , ListProperty , NumericProperty , ObjectProperty , StringProperty
2014-12-09 03:12:21 +00:00
from kivy . uix . boxlayout import BoxLayout
2014-12-13 17:57:33 +00:00
from kivy . uix . button import Button
from kivy . uix . dropdown import DropDown
2014-12-10 22:22:52 +00:00
from kivy . uix . floatlayout import FloatLayout
2014-12-13 17:57:33 +00:00
from kivy . uix . gridlayout import GridLayout
2014-12-11 03:38:40 +00:00
from kivy . uix . image import Image
2014-12-10 22:22:52 +00:00
from kivy . uix . label import Label
2014-12-13 21:44:05 +00:00
from kivy . uix . modalview import ModalView
from kivy . uix . popup import Popup
from kivy . uix . spinner import Spinner
2014-12-10 22:22:52 +00:00
import kivy . lang
2014-12-11 03:38:40 +00:00
import numpy
import matplotlib
matplotlib . use ( ' Agg ' )
import matplotlib . image
import matplotlib . pyplot
2014-12-13 17:57:33 +00:00
import pygame . image
2014-12-10 22:22:52 +00:00
import Queue
2014-12-11 03:38:40 +00:00
import StringIO
2014-12-10 22:22:52 +00:00
import temp_log
import threading
2014-12-09 03:12:21 +00:00
2014-12-10 22:22:52 +00:00
dataThread = None
dataThreadDataQueue = Queue . Queue ( )
dataThreadCommandQueue = Queue . Queue ( )
2014-12-13 17:57:33 +00:00
defaultSerialPort = ' /dev/ttyACM0 '
2014-12-10 22:22:52 +00:00
class MainApp ( App ) :
2014-12-09 03:12:21 +00:00
def build ( self ) :
2014-12-10 22:22:52 +00:00
mainWindow = MainWindow ( )
2014-12-13 17:57:33 +00:00
Clock . schedule_interval ( mainWindow . update , 0.25 )
2014-12-10 22:22:52 +00:00
return mainWindow
def on_stop ( self ) :
global dataThreadCommandQueue
dataThreadCommandQueue . put_nowait ( ' stop ' )
def on_start ( self ) :
global dataThread
dataThread . start ( )
class MainWindow ( FloatLayout ) :
""" Main Window class """
2014-12-13 17:57:33 +00:00
dataSource = StringProperty ( defaultSerialPort )
dataSources = ListProperty ( temp_log . list_serial_ports ( ) )
2014-12-11 03:38:40 +00:00
lastTemperature = NumericProperty ( - 1000. )
lastTime = NumericProperty ( - 1. )
2014-12-13 17:57:33 +00:00
recordingState = StringProperty ( ' ' )
2014-12-13 21:44:05 +00:00
state = StringProperty ( ' running ' )
lastStatus = DictProperty ( { } )
2014-12-10 22:22:52 +00:00
2014-12-13 17:57:33 +00:00
def __init__ ( self , * * kwargs ) :
super ( MainWindow , self ) . __init__ ( * * kwargs )
# self.ids is available here; use it's references to connect bindings
Logger . debug ( ' MainWindow IDs: ' + str ( self . ids ) )
2014-12-13 21:44:05 +00:00
self . modal_view = None
#self.ids['serial_chooser_dropdown'].bind(on_serial_port_change=self.serial_port_changed)
2014-12-13 17:57:33 +00:00
def update_last_temperature ( self ) :
2014-12-10 22:22:52 +00:00
global dataThreadDataQueue
# This breaks if there are more updates in the queue than the frequency of update_last_temperature - it pulls old data out of the queue, and not the most recent.
try :
data = dataThreadDataQueue . get_nowait ( )
if data is not None :
2014-12-11 03:38:40 +00:00
Logger . debug ( ' Data: ' + str ( data ) )
2014-12-13 21:44:05 +00:00
if self . state != ' paused ' :
if ' data ' in data and ' temperature ' in data [ ' data ' ] :
self . lastStatus = { ' status ' : ' ok ' , ' message ' : ' ' }
self . lastTemperature = float ( data [ ' data ' ] [ ' temperature ' ] )
if ' time ' in data :
self . lastTime = float ( data [ ' time ' ] )
if ' exception ' in data :
self . lastStatus = { ' status ' : ' error ' , ' message ' : data [ ' exception ' ] }
else :
self . lastStatus = { ' status ' : ' paused ' , ' message ' : ' Data reception halted by user ' }
Logger . debug ( ' MainWindow: state set to ' + str ( self . state ) + ' : ignoring data ' )
2014-12-10 22:22:52 +00:00
except Queue . Empty :
pass
2014-12-11 03:38:40 +00:00
2014-12-10 22:22:52 +00:00
def on_lastTemperature ( self , instance , value ) :
Logger . debug ( ' lastTemperature has changed to: ' + str ( value ) )
2014-12-11 03:38:40 +00:00
def on_lastTime ( self , instance , value ) :
Logger . debug ( ' lastTime has changed to: ' + str ( value ) )
2014-12-13 17:57:33 +00:00
def update ( self , dt ) :
self . update_data_sources ( )
self . update_last_temperature ( )
2014-12-13 21:44:05 +00:00
#Logger.debug('MainWindow: dataThread ' + str(dataThread.name) + ' status: ' + str(dataThread.is_alive()))
2014-12-13 17:57:33 +00:00
def update_data_sources ( self ) :
self . dataSources = temp_log . list_serial_ports ( )
def on_dataSources ( self , instance , value ) :
Logger . debug ( ' dataSources changed to: ' + str ( self . dataSources ) )
children = self . children [ : ]
def serial_port_changed ( self , * args ) :
Logger . debug ( ' MainWindow: Received on_serial_port_changed: ' + str ( args ) )
Logger . debug ( ' MainWindow: Current dataSource: ' + str ( self . dataSource ) )
if args :
new_port = args [ 0 ]
if new_port == self . dataSource :
Logger . debug ( ' MainWindow: Selected port matches current port, no change ' )
2014-12-13 21:44:05 +00:00
elif new_port in self . dataSources :
2014-12-13 17:57:33 +00:00
self . dataSource = new_port
2014-12-13 21:44:05 +00:00
else :
self . dataSource = ' '
2014-12-13 17:57:33 +00:00
def on_dataSource ( self , instance , value ) :
# Stop the current data thread, if any.
Logger . debug ( ' MainWindow: stopping dataThread ' )
global dataThread , dataThreadDataQueue , dataThreadCommandQueue
dataThreadCommandQueue . put ( ' stop ' )
# This could cause the UI to hang until the thread is joined, if ever.
dataThread . join ( )
# Start a new data thread using the selected data source
if value :
Logger . debug ( ' MainWindow: Starting new data thread on ' + str ( value ) )
# For some reason it seems necessary to create new Queue objects.
dataThreadCommandQueue = Queue . Queue ( )
dataThreadDataQueue = Queue . Queue ( )
dataThread = threading . Thread ( None , temp_log . threaded_reader , None , [ value , dataThreadDataQueue , dataThreadCommandQueue ] )
dataThread . start ( )
2014-12-13 21:44:05 +00:00
def set_state ( self , new_state ) :
self . state = new_state
def initiate_clear_dialog ( self ) :
if self . modal_view is None :
self . modal_view = YesNoModalView ( id = ' startstop_modal_dialog ' )
self . modal_view . process_callback = self . process_clear_dialog
self . add_widget ( self . modal_view )
2014-12-13 17:57:33 +00:00
2014-12-13 21:44:05 +00:00
def process_clear_dialog ( self , * args , * * kwargs ) :
Logger . debug ( ' StatusWidget: process_dialog args: ' + str ( args ) )
if args and args [ 0 ] :
self . ids . mainplot . clear_data ( )
self . remove_widget ( self . modal_view )
self . modal_view = None
class SerialPortButton ( Spinner ) :
__events__ = ( ' on_serial_port_changed ' , )
noValueText = StringProperty ( ' No serial port selected ' )
2014-12-13 17:57:33 +00:00
def __init__ ( self , * * kwargs ) :
super ( SerialPortButton , self ) . __init__ ( * * kwargs )
self . text = ' /dev/ttyACM0 '
2014-12-13 21:44:05 +00:00
self . register_event_type ( ' on_serial_port_changed ' )
self . values = temp_log . list_serial_ports ( )
2014-12-13 17:57:33 +00:00
2014-12-13 21:44:05 +00:00
def on_text ( self , instance , value ) :
if value == self . noValueText :
value = ' '
Logger . debug ( ' SerialPortDropdown: ' + ' selected with args ' + str ( value ) )
self . dispatch ( ' on_serial_port_changed ' , value )
def on_values ( self , instance , value ) :
if self . text not in self . values :
self . text = self . noValueText
def on_serial_port_changed ( self , * args , * * kwargs ) :
pass
2014-12-13 17:57:33 +00:00
2014-12-10 22:22:52 +00:00
class MyLabel ( Label ) :
2014-12-09 03:12:21 +00:00
2014-12-13 17:57:33 +00:00
def __init__ ( self , * * kwargs ) :
super ( MyLabel , self ) . __init__ ( * * kwargs )
def on_temperature_change ( self , value ) :
if not value :
value = ' n/a '
2014-12-10 22:22:52 +00:00
self . text = ' Current temperature: ' + str ( value )
2014-12-09 03:12:21 +00:00
2014-12-13 21:44:05 +00:00
class StatusWidget ( BoxLayout ) :
def __init__ ( self , * * kwargs ) :
super ( StatusWidget , self ) . __init__ ( * * kwargs )
self . states = [ ' running ' , ' paused ' ]
self . change_state_labels = { ' running ' : ' Pause ' , ' paused ' : ' Resume ' }
self . modal_view = None
def change_state ( self ) :
current_state = self . states . index ( self . state )
new_state = ( current_state + 1 ) % len ( self . states )
self . state = self . states [ new_state ]
def on_state ( self , instance , value ) :
Logger . debug ( ' StatusWidget: new state ' + str ( self . state ) )
2014-12-11 03:38:40 +00:00
class PlotWidget ( Image ) :
def __init__ ( self , * * kwargs ) :
2014-12-13 17:57:33 +00:00
super ( PlotWidget , self ) . __init__ ( * * kwargs )
2014-12-11 03:38:40 +00:00
self . data = numpy . array ( [ ] , dtype = float ) ;
self . lastTime = 0
self . lastTemperature = 0
self . figure = matplotlib . pyplot . figure ( )
self . plot_axes = self . figure . add_subplot ( 1 , 1 , 1 )
self . plot_axes . hold ( False )
self . plot_axes . set_ylabel ( ' Temperature (deg C) ' )
self . plot_axes . set_xlabel ( ' Time ' )
self . plot_axes . set_title ( ' Recorded Temperature ' )
2014-12-13 17:57:33 +00:00
self . image_data = None
self . _image_raw_data = None
2014-12-11 03:38:40 +00:00
2014-12-13 21:44:05 +00:00
def clear_data ( self ) :
self . data = numpy . array ( [ ] , dtype = float ) ;
self . texture = None
2014-12-13 17:57:33 +00:00
def do_update ( self ) :
2014-12-11 03:38:40 +00:00
if not self . data . any ( ) :
return
#Logger.debug('self.data: ' + str(self.data))
#Logger.debug('self.data shape: ' + str(self.data.shape))
data = numpy . copy ( self . data )
data = data . transpose ( )
#Logger.debug('transpoed data: ' + str(data))
#Logger.debug('transposed data shape: ' + str(data.shape))
t , temperature = numpy . split ( data , 2 , axis = 0 )
#Logger.debug('time: ' + str(t[0]))
#Logger.debug('temperature: ' + str(temperature[0]))
self . plot_axes . plot ( t [ 0 ] , temperature [ 0 ] )
image_data = StringIO . StringIO ( )
self . figure . savefig ( image_data , format = ' png ' )
image_data . seek ( 0 )
2014-12-13 17:57:33 +00:00
self . _image_raw_data = image_data
# We can't use ImageLoaders since they assume it's a file on disk.
# This replicates code from ImageLoaderPygame.load() and ImageLoaderBase.populate()
try :
im = pygame . image . load ( image_data )
except :
Logger . warning ( ' Image: Unable to load image from data ' )
raise
fmt = ' '
if im . get_bytesize ( ) == 3 :
fmt = ' rgb '
elif im . get_bytesize ( ) == 4 :
fmt = ' rgba '
data = pygame . image . tostring ( im , fmt . upper ( ) )
self . image_data = ImageData ( im . get_width ( ) , im . get_height ( ) , fmt , data )
self . texture = Texture . create_from_data ( self . image_data )
self . texture . flip_vertical ( )
2014-12-11 03:38:40 +00:00
2014-12-13 17:57:33 +00:00
def on_lastTemperature ( self , value ) :
2014-12-11 03:38:40 +00:00
self . lastTemperature = value
2014-12-13 17:57:33 +00:00
def on_lastTime ( self , value ) :
2014-12-11 03:38:40 +00:00
self . lastTime = value
self . update_data ( )
def update_data ( self ) :
if self . lastTime == 0 or self . lastTemperature == 0 :
return
newpoint = numpy . array ( [ ( self . lastTime , self . lastTemperature ) ] , dtype = float , ndmin = 2 )
if not self . data . any ( ) :
self . data = newpoint
return
#Logger.debug('self.data: ' + str(self.data))
#Logger.debug('newpoint: ' + str(newpoint))
self . data = numpy . vstack ( ( self . data , newpoint ) )
2014-12-13 17:57:33 +00:00
self . do_update ( )
2014-12-11 03:38:40 +00:00
2014-12-13 21:44:05 +00:00
class YesNoModalView ( Popup ) :
def process ( self , result , * args , * * kwargs ) :
Logger . debug ( ' YesNoModalView: ' + str ( result ) + ' ' + str ( args ) )
Logger . debug ( ' YesNoModalView: callback ' + str ( self . process_callback ) )
if self . process_callback :
self . process_callback ( result , * args , * * kwargs )
self . dismiss ( )
class StatusBar ( BoxLayout ) :
def update_status ( self , status ) :
Logger . debug ( ' StatusBar: received status ' + str ( status ) )
Logger . debug ( ' StatusBar: ids available ' + str ( self . ids ) )
if ' message ' in status :
self . ids . status_message . text = status [ ' message ' ]
if ' status ' in status :
current_status = status [ ' status ' ]
if current_status == ' ok ' :
self . ids . status_text . text = ' Running '
self . ids . status_image . source = ' ./images/ok.png '
if not self . ids . status_message . text :
self . ids . status_message . text = " Everything ' s perfectly all right now. We ' re fine. We ' re all fine here now, thank you. How are you? "
elif current_status == ' error ' :
self . ids . status_text . text = ' ERROR '
self . ids . status_image . source = ' ./images/error.png '
elif current_status == ' paused ' :
self . ids . status_text . text = ' Paused '
self . ids . status_image . source = ' ./images/warning.png '
else :
self . ids . status_text . text = ' Unknown Status: ' + current_status
self . ids . status_image . source = ' ./images/warning.png '
2014-12-09 03:12:21 +00:00
if __name__ == ' __main__ ' :
2014-12-10 22:22:52 +00:00
dataThread = threading . Thread ( None , temp_log . threaded_reader , None , [ ' /dev/ttyACM0 ' , dataThreadDataQueue , dataThreadCommandQueue ] )
MainApp ( ) . run ( )