GTK+ GUI for configuring PD Buddy Sink devices
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

pd-buddy-gtk.py 7.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. #!/usr/bin/env python3
  2. import sys
  3. import serial
  4. import serial.tools.list_ports
  5. import gi
  6. gi.require_version('Gtk', '3.0')
  7. from gi.repository import Gtk, Gio, GObject, GLib
  8. def pdb_send_message(sp, message):
  9. """Send a message over the serial port and return the response"""
  10. # Open the serial port
  11. # FIXME handle exceptions
  12. sp = serial.Serial(sp.device, baudrate=115200, timeout=0.01)
  13. sp.write(bytes(message, 'utf-8') + b'\r\n')
  14. sp.flush()
  15. answer = sp.readlines()
  16. sp.close()
  17. # Remove the echoed command and prompt
  18. answer = answer[1:-1]
  19. return answer
  20. class ListRowModel(GObject.GObject):
  21. def __init__(self, serport):
  22. GObject.GObject.__init__(self)
  23. self.serport = serport
  24. class SelectListStore(Gio.ListStore):
  25. def update_items(self):
  26. # Get a list of serial ports
  27. serports = list(serial.tools.list_ports.grep("1209:0001"))
  28. # Mark ports to remove or add
  29. remove_list = []
  30. list_len = self.get_n_items()
  31. for i in range(list_len):
  32. remove = True
  33. for j in range(len(serports)):
  34. if serports[j] is not None and self.get_item(i).serport == serports[j]:
  35. serports[j] = None
  36. remove = False
  37. if remove:
  38. remove_list.append(i)
  39. # Remove the missing ones
  40. for i in remove_list:
  41. self.remove(i)
  42. # Add any new ports
  43. for port in serports:
  44. if port is not None:
  45. self.append(ListRowModel(port))
  46. class SelectList(Gtk.Box):
  47. __gsignals__ = {
  48. 'row-activated': (GObject.SIGNAL_RUN_FIRST, None,
  49. (object,))
  50. }
  51. def __init__(self):
  52. Gtk.Box.__init__(self)
  53. self._model = None
  54. self._builder = Gtk.Builder()
  55. self._builder.add_from_file("data/select-stack.ui")
  56. self._builder.connect_signals(self)
  57. sl = self._builder.get_object("select-list")
  58. # Add separators to the list
  59. sl.set_header_func(self._update_header_func, None)
  60. self.pack_start(self._builder.get_object("select-stack"), True, True, 0)
  61. self.show_all()
  62. def _update_header_func(self, row, before, data):
  63. """Add a separator header to all rows but the first one"""
  64. if before is None:
  65. row.set_header(None)
  66. return
  67. current = row.get_header()
  68. if current is None:
  69. current = Gtk.Separator.new(Gtk.Orientation.HORIZONTAL)
  70. row.set_header(current)
  71. def bind_model(self, model, func):
  72. self._builder.get_object("select-list").bind_model(model, func)
  73. self._model = model
  74. self._reload()
  75. GLib.timeout_add(1000, self._reload)
  76. def _reload(self):
  77. self._model.update_items()
  78. # Set the visible child
  79. stack = self._builder.get_object("select-stack")
  80. if self._model.get_n_items():
  81. stack.set_visible_child(self._builder.get_object("select-frame"))
  82. else:
  83. stack.set_visible_child(self._builder.get_object("select-none"))
  84. return True
  85. def on_select_list_row_activated(self, box, row):
  86. self.emit("row-activated", row.model.serport)
  87. class SelectListRow(Gtk.ListBoxRow):
  88. def __init__(self, model):
  89. Gtk.EventBox.__init__(self)
  90. self.model = model
  91. self._builder = Gtk.Builder()
  92. self._builder.add_from_file("data/select-list-row.ui")
  93. self._builder.connect_signals(self)
  94. name = self._builder.get_object("name")
  95. name.set_text(self.model.serport.description)
  96. device = self._builder.get_object("device")
  97. device.set_text(self.model.serport.device)
  98. self.add(self._builder.get_object("grid"))
  99. self.show_all()
  100. def on_identify_clicked(self, button):
  101. pdb_send_message(self.model.serport, 'identify')
  102. class Handler:
  103. def __init__(self, builder):
  104. self.builder = builder
  105. def on_pdb_window_realize(self, *args):
  106. # Get the list
  107. sb = self.builder.get_object("select-box")
  108. sl = SelectList()
  109. sb.pack_start(sl, True, True, 0)
  110. liststore = SelectListStore()
  111. sl.bind_model(liststore, SelectListRow)
  112. sl.connect("row-activated", self.on_select_list_row_activated)
  113. def on_pdb_window_delete_event(self, *args):
  114. Gtk.main_quit(*args)
  115. def on_select_list_row_activated(self, selectlist, serport):
  116. # Get voltage and current widgets
  117. voltage = self.builder.get_object("voltage-combobox")
  118. current = self.builder.get_object("current-spinbutton")
  119. self.serial_port = serport
  120. pdb_send_message(self.serial_port, 'load')
  121. tmpcfg = pdb_send_message(self.serial_port, 'get_tmpcfg')
  122. # Get information
  123. for line in tmpcfg:
  124. if line.startswith(b'v:'):
  125. v = line.split()[1]
  126. if v == b'5.00':
  127. voltage.set_active_id('voltage-five')
  128. elif v == b'9.00':
  129. voltage.set_active_id('voltage-nine')
  130. elif v == b'15.00':
  131. voltage.set_active_id('voltage-fifteen')
  132. if v == b'20.00':
  133. voltage.set_active_id('voltage-twenty')
  134. elif line.startswith(b'i:'):
  135. i = float(line.split()[1])
  136. current.set_value(i)
  137. # Hide the Save button
  138. rev = self.builder.get_object("header-sink-save-revealer")
  139. rev.set_reveal_child(False)
  140. # Show the Sink page
  141. hst = self.builder.get_object("header-stack")
  142. hsink = self.builder.get_object("header-sink")
  143. hsink.set_subtitle(serport.device)
  144. hst.set_visible_child(hsink)
  145. st = self.builder.get_object("stack")
  146. sink = self.builder.get_object("sink")
  147. st.set_visible_child(sink)
  148. def on_header_sink_back_clicked(self, data):
  149. # Show the Select page
  150. hst = self.builder.get_object("header-stack")
  151. hselect = self.builder.get_object("header-select")
  152. hst.set_visible_child(hselect)
  153. st = self.builder.get_object("stack")
  154. select = self.builder.get_object("select")
  155. st.set_visible_child(select)
  156. def on_header_sink_save_clicked(self, button):
  157. rev = self.builder.get_object("header-sink-save-revealer")
  158. rev.set_reveal_child(False)
  159. pdb_send_message(self.serial_port, 'write')
  160. def on_voltage_combobox_changed(self, combo):
  161. rev = self.builder.get_object("header-sink-save-revealer")
  162. rev.set_reveal_child(True)
  163. pdb_send_message(self.serial_port,
  164. 'set_v {}'.format(int(combo.get_active_text())*1000))
  165. def on_current_spinbutton_changed(self, spin):
  166. rev = self.builder.get_object("header-sink-save-revealer")
  167. rev.set_reveal_child(True)
  168. pdb_send_message(self.serial_port,
  169. 'set_i {}'.format(int(spin.get_value()*1000)))
  170. class Application(Gtk.Application):
  171. def __init__(self, *args, **kwargs):
  172. super().__init__(*args, application_id="com.clayhobbs.pd-buddy-gtk",
  173. **kwargs)
  174. self.window = None
  175. def do_startup(self):
  176. Gtk.Application.do_startup(self)
  177. self.builder = Gtk.Builder()
  178. self.builder.add_from_file("data/pd-buddy-gtk.ui")
  179. self.builder.connect_signals(Handler(self.builder))
  180. def do_activate(self):
  181. # We only allow a single window and raise any existing ones
  182. if not self.window:
  183. # Windows are associated with the application
  184. # when the last one is closed the application shuts down
  185. self.window = self.builder.get_object("pdb-window")
  186. self.add_window(self.window)
  187. self.window.set_wmclass("PD Buddy Configuration",
  188. "PD Buddy Configuration")
  189. self.window.present()
  190. if __name__ == "__main__":
  191. app = Application()
  192. app.run(sys.argv)