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-11 03:38:40 +00:00
from kivy . properties import 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
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-13 17:57:33 +00:00
#root = kivy.lang.Builder.load_file('./main.kv')
#Logger.debug('root from super: ' + str(root))
#Logger.debug(str(root.ids))
#children = root.ids
2014-12-10 22:22:52 +00:00
mainWindow = MainWindow ( )
2014-12-13 17:57:33 +00:00
#Logger.debug('mainWindow' + str(mainWindow))
#Logger.debug(str(mainWindow.ids))
#children = mainWindow.children[:]
#while children:
# child = children.pop()
# children.extend(child.children)
# id = getattr(child, 'id', 'no id')
# if id is None:
# id = 'no id'
# Logger.debug(id + ' : ' + str(child))
# Logger.debug(str(child.ids))
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
# @TODO fix on_X functions so they use specific bindings in the kv file rather than trying to call everything.
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-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 ) )
self . ids [ ' serial_chooser_dropdown ' ] . bind ( on_serial_port_change = self . serial_port_changed )
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 ) )
if ' data ' in data and ' temperature ' in data [ ' data ' ] :
self . lastTemperature = float ( data [ ' data ' ] [ ' temperature ' ] )
if ' time ' in data :
self . lastTime = float ( data [ ' time ' ] )
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 ( )
Logger . debug ( ' MainWindow: dataThread ' + str ( dataThread . name ) + ' status: ' + str ( dataThread . is_alive ( ) ) )
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 ' )
else :
self . dataSource = new_port
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 ( )
class SerialPortButton ( Button ) :
currentSerialPort = StringProperty ( ' ' )
def __init__ ( self , * * kwargs ) :
super ( SerialPortButton , self ) . __init__ ( * * kwargs )
self . text = ' /dev/ttyACM0 '
def serial_port_selected ( self , * args ) :
Logger . debug ( ' SerialPortButton serial_port_selected: ' + str ( args ) )
if args :
if args [ 0 ] :
self . text = args [ 0 ]
else :
self . text = ' No serial port selected! '
class SerialPortDropdown ( DropDown ) :
__events__ = ( ' on_serial_port_changed ' , )
def __init__ ( self , * * kwargs ) :
super ( SerialPortDropdown , self ) . __init__ ( * * kwargs )
self . register_event_type ( ' on_serial_port_changed ' )
self . size_hint = ( None , None )
Logger . debug ( str ( self . get_root_window ( ) ) )
self . on_dataSources ( temp_log . list_serial_ports ( ) )
def on_dataSources ( self , values ) :
2014-12-11 03:38:40 +00:00
children = self . children [ : ]
2014-12-13 17:57:33 +00:00
values_used = [ ]
current_value = getattr ( self . parent , ' text ' , None )
2014-12-11 03:38:40 +00:00
while children :
child = children . pop ( )
2014-12-10 22:22:52 +00:00
children . extend ( child . children )
2014-12-13 17:57:33 +00:00
text = getattr ( child , ' text ' , None )
if text and text in values :
values_used . append ( text )
Logger . debug ( ' Child ' + str ( child ) + ' is used ' )
2014-12-11 03:38:40 +00:00
continue
2014-12-13 17:57:33 +00:00
if text and text not in values :
Logger . debug ( ' Child ' + str ( child ) + ' is now unused, to be removed ' )
self . remove_widget ( child )
self . data = ' '
self . dispatch ( ' on_serial_port_changed ' , ' ' )
# add in new children
new_values = set ( values ) - set ( values_used )
for new_value in new_values :
btn = Button ( text = new_value , size_hint_y = None , height = 20 )
Logger . debug ( ' Child ' + str ( btn ) + ' added to dropdown with value ' + new_value )
btn . bind ( on_release = lambda btn : self . select ( btn . text ) )
self . add_widget ( btn )
def select ( self , value ) :
super ( SerialPortDropdown , self ) . select ( value )
Logger . debug ( ' SerialPortDropdown: ' + ' selected with args ' + str ( value ) )
self . data = value
self . dispatch ( ' on_serial_port_changed ' , value )
def on_serial_port_changed ( self , * args ) :
pass
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-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 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-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 ( )