gda.hrpd.pmac.EpicsCVScanController.java Source code

Java tutorial

Introduction

Here is the source code for gda.hrpd.pmac.EpicsCVScanController.java

Source

/*-
 * Copyright  2010 Diamond Light Source Ltd.
 *
 * This file is part of GDA.
 *
 * GDA is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License version 3 as published by the Free
 * Software Foundation.
 *
 * GDA is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with GDA. If not, see <http://www.gnu.org/licenses/>.
 */

package gda.hrpd.pmac;

import java.util.Vector;

import org.apache.commons.lang.ArrayUtils;
import org.eclipse.january.dataset.Dataset;
import org.eclipse.january.dataset.DatasetFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import gda.configuration.properties.LocalProperties;
import gda.device.DeviceBase;
import gda.device.DeviceException;
import gda.epics.connection.EpicsChannelManager;
import gda.epics.connection.EpicsController;
import gda.epics.connection.InitializationListener;
import gda.factory.FactoryException;
import gda.jython.InterfaceProvider;
import gov.aps.jca.CAException;
import gov.aps.jca.CAStatus;
import gov.aps.jca.Channel;
import gov.aps.jca.TimeoutException;
import gov.aps.jca.dbr.DBR;
import gov.aps.jca.dbr.DBR_Enum;
import gov.aps.jca.dbr.DBR_Int;
import gov.aps.jca.dbr.DBR_String;
import gov.aps.jca.event.MonitorEvent;
import gov.aps.jca.event.MonitorListener;
import gov.aps.jca.event.PutEvent;
import gov.aps.jca.event.PutListener;
import uk.ac.diamond.scisoft.analysis.SDAPlotter;

public class EpicsCVScanController extends DeviceBase implements InitializationListener {

    private static final Logger logger = LoggerFactory.getLogger(EpicsCVScanController.class);
    /* base class declared name private, so need to re-declare here */
    @SuppressWarnings("unused")
    private String name = null;
    private String pv_root = null;
    private String plotPanelName = null;

    /* cached data - volatile as they are updated potentially by multiple threads */
    private volatile CurrentState currentstate = CurrentState.Done;
    private volatile String message = null;
    private volatile int numberofpulsedone;
    private volatile int totalnumberofpulse;
    private volatile String profile;
    private volatile boolean busy = false;

    private StateListener statel;
    private MessageListener messagel;
    private PulseDoneListener pdl;
    private PulseTotalListener ptl;
    private ProfileListener pfl;
    // control channels
    private Channel timechannel;
    private Channel profilechannel;
    private Channel keepseparate;
    private Channel mstart;
    private Channel mrange;
    private Channel start;
    private Channel pause;
    private Channel abort;
    private Channel currentstatechannel;
    private Channel statusmessagechannel;
    private Channel pulsesnumberdonechannel;
    private Channel puslestotalnumberchannel;
    private volatile boolean GDAScanning = false;
    private String legend = null;

    public boolean isGDAScanning() {
        return GDAScanning;
    }

    public void setGDAScanning(boolean gDAScanning) {
        GDAScanning = gDAScanning;
    }

    public enum CurrentState {
        Done, Aborted, Executing, Flyback, Paused, Fault, Reduction, LVIO
    }

    /**
     * EPICS controller
     */
    private EpicsController controller;

    /**
     * EPICS Channel Manager
     */
    private EpicsChannelManager channelManager;
    private Vector<String> profiles = new Vector<String>();
    private StartCallbackListener startcallbacklistener;
    private PauseCallbackListener pausecallbacklistener;
    private AbortCallbackListener abortcallbacklistener;
    // data channels
    private Channel rawx;
    private Channel mac1x;
    private Channel mac2x;
    private Channel mac3x;
    private Channel mac4x;
    private Channel mac5x;
    private Channel mac1y;
    private Channel mac2y;
    private Channel mac3y;
    private Channel mac4y;
    private Channel mac5y;
    // private Channel mac1m;
    // private Channel mac2m;
    // private Channel mac3m;
    // private Channel mac4m;
    // private Channel mac5m;
    private Channel allx;
    private Channel ally;
    private Channel allye;
    private Channel mav;
    @SuppressWarnings("unused")
    private boolean local;
    private long collectionNumber;
    private boolean live = true;
    private long fileNumber;

    public long getFileNumber() {
        return fileNumber;
    }

    public void setFileNumber(long fileNumber) {
        this.fileNumber = fileNumber;

    }

    public boolean isLive() {
        return live;
    }

    public void setLive(boolean live) {
        this.live = live;
    }

    public long getCollectionNumber() {
        return collectionNumber;
    }

    public void setCollectionNumber(long collectionNumber) {
        this.collectionNumber = collectionNumber;
    }

    public EpicsCVScanController() {
        controller = EpicsController.getInstance();
        channelManager = new EpicsChannelManager(this);
        statel = new StateListener();
        messagel = new MessageListener();
        startcallbacklistener = new StartCallbackListener();
        pausecallbacklistener = new PauseCallbackListener();
        abortcallbacklistener = new AbortCallbackListener();
        pdl = new PulseDoneListener();
        ptl = new PulseTotalListener();
        pfl = new ProfileListener();
    }

    @Override
    public void configure() throws FactoryException {
        if (!isConfigured()) {
            if (getPv_root() != null) {
                createChannelAccess(pv_root);
                channelManager.tryInitialize(100);
            } else {
                logger.error("Missing EPICS configuration for {}", getName());
                throw new FactoryException("Missing EPICS interface configuration for " + getName());
            }
            if (getPlotPanelName() == null) {
                throw new FactoryException("Missing Plot Panel Name configuration for " + getName());
            }
            setConfigured(true);
        }
    }

    private void createChannelAccess(String pvRoot) throws FactoryException {

        try {
            timechannel = channelManager.createChannel(pvRoot + ":TIME", false);
            profilechannel = channelManager.createChannel(pvRoot + ":PROFILE", pfl, false);
            // flyback = channelManager.createChannel(pvRoot + ":FLY", false);
            keepseparate = channelManager.createChannel(pvRoot + ":KEEP", false);
            // mflyback = channelManager.createChannel(pvRoot + ":MFLY", false);
            mstart = channelManager.createChannel(pvRoot + ":MSTART", false);
            mrange = channelManager.createChannel(pvRoot + ":MRANGE", false);
            start = channelManager.createChannel(pvRoot + ":START", false);
            pause = channelManager.createChannel(pvRoot + ":PAUSE", false);
            abort = channelManager.createChannel(pvRoot + ":ABORT", false);
            currentstatechannel = channelManager.createChannel(pvRoot + ":STATE", statel, false);
            statusmessagechannel = channelManager.createChannel(pvRoot + ":MESSAGE", messagel, false);
            pulsesnumberdonechannel = channelManager.createChannel(pvRoot + ":GPULSES", pdl, false);
            puslestotalnumberchannel = channelManager.createChannel(pvRoot + ":NPULSES", ptl, false);
            // Raw 2theta positions where scaler was triggered
            rawx = channelManager.createChannel(pvRoot + ":RAWX", false);
            // X scale of each MAC arm,
            mac1x = channelManager.createChannel(pvRoot + ":MAC1X", false);
            mac2x = channelManager.createChannel(pvRoot + ":MAC2X", false);
            mac3x = channelManager.createChannel(pvRoot + ":MAC3X", false);
            mac4x = channelManager.createChannel(pvRoot + ":MAC4X", false);
            mac5x = channelManager.createChannel(pvRoot + ":MAC5X", false);
            // Y Counts on each MAC arm
            mac1y = channelManager.createChannel(pvRoot + ":MAC1Y", false);
            mac2y = channelManager.createChannel(pvRoot + ":MAC2Y", false);
            mac3y = channelManager.createChannel(pvRoot + ":MAC3Y", false);
            mac4y = channelManager.createChannel(pvRoot + ":MAC4Y", false);
            mac5y = channelManager.createChannel(pvRoot + ":MAC5Y", false);
            // Monitor sum Ie+Io on each MAC arm
            // mac1m = channelManager.createChannel(pvRoot + ":MAC1M", false);
            // mac2m = channelManager.createChannel(pvRoot + ":MAC2M", false);
            // mac3m = channelManager.createChannel(pvRoot + ":MAC3M", false);
            // mac4m = channelManager.createChannel(pvRoot + ":MAC4M", false);
            // mac5m = channelManager.createChannel(pvRoot + ":MAC5M", false);
            // X scale of the combined, rebinned data
            allx = channelManager.createChannel(pvRoot + ":ALLX", false);
            // Y counts on the combined, rebinned data
            ally = channelManager.createChannel(pvRoot + ":ALLY", false);
            // Count errors on the combined rebinned data
            allye = channelManager.createChannel(pvRoot + ":ALLYE", false);
            mav = channelManager.createChannel(pvRoot + ":MAV", false);
            // acknowledge that creation phase is completed
            channelManager.creationPhaseCompleted();
        } catch (Throwable th) {
            throw new FactoryException("failed to create reuqired channels", th);
        }
    }

    public double getMonitorAvaerage() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDouble(mav);
    }

    /**
     * gets the total time of the constant velocity scan.
     *
     * @return total time in seconds
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public double getTime() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDouble(timechannel);
    }

    /**
     * sets the total time for the constant velocity scan.
     *
     * @param time
     *            in seconds
     * @throws CAException
     * @throws InterruptedException
     */
    public void setTime(double time) throws CAException, InterruptedException {
        controller.caput(timechannel, time);
    }

    /**
     * gets the motor components that participate the subsequent constant velocity scan.
     *
     * @return String motor names
     * @throws DeviceException
     */
    public String getProfile() throws DeviceException {
        short test;
        try {
            test = controller.cagetEnum(profilechannel);
            return profiles.get(test);
        } catch (Throwable th) {
            throw new DeviceException("failed to get profile from " + profilechannel.getName(), th);
        }
    }

    /**
     * sets the motor components that participate the subsequent constant velocity scan.
     *
     * @param profile
     * @throws DeviceException
     * @throws InterruptedException
     */
    public void setProfile(String profile) throws DeviceException, InterruptedException {
        if (profiles.contains(profile)) {
            int target = profiles.indexOf(profile);
            try {
                controller.caput(profilechannel, target);
            } catch (CAException e) {
                throw new DeviceException(profilechannel.getName() + " failed to move to " + profile, e);
            }
        } else {
            throw new DeviceException("Profile called " + profile + " is not found.");
        }
    }

    public boolean isKeepSeparate() throws TimeoutException, CAException, InterruptedException {
        String isKeepSeparate = controller.cagetLabels(keepseparate)[0];
        if (isKeepSeparate.equalsIgnoreCase("Yes")) {
            return true;
        }
        return false;
    }

    public void setKeepSeparate(boolean b) throws CAException, InterruptedException {
        if (b) {
            controller.caput(keepseparate, 1);
        } else {
            controller.caput(keepseparate, 0);
        }
    }

    /**
     * gets the scan start position of 2nd motor that participating the constant velocity scan of two-theta.
     *
     * @return the 2nd motor scan range.
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public double get2ndMotorStartPosition() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDouble(mstart);
    }

    /**
     * sets the scan start position of 2nd motor that participating the constant velocity scan of two-theta.
     *
     * @param position
     * @throws CAException
     * @throws InterruptedException
     */
    public void set2ndMotorStartPosition(double position) throws CAException, InterruptedException {
        controller.caput(mstart, position);
    }

    /**
     * gets the scan range of 2nd motor that participating the constant velocity scan of two-theta.
     *
     * @return the 2nd motor scan range.
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public double get2ndMotorScanRange() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDouble(mrange);
    }

    /**
     * sets the scan range of 2nd motor that participating the constant velocity scan of two-theta.
     *
     * @param position
     * @throws CAException
     * @throws InterruptedException
     */
    public void set2ndMotorScanRange(double position) throws CAException, InterruptedException {
        controller.caput(mrange, position);
    }

    /**
     * starts, restarts, or resume the constant velocity scan. The scan only starts when its current state is in one of
     * the following modes: Done, Aborted, or Paused. This is done to ensure the EPICS "Start" is never being called
     * more than once during a constant velocity scan.
     *
     * @throws CAException
     * @throws InterruptedException
     */
    public void start() throws CAException, InterruptedException {
        if (currentstate == CurrentState.Done || currentstate == CurrentState.Aborted
                || currentstate == CurrentState.Paused || currentstate == CurrentState.Fault) {
            busy = true;
            controller.caput(start, 1, startcallbacklistener);
            logger.info("{}: Start CV scan", getName());
        } else {
            InterfaceProvider.getTerminalPrinter()
                    .print("EPICS CVScan is busy, its current state is " + currentstate.toString());
            logger.warn("EPICS CVScan is busy. its current state is {}", currentstate.toString());
            legend = null;
        }
    }

    /**
     * pauses current constant velocity scan.
     *
     * @throws CAException
     * @throws InterruptedException
     */
    public void pause() throws CAException, InterruptedException {
        controller.caput(pause, 1, pausecallbacklistener);
        logger.info("{}: Pause CV scan", getName());
    }

    /**
     * aborts current constant velocity scan.
     *
     * @throws CAException
     * @throws InterruptedException
     */
    public void abort() throws CAException, InterruptedException {
        controller.caput(abort, 1, abortcallbacklistener);
        logger.info("{}: Abort CV scan", getName());
    }

    /**
     * pulls the current state of the CVScan from EPICS and update cached {@link #currentstate}.
     *
     * @return the current state
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public CurrentState getCurrentState() throws TimeoutException, CAException, InterruptedException {
        short value = controller.cagetEnum(currentstatechannel);
        if (value == 0) {
            currentstate = CurrentState.Done;
        } else if (value == 1) {
            currentstate = CurrentState.Aborted;
        } else if (value == 2) {
            currentstate = CurrentState.Executing;
        } else if (value == 3) {
            currentstate = CurrentState.Flyback;
        } else if (value == 4) {
            currentstate = CurrentState.Paused;
        } else if (value == 5) {
            currentstate = CurrentState.Fault;
        } else if (value == 6) {
            currentstate = CurrentState.Reduction;
        } else if (value == 7) {
            currentstate = CurrentState.LVIO;
        } else {
            logger.error("{} reports UNKNOWN state value: {}", getName(), value);
            throw new IllegalStateException(getName() + " in a unknown state.");
        }

        return currentstate;
    }

    /**
     * pulls the status message from EPICS and update cached {@link #message} value
     *
     * @return the status message as String
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public String getStatusMessage() throws TimeoutException, CAException, InterruptedException {
        return message = controller.cagetString(statusmessagechannel);
    }

    /**
     * pulls the number of pulse done from EPICS and update cached {@link #numberofpulsedone} value
     *
     * @return the status message as String
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public int getNumberOfPulsesDone() throws TimeoutException, CAException, InterruptedException {
        return numberofpulsedone = controller.cagetInt(pulsesnumberdonechannel);
    }

    /**
     * pulls the total number of pulse from EPICS and update cached {@link #totalnumberofpulse} value
     *
     * @return the status message as String
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public int getTotalNumberPulses() throws TimeoutException, CAException, InterruptedException {
        return totalnumberofpulse = controller.cagetInt(puslestotalnumberchannel);
    }

    /****************** Access CVScan data *****************************/
    /**
     * gets the raw two-theta position where scalers/detectors are triggered.
     *
     * @return two-theta positions
     * @throws TimeoutException
     * @throws CAException
     * @throws InterruptedException
     */
    public double[] getRaw2ThetaPositions() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(rawx);
    }

    public int getRaw2ThetaSize() {
        return rawx.getElementCount();
    }

    public double[] getRebinned2ThetaPositions() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(allx);
    }

    public int getRebinned2ThetaSize() {
        return allx.getElementCount();
    }

    public double[] getRebinnedCounts() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(ally);
    }

    public int getRebinnedCountsSize() {
        return ally.getElementCount();
    }

    public double[] getRebinnedCountErrors() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(allye);
    }

    public int getRebinnedCountErrorsSize() {
        return allye.getElementCount();
    }

    public double[] getMAC1X() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac1x);
    }

    public int getMAC1XSize() {
        return mac1x.getElementCount();
    }

    public double[] getMAC2X() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac2x);
    }

    public int getMAC2XSize() {
        return mac3x.getElementCount();
    }

    public double[] getMAC3X() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac3x);
    }

    public int getMAC3XSize() {
        return mac3x.getElementCount();
    }

    public double[] getMAC4X() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac4x);
    }

    public int getMAC4XSize() {
        return mac4x.getElementCount();
    }

    public double[] getMAC5X() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac5x);
    }

    public int getMAC5XSize() {
        return mac5x.getElementCount();
    }

    public double[] getMAC1Y() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac1y);
    }

    public int getMAC1YSize() {
        return mac1y.getElementCount();
    }

    public double[] getMAC2Y() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac2y);
    }

    public int getMAC2YSize() {
        return mac3y.getElementCount();
    }

    public double[] getMAC3Y() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac3y);
    }

    public int getMAC3YSize() {
        return mac3y.getElementCount();
    }

    public double[] getMAC4Y() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac4y);
    }

    public int getMAC4YSize() {
        return mac4y.getElementCount();
    }

    public double[] getMAC5Y() throws TimeoutException, CAException, InterruptedException {
        return controller.cagetDoubleArray(mac5y);
    }

    public int getMAC5YSize() {
        return mac5y.getElementCount();
    }

    // public double[] getMAC1M() throws TimeoutException, CAException {
    // return controller.cagetDoubleArray(mac1m);
    // }
    //
    // public int getMAC1MSize() {
    // return mac1m.getElementCount();
    // }
    //
    // public double[] getMAC2M() throws TimeoutException, CAException {
    // return controller.cagetDoubleArray(mac2m);
    // }
    //
    // public int getMAC2MSize() {
    // return mac3m.getElementCount();
    // }
    //
    // public double[] getMAC3M() throws TimeoutException, CAException {
    // return controller.cagetDoubleArray(mac3m);
    // }
    //
    // public int getMAC3MSize() {
    // return mac3m.getElementCount();
    // }
    //
    // public double[] getMAC4M() throws TimeoutException, CAException {
    // return controller.cagetDoubleArray(mac4m);
    // }
    //
    // public int getMAC4MSize() {
    // return mac4m.getElementCount();
    // }
    //
    // public double[] getMAC5M() throws TimeoutException, CAException {
    // return controller.cagetDoubleArray(mac5m);
    // }
    //
    // public int getMAC5MSize() {
    // return mac5m.getElementCount();
    // }

    /**
     * return the latest status message that updated from EPICS
     *
     * @return the cached {@link #message}
     */
    public String getMessage() {
        return this.message;
    }

    /**
     * returns the latest current state that updated from EPICS
     *
     * @return the cached {@link #currentstate}
     */
    public CurrentState getState() {
        return currentstate;
    }

    /**
     * returns the latest number of pulses completed that updated from EPICS
     *
     * @return the cached {@link #numberofpulsedone}
     */
    public int getNumberOfPulseDone() {
        return numberofpulsedone;
    }

    /**
     * returns the latest total number of pulses in this CVScan that updated from EPICS
     *
     * @return the cached {@link #totalnumberofpulse}
     */
    public int getTotalNumberOfPulses() {
        return totalnumberofpulse;
    }

    public String getPv_root() {
        return pv_root;
    }

    public void setPv_root(String pvRoot) {
        pv_root = pvRoot;
    }

    public String getPlotPanelName() {
        return plotPanelName;
    }

    public void setPlotPanelName(String plotPanelName) {
        this.plotPanelName = plotPanelName;
    }

    public String[] getProfiles() throws InterruptedException {
        String[] profileLabels = new String[profiles.size()];
        try {
            profileLabels = controller.cagetLabels(profilechannel);
        } catch (TimeoutException e) {
            logger.error("Timeout on initialising Profiles of " + getName(), e);
        } catch (CAException e) {
            logger.error("CAException on initialising Profiles of " + getName(), e);
        }
        return profileLabels;
    }

    @Override
    public void initializationCompleted() throws InterruptedException {
        // initialise Profile choices
        for (String profileName : getProfiles()) {
            if (profileName != null && !profileName.isEmpty()) {
                this.profiles.add(profileName);
            }
        }
        // initialise state variables
        try {
            this.currentstate = getCurrentState();
        } catch (TimeoutException e) {
            logger.error("Timeout on initialising Current State of " + getName(), e);
        } catch (CAException e) {
            logger.error("CAException on initialising Current State of " + getName(), e);
        }
        try {
            this.message = getStatusMessage();
        } catch (TimeoutException e) {
            logger.error("Timeout on initialising Status Message of " + getName(), e);
        } catch (CAException e) {
            logger.error("CAException on initialising Status Message of " + getName(), e);
        }
        logger.info("{} is initialised", this.getName());
    }

    public boolean isBusy() {
        return busy;
    }

    public void setBusy(boolean b) {
        busy = b;
    }

    /**
     * Monitor Current State in EPICS, and update cached state variable.
     */
    private class StateListener implements MonitorListener {

        @Override
        public void monitorChanged(MonitorEvent arg0) {
            short value = -1;
            DBR dbr = arg0.getDBR();
            if (dbr.isENUM()) {
                value = ((DBR_Enum) dbr).getEnumValue()[0];
            } else {
                logger.error("{} : StateListener expect Enum type but got {} type.", getName(), dbr.getType());
            }
            if (value == 0) {
                currentstate = CurrentState.Done;
                busy = false;
            } else if (value == 1) {
                currentstate = CurrentState.Aborted;
                busy = false;
            } else if (value == 2) {
                currentstate = CurrentState.Executing;
                busy = true;
            } else if (value == 3) {
                currentstate = CurrentState.Flyback;
                busy = true;
            } else if (value == 4) {
                currentstate = CurrentState.Paused;
                busy = true;
            } else if (value == 5) {
                currentstate = CurrentState.Fault;
                legend = null;
                busy = true; // GDA must be busy as auto retry kicked in on fault, otherwise for-loop carrys on
            } else if (value == 6) {
                currentstate = CurrentState.Reduction;
                busy = true;
            } else if (value == 7) {
                currentstate = CurrentState.LVIO;
                busy = true;
            } else {
                logger.error("{} reports UNKNOWN state value: {}", getName(), value);
                throw new IllegalStateException(getName() + " in a unknown state.");
            }
            logger.info("Monitor update from EPICS {}: Current State: {}", ((Channel) arg0.getSource()).getName(),
                    currentstate);

            notifyIObservers(currentstate);
        }
    }

    private void notifyIObservers(CurrentState currentstate) {
        notifyIObservers(this, currentstate);
    }

    /**
     * Monitor Status Message in EPICS, and update cached message variable.
     */
    private class MessageListener implements MonitorListener {
        boolean first = true;

        @Override
        public void monitorChanged(MonitorEvent arg0) {
            if (first) {
                first = false;
                return;
            }
            DBR dbr = arg0.getDBR();
            if (dbr.isSTRING()) {
                message = ((DBR_String) dbr).getStringValue()[0];
                if (InterfaceProvider.getTerminalPrinter() != null) {
                    InterfaceProvider.getTerminalPrinter().print(getName() + ": " + message);
                }
            } else {
                logger.error("{} : MessageListener expect String type but got {} type.", getName(), dbr.getType());
            }
        }
    }

    /**
     * Monitor Number of pulses done in EPICS, and update cached numberofpulsedone variable. This state variable can be
     * used to display progress bar on Client. It also updates plot of stage-based rebinned data by pulling them from
     * EPICS CVScan.
     */
    private class PulseDoneListener implements MonitorListener {
        double[] x = null;
        double[] y = null;
        String filePrefix = null;
        String fileSuffix = null;
        boolean first = true;

        @Override
        public void monitorChanged(MonitorEvent arg0) {
            if (first) {
                first = false;
                return;
            }
            DBR dbr = arg0.getDBR();
            boolean live = false;
            if (InterfaceProvider.getJythonNamespace() != null) {
                live = (Boolean) InterfaceProvider.getJythonNamespace().getFromJythonNamespace("LIVE");
            }
            if (!live) {
                return;
            }
            if (dbr.isINT()) {
                numberofpulsedone = ((DBR_Int) dbr).getIntValue()[0];
                if (numberofpulsedone == 0) { // if current state=Fault, restart do not send Pulse processed = 0 event
                    // logger.info("get 2theta data");
                    x = get2Theta();
                    filePrefix = LocalProperties.get("gda.data.file.prefix", "");
                    fileSuffix = LocalProperties.get("gda.data.file.suffix", "-mac");
                    legend = filePrefix + getFileNumber() + fileSuffix + "-"
                            + String.format("%03d", collectionNumber);
                    // logger.info("2theta size {}", x.length);
                } else {
                    // logger.info("get counts data");
                    y = getCount();
                    // logger.info("Count size {}", y.length);
                }
                if (x != null && y != null) {
                    Dataset yds = DatasetFactory.createFromObject(y);
                    if (legend == null) {
                        filePrefix = LocalProperties.get("gda.data.file.prefix", "");
                        fileSuffix = LocalProperties.get("gda.data.file.suffix", "-mac");
                        legend = filePrefix + getFileNumber() + fileSuffix + "-"
                                + String.format("%03d", collectionNumber);
                    }
                    yds.setName(legend);
                    try {
                        SDAPlotter.plot(getPlotPanelName(), DatasetFactory.createFromObject(x), yds);
                    } catch (Exception e) {
                        logger.error("MAC detector data live plot failed.", e);
                    }
                }
            } else {
                logger.error("{} : PulseDoneListener expect Integer type but got {} type.", getName(),
                        dbr.getType());
            }
        }
    }

    private double[] get2Theta() {
        double[] x1 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC1X");
            x1 = getMAC1X();
            // logger.info("gets MAC1X DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC1X", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC1X", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC1X", e);
        }
        double[] x2 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC2X");
            x2 = getMAC2X();
            // logger.info("gets MAC2X DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC2X", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC2X", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC2X", e);
        }
        double[] x3 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC3X");
            x3 = getMAC3X();
            // logger.info("gets MAC3X DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC3X", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC3X", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC3X", e);
        }
        double[] x4 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC4X");
            x4 = getMAC4X();
            // logger.info("gets MAC4X DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC4X", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC4X", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC4X", e);
        }
        double[] x5 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC5X");
            x5 = getMAC5X();
            // logger.info("gets MAC5X DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC5X", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC5X", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC5X", e);
        }
        return ArrayUtils.subarray(
                ArrayUtils.addAll(ArrayUtils.addAll(ArrayUtils.addAll(ArrayUtils.addAll(x1, x2), x3), x4), x5),
                16500, 305000);
    }

    // Y1,Y2,Y3,Y4,Y5 only temperarily available in EPICS so must be get first before processing them.
    private double[] getCount() {
        double[] y1 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC1Y");
            y1 = getMAC1Y();
            // logger.info("gets MAC1Y DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC1Y", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC1Y", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC1Y", e);
        }
        double[] y2 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC2Y");
            y2 = getMAC2Y();
            // logger.info("gets MAC2Y DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC2Y", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC2Y", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC2Y", e);
        }
        double[] y3 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC3Y");
            y3 = getMAC3Y();
            // logger.info("gets MAC3Y DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC3Y", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC3Y", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC3Y", e);
        }
        double[] y4 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC4Y");
            y4 = getMAC4Y();
            // logger.info("gets MAC4Y DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC4Y", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC4Y", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC4Y", e);
        }
        double[] y5 = ArrayUtils.EMPTY_DOUBLE_ARRAY;
        try {
            // logger.info("gets MAC5Y");
            y5 = getMAC5Y();
            // logger.info("gets MAC5Y DONE");
        } catch (TimeoutException e) {
            logger.error("Timeout while gets MAC5Y", e);
            e.printStackTrace();
        } catch (CAException e) {
            logger.error("CAException while gets MAC5Y", e);
            e.printStackTrace();
        } catch (InterruptedException e) {
            logger.error("InterruptedException while gets MAC5Y", e);
        }
        return ArrayUtils.subarray(
                ArrayUtils.addAll(ArrayUtils.addAll(ArrayUtils.addAll(ArrayUtils.addAll(y1, y2), y3), y4), y5),
                16500, 305000);
    }

    /**
     * Monitor the total number of pulses in EPICS Constant Velocity scan, and update cached total number of pulse
     * variable. This state variable can be used to display progress bar on Client.
     */
    private class PulseTotalListener implements MonitorListener {

        @Override
        public void monitorChanged(MonitorEvent arg0) {
            DBR dbr = arg0.getDBR();
            if (dbr.isINT()) {
                totalnumberofpulse = ((DBR_Int) dbr).getIntValue()[0];
            } else {
                logger.error("{} : PulseTotalListener expect Integer type but got {} type.", getName(),
                        dbr.getType());
            }
        }
    }

    /**
     * Monitor the total number of pulses in EPICS Constant Velocity scan, and update cached total number of pulse
     * variable. This state variable can be used to display progress bar on Client.
     */
    private class ProfileListener implements MonitorListener {

        @Override
        public void monitorChanged(MonitorEvent arg0) {
            DBR dbr = arg0.getDBR();
            if (dbr.isENUM()) {
                short profileindex = ((DBR_Enum) dbr).getEnumValue()[0];
                if (!profiles.isEmpty())
                    profile = profiles.get(profileindex);
                notifyIObservers(profile);
            } else {
                logger.error("{} : ProfileListener expect Enum type but got {} type.", getName(), dbr.getType());
            }
        }
    }

    private void notifyIObservers(String profile) {
        notifyIObservers(this, profile);
    }

    /**
     * The start call back handler
     */
    public class StartCallbackListener implements PutListener {

        @Override
        public synchronized void putCompleted(PutEvent ev) {
            logger.debug("{}: Start caputCallback complete ", getName());
            if (ev.getStatus() != CAStatus.NORMAL) {
                logger.error("Put failed. Channel {} : Status {}", ((Channel) ev.getSource()).getName(),
                        ev.getStatus());
            }
            // always update current state
            try {
                if ((currentstate = getCurrentState()) == CurrentState.Done) {
                    busy = false;
                }
            } catch (TimeoutException e) {
                logger.error("Timeout on getting current state from " + currentstatechannel.getName(), e);
            } catch (CAException e) {
                logger.error("CAException on getting current state from " + currentstatechannel.getName(), e);
            } catch (InterruptedException e) {
                logger.error("InterruptedException  on getting current state from " + currentstatechannel.getName(),
                        e);
            }

        }

    }

    public class PauseCallbackListener implements PutListener {

        @Override
        public synchronized void putCompleted(PutEvent ev) {
            logger.debug("{}: Pause caputCallback complete ", getName());
            if (ev.getStatus() != CAStatus.NORMAL) {
                logger.error("Put failed. Channel {} : Status {}", ((Channel) ev.getSource()).getName(),
                        ev.getStatus());
            }
            try {
                busy = true;
                currentstate = getCurrentState();
            } catch (TimeoutException e) {
                logger.error("Timeout on getting current state from " + currentstatechannel.getName(), e);
            } catch (CAException e) {
                logger.error("CAException on getting current state from " + currentstatechannel.getName(), e);
            } catch (InterruptedException e) {
                logger.error("InterruptedException on getting current state from " + currentstatechannel.getName(),
                        e);
            }
        }
    }

    public class AbortCallbackListener implements PutListener {

        @Override
        public synchronized void putCompleted(PutEvent ev) {
            logger.debug("{}: Abort caputCallback complete ", getName());
            if (ev.getStatus() != CAStatus.NORMAL) {
                logger.error("Put failed. Channel {} : Status {}", ((Channel) ev.getSource()).getName(),
                        ev.getStatus());
            }
            try {
                busy = false;
                currentstate = getCurrentState();
            } catch (TimeoutException e) {
                logger.error("Timeout on getting current state from " + currentstatechannel.getName(), e);
            } catch (CAException e) {
                logger.error("CAException on getting current state from " + currentstatechannel.getName(), e);
            } catch (InterruptedException e) {
                logger.error("InterruptedException on getting current state from " + currentstatechannel.getName(),
                        e);
            }
        }

    }

}