GTK+ GUI for configuring PD Buddy Sink devices
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

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)