627 lines
23 KiB
Python
627 lines
23 KiB
Python
import kivy
|
|
from kivy.app import App
|
|
from kivy.clock import Clock
|
|
import kivy.core.image
|
|
from kivy.core.image import ImageData
|
|
from kivy.core.image.img_pil import ImageLoaderPIL
|
|
from kivy.core.image.img_pygame import ImageLoaderPygame
|
|
import kivy.graphics.texture
|
|
from kivy.graphics.texture import Texture
|
|
from kivy.logger import Logger
|
|
from kivy.properties import BooleanProperty, DictProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty
|
|
from kivy.uix.boxlayout import BoxLayout
|
|
from kivy.uix.button import Button
|
|
from kivy.uix.dropdown import DropDown
|
|
from kivy.uix.floatlayout import FloatLayout
|
|
from kivy.uix.gridlayout import GridLayout
|
|
from kivy.uix.image import Image
|
|
from kivy.uix.label import Label
|
|
from kivy.uix.modalview import ModalView
|
|
from kivy.uix.popup import Popup
|
|
from kivy.uix.spinner import Spinner
|
|
import kivy.lang
|
|
import matplotlib
|
|
matplotlib.use('Agg')
|
|
import matplotlib.image
|
|
import matplotlib.lines
|
|
import matplotlib.patches
|
|
import matplotlib.pyplot
|
|
import numpy
|
|
import os
|
|
import os.path
|
|
import pygame.image
|
|
import Queue
|
|
import cStringIO
|
|
import temp_log
|
|
import time
|
|
import threading
|
|
|
|
dataThread = None
|
|
dataThreadDataQueue = Queue.Queue()
|
|
dataThreadCommandQueue = Queue.Queue()
|
|
defaultSerialPort = '/dev/ttyACM0'
|
|
|
|
class MainApp(App):
|
|
|
|
def build(self):
|
|
mainWindow = MainWindow()
|
|
Clock.schedule_interval(mainWindow.update, 0.25)
|
|
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"""
|
|
dataSource = StringProperty(defaultSerialPort)
|
|
dataSources = ListProperty(temp_log.list_serial_ports())
|
|
lastTemperature = NumericProperty(-1000.)
|
|
lastTime = NumericProperty(-1.)
|
|
recordingState = StringProperty('')
|
|
state = StringProperty('running')
|
|
lastStatus = DictProperty({})
|
|
image_output_file = StringProperty('')
|
|
raw_data_output_file = StringProperty('')
|
|
reference_profile_file = StringProperty('')
|
|
|
|
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.modal_view = None
|
|
#self.ids['serial_chooser_dropdown'].bind(on_serial_port_change=self.serial_port_changed)
|
|
|
|
|
|
def update_last_temperature(self):
|
|
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:
|
|
Logger.debug('Data: ' + str(data))
|
|
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')
|
|
if dataThreadDataQueue.qsize():
|
|
Logger.debug('Queue Size: ' + str(dataThreadDataQueue.qsize()))
|
|
except Queue.Empty:
|
|
pass
|
|
|
|
|
|
def on_lastTemperature(self, instance, value):
|
|
Logger.debug('lastTemperature has changed to: ' + str(value))
|
|
|
|
|
|
def on_lastTime(self, instance, value):
|
|
Logger.debug('lastTime has changed to: ' + str(value))
|
|
|
|
|
|
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')
|
|
elif new_port in self.dataSources:
|
|
self.dataSource = new_port
|
|
else:
|
|
self.dataSource = ''
|
|
|
|
|
|
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()
|
|
|
|
|
|
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)
|
|
|
|
|
|
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.image_output_file = ''
|
|
self.raw_data_output_file = ''
|
|
self.remove_widget(self.modal_view)
|
|
self.modal_view = None
|
|
|
|
|
|
def choose_output_file(self, *args, **kwargs):
|
|
Logger.debug('Choose Output File pressed')
|
|
output_file_dialog = OutputFileDialog(cancel = self.dismiss_popup,
|
|
set_output_file = self.set_output_file)
|
|
self.create_popup(title="Choose outputfile", content = output_file_dialog)
|
|
|
|
|
|
def set_output_file(self, path, file_name, *args, **kwargs):
|
|
#Logger.debug('Setouput file: ', str((path, file_name, args, kwargs)))
|
|
# If we're oaky with writing path + file_name + (.csv|.png)
|
|
files = {
|
|
'image_file' : os.path.join(path, file_name + '.png'),
|
|
'raw_data_file' : os.path.join(path, file_name + '.csv'),
|
|
}
|
|
errors = {}
|
|
has_errors = False
|
|
for k, file_name in files.iteritems():
|
|
errors[k] = self.validate_output_file(file_name)
|
|
if errors[k]:
|
|
has_errors = True
|
|
if not has_errors:
|
|
Logger.debug('MainWindow: Set output files: ' + str(files))
|
|
self.raw_data_output_file = files['raw_data_file']
|
|
self.image_output_file = files['image_file']
|
|
self.dismiss_popup()
|
|
else:
|
|
# @TODO UI Feedback in the popup dialog
|
|
Logger.debug('MainWindow: Set output file errors: ' + str(errors))
|
|
|
|
|
|
def validate_output_file(self, file_name):
|
|
errors = []
|
|
# File doesn't exist, parent dir is writable
|
|
exists = os.path.exists(file_name)
|
|
# Note, the access check isn't for security, but usability here.
|
|
access = os.access(os.path.dirname(file_name), os.W_OK)
|
|
Logger.debug('MainWindow: validate_ouput_file file: ' + str(file_name))
|
|
Logger.debug('MainWindow: validate_output_file stat: ' + str(exists))
|
|
Logger.debug('MainWindow: validate_output_file parent dir write access: ' + str(access))
|
|
if exists:
|
|
errors.append('File already exists')
|
|
if not access:
|
|
errors.append('No write access to parent directory')
|
|
return errors
|
|
|
|
|
|
def create_popup(self, *args, **kwargs):
|
|
self._popup = Popup(*args, **kwargs)
|
|
self._popup.open()
|
|
|
|
|
|
def dismiss_popup(self, *args, **kwargs):
|
|
self._popup.dismiss()
|
|
|
|
|
|
def clear_output_files(self):
|
|
self.raw_data_output_file = ''
|
|
self.image_output_file = ''
|
|
|
|
|
|
def on_raw_data_output_file(self, *args, **kwargs):
|
|
if self.raw_data_output_file:
|
|
root = self.raw_data_output_file.rsplit('.', 1)
|
|
Logger.debug('MainWindow: ' + str(root))
|
|
self.ids.output_file_selected.text = root[0] + '[.csv|.png]'
|
|
else:
|
|
self.ids.output_file_selected.text = 'No file Chosen'
|
|
|
|
|
|
def select_reference_profile(self, *args, **kwargs):
|
|
dialog = SelectReferenceProfileDialog(output = self.set_reference_profile,
|
|
cancel = self.dismiss_popup)
|
|
self.create_popup(title='Reference Profile Selection',
|
|
content = dialog)
|
|
|
|
|
|
def set_reference_profile(self, *args, **kwargs):
|
|
Logger.debug('MainWindow: reference profile kwargs ' + str(kwargs))
|
|
ref_file = ''
|
|
if kwargs['file']:
|
|
ref_file = kwargs['file'][0]
|
|
if not os.path.isfile(ref_file):
|
|
# Not a valid path/file
|
|
return
|
|
self.reference_profile_file = ref_file
|
|
self.dismiss_popup()
|
|
|
|
|
|
def on_reference_profile_file(self, *kargs, **kwargs):
|
|
if not self.reference_profile_file:
|
|
self.ids.reference_profile_selected.text = 'No profile selected'
|
|
else:
|
|
self.ids.reference_profile_selected.text = self.reference_profile_file
|
|
|
|
|
|
class OutputFileDialog(FloatLayout):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(OutputFileDialog, self).__init__(*args, **kwargs)
|
|
self.cancel = kwargs['cancel']
|
|
self.set_output = kwargs['set_output_file']
|
|
|
|
|
|
def cancel(self, *args, **kwargs):
|
|
self.cancel(*args, **kwargs)
|
|
|
|
|
|
def set_output(self, *args, **kwargs):
|
|
self.set_output(*args, **kwargs)
|
|
|
|
|
|
class SelectReferenceProfileDialog(FloatLayout):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super(SelectReferenceProfileDialog, self).__init__(*args, **kwargs)
|
|
self.cancel = kwargs['cancel']
|
|
self.output = kwargs['output']
|
|
|
|
|
|
def cancel(self, *args, **kwargs):
|
|
self.cancel(*args, **kwargs)
|
|
|
|
|
|
def set_output(self, *args, **kwargs):
|
|
self.output(*args, **kwargs)
|
|
|
|
|
|
class SerialPortButton(Spinner):
|
|
|
|
__events__ = ('on_serial_port_changed',)
|
|
noValueText = StringProperty('No serial port selected')
|
|
|
|
def __init__(self, **kwargs):
|
|
super(SerialPortButton, self).__init__(**kwargs)
|
|
self.text = '/dev/ttyACM0'
|
|
self.register_event_type('on_serial_port_changed')
|
|
self.values = temp_log.list_serial_ports()
|
|
|
|
|
|
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
|
|
|
|
|
|
class MyLabel(Label):
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
super(MyLabel, self).__init__(**kwargs)
|
|
|
|
|
|
def on_temperature_change(self, value):
|
|
if not value:
|
|
value = 'n/a'
|
|
self.text = 'Current temperature: ' + str(value)
|
|
|
|
|
|
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))
|
|
|
|
|
|
class PlotWidget(Image):
|
|
|
|
image_output_file = StringProperty('')
|
|
raw_data_output_file = StringProperty('')
|
|
reference_profile_file = StringProperty('')
|
|
# @TODO Include a way to change these properties.
|
|
show_betaglucan = BooleanProperty(True)
|
|
show_protease = BooleanProperty(True)
|
|
show_betaamylase = BooleanProperty(True)
|
|
show_alphaamylase = BooleanProperty(True)
|
|
file_write_interval = NumericProperty(15)
|
|
image_update_interval = NumericProperty(5)
|
|
|
|
|
|
# Common data that we should only instantiate once
|
|
# Source : http://www.howtobrew.com/section3/chapter14-1.html
|
|
# accessed on march 21, 2015
|
|
# x_vertices = ((x, y), width, height, color, legend_string)
|
|
betaglucan_vertices = ((0, 35), 12000, 10, '#57ff14', 'Betaglucanase')
|
|
protease_vertices = ((0,45), 12000, 10, '#f3ff14', 'Protease / Peptidase')
|
|
betaamylase_vertices = ((0,55), 12000, 10.6, '#ffc014', 'Beta Amylase')
|
|
alphaamylase_vertices = ((0, 67.7), 12000, 4.5, '#ff6614', 'Alpha Amylase')
|
|
|
|
|
|
def __init__(self, **kwargs):
|
|
super(PlotWidget, self).__init__(**kwargs)
|
|
self.data = numpy.array([], dtype=float);
|
|
self.lastTime = 0
|
|
self.lastTemperature = 0
|
|
self.lastSave = 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')
|
|
self.image_data = None
|
|
self._image_raw_data = None
|
|
self.reference_profile = None
|
|
Clock.schedule_interval(self.do_update, self.image_update_interval)
|
|
self.do_update()
|
|
|
|
|
|
def on_image_update_interval(self, *args):
|
|
# Reschedule the update function.
|
|
Clock.unschedule(self.do_update, True)
|
|
Clock.schedule_interval(self.do_update, self.image_update_interval)
|
|
|
|
|
|
def update_patches(self, tmax = None):
|
|
items = [
|
|
(self.show_betaglucan, self.betaglucan_vertices),
|
|
(self.show_protease, self.protease_vertices),
|
|
(self.show_betaamylase, self.betaamylase_vertices),
|
|
(self.show_alphaamylase, self.alphaamylase_vertices)
|
|
]
|
|
legend_handles = []
|
|
legend_strings = []
|
|
for show, config in items:
|
|
color = '0.5' # default color.
|
|
if show:
|
|
origin, width, height, color, name = config
|
|
if tmax is not None:
|
|
width = tmax
|
|
p = self.plot_axes.add_patch(
|
|
matplotlib.patches.Rectangle(origin, width, height,
|
|
fc = color, visible = True,
|
|
fill = True)
|
|
)
|
|
legend_handles.append(p)
|
|
legend_strings.append(name)
|
|
# Disabled for the moment
|
|
self.figure.legend(legend_handles, legend_strings, 'lower right',
|
|
title = 'Enzyme Activity Zones')
|
|
return legend_handles
|
|
|
|
|
|
def clear_data(self):
|
|
self.update_output_files(True)
|
|
self.data = numpy.array([], dtype=float);
|
|
self.reference_profile_file = ''
|
|
self.reference_profile = None
|
|
self.texture = None
|
|
self.image_output_file = ''
|
|
self.raw_data_output_file = ''
|
|
self.do_update()
|
|
|
|
|
|
def do_update(self, *args):
|
|
image_data = self.to_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()
|
|
# Update output files, if any
|
|
# @TODO Add in an update interval so the disk isn't hammered on every single update
|
|
self.update_output_files()
|
|
|
|
|
|
def to_image_data(self):
|
|
# Add in polygons for hilightning particular temperature ranges of interest.
|
|
plot_args = []
|
|
t = None
|
|
time_max = 60
|
|
temp_max = 80
|
|
handles = []
|
|
if self.data.any():
|
|
#data = numpy.copy(self.data)
|
|
data = self.data.transpose()
|
|
t, temperature = numpy.split(data, 2, axis = 0)
|
|
if t[0][-1] > time_max:
|
|
time_max = t[0][-1]
|
|
#if numpy.nanmax(temperature[0]) > temp_max:
|
|
# temp_max = numpy.nanmax(temperature[0])
|
|
plot_args.extend((t[0], temperature[0], 'b'))
|
|
handles.append('Recorded Temperature Profile')
|
|
if self.reference_profile is not None and self.reference_profile.any():
|
|
#reference_data = numpy.copy(self.reference_profile)
|
|
reference_data = self.reference_profile.transpose()
|
|
t2, reference_temperature = numpy.split(reference_data, 2, axis = 0)
|
|
# Add t[0] to all reference points to make it fit on the current graph
|
|
t2 = numpy.add(t2, t[0][0])
|
|
if t2[0][-1] > time_max:
|
|
time_max = t2[0][-1]
|
|
#if numpy.nanmax(reference_temperature[0]) > temp_max:
|
|
# temp_max = numpy.nanmax(reference_temperature[0])
|
|
plot_args.extend((t2[0], reference_temperature[0], 'r'))
|
|
handles.append('Reference Temperature Profile')
|
|
lines = self.plot_axes.plot(*plot_args)
|
|
# Patches must be changed after the axes are plotted.
|
|
self.update_patches()
|
|
# Disabled the legend for the moment.
|
|
# @TODO Make a small subplot next to the main plot into which legends may be placed.
|
|
#self.plot_axes.legend(lines, handles, 'lower right', title = 'Temperature Profiles')
|
|
# Set a default in case no data is plotted. This may allow patches to show,
|
|
# and it will look a little nicer before recording starts.
|
|
#self.plot_axes.set_xlim(0, time_max)
|
|
self.plot_axes.set_ylim(0, temp_max)
|
|
image_data = cStringIO.StringIO()
|
|
self.figure.savefig(image_data, format = 'png')
|
|
image_data.seek(0)
|
|
self._image_raw_data = image_data.getvalue()
|
|
return image_data
|
|
|
|
|
|
def update_output_files(self, force = False):
|
|
# Do this only once every 15 seconds to avoid hitting the disk frequently
|
|
Logger.debug('update_output_files called')
|
|
if force or time.time() - self.lastSave > self.file_write_interval:
|
|
Logger.debug('update_output_files going ahead')
|
|
self.update_raw_data_output_file()
|
|
self.update_image_output_file()
|
|
self.lastSave = time.time()
|
|
|
|
|
|
def update_raw_data_output_file(self):
|
|
if self.raw_data_output_file and self.data.any():
|
|
numpy.savetxt(self.raw_data_output_file, self.data, delimiter=',')
|
|
|
|
|
|
def update_image_output_file(self, image_data = None):
|
|
if not image_data:
|
|
image_data = self._image_raw_data
|
|
if self.image_output_file and image_data:
|
|
f = open(self.image_output_file, 'w')
|
|
if f:
|
|
Logger.debug('Updated image output file')
|
|
f.write(image_data)
|
|
f.close()
|
|
else:
|
|
Logger.debug('update_image_output_file: Unable to open file ' + self.image_output_file)
|
|
|
|
|
|
def on_lastTemperature(self, value):
|
|
self.lastTemperature = value
|
|
|
|
|
|
def on_lastTime(self, value):
|
|
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))
|
|
|
|
|
|
def on_reference_profile_file(self, *args, **kwargs):
|
|
Logger.debug('PlotWidget: on_reference_profile_file ' + str(self.reference_profile_file))
|
|
self.load_reference_profile()
|
|
Clock.schedule_once(self.do_update)
|
|
|
|
|
|
def load_reference_profile(self):
|
|
if not self.reference_profile_file:
|
|
return
|
|
self.reference_profile = numpy.loadtxt(self.reference_profile_file,
|
|
delimiter=',')
|
|
Logger.debug('PlotWidget: reference_profile: ' + str(self.reference_profile))
|
|
|
|
|
|
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'
|
|
|
|
|
|
if __name__ == '__main__':
|
|
dataThread = threading.Thread(None, temp_log.threaded_reader, None, ['/dev/ttyACM0', dataThreadDataQueue, dataThreadCommandQueue])
|
|
MainApp().run()
|