From 7069a3ff81fa5d30238a617e7e987ca63a4a006a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Hru=C5=A1ka?= Date: Fri, 16 Feb 2018 20:34:52 +0100 Subject: [PATCH] added the ini editor --- gex/Client.py | 5 +- gex/transport.py | 1 + gexync.py | 119 +++++++++++++++++++++++++++++++++++++++++++++++ ini_syntax.py | 81 ++++++++++++++++++++++++++++++++ main.py | 4 +- 5 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 gexync.py create mode 100644 ini_syntax.py diff --git a/gex/Client.py b/gex/Client.py index 1b2d0b1..0797de5 100644 --- a/gex/Client.py +++ b/gex/Client.py @@ -14,7 +14,7 @@ class EventReport: class Client: """ GEX client """ - def __init__(self, transport): + def __init__(self, transport, load_units=True): """ Set up the client, looking up the GEX USB device using the S/N. You may need to configure the udev rule to have direct access. @@ -55,7 +55,8 @@ class Client: self.unit_lu = {} self.report_handlers = {} - self.load_units() + if load_units: + self.load_units() def close(self): self.transport.close() diff --git a/gex/transport.py b/gex/transport.py index d747d34..d40d893 100644 --- a/gex/transport.py +++ b/gex/transport.py @@ -184,6 +184,7 @@ class TrxRawUSB (BaseGexTransport): # Tell the thread to shut down self._ending = True self._thread.join() + usb.util.dispose_resources(self._dev) def write(self, buffer): """ Send a buffer of bytes """ diff --git a/gexync.py b/gexync.py new file mode 100644 index 0000000..3a82712 --- /dev/null +++ b/gexync.py @@ -0,0 +1,119 @@ +#!/bin/env python3 +import gex + +import sys +from PyQt4 import QtGui, QtCore +import ini_syntax + +class GexIniEditor(QtGui.QMainWindow): + """ + Gexync is a GEX ini file editor. + The editor loads the INI file through the communication interface + without having to mount the virtual filesystem, which is unreliable + on some systems. + + This utility allows live editing of the UNITS.INI file. + + On save, a new version is loaded with formatting and error messages + generated by GEX, as if the virtual config filesystem was re-mounted. + + The editor does not keep GEX claimed, instead does so only when needed. + This allows testing of the current configuration without having to close + and reopen the editor. + """ + + def __init__(self): + super().__init__() + self.initUI() + # TODO let user pick GEX device if multiple + + def initToolbar(self): + icon = self.style().standardIcon(QtGui.QStyle.SP_BrowserReload) + loadAction = QtGui.QAction(icon, 'Reload', self) + loadAction.setShortcut('Ctrl+O') + loadAction.triggered.connect(self.gexLoad) + + icon = self.style().standardIcon(QtGui.QStyle.SP_DialogSaveButton) + syncAction = QtGui.QAction(icon, 'Write Changes', self) + syncAction.setShortcut('Ctrl+S') + syncAction.triggered.connect(self.gexSync) + + icon = self.style().standardIcon(QtGui.QStyle.SP_DialogOkButton) + persAction = QtGui.QAction(icon, 'Persist, Close', self) + persAction.setShortcut('Ctrl+P') + persAction.triggered.connect(self.gexPersist) + + self.toolbar = self.addToolBar('Toolbar') + self.toolbar.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) + self.toolbar.addAction(loadAction) + self.toolbar.addAction(syncAction) + self.toolbar.addSeparator() + self.toolbar.addAction(persAction) + + def initEditor(self): + self.editor = QtGui.QPlainTextEdit() + + # Editor background and text color + pal = QtGui.QPalette() + bgc = QtGui.QColor(0xFFFFF6) + pal.setColor(QtGui.QPalette.Base, bgc) + textc = QtGui.QColor(0x000000) + pal.setColor(QtGui.QPalette.Text, textc) + self.editor.setPalette(pal) + # Font + font = QtGui.QFont('Liberation Mono', 12) + font.setStyleHint(QtGui.QFont.TypeWriter) + self.editor.setFont(font) + # Initial size + self.highlight = ini_syntax.IniHighlighter(self.editor.document()) + + def initUI(self): + self.setWindowTitle('GEX config file editor') + self.initToolbar() + self.initEditor() + self.setCentralWidget(self.editor) + self.show() + + self.gexLoad() + + def gexLoad(self): + self.editor.setPlainText("") + self.editor.repaint() + + client = gex.Client(gex.TrxRawUSB(), load_units=False) + read_ini = client.ini_read() + client.close() + + self.editor.setPlainText(read_ini) + self.highlight.rehighlight() + + def gexSync(self): + new_txt = self.editor.toPlainText() + self.editor.setPlainText("") + self.editor.repaint() + + client = gex.Client(gex.TrxRawUSB(), load_units=False) + client.ini_write(new_txt) + read_ini = client.ini_read() + client.close() + + self.editor.setPlainText(read_ini) + self.highlight.rehighlight() + + def gexPersist(self): + client = gex.Client(gex.TrxRawUSB(), load_units=False) + client.ini_persist() + client.close() + self.close() + +if __name__ == '__main__': + app = QtGui.QApplication(sys.argv) + editor = GexIniEditor() + + # centered resize + w = 800 + h = 900 + ss = app.desktop().availableGeometry().size() + editor.setGeometry(int(ss.width() / 2 - w / 2), int(ss.height() / 2 - h / 2), w, h) + + sys.exit(app.exec_()) diff --git a/ini_syntax.py b/ini_syntax.py new file mode 100644 index 0000000..4d3cc29 --- /dev/null +++ b/ini_syntax.py @@ -0,0 +1,81 @@ +# syntax.py + +# This is a companion file to gexync.py + +# based on https://wiki.python.org/moin/PyQt/Python%20syntax%20highlighting + +from PyQt4.QtCore import QRegExp +from PyQt4.QtGui import QColor, QTextCharFormat, QFont, QSyntaxHighlighter + +def format(color, style=''): + """Return a QTextCharFormat with the given attributes. + """ + _color = QColor() + _color.setNamedColor(color) + + _format = QTextCharFormat() + _format.setForeground(_color) + if 'bold' in style: + _format.setFontWeight(QFont.Bold) + if 'italic' in style: + _format.setFontItalic(True) + + return _format + + +# Syntax styles that can be shared by all languages +STYLES = { + 'operator': format('red'), + 'string': format('magenta'), + 'comment': format('#6C8A70', 'italic'), + 'key': format('#008AFF'), + 'numbers': format('brown'), + 'section': format('black', 'bold'), +} + +class IniHighlighter (QSyntaxHighlighter): + # Python braces + def __init__(self, document): + QSyntaxHighlighter.__init__(self, document) + + rules = [ + (r'=', 0, STYLES['operator']), + (r'\b[YN]\b', 0, STYLES['numbers']), + + # Double-quoted string, possibly containing escape sequences + (r'"[^"\\]*(\\.[^"\\]*)*"', 0, STYLES['string']), + # Single-quoted string, possibly containing escape sequences + (r"'[^'\\]*(\\.[^'\\]*)*'", 0, STYLES['string']), + + # Numeric literals + (r'\b[+-]?[0-9]+\b', 0, STYLES['numbers']), + (r'\b[+-]?0[xX][0-9A-Fa-f]+\b', 0, STYLES['numbers']), + (r'\b[+-]?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\b', 0, STYLES['numbers']), + + # From '#' until a newline + (r'#[^\n]*', 0, STYLES['comment']), + + (r'^\[.+\]', 0, STYLES['section']), + + (r'^[a-zA-Z0-9_-]+\s?=', 0, STYLES['key']), + ] + + # Build a QRegExp for each pattern + self.rules = [(QRegExp(pat), index, fmt) + for (pat, index, fmt) in rules] + + def highlightBlock(self, text): + """Apply syntax highlighting to the given block of text. + """ + # Do other syntax formatting + for expression, nth, format in self.rules: + index = expression.indexIn(text, 0) + + while index >= 0: + # We actually want the index of the nth match + index = expression.pos(nth) + length = len(expression.cap(nth)) + self.setFormat(index, length, format) + index = expression.indexIn(text, index + length) + + self.setCurrentBlockState(0) diff --git a/main.py b/main.py index 5063050..3aaa213 100644 --- a/main.py +++ b/main.py @@ -18,9 +18,7 @@ with gex.Client(transport) as client: if True: sipo = gex.SIPO(client, 'sipo') - while True: - sipo.load([[0xFF], [0xAA], [0x11], [0x00]]) - time.sleep(0.2) + sipo.load([[0xA5], [0xFF]]) if False: adc = gex.ADC(client, 'adc')