Changed SerialChooserButton to a spinner. Added status bar, and fixed clear data popup

This commit is contained in:
Kienan Stewart 2014-12-13 16:44:05 -05:00
parent f5ba978e64
commit eb565fe797
2 changed files with 204 additions and 61 deletions

102
main.kv
View File

@ -8,36 +8,90 @@
#<SerialPortButton@Button>:
<StatusWidget@BoxLayout>:
orientation: 'horizontal'
<YesNoModalView@Popup>:
id: clear_modal_view
process_callback:
title: 'Clear graph?'
BoxLayout:
orientation: 'vertical'
Label:
id: default_content
text: 'Continuing will clear the graph and turn off logging to file.'
BoxLayout:
orientation: 'horizontal'
Button:
id: cancel_button
text: 'Cancel'
on_press: clear_modal_view.process(0)
Button:
id: confirm_button
text: 'Delete data'
on_press: clear_modal_view.process(1)
<StatusBar@BoxLayout>:
orientation: 'horizontal'
pos_hint: { 'x' : 0.01 }
Image:
id: status_image
size_hint_x: 0.05
source: './images/ok.png'
Label:
id: status_text
size_hint_x: 0.1
Label:
id: status_message
size_hint_x: 0.80
<MainWindow>:
label_wid: "Temperature Monitor"
current_temperature: current_temperature
serial_chooser_button: serial_chooser_button
on_dataSources: serial_chooser_dropdown.on_dataSources(self.dataSources)
on_dataSources: serial_chooser_button.values = self.dataSources
on_lastTemperature: current_temperature.on_temperature_change(self.lastTemperature)
on_lastTemperature: mainplot.on_lastTemperature(self.lastTemperature)
on_lastTime: mainplot.on_lastTime(self.lastTime)
on_lastStatus: status_bar.update_status(self.lastStatus)
BoxLayout:
id: 'top_menu'
size_hint_y: 0.1
pos_hint: {'top':1}
orientation: 'horizontal'
MyLabel:
id: current_temperature
Button:
text: 'Start/Stop'
Label:
text: 'No File Chosen'
SerialPortButton:
id: serial_chooser_button
dropdown: serial_chooser_dropdown.__self__
on_release: serial_chooser_dropdown.open(self)
SerialPortDropdown:
id: serial_chooser_dropdown
data:
on_serial_port_changed: serial_chooser_button.serial_port_selected(self.data)
on_serial_port_changed: root.serial_port_changed(self.data)
PlotWidget:
size_hint_y: 0.9
id: mainplot
orientation: 'vertical'
BoxLayout:
size_hint_y: 0.1
id: 'top_menu'
pos_hint: {'top':1}
orientation: 'horizontal'
MyLabel:
id: current_temperature
StatusWidget:
id: startstop_widget
state: 'running'
on_state: setattr(startstop_button, 'text', self.change_state_labels[self.state])
on_state: root.set_state(self.state)
Button:
id: startstop_button
text: 'Pause'
on_press: startstop_widget.change_state()
Button:
id: clear_button
text: 'Clear'
on_press: root.initiate_clear_dialog()
Label:
text: 'No File Chosen'
SerialPortButton:
id: serial_chooser_button
on_serial_port_changed: root.serial_port_changed(self.text)
#dropdown: serial_chooser_dropdown.__self__
#on_release: serial_chooser_dropdown.open(self)
#SerialPortDropdown:
# id: serial_chooser_dropdown
# data:
# on_serial_port_changed: serial_chooser_button.serial_port_selected(self.data)
# on_serial_port_changed: root.serial_port_changed(self.data)
PlotWidget:
id: mainplot
size_hint_y: 0.8
StatusBar:
size_hint_y: 0.1
id: status_bar
MainWindow:

163
main.py
View File

@ -8,7 +8,7 @@ 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 ListProperty, NumericProperty, ObjectProperty, StringProperty
from kivy.properties import DictProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
@ -16,6 +16,9 @@ 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 numpy
import matplotlib
@ -36,22 +39,7 @@ defaultSerialPort = '/dev/ttyACM0'
class MainApp(App):
def build(self):
#root = kivy.lang.Builder.load_file('./main.kv')
#Logger.debug('root from super: ' + str(root))
#Logger.debug(str(root.ids))
#children = root.ids
mainWindow = MainWindow()
#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)
return mainWindow
@ -68,18 +56,20 @@ class MainApp(App):
class MainWindow(FloatLayout):
"""Main Window class"""
# @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())
lastTemperature = NumericProperty(-1000.)
lastTime = NumericProperty(-1.)
recordingState = StringProperty('')
state = StringProperty('running')
lastStatus = DictProperty({})
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)
self.modal_view = None
#self.ids['serial_chooser_dropdown'].bind(on_serial_port_change=self.serial_port_changed)
def update_last_temperature(self):
@ -89,10 +79,17 @@ class MainWindow(FloatLayout):
data = dataThreadDataQueue.get_nowait()
if data is not None:
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'])
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')
except Queue.Empty:
pass
@ -108,7 +105,7 @@ class MainWindow(FloatLayout):
def update(self, dt):
self.update_data_sources()
self.update_last_temperature()
Logger.debug('MainWindow: dataThread ' + str(dataThread.name) + ' status: ' + str(dataThread.is_alive()))
#Logger.debug('MainWindow: dataThread ' + str(dataThread.name) + ' status: ' + str(dataThread.is_alive()))
def update_data_sources(self):
@ -127,8 +124,10 @@ class MainWindow(FloatLayout):
new_port = args[0]
if new_port == self.dataSource:
Logger.debug('MainWindow: Selected port matches current port, no change')
else:
elif new_port in self.dataSources:
self.dataSource = new_port
else:
self.dataSource = ''
def on_dataSource(self, instance, value):
@ -148,33 +147,63 @@ class MainWindow(FloatLayout):
dataThread.start()
class SerialPortButton(Button):
def set_state(self, new_state):
self.state = new_state
currentSerialPort = StringProperty('')
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.remove_widget(self.modal_view)
self.modal_view = None
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 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!'
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 SerialPortDropdown(DropDown):
__events__ = ('on_serial_port_changed',)
defaultSerialPort = '/dev/ttyACM0'
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()))
#Logger.debug(str(self.get_root_window()))
self.on_dataSources(temp_log.list_serial_ports())
@ -188,10 +217,10 @@ class SerialPortDropdown(DropDown):
text = getattr(child, 'text', None)
if text and text in values:
values_used.append(text)
Logger.debug('Child ' + str(child) + ' is used')
#Logger.debug('Child ' + str(child) + ' is used')
continue
if text and text not in values:
Logger.debug('Child ' + str(child) + ' is now unused, to be removed')
#Logger.debug('Child ' + str(child) + ' is now unused, to be removed')
self.remove_widget(child)
self.data = ''
self.dispatch('on_serial_port_changed', '')
@ -199,7 +228,7 @@ class SerialPortDropdown(DropDown):
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)
#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)
@ -228,6 +257,26 @@ class MyLabel(Label):
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):
def __init__(self, **kwargs):
@ -245,6 +294,11 @@ class PlotWidget(Image):
self._image_raw_data = None
def clear_data(self):
self.data = numpy.array([], dtype=float);
self.texture = None
def do_update(self):
if not self.data.any():
return
@ -302,6 +356,41 @@ class PlotWidget(Image):
self.do_update()
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()