Take shot.py - MdsWiki
Navigation
Personal tools

From MdsWiki

Jump to: navigation, search

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()