GTK+ GUI for configuring PD Buddy Sink devices
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

pd-buddy-gtk.py 7.2KB

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