From MdsWiki
This script assumes that the appropriate jServers are already running. It selects a random port number (so that several experiments can run at the same time without interfering with each other) and starts a new jDispatcherIp and jDispatchMonitor instance. It then displays a GUI that allows to either cycle through all the experimental phases, or to dispatch them individually.
#!/usr/bin/env python from __future__ import print_function, absolute_import import subprocess import time import pygtk pygtk.require('2.0') import gtk import sys import atexit import tempfile import random import MDSplus import shutil import os class CenteredHBox(gtk.HBox): def __init__(self, spacing): super(CenteredHBox, self).__init__(False, spacing) super(CenteredHBox, self).pack_start(gtk.Label(''), True, True) super(CenteredHBox, self).pack_end(gtk.Label(''), True, True) def pack_start(self, obj): super(CenteredHBox, self).pack_start(obj, False) def pack_end(self, obj): super(CenteredHBox, self).pack_end(obj, False) class View(gtk.Window): def __init__(self, ctrl): super(View, self).__init__(gtk.WINDOW_TOPLEVEL) self.connect("delete_event", lambda *a: False) self.connect("destroy", gtk.main_quit) self.set_border_width(10) self.set_title('MDSplus Dispatch Control') vbox = gtk.VBox(False, 10) vbox.pack_start(self.make_shotno_box(ctrl), False) vbox.pack_start(self.make_dispatch_box(ctrl), False) vbox.pack_start(self.make_comment_box(ctrl), True, True) vbox.pack_start(self.make_other_box(ctrl), False) self.add(vbox) self.show_all() ctrl.view = self def make_shotno_box(self, ctrl): box = CenteredHBox(10) label = gtk.Label("Shot Number: ") box.pack_start(label) button = gtk.SpinButton(adjustment=ctrl.shotno, digits=0) button.set_numeric(True) box.pack_start(button) return box def make_phase_box(self, ctrl): hbox = gtk.HBox(True, 10) button = gtk.Button("BUILD") button.connect('clicked', lambda *a: ctrl.build()) hbox.pack_start(button, False) def wrap_init(*a): try: ctrl.init() except AbortCycle: pass button = gtk.Button("INIT") button.connect('clicked', wrap_init) hbox.pack_start(button, False) button = gtk.Button("PULSE ON") button.connect('clicked', lambda *a: ctrl.pulse_on()) hbox.pack_start(button, False) button = gtk.Button("STORE") button.connect('clicked', lambda *a: ctrl.store()) hbox.pack_start(button, False) button = gtk.Button("ANALYSIS") button.connect('clicked', lambda *a: ctrl.analysis()) hbox.pack_start(button, False) return hbox def make_cycle_box(self, ctrl): button = gtk.Button("Full Shot Cycle") button.connect('clicked', lambda *a: ctrl.cycle()) return button def make_dispatch_box(self, ctrl): vbox = gtk.VBox(True, 0) vbox.pack_start(self.make_cycle_box(ctrl), True, True) label = gtk.Label('- or -') vbox.pack_start(label, True, True) vbox.pack_start(self.make_phase_box(ctrl), True, True) outer = CenteredHBox(0) outer.pack_start(vbox) return outer def make_comment_box(self, ctrl): textview = gtk.TextView(ctrl.comment_buffer) textview.set_editable(True) textview.set_wrap_mode(gtk.WRAP_WORD) sw = gtk.ScrolledWindow() sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) sw.set_border_width(5) sw.add(textview) frame = gtk.Frame('Shot Comment') frame.add(sw) return frame def make_other_box(self, ctrl): hbox = gtk.HBox(True, 10) button = gtk.Button("Delete Shot") button.connect('clicked', lambda *a: ctrl.delete_shot()) hbox.pack_start(button, False) button = gtk.Button("Update Comment") button.connect('clicked', lambda *a: ctrl.update_comment()) hbox.pack_start(button, False) outer = CenteredHBox(0) outer.pack_start(hbox) return outer def yes_or_cancel_dialog(self, title, text): dialog = gtk.Dialog(title, self, gtk.DIALOG_MODAL, (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) label = gtk.Label(text) label.show() dialog.vbox.pack_start(label) response = dialog.run() dialog.destroy() self.process_pending_events() return response == gtk.RESPONSE_ACCEPT def ok_dialog(self, title, text): dialog = gtk.Dialog(title, self, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) label = gtk.Label(text) label.show() dialog.vbox.pack_start(label) response = dialog.run() dialog.destroy() self.process_pending_events() def warn_dialog(self, title, text): dialog = gtk.Dialog(title, self, gtk.DIALOG_MODAL, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT)) label = gtk.Label(text) label.show() dialog.vbox.pack_start(label) response = dialog.run() dialog.destroy() self.process_pending_events() def process_pending_events(self): while gtk.events_pending(): gtk.main_iteration () class Controller(object): def __init__(self, tree): super(Controller, self).__init__() self.tree = tree tempdir = tempfile.mkdtemp() atexit.register(shutil.rmtree, tempdir) # Port range is protected by UFW and must be kept # in sync with UFW rules self.base_port = random.randint(100, 600) with open(os.path.join(tempdir, 'jDispatcher.properties'), 'w') as fh: fh.write(dispatcher_properties % {'jDispatcher.port': 8001+self.base_port, 'jDispatcher.monitor_1.port': 8010+self.base_port, 'jDispatcher.info_port': 8011 +self.base_port}) logger = subprocess.Popen(['logger', '-t', 'jDispatcher', '-p', 'local0.info'], stdin=subprocess.PIPE) self.jdispatcher = subprocess.Popen(['jDispatcherIp', tree], stderr=subprocess.STDOUT, cwd=tempdir, stdout=logger.stdin) atexit.register(self.jdispatcher.terminate) time.sleep(3) null = open('/dev/null', 'r+b') self.jmonitor = subprocess.Popen(['jDispatchMonitor', 'localhost:%d' % (8010 + self.base_port)], cwd=tempdir, stdout=null, stderr=null) atexit.register(self.jmonitor.terminate) cur_shot = MDSplus.Tree.getCurrent(tree) self.shotno = gtk.Adjustment(value=cur_shot+1, lower=1, upper=cur_shot+1, step_incr=1) self.comment_buffer = gtk.TextBuffer() self.view = None def eval_tcl(self, cmd): MDSplus.Data.execute('tcl($)', cmd) def dispatch_cmd(self, cmd): self.eval_tcl('dispatch/command/server=localhost:%d %s\n' % (8001+self.base_port, cmd)) def shot_exists(self, shotno): try: tree = MDSplus.Tree(self.tree, shotno) except MDSplus._treeshr.TreeException as exc: if exc.args[0] == '%TREE-E-TreeFILE_NOT_FOUND, File or Directory Not Found': return False else: raise else: return True def delete_shot(self): this_shot = int(self.shotno.get_value()) if not self.shot_exists(this_shot): self.view.warn_dialog('Warning', 'Shot does not exist') return cur_shot = MDSplus.Tree.getCurrent(self.tree) tree = MDSplus.Tree(self.tree, -1) tree.deletePulse(this_shot) if this_shot == cur_shot: i = 1 while not self.shot_exists(cur_shot - i): i += 1 MDSplus.Tree.setCurrent(self.tree, cur_shot - i) self.shotno.set_upper(cur_shot - i + 1) self.view.ok_dialog('Info', 'Deleted shot %d' % this_shot) def build(self): self.dispatch_cmd('set tree %s' % self.tree) self.dispatch_cmd('dispatch /build') def init(self): this_shot = int(self.shotno.get_value()) if self.shot_exists(this_shot): self.view.warn_dialog('Warning', 'Shot already exists, aborting.') raise AbortCycle() self.dispatch_cmd('set tree %s' % self.tree) self.dispatch_cmd('create pulse %d' % this_shot) self.dispatch_cmd('dispatch /build') self.dispatch_cmd('dispatch /phase INIT') cur_shot = MDSplus.Tree.getCurrent(self.tree) if this_shot >= cur_shot: MDSplus.Tree.setCurrent(self.tree, this_shot) self.shotno.set_upper(this_shot+1) def pulse_on(self): self.dispatch_cmd('dispatch /phase PULSE_ON') def store(self): self.dispatch_cmd('dispatch /phase STORE') self.update_comment() def update_comment(self): tree = MDSplus.Tree(self.tree, int(self.shotno.get_value())) cmt = tree.getNode('comment') comment = self.comment_buffer.get_text(*self.comment_buffer.get_bounds()) cmt.record = MDSplus.mdsscalar.String(comment) def analysis(self): self.dispatch_cmd('dispatch /phase ANALYSIS') self.dispatch_cmd('close tree') def cycle(self): try: self.init() self.pulse_on() self.store() self.analysis() self.shotno.set_value(self.shotno.get_value()+1) except AbortCycle: pass class AbortCycle(Exception): '''Raised to abort the current cycle''' pass dispatcher_properties = ''' #The port at which jDispatcherIp listens to incoming commands jDispatcher.port = %(jDispatcher.port)d #server classes and addresses jDispatcher.server_1.class = CPCI_SERVER jDispatcher.server_1.address=spitzer.ap:8002 #jDispatcher.server_1.use_jserver = false jDispatcher.server_2.class = CAMAC_SERVER jDispatcher.server_2.address=matterhorn.ap:8002 #default server id: used by jDispatcher when an unknown server is found jDispatcher.default_server_idx = 1 #phase names and corresponding identifiers jDispatcher.phase_1.id = 1 jDispatcher.phase_1.name = INIT jDispatcher.phase_2.id = 2 jDispatcher.phase_2.name = PULSE_ON jDispatcher.phase_3.id = 3 jDispatcher.phase_3.name = STORE jDispatcher.phase_4.id = 4 jDispatcher.phase_4.name = ANALYSIS # The ports used by jDispatcher to export information to jDispatchMonitor jDispatcher.monitor_1.port = %(jDispatcher.monitor_1.port)d jDispatcher.info_port = %(jDispatcher.info_port)d ''' if __name__ == '__main__': if len(sys.argv) != 2: raise SystemExit('Usage: take_shot') tree = sys.argv[1] ctrl = Controller(tree) view = View(ctrl) gtk.main()