Changed SerialChooserButton to a spinner. Added status bar, and fixed clear data popup
This commit is contained in:
parent
f5ba978e64
commit
eb565fe797
102
main.kv
102
main.kv
|
@ -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
163
main.py
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue