openmv/usr/qt4-ide.py
2014-10-27 15:56:57 -06:00

526 lines
17 KiB
Python
Executable File

#!/usr/bin/env python
from __future__ import print_function
from framebuffer import FrameBuffer
from editor import *
import pydfu
from terminal import *
from PyQt4.QtGui import *
from serial import *
from time import sleep
import sys
import os
import signal
import openmv
class OpenMVConnector(QObject):
found = pyqtSignal()
not_found = pyqtSignal()
def __init__(self):
QObject.__init__(self)
self.timer = QTimer()
self.timer.timeout.connect(self.auto_detect)
def start(self):
self.timer.start(500)
def stop(self):
self.timer.stop()
def auto_detect(self):
if openmv.find():
print('OpenMV Cam found')
self.found.emit()
else:
self.not_found.emit()
class OpenMVIDE(QMainWindow):
def __init__(self):
super(OpenMVIDE, self).__init__()
# Connector detects camera connect/disconnect
self.connector = OpenMVConnector()
self.connector.start()
# Catch connect / disconnect notifications
self.connector.found.connect(self.do_connect)
self.connector.not_found.connect(self.do_disconnect)
self.scale = 1
# default working directory
self.dir = os.path.dirname(os.path.realpath(__file__)) + '/'
# default icons dir
self.icon_dir = self.dir + 'icons/'
# default examples location
self.example_dir = self.dir + 'examples/'
# default scripts location
self.script_dir = self.dir + 'scripts/'
# Location for binary firmware
self.flash_dir = self.dir + 'bin/'
self.flash_file = ''
# script filename
self.filename = ''
# recent files
self.recent = set()
# default auto connect behavior
self.auto_connect = True
# default auto flash behavior
self.auto_flash = True
# Actions
self.exit_action = QAction(QIcon(self.icon_dir + 'Exit.png'), 'Exit', self)
self.exit_action.setStatusTip('Exit application')
self.exit_action.setShortcut('Ctrl+Q')
self.exit_action.triggered.connect(self.do_quit)
self.connect_action = QAction(QIcon(self.icon_dir + 'Connect.png'), 'Connect', self)
self.connect_action.setStatusTip('Connect to camera')
self.connect_action.triggered.connect(self.do_connect)
self.reset_action = QAction(QIcon(self.icon_dir + 'Eject.png'), 'Reset', self)
self.reset_action.setStatusTip('Reset camera and disconnect')
self.reset_action.triggered.connect(self.do_disconnect)
self.flash_action = QAction(QIcon(self.icon_dir + 'Lightning.png'), 'Flash', self)
self.flash_action.setStatusTip('Flash new firmware')
self.flash_action.triggered.connect(self.do_flash)
self.run_action = QAction(QIcon(self.icon_dir + 'Run.png'), 'Run', self)
self.run_action.setStatusTip('Run script')
self.run_action.triggered.connect(self.do_run)
self.stop_action = QAction(QIcon(self.icon_dir + 'Stop.png'), 'Stop', self)
self.stop_action.setStatusTip('Stop script')
self.stop_action.triggered.connect(self.do_stop)
self.new_action = QAction(QIcon(self.icon_dir + 'NewDocument.png'), 'New', self)
self.new_action.setStatusTip('New script')
self.new_action.triggered.connect(self.do_new)
self.open_action = QAction(QIcon(self.icon_dir + 'Open.png'), 'Open...', self)
self.open_action.setStatusTip('Open script')
self.open_action.setShortcut('Ctrl+O')
self.open_action.triggered.connect(self.do_open)
self.save_action = QAction(QIcon(self.icon_dir + 'Save.png'), 'Save', self)
self.save_action.setStatusTip('Save script')
self.save_action.setShortcut('Ctrl+S')
self.save_action.triggered.connect(self.do_save)
self.save_as_action = QAction(QIcon(self.icon_dir + 'SaveAs.png'), 'Save As...', self)
self.save_as_action.setStatusTip('Save script as new file')
self.save_as_action.triggered.connect(self.do_save_as)
self.zoom_in_action = QAction(QIcon(self.icon_dir + 'ZoomIn.png'), 'Zoom In', self)
self.zoom_in_action.setStatusTip('Make frame buffer preview bigger')
self.zoom_in_action.triggered.connect(self.do_zoom_in)
self.zoom_out_action = QAction(QIcon(self.icon_dir + 'ZoomOut.png'), 'Zoom Out', self)
self.zoom_out_action.setStatusTip('Make frame buffer preview smaller')
self.zoom_out_action.triggered.connect(self.do_zoom_out)
self.zoom_reset_action = QAction(QIcon(self.icon_dir + 'ZoomReset.png'), 'Zoom Reset', self)
self.zoom_reset_action.setStatusTip('Reset frame buffer preview size')
self.zoom_reset_action.triggered.connect(self.do_zoom_reset)
self.auto_flash_action = QAction('Auto-flash', self)
self.auto_flash_action.setStatusTip('Automatically detect DFU mode and flash firmware')
self.auto_flash_action.triggered.connect(self.do_auto_flash)
self.auto_connect_action = QAction('Auto-connect', self)
self.auto_connect_action.setStatusTip('Automatically connect to OpenMV when detected')
self.auto_connect_action.triggered.connect(self.do_auto_connect)
self.toolbar1 = self.addToolBar('toolbar1')
self.toolbar1.addAction(self.connect_action)
self.toolbar1.addAction(self.reset_action)
self.toolbar1.addAction(self.flash_action)
self.toolbar1.addAction(self.run_action)
self.toolbar1.addAction(self.stop_action)
self.toolbar2 = self.addToolBar('toolbar2')
self.toolbar2.addAction(self.new_action)
self.toolbar2.addAction(self.open_action)
self.toolbar2.addAction(self.save_action)
self.toolbar3 = self.addToolBar('toolbar3')
self.toolbar3.addAction(self.zoom_in_action)
self.toolbar3.addAction(self.zoom_reset_action)
self.toolbar3.addAction(self.zoom_out_action)
# Enable/disable icons
self.connected = False
self.running = False
self.statusBar()
# Frame buffer
self.image = None
# Menu Bar
menu_bar = self.menuBar()
file_menu = menu_bar.addMenu('&File')
file_menu.addAction(self.new_action)
file_menu.addAction(self.open_action)
file_menu.addAction(self.save_action)
file_menu.addAction(self.save_as_action)
file_menu.addSeparator()
self.example_menu = QMenu('Examples')
self.recent_menu = QMenu('Recent')
file_menu.addMenu(self.example_menu)
file_menu.addMenu(self.recent_menu)
file_menu.addSeparator()
file_menu.addAction(self.exit_action)
file_menu.aboutToShow.connect(self.update_example_menu)
file_menu.aboutToShow.connect(self.update_recent_menu)
self.default_height = 600
self.default_width = 800
# Geometry
self.setGeometry(50, 50, self.default_width, self.default_height)
# Editor
self.editor = PyEditor(self)
self.editor.setMinimumWidth(300)
self.editor.setMinimumHeight(200)
# FrameBuffer
self.framebuffer = FrameBuffer()
self.framebuffer.show()
self.framebuffer.error.connect(self.do_disconnect)
# Vertical box for framebuffer
pvbox = QVBoxLayout()
pvbox.addWidget(self.framebuffer)
pvbox.addStretch(1)
# Horizontal box for editor + framebuffer
hbox = QHBoxLayout()
hbox.addWidget(self.editor)
hbox.addLayout(pvbox)
self.terminal = SerialTerminal(self)
self.terminal.setMinimumHeight(100)
self.terminal.setMaximumHeight(200)
self.terminal.show()
self.serial = Serial()
self.terminal.error.connect(self.do_disconnect)
# Vertical box for hbox + terminal
vbox = QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(self.terminal)
w = QWidget()
w.setLayout(vbox)
self.setCentralWidget(w)
# UI Statuses
self.update_ui()
self.show()
def update_ui(self):
## TODO: enable/disable save icon when file is changed
self.run_action.setEnabled(self.connected)
self.stop_action.setEnabled(self.connected and self.running)
self.connect_action.setEnabled(not self.connected)
self.reset_action.setEnabled(self.connected)
self.flash_action.setEnabled(self.connected)
self.zoom_in_action.setEnabled(self.connected)
self.zoom_out_action.setEnabled(self.connected)
self.zoom_reset_action.setEnabled(self.connected)
if self.connected:
con = '[connected]'
else:
con = '[disconnected]'
if self.filename:
fn = os.path.basename(self.filename)
else:
fn = 'untitled.py'
self.setWindowTitle('OpenMV IDE (' + fn + ') - ' + con)
def do_auto_connect(self):
self.auto_connect = not self.auto_connect
def do_connect(self):
if not self.connected:
self.connected = True
try:
# init OpenMV
openmv.init()
sleep(0.2)
# interrupt any running code
openmv.stop_script()
sleep(0.2)
## TODO: device config
self.serial = Serial('/dev/openmvcam', 115200, timeout=1)
self.terminal.start(self.serial)
except Exception as e:
print('error connecting OpenMV Cam: %s' % e)
self.connected = False
else:
self.update_ui()
self.statusBar().showMessage('OpenMV Cam connected.')
self.framebuffer.start_updater()
def do_disconnect(self):
if self.connected:
self.connected = False
self.update_ui()
self.framebuffer.stop_updater()
openmv.stop_script()
sleep(0.2)
# release OpenMV
openmv.release()
sleep(0.2)
openmv.reset()
sleep(0.2)
self.connector.start()
self.statusBar().showMessage('Camera disconnected.')
try:
if self.serial and self.serial.isOpen():
print('Disconnecting terminal')
self.serial.close()
self.terminal.reset()
except IOError as e:
print('error disconnecting OpenMV Serial: %s' % e)
def do_auto_flash(self):
self.auto_flash = not self.auto_flash
def do_flash(self):
self.update_ui()
if self.connected:
dialog = QDialog(self)
filename = QLineEdit()
chooser = QFileDialog()
chooser.setDirectory(self.flash_dir)
browse = QPushButton('&...')
browse.pressed.connect(chooser.exec_)
chooser.fileSelected.connect(filename.setText)
hlayout1 = QHBoxLayout()
hlayout1.addWidget(filename, stretch=1)
hlayout1.addWidget(browse)
ok_button = QPushButton('&Ok')
cancel_button = QPushButton('&Cancel')
hlayout2 = QHBoxLayout()
hlayout2.addWidget(ok_button)
hlayout2.addWidget(cancel_button)
vlayout = QVBoxLayout()
vlayout.addLayout(hlayout1)
vlayout.addLayout(hlayout2)
dialog.setLayout(vlayout)
ok_button.pressed.connect(dialog.accept)
cancel_button.pressed.connect(dialog.reject)
dialog.exec_()
dialog.hide()
if dialog.result() == QDialog.Accepted:
print('Accepted')
self.flash_file = filename.text()
with open(self.flash_file, 'r') as f:
buf = f.read()
self.flash_update(buf)
else:
print('Rejected')
def flash_update(self, buf):
flash_offsets = [0x08000000, 0x08004000, 0x08008000, 0x0800C000,
0x08010000, 0x08020000, 0x08040000, 0x08060000,
0x08080000, 0x080A0000, 0x080C0000, 0x080E0000]
# Erasing
total = len(flash_offsets)
progress = QProgressDialog(self)
progress.setRange(0, total)
progress.setValue(0)
progress.setWindowTitle('Initialize...')
print('Initialize...')
progress.setAutoReset(False)
progress.show()
# call dfu-util
openmv.enter_dfu()
sleep(1.0)
# Initialize
pydfu.init()
progress.setWindowTitle('Erasing...')
print('Erasing...')
progress.setValue(0)
pg = 0
for offset in flash_offsets:
progress.setValue(pg)
pydfu.page_erase(flash_offsets[pg])
print(pg)
pg += 1
offset = 0
size = len(buf)
progress.setRange(0, size)
progress.setWindowTitle('Downloading...')
print('Downloading...')
while offset < size:
progress.setValue(offset)
pg_size = min(64, size - offset)
page = buf[offset:offset + pg_size]
#print pg_size, offset, offset + pg_size, size - offset, size
pydfu.write_page(page, offset)
offset += pg_size
progress.hide()
# this isn't working...
openmv.reset()
def do_run(self):
buf = str(self.editor.toPlainText())
# interrupt any running code
openmv.stop_script()
sleep(0.1)
# exec script
openmv.exec_script(buf)
self.running = True
self.update_ui()
def do_stop(self):
openmv.stop_script()
self.running = False
self.update_ui()
def do_new(self):
# TODO: Check for save-as first
self.editor.setPlainText('')
self.filename = ''
self.update_ui()
def update_example_menu(self):
if os.path.isdir(self.example_dir):
self.example_menu.clear()
files = sorted(os.listdir(self.example_dir))
for f in files:
if f.endswith(".py"):
self.example_menu.addAction(QAction(f, self))
#label = os.path.basename(f)
def update_recent_menu(self):
print('recent')
self.recent_menu.clear()
for f in self.recent:
print(f)
self.recent_menu.addAction(QAction(f, self))
def do_open(self):
# TODO: Check for save-as first
if os.path.exists(self.script_dir):
my_dir = self.script_dir
else:
my_dir = self.example_dir
filename = QFileDialog.getOpenFileName(parent=self,
caption=self.tr('Open Micro Python Script'),
directory=my_dir,
filter=self.tr("Python scripts (*.py)"))
if filename:
try:
infile = open(filename, 'r')
except (IOError, OSError) as e:
QErrorMessage(self).showMessage('Error opening file: ' + e)
else:
self.editor.setPlainText(infile.read())
# store new filename
self.filename = str(filename)
self.update_ui()
self.recent.add(self.filename)
def do_save_as(self):
self.do_save(True)
def do_save(self, save_as=False):
if save_as:
filename = ''
else:
filename = self.filename
# filename will be empty if we haven't saved the document yet
if not filename:
filename = QFileDialog.getSaveFileName(parent=self,
caption=self.tr('Save Micro Python Script'),
directory=self.script_dir,
filter=self.tr("Python scripts (*.py)"))
if filename:
try:
outfile = open(filename, 'w')
outfile.write(self.editor.toPlainText())
# store new filename
self.filename = str(filename)
self.update_ui()
except (IOError, OSError) as e:
QErrorMessage(self).showMessage('Error saving file: ' + e)
def do_quit(self):
# TODO: check for file save status
if self.connected:
self.do_disconnect()
self.framebuffer.quit()
QApplication.quit()
def do_zoom_in(self):
self.framebuffer.increase_scale(0.5)
def do_zoom_out(self):
self.framebuffer.decrease_scale(0.5)
def do_zoom_reset(self):
self.framebuffer.reset_scale()
def interrupt_handler(self, signum, frame):
print('CTRL-C caught')
def main():
app = QApplication(sys.argv)
ide = OpenMVIDE()
signal.signal(signal.SIGINT, ide.interrupt_handler)
sys.exit(app.exec_())
## TODO: Take script as argument and run straightaway
if __name__ == '__main__':
main()