diff --git a/src/main/java/ch/psi/fda/aq/Acquisition.java b/src/main/java/ch/psi/fda/aq/Acquisition.java new file mode 100644 index 0000000..2ba78be --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/Acquisition.java @@ -0,0 +1,1085 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeoutException; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +import com.google.common.eventbus.AsyncEventBus; +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.core.ActionLoop; +import ch.psi.fda.core.Actor; +import ch.psi.fda.core.Manipulation; +import ch.psi.fda.core.Sensor; +import ch.psi.fda.core.actions.ChannelAccessCondition; +import ch.psi.fda.core.actions.ChannelAccessPut; +import ch.psi.fda.core.actions.Delay; +import ch.psi.fda.core.actors.ChannelAccessFunctionActuator; +import ch.psi.fda.core.actors.ChannelAccessLinearActuator; +import ch.psi.fda.core.actors.ChannelAccessTableActuator; +import ch.psi.fda.core.actors.ComplexActuator; +import ch.psi.fda.core.actors.JythonFunction; +import ch.psi.fda.core.actors.PseudoActuatorSensor; +import ch.psi.fda.core.guard.ChannelAccessGuard; +import ch.psi.fda.core.guard.ChannelAccessGuardCondition; +import ch.psi.fda.core.loops.ActorSensorLoop; +import ch.psi.fda.core.loops.cr.CrlogicLoopStream; +import ch.psi.fda.core.loops.cr.CrlogicResource; +import ch.psi.fda.core.loops.cr.ParallelCrlogic; +import ch.psi.fda.core.loops.cr.ScrlogicLoop; +import ch.psi.fda.core.manipulator.JythonManipulation; +import ch.psi.fda.core.scripting.JythonGlobalVariable; +import ch.psi.fda.core.scripting.JythonParameterMapping; +import ch.psi.fda.core.scripting.JythonParameterMappingChannel; +import ch.psi.fda.core.scripting.JythonParameterMappingGlobalVariable; +import ch.psi.fda.core.scripting.JythonParameterMappingID; +import ch.psi.fda.core.sensors.ChannelAccessSensor; +import ch.psi.fda.core.sensors.TimestampSensor; +import ch.psi.fda.model.ModelManager; +import ch.psi.fda.model.v1.Action; +import ch.psi.fda.model.v1.ArrayDetector; +import ch.psi.fda.model.v1.ArrayPositioner; +import ch.psi.fda.model.v1.ChannelAction; +import ch.psi.fda.model.v1.ChannelParameterMapping; +import ch.psi.fda.model.v1.Configuration; +import ch.psi.fda.model.v1.ContinuousDimension; +import ch.psi.fda.model.v1.ContinuousPositioner; +import ch.psi.fda.model.v1.Detector; +import ch.psi.fda.model.v1.DetectorOfDetectors; +import ch.psi.fda.model.v1.DiscreteStepDimension; +import ch.psi.fda.model.v1.DiscreteStepPositioner; +import ch.psi.fda.model.v1.Function; +import ch.psi.fda.model.v1.FunctionPositioner; +import ch.psi.fda.model.v1.Guard; +import ch.psi.fda.model.v1.GuardCondition; +import ch.psi.fda.model.v1.IDParameterMapping; +import ch.psi.fda.model.v1.LinearPositioner; +import ch.psi.fda.model.v1.ParameterMapping; +import ch.psi.fda.model.v1.Positioner; +import ch.psi.fda.model.v1.PseudoPositioner; +import ch.psi.fda.model.v1.Recipient; +import ch.psi.fda.model.v1.Region; +import ch.psi.fda.model.v1.RegionPositioner; +import ch.psi.fda.model.v1.ScalarDetector; +import ch.psi.fda.model.v1.ScalerChannel; +import ch.psi.fda.model.v1.Scan; +import ch.psi.fda.model.v1.ScriptAction; +import ch.psi.fda.model.v1.ScriptManipulation; +import ch.psi.fda.model.v1.ShellAction; +import ch.psi.fda.model.v1.SimpleScalarDetector; +import ch.psi.fda.model.v1.Timestamp; +import ch.psi.fda.model.v1.Variable; +import ch.psi.fda.model.v1.VariableParameterMapping; +import ch.psi.fda.serializer.SerializerTXT; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.type.DoubleTimestamp; +import ch.psi.jcae.util.ComparatorAND; +import ch.psi.jcae.util.ComparatorOR; +import ch.psi.jcae.util.ComparatorREGEX; + +/** + * Data acquisition engine for performing scans + * Mapping is specific to scan model version 1.0 + */ +public class Acquisition { + + private static Logger logger = Logger.getLogger(Acquisition.class.getName()); + + private final AcquisitionConfiguration configuration; + + private ActionLoop actionLoop; + private Manipulator manipulator; + private SerializerTXT serializer; + + private List manipulations; + private volatile boolean active = false; + + private NotificationAgent notificationAgent; + + private Handler logHandler = null; + + private File datafile; + + + private ChannelService cservice; + private List> channels = new ArrayList<>(); + private List templates = new ArrayList<>(); + + + private Configuration configModel; + + private HashMap jVariableDictionary = new HashMap(); + + public Acquisition(ChannelService cservice, AcquisitionConfiguration configuration){ + this.cservice = cservice; + this.configuration = configuration; + this.actionLoop = null; + this.manipulations = new ArrayList(); + } + + + + /** + * Get state of the acquisition engine + * @return the active + */ + public boolean isActive() { + return active; + } + + + + /** + * Acquire data + * + * @param smodel Model of the scan + * @param getQueue Flag whether to return a queue or not. If false the return value of the function will be null. + * @throws InterruptedException + */ + public void initalize(EventBus bus, Configuration smodel) { + + // Create notification agent with globally configured recipients + notificationAgent = new NotificationAgent(configuration.getSmptServer(), "fda.notification@psi.ch"); + + // Update recipients list of the Notifiaction Agent + if(smodel.getNotification()!=null){ + for(Recipient r: smodel.getNotification().getRecipient()){ + notificationAgent.getRecipients().add(r); + } + } + + + Date date = new Date(); + // Prepare output directory / create directory if it does not exist + File bdir = new File(configuration.replaceMacros(configuration.getDataBaseDirectory(), date, smodel.getData().getFileName())); +// bdir.mkdirs(); + String fprefix = configuration.replaceMacros(configuration.getDataFilePrefix(), date, smodel.getData().getFileName()); + + // Construct filenames + File xmlfile = new File(bdir, fprefix+smodel.getData().getFileName()+".xml"); + datafile = new File(bdir, fprefix+smodel.getData().getFileName()+".txt"); + + // Create required directories + xmlfile.getParentFile().mkdirs(); + + try{ + // Workaround - to avoid that multiple handlers get attached + // this should be removed when rewriting the acquisition logic + if(logHandler!=null){ + Logger.getLogger("").removeHandler(logHandler); + } + + File logfile = new File(bdir, fprefix+smodel.getData().getFileName()+".log"); + logHandler = new FileHandler(logfile.getAbsolutePath()); + logHandler.setFormatter(new SimpleFormatter()); + Logger.getLogger("").addHandler(logHandler); + Logger.getLogger("ch.psi.fda").setLevel(Level.FINEST); + // Also include log messages of the channel access implementation + Logger.getLogger("com.cosylab.epics.caj").setLevel(Level.INFO); + } catch (SecurityException e1) { + e1.printStackTrace(); + } catch (IOException e1) { + e1.printStackTrace(); + } + + + // Save a copy of the model to the data directory + try { + ModelManager.marshall(smodel, xmlfile); + } catch (Exception e) { + throw new RuntimeException("Unable to serialize scan",e); + } + + logger.fine("Map Model to internal logic"); + + if(smodel.getScan().getManipulation()!= null && smodel.getScan().getManipulation().size()>0){ + // Setup optimized with manipulations + //EventBus b = new AsyncEventBus(Executors.newCachedThreadPool()); + EventBus b = new AsyncEventBus(Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())); + + //EventBus b = new AsyncEventBus(Executors.newSingleThreadExecutor()); + + // Map scan to base model + // After this call actionLoop and collector will be initialized + Collector collector = new Collector(b); + mapScan(collector, smodel); +// col = collector; + logger.fine("ActionLoop and Collector initialized"); + + + + // Add manipulator into processing chain + this.manipulator = new Manipulator(bus, this.manipulations); + b.register(this.manipulator); + + + this.serializer = new SerializerTXT(datafile, true); + bus.register(serializer); + } + else{ + // Setup optimized without manipulations + Collector collector = new Collector(bus); + mapScan(collector, smodel); +// col = collector; + + this.serializer = new SerializerTXT(datafile, true); + bus.register(serializer); + } + } + + /** + * Execute acquisition + * @throws InterruptedException + */ + public void execute() throws InterruptedException { + String hostname; + try { + hostname = InetAddress.getLocalHost().getHostName(); + } catch (UnknownHostException e) { + hostname="unknown"; + } + + try{ + active = true; + + actionLoop.prepare(); + actionLoop.execute(); + actionLoop.cleanup(); + + notificationAgent.sendNotification("Notification - FDA Execution Finished", "The execution of the FDA on '"+hostname+"' for file '"+datafile.getName()+"' finished successfully\n\nYou received this message because you are listed in the notification list for this data acquisition configuration.", false,true); + } + catch(RuntimeException e){ + logger.log(Level.WARNING, "Execution failed: ", e); + notificationAgent.sendNotification("Notification - FDA Execution Failed", "The execution of the FDA failed on '"+hostname+"' for file '"+datafile.getName()+"'\n\nYou received this message because you are listed in the notification list for this data acquisition configuration.", true,false); + throw e; + } + catch(InterruptedException e){ + logger.log(Level.WARNING, "Execution interrupted: ", e); + notificationAgent.sendNotification("Notification - FDA Execution was aborted", "The execution of the FDA on '"+hostname+"' for file '"+datafile.getName()+"' was aborted\n\nYou received this message because you are listed in the notification list for this data acquisition configuration.", false, true); + } + finally{ + active = false; + } + } + + + public void destroy(){ + if(actionLoop != null){ + logger.finest("Destroy managed resources"); + + for(Channel c: channels){ + try { + c.destroy(); + } catch (ChannelException e) { + throw new RuntimeException("Unable to destroy channel "+c.getName(),e); + } + } + for(Object o: templates){ + try { + cservice.destroyAnnotatedChannels(o); + } catch (ChannelException e) { + throw new RuntimeException("Unable to destroy channels of template: "+o.getClass().getName(), e); + } + } + + } + + // Clear global variables Jython + jVariableDictionary.clear(); + + // Remove log handler + if(logHandler!=null){ + logger.fine("Close log handler"); + logHandler.close(); + Logger.getLogger("").removeHandler(logHandler); + } + } + + public void abort(){ + actionLoop.abort(); + } + + public String getDatafileName(){ + return(datafile.getName()); + } + + /** + * Retrieve id string of the passed object + * @param object + * @return Id string of object + */ + private static String resolveIdRef(Object object){ + String id; + if(object instanceof Positioner){ + id = ((Positioner)object).getId(); + } + else if (object instanceof Detector){ + id = ((Detector)object).getId(); + } + else if (object instanceof ch.psi.fda.model.v1.Manipulation){ + id = ((ch.psi.fda.model.v1.Manipulation)object).getId(); + } + else{ + throw new RuntimeException("Unable to identify id of object reference "+object); + } + return id; + } + + + + /** + * Map scan to base model + * @param scan + */ + private void mapScan(Collector collector, Configuration configuration){ + this.configModel = configuration; + Scan scan = configuration.getScan(); + + for(Variable v: configuration.getVariable()){ + JythonGlobalVariable var = new JythonGlobalVariable(); + var.setName(v.getName()); + var.setValue(v.getValue()); + jVariableDictionary.put(v.getName(), var); + v.getValue(); + } + + // Map continuous dimension + if(scan.getCdimension() != null){ + ActionLoop aLoop = mapContinuousDimension(scan.getCdimension()); + actionLoop = aLoop; + collector.addEventBus(aLoop.getEventBus()); + } + + // Map discrete step dimensions + for(DiscreteStepDimension d: scan.getDimension()){ + ActorSensorLoop l = mapDiscreteStepDimension(d); + collector.addEventBus(l.getEventBus()); + if(actionLoop != null){ + l.getActionLoops().add(actionLoop); + } + actionLoop = l; + } + + // No dimensions where specified for scan + if(actionLoop == null){ + actionLoop = new ActorSensorLoop(); + } + + // Map pre actions to pre actions of the top level dimension + actionLoop.getPreActions().addAll(mapActions(scan.getPreAction())); + + // Map post actions to post actions of the top level dimension + actionLoop.getPostActions().addAll(mapActions(scan.getPostAction())); + + + // TODO need to be removed! and done differently !!!! + // Handle iterations by adding a pseudo dimension and setting the + // datagroup flag in the main loop + if(configuration.getNumberOfExecution()>1){ + // Create Iterations pseudo loop + ActorSensorLoop l = new ActorSensorLoop(); + PseudoActuatorSensor a = new PseudoActuatorSensor("iterations", configuration.getNumberOfExecution()); + l.getActors().add(a); + l.getActionLoops().add(actionLoop); + actionLoop.setDataGroup(true); // Need to add setDataGroup to ActionLoop interface + + // Set toplevel action loop + actionLoop = l; + collector.addEventBus(l.getEventBus()); + } + + // handling manipulations + for(ch.psi.fda.model.v1.Manipulation m : scan.getManipulation()){ + if(m instanceof ScriptManipulation){ + ScriptManipulation sm = (ScriptManipulation) m; + + List mapping = new ArrayList(); + for(ParameterMapping pm: sm.getMapping()){ + if(pm instanceof IDParameterMapping){ + String refid = resolveIdRef(((IDParameterMapping)pm).getRefid()); + mapping.add( new JythonParameterMappingID(pm.getVariable(), refid)); + } + else if(pm instanceof ChannelParameterMapping){ + ChannelParameterMapping cpm = (ChannelParameterMapping) pm; + if(cpm.getType().equals("String")){ + mapping.add( new JythonParameterMappingChannel(cpm.getVariable(), createChannel(String.class, cpm.getChannel()))); + } + else if(cpm.getType().equals("Integer")){ + mapping.add( new JythonParameterMappingChannel(cpm.getVariable(), createChannel(Integer.class, cpm.getChannel()))); + } + else if(cpm.getType().equals("Double")){ + mapping.add( new JythonParameterMappingChannel(cpm.getVariable(), createChannel(Double.class, cpm.getChannel()))); + } + else{ + logger.warning("Channel type ["+cpm.getType()+"] is not supported for mapping"); + } + } + else if(pm instanceof VariableParameterMapping){ + VariableParameterMapping vp = (VariableParameterMapping) pm; + Variable v = (Variable)vp.getName(); + JythonGlobalVariable var = jVariableDictionary.get(v.getName()); + var.setValue(v.getValue()); + mapping.add(new JythonParameterMappingGlobalVariable(vp.getVariable(), var)); + } + } + + JythonManipulation manipulation = new JythonManipulation(sm.getId(), sm.getScript(), mapping, sm.isReturnArray()); + + if(configuration.getData()!=null){ // Safety + manipulation.setVariable("FILENAME", configuration.getData().getFileName()); + manipulation.setVariable("DATAFILE", datafile.getAbsoluteFile()); + } + + this.manipulations.add(manipulation); + } + } + } + + /** + * Map a model action to base actions + * @param actions + * @return + */ + private List mapActions(List actions){ + List alist = new ArrayList(); + for(Action a: actions){ + if(a instanceof ChannelAction){ + ChannelAction ca = (ChannelAction) a; + + String operation = ca.getOperation(); // Default = put + String type=ca.getType(); // Default = String + + if(operation.equals("put")){ + Long timeout = null; + if(ca.getTimeout()!=null){ + timeout = Math.round(ca.getTimeout()*1000); + } + if(type.equals("String")){ + alist.add(new ChannelAccessPut(createChannel(String.class, ca.getChannel()), ca.getValue(), false, timeout)); + } + else if(type.equals("Integer")){ + alist.add(new ChannelAccessPut(createChannel(Integer.class, ca.getChannel()), new Integer(ca.getValue()), false, timeout)); + } + else if(type.equals("Double")){ + alist.add(new ChannelAccessPut(createChannel(Double.class,ca.getChannel()), new Double(ca.getValue()), false, timeout)); + } + } + else if(operation.equals("putq")){ + if(type.equals("String")){ + alist.add(new ChannelAccessPut(createChannel(String.class,ca.getChannel()), ca.getValue(), true, null)); + } + else if(type.equals("Integer")){ + alist.add(new ChannelAccessPut(createChannel(Integer.class,ca.getChannel()), new Integer(ca.getValue()), true, null)); + } + else if(type.equals("Double")){ + alist.add(new ChannelAccessPut(createChannel(Double.class,ca.getChannel()), new Double(ca.getValue()), true, null)); + } + } + else if(operation.equals("wait")){ + Long timeout = null ; // Default timeout = wait forever + if(ca.getTimeout()!=null){ + timeout = Math.round(ca.getTimeout()*1000); + } + if(type.equals("String")){ + alist.add(new ChannelAccessCondition(createChannel(String.class,ca.getChannel()), ca.getValue(), timeout)); + } + else if(type.equals("Integer")){ + alist.add(new ChannelAccessCondition(createChannel(Integer.class,ca.getChannel()), new Integer(ca.getValue()), timeout)); + } + else if(type.equals("Double")){ + alist.add(new ChannelAccessCondition(createChannel(Double.class,ca.getChannel()), new Double(ca.getValue()), timeout)); + } + } + else if(operation.equals("waitREGEX")){ + Long timeout = null ; // Default timeout = wait forever + if(ca.getTimeout()!=null){ + timeout = Math.round(ca.getTimeout()*1000); + } + if(type.equals("String")){ + alist.add(new ChannelAccessCondition<>(createChannel(String.class, ca.getChannel()), ca.getValue(), new ComparatorREGEX(), timeout)); + } + else{ + logger.warning("Operation "+operation+" wity type "+type+" for action is not supported"); + } + } + else if(operation.equals("waitOR")){ + Long timeout = null ; // Default timeout = wait forever + if(ca.getTimeout()!=null){ + timeout = Math.round(ca.getTimeout()*1000); + } + + if(type.equals("Integer")){ + alist.add(new ChannelAccessCondition<>(createChannel(Integer.class,ca.getChannel()), new Integer(ca.getValue()), new ComparatorOR(), timeout)); + } + else{ + logger.warning("Operation "+operation+" wity type "+type+" for action is not supported"); + } + } + else if(operation.equals("waitAND")){ + Long timeout = null ; // Default timeout = wait forever + if(ca.getTimeout()!=null){ + timeout = Math.round(ca.getTimeout()*1000); + } + if(type.equals("Integer")){ + alist.add(new ChannelAccessCondition<>(createChannel(Integer.class,ca.getChannel()), new Integer(ca.getValue()), new ComparatorAND(), timeout)); + } + else { + logger.warning("Operation "+operation+" wity type "+type+" for action is not supported"); + } + } + else{ + // Operation not supported + logger.warning("Operation "+operation+" for action is not supported"); + } + + // Translate delay attribute to delay action + if(ca.getDelay()!=null){ + Double x = ca.getDelay()*1000; + alist.add(new Delay(x.longValue())); + } + + } + else if(a instanceof ShellAction){ + ShellAction sa = (ShellAction) a; + String com = sa.getCommand().replaceAll("\\$\\{DATAFILE\\}", datafile.getAbsolutePath()); + com = com.replaceAll("\\$\\{FILENAME\\}", datafile.getName().replaceAll("\\.\\w*$", "")); + ch.psi.fda.core.actions.ShellAction action = new ch.psi.fda.core.actions.ShellAction(com); + action.setCheckExitValue(sa.isCheckExitValue()); + action.setExitValue(sa.getExitValue()); + alist.add(action); + } + else if(a instanceof ScriptAction){ + + ScriptAction sa = (ScriptAction) a; + + // TODO set global variables DATAFILE and FILENAME + + // TODO create Jython Action + Map> mapping = new HashMap<>(); + for(ChannelParameterMapping ma: sa.getMapping()){ + if(ma.getType().equals("String")){ + mapping.put(ma.getVariable(), createChannel(String.class, ma.getChannel())); + } + else if(ma.getType().equals("Integer")){ + mapping.put(ma.getVariable(), createChannel(Integer.class, ma.getChannel())); + } + else if(ma.getType().equals("Double")){ + mapping.put(ma.getVariable(), createChannel(Double.class, ma.getChannel())); + } + else{ + logger.warning("Channel type ["+ma.getType()+"] is not supported for mapping"); + } + } + + Map gobjects = new HashMap<>(); + gobjects.put("FILENAME", datafile.getName().replaceAll("\\.\\w*$", "")); + gobjects.put("DATAFILE", datafile.getAbsoluteFile()); + ch.psi.fda.core.actions.JythonAction ja = new ch.psi.fda.core.actions.JythonAction(sa.getScript(), mapping, gobjects); + + alist.add(ja); + } + } + return(alist); + } + + /** + * Map a discrete step dimension onto a actor sensor loop + * @param dimension + * @return + */ + private ActorSensorLoop mapDiscreteStepDimension(DiscreteStepDimension dimension){ + ActorSensorLoop aLoop = new ActorSensorLoop(dimension.isZigzag()); + // Set split flag of action loop (default is false) + aLoop.setDataGroup(dimension.isDataGroup()); + + // Mapping dimension pre-actions + aLoop.getPreActions().addAll(mapActions(dimension.getPreAction())); + + Long moveTimeout = this.configuration.getActorMoveTimeout(); + + // Mapping positioners + Double stime = 0d; + for(DiscreteStepPositioner p: dimension.getPositioner()){ + + if(p.getSettlingTime()>stime){ + stime = p.getSettlingTime(); + } + + if(p instanceof LinearPositioner){ + LinearPositioner lp =(LinearPositioner) p; + ChannelAccessLinearActuator a; + if(lp.getType().equals("String")){ + a = new ChannelAccessLinearActuator(createChannel(Double.class, lp.getName()), createChannel(String.class, lp.getDone()), lp.getDoneValue(), lp.getDoneDelay(), lp.getStart(), lp.getEnd(), lp.getStepSize(), moveTimeout); + } + else if(lp.getType().equals("Double")){ + a = new ChannelAccessLinearActuator(createChannel(Double.class, lp.getName()), createChannel(Double.class,lp.getDone()), Double.parseDouble(lp.getDoneValue()), lp.getDoneDelay(), lp.getStart(), lp.getEnd(), lp.getStepSize(), moveTimeout); + } + else{ + // Default + a = new ChannelAccessLinearActuator(createChannel(Double.class, lp.getName()), createChannel(Integer.class,lp.getDone()), Integer.parseInt(lp.getDoneValue()), lp.getDoneDelay(), lp.getStart(), lp.getEnd(), lp.getStepSize(), moveTimeout); + } + + a.setAsynchronous(lp.isAsynchronous()); + Actor actuator = a; + + aLoop.getActors().add(actuator); + + // Add a sensor for the readback + String name = lp.getReadback(); + if(name==null){ + name = lp.getName(); + } + ChannelAccessSensor sensor = new ChannelAccessSensor(lp.getId(), createChannel(Double.class, name), configModel.isFailOnSensorError()); + aLoop.getSensors().add(sensor); + } + else if(p instanceof FunctionPositioner){ + FunctionPositioner lp =(FunctionPositioner) p; + + // Create function object + JythonFunction function = mapFunction(lp.getFunction()); + + + // Create actuator + ChannelAccessFunctionActuator a; + if(lp.getType().equals("String")){ + a = new ChannelAccessFunctionActuator(createChannel(Double.class,lp.getName()), createChannel(String.class,lp.getDone()), lp.getDoneValue(), lp.getDoneDelay(), function, lp.getStart(), lp.getEnd(), lp.getStepSize(), moveTimeout); + } + else if(lp.getType().equals("Double")){ + a = new ChannelAccessFunctionActuator(createChannel(Double.class, lp.getName()), createChannel(Double.class, lp.getDone()), Double.parseDouble(lp.getDoneValue()), lp.getDoneDelay(), function, lp.getStart(), lp.getEnd(), lp.getStepSize(), moveTimeout); + } + else{ + // Default + a = new ChannelAccessFunctionActuator(createChannel(Double.class, lp.getName()), createChannel(Integer.class, lp.getDone()), Integer.parseInt(lp.getDoneValue()), lp.getDoneDelay(), function, lp.getStart(), lp.getEnd(), lp.getStepSize(), moveTimeout); + } + + a.setAsynchronous(lp.isAsynchronous()); + Actor actuator = a; + + aLoop.getActors().add(actuator); + + // Add a sensor for the readback + String name = lp.getReadback(); + if(name==null){ + name = lp.getName(); + } + ChannelAccessSensor sensor = new ChannelAccessSensor(lp.getId(), createChannel(Double.class, name), configModel.isFailOnSensorError()); + aLoop.getSensors().add(sensor); + } + else if (p instanceof ArrayPositioner){ + ArrayPositioner ap = (ArrayPositioner) p; + String[] positions = (ap.getPositions().trim()).split(" +"); + double[] table = new double[positions.length]; + for(int i=0;i a; + if(p.getType().equals("String")){ + a = new ChannelAccessTableActuator(createChannel(Double.class, p.getName()), createChannel(String.class, p.getDone()), p.getDoneValue(), p.getDoneDelay(), table, moveTimeout); + } + else if(p.getType().equals("Double")){ + a = new ChannelAccessTableActuator(createChannel(Double.class, p.getName()), createChannel(Double.class, p.getDone()), Double.parseDouble(p.getDoneValue()), p.getDoneDelay(), table, moveTimeout); + } + else{ + // Default + a = new ChannelAccessTableActuator(createChannel(Double.class, p.getName()), createChannel(Integer.class, p.getDone()), Integer.parseInt(p.getDoneValue()), p.getDoneDelay(), table, moveTimeout); + } + + a.setAsynchronous(p.isAsynchronous()); + Actor actuator = a; + + aLoop.getActors().add(actuator); + + // Add a sensor for the readback + String name = ap.getReadback(); + if(name==null){ + name = ap.getName(); + } + ChannelAccessSensor sensor = new ChannelAccessSensor(ap.getId(), createChannel(Double.class, name), configModel.isFailOnSensorError()); + aLoop.getSensors().add(sensor); + } + else if (p instanceof RegionPositioner){ + RegionPositioner rp = (RegionPositioner) p; + + ComplexActuator actuator = new ComplexActuator(); + /* + * Regions are translated into a complex actor consisting of a LinearActuator + * If consecutive regions are overlapping, i.e. end point of region a equals the + * start point of region b then the start point for the LinearActuator of region b + * is changes to its next step (start+/-stepSize depending on whether end position of the + * region is > or < start of the region) + */ + Region lastRegion = null; + for(Region r: rp.getRegion()){ + // Normal region + if(r.getFunction()==null){ + + // Check whether regions are consecutive + double start = r.getStart(); + if(lastRegion!=null && start == lastRegion.getEnd()){ // TODO verify whether double comparison is ok + if(r.getStart() act; + if(rp.getType().equals("String")){ + act = new ChannelAccessLinearActuator(createChannel(Double.class, rp.getName()), createChannel(String.class, rp.getDone()), rp.getDoneValue(), rp.getDoneDelay(), start, r.getEnd(), r.getStepSize(), moveTimeout); + } + else if(rp.getType().equals("Double")){ + act = new ChannelAccessLinearActuator(createChannel(Double.class, rp.getName()), createChannel(Double.class, rp.getDone()), Double.parseDouble(rp.getDoneValue()), rp.getDoneDelay(), start, r.getEnd(), r.getStepSize(), moveTimeout); + } + else{ + act = new ChannelAccessLinearActuator(createChannel(Double.class, rp.getName()), createChannel(Integer.class, rp.getDone()), Integer.parseInt(rp.getDoneValue()), rp.getDoneDelay(), start, r.getEnd(), r.getStepSize(), moveTimeout); + } + + act.setAsynchronous(rp.isAsynchronous()); + Actor a = act; + + ComplexActuator ca = new ComplexActuator(); + ca.getActors().add(a); + ca.getPreActions().addAll(mapActions(r.getPreAction())); + actuator.getActors().add(ca); + lastRegion = r; + } + else{ + // Function based region + + // Cannot check whether the regions are consecutive as the function + // used might change the start value to something else + // [THIS LIMITATION NEEDS TO BE SOMEHOW RESOLVED IN THE NEXT VERSIONS] + JythonFunction function = mapFunction(r.getFunction()); + ChannelAccessFunctionActuator act; + if(rp.getType().equals("String")){ + act = new ChannelAccessFunctionActuator(createChannel(Double.class,rp.getName()), createChannel(String.class,rp.getDone()), rp.getDoneValue(), rp.getDoneDelay(), function, r.getStart(), r.getEnd(), r.getStepSize(), moveTimeout); + } + else if(rp.getType().equals("Double")){ + act = new ChannelAccessFunctionActuator(createChannel(Double.class, rp.getName()), createChannel(Double.class, rp.getDone()), Double.parseDouble(rp.getDoneValue()), rp.getDoneDelay(), function, r.getStart(), r.getEnd(), r.getStepSize(), moveTimeout); + } + else{ + // Default + act = new ChannelAccessFunctionActuator(createChannel(Double.class, rp.getName()), createChannel(Integer.class, rp.getDone()), Integer.parseInt(rp.getDoneValue()), rp.getDoneDelay(), function, r.getStart(), r.getEnd(), r.getStepSize(), moveTimeout); + } + + act.setAsynchronous(rp.isAsynchronous()); + Actor a = act; + + ComplexActuator ca = new ComplexActuator(); + ca.getActors().add(a); + ca.getPreActions().addAll(mapActions(r.getPreAction())); + actuator.getActors().add(ca); + lastRegion = r; + } + } + aLoop.getActors().add(actuator); + + // Add a sensor for the readback + String name = rp.getReadback(); + if(name==null){ + name = rp.getName(); + } + ChannelAccessSensor sensor = new ChannelAccessSensor(rp.getId(), createChannel(Double.class, name), configModel.isFailOnSensorError()); + aLoop.getSensors().add(sensor); + } + else if(p instanceof PseudoPositioner){ + PseudoPositioner pp =(PseudoPositioner) p; + PseudoActuatorSensor actorSensor = new PseudoActuatorSensor(pp.getId(), pp.getCounts()); + + // Register as actor + aLoop.getActors().add(actorSensor); + + // Register as sensor + aLoop.getSensors().add(actorSensor); + } + else{ + // Not supported + logger.warning("Mapping for "+p.getClass().getName()+" not available"); + } + } + + // Translate settling time to post actor action + // Only add the post actor action if the settling time is > 0 + if(stime>0){ + Double delay = stime*1000; + aLoop.getPostActorActions().add(new Delay(delay.longValue())); + } + + // Map actions between positioner and detector + aLoop.getPreSensorActions().addAll(mapActions(dimension.getAction())); + + + // Map guard (if specified) + Guard g = dimension.getGuard(); + if(g != null){ + // Map conditions + List> conditions = new ArrayList<>(); + for(GuardCondition con: g.getCondition()){ + if(con.getType().equals("Integer")){ + conditions.add(new ChannelAccessGuardCondition(createChannel(Integer.class, con.getChannel()), new Integer(con.getValue()))); + } + else if(con.getType().equals("Double")){ + conditions.add(new ChannelAccessGuardCondition(createChannel(Double.class, con.getChannel()), new Double(con.getValue()))); + } + else{ + conditions.add(new ChannelAccessGuardCondition(createChannel(String.class, con.getChannel()), con.getValue())); + } + } + // Create guard and add to loop + ChannelAccessGuard guard = new ChannelAccessGuard(conditions); + aLoop.setGuard(guard); + } + + // Map detectors + for(Detector detector : dimension.getDetector()){ + mapDetector(aLoop, detector); + } + + + // Mapping dimension post-actions + aLoop.getPostActions().addAll(mapActions(dimension.getPostAction())); + + + return aLoop; + } + + /** + * Map function + * @param f Function object in the model + * @return Internal function object + */ + private JythonFunction mapFunction(Function f){ + HashMap map = new HashMap(); + for(ParameterMapping m: f.getMapping()){ + if(m instanceof VariableParameterMapping){ + VariableParameterMapping vp = (VariableParameterMapping)m; + Variable v = (Variable)vp.getName(); + JythonGlobalVariable var = jVariableDictionary.get(v.getName()); + var.setValue(v.getValue()); + map.put(vp.getVariable(), var); + } + } + JythonFunction function = new JythonFunction(f.getScript(), map); + return function; + } + + private void mapDetector(ActorSensorLoop aLoop, Detector detector){ + if(detector instanceof ScalarDetector){ + ScalarDetector sd = (ScalarDetector) detector; + + // Add pre actions + aLoop.getPreSensorActions().addAll(mapActions(sd.getPreAction())); + + // Add sensor + Sensor sensor; + if(sd.getType().equals("String")){ + sensor = new ChannelAccessSensor<>(sd.getId(), createChannel(String.class,sd.getName()), configModel.isFailOnSensorError()); + } + else{ + sensor = new ChannelAccessSensor<>(sd.getId(), createChannel(Double.class,sd.getName()), configModel.isFailOnSensorError()); + } + + aLoop.getSensors().add(sensor); + } + else if (detector instanceof ArrayDetector){ + ArrayDetector ad = (ArrayDetector) detector; + + // Add pre actions + aLoop.getPreSensorActions().addAll(mapActions(ad.getPreAction())); + + // Ad sensor + Sensor sensor = new ChannelAccessSensor<>(ad.getId(), createChannel(double[].class, ad.getName(), ad.getArraySize()), configModel.isFailOnSensorError()); + aLoop.getSensors().add(sensor); + } + else if (detector instanceof DetectorOfDetectors){ + DetectorOfDetectors dd = (DetectorOfDetectors) detector; + + // Add pre actions + aLoop.getPreSensorActions().addAll(mapActions(dd.getPreAction())); + + for(Detector d: dd.getDetector()){ + // Recursively call mapping method + mapDetector(aLoop, d); + } + } + else if (detector instanceof Timestamp){ + Timestamp dd = (Timestamp) detector; + + // Ad sensor + TimestampSensor sensor = new TimestampSensor(dd.getId()); + aLoop.getSensors().add(sensor); + } + else{ + // Not supported + logger.warning("Detector type "+detector.getClass().getName()+" not supported"); + } + } + + /** + * Map OTF dimension onto a OTF loop + * @param dimension + * @return + */ + private ActionLoop mapContinuousDimension(ContinuousDimension dimension) { + + ActionLoop aLoop = null; + + boolean hcrOnly = true; + for (SimpleScalarDetector detector : dimension.getDetector()) { + if (detector.isScr()) { + hcrOnly = false; + break; + } + } + + // Create loop + boolean zigZag = dimension.isZigzag(); // default value is false + + CrlogicLoopStream actionLoop = new CrlogicLoopStream(cservice, configuration.getCrlogicPrefix(), configuration.getCrlogicIoc(), zigZag); + + actionLoop.getPreActions().addAll(mapActions(dimension.getPreAction())); + + // Map positioner + ContinuousPositioner p = dimension.getPositioner(); + double backlash = 0; + if (p.getAdditionalBacklash() != null) { + backlash = p.getAdditionalBacklash(); + } + actionLoop.setActuator(p.getId(), p.getName(), p.getReadback(), p.getStart(), p.getEnd(), p.getStepSize(), p.getIntegrationTime(), backlash); + + // Map sensors + // ATTENTION: the sequence of the mapping depends on the sequence in the + // schema file ! + for (SimpleScalarDetector detector : dimension.getDetector()) { + if (!detector.isScr()) { + actionLoop.getSensors().add(new CrlogicResource(detector.getId(), detector.getName())); + } + } + + for (ScalerChannel detector : dimension.getScaler()) { + actionLoop.getSensors().add(new CrlogicResource(detector.getId(), "SCALER" + detector.getChannel(), true)); + } + + Timestamp tdetector = dimension.getTimestamp(); + if (tdetector != null) { + actionLoop.getSensors().add(new CrlogicResource(tdetector.getId(), "TIMESTAMP")); + } + + actionLoop.getPostActions().addAll(mapActions(dimension.getPostAction())); + + if (hcrOnly) { + // There are no additional channels to be read out while taking data + // via hcrlogic + // Therefore we just register the hcr loop as action loop + + aLoop = actionLoop; + } else { + List> sensors = new ArrayList<>(); + List ids = new ArrayList<>(); + + for (SimpleScalarDetector detector : dimension.getDetector()) { + if (detector.isScr()) { + ids.add(detector.getId()); + sensors.add(createChannel(DoubleTimestamp.class, detector.getName(), true)); + } + } + // Create soft(ware) based crlogic + ScrlogicLoop scrlogic = new ScrlogicLoop(ids, sensors); + + // Create parallel logic + ParallelCrlogic pcrlogic = new ParallelCrlogic(actionLoop, scrlogic); + + aLoop = pcrlogic; + } + return aLoop; + } + + private Channel createChannel(Class type, String name, boolean monitor){ + try { + if(name== null){ + return null; + } + + Channel c = cservice.createChannel(new ChannelDescriptor(type, name, monitor) ); + channels.add(c); + return c; + } catch (ChannelException | InterruptedException | TimeoutException e) { + throw new RuntimeException("Unable to create channel: "+name,e); + } + + } + + /** + * Create channel and remember to be able to destroy channels at the end + * @param name + * @param type + * @return null if the name of the channel is null, otherwise the channel + */ + private Channel createChannel(Class type, String name){ + try { + if(name== null){ + return null; + } + + Channel c = cservice.createChannel(new ChannelDescriptor(type, name) ); + channels.add(c); + return c; + } catch (ChannelException | InterruptedException | TimeoutException e) { + throw new RuntimeException("Unable to create channel: "+name,e); + } + + } + + private Channel createChannel(Class type, String name, int size){ + try { + if(name== null){ + return null; + } + + Channel c = cservice.createChannel(new ChannelDescriptor(type, name, false, size) ); + channels.add(c); + return c; + } catch (ChannelException | InterruptedException | TimeoutException e) { + throw new RuntimeException("Unable to create channel: "+name,e); + } + + } +} diff --git a/src/main/java/ch/psi/fda/aq/AcquisitionConfiguration.java b/src/main/java/ch/psi/fda/aq/AcquisitionConfiguration.java new file mode 100644 index 0000000..a9b86d2 --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/AcquisitionConfiguration.java @@ -0,0 +1,187 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Properties; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class AcquisitionConfiguration { + + + private static final Logger logger = Logger.getLogger(AcquisitionConfiguration.class.getName()); + + public final static String FDA_CONFIG_FILE = "ch.psi.fda.xscan.config.file"; + + private String crlogicPrefix; + private String crlogicIoc; + + /** + * Base directory for data. The directory may contain date macros. The string may contain any @see java.text.SimpleDateFormat + * patterns within ${ } brackets. The macros are resolved with the actual time while the get method + * of this property is called. + */ + private String dataBaseDirectory = System.getProperty("user.home"); + /** + * Prefix of the data file. The prefix may contain date macros. The string may contain any @see java.text.SimpleDateFormat + * patterns within ${ } brackets. The macros are resolved with the actual time while the get method + * of this property is called. + */ + private String dataFilePrefix = ""; + + /** + * Maximum time for a actor move + */ + private Long actorMoveTimeout = 600000l; // 10 Minutes maximum move time + + private String smptServer; + + + /** + * Default Constructor + * The constructor will read the configuration from the /fda.properties file (resource) located in the classpath. + */ + public AcquisitionConfiguration(){ + loadConfiguration(System.getProperty(FDA_CONFIG_FILE)); + } + + /** + * Load configuration from properties file + */ + public void loadConfiguration(String file) { + Properties properties = new Properties(); + + File cfile = null; + // Only read in the property file if a file is specified + if(file != null){ + cfile = new File(file); + try { + properties.load(new FileReader(cfile)); + } catch (FileNotFoundException e) { + throw new RuntimeException("Configuration file "+file+" not found", e); + } catch (IOException e) { + throw new RuntimeException("Cannot read configuration file "+file, e); + } + } + else{ + logger.warning("No configfile specified !"); + } + + // The defaults are set here + crlogicPrefix = properties.getProperty(AcquisitionConfiguration.class.getPackage().getName()+".crlogic.prefix", ""); + crlogicIoc= properties.getProperty(AcquisitionConfiguration.class.getPackage().getName()+".crlogic.ioc", ""); + + dataBaseDirectory = properties.getProperty(AcquisitionConfiguration.class.getPackage().getName()+".data.dir","."); + if(cfile!=null && dataBaseDirectory.matches("^\\.\\.?/.*")){ // if basedir starts with . or .. we assume the data directory to be relative to the directory of the configuration file + dataBaseDirectory = cfile.getParentFile().getAbsolutePath()+"/"+dataBaseDirectory; + } + dataFilePrefix = properties.getProperty(AcquisitionConfiguration.class.getPackage().getName()+".data.filePrefix",""); + + + actorMoveTimeout = new Long(properties.getProperty(AcquisitionConfiguration.class.getPackage().getName()+".actorMoveTimeout","600000")); + + smptServer= properties.getProperty(AcquisitionConfiguration.class.getPackage().getName()+".notification.host","mail.psi.ch"); + + } + + /** + * Replace ${name} and ${date} macro given string + * @param string + * @param date + * @param name + * @return + */ + public String replaceMacros(String string, Date date, String name){ + String newString = string; + + // Replace scan name macros + newString = newString.replaceAll("\\$\\{name\\}", name); + + + // Replace date macros + Pattern pattern = Pattern.compile("\\$\\{[a-z,A-Z,-,_,:]*\\}"); + Matcher matcher = pattern.matcher(newString); + while(matcher.find()){ + String datePattern = matcher.group(); + datePattern = datePattern.replaceAll("\\$\\{", ""); + datePattern = datePattern.replaceAll("\\}", ""); + SimpleDateFormat datef = new SimpleDateFormat(datePattern); + newString = matcher.replaceFirst(datef.format(date)); + matcher = pattern.matcher(newString); + } + return newString; + } + + + public String getCrlogicPrefix() { + return crlogicPrefix; + } + + public void setCrlogicPrefix(String crlogicPrefix) { + this.crlogicPrefix = crlogicPrefix; + } + + public void setCrlogicIoc(String crlogicIoc) { + this.crlogicIoc = crlogicIoc; + } + public String getCrlogicIoc() { + return crlogicIoc; + } + + + public String getDataBaseDirectory() { + return dataBaseDirectory; + } + + public void setDataBaseDirectory(String dataBaseDirectory) { + this.dataBaseDirectory = dataBaseDirectory; + } + + public String getDataFilePrefix() { + return dataFilePrefix; + } + + public void setDataFilePrefix(String dataFilePrefix) { + this.dataFilePrefix = dataFilePrefix; + } + + public Long getActorMoveTimeout() { + return actorMoveTimeout; + } + + public void setActorMoveTimeout(Long actorMoveTimeout) { + this.actorMoveTimeout = actorMoveTimeout; + } + + public String getSmptServer() { + return smptServer; + } + + public void setSmptServer(String smptServer) { + this.smptServer = smptServer; + } +} diff --git a/src/main/java/ch/psi/fda/aq/Collector.java b/src/main/java/ch/psi/fda/aq/Collector.java new file mode 100644 index 0000000..664f4cf --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/Collector.java @@ -0,0 +1,102 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Message; +import ch.psi.fda.messages.Metadata; +import ch.psi.fda.messages.StreamDelimiterMessage; + +/** + * Collector class that is collecting and merging data from different Queues. + */ +public class Collector { + + private EventBus bus; + private List listeners = new ArrayList<>(); + + private List metadata; + private boolean first = true; + + public Collector(EventBus b){ + this.bus = b; + } + + public void addEventBus(EventBus b){ + MessageListener l = new MessageListener(); + listeners.add(l); + b.register(l); + } + + + + private class MessageListener{ + + private DataMessage message; + + @Subscribe + public void onMessage(Message message){ + int level = listeners.indexOf(this); + if(message instanceof DataMessage){ + this.message = (DataMessage) message; + + if(level==0){ + if(first){ + metadata = new ArrayList<>(); + for(int i=listeners.size()-1;i>=0;i--){ + // Correct/Add dimension information + for(Metadata m: listeners.get(i).getMessage().getMetadata()){ + m.setDimension(i); + } + metadata.addAll(listeners.get(i).getMessage().getMetadata()); + } + + } + DataMessage m = new DataMessage(metadata); + for(int i=listeners.size()-1;i>=0;i--){ + m.getData().addAll(listeners.get(i).getMessage().getData()); + } + bus.post(m); + } + } + if(message instanceof EndOfStreamMessage){ + + StreamDelimiterMessage ddm = new StreamDelimiterMessage(level, ((EndOfStreamMessage)message).isIflag()); + bus.post(ddm); + if(level==(listeners.size()-1)){ // if highest dimension then send end of stream + bus.post(new EndOfStreamMessage()); + } + } + } + + public DataMessage getMessage(){ + return message; + } + } + +} + diff --git a/src/main/java/ch/psi/fda/aq/Manipulator.java b/src/main/java/ch/psi/fda/aq/Manipulator.java new file mode 100644 index 0000000..8fcbb2d --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/Manipulator.java @@ -0,0 +1,79 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import java.util.ArrayList; +import java.util.List; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.core.Manipulation; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.Message; +import ch.psi.fda.messages.Metadata; + +/** + * Applies manipulations to the data stream + */ +public class Manipulator { + + private EventBus bus; + + private final List manipulations; + private boolean first = true; + private List metadata = new ArrayList<>(); + + public Manipulator(EventBus b, List manipulations){ + this.bus = b; + this.manipulations = manipulations; + } + + @Subscribe + public void onMessage(Message message){ + if(message instanceof DataMessage){ + if(first){ + first=false; + + metadata.addAll(((DataMessage) message).getMetadata()); + + for(Manipulation manipulation: this.manipulations){ + manipulation.initialize(this.metadata); + + // Add manipulation id to metadata + this.metadata.add(new Metadata(manipulation.getId(),0)); // Calculated component always belongs to lowes dimension + } + } + + + DataMessage dm = (DataMessage) message; +// message = new DataMessage(metadata); + for(Manipulation manipulation: manipulations){ +// ((DataMessage)message).getData().add(manipulation.execute(dm)); + dm.getData().add(manipulation.execute(dm)); + + // Need to update the metadata of the message + dm.setMetadata(this.metadata); + } + } + bus.post(message); + //System.out.println(Thread.currentThread()); + } +} diff --git a/src/main/java/ch/psi/fda/aq/NotificationAgent.java b/src/main/java/ch/psi/fda/aq/NotificationAgent.java new file mode 100644 index 0000000..f933152 --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/NotificationAgent.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2011 Paul Scherrer Institute + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 this program. If not, see . + */ + +package ch.psi.fda.aq; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.mail.Message; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.Transport; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeMessage; + +import ch.psi.fda.model.v1.Recipient; + +/** + * Agent to send out notifications to specified recipients. + */ +public class NotificationAgent { + + private final static String smsPostfix = "@sms.switch.ch"; + + private Properties properties; + private String fromAddress; + private final List recipients = new ArrayList(); + + /** + * Constructor + * @param host SMTP server to send notifications to + * @param recipients List of recipients + * @param from Address string that will show up in the from field. For example: fda@psi.ch. This argument must not contain white spaces + */ + public NotificationAgent(String host, String from){ + + fromAddress = from; + + properties = new Properties(); + properties.put("mail.smtp.host", host); + } + + + + public void sendNotification(String aSubject, String aBody, boolean error, boolean success) { + + for(Recipient recipient: recipients){ + + if((error && recipient.isError()) || (success && recipient.isSuccess())){ + String receiver; + + // Verify mail recipients + if(recipient.getValue().matches("[0-9,\\\\.,-,a-z,A-Z]*@[0-9,\\\\.,a-z,A-Z]*")){ + receiver = recipient.getValue(); + } + else if(recipient.getValue().matches("[0-9]+")){ + // Assume that it is a SMS number + receiver = recipient.getValue() + smsPostfix; + } + else{ + Logger.getLogger(NotificationAgent.class.getName()).log(Level.WARNING, "Invalid email address"); + continue; + } + + + Logger.getLogger(NotificationAgent.class.getName()).log(Level.INFO, "Send notification to " + receiver); + + //Here, no Authenticator argument is used (it is null). + //Authenticators are used to prompt the user for user + //name and password. + Session session = Session.getDefaultInstance(properties, null); + MimeMessage message = new MimeMessage(session); + try { + //The "from" address may be set in code, or set in the + //config file under "mail.from" ; here, the latter style is used + message.setFrom( new InternetAddress(fromAddress) ); + + message.addRecipient(Message.RecipientType.TO, new InternetAddress(receiver)); + message.setSubject(aSubject); + message.setText(aBody); + Transport.send(message); + } catch (MessagingException ex) { + Logger.getLogger(NotificationAgent.class.getName()).log(Level.WARNING, "Failed to send notification to " + receiver, ex); + } + } + } + } + + public List getRecipients() { + return recipients; + } + +} diff --git a/src/main/java/ch/psi/fda/aq/XScanContainer.java b/src/main/java/ch/psi/fda/aq/XScanContainer.java new file mode 100644 index 0000000..44ac42f --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/XScanContainer.java @@ -0,0 +1,50 @@ +package ch.psi.fda.aq; + +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.EContainer; +import ch.psi.fda.model.v1.Configuration; +import ch.psi.jcae.ChannelService; + +public class XScanContainer implements EContainer { + + private final Acquisition acquisition; + + private EventBus bus; + private Configuration xscanConfiguration; + + public XScanContainer(ChannelService cservice, AcquisitionConfiguration config, EventBus bus, Configuration xscanConfiguration){ + acquisition = new Acquisition(cservice, config); + this.bus = bus; + this.xscanConfiguration = xscanConfiguration; + } + + @Override + public void initialize() { + acquisition.initalize(bus, xscanConfiguration); + } + + @Override + public void execute() { + try { + acquisition.execute(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + @Override + public void abort() { + acquisition.abort(); + } + + @Override + public void destroy() { + acquisition.destroy(); + } + + @Override + public boolean isActive() { + return acquisition.isActive(); + } +} diff --git a/src/main/java/ch/psi/fda/aq/XScanDescriptor.java b/src/main/java/ch/psi/fda/aq/XScanDescriptor.java new file mode 100644 index 0000000..2ea139b --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/XScanDescriptor.java @@ -0,0 +1,29 @@ +package ch.psi.fda.aq; + +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +import ch.psi.fda.edescriptor.EDescriptor; +import ch.psi.fda.model.v1.Configuration; + +@XmlRootElement(name="edescriptor") +@XmlType(name="edescriptor") +public class XScanDescriptor implements EDescriptor { + + private Configuration configuration; + + public XScanDescriptor(){ + } + + public XScanDescriptor(Configuration configuration){ + this.configuration = configuration; + } + + + public Configuration getConfiguration() { + return configuration; + } + public void setConfiguration(Configuration configuration) { + this.configuration = configuration; + } +} diff --git a/src/main/java/ch/psi/fda/aq/XScanDescriptorProvider.java b/src/main/java/ch/psi/fda/aq/XScanDescriptorProvider.java new file mode 100644 index 0000000..a68a156 --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/XScanDescriptorProvider.java @@ -0,0 +1,339 @@ +package ch.psi.fda.aq; + +import java.io.File; +import java.util.List; +import java.util.logging.Logger; + +import ch.psi.fda.DescriptorProvider; +import ch.psi.fda.edescriptor.EDescriptor; +import ch.psi.fda.model.ModelManager; +import ch.psi.fda.model.v1.ArrayDetector; +import ch.psi.fda.model.v1.Configuration; +import ch.psi.fda.model.v1.ContinuousPositioner; +import ch.psi.fda.model.v1.Data; +import ch.psi.fda.model.v1.Detector; +import ch.psi.fda.model.v1.LinearPositioner; +import ch.psi.fda.model.v1.Positioner; +import ch.psi.fda.model.v1.PseudoPositioner; +import ch.psi.fda.model.v1.Visualization; +import ch.psi.fda.vdescriptor.LinePlot; +import ch.psi.fda.vdescriptor.VDescriptor; +import ch.psi.fda.vdescriptor.XYSeries; +import ch.psi.fda.vdescriptor.XYZSeries; +import ch.psi.fda.vdescriptor.YSeries; +import ch.psi.fda.vdescriptor.YZSeries; + +public class XScanDescriptorProvider implements DescriptorProvider { + + private static final Logger logger = Logger.getLogger(XScanDescriptorProvider.class.getName()); + + private EDescriptor edescriptor; + private VDescriptor vdescriptor; + + @Override + public void load(File... files) { + + if(files.length<1 || files[0]==null){ + throw new IllegalArgumentException("There need to be at lease one file specified"); + } + File file = files[0]; + + if(!file.exists()){ + throw new IllegalArgumentException("File "+file.getAbsolutePath()+" does not exist"); + } + + Configuration c; + try { + c = ModelManager.unmarshall(file); + } catch (Exception e) { + throw new UnsupportedOperationException("Unable to deserialize configuration: "+e.getMessage(), e); + } + + // Set data file name + // Determine name used for the data file + String name = file.getName(); + name = name.replaceAll("\\.xml$", ""); + + if(c.getData()!=null){ + Data data = c.getData(); + // Only update filename if no name is specified in xml file + if(data.getFileName()==null){ + data.setFileName(name); + } + } + else{ + Data data = new Data(); + data.setFileName(name); + c.setData(data); + } + + +// // Override number of executions +// if(iterations != null){ +// c.setNumberOfExecution(iterations); +// } + // Fix configuration if iterations is specified with 0 and no iterations option is specified + if(c.getNumberOfExecution()==0){ + c.setNumberOfExecution(1); + } + + this.edescriptor = new XScanDescriptor(c); + this.vdescriptor = mapVisualizations(c.getVisualization()); + + } + + /** + * Create a vdescriptor out of the scan description + * @param vl + * @return + */ + public static VDescriptor mapVisualizations(List vl){ + VDescriptor vd = new VDescriptor(); + + + for(Visualization v: vl){ + if(v instanceof ch.psi.fda.model.v1.LinePlot){ + ch.psi.fda.model.v1.LinePlot lp = (ch.psi.fda.model.v1.LinePlot) v; + + String x = getId(lp.getX()); + + LinePlot lineplot = new LinePlot(lp.getTitle()); + List l = lp.getY(); + for(Object o: l){ + String y = getId(o); + lineplot.getData().add(new XYSeries(x, y)); + } + + vd.getPlots().add(lineplot); + } + else if(v instanceof ch.psi.fda.model.v1.LinePlotArray){ + // Array visualization + ch.psi.fda.model.v1.LinePlotArray lp = (ch.psi.fda.model.v1.LinePlotArray) v; + + LinePlot lineplot = new LinePlot(lp.getTitle()); + // Create data filter for visualization + List l = lp.getY(); + for(Object o: l){ + String idY = getId(o); + + // TODO Need to actually check if minX of + lineplot.setMinX(new Double(lp.getOffset())); + lineplot.setMaxX(new Double(lp.getOffset()+lp.getSize())); + lineplot.setMaxSeries(lp.getMaxSeries()); + lineplot.getData().add(new YSeries(idY)); + } + vd.getPlots().add(lineplot); + } + else if(v instanceof ch.psi.fda.model.v1.MatrixPlot){ + + // MatrixPlot does currently not support RegionPositioners because of the + // plotting problems this would cause. If regions of the positioner have different + // step sizes it is not easily possible (without (specialized) rasterization) to plot the data. + + ch.psi.fda.model.v1.MatrixPlot mp = (ch.psi.fda.model.v1.MatrixPlot) v; + + + double minX, maxX; + int nX; + double minY, maxY; + int nY; + + String idX, idY, idZ; + + // X Axis + if(mp.getX() instanceof LinearPositioner){ + LinearPositioner linp = ((LinearPositioner)mp.getX()); + idX = linp.getId(); + + minX = (Math.min(linp.getStart(), linp.getEnd())); + maxX = (Math.max(linp.getStart(), linp.getEnd())); + nX = ((int) Math.floor((Math.abs(maxX-minX))/linp.getStepSize()) + 1); + } + else if(mp.getX() instanceof PseudoPositioner){ + PseudoPositioner pp = ((PseudoPositioner)mp.getX()); + idX = pp.getId(); + minX = (1); // Count starts at 1 + maxX = (pp.getCounts()); + nX = (pp.getCounts()); + } + else if(mp.getX() instanceof ContinuousPositioner){ + ContinuousPositioner conp = ((ContinuousPositioner)mp.getX()); + idX = conp.getId(); + + minX = (Math.min(conp.getStart(), conp.getEnd())); + maxX = (Math.max(conp.getStart(), conp.getEnd())); + nX = ((int) Math.floor((Math.abs(maxX-minX))/conp.getStepSize()) + 1); + } + else{ + // Fail as we cannot determine the min, max and number of steps + throw new RuntimeException(mp.getX().getClass().getName()+" is not supported as x-axis of a MatrixPlot"); + } + + // Y Axis + if(mp.getY() instanceof LinearPositioner){ + LinearPositioner linp = ((LinearPositioner)mp.getY()); + idY = linp.getId(); + minY = (Math.min(linp.getStart(), linp.getEnd())); + maxY = (Math.max(linp.getStart(), linp.getEnd())); + nY = ((int) Math.floor((Math.abs(maxY-minY))/linp.getStepSize()) + 1); + } + else if(mp.getY() instanceof PseudoPositioner){ + PseudoPositioner pp = ((PseudoPositioner)mp.getY()); + idY = pp.getId(); + minY = (1); // Count starts at 1 + maxY = (pp.getCounts()); + nY = (pp.getCounts()); + } + else{ + // Fail as we cannot determine the min, max and number of steps + throw new RuntimeException(mp.getY().getClass().getName()+" is not supported as y-axis of a MatrixPlot"); + } + + // Z Dimension + idZ = getId(mp.getZ()); + + + ch.psi.fda.vdescriptor.MatrixPlot matrixplot = new ch.psi.fda.vdescriptor.MatrixPlot(mp.getTitle()); + matrixplot.setMinX(minX); + matrixplot.setMaxX(maxX); + matrixplot.setnX(nX); + matrixplot.setMinY(minY); + matrixplot.setMaxY(maxY); + matrixplot.setnY(nY); + matrixplot.setType(mp.getType()); + + matrixplot.getData().add(new XYZSeries(idX, idY, idZ)); + vd.getPlots().add(matrixplot); + } + else if(v instanceof ch.psi.fda.model.v1.MatrixPlotArray){ + // Support for 2D waveform plots + ch.psi.fda.model.v1.MatrixPlotArray mp = (ch.psi.fda.model.v1.MatrixPlotArray) v; + + // Get size of the array detector + int arraySize = 0; + Object o = mp.getZ(); + if(o instanceof ArrayDetector){ + ArrayDetector ad = (ArrayDetector) o; + arraySize = ad.getArraySize(); + } + else{ + // Workaround + arraySize = mp.getSize(); // of array is from a manipulation the size is not known. Then the size will indicate the size of the array to display + } + + int offset = mp.getOffset(); + // Determine size for array + int size = mp.getSize(); + if(size>0 && offset+size getEDescriptorClass() { + return XScanDescriptor.class; + } + +} diff --git a/src/main/java/ch/psi/fda/aq/XScanFactory.java b/src/main/java/ch/psi/fda/aq/XScanFactory.java new file mode 100644 index 0000000..09fd282 --- /dev/null +++ b/src/main/java/ch/psi/fda/aq/XScanFactory.java @@ -0,0 +1,36 @@ +package ch.psi.fda.aq; + +import javax.inject.Inject; + +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.EContainer; +import ch.psi.fda.EContainerFactory; +import ch.psi.fda.edescriptor.EDescriptor; +import ch.psi.jcae.ChannelService; + +public class XScanFactory implements EContainerFactory { + + @Inject + private ChannelService cservice; + + private AcquisitionConfiguration config = new AcquisitionConfiguration(); + + @Override + public boolean supportsEDescriptor(EDescriptor descriptor) { + return (descriptor instanceof XScanDescriptor); + } + + @Override + public EContainer getEContainer(EDescriptor descriptor, EventBus bus) { + + if(! (descriptor instanceof XScanDescriptor)){ + throw new IllegalArgumentException("Descriptor of type "+descriptor.getClass().getName()+" is not supported - descriptor need to be of type "+XScanDescriptor.class); + } + + XScanDescriptor xdescriptor = (XScanDescriptor) descriptor; + + return new XScanContainer(cservice, config, bus, xdescriptor.getConfiguration()); + } + +} diff --git a/src/main/java/ch/psi/fda/core/Action.java b/src/main/java/ch/psi/fda/core/Action.java new file mode 100644 index 0000000..a0c0fdf --- /dev/null +++ b/src/main/java/ch/psi/fda/core/Action.java @@ -0,0 +1,33 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +public interface Action { + + /** + * Execute logic of the action + */ + public void execute() throws InterruptedException; + + /** + * Abort the execution logic of the action + */ + public void abort(); +} diff --git a/src/main/java/ch/psi/fda/core/ActionLoop.java b/src/main/java/ch/psi/fda/core/ActionLoop.java new file mode 100644 index 0000000..2c6d376 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/ActionLoop.java @@ -0,0 +1,66 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +import java.util.List; + +import com.google.common.eventbus.EventBus; + +/** + * Loop of actions to accomplish a task. Depending on the loop + * actions may be executed in a different way. + */ +public interface ActionLoop extends Action { + + /** + * Prepare ActionLoop for execution. + */ + public void prepare(); + + /** + * Cleanup resources used by this ActionLoop while it was executed. + */ + public void cleanup(); + + /** + * Get the pre actions of the Loop + * @return pre actions + */ + public List getPreActions(); + + /** + * Get the post actions of the loop + * @return post actions + */ + public List getPostActions(); + + /** + * @return is a datagroup + */ + public boolean isDataGroup(); + + /** + * Set whether data of the loop belongs to a own data group + * @param dataGroup + */ + public void setDataGroup(boolean dataGroup); + + public EventBus getEventBus(); +} diff --git a/src/main/java/ch/psi/fda/core/Actor.java b/src/main/java/ch/psi/fda/core/Actor.java new file mode 100644 index 0000000..fc7c045 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/Actor.java @@ -0,0 +1,49 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +public interface Actor { + /** + * Set actor value + */ + public void set() throws InterruptedException; + + /** + * Function to check whether the actor has a next set value + * @return Returns true if there is an actor value for the next iteration. + * False if there is no actor value (i.e. this will be the last iteration in the ActionLoop) + */ + public boolean hasNext(); + + /** + * Initialize the actor to the start + */ + public void init(); + + /** + * Reverse the set values of the actor + */ + public void reverse(); + + /** + * Reset the actuator to its initial configuration + */ + public void reset(); +} diff --git a/src/main/java/ch/psi/fda/core/ActorSetCallable.java b/src/main/java/ch/psi/fda/core/ActorSetCallable.java new file mode 100644 index 0000000..3446875 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/ActorSetCallable.java @@ -0,0 +1,40 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +import java.util.concurrent.Callable; + +/** + * Callable used for parallel execution of the set method of an actor + */ +public class ActorSetCallable implements Callable { + + private Actor actor; + + public ActorSetCallable(Actor actor){ + this.actor = actor; + } + + @Override + public Object call() throws Exception { + actor.set(); + return null; + } +} diff --git a/src/main/java/ch/psi/fda/core/Guard.java b/src/main/java/ch/psi/fda/core/Guard.java new file mode 100644 index 0000000..16915e3 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/Guard.java @@ -0,0 +1,41 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +/** + * Guard to protect specific activities. A guard can be used to check for environment changes while a + * certain activity was executed. + * + * Example: + * An Guard can be used to check whether an injection happened while the the detector were read. + */ +public interface Guard { + + /** + * Initialize guard object and its internal state. + */ + public void init(); + + /** + * Check the status of the guard. + * @return Returns true if the guard condition was not constrainted since the last init call. False otherwise. + */ + public boolean check(); +} diff --git a/src/main/java/ch/psi/fda/core/Manipulation.java b/src/main/java/ch/psi/fda/core/Manipulation.java new file mode 100644 index 0000000..b73b52f --- /dev/null +++ b/src/main/java/ch/psi/fda/core/Manipulation.java @@ -0,0 +1,47 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +import java.util.List; + +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.Metadata; + +public interface Manipulation { + + /** + * Get the id of the manipulation + * @return id of manipulation + */ + public String getId(); + + /** + * Initialize the manipulation + * @param metadata Metadata of the incomming data message + */ + public void initialize(List metadata); + + /** + * Execute the manipulation on the passed data message + * @param message Message to manipulate + * @return Result of the manipulation + */ + public Object execute(DataMessage message); +} diff --git a/src/main/java/ch/psi/fda/core/Sensor.java b/src/main/java/ch/psi/fda/core/Sensor.java new file mode 100644 index 0000000..fbace06 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/Sensor.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core; + +/** + * The sensor interface describes an entity that can be read out like a + * simple channel or (image) detector. Depending on the sensor type the + * returned data is of a certain type. + */ +public interface Sensor { + /** + * Readout sensor. + * @return Sensor value. The type of the returned value depends on the sensor type. + */ + public Object read() throws InterruptedException; + + /** + * Get the global id of the sensor + * @return id of sensor + */ + public String getId(); +} diff --git a/src/main/java/ch/psi/fda/core/TestConfiguration.java b/src/main/java/ch/psi/fda/core/TestConfiguration.java new file mode 100644 index 0000000..b213fe1 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/TestConfiguration.java @@ -0,0 +1,87 @@ +/** + * + * Copyright 2012 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ +package ch.psi.fda.core; + +public class TestConfiguration { + private static final TestConfiguration instance = new TestConfiguration(); + + private final String otfPrefix = "MTEST-HW3-OTFX"; + private final String crlogicPrefix = "MTEST-HW3-CRL"; + private final String prefixScaler = "MTEST-HW3:JS"; + private final String server = "MTEST-VME-HW3"; + + private final String motor1 = "MTEST-HW3:MOT1"; + private final String analogIn1 = "MTEST-HW3-AI1:AI_01"; + + private final String ioc = "MTEST-VME-HW3.psi.ch"; + + private TestConfiguration(){ + } + + public static TestConfiguration getInstance(){ + return instance; + } + + /** + * @return the prefix + */ + public String getCrlogicPrefix() { + return crlogicPrefix; + } + + /** + * @return the prefixScaler + */ + public String getPrefixScaler() { + return prefixScaler; + } + + /** + * @return the server + */ + public String getServer() { + return server; + } + + /** + * @return the motor1 + */ + public String getMotor1() { + return motor1; + } + + /** + * @return the analogIn1 + */ + public String getAnalogIn1() { + return analogIn1; + } + + /** + * @return the otfPrefix + */ + public String getOtfPrefix() { + return otfPrefix; + } + + public String getIoc() { + return ioc; + } + +} diff --git a/src/main/java/ch/psi/fda/core/actions/ChannelAccessCondition.java b/src/main/java/ch/psi/fda/core/actions/ChannelAccessCondition.java new file mode 100644 index 0000000..05429bb --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actions/ChannelAccessCondition.java @@ -0,0 +1,124 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import java.util.Comparator; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import ch.psi.fda.core.Action; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; + +/** + * Perform a put on the specified Channel Access channel. The put can be done synchronous or + * asynchronously. + */ +public class ChannelAccessCondition implements Action { + + private static Logger logger = Logger.getLogger(ChannelAccessCondition.class.getName()); + + private final Channel channel; + private final E expectedValue; + private final Comparator comparator; + private final Long timeout; + + private volatile boolean abort = false; + private volatile Thread waitT = null; + + /** + * @param channel Channel to wait value for + * @param expectedValue Value to wait for + * @param timeout Timeout of the condition in milliseconds (null accepted - will take default wait timeout for channels ch.psi.jcae.ChannelBeanFactory.waitTimeout) + * + * @throws IllegalArgumentException Timeout specified is not >=0 + */ + public ChannelAccessCondition(Channel channel, E expectedValue, Long timeout){ + + if(timeout != null && timeout<=0){ + throw new IllegalArgumentException("Timeout must be > 0"); + } + + this.channel = channel; + this.expectedValue = expectedValue; + this.comparator = null; + this.timeout = timeout; + } + + public ChannelAccessCondition(Channel channel, E expectedValue, Comparator comparator, Long timeout){ + + if(timeout != null && timeout<=0){ + throw new IllegalArgumentException("Timeout must be > 0"); + } + + this.channel = channel; + this.expectedValue = expectedValue; + this.comparator = comparator; + this.timeout = timeout; + } + + + /** + * @throws RuntimeException Channel value did not reach expected value (within the specified timeout period) + */ + @Override + public void execute() throws InterruptedException { + abort=false; + logger.finest("Checking channel "+channel.getName()+" for value "+expectedValue+" [timeout: "+timeout+"]" ); + try{ + waitT = Thread.currentThread(); + try { + if(comparator==null){ + if(timeout == null){ + channel.waitForValue(expectedValue); + } + else{ + channel.waitForValue(expectedValue, timeout); + } + } + else{ + if(timeout == null){ + channel.waitForValue(expectedValue, comparator); + } + else{ + channel.waitForValue(expectedValue, comparator, timeout); + } + } + } catch (ExecutionException | ChannelException | TimeoutException | InterruptedException e) { + if(abort && e instanceof InterruptedException){ + return; + } + throw new RuntimeException("Channel [name:"+channel.getName()+"] did not reach expected value "+expectedValue+" ", e); + } + } + finally{ + waitT=null; + } + } + + @Override + public void abort() { + abort=true; + if(waitT!=null){ + waitT.interrupt(); + } + } +} diff --git a/src/main/java/ch/psi/fda/core/actions/ChannelAccessPut.java b/src/main/java/ch/psi/fda/core/actions/ChannelAccessPut.java new file mode 100644 index 0000000..8c71ca3 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actions/ChannelAccessPut.java @@ -0,0 +1,94 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import ch.psi.fda.core.Action; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; + +/** + * Perform a put on the specified Channel Access channel. The put can be done synchronous or + * asynchronously. + */ +public class ChannelAccessPut implements Action { + + private static Logger logger = Logger.getLogger(ChannelAccessPut.class.getName()); + + private final Channel channel; + private final E value; + private final boolean asynchronous; + + private final Long timeout; + + /** + * @param channel + * @param value Value to set + * @param asynchronous Flag whether to set the value synchronous (wait for response) or asynchronously (fire and forget) + * @param timeout Timeout used for set operation (time that set need to come back) + */ + public ChannelAccessPut(Channel channel, E value, boolean asynchronous, Long timeout){ + + this.channel = channel; + this.value = value; + this.asynchronous = asynchronous; + this.timeout = timeout; + } + + /** + * Additional constructor for convenience. This constructor defaults the operation type to synchronous put. + * @param channel + * @param value Value to set + */ + public ChannelAccessPut(Channel channel, E value){ + this(channel, value, false, null); + } + + /** + * @throws RuntimeException Cannot set value on channel + */ + @Override + public void execute() throws InterruptedException { + logger.finest("Put to channel: "+channel.getName()+ " asynchronous: "+asynchronous); + try{ + if(asynchronous){ + channel.setValueNoWait(value); + } + else{ + if(timeout==null){ + channel.setValue(value); + } + else{ + channel.setValueAsync(value).get(timeout, TimeUnit.MILLISECONDS); + } + } + } catch (ExecutionException | TimeoutException | ChannelException e) { + throw new RuntimeException("Unable to set channel [name:"+channel.getName()+"] to value "+value, e); + } + } + + @Override + public void abort() { + } +} diff --git a/src/main/java/ch/psi/fda/core/actions/Delay.java b/src/main/java/ch/psi/fda/core/actions/Delay.java new file mode 100644 index 0000000..b799823 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actions/Delay.java @@ -0,0 +1,52 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import ch.psi.fda.core.Action; + +/** + * Wait a specific time until executing the next action ... + */ +public class Delay implements Action { + + private final long time; + + /** + * @param time Time to wait in milliseconds + */ + public Delay(long time){ + + // Check if delay time is positive and >0 + if(time<=0){ + throw new IllegalArgumentException("Wait time must be >0"); + } + + this.time = time; + } + + @Override + public void execute() throws InterruptedException { + Thread.sleep(time); + } + + @Override + public void abort() { + } +} diff --git a/src/main/java/ch/psi/fda/core/actions/JythonAction.java b/src/main/java/ch/psi/fda/core/actions/JythonAction.java new file mode 100644 index 0000000..590e5f4 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actions/JythonAction.java @@ -0,0 +1,142 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import ch.psi.fda.core.Action; + +/** + * Executes a python script inside a Jython interpreter + */ +public class JythonAction implements Action { + + private static Logger logger = Logger.getLogger(JythonAction.class.getName()); + + /** + * Pattern of the entry function of the jython script + */ + private static final String entryFunction = "process"; + private static final String entryFunctionPattern = "def "+entryFunction+"\\((.*)\\):"; + + private ScriptEngine engine; + + private String jythonCall; // entry call to script - including all parameters, etc. + + private final Map globalObjects; + + public JythonAction(String script, Map mapping){ + this(script, mapping, new HashMap()); + } + + public JythonAction(String script, Map mapping, Map globalObjects){ + + this.globalObjects = globalObjects; + + // Workaround for Jython memory leak + // http://blog.hillbrecht.de/2009/07/11/jython-memory-leakout-of-memory-problem/ + System.setProperty("python.options.internalTablesImpl","weak"); + + // Create new script engine + this.engine = new ScriptEngineManager().getEngineByName("python"); + + // Determine script entry function and the function parameters + Pattern pattern = Pattern.compile(entryFunctionPattern); + Matcher matcher = pattern.matcher(script); + String[] functionParameters = null; + if(matcher.find() && matcher.groupCount()==1){ + logger.finest("Entry function '"+entryFunctionPattern+"' found - Identified parameters: "+matcher.group(1)); + jythonCall = entryFunction+"("+matcher.group(1)+")"; + if(matcher.group(1).matches(" *")){ + functionParameters = new String[0]; + } + else{ + functionParameters = matcher.group(1).split(" *, *"); + } + } + else{ + throw new IllegalArgumentException("Cannot determine entry function: "+entryFunctionPattern); + } + + // Check whether all function parameters have a mapping + for(int i=0;i. + * + */ + +package ch.psi.fda.core.actions; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.logging.Level; +import java.util.logging.Logger; + +import ch.psi.fda.core.Action; + +/** + * Action that executes a specified command when it is executed. + */ +public class ShellAction implements Action{ + + private static Logger logger = Logger.getLogger(ShellAction.class.getName()); + + private volatile Process process; + private volatile boolean abort = false; + private boolean checkExitValue = true; + private int exitValue = 0; + + /** + * Name (full path if it is not in the system path) of the script to execute when + * the execute() function of this action is invoked. + */ + private final String script; + + /** + * @param script Name of the command to execute when this action is invoked + * + * @throws IllegalArgumentException Specified script does not exist + */ + public ShellAction(String script){ + String[] scri = script.split("[ ,\t]"); + File s = new File(scri[0]); + if(!s.exists()){ + throw new IllegalArgumentException("Script "+script+" does not exist."); + } + this.script = script; + } + + @Override + public void execute() throws InterruptedException { + try{ + abort = false; + logger.fine("Execute script "+script); + process = Runtime.getRuntime().exec(new String[]{"/bin/bash","-c",script}); + int exitVal = process.waitFor(); + + // Log output of the shell script if loglevel is finest + if(logger.isLoggable(Level.FINEST)){ + logger.finest("STDOUT [BEGIN]"); + // Ideally the readout of the stream should be in parallel to the processing of the script! I.e. the output appears in the log as it is generated by the script! + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line = null; + while((line=reader.readLine()) != null){ + logger.finest(line); + } + logger.finest("STDOUT [END]"); + + logger.finest("STDERR [BEGIN]"); + // Ideally the readout of the stream should be in parallel to the processing of the script! I.e. the output appears in the log as it is generated by the script! + reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); + line = null; + while((line=reader.readLine()) != null){ + logger.finest(line); + } + logger.finest("STDERR [END]"); + } + + logger.fine("Script ["+script+"] return value: "+exitVal); + + if(abort){ + throw new RuntimeException("Script ["+script+"] was aborted"); + } + else{ + // Check script exit value to 0 if != 0 then throw an runtime exception + if(checkExitValue && exitVal != exitValue){ + throw new RuntimeException("Script ["+script+"] returned with an exit value not equal to 0"); + } + } + process = null; + } + catch(IOException e){ + throw new RuntimeException("Unable to execute script: "+script,e); + } + } + + @Override + public void abort() { + abort=true; + if(process!=null){ + process.destroy(); + } + } + + public boolean isCheckExitValue() { + return checkExitValue; + } + + public void setCheckExitValue(boolean checkExitValue) { + this.checkExitValue = checkExitValue; + } + + public int getExitValue() { + return exitValue; + } + + public void setExitValue(int exitValue) { + this.exitValue = exitValue; + } +} diff --git a/src/main/java/ch/psi/fda/core/actors/ChannelAccessFunctionActuator.java b/src/main/java/ch/psi/fda/core/actors/ChannelAccessFunctionActuator.java new file mode 100644 index 0000000..b8f44f9 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actors/ChannelAccessFunctionActuator.java @@ -0,0 +1,253 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import ch.psi.fda.core.Actor; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; + +public class ChannelAccessFunctionActuator implements Actor { + + private static Logger logger = Logger.getLogger(ChannelAccessFunctionActuator.class.getName()); + + private boolean asynchronous = false; + private double start; + private double end; + private double stepSize; + private int direction; // Move direction (start<end = 1, start>end = -1) + + /** + * Execution count of actuator. This variable is used to minimize the floating point + * rounding errors for calculating the next step. + */ + private int count; + + /** + * Flag that indicates whether there is a next set value for the Actor + */ + private boolean next; + + /** + * Value to set at next @see ch.psi.fda.engine.Actor#set() call + */ + private double value; + + /** + * Level of accuracy the positioner need to have (e.g. if a positioner is set to 1 the readback set value + * of the positioner need to have at lease 1+/-accuracy) + * Default is stepSize/2 + */ + private double accuracy; + + /** + * Move timeout + */ + private Long timeout; + + private final T doneValue; + private final long doneDelay; + + private final double originalStart; + private final double originalEnd; + private final int originalDirection; + + private Channel channel; + private Channel doneChannel = null; + + private final Function function; + private boolean checkActorSet = true; + + /** + * Constructor - Initialize actor + * @param channelName + * @param start + * @param end + * @param stepSize + * @param timeout Maximum move time (in milliseconds) + */ + public ChannelAccessFunctionActuator(Channel channel, Function function, double start, double end, double stepSize, Long timeout){ + this(channel, null, null, 0, function, start, end, stepSize, timeout); + } + + /** + * Constructor + * @param channel + * @param doneChannel If null actor will not wait (for this channel) to continue + * @param doneValue + * @param doneDelay Delay in seconds before checking the done channel + * @param start + * @param end + * @param stepSize + * @param timeout Maximum move time (in milliseconds) + */ + public ChannelAccessFunctionActuator(Channel channel, Channel doneChannel, T doneValue, double doneDelay, Function function, double start, double end, double stepSize, Long timeout){ + + this.doneValue = doneValue; + this.doneDelay = (long) Math.floor((doneDelay*1000)); + this.start = start; + this.end = end; + + if(stepSize <= 0){ + throw new IllegalArgumentException("Step size ["+stepSize+"] must be > 0"); + } + this.stepSize = stepSize; + + this.accuracy = stepSize/2; + + // Validate and save timeout parameter + if(timeout!=null && timeout<=0){ + throw new IllegalArgumentException("Timeout must be >0 or null"); + } + else{ + this.timeout = timeout; + } + + if(function==null){ + throw new IllegalArgumentException("Function must not be null"); + } + this.function = function; + + + init(); + + // Save original settings + this.originalStart = start; + this.originalEnd = end; + this.originalDirection = direction; + + this.channel = channel; + this.doneChannel = doneChannel; + } + + @Override + public void set() throws InterruptedException { + + // Throw an IllegalStateException in the case that set is called although there is no next step. + if(!next){ + throw new IllegalStateException("The actuator does not have any next step."); + } + + // Set actuator channel + logger.finest("Set actuator channel "+channel.getName()+" to value: "+value); + try { + double fvalue = function.calculate(value); + + if(!asynchronous){ + if(timeout==null){ + channel.setValue(fvalue); + } + else{ + channel.setValueAsync(fvalue).get(timeout, TimeUnit.MILLISECONDS); + } + } + else{ + channel.setValueNoWait(fvalue); + } + + if(doneChannel != null){ + Thread.sleep(doneDelay); + doneChannel.waitForValue(doneValue); + } + + // Check whether the set value is really on the value that was set before. + if(checkActorSet){ + double c = channel.getValue(true); + double a = Math.abs( c - fvalue ); + if ( a > accuracy ){ + throw new RuntimeException("Actor could not be set to the value "+fvalue+" The readback of the set value does not match the value that was set [value: "+c+" delta: "+a+" accuracy: "+accuracy+"]"); + } + } + + } catch (ExecutionException | TimeoutException | ChannelException e) { + throw new RuntimeException("Unable to move actuator [channel: "+channel.getName()+"] to value "+value,e); + } + + + count++; + double nextValue = start+(count*stepSize*direction); // Done like this to keep floating point rounding errors minimal + + if((direction==1&&nextValue<=end)||(direction==-1&nextValue>=end)){ + + // Apply function +// nextValue = function.calculate(nextValue); + + logger.fine("Next actor value: "+nextValue); + value=nextValue; + this.next = true; + } + else{ + // There is no next set value + this.next = false; + } + } + + @Override + public boolean hasNext() { + return next; + } + + @Override + public void init() { + this.count = 0; + + // Determine move direction + this.direction = 1; + if(start>end){ + direction=-1; // Move in negative direction + } + + // Set first set value to the start value + this.value = start; + this.next = true; + } + + @Override + public synchronized void reverse() { + double oldStart = start; + this.start = this.end; + this.end = oldStart; + + // Determine move direction + this.direction = 1; + if(this.start>this.end){ + direction=-1; // Move in negative direction + } + + } + + @Override + public void reset() { + this.start = this.originalStart; + this.end = this.originalEnd; + this.direction = this.originalDirection; + } + + public boolean isAsynchronous() { + return asynchronous; + } + public void setAsynchronous(boolean asynchronous) { + this.asynchronous = asynchronous; + } +} diff --git a/src/main/java/ch/psi/fda/core/actors/ChannelAccessLinearActuator.java b/src/main/java/ch/psi/fda/core/actors/ChannelAccessLinearActuator.java new file mode 100644 index 0000000..bd244b4 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actors/ChannelAccessLinearActuator.java @@ -0,0 +1,234 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import ch.psi.fda.core.Actor; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; + +/** + * This actuator sets an Channel Access channel from a start to an end value by doing discrete steps. + */ +public class ChannelAccessLinearActuator implements Actor { + + private static Logger logger = Logger.getLogger(ChannelAccessLinearActuator.class.getName()); + + private boolean asynchronous = false; + + private double start; + private double end; + private double stepSize; + private int direction; + + /** + * Execution count of actuator. This variable is used to minimize the floating point + * rounding errors for calculating the next step. + */ + private int count; + + /** + * Flag that indicates whether there is a next set value for the Actor + */ + private boolean next; + + /** + * Value to set at next @see ch.psi.fda.engine.Actor#set() call + */ + private double value; + + /** + * Level of accuracy the positioner need to have (e.g. if a positioner is set to 1 the readback set value + * of the positioner need to have at lease 1+/-accuracy) + * Default is stepSize/2 + */ + private double accuracy; + + + + private final T doneValue; + private final long doneDelay; + + private final double originalStart; + private final double originalEnd; + private final int originalDirection; + + private Long timeout; // Set timeout + + private final Channel channel; + private final Channel doneChannel; + + private boolean checkActorSet = true; + + /** + * Constructor + * @param channel + * @param doneChannel If null actor will not wait (for this channel) to continue + * @param doneValue + * @param doneDelay Delay in seconds before checking the done channel + * @param start + * @param end + * @param stepSize + * @param timeout Maximum move time (in milliseconds) + */ + public ChannelAccessLinearActuator(Channel channel, Channel doneChannel, T doneValue, double doneDelay, double start, double end, double stepSize, Long timeout){ + + this.doneValue = doneValue; + this.doneDelay = (long) Math.floor((doneDelay*1000)); + this.start = start; + this.end = end; + + if(stepSize <= 0){ + throw new IllegalArgumentException("Step size ["+stepSize+"] must be > 0"); + } + this.stepSize = stepSize; + + this.accuracy = stepSize/2; + + // Validate and save timeout parameter + if(timeout!=null && timeout<=0){ + throw new IllegalArgumentException("Timeout must be >0 or null"); + } + else{ + this.timeout = timeout; + } + + + init(); + + // Save original settings + this.originalStart = start; + this.originalEnd = end; + this.originalDirection = direction; + + this.channel = channel; + this.doneChannel = doneChannel; + } + + @Override + public void set() throws InterruptedException { + + // Throw an IllegalStateException in the case that set is called although there is no next step. + if(!next){ + throw new IllegalStateException("The actuator does not have any next step."); + } + + // Set actuator channel + logger.finest("Set actuator channel "+channel.getName()+" to value: "+value); + try { + + if(!asynchronous){ + if(timeout==null){ + channel.setValue(value); + } + else{ + channel.setValueAsync(value).get(timeout, TimeUnit.MILLISECONDS); + } + } + else{ + channel.setValueNoWait(value); + } + + if(doneChannel != null){ + Thread.sleep(doneDelay); + doneChannel.waitForValue(doneValue); + } + + // Check whether the set value is really on the value that was set before. + if(checkActorSet){ + double c = channel.getValue(true); + double a = Math.abs( c - value ); + if ( a > accuracy ){ + throw new RuntimeException("Actor could not be set to the value "+value+" The readback of the set value does not match the value that was set [value: "+c+" delta: "+a+" accuracy: "+accuracy+"]"); + } + } + + } catch (ExecutionException | TimeoutException | ChannelException e) { + throw new RuntimeException("Unable to move actuator [channel: "+channel.getName()+"] to value "+value,e); + } + + + count++; + double nextValue = start+(count*stepSize*direction); // Done like this to keep floating point rounding errors minimal + + if((direction==1&&nextValue<=end)||(direction==-1&nextValue>=end)){ + logger.fine("Next actor value: "+nextValue); + value=nextValue; + this.next = true; + } + else{ + // There is no next set value + this.next = false; + } + } + + @Override + public boolean hasNext() { + return next; + } + + @Override + public void init() { + this.count = 0; + + // Determine move direction + this.direction = 1; + if(start>end){ + direction=-1; // Move in negative direction + } + + + // Set first set value to the start value + this.value = start; + this.next = true; + } + + @Override + public synchronized void reverse() { + double oldStart = start; + this.start = this.end; + this.end = oldStart; + + // Determine move direction + this.direction = 1; + if(this.start>this.end){ + direction=-1; // Move in negative direction + } + + } + + @Override + public void reset() { + this.start = this.originalStart; + this.end = this.originalEnd; + this.direction = this.originalDirection; + } + + public boolean isAsynchronous() { + return asynchronous; + } + public void setAsynchronous(boolean asynchronous) { + this.asynchronous = asynchronous; + } +} diff --git a/src/main/java/ch/psi/fda/core/actors/ChannelAccessTableActuator.java b/src/main/java/ch/psi/fda/core/actors/ChannelAccessTableActuator.java new file mode 100644 index 0000000..0400ab5 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actors/ChannelAccessTableActuator.java @@ -0,0 +1,236 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import ch.psi.fda.core.Actor; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; + +/** + * This actuator sets an Channel Access channel by using the positions from the given table. + */ +public class ChannelAccessTableActuator implements Actor { + + private static Logger logger = Logger.getLogger(ChannelAccessTableActuator.class.getName()); + + private boolean asynchronous = false; + + /** + * Position table + */ + private final double[] table; + + /** + * Level of accuracy the positioner need to have (e.g. if a positioner is set to 1 the readback set value + * of the positioner need to have at lease 1+/-accuracy) + */ + private double accuracy = 0.1; + + /** + * Execution count of actuator. This variable is used to minimize the floating point + * rounding errors for calculating the next step. + */ + private int count; + + /** + * Flag that indicates whether there is a next set value for the Actor + */ + private boolean next; + + + private Channel channel; + private Channel doneChannel = null; + + private final T doneValue; + private final long doneDelay; + + /** + * Flag that indicates whether the actor moves in the positive direction + */ + private boolean positiveDirection = true; + private final boolean originalPositiveDirection; + + /** + * Maximum move time (in milliseconds) + */ + private Long timeout = null; + + private boolean checkActorSet = true; + + /** + * Constructor - Initialize actor + * @param channelName Name of the channel to set + * @param table Position table with the explicit positions for each step + * @param timeout Maximum move time (in milliseconds) + */ + public ChannelAccessTableActuator(Channel channel, double[] table, Long timeout){ + this(channel, null, null, 0, table, timeout); + } + + /** + * Constructor + * @param channel + * @param doneChannel + * @param doneValue + * @param doneDelay + * @param table + * @param timeout Maximum move time (in milliseconds) + */ + public ChannelAccessTableActuator(Channel channel, Channel doneChannel, T doneValue, double doneDelay, double[] table, Long timeout){ + + this.doneValue = doneValue; + this.doneDelay = (long) Math.floor((doneDelay*1000)); + + if(table==null){ + throw new IllegalArgumentException("Null table is not accepted"); + } + if(table.length==0){ + throw new IllegalArgumentException("Position table need to have at least one position"); + } + + this.table = table; + + // Validate and save timeout parameter + if(timeout!=null && timeout<=0){ + throw new IllegalArgumentException("Timeout must be >0 or null"); + } + else{ + this.timeout = timeout; + } + + init(); + + // Save the initial direction + this.originalPositiveDirection = positiveDirection; + + this.channel = channel; + this.doneChannel = doneChannel; + } + + @Override + public void set() throws InterruptedException { + + // Throw an IllegalStateException in the case that set is called although there is no next step. + if(!next){ + throw new IllegalStateException("The actuator does not have any next step."); + } + + // Set actuator channel + logger.finest("Set actuator channel "+channel.getName()+" to value: "+table[count]); + try { + if(!asynchronous){ + if(timeout==null){ + channel.setValue(table[count]); + } + else{ + channel.setValueAsync(table[count]).get(timeout, TimeUnit.MILLISECONDS); + } + } + else{ + channel.setValueNoWait(table[count]); + } + + + if(doneChannel != null){ + Thread.sleep(doneDelay); + doneChannel.waitForValue(doneValue); + } + + // Check whether the set value is really on the value that was set before. + if(doneChannel==null && !asynchronous && checkActorSet){ + if ( Math.abs( channel.getValue() - table[count] ) > accuracy ){ + throw new RuntimeException("Actor could not be set to the value "+table[count]+" The readback of the set value does not match the value that was set"); + } + } + + } catch (ExecutionException | ChannelException | TimeoutException e) { + throw new RuntimeException("Move actuator [channel: "+channel.getName()+"] to value "+table[count],e); + } + + if(positiveDirection){ + count++; + if(count=0){ + this.next = true; + } + else{ + // There is no next set value + this.next = false; + } + } + } + + @Override + public boolean hasNext() { + return next; + } + + @Override + public void init() { + + // Set first set value to the start value + if(positiveDirection){ + this.count = 0; // Set count to the first element + } + else{ + this.count = table.length-1; // Set count to the last element + } + + if(table.length>0){ + this.next = true; + } + } + + @Override + public void reverse() { + if(positiveDirection){ + positiveDirection=false; + } + else{ + positiveDirection=true; + } + } + + @Override + public void reset() { + this.positiveDirection = this.originalPositiveDirection; + } + + public boolean isAsynchronous() { + return asynchronous; + } + public void setAsynchronous(boolean asynchronous) { + this.asynchronous = asynchronous; + } +} diff --git a/src/main/java/ch/psi/fda/core/actors/ComplexActuator.java b/src/main/java/ch/psi/fda/core/actors/ComplexActuator.java new file mode 100644 index 0000000..1db950a --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actors/ComplexActuator.java @@ -0,0 +1,201 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import ch.psi.fda.core.Action; +import ch.psi.fda.core.Actor; + +/** + * Complex actuator that consists of a list of other actuators. The steps of this actor are composed out of the + * steps of the actors that this actuator consists of. + * First all the steps of the first actor are used, after that the steps of the next actor are done. + * Before the first step of an actor pre actions are available and after the last step of an actor a post action is available. + */ +public class ComplexActuator implements Actor { + + private static Logger logger = Logger.getLogger(ComplexActuator.class.getName()); + + /** + * List of actors this actor is made of + */ + private final List actors; + + /** + * Actions that are executed directly before the first step of this actor + */ + private final List preActions; + + /** + * Actions that are executed directly after the last step of this actor + */ + private final List postActions; + + /** + * Flag that indicates whether there is a next set value for the Actor + */ + private boolean next; + + /** + * Index of the actor currently used + */ + private int actualActorCount; + + /** + * Actor currently used to perform steps + */ + private Actor actualActor; + + /** + * Flag to indicate the first set() execution of this actor. + * This flag is used to decide whether to execute the pre actions. + */ + private boolean firstrun; + + public ComplexActuator(){ + this.actors = new ArrayList(); + this.preActions = new ArrayList(); + this.postActions = new ArrayList(); + + this.next = false; + this.actualActorCount = 0; + + this.firstrun = true; + } + + @Override + public void set() throws InterruptedException { + if(!next){ + throw new IllegalStateException("The actuator does not have any next step."); + } + + // PRE actions + if(firstrun){ + this.firstrun = false; + + // Execute pre actions + logger.finest("Execute pre actions"); + for(Action action: preActions){ + action.execute(); + } + } + + actualActor.set(); // If the actor has no next step then something is wrong in the init/next step logic + + // If the last point of the actual actuator is set take the next actuator in the list if there is one available + while(!actualActor.hasNext()&&actualActorCount getPreActions() { + return preActions; + } + public List getActors() { + return actors; + } + public List getPostActions() { + return postActions; + } +} diff --git a/src/main/java/ch/psi/fda/core/actors/Function.java b/src/main/java/ch/psi/fda/core/actors/Function.java new file mode 100644 index 0000000..9079d09 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actors/Function.java @@ -0,0 +1,24 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +public interface Function { + public double calculate(double parameter); +} diff --git a/src/main/java/ch/psi/fda/core/actors/JythonFunction.java b/src/main/java/ch/psi/fda/core/actors/JythonFunction.java new file mode 100644 index 0000000..5f7a792 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/actors/JythonFunction.java @@ -0,0 +1,106 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import java.util.Map; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import ch.psi.fda.core.scripting.JythonGlobalVariable; + +public class JythonFunction implements Function { + + private static final Logger logger = Logger.getLogger(JythonFunction.class.getName()); + + public static final String ENTRY_FUNCTION_NAME = "calculate"; + private static final String ENTRY_FUNCTION_PATTERN = "def "+ENTRY_FUNCTION_NAME+"\\((.*)\\):"; + + private ScriptEngine engine; + private String additionalParameter = ""; + + + public JythonFunction(String script, Map map){ + + // Workaround for Jython memory leak + // http://blog.hillbrecht.de/2009/07/11/jython-memory-leakout-of-memory-problem/ + System.setProperty("python.options.internalTablesImpl","weak"); + + this.engine = new ScriptEngineManager().getEngineByName("python"); + + // Determine script entry function and the function parameters + String[] parameter; + Pattern pattern = Pattern.compile(ENTRY_FUNCTION_PATTERN); + Matcher matcher = pattern.matcher(script); + if(matcher.find() && matcher.groupCount()==1){ + if(!matcher.group(1).trim().equals("")){ + logger.finest("Entry function '"+ENTRY_FUNCTION_PATTERN+"' found - Identified parameters: "+matcher.group(1)); + parameter = matcher.group(1).split(" *, *"); + } + else{ + parameter = new String[0]; + } + } + else{ + throw new IllegalArgumentException("Cannot determine entry function: "+ENTRY_FUNCTION_PATTERN); + } + + // Check whether all parameters are mapped + StringBuilder b = new StringBuilder(); + for(int i=1;i. + * + */ + +package ch.psi.fda.core.actors; + +import ch.psi.fda.core.Actor; +import ch.psi.fda.core.Sensor; + +/** + * Pseudo actor that is literally doing nothing for n times + */ +public class PseudoActuatorSensor implements Actor, Sensor { + + /** + * Execution count of actuator. + */ + private int count; + + /** + * Number of counts for this actuator + */ + private final int counts; + + private final String id; + + /** + * @param counts + * @param id Id of the Actor/Sensor + */ + public PseudoActuatorSensor(String id, int counts){ + if(counts < 1){ + throw new IllegalArgumentException("Count ["+counts+"] must be > 0"); + } + this.id = id; + this.counts = counts; + + init(); + } + + + @Override + public void set() { + if(!hasNext()){ + throw new IllegalStateException("The actuator does not have any next step."); + } + + count++; + } + + @Override + public boolean hasNext() { + return (count. + * + */ + +package ch.psi.fda.core.guard; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import ch.psi.fda.core.Guard; +import ch.psi.jcae.ChannelException; + +/** + * Guard checking channels to meet a certain condition + */ +public class ChannelAccessGuard implements Guard { + + private static Logger logger = Logger.getLogger(ChannelAccessGuard.class.getName()); + + /** + * Flag to indicate whether a guard condition failed since the last init call + * true: all conditions met, false: at least one condition failed + */ + private volatile boolean check = true; + + private final List> conditions; + + public ChannelAccessGuard(List> conditions){ + + this.conditions = conditions; + + for(final ChannelAccessGuardCondition condition: conditions){ + condition.getChannel().addPropertyChangeListener(new PropertyChangeListener() { + @Override + public void propertyChange(PropertyChangeEvent evt) { + if(! evt.getNewValue().equals(condition.getValue())){ + check=false; + } + } + }); + } + } + + @Override + public void init() { + check = true; + + // Check one time if all conditions are met + for(ChannelAccessGuardCondition condition: conditions){ + try{ + if(! (condition.getChannel().getValue(true)).equals(condition.getValue()) ){ + check=false; + break; + } + } + catch (InterruptedException e) { + throw new RuntimeException("Guard interrupted ",e); + } catch (TimeoutException | ChannelException | ExecutionException e) { + logger.log(Level.WARNING, "Unable ", e); + check=false; + } + } + } + + @Override + public boolean check() { + return check; + } +} diff --git a/src/main/java/ch/psi/fda/core/guard/ChannelAccessGuardCondition.java b/src/main/java/ch/psi/fda/core/guard/ChannelAccessGuardCondition.java new file mode 100644 index 0000000..3b93c76 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/guard/ChannelAccessGuardCondition.java @@ -0,0 +1,41 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.guard; + +import ch.psi.jcae.Channel; + +public class ChannelAccessGuardCondition { + + private final Channel channel; + private final T value; // Value of the channel to meet condition + + public ChannelAccessGuardCondition(Channel channel, T value){ + this.channel = channel; + this.value = value; + } + + public Channel getChannel() { + return channel; + } + + public T getValue() { + return value; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/ActorSensorLoop.java b/src/main/java/ch/psi/fda/core/loops/ActorSensorLoop.java new file mode 100644 index 0000000..8b71b38 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/ActorSensorLoop.java @@ -0,0 +1,436 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.logging.Logger; + +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.core.Action; +import ch.psi.fda.core.ActionLoop; +import ch.psi.fda.core.Actor; +import ch.psi.fda.core.ActorSetCallable; +import ch.psi.fda.core.Guard; +import ch.psi.fda.core.Sensor; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Metadata; + +/** + * Loop of actions to accomplish a task or test. + */ +public class ActorSensorLoop implements ActionLoop { + + private static Logger logger = Logger.getLogger(ActorSensorLoop.class.getName()); + + /** + * Flag to indicate whether the data of this loop will be grouped + * According to this flag the dataGroup flag in EndOfStream will be set. + */ + private boolean dataGroup = false; + + /** + * List of additional action loops that are executed at the end of each + * iteration of the loop + */ + private List actionLoops; + /** + * List of actions that are executed at the beginning of the loop. + */ + private List preActions; + /** + * List of actions that are executed at the end of the loop. + */ + private List postActions; + + /** + * List of actions that are executed before the actors are set + */ + private List preActorActions; + + /** + * List of actions that are executed after all actors have been set + */ + private List postActorActions; + + /** + * List of actions that are executed before the sensors are read out + */ + private List preSensorActions; + /** + * List of actions that are executed after all sensors have been read out + */ + private List postSensorActions; + + /** + * List of actors of this loop + */ + private List actors; + + /** + * List of sensors of this loop + */ + private List sensors; + + /** + * Guard used to check whether the environment was ok while reading out the sensors + */ + private Guard guard = null; + + private volatile boolean loop = false; + private volatile boolean abort = false; + + private final boolean zigZag; + + private List pactors; + + private EventBus eventBus = new EventBus(); + + + public ActorSensorLoop(){ + this(false); + } + + public ActorSensorLoop(boolean zigZag){ + this.zigZag = zigZag; + this.actionLoops = new ArrayList(); + this.preActions = new ArrayList(); + this.postActions = new ArrayList(); + this.preActorActions = new ArrayList(); + this.postActorActions = new ArrayList(); + this.preSensorActions = new ArrayList(); + this.postSensorActions = new ArrayList(); + + this.actors = new ArrayList(); + this.pactors = new ArrayList(); + this.sensors = new ArrayList(); + } + + + /** + * Executes the actor sensor loop. The actor sensor loop is build up as follows: + * preActions + * loop{ + * check actors - abort if there are no new steps + * pre actor actions + * set actors + * post actor actions + * pre sensor actions + * read sensors + * post sensor actions + * execute additional registered action loops + * } + * postActions + * @throws InterruptedException + */ + @Override + public void execute() throws InterruptedException { + abort = false; + + List metadata = new ArrayList<>(); + + // Build up data message metadata based on the sensors currently registered. + for(Sensor s: sensors){ + metadata.add(new Metadata(s.getId())); + } + + + /** + * Thread pool for parallel execution of tasks + */ + ExecutorService executorService = Executors.newCachedThreadPool(); + + loop = true; + + // Execute pre actions + for(Action action: preActions){ + action.execute(); + } + + // Initialize actors of Loop + for(Actor actor: actors){ + actor.init(); + } + + // Variable to store the last guard status + boolean guardOK=true; + + try{ + + // Execute loop logic + while(loop){ + + if(guardOK){ + + + // If actors are defined for the loop check whether all of them + // have a next step defined if there is no actor defined only run this loop once + if(actors.size()>0){ + // Check whether the actors of this loop have a next step. If not + // abort the loop + boolean hasNext = true; + for(Actor actor: actors){ + if(!actor.hasNext()){ + hasNext=false; + break; // Stop actor check loop + } + } + + // If not all actors have a next step abort the loop + if(!hasNext){ + break; // Stop action loop + } + } + else{ + // No actors defined, only run loop once + loop = false; + } + + + + // Execute pre actor actions + for(Action action: preActorActions){ + action.execute(); + } + + if(abort){ // End loop if abort was issued + break; + } + + // Set actors + // for(Actor actor: actors){ + // actor.set(); + // } + // Parallel set of the actors + try { + for (Future f : executorService.invokeAll(pactors)) { + f.get(); //Blocks until the async set() is finished + } + } catch (ExecutionException e) { + throw new RuntimeException("Setting the actors failed",e); + } + + // Execute post actor actions + for(Action action: postActorActions){ + action.execute(); + } + + if(abort){ // End loop if abort was issued + break; + } + + } + + if(guard!=null){ + // Initialize guard + guard.init(); + guardOK=guard.check(); + + // Wait until guard is ok + while(!guardOK){ + logger.info("Waiting for guard condition(s) to be met"); + // Sleep 100 milliseconds before next check + Thread.sleep(1000); + + // Check whether the loop is not aborted, if it is aborted + // break the wait loop (afterwards also the loop loop is aborted) + if(!loop){ + break; + } + + guard.init(); + guardOK=guard.check(); + } + + // If loop is aborted proceed to next iteration an abort loop + if(!loop){ + continue; + } + } + + // Execute pre sensor actions + for(Action action: preSensorActions){ + action.execute(); + } + + if(abort){ // End loop if abort was issued + break; + } + + // Read sensors + DataMessage message = new DataMessage(metadata); + for(Sensor sensor: sensors){ + // Readout sensor + Object o = sensor.read(); + // Add sensor data item to message + message.getData().add(o); + } + + // Execute post sensor actions + for(Action action: postSensorActions){ + action.execute(); + } + + if(abort){ // End loop if abort was issued + break; + } + + // Check guard if one is registered + if(guard!=null){ + guardOK=guard.check(); + } + + if(guardOK){ + + // Post a message with the sensor data + eventBus.post(message); + + // Loop all configured ActionLoop objects + for(ActionLoop actionLoop: actionLoops){ + actionLoop.execute(); + } + } + } + + // Execute post actions + for(Action action: postActions){ + action.execute(); + } + + } + finally{ + // Ensure that data stream is terminated ... + + // Issue end of loop control message + // Set iflag of the EndOfStreamMessage according to dataGroup flag of this loop + eventBus.post(new EndOfStreamMessage(dataGroup)); + } + + if(zigZag){ + // Reverse actors for the next run + for(Actor actor: actors){ + actor.reverse(); + } + } + + executorService.shutdownNow(); + + } + + @Override + public void abort(){ + abort = true; + loop = false; + + // To abort all wait actions interrupt this thread + for(Action a: preSensorActions){ + a.abort(); + } + + // Recursively abort all registered action loops + for(ActionLoop actionLoop: actionLoops){ + actionLoop.abort(); + } + } + + @Override + public void prepare() { + // Create callable for all actors + pactors.clear(); + for(final Actor a: actors){ + pactors.add(new ActorSetCallable(a)); + } + + // Reset all actuators + for(Actor a: actors){ + a.reset(); + } + + // Recursively call prepare() method of all registered action loops + for(ActionLoop actionLoop: actionLoops){ + actionLoop.prepare(); + } + } + + @Override + public void cleanup() { + // Recursively call cleanup() method of all registered action loops + for(ActionLoop actionLoop: actionLoops){ + actionLoop.cleanup(); + } + } + + @Override + public List getPreActions() { + return preActions; + } + + @Override + public List getPostActions() { + return postActions; + } + + + @Override + public EventBus getEventBus(){ + return eventBus; + } + + + public List getActionLoops() { + return actionLoops; + } + public List getPreActorActions() { + return preActorActions; + } + public List getPostActorActions() { + return postActorActions; + } + public List getPreSensorActions() { + return preSensorActions; + } + public List getPostSensorActions() { + return postSensorActions; + } + public List getActors() { + return actors; + } + public List getSensors() { + return sensors; + } + public Guard getGuard() { + return guard; + } + public void setGuard(Guard guard) { + this.guard = guard; + } + public boolean isDataGroup() { + return dataGroup; + } + public void setDataGroup(boolean dataGroup) { + this.dataGroup = dataGroup; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/CrlogicDeltaDataFilter.java b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicDeltaDataFilter.java new file mode 100644 index 0000000..0d74f29 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicDeltaDataFilter.java @@ -0,0 +1,46 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +/** + * Filter calculating the delta of two subsequent values + */ +public class CrlogicDeltaDataFilter { + + private Double lastValue = null; + + public void reset(){ + lastValue = null; + } + + public Double delta(Double value){ + + Double lvalue = lastValue; + lastValue = value; + + if(lvalue==null){ + return Double.NaN; + } + else{ + // Return delta + return(value-lvalue); + } + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/CrlogicLoopStream.java b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicLoopStream.java new file mode 100644 index 0000000..ac4ff6e --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicLoopStream.java @@ -0,0 +1,723 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.zeromq.ZMQ; +import org.zeromq.ZMQ.Context; +import org.zeromq.ZMQ.Socket; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.core.Action; +import ch.psi.fda.core.ActionLoop; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Metadata; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; + +/** + * While using Crlogic the IOC system clock rate should/must be set to 1000 (default 60) + * + * sysClkRateSet 1000 + * + */ +public class CrlogicLoopStream implements ActionLoop { + + private static final Logger logger = Logger.getLogger(CrlogicLoopStream.class.getName()); + + public static final int HIGH_WATER_MARK = 100; + private Context context; + private Socket socket; + + private ObjectMapper mapper = new ObjectMapper(); + + /** + * Flag to indicate whether the data of this loop will be grouped + * According to this flag the dataGroup flag in EndOfStream will be set. + */ + private boolean dataGroup = false; + + // Default timeout (in milliseconds) for wait operations + private long startStopTimeout = 8000; + + + private String ioc; + + /** + * Flag whether the actor of this loop should move in zig zag mode + */ + private final boolean zigZag; + + private boolean useReadback; + private boolean useEncoder; + + + private List preActions; + private List postActions; + private List sensors; + + // Prefix for the CRLOGIC channels + private final String prefix; + + private TemplateCrlogic template; + private TemplateMotor motortemplate; + + private List readoutResources; + private Map scalerIndices; + private CrlogicRangeDataFilter crlogicDataFilter; + + private boolean abort = false; + + private final ChannelService cservice; + + private String id; + private String name; // name of the motor channel + private String readback; // name of the encoder channel + private double start; + private double end; + private double stepSize; + private double integrationTime; + private double additionalBacklash; + + + private final EventBus eventbus; + private List metadata; + + + /** + * Constructor + * @param cservice Channel Access context + * @param prefix Prefix of the IOC channels + * @param ioc Name of the IOC running CRLOGIC + * @param zigZag Do zigZag scan + */ + public CrlogicLoopStream(ChannelService cservice, String prefix, String ioc, boolean zigZag){ + eventbus = new EventBus(); + this.cservice = cservice; + this.prefix = prefix; + this.ioc = ioc; + this.zigZag = zigZag; + + // Initialize lists used by the loop + this.preActions = new ArrayList(); + this.postActions = new ArrayList(); + this.sensors = new ArrayList<>(); + this.readoutResources = new ArrayList(); + this.scalerIndices = new HashMap(); + + this.crlogicDataFilter = new CrlogicRangeDataFilter(); + } + + + public void setActuator(String id, String name, String readback, double start, double end, double stepSize, double integrationTime, double additionalBacklash){ + this.id = id; + this.name = name; + this.readback = readback; + this.start = start; + this.end = end; + this.stepSize = stepSize; + this.integrationTime = integrationTime; + this.additionalBacklash = additionalBacklash; + } + + + /** + * Receive a ZMQ message + */ + private void receive() { + + MainHeader mainHeader; + + try { + byte[] bytes = socket.recv(ZMQ.NOBLOCK); + if(bytes == null){ + // There is no message hanging + return; + } + mainHeader = mapper.readValue(bytes, MainHeader.class); + } catch (IOException e) { + throw new RuntimeException("Unable to decode main header", e); + } + + if(!socket.hasReceiveMore()){ + throw new RuntimeException("There is no data submessage"); + } + + byte[] bytes = socket.recv(); + ByteBuffer byteBuffer = ByteBuffer.wrap(bytes); + + DataMessage message = new DataMessage(metadata); + boolean use = true; + + for(int i=0; i1){ + throw new RuntimeException("More than 1 message drained from stream: "+n); + } + + if(use){ + eventbus.post(message); + } + } + + + + @Override + public void execute() throws InterruptedException { + try{ + + // Set values for the datafilter + crlogicDataFilter.setStart(start); + crlogicDataFilter.setEnd(end); + + // Reset data filter + for(Integer k: scalerIndices.keySet()){ + scalerIndices.get(k).reset(); + } + + // Set abort state to false + abort = false; + + Long timeout = 600000l; // 10 minutes move timeout + + // Check if logic is inactive, otherwise return early + if(!template.getStatus().getValue().equals(TemplateCrlogic.Status.INACTIVE.toString())){ + logger.info("CRLOGIC is not inactive!"); + // TODO Decide what to do in this situation + + if(template.getStatus().getValue().equals(TemplateCrlogic.Status.FAULT.toString())){ + // If in fault show message and recover + logger.info("CRLOGIC in FAULT state"); + logger.info("Error message: "+template.getMessage().getValue()); + logger.info("Recover logic and set it to INACTIVE"); + template.getStatus().setValue(TemplateCrlogic.Status.INACTIVE.toString()); + } + else if(template.getStatus().getValue().equals(TemplateCrlogic.Status.ACTIVE.toString())){ + template.getStatus().setValue(TemplateCrlogic.Status.STOP.toString()); + template.getStatus().waitForValue(TemplateCrlogic.Status.INACTIVE.toString(), startStopTimeout); + } + else{ + throw new RuntimeException("CRLOGIC is not inactive"); + } + } + + logger.info("Set parameters"); + template.getNfsServer().setValue(""); + template.getNfsShare().setValue(""); + template.getDataFile().setValue(""); + + int tps = template.getTicksPerSecond().getValue(); + logger.info("Ticks per second: "+tps); + + logger.info("Set readout resources"); + + template.getReadoutResources().setValue(readoutResources.toArray(new String[readoutResources.size()])); + + + + // Set ticks between interrupt to integration time + int ticks = (int)(tps*integrationTime); + template.getTicksBetweenInterrupts().setValue(ticks); + + // Prepare motor + double totalTimeSeconds = Math.abs((end-start)/stepSize*integrationTime); + int hours = (int) Math.floor(totalTimeSeconds/60/60); + int minutes = (int) Math.floor(totalTimeSeconds/60-hours*60); + int seconds = (int) Math.floor(totalTimeSeconds-hours*60*60-minutes*60); + + logger.info("Estimated time: "+hours+":"+minutes+":"+seconds); + + int direction = 1; + if(end-start<0){ + direction = -1; + } + + double motorBaseSpeed = motortemplate.getBaseSpeed().getValue(); + double motorHighLimit = motortemplate.getHighLimit().getValue(); + double motorLowLimit = motortemplate.getLowLimit().getValue(); + double motorBacklash = motortemplate.getBacklashDistance().getValue(); + + boolean respectMotorMinSpeed = false; // if false set min speed to 0 + double motorMinSpeed = 0; + if(respectMotorMinSpeed){ + motorMinSpeed = motorBaseSpeed; + } + + // Check user parameters + // TODO start and end values must be between the motor high and low value - otherwise fail + if(start>motorHighLimit || startmotorHighLimit || end0){ + double maxIntegrationTime = stepSize/motorMinSpeed; + if(integrationTime>maxIntegrationTime){ + logger.info("Integration time is too big"); + // Integration time is too big + throw new IllegalArgumentException("Integration time is too big"); + } + } + double motorMaxSpeed = motortemplate.getVelocity().getValue(); + double minIntegrationTime = Math.min( (stepSize/motorMaxSpeed), ((double)minimumTicks/(double)tps) ); + if(integrationTime future = motortemplate.getSetValue().setValueAsync(realEnd); + + long timeoutTime = System.currentTimeMillis()+timeout; + + // This loop will keep spinning until the motor reached the final position + while(!future.isDone()){ + if(System.currentTimeMillis()>timeoutTime){ + throw new TimeoutException("Motion timed out"); + } + + receive(); // TODO Problem still blocking + + if(abort){ + // Abort motor move + motortemplate.getCommand().setValue(TemplateMotor.Commands.Stop.ordinal()); + motortemplate.getCommand().setValue(TemplateMotor.Commands.Go.ordinal()); + break; + } + + } + } + finally { + receive(); + + // Send end of stream message + logger.info("Sending - End of Line - Data Group: "+ dataGroup); + eventbus.post(new EndOfStreamMessage(dataGroup)); + + // Close ZMQ stream + close(); + } + logger.info("Motor reached end position"); + + // Stop crlogic logic + logger.info("Stop CRLOGIC"); + template.getStatus().setValue(TemplateCrlogic.Status.STOP.toString()); + // Wait until stopped + logger.info("Wait until stopped"); + try{ + template.getStatus().waitForValue(TemplateCrlogic.Status.INACTIVE.toString(), startStopTimeout); + } + catch(ChannelException | ExecutionException | TimeoutException e){ + logger.info( "Failed to stop CRLOGIC. Logic in status: "+template.getStatus().getValue() ); + // TODO Improve error handling + throw new RuntimeException("Failed to stop CRLOGIC. Logic in status: "+template.getStatus().getValue(), e); + } + logger.info("CRLOGIC is now stopped"); + + + // Execute post actions + for(Action action: postActions){ + action.execute(); + } + + + } + finally{ + logger.info("Restore motor settings"); + motortemplate.getBaseSpeed().setValue(backupMinSpeed); + motortemplate.getVelocity().setValue(backupSpeed); + motortemplate.getBacklashDistance().setValue(backupBacklash); + } + + + if(zigZag){ + // reverse start/end + double aend = end; + end=start; + start=aend; + } + + } + catch(ChannelException | ExecutionException | TimeoutException e){ + throw new RuntimeException("Unable to execute crloop", e); + } + } + + /** + * Abort execution + */ + @Override + public void abort() { + abort = true; + } + + + /** + * Prepare this loop for execution + */ + @Override + public void prepare() { + + metadata = new ArrayList<>(); + + // Build up metadata + metadata.add(new Metadata(this.id)); + for(CrlogicResource s: sensors){ + metadata.add(new Metadata(s.getId())); + } + + try{ + // Connect crlogic channels + template = new TemplateCrlogic(); + logger.info("Connect channels"); + Map map = new HashMap<>(); + map.put("PREFIX", prefix); + cservice.createAnnotatedChannels(template, map); + + // Connect motor channels + motortemplate = new TemplateMotor(); + map = new HashMap<>(); + map.put("PREFIX", this.name); + cservice.createAnnotatedChannels(motortemplate, map); + + useReadback = motortemplate.getUseReadback().getValue(); + useEncoder = motortemplate.getUseEncoder().getValue(); + + logger.info("Motor type: "+ TemplateMotor.Type.values()[motortemplate.getType().getValue()]); + logger.info("Motor use readback: "+useReadback); + logger.info("Motor use encoder: "+useEncoder); + + // TODO build up list of readout resources (based on sensors) + readoutResources.clear(); + // first sensor is the actuator + + // Determine mode of motor + if((!useReadback) && (!useEncoder)){ + // Open loop + if(this.readback!=null){ + throw new IllegalArgumentException("Readback not supported if motor is configured in open loop"); + } + else{ + readoutResources.add(this.name); + } + + } + else if(useReadback && (!useEncoder)){ + String readback; + // use readback link + if(this.readback!=null){ + // Use specified readback + readback = this.readback; + } + else{ + // Set resouce to readback link + readback = (motortemplate.getReadbackLink().getValue()); + readback = readback.replaceAll(" +.*", ""); // remove NPP etc at the end + } + + readoutResources.add(readback); + + // Fill readback encoder settings + // Connect to encoder + TemplateEncoder encodertemplate = new TemplateEncoder(); + map = new HashMap<>(); + map.put("PREFIX", readback); + cservice.createAnnotatedChannels(encodertemplate, map); + + // Read encoder settings + if(encodertemplate.getDirection().getValue()==TemplateEncoder.Direction.Positive.ordinal()){ + crlogicDataFilter.setEncoderDirection(1); + } + else{ + crlogicDataFilter.setEncoderDirection(-1); + } + crlogicDataFilter.setEncoderOffset(encodertemplate.getOffset().getValue()); + crlogicDataFilter.setEncoderResolution(encodertemplate.getResolution().getValue()); + + // Disconnect from encoder + cservice.destroyAnnotatedChannels(encodertemplate); + + } + else if (useEncoder && (!useReadback)){ + // use readback link + if(this.readback!=null){ + throw new IllegalArgumentException("Readback not supported if motor is configured to use encoder"); + } + else{ + // Set resouce to readback link + readoutResources.add(this.name+"_ENC"); + } + } + else{ + throw new IllegalArgumentException("Motor configuration not supportet: use readback - "+useReadback+" use encoder - "+useEncoder); + } + + // Fill Motor specific settings + if(motortemplate.getDirection().getValue()==TemplateMotor.Direction.Positive.ordinal()){ + crlogicDataFilter.setMotorDirection(1); + } + else{ + crlogicDataFilter.setMotorDirection(-1); + } + crlogicDataFilter.setMotorEncoderResolution(motortemplate.getEncoderResolution().getValue()); + crlogicDataFilter.setMotorOffset(motortemplate.getOffset().getValue()); + crlogicDataFilter.setMotorReadbackResolution(motortemplate.getReadbackResolution().getValue()); + crlogicDataFilter.setMotorResolution(motortemplate.getMotorResolution().getValue()); + + // Clear all indices + scalerIndices.clear(); + + int c = 1; // We start at 1 because the actuator right now is an implicit sensor + for(CrlogicResource s: sensors){ + readoutResources.add(s.getKey()); + if(s.isDelta()){ + scalerIndices.put(c, new CrlogicDeltaDataFilter()); + } + c++; + } + + // Workaround - somehow one has to add an empty thing to the value otherwise the c logic + // does not pick up the end + readoutResources.add(""); + } + catch(Exception e){ + throw new RuntimeException("Unable to prepare crloop: ",e); + } + } + + + /** + * Connect ZMQ stream + * @param address ZMQ endpoint address + */ + private void connect(String address) { + // Clear interrupted state + Thread.interrupted(); + + logger.info("Connecting with IOC"+address); + context = ZMQ.context(1); + socket = context.socket(ZMQ.PULL); + socket.setRcvHWM(HIGH_WATER_MARK); + socket.connect("tcp://"+address+":9999"); + } + + + /** + * Close ZMQ stream + */ + private void close() { + logger.info("Closing stream from IOC "+ioc); + + socket.close(); + context.close(); + socket = null; + context = null; + } + + + /** + * Drain sub-messages + * @return Number of sub-messages drained + */ + private int drainHangingSubmessages() { + int count = 0; + while (socket.hasReceiveMore()) { + // is there a way to avoid copying data to user space here? + socket.recv(); + count++; + } + if(count>0){ + logger.info("Drain hanging submessages"); + } + return count; + } + + + /** + * Cleanup loop + */ + @Override + public void cleanup() { + logger.info("Cleanup"); + try { + cservice.destroyAnnotatedChannels(template); + cservice.destroyAnnotatedChannels(motortemplate); + template = null; + motortemplate = null; + } catch (Exception e) { + throw new RuntimeException("Unable to destroy Channel Access channels", e); + } + } + + + @Override + public List getPreActions() { + return preActions; + } + + @Override + public List getPostActions() { + return postActions; + } + + public List getSensors() { + return sensors; + } + + @Override + public boolean isDataGroup() { + return dataGroup; + } + + @Override + public void setDataGroup(boolean dataGroup) { + this.dataGroup = dataGroup; + } + + public EventBus getEventBus(){ + return eventbus; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/CrlogicRangeDataFilter.java b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicRangeDataFilter.java new file mode 100644 index 0000000..852aafa --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicRangeDataFilter.java @@ -0,0 +1,270 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +public class CrlogicRangeDataFilter { + + private double motorResolution = 1; + private int motorDirection = 1; + private double motorOffset = 0; + private double motorReadbackResolution = 1; + private double motorEncoderResolution = 1; + + private double encoderOffset = 0; + private double encoderResolution = 1; + private int encoderDirection = 1; + + private double start = 0; + private double end = 0; + private boolean positive = true; + + /** + * Marker whether the value was equal + */ + private boolean wasEqualBefore = false; + + /** + * @return the motorResolution + */ + public double getMotorResolution() { + return motorResolution; + } + + /** + * @param motorResolution the motorResolution to set + */ + public void setMotorResolution(double motorResolution) { + this.motorResolution = motorResolution; + } + + /** + * @return the motorDirection + */ + public int getMotorDirection() { + return motorDirection; + } + + /** + * @param motorDirection the motorDirection to set + */ + public void setMotorDirection(int motorDirection) { + this.motorDirection = motorDirection; + } + + /** + * @return the motorOffset + */ + public double getMotorOffset() { + return motorOffset; + } + + /** + * @param motorOffset the motorOffset to set + */ + public void setMotorOffset(double motorOffset) { + this.motorOffset = motorOffset; + } + + /** + * @return the motorReadbackResolution + */ + public double getMotorReadbackResolution() { + return motorReadbackResolution; + } + + /** + * @param motorReadbackResolution the motorReadbackResolution to set + */ + public void setMotorReadbackResolution(double motorReadbackResolution) { + this.motorReadbackResolution = motorReadbackResolution; + } + + /** + * @return the motorEncoderResolution + */ + public double getMotorEncoderResolution() { + return motorEncoderResolution; + } + + /** + * @param motorEncoderResolution the motorEncoderResolution to set + */ + public void setMotorEncoderResolution(double motorEncoderResolution) { + this.motorEncoderResolution = motorEncoderResolution; + } + + /** + * @return the encoderOffset + */ + public double getEncoderOffset() { + return encoderOffset; + } + + /** + * @param encoderOffset the encoderOffset to set + */ + public void setEncoderOffset(double encoderOffset) { + this.encoderOffset = encoderOffset; + } + + /** + * @return the encoderResolution + */ + public double getEncoderResolution() { + return encoderResolution; + } + + /** + * @param encoderResolution the encoderResolution to set + */ + public void setEncoderResolution(double encoderResolution) { + this.encoderResolution = encoderResolution; + } + + /** + * @return the encoderDirection + */ + public int getEncoderDirection() { + return encoderDirection; + } + + /** + * @param encoderDirection the encoderDirection to set + */ + public void setEncoderDirection(int encoderDirection) { + this.encoderDirection = encoderDirection; + } + + + /** + * @return the start + */ + public double getStart() { + return start; + } + + /** + * @param start the start to set + */ + public void setStart(double start) { + this.start = start; + if(start<=end){ + positive=true; + } + else{ + positive=false; + } + } + + /** + * @return the end + */ + public double getEnd() { + return end; + } + + /** + * @param end the end to set + */ + public void setEnd(double end) { + this.end = end; + if(end>=start){ + positive=true; + } + else{ + positive=false; + } + } + + /** + * Calculate real position + * @param raw + * @return + */ + public double calculatePositionMotor(double raw){ + return(((raw * motorResolution * motorReadbackResolution)/motorDirection)+motorOffset); + } + + /** + * Calculate real motor position using the readback link + * @param raw + * @return + */ + public double calculatePositionMotorUseReadback(double raw){ + return((((raw - encoderOffset) * encoderResolution * encoderDirection * motorReadbackResolution) / motorDirection) + motorOffset); + } + + /** + * Calculate real motor position using encoder + * @param raw + * @return + */ + public double calculatePositionMotorUseEncoder(double raw){ + return(raw * motorEncoderResolution * motorReadbackResolution/motorDirection + motorOffset); + } + + /** + * Filter whether value is with the range + * @param value + * @return + */ + public boolean filter(Double value){ + if(positive){ + if(start<=value && value<=end){ + + // If motor is very accurete and user backlash==null it might be that value is exactly + // the end value. To prevent that unnecessary data is captured execute this check + if(wasEqualBefore){ + wasEqualBefore=false; // Reset flag + return false; + } + + // Check whether was equal + if(value==end){ + wasEqualBefore=true; + } + + return true; + } + else{ + return false; + } + } + else{ + if(end<=value && value <=start){ + + if(wasEqualBefore){ + wasEqualBefore=false; // Reset flag + return false; + } + + // Check whether was equal + if(value==start){ + wasEqualBefore=true; + } + + return true; + } + else{ + return false; + } + } + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/CrlogicResource.java b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicResource.java new file mode 100644 index 0000000..fa96f13 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/CrlogicResource.java @@ -0,0 +1,71 @@ +/** + * + * Copyright 2013 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ +package ch.psi.fda.core.loops.cr; + +/** + * Readout resource of crlogic + */ +public class CrlogicResource { + + /** + * Id of the data + */ + private String id; + /** + * Id of the crlogic resource to be read out. As configured in the CRLOGIC part of the IOC startup script + */ + private String key; + /** + * Flag whether to read back the delta of the values of this resource. (delta is done in software) + */ + private boolean delta = false; + + public CrlogicResource(){ + } + + public CrlogicResource(String id, String key){ + this.id = id; + this.key = key; + } + + public CrlogicResource(String id, String key, boolean delta){ + this.id = id; + this.key = key; + this.delta = delta; + } + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getKey() { + return key; + } + public void setKey(String key) { + this.key = key; + } + public boolean isDelta() { + return delta; + } + public void setDelta(boolean delta) { + this.delta = delta; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/MainHeader.java b/src/main/java/ch/psi/fda/core/loops/cr/MainHeader.java new file mode 100644 index 0000000..c022563 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/MainHeader.java @@ -0,0 +1,23 @@ +package ch.psi.fda.core.loops.cr; + +/** + * Header of the ZMQ message + */ +public class MainHeader { + private String htype; + private int elements; + + public void setElements(int elements) { + this.elements = elements; + } + public int getElements() { + return elements; + } + + public void setHtype(String htype) { + this.htype = htype; + } + public String getHtype() { + return htype; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/MergeLogic.java b/src/main/java/ch/psi/fda/core/loops/cr/MergeLogic.java new file mode 100644 index 0000000..2be847c --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/MergeLogic.java @@ -0,0 +1,209 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Message; +import ch.psi.fda.messages.Metadata; + +/** + * Class to merge two data streams into one. The secondary queues data is added to the primary queues data. + * The resulting queue therefor will hold the same amount of elements as the primary queue does. + * The datagroup flag set in the end of stream message will be set according to the flag set in the primary queue. + */ +public class MergeLogic { + + private static final Logger logger = Logger.getLogger(MergeLogic.class.getName()); + + private final EventBus primaryEventBus; + private final EventBus secondaryEventBus; + private final EventBus outputEventBus; + + private List metadata; + + // Handler of the secondary event bus. We need to keep the reference on this + // to be able to unregister ... + private Object handler; + + + boolean firstPrimary = true; + boolean firstSecondary = true; + + private BlockingQueue queue = new LinkedBlockingQueue<>(); + private List currentData = null; + + + public MergeLogic(EventBus primaryEventBus, EventBus secondaryEventBus, EventBus outputEventBus){ + this.primaryEventBus = primaryEventBus; + this.secondaryEventBus = secondaryEventBus; + this.outputEventBus = outputEventBus; + } + + // Enables the merge logic + public void enable(){ + + firstPrimary = true; + firstSecondary = true; + + queue.clear(); + + handler = new Object(){ + @Subscribe + public void onMessage(DataMessage m){ + // Only queue data messages + queue.add(m); + } + }; + + primaryEventBus.register(this); + secondaryEventBus.register(handler); + } + + // Disables the merge logic + public void disable(){ + primaryEventBus.unregister(this); + secondaryEventBus.unregister(handler); + + queue.clear(); + } + + + /** + * This is the master merging logic + * @param m Data message from primary event bus + */ + @Subscribe + public void onMessage(Message rawMessage){ + if(rawMessage instanceof EndOfStreamMessage){ + outputEventBus.post(rawMessage); + + // Clear queue of secondary event bus + queue.clear(); + return; + } + else if(!(rawMessage instanceof DataMessage)){ + logger.warning("Message type not supported - ignore"); + return; + } + + DataMessage message = (DataMessage) rawMessage; + + // Hack to remove the synchronization timestamp from the primary event bus + if(firstPrimary){ + firstPrimary = false; + + metadata = new ArrayList<>(); + List pqm = message.getMetadata(); + metadata.add(pqm.get(0)); // add first component (this is the actuator) + // Skip the next component as this is the timestamp used to merge the data + for(int i=2;i sqm = msCheck.getMetadata(); + // Skip first two components of the message as this is the timestamp + for(int i=2;imilliseconds || (currMilliCheck==milliseconds && currNanoCheck>nanoOffset)){ + break; + } + + try { + DataMessage currentDataMessage = queue.take(); + currentData = currentDataMessage.getData(); + // Remove timestamps used for synchronization + currentData.remove(0); + currentData.remove(0); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + } + + if(currentData == null){ + throw new RuntimeException("No SCR data arrived"); + } + + // Add data to primary data queue message and put it into the out queue + message.getData().addAll(currentData); + message.setMetadata(metadata); + outputEventBus.post(message); + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/ParallelCrlogic.java b/src/main/java/ch/psi/fda/core/loops/cr/ParallelCrlogic.java new file mode 100644 index 0000000..884f82b --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/ParallelCrlogic.java @@ -0,0 +1,211 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.core.Action; +import ch.psi.fda.core.ActionLoop; + +public class ParallelCrlogic implements ActionLoop { + + private static final Logger logger = Logger.getLogger(ParallelCrlogic.class.getName()); + + /** + * Flag to indicate whether the data of this loop will be grouped + * According to this flag the dataGroup flag in EndOfStream will be set. + */ + private boolean dataGroup = false; + + private CrlogicLoopStream crlogic; + private ScrlogicLoop scrlogic; + + /** + * List of actions that are executed at the beginning of the loop. + */ + private List preActions; + /** + * List of actions that are executed at the end of the loop. + */ + private List postActions; + + private MergeLogic mergeLogic; + + + private final EventBus eventbus; + + public ParallelCrlogic(CrlogicLoopStream crlogic, ScrlogicLoop scrlogic){ + + if(crlogic==null){ + throw new IllegalArgumentException("No Crloop specified"); + } + if(scrlogic==null){ + throw new IllegalArgumentException("No Scrloop specified"); + } + + + this.eventbus = new EventBus(); + + this.crlogic = crlogic; + // Add timestamp to sensor at the beginning of the sensor list as this is required for merging the data + // Timestamp will be at the second position of a message in the queue! + this.crlogic.getSensors().add(0, new CrlogicResource("tmp_timestamp","TIMESTAMP")); + this.scrlogic = scrlogic; + + // Initialize lists used by the loop + this.preActions = new ArrayList(); + this.postActions = new ArrayList(); + + + + this.mergeLogic = new MergeLogic(crlogic.getEventBus(), scrlogic.getEventBus(), eventbus); + } + + @Override + public void prepare() { + } + + @Override + public void execute() throws InterruptedException { + + mergeLogic.enable(); + + // Execute pre actions + for(Action action: preActions){ + action.execute(); + } + + + ExecutorService service = Executors.newFixedThreadPool(3); // 2 for the parallel data acquisition and 1 for the merger thread + final CyclicBarrier b = new CyclicBarrier(2); + + List> list = new ArrayList>(); + + // Start a thread for each logic + logger.info("Submit logic"); + Future f = service.submit(new Callable(){ + @Override + public Boolean call() throws Exception { + + // Prepare logic + crlogic.prepare(); + + // Ensure that really all parallel logics start in parallel + b.await(); + + // Execute the logic of this path + crlogic.execute(); + +// crlogic.cleanup(); + + // Need to stop the scrlogic logic (otherwise it would keep going to take data) + scrlogic.abort(); + return true; + }}); + list.add(f); + + //Start a thread for the scrlogic + logger.info("Submit logic"); + f = service.submit(new Callable(){ + @Override + public Boolean call() throws Exception { + + // Prepare logic + scrlogic.prepare(); + + // Ensure that really all parallel logics start in parallel + b.await(); + + // Execute the logic of this path + scrlogic.execute(); // This actually just starts the collection ... + +// scrlogic.cleanup(); + return true; + }}); + list.add(f); + + for(Future bf: list){ + // Wait for completion of the thread + try { + bf.get(); + } catch (ExecutionException e) { + logger.log(Level.WARNING, "Something went wrong while waiting for crthreads to finish"); + throw new RuntimeException(e); + } + } + + // Wait until all threads have finished + service.shutdown(); + service.awaitTermination(1, TimeUnit.MINUTES); + + + // Execute post actions + for(Action action: postActions){ + action.execute(); + } + + mergeLogic.disable(); + } + + @Override + public void abort() { + } + + @Override + public void cleanup() { + } + + @Override + public List getPreActions() { + return preActions; + } + + @Override + public List getPostActions() { + return postActions; + } + + @Override + public boolean isDataGroup() { + return dataGroup; + } + + @Override + public void setDataGroup(boolean dataGroup) { + this.dataGroup = dataGroup; + } + + @Override + public EventBus getEventBus(){ + return eventbus; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/ScrlogicLoop.java b/src/main/java/ch/psi/fda/core/loops/cr/ScrlogicLoop.java new file mode 100644 index 0000000..e75d471 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/ScrlogicLoop.java @@ -0,0 +1,224 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.beans.PropertyChangeEvent; +import java.beans.PropertyChangeListener; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import com.google.common.eventbus.EventBus; + +import ch.psi.fda.core.Action; +import ch.psi.fda.core.ActionLoop; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Metadata; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.impl.type.DoubleTimestamp; + +/** + * Assumptions: - The delay between the monitor writing the value to the monitor + * queue and the readout of all the queues is sufficient to prevent the + * situation that some monitors of events close to each other on different IOC's + * have not arrived yet. - The sequence of monitors fired for one channel is + * according to the sequence of the causes. No monitor package is overtaking an + * other package on the network. + * + * - No monitor events are lost on the network (while using monitors you cannot + * guarantee this) + * + * The data queue returned by this logic includes two items for the timestamp + * and nanoseconds offset. These two items are the first two items of a message + * The id's are: crTimestampMilliseconds crTimestampOffsetNanoseconds + */ +public class ScrlogicLoop implements ActionLoop { + + private static String ID_TIMESTAMP_MILLISECONDS = "crTimestampMilliseconds"; + private static String ID_TIMESTAMP_OFFSET_NANOSECONDS = "crTimestampOffsetNanoseconds"; + + private static final Logger logger = Logger.getLogger(ScrlogicLoop.class.getName()); + + private boolean dataGroup = false; + private final List preActions = new ArrayList(); + private final List postActions = new ArrayList(); + + private final List> sensors; + private final List sensorIds; + + /** + * List of monitors that were attached to the sensor channels (i.e + * workaround) + */ + private final List monitors = new ArrayList<>(); + + private List currentValues; + + private CountDownLatch latch; + + private final EventBus eventbus; + private List metadata; + + public ScrlogicLoop(List sensorIds, List> sensors) { + this.eventbus = new EventBus(); + this.sensors = sensors; + this.sensorIds = sensorIds; + } + + @Override + public void execute() throws InterruptedException { + + latch = new CountDownLatch(1); + + // Initialize current values + currentValues = new ArrayList(sensors.size() + 2); + // Initialize values + currentValues.add(0.0); + currentValues.add(0.0); + for (Channel sensor : sensors) { + try { + DoubleTimestamp value = sensor.getValue(); + double timestamp = value.getTimestamp().getTime(); + double noffset = value.getNanosecondOffset(); + if (timestamp > ((Double) currentValues.get(0))) { + // We don't care about the nanoseconds offset so far + currentValues.set(0, timestamp); + currentValues.set(1, noffset); + } + currentValues.add(value.getValue()); + } catch (InterruptedException | TimeoutException | ChannelException | ExecutionException e) { + throw new RuntimeException("Unable to retrieve initial value"); + } + // Initialize current value with NAN + } + + // Create metadata + metadata = new ArrayList<>(sensors.size()); + + // Build up data message metadata based on the channels registered. + metadata.add(new Metadata(ID_TIMESTAMP_MILLISECONDS)); + metadata.add(new Metadata(ID_TIMESTAMP_OFFSET_NANOSECONDS)); + for (String id : sensorIds) { + metadata.add(new Metadata(id)); + } + + // Send a first data message to be sure that there is a message inside + // the queue + DataMessage message = new DataMessage(metadata); + message.getData().addAll(currentValues); + + eventbus.post(message); + + // Attach monitors to the channels (this is actually a workaround) + int counter = 2; + for (Channel sensor : sensors) { + final int currentCount = counter; + + PropertyChangeListener listener = new PropertyChangeListener() { + + @Override + public void propertyChange(PropertyChangeEvent evt) { + if (evt.getPropertyName().equals("value")) { + + // Merging values this way it is not 100% accurate as + // theoretically values does not need to come in + // in the correct time sequence. However we tradeoff + // this accuracy as we are not precise anyway + + DoubleTimestamp v = (DoubleTimestamp) evt.getNewValue(); + double timestamp = v.getTimestamp().getTime(); + double noffset = v.getNanosecondOffset(); + currentValues.set(0, timestamp); + currentValues.set(1, noffset); + currentValues.set(currentCount, v.getValue()); + + DataMessage message = new DataMessage(metadata); + message.getData().addAll(currentValues); + // logger.info("update"+v.getValue()); + eventbus.post(message); + } + } + }; + sensor.addPropertyChangeListener(listener); + monitors.add(listener); + + counter++; + } + + logger.info("Start data acquisition"); + + latch.await(); + + // Remove monitors + + logger.info("Remove monitors"); + for (int i = 0; i < sensors.size(); i++) { + Channel sensor = sensors.get(i); + sensor.removePropertyChangeListener(monitors.get(i)); + } + + // Put end of stream to the queue + eventbus.post(new EndOfStreamMessage(dataGroup)); + + } + + @Override + public void abort() { + latch.countDown(); + } + + @Override + public void prepare() { + } + + @Override + public void cleanup() { + } + + @Override + public List getPreActions() { + return preActions; + } + + @Override + public List getPostActions() { + return postActions; + } + + @Override + public boolean isDataGroup() { + return dataGroup; + } + + @Override + public void setDataGroup(boolean dataGroup) { + this.dataGroup = dataGroup; + } + + @Override + public EventBus getEventBus() { + return eventbus; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/TemplateCrlogic.java b/src/main/java/ch/psi/fda/core/loops/cr/TemplateCrlogic.java new file mode 100644 index 0000000..57736fc --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/TemplateCrlogic.java @@ -0,0 +1,114 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import ch.psi.jcae.Channel; +import ch.psi.jcae.annotation.CaChannel; + +public class TemplateCrlogic { + + /** + * Ticks per second - IOC setting + * ATTENTION - This field must only be set bu the IOC - ATTENTION + */ + @CaChannel(type=Integer.class, name ="${PREFIX}:TPS") + private Channel ticksPerSecond; + + /** + * Status of the OTFSCAN IOC logic + */ + public enum Status { SETUP, INACTIVE, INITIALIZE, ACTIVE, STOP, FAULT, ERROR }; + @CaChannel(type=String.class, name ="${PREFIX}:STATUS") + private Channel status; + + /** + * Message from the OTFSCAN IOC logic + */ + @CaChannel(type=String.class, name ="${PREFIX}:MSG") + private Channel message; + + /** + * IOC ticks between data acquisition interrupts + */ + @CaChannel(type=Integer.class, name ="${PREFIX}:TBINT") + private Channel ticksBetweenInterrupts; + + /** + * Name or ip address of the NFS server to save the data to + * (depending on the IOC setup) + */ + @CaChannel(type=String.class, name ="${PREFIX}:NFSSE") + private Channel nfsServer; + + /** + * Name of the NFS share on the NFS server + */ + @CaChannel(type=String.class, name ="${PREFIX}:NFSSH") + private Channel nfsShare; + + /** + * Name of the data file + */ + @CaChannel(type=String.class, name ="${PREFIX}:DFNAM") + private Channel dataFile; + + /** + * Flag to identify whether the data file should be appended + */ + @CaChannel(type=Boolean.class, name ="${PREFIX}:FAPPE") + private Channel appendFile; + + /** + * Readout resources + */ + @CaChannel(type=String[].class, name ="${PREFIX}:RRES") + private Channel readoutResources; + + + + public Channel getTicksPerSecond() { + return ticksPerSecond; + } + public Channel getStatus() { + return status; + } + public Channel getMessage() { + return message; + } + public Channel getTicksBetweenInterrupts() { + return ticksBetweenInterrupts; + } + public Channel getNfsServer() { + return nfsServer; + } + public Channel getNfsShare() { + return nfsShare; + } + public Channel getDataFile() { + return dataFile; + } + public Channel getAppendFile() { + return appendFile; + } + public Channel getReadoutResources() { + return readoutResources; + } + +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/TemplateEncoder.java b/src/main/java/ch/psi/fda/core/loops/cr/TemplateEncoder.java new file mode 100644 index 0000000..c61380d --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/TemplateEncoder.java @@ -0,0 +1,59 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import ch.psi.jcae.Channel; +import ch.psi.jcae.annotation.CaChannel; + +public class TemplateEncoder { + + /** + * Resolution - $(P)$(E)_SCL + */ + @CaChannel(type=Double.class, name="${PREFIX}_SCL") + private Channel resolution; + + /** + * Offset - $(P)$(E)_OFF + */ + @CaChannel(type=Double.class, name ="${PREFIX}_OFF") + private Channel offset; + + /** + * Direction - $(P)$(E)_DIR + */ + public enum Direction {Negative, Positive}; + @CaChannel(type=Integer.class, name ="${PREFIX}_DIR") + private Channel direction; + + + + public Channel getResolution() { + return resolution; + } + public Channel getOffset() { + return offset; + } + public Channel getDirection() { + return direction; + } + + +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/TemplateMotor.java b/src/main/java/ch/psi/fda/core/loops/cr/TemplateMotor.java new file mode 100644 index 0000000..fea3dcb --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/TemplateMotor.java @@ -0,0 +1,471 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import ch.psi.jcae.Channel; +import ch.psi.jcae.annotation.CaChannel; + +public class TemplateMotor { + + public enum Type {SOFT_CHANNEL, MOTOR_SIMULATION, OMS_VME58, OMS_MAXv}; + + /** + * ## Drive section ## + * # User coordinates # + */ + + /** + * .HLM High limit - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.HLM") + private Channel highLimit; + + /** + * .LLM Low limit - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.LLM") + private Channel lowLimit; + + /** + * .RBV Readback value - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.RBV", monitor=true) + private Channel readbackValue; + + /** + * .VAL Set value - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.VAL", monitor=true) + private Channel setValue; + + /** + * .RLV Relative move value - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.RLV") + private Channel relativeMoveValue; + + /** + * .TWV Teak value - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.TWV") + private Channel tweakValue; + + /** + * .TWR Tweak reverse - move left - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.TWR") + private Channel tweakReverse; + + /** + * .TWF Tweak forward - move right - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.TWF") + private Channel tweakForward; + + /** + * .JOGR Jog reverse - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.JOGR") + private Channel jogReverse; + + /** + * .JOGF Jog forward - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.JOGF") + private Channel jogForward; + + /** + * .HOMR Home reverse - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.HOMR") + private Channel homeReverse; + + /** + * .HOMF Home forward - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.HOMF") + private Channel homeForward; + + /** + * .EGU Engineering unit - String + */ + @CaChannel(type=String.class, name ="${PREFIX}.EGU") + private Channel engineeringUnit; + + /** + * .DTYP Type - String (e.g. "OMS MAXv") see enum Type + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.DTYP") + private Channel type; + + /** + * .DESC Description - String + */ + @CaChannel(type=String.class, name ="${PREFIX}.DESC") + private Channel description; + + + /** + * # Dial coordinates # + */ + + /** + * .DHLM Dial high limit - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.DHLM") + private Channel dialHighLimit; + + /** + * .DLLM Dial low limit - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.DLLM") + private Channel dialLowLimit; + + /** + * .DRBV Dial readback value - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.DRBV", monitor=true) + private Channel dialReadbackValue; + + /** + * .DVAL Dial set value - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.DVAL", monitor=true) + private Channel dialSetValue; + + /** + * .RVAL Raw value - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.RVAL", monitor=true) + private Channel rawValue; + + /** + * .RRBV Raw readback value - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.RRBV", monitor=true) + private Channel rawReadbackValue; + + /** + * .SPMG Stop/Pause/Move/Go - (0:"Stop", 1:"Pause", 2:"Move", 3:"Go") - 3 + */ + public enum Commands { Stop, Pause, Move, Go }; + @CaChannel(type=Integer.class, name ="${PREFIX}.SPMG") + private Channel command; + + + /** + * ## Calibration section ## + */ + + /** + * .SET Set/Use Switch - (0:"Use", 1:"Set") - 0 + */ + public enum Calibration {Use, Set}; + @CaChannel(type=Integer.class, name ="${PREFIX}.SET") + private Channel calibration; + + /** + * .OFF User offset (EGU) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.OFF") + private Channel offset; + + /** + * .FOFF Offset-Freeze Switch - (0:"Variable", 1:"Frozen") - 1 + */ + public enum OffsetMode {Variable, Frozen}; + @CaChannel(type=Integer.class, name ="${PREFIX}.FOFF") + private Channel offsetMode; + + /** + * .DIR User direction - (0:"Pos", 1:"Neg") + */ + public enum Direction {Positive, Negative}; + @CaChannel(type=Integer.class, name ="${PREFIX}.DIR") + private Channel direction; + + + /** + * ## Dynamics ## + */ + + /** + * .VELO Velocity (EGU/s) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.VELO") + private Channel velocity; + + /** + * .BVEL Backlash velocity (EGU/s) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.BVEL") + private Channel backlashVelocity; + + /** + * .VBAS Base speed (EGU/s) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.VBAS") + private Channel baseSpeed; + + /** + * .ACCL Acceleration time / seconds to velocity - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.ACCL") + private Channel accelerationTime; + + /** + * .BACC Backlash acceleration time / seconds to velocity - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.BACC") + private Channel backlashAccelerationTime; + + /** + * .BDST Backlash distance (EGU) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.BDST") + private Channel backlashDistance; + + /** + * .FRAC Move fraction - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.FRAC") + private Channel moveFracion; + + + /** + * ## Resolution ## + */ + + /** + * .MRES Motor resolution - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.MRES") + private Channel motorResolution; + + /** + * .ERES Encoder resolution - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.ERES") + private Channel encoderResolution; + + /** + * .RRES Readback resolution - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.RRES") + private Channel readbackResolution; + + /** + * .RDBD Retry deadband (EGU) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.RDBD") + private Channel retryDeadband; + + /** + * .RTRY Max retry count - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.RTRY") + private Channel maxRetryCount; + + /** + * .RCNT Retry count - int + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.RCNT", monitor=true) + private Channel retryCount; + + /** + * .UEIP Use encoder (if present) - (0:"No", 1:"Yes") + */ + @CaChannel(type=Boolean.class, name ="${PREFIX}.UEIP") + private Channel useEncoder; + + /** + * .URIP Use readback link (if present) - (0:"No", 1:"Yes") + */ + @CaChannel(type=Boolean.class, name ="${PREFIX}.URIP") + private Channel useReadback; + + /** + * .DLY Readback delay (s) - double + */ + @CaChannel(type=Double.class, name ="${PREFIX}.DLY") + private Channel readbackDelay; + + /** + * .RDBL Readback link - String + */ + @CaChannel(type=String.class, name ="${PREFIX}.RDBL") + private Channel readbackLink; + + /** + * .OMSL Output mode select - (0:"supervisory", 1:"closed_loop") + */ + public enum OutputMode {Supervisory, Closed_Loop}; + @CaChannel(type=Integer.class, name ="${PREFIX}.OMSL") + private Channel outputMode; + + /** + * ## Status ## + */ + + /** + * .DMOV Done move - int + */ + @CaChannel(type=Boolean.class, name ="${PREFIX}.DMOV", monitor=true) + private Channel moveDone; + + + + public Channel getHighLimit() { + return highLimit; + } + public Channel getLowLimit() { + return lowLimit; + } + public Channel getReadbackValue() { + return readbackValue; + } + public Channel getSetValue() { + return setValue; + } + public Channel getRelativeMoveValue() { + return relativeMoveValue; + } + public Channel getTweakValue() { + return tweakValue; + } + public Channel getTweakReverse() { + return tweakReverse; + } + public Channel getTweakForward() { + return tweakForward; + } + public Channel getJogReverse() { + return jogReverse; + } + public Channel getJogForward() { + return jogForward; + } + public Channel getHomeReverse() { + return homeReverse; + } + public Channel getHomeForward() { + return homeForward; + } + public Channel getEngineeringUnit() { + return engineeringUnit; + } + public Channel getType() { + return type; + } + public Channel getDescription() { + return description; + } + public Channel getDialHighLimit() { + return dialHighLimit; + } + public Channel getDialLowLimit() { + return dialLowLimit; + } + public Channel getDialReadbackValue() { + return dialReadbackValue; + } + public Channel getDialSetValue() { + return dialSetValue; + } + public Channel getRawValue() { + return rawValue; + } + public Channel getRawReadbackValue() { + return rawReadbackValue; + } + public Channel getCommand() { + return command; + } + public Channel getCalibration() { + return calibration; + } + public Channel getOffset() { + return offset; + } + public Channel getOffsetMode() { + return offsetMode; + } + public Channel getDirection() { + return direction; + } + public Channel getVelocity() { + return velocity; + } + public Channel getBacklashVelocity() { + return backlashVelocity; + } + public Channel getBaseSpeed() { + return baseSpeed; + } + public Channel getAccelerationTime() { + return accelerationTime; + } + public Channel getBacklashAccelerationTime() { + return backlashAccelerationTime; + } + public Channel getBacklashDistance() { + return backlashDistance; + } + public Channel getMoveFracion() { + return moveFracion; + } + public Channel getMotorResolution() { + return motorResolution; + } + public Channel getEncoderResolution() { + return encoderResolution; + } + public Channel getReadbackResolution() { + return readbackResolution; + } + public Channel getRetryDeadband() { + return retryDeadband; + } + public Channel getMaxRetryCount() { + return maxRetryCount; + } + public Channel getRetryCount() { + return retryCount; + } + public Channel getUseEncoder() { + return useEncoder; + } + public Channel getUseReadback() { + return useReadback; + } + public Channel getReadbackDelay() { + return readbackDelay; + } + public Channel getReadbackLink() { + return readbackLink; + } + public Channel getOutputMode() { + return outputMode; + } + public Channel getMoveDone() { + return moveDone; + } + +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/TemplateVSC16Scaler.java b/src/main/java/ch/psi/fda/core/loops/cr/TemplateVSC16Scaler.java new file mode 100644 index 0000000..4bac08c --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/TemplateVSC16Scaler.java @@ -0,0 +1,80 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.List; + +import ch.psi.jcae.Channel; +import ch.psi.jcae.annotation.CaChannel; + +public class TemplateVSC16Scaler { + + /** + * Command + */ + public enum Command {Done, Count}; + @CaChannel(type=Integer.class, name ="${PREFIX}.CNT") + private Channel command; + + + public enum Mode {OneShot, AutoCount}; + /** + * Count mode + */ + @CaChannel(type=Integer.class, name ="${PREFIX}.CONT") + private Channel mode; + + /** + * Channel description + */ + @CaChannel(type=Boolean.class, name={"${PREFIX}.NM1", "${PREFIX}.NM2", "${PREFIX}.NM3", "${PREFIX}.NM4", "${PREFIX}.NM5", "${PREFIX}.NM6", "${PREFIX}.NM7", "${PREFIX}.NM8", "${PREFIX}.NM9", "${PREFIX}.NM10", "${PREFIX}.NM11", "${PREFIX}.NM12", "${PREFIX}.NM13", "${PREFIX}.NM14", "${PREFIX}.NM15", "${PREFIX}.NM16"}) + private List> channelDescription; + + /** + * Channel gate + */ + @CaChannel(type=Boolean.class, name={"${PREFIX}.G1", "${PREFIX}.G2", "${PREFIX}.G3", "${PREFIX}.G4", "${PREFIX}.G5", "${PREFIX}.G6", "${PREFIX}.G7", "${PREFIX}.G8", "${PREFIX}.G9", "${PREFIX}.G10", "${PREFIX}.G11", "${PREFIX}.G12", "${PREFIX}.G13", "${PREFIX}.G14", "${PREFIX}.G15", "${PREFIX}.G16"}) + private List> channelGate; + + /** + * Channel preset count + * If gate is on scaler will only count until this value + */ + @CaChannel(type=Integer.class, name={"${PREFIX}.PR1", "${PREFIX}.PR2", "${PREFIX}.PR3", "${PREFIX}.PR4", "${PREFIX}.PR5", "${PREFIX}.PR6", "${PREFIX}.PR7", "${PREFIX}.PR8", "${PREFIX}.PR9", "${PREFIX}.PR10", "${PREFIX}.PR11", "${PREFIX}.PR12", "${PREFIX}.PR13", "${PREFIX}.PR14", "${PREFIX}.PR15", "${PREFIX}.PR16"}) + private List> channelPresetCount; + + + + public Channel getCommand() { + return command; + } + public Channel getMode() { + return mode; + } + public List> getChannelDescription() { + return channelDescription; + } + public List> getChannelGate() { + return channelGate; + } + public List> getChannelPresetCount() { + return channelPresetCount; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/TimestampedValue.java b/src/main/java/ch/psi/fda/core/loops/cr/TimestampedValue.java new file mode 100644 index 0000000..0250cf4 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/TimestampedValue.java @@ -0,0 +1,43 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +public class TimestampedValue { + + private final Double value; + private final long timestamp; + private final long nanosecondsOffset; + + public TimestampedValue(Double value, long timestamp, long nanosecondsOffset){ + this.value = value; + this.timestamp = timestamp; + this.nanosecondsOffset = nanosecondsOffset; + } + + public Double getValue() { + return value; + } + public long getTimestamp() { + return timestamp; + } + public long getNanosecondsOffset() { + return nanosecondsOffset; + } +} diff --git a/src/main/java/ch/psi/fda/core/loops/cr/TimestampedValueComparator.java b/src/main/java/ch/psi/fda/core/loops/cr/TimestampedValueComparator.java new file mode 100644 index 0000000..6d0cc69 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/loops/cr/TimestampedValueComparator.java @@ -0,0 +1,44 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.Comparator; + +/** + * Comparator for comparint 2 timestamped values + */ +public class TimestampedValueComparator implements Comparator { + + @Override + public int compare(TimestampedValue o1, TimestampedValue o2) { + if (o1.getTimestamp() < o2.getTimestamp()) { + return -1; + } else if (o1.getTimestamp() > o2.getTimestamp()) { + return 1; + } else if (o1.getTimestamp() == o2.getTimestamp()) { + if (o1.getNanosecondsOffset() < o2.getNanosecondsOffset()) { + return -1; + } else if (o1.getNanosecondsOffset() > o2.getNanosecondsOffset()) { + return 1; + } + } + return 0; + } +} diff --git a/src/main/java/ch/psi/fda/core/manipulator/JythonManipulation.java b/src/main/java/ch/psi/fda/core/manipulator/JythonManipulation.java new file mode 100644 index 0000000..db84bd9 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/manipulator/JythonManipulation.java @@ -0,0 +1,283 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.manipulator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; + +import ch.psi.fda.core.Manipulation; +import ch.psi.fda.core.scripting.JythonParameterMapping; +import ch.psi.fda.core.scripting.JythonParameterMappingChannel; +import ch.psi.fda.core.scripting.JythonParameterMappingGlobalVariable; +import ch.psi.fda.core.scripting.JythonParameterMappingID; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.Metadata; + +public class JythonManipulation implements Manipulation{ + + private static Logger logger = Logger.getLogger(JythonManipulation.class.getName()); + + /** + * Pattern of the entry function of the jython script + */ + private static final String entryFunction = "process"; + private static final String entryFunctionPattern = "def "+entryFunction+"\\((.*)\\):"; + + /** + * Code of the manipulation. The script need to implement a function of the type: + * + * def process(): + * # ... your code + * return value + * + * + * The number of parameters of the script are not limited. So process(a) is valid + * as well as process(a,b,c,d,e,f). + * However for each parameter there need to be a mapping inside the mapping list! If there is no + * mapping inside the Manipulator evaluating this Manipulation will throw an Exception. + */ + private final String script; + + /** + * Id of the resulting component + */ + private final String id; + + /** + * Mapping of components to manipulation code variables + */ + private final List mapping; + + private final boolean returnArray; + + /** + * Script engine of the manipulator + */ + private ScriptEngine engine; + /** + * Component index of the script parameter. The sequence of the indexes in this array correspond to the script + * parameter position, i.e. the first index corresponds to the first parameter. + */ +// private Integer[] parameterIndex; + private List parameterIds = new ArrayList<>(); + /** + * Parameter array of the entry function + */ + private String[] parameter; + + /** + * Jython entry call + */ + private String jythonCall; + + + private Map gvariables = new HashMap(); + + + public JythonManipulation(String id, String script, List mapping){ + this(id, script, mapping, false); + } + + /** + * Constructor + * @param id + * @param script + * @param mapping + */ + public JythonManipulation(String id, String script, List mapping, boolean returnArray){ + this.id = id; + this.script = script; + this.mapping = mapping; + this.returnArray = returnArray; + } + + /** + * @return the script + */ + public String getScript() { + return script; + } + + /** + * @return the mapping + */ + public List getMapping() { + return mapping; + } + + + @Override + public String getId() { + return id; + } + + + @Override + public void initialize(List metadata){ + + // Workaround for Jython memory leak + // http://blog.hillbrecht.de/2009/07/11/jython-memory-leakout-of-memory-problem/ + System.setProperty("python.options.internalTablesImpl","weak"); + + // Create new script engine + this.engine = new ScriptEngineManager().getEngineByName("python"); + + // Determine script entry function and the function parameters + Pattern pattern = Pattern.compile(entryFunctionPattern); + Matcher matcher = pattern.matcher(this.script); + if(matcher.find() && matcher.groupCount()==1){ + if(!matcher.group(1).trim().equals("")){ + logger.finest("Entry function '"+entryFunctionPattern+"' found - Identified parameters: "+matcher.group(1)); + parameter = matcher.group(1).split(" *, *"); + } + else{ + parameter = new String[0]; + } + } + else{ + throw new IllegalArgumentException("Cannot determine entry function: "+entryFunctionPattern); + } + + // Load manipulation script + try { + engine.eval(this.script); + } catch (ScriptException e) { + throw new RuntimeException("Unable to load manipulation script", e); + } + + // Determine component index of the needed parameters + // If the component index of the parameter cannot be determined an IllegalArgumentException is thrown +// parameterIndex = new Integer[parameter.length]; + parameterIds = new ArrayList<>(); + for(int i=0;i pm = (JythonParameterMappingChannel) jpm; +// parameterIndex[i] = null; + parameterIds.add(null); + engine.put(pm.getVariable(), pm.getChannel()); + } + else if (jpm instanceof JythonParameterMappingGlobalVariable){ + JythonParameterMappingGlobalVariable pm = (JythonParameterMappingGlobalVariable)jpm; + parameterIds.add(null); +// parameterIndex[i] = null; + + engine.put(pm.getVariable(), pm.getGlobalVariable()); + } + found=true; + break; + } + } + // If there is no mapping nothing can be found + // If there are mappings everything need to be found + if(!found){ + throw new IllegalArgumentException("No mapping compontent found for parameter "+p); + } + } + + StringBuffer buffer = new StringBuffer(); + buffer.append(entryFunction); + buffer.append("("); + + for(String p: parameter){ + buffer.append(p); + buffer.append(","); + } + + buffer.setCharAt(buffer.length()-1, ')'); + + jythonCall = buffer.toString(); + } + + @Override + public Object execute(DataMessage message){ + + // Set global variables - WORKAROUND gvariables + // This block is not in initialization as we want to assure that all invocations + // of this manipulation will get the same value (i.e. to prevent inconsistent behaviour + // if variable was changed during an execution of the manipulation) + for(String k: gvariables.keySet()){ + engine.put(k, gvariables.get(k)); + } + + // Manipulate data + for(int i=0;i. + * + */ + +package ch.psi.fda.core.scripting; + +public class JythonGlobalVariable { + private String name; + private double value; + + /** + * @return the name + */ + public String getName() { + return name; + } + /** + * @param name the name to set + */ + public void setName(String name) { + this.name = name; + } + /** + * @return the value + */ + public double getValue() { + return value; + } + /** + * @param value the value to set + */ + public void setValue(double value) { + this.value = value; + } + +} diff --git a/src/main/java/ch/psi/fda/core/scripting/JythonParameterMapping.java b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMapping.java new file mode 100644 index 0000000..fa03630 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMapping.java @@ -0,0 +1,53 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.scripting; + +/** + * Mapping of a parameter to something + */ +public abstract class JythonParameterMapping { + + /** + * Variable name inside the script + */ + private String variable; + + /** + * Constructor + * @param variable + */ + public JythonParameterMapping(String variable){ + this.variable = variable; + } + + /** + * @return the variable + */ + public String getVariable() { + return variable; + } + /** + * @param variable the variable to set + */ + public void setVariable(String variable) { + this.variable = variable; + } + +} diff --git a/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingChannel.java b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingChannel.java new file mode 100644 index 0000000..9f62b8d --- /dev/null +++ b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingChannel.java @@ -0,0 +1,40 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.scripting; + +import ch.psi.jcae.Channel; + + +/** + * Mapping of a script parameter to a channel bean. + */ +public class JythonParameterMappingChannel extends JythonParameterMapping { + + private Channel channel; + + public JythonParameterMappingChannel(String variable, Channel channel){ + super(variable); + this.channel = channel; + } + + public Channel getChannel() { + return channel; + } +} diff --git a/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingGlobalVariable.java b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingGlobalVariable.java new file mode 100644 index 0000000..105066c --- /dev/null +++ b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingGlobalVariable.java @@ -0,0 +1,54 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.scripting; + + +/** + * Mapping of a script parameter to a component via the Id. + */ +public class JythonParameterMappingGlobalVariable extends JythonParameterMapping { + + private JythonGlobalVariable globalVariable; + + /** + * Constuctor + * @param variable + * @param globalVariable + */ + public JythonParameterMappingGlobalVariable(String variable, JythonGlobalVariable globalVariable){ + super(variable); + this.globalVariable = globalVariable; + } + + /** + * @return the globalVariable + */ + public JythonGlobalVariable getGlobalVariable() { + return globalVariable; + } + + /** + * @param globalVariable the globalVariable to set + */ + public void setGlobalVariable(JythonGlobalVariable globalVariable) { + this.globalVariable = globalVariable; + } + +} diff --git a/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingID.java b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingID.java new file mode 100644 index 0000000..34556ac --- /dev/null +++ b/src/main/java/ch/psi/fda/core/scripting/JythonParameterMappingID.java @@ -0,0 +1,56 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.scripting; + + +/** + * Mapping of a script parameter to a component via the Id. + */ +public class JythonParameterMappingID extends JythonParameterMapping { + + /** + * Id of the component to map to this variable + */ + private String refid; + + /** + * Constructor accepting varible/id pair + * @param variable + * @param refid + */ + public JythonParameterMappingID(String variable, String refid){ + super(variable); + this.refid = refid; + } + + /** + * @return the refid + */ + public String getRefid() { + return refid; + } + /** + * @param refid the refid to set + */ + public void setRefid(String refid) { + this.refid = refid; + } + +} diff --git a/src/main/java/ch/psi/fda/core/sensors/ChannelAccessSensor.java b/src/main/java/ch/psi/fda/core/sensors/ChannelAccessSensor.java new file mode 100644 index 0000000..e04a80f --- /dev/null +++ b/src/main/java/ch/psi/fda/core/sensors/ChannelAccessSensor.java @@ -0,0 +1,72 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.sensors; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import ch.psi.fda.core.Sensor; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelException; + +/** + * Channel Access sensor capable of reading a channel access channel + */ +public class ChannelAccessSensor implements Sensor { + + private static Logger logger = Logger.getLogger(ChannelAccessSensor.class.getName()); + + private Channel channel; + private final String id; + private boolean failOnSensorError = true; + + public ChannelAccessSensor(String id, Channel channel){ + this.id = id; + this.channel = channel; + } + + public ChannelAccessSensor(String id, Channel channel, boolean failOnSensorError){ + this.id = id; + this.channel = channel; + this.failOnSensorError = failOnSensorError; + } + + @Override + public Object read() throws InterruptedException { + logger.finest("Read sensor "+channel.getName()); + + T v; + try { + v = channel.getValue(); + } catch (TimeoutException | ChannelException | ExecutionException e) { + if(failOnSensorError){ + throw new RuntimeException("Unable to get value from channel [name:"+channel.getName()+"]",e); + } + v = null; + } + return(v); + } + + @Override + public String getId() { + return id; + } +} diff --git a/src/main/java/ch/psi/fda/core/sensors/ComplexSensor.java b/src/main/java/ch/psi/fda/core/sensors/ComplexSensor.java new file mode 100644 index 0000000..3160a05 --- /dev/null +++ b/src/main/java/ch/psi/fda/core/sensors/ComplexSensor.java @@ -0,0 +1,56 @@ +package ch.psi.fda.core.sensors; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; +import ch.psi.fda.core.Action; +import ch.psi.fda.core.Sensor; +/** + * Complex sensor that complements an other sensor with pre and post actions. + * Before reading out the complemented sensor the pre actions are executed. After the + * readout the post actions. + */ +public class ComplexSensor implements Sensor { + + private static Logger logger = Logger.getLogger(ComplexSensor.class.getName()); + + private String id; + private final Sensor sensor; + private final List preActions; + private final List postActions; + + public ComplexSensor(String id, Sensor sensor){ + this.id = id; + this.sensor = sensor; + this.preActions = new ArrayList(); + this.postActions = new ArrayList(); + } + + @Override + public Object read() throws InterruptedException { + logger.finest("Execute pre actions"); + for(Action action: preActions){ + action.execute(); + } + + Object value = sensor.read(); + + logger.finest("Execute post actions"); + for(Action action: postActions){ + action.execute(); + } + + return value; + } + + @Override + public String getId() { + return id; + } + + public List getPreActions() { + return preActions; + } + public List getPostActions() { + return postActions; + } +} \ No newline at end of file diff --git a/src/main/java/ch/psi/fda/core/sensors/TimestampSensor.java b/src/main/java/ch/psi/fda/core/sensors/TimestampSensor.java new file mode 100644 index 0000000..93d036e --- /dev/null +++ b/src/main/java/ch/psi/fda/core/sensors/TimestampSensor.java @@ -0,0 +1,46 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.sensors; + +import ch.psi.fda.core.Sensor; + +/** + * Sensor to read the current time in milliseconds + */ +public class TimestampSensor implements Sensor { + + private final String id; // Global id of the sensor + + public TimestampSensor(String id){ + this.id = id; + } + + @Override + public Object read() { + // Return current time in milliseconds + return new Double(System.currentTimeMillis()); + } + + @Override + public String getId() { + return id; + } + +} diff --git a/src/main/java/ch/psi/fda/model/.gitignore b/src/main/java/ch/psi/fda/model/.gitignore new file mode 100644 index 0000000..c7bcf9d --- /dev/null +++ b/src/main/java/ch/psi/fda/model/.gitignore @@ -0,0 +1 @@ +/v1/ diff --git a/src/main/java/ch/psi/fda/model/ModelManager.java b/src/main/java/ch/psi/fda/model/ModelManager.java new file mode 100644 index 0000000..7ed8237 --- /dev/null +++ b/src/main/java/ch/psi/fda/model/ModelManager.java @@ -0,0 +1,100 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.model; + +import java.io.File; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.UnmarshalException; +import javax.xml.bind.Unmarshaller; +import javax.xml.namespace.QName; +import javax.xml.transform.Source; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.SchemaFactory; + +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; + +import ch.psi.fda.model.v1.Configuration; + +/** + * Manage the serialization and deserialization of the FDA data model + */ +public class ModelManager { + + /** + * De-serialize an instance of the FDA data model + * + * @param file File to deserialize + * @throws JAXBException Something went wrong while unmarshalling + * @throws SAXException Cannot read model schema file + */ + public static Configuration unmarshall(File file) throws JAXBException, SAXException { + + JAXBContext context = JAXBContext.newInstance(Configuration.class); + Unmarshaller u = context.createUnmarshaller(); + + // Validation + SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); + Source s = new StreamSource(Configuration.class.getResourceAsStream("/model-v1.xsd")); + Schema schema = sf.newSchema(new Source[]{s}); // Use schema reference provided in XML + u.setSchema(schema); + + try{ + Configuration model = (Configuration) u.unmarshal(new StreamSource(file), Configuration.class).getValue(); + return (model); + } + catch(UnmarshalException e){ + // Check + if(e.getLinkedException() instanceof SAXParseException){ + throw new RuntimeException("Configuration file does not comply to required model specification\nCause: "+e.getLinkedException().getMessage(), e); + } + throw e; + } + } + + /** + * Serialize an instance of the FDA data model + * + * @param model Model datastructure + * @param file File to write the model data into + * @throws JAXBException Something went wrong while marshalling model + * @throws SAXException Cannot read model schema files + */ + public static void marshall(Configuration model, File file) throws JAXBException, SAXException{ + QName qname = new QName("http://www.psi.ch/~ebner/models/scan/1.0", "configuration"); + + JAXBContext context = JAXBContext.newInstance(Configuration.class); + Marshaller m = context.createMarshaller(); + m.setProperty("jaxb.formatted.output", true); + + // Validation + SchemaFactory sf = SchemaFactory.newInstance(javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI); + Source s = new StreamSource(Configuration.class.getResourceAsStream("/model-v1.xsd")); + Schema schema = sf.newSchema(new Source[]{s}); // Use schema reference provided in XML + m.setSchema(schema); + + m.marshal( new JAXBElement(qname, Configuration.class, model ), file); + } +} diff --git a/src/main/resources/META-INF/services/ch.psi.fda.DescriptorProvider b/src/main/resources/META-INF/services/ch.psi.fda.DescriptorProvider index 5ebabef..fcfee86 100644 --- a/src/main/resources/META-INF/services/ch.psi.fda.DescriptorProvider +++ b/src/main/resources/META-INF/services/ch.psi.fda.DescriptorProvider @@ -1,2 +1,3 @@ ch.psi.fda.cdump.CdumpEDescriptorProvider ch.psi.fda.fdaq.FdaqEDescriptorProvider +ch.psi.fda.aq.XScanDescriptorProvider diff --git a/src/main/resources/META-INF/services/ch.psi.fda.EContainerFactory b/src/main/resources/META-INF/services/ch.psi.fda.EContainerFactory index 8a1f352..f457af9 100644 --- a/src/main/resources/META-INF/services/ch.psi.fda.EContainerFactory +++ b/src/main/resources/META-INF/services/ch.psi.fda.EContainerFactory @@ -1,2 +1,3 @@ ch.psi.fda.cdump.CdumpEContainerFactory ch.psi.fda.fdaq.FdaqEContainerFactory +ch.psi.fda.aq.XScanFactory diff --git a/src/main/resources/model-v1.xsd b/src/main/resources/model-v1.xsd new file mode 100644 index 0000000..09107da --- /dev/null +++ b/src/main/resources/model-v1.xsd @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/ch/psi/fda/aq/AcquisitionConfigurationTest.java b/src/test/java/ch/psi/fda/aq/AcquisitionConfigurationTest.java new file mode 100644 index 0000000..dfb3cf4 --- /dev/null +++ b/src/test/java/ch/psi/fda/aq/AcquisitionConfigurationTest.java @@ -0,0 +1,59 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import java.io.File; +import java.net.URI; +import java.net.URL; +import java.util.logging.Logger; + +import junit.framework.Assert; + +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.aq.AcquisitionConfiguration; + +public class AcquisitionConfigurationTest { + + private static Logger logger = Logger.getLogger(AcquisitionConfigurationTest.class.getName()); + + @Before + public void setUp() throws Exception { + URL url = this.getClass().getClassLoader().getResource("home/config/fda.properties"); + System.setProperty("ch.psi.fda.config.file", new File(new URI(url.toString())).getAbsolutePath()); + } + + + /** + * Test method for {@link ch.psi.fda.aq.AcquisitionConfiguration#getDataFilePrefix()}. + * Test whether the supported date macros in the file prefix are resolved + */ + @Test + public void testGetConfiguration() { + AcquisitionConfiguration configuration = new AcquisitionConfiguration(); + String s = configuration.getDataFilePrefix(); + if(s==null){ + Assert.fail("No configuration returned for data file prefix"); + } + logger.info("Data file prefix: "+s); + } + +} diff --git a/src/test/java/ch/psi/fda/aq/CollectorTest.java b/src/test/java/ch/psi/fda/aq/CollectorTest.java new file mode 100644 index 0000000..47fca08 --- /dev/null +++ b/src/test/java/ch/psi/fda/aq/CollectorTest.java @@ -0,0 +1,232 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.aq.Collector; +import ch.psi.fda.messages.ControlMessage; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Message; +import ch.psi.fda.messages.Metadata; + +public class CollectorTest { + + private static Logger logger = Logger.getLogger(CollectorTest.class.getName()); + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link ch.psi.fda.aq.Collector#run()}. + * + * @throws InterruptedException + */ + @Test + public void testRun() throws InterruptedException { + + EventBus bus = new EventBus(); + + // Metadata + List m0; + List m1; + List m2; + m0 = new ArrayList<>(); + m0.add(new Metadata("id0-0", 0)); + m1 = new ArrayList<>(); + m1.add(new Metadata("id1-0", 1)); + m2 = new ArrayList<>(); + m2.add(new Metadata("id2-0", 2)); + + EventBus b0 = new EventBus(); + EventBus b1 = new EventBus(); + EventBus b2 = new EventBus(); + + Collector collector = new Collector(bus); + collector.addEventBus(b0); + collector.addEventBus(b1); + collector.addEventBus(b2); + + // Check component metadata of output queue + + // check wether messages arrive + bus.register(new Object() { + + private boolean first = true; + + @Subscribe + public void onMessage(Message m) { + if (m instanceof DataMessage) { + DataMessage x = (DataMessage) m; + + if (first) { + first = false; + // Check metadata + int c = 2; + for (Metadata cm : x.getMetadata()) { + logger.info(String.format("id: %s dimension: %s", cm.getId(),cm.getDimension())); + if (cm.getDimension() != c) { + fail("Dimension number does not match required dimension number"); + } + if (!cm.getId().equals("id" + c + "-0")) { + fail("Id does not match required id"); + } + c--; + } + } + + logger.info(x.toString()); + } else if (m instanceof ControlMessage) { + logger.info("---- " + m.toString() + " ----"); + } + } + }); + + // dimension 2 + DataMessage m = new DataMessage(m2); + m.getData().add(1.0); + b2.post(m); + + // dimension 1 + m = new DataMessage(m1); + m.getData().add(0.1); + b1.post(m); + + // dimension 0 + m = new DataMessage(m0); + m.getData().add(0.01); + b0.post(m); + m = new DataMessage(m0); + m.getData().add(0.02); + b0.post(m); + b0.post(new EndOfStreamMessage()); + + // dimension 1 + m = new DataMessage(m1); + m.getData().add(0.2); + b1.post(m); + + // dimension 0 + m = new DataMessage(m0); + m.getData().add(0.01); + b0.post(m); + m = new DataMessage(m0); + m.getData().add(0.02); + b0.post(m); + b0.post(new EndOfStreamMessage()); + + // dimension 1 + b1.post(new EndOfStreamMessage()); + + // dimension 2 + m = new DataMessage(m2); + m.getData().add(2.0); + b2.post(m); + + // dimension 1 + m = new DataMessage(m1); + m.getData().add(0.1); + b1.post(m); + + // dimension 0 + m = new DataMessage(m0); + m.getData().add(0.01); + b0.post(m); + m = new DataMessage(m0); + m.getData().add(0.02); + b0.post(m); + b0.post(new EndOfStreamMessage()); + + // dimension 1 + m = new DataMessage(m1); + m.getData().add(0.2); + b1.post(m); + + // dimension 0 + m = new DataMessage(m0); + m.getData().add(0.01); + b0.post(m); + m = new DataMessage(m0); + m.getData().add(0.02); + b0.post(m); + b0.post(new EndOfStreamMessage()); + + // dimension 1 + b1.post(new EndOfStreamMessage()); + + // dimension 2 + m = new DataMessage(m2); + m.getData().add(3.0); + b2.post(m); + + // dimension 1 + m = new DataMessage(m1); + m.getData().add(0.1); + b1.post(m); + + // dimension 0 + m = new DataMessage(m0); + m.getData().add(0.01); + b0.post(m); + m = new DataMessage(m0); + m.getData().add(0.02); + b0.post(m); + b0.post(new EndOfStreamMessage()); + + // dimension 1 + m = new DataMessage(m1); + m.getData().add(0.2); + b1.post(m); + + // dimension 0 + m = new DataMessage(m0); + m.getData().add(0.01); + b0.post(m); + m = new DataMessage(m0); + m.getData().add(0.02); + b0.post(m); + b0.post(new EndOfStreamMessage()); + + // dimension 1 + b1.post(new EndOfStreamMessage()); + + // dimension 2 + b2.post(new EndOfStreamMessage()); + + } + +} diff --git a/src/test/java/ch/psi/fda/aq/ManipulatorTest.java b/src/test/java/ch/psi/fda/aq/ManipulatorTest.java new file mode 100644 index 0000000..fe09436 --- /dev/null +++ b/src/test/java/ch/psi/fda/aq/ManipulatorTest.java @@ -0,0 +1,490 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.aq; + +import static org.junit.Assert.*; +import gov.aps.jca.CAException; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.aq.Manipulator; +import ch.psi.fda.core.Manipulation; +import ch.psi.fda.core.manipulator.JythonManipulation; +import ch.psi.fda.core.scripting.JythonParameterMapping; +import ch.psi.fda.core.scripting.JythonParameterMappingChannel; +import ch.psi.fda.core.scripting.JythonParameterMappingID; +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Message; +import ch.psi.fda.messages.Metadata; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class ManipulatorTest { + + private static Logger logger = Logger.getLogger(ManipulatorTest.class.getName()); + + private EventBus bus; + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + bus = new EventBus(); + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + @Test + public void testConstructorNoMappingNoParam() { + String id="cid"; + String script = "import math\ndef process():\n return 0.0"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + new Manipulator(bus, manipulations); + // Expect IllegalArgument Exception as there is no mapping for the parameter c + } + + @Test + public void testConstructorMappingNoParam() { + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + dmm.add(new Metadata("myid2")); + + String id="cid"; + String script = "import math\ndef process():\n return 0.0"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + new Manipulator(bus, manipulations).onMessage(new DataMessage(dmm)); + } + + /** + * Test method for {@link ch.psi.fda.aq.Manipulator#Manipulator()}. + */ + @Test(expected=IllegalArgumentException.class) + public void testConstructorNoMapping() { + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + dmm.add(new Metadata("myid2")); + + String id="cid"; + String script = "import math\ndef process(o ,c):\n return math.cos(c) + math.sin(o)"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + new Manipulator(bus, manipulations).onMessage(new DataMessage(dmm));; + + // Expect IllegalArgument Exception as there is no mapping for the parameter c + } + + /** + * Test method for {@link ch.psi.fda.aq.Manipulator#run()}. + * @throws InterruptedException + */ + @Test + public void testRun() throws InterruptedException { + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + + String id="cid"; + String script = "import math\ndef process(o):\n return math.cos(10.0) + math.sin(o)"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + Manipulator manipulator = new Manipulator(bus, manipulations); + + + bus.register(new Object(){ + boolean first = true; + @Subscribe + public void onMessage(Message message){ + logger.info(message.toString()); + + if(message instanceof DataMessage){ + DataMessage dm = (DataMessage) message; + + if(first){ + first=false; + // Check whether output queue message structur complies to expected one + + // Test whether only the expected components are within the outgoing queue + if(dm.getMetadata().size()!=2){ + fail("There are more than the expected components in the outgoing message"); + } + + // Test whether the id of the first component matches the expected id + if(!dm.getMetadata().get(0).getId().equals("myid")){ + fail("Id of the first component does not match the expected id 'myid'"); + } + + // Test whether the id of the second component (which was computed) matches the expected id + if(!dm.getMetadata().get(1).getId().equals("cid")){ + fail("Id of the second component does not match the expected id 'cid'"); + } + } + + + dm.getData().get(0); + double res = ((Double)dm.getData().get(1)) - (Math.cos(10.0)+Math.sin(((Double)dm.getData().get(0)))); + if( Math.abs(res) > 0.000000001){ + fail("Calculation failed"); + } + } + } + }); + + EventBus b = new EventBus(); + b.register(manipulator); + + DataMessage m = new DataMessage(dmm); + m.getData().add(10d); + b.post(m); + b.post(new EndOfStreamMessage()); + + logger.info(""+(Math.cos(10.0)+Math.sin(10))); + } + + /** + * Test run that returns an integer + * @throws InterruptedException + */ + @Test + public void testRunIntegerReturn() throws InterruptedException { + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + + + String id="cid"; + String script = "import math\ndef process(o):\n return 1"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + Manipulator manipulator = new Manipulator(bus, manipulations); + + + + + + bus.register(new Object(){ + + boolean first = true; + + @Subscribe + public void onMessage(Message message){ + + logger.info(message.toString()); + + if(message instanceof DataMessage){ + DataMessage dm = (DataMessage) message; + + if(first){ + first=false; + // Check whether output queue message structur complies to expected one + + // Test whether only the expected components are within the outgoing queue + if(dm.getMetadata().size()!=2){ + fail("There are more than the expected components in the outgoing message"); + } + + // Test whether the id of the first component matches the expected id + if(!dm.getMetadata().get(0).getId().equals("myid")){ + fail("Id of the first component does not match the expected id 'myid'"); + } + + // Test whether the id of the second component (which was computed) matches the expected id + if(!dm.getMetadata().get(1).getId().equals("cid")){ + fail("Id of the second component does not match the expected id 'cid'"); + } + } + + dm.getData().get(0); + } + } + }); + + EventBus b = new EventBus(); + b.register(manipulator); + + DataMessage m = new DataMessage(dmm); + m.getData().add(10d); + b.post(m); + b.post(new EndOfStreamMessage()); + } + + /** + * Test method for {@link ch.psi.fda.aq.Manipulator#run()}. + * @throws InterruptedException + */ + @Ignore + @Test + public void testRunLongTimeTest() throws InterruptedException { + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + + String id="cid"; + String script = "import math\ndef process(o):\n return math.cos(10.0) + math.sin(o)"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + Manipulator manipulator = new Manipulator(bus, manipulations); + + bus.register(new Object(){ + int count=0; + @Subscribe + public void onMessage(Message message){ + if(!(message instanceof EndOfStreamMessage)){ + logger.info(count+" - "+message.toString()); + count++; + } + + } + }); + + + EventBus b = new EventBus(); + b.register(manipulator); + for(Double i=0d;i<1000000;i++){ + DataMessage m = new DataMessage(dmm); + m.getData().add(i); + b.post(m); + } + b.post(new EndOfStreamMessage()); + } + + /** + * Test method for {@link ch.psi.fda.aq.Manipulator#run()}. + * @throws InterruptedException + */ + @Test + public void testRunMultipleParameter() throws InterruptedException { + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + dmm.add(new Metadata("myid2")); + + + String id="cid"; + String script = "import math\ndef process(o ,c):\n return math.cos(c) + math.sin(o)"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + mapping.add(new JythonParameterMappingID("c", "myid2")); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + Manipulator manipulator = new Manipulator(bus, manipulations); + + bus.register(new Object(){ + + boolean first = true; + + @Subscribe + public void onMessage(Message message){ + logger.info(message.toString()); + + if(message instanceof DataMessage){ + DataMessage dm = (DataMessage) message; + + if(first){ + first=false; + + // Check whether output queue message structur complies to expected one + + // Test whether only the expected components are within the outgoing queue + if(dm.getMetadata().size()!=3){ + fail("There are more than the expected components in the outgoing message"); + } + + // Test whether the id of the first component matches the expected id + if(!dm.getMetadata().get(0).getId().equals("myid")){ + fail("Id of the first component does not match the expected id 'myid'"); + } + + if(!dm.getMetadata().get(1).getId().equals("myid2")){ + fail("Id of the first component does not match the expected id 'myid'"); + } + + // Test whether the id of the second component (which was computed) matches the expected id + if(!dm.getMetadata().get(2).getId().equals("cid")){ + fail("Id of the second component does not match the expected id 'cid'"); + } + } + + dm.getData().get(0); + double res = ((Double)dm.getData().get(2)) - (Math.cos(((Double)dm.getData().get(1)))+Math.sin(((Double)dm.getData().get(0)))); + if( Math.abs(res) > 0.000000001){ + fail("Calculation failed"); + } + } + } + }); + + + EventBus b = new EventBus(); + b.register(manipulator); + + DataMessage m = new DataMessage(dmm); + m.getData().add(10d); + m.getData().add(0.2d); + b.post(m); + b.post(new EndOfStreamMessage()); + + logger.info(""+(Math.cos(0.2)+Math.sin(10))); + + } + + + /** + * Test method for {@link ch.psi.fda.aq.Manipulator#run()}. + * @throws InterruptedException + * @throws CAException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testRunMultipleParameterAndChannel() throws InterruptedException, CAException, ChannelException, TimeoutException, ExecutionException { + + Double setValue = 12.22; + + Channel channel = cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT)); + + + List dmm = new ArrayList<>(); + dmm.add(new Metadata("myid")); + dmm.add(new Metadata("myid2")); + + String id="cid"; + String script = "import math\ndef process(o ,c,d):\n d.setValue("+setValue+")\n return math.cos(c) + math.sin(o)"; + List mapping = new ArrayList(); + mapping.add(new JythonParameterMappingID("o", "myid")); + mapping.add(new JythonParameterMappingID("c", "myid2")); + mapping.add(new JythonParameterMappingChannel("d", channel)); + JythonManipulation manipulation = new JythonManipulation(id, script, mapping); + + List manipulations = new ArrayList(); + manipulations.add(manipulation); + Manipulator manipulator = new Manipulator(bus, manipulations); + + // Change something different on the channel than the value that will be set in the manipulator script + channel.setValue(setValue+1); + + bus.register(new Object(){ + + boolean first = true; + + @Subscribe + public void onMessage(Message message){ + logger.info(message.toString()); + + if(message instanceof DataMessage){ + DataMessage dm = (DataMessage) message; + + if(first){ + first=false; + + // Check whether output queue message structur complies to expected one + // Test whether only the expected components are within the outgoing queue + if(dm.getMetadata().size()!=3){ + fail("There are more than the expected components in the outgoing message"); + } + + // Test whether the id of the first component matches the expected id + if(!dm.getMetadata().get(0).getId().equals("myid")){ + fail("Id of the first component does not match the expected id 'myid'"); + } + + if(!dm.getMetadata().get(1).getId().equals("myid2")){ + fail("Id of the first component does not match the expected id 'myid'"); + } + + // Test whether the id of the second component (which was computed) matches the expected id + if(!dm.getMetadata().get(2).getId().equals("cid")){ + fail("Id of the second component does not match the expected id 'cid'"); + } + + } + + dm.getData().get(0); + double res = ((Double)dm.getData().get(2)) - (Math.cos(((Double)dm.getData().get(1)))+Math.sin(((Double)dm.getData().get(0)))); + if( Math.abs(res) > 0.000000001){ + fail("Calculation failed"); + } + } + } + }); + + + EventBus b = new EventBus(); + b.register(manipulator); + DataMessage m = new DataMessage(dmm); + m.getData().add(10d); + m.getData().add(0.2d); + b.post(m); + b.post(new EndOfStreamMessage()); + + logger.info(""+(Math.cos(0.2)+Math.sin(10))); + + // Check whether the channel was set correctly by the manipulator script + if(Math.abs(channel.getValue()-setValue)>0.00000001){ + fail("Channel was not set correctly in the manipulator script"); + } + + } + +} diff --git a/src/test/java/ch/psi/fda/aq/NotificationAgentTest.java b/src/test/java/ch/psi/fda/aq/NotificationAgentTest.java new file mode 100644 index 0000000..75298a3 --- /dev/null +++ b/src/test/java/ch/psi/fda/aq/NotificationAgentTest.java @@ -0,0 +1,39 @@ +/** + * + * Copyright 2013 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ +package ch.psi.fda.aq; + +import org.junit.Ignore; +import org.junit.Test; + +import ch.psi.fda.aq.NotificationAgent; +import ch.psi.fda.model.v1.Recipient; + +public class NotificationAgentTest { + + @Ignore + @Test + public void testSendNotification() { + NotificationAgent a = new NotificationAgent("mail.psi.ch", "supertoll@psi.ch"); + Recipient r = new Recipient(); + r.setValue("simon.ebner@psi.ch"); + a.getRecipients().add(r); + a.sendNotification("super","hello worl this is a test", true, true); + } + +} diff --git a/src/test/java/ch/psi/fda/aq/XScanDescriptorProviderTest.java b/src/test/java/ch/psi/fda/aq/XScanDescriptorProviderTest.java new file mode 100644 index 0000000..51ce22c --- /dev/null +++ b/src/test/java/ch/psi/fda/aq/XScanDescriptorProviderTest.java @@ -0,0 +1,31 @@ +package ch.psi.fda.aq; + +import java.util.ServiceLoader; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.EContainerFactory; + +public class XScanDescriptorProviderTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void test() { + ServiceLoader factories = ServiceLoader.load(EContainerFactory.class); + for (EContainerFactory factory : factories) { + System.out.println(factory.getClass().getName()); + } + System.out.println("done"); + } + + +} diff --git a/src/test/java/ch/psi/fda/core/TestChannels.java b/src/test/java/ch/psi/fda/core/TestChannels.java new file mode 100644 index 0000000..18973f9 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/TestChannels.java @@ -0,0 +1,34 @@ +package ch.psi.fda.core; +/** + * + * Copyright 2011 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + + +public class TestChannels { + // TODO Need to create softio with test channels !!!! + + public static final String BINARY_OUT = "MTEST-PC-FDA:BO"; + public static final String BINARY_OUT_TWO = "MTEST-PC-FDA:BO2"; + public static final String ANALOG_OUT = "MTEST-PC-FDA:AO"; + public static final String STRING_OUT = "MTEST-PC-FDA:SOUT"; + public static final String DOUBLE_WAVEFORM = "MTEST-PC-FDA:DWAVE"; + public static final String STRING_OUT_NOT_EXIST = "MTEST-PC-FDA:SOUT-NOT-EXIST"; + + public static final String MOTOR_ONE = "MTEST-HW3:MOT1"; + +} diff --git a/src/test/java/ch/psi/fda/core/actions/ChannelAccessConditionTest.java b/src/test/java/ch/psi/fda/core/actions/ChannelAccessConditionTest.java new file mode 100644 index 0000000..90b5bf8 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actions/ChannelAccessConditionTest.java @@ -0,0 +1,232 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import static org.junit.Assert.fail; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import gov.aps.jca.CAException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actions.ChannelAccessCondition; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; +import ch.psi.jcae.util.ComparatorREGEX; + +public class ChannelAccessConditionTest { + + private static Logger logger = Logger.getLogger(ChannelAccessConditionTest.class.getName()); + + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringCondition#ChannelAccessStringCondition(java.lang.String, java.lang.String, long)}. + * @throws InterruptedException + * @throws CAException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testChannelAccessStringCondition() throws InterruptedException, CAException, ChannelException, TimeoutException, ExecutionException { + final Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)); + channel.setValue("SomeValue"); + ChannelAccessCondition c = new ChannelAccessCondition(channel, "SomeValue", 1000l); + c.execute(); + } + + @Test + public void testChannelAccessStringConditionRegex() throws InterruptedException, CAException, ExecutionException, ChannelException, TimeoutException { + final Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)); + channel.setValue("SomeValue"); + ChannelAccessCondition c = new ChannelAccessCondition<>(channel, "Some.*",new ComparatorREGEX(), 1000l); + c.execute(); + } + + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringCondition#execute()}. + * @throws CAException + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testExecute() throws CAException, InterruptedException, ChannelException, TimeoutException, ExecutionException { + String value = "SomeValue"; + + final Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)); + final ChannelAccessCondition action = new ChannelAccessCondition(channel, value, 10000l); + + + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + try { + action.execute(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }); + + // Set the value to something else than the expected value + channel.setValue(value+"hello"); + + // Start new thread that is executing the actions execute() method + logger.fine("Execute action"); + t.start(); + + // Wait some time and set the channel to the expected value + Thread.sleep(1000); + logger.fine("Set channel value to "+value); + channel.setValue(value); + + // Wait thread to join and check whether the thread has terminated + t.join(100); + if(t.isAlive()){ + fail("Action did not return although the value was reached"); + } + } + + @Test(expected=RuntimeException.class) + public void testExecuteFail() throws CAException, InterruptedException, ExecutionException, ChannelException, TimeoutException { + String value = "SomeValue"; + + final Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)); + final ChannelAccessCondition action = new ChannelAccessCondition(channel, value, 9000l); + + + + // Set the value to something else than the expected value + channel.setValue(value+"hello"); + action.execute(); + } + + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringCondition#execute()}. + * @throws CAException + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testExecuteInteger() throws CAException, InterruptedException, ChannelException, TimeoutException, ExecutionException { + Integer value = 12; + + final Channel channel = cservice.createChannel(new ChannelDescriptor<>(Integer.class, TestChannels.STRING_OUT)); + final ChannelAccessCondition action = new ChannelAccessCondition(channel, value, 10000l); + + + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + try { + action.execute(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + }); + + // Set the value to something else than the expected value + channel.setValue(value+1); + + // Start new thread that is executing the actions execute() method + logger.fine("Execute action"); + t.start(); + + // Wait some time and set the channel to the expected value + Thread.sleep(1000); + logger.fine("Set channel value to "+value); + channel.setValue(value); + + // Wait thread to join and check whether the thread has terminated + t.join(100); + if(t.isAlive()){ + fail("Action did not return although the value was reached"); + } + } + + /** + * Test to ensure that the abort function is working correctly + * @throws CAException + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testWaitAbort() throws CAException, InterruptedException, ChannelException, TimeoutException, ExecutionException { + + final Channel channel = cservice.createChannel(new ChannelDescriptor<>(Integer.class, TestChannels.BINARY_OUT)); + final ChannelAccessCondition action = new ChannelAccessCondition(channel, 1, 10000l); + + + Thread t = new Thread(new Runnable() { + + @Override + public void run() { + try { + Thread.sleep(2000); + logger.info("Send abort ..."); + action.abort(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }); + + // Set wait channel to 0 + channel.setValue(0); + + t.start(); + action.execute(); // timeouts after 10 seconds + + } + +} diff --git a/src/test/java/ch/psi/fda/core/actions/ChannelAccessPutTest.java b/src/test/java/ch/psi/fda/core/actions/ChannelAccessPutTest.java new file mode 100644 index 0000000..74b25b3 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actions/ChannelAccessPutTest.java @@ -0,0 +1,163 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actions.ChannelAccessPut; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class ChannelAccessPutTest { + + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringPut#ChannelAccessStringPut(java.lang.String, java.lang.String, boolean)}. + * Test correct initialization + * @throws TimeoutException + * @throws InterruptedException + * @throws ChannelException + */ + @Test + public void testChannelAccessStringPutStringStringBoolean() throws ChannelException, InterruptedException, TimeoutException { + new ChannelAccessPut(cservice.createChannel(new ChannelDescriptor<>(String.class,TestChannels.STRING_OUT)), "SomeValue", true, null); + } + + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringPut#ChannelAccessStringPut(java.lang.String, java.lang.String)}. + * Test correct initialization + * @throws TimeoutException + * @throws InterruptedException + * @throws ChannelException + */ + @Test + public void testChannelAccessStringPutStringString() throws ChannelException, InterruptedException, TimeoutException { + new ChannelAccessPut(cservice.createChannel(new ChannelDescriptor<>(String.class,TestChannels.STRING_OUT)), "SomeValue"); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringPut#ChannelAccessStringPut(java.lang.String, java.lang.String)}. + * Test whether the correct Exception is thrown if a non existing channel is specified at creation time + * @throws TimeoutException + * @throws InterruptedException + * @throws ChannelException + */ + @Test(expected=ChannelException.class) + public void testChannelAccessStringPutStringStringNotExist() throws ChannelException, InterruptedException, TimeoutException { + new ChannelAccessPut(cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT_NOT_EXIST)), "SomeValue"); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringPut#execute()}. + * @throws CAException + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testExecute() throws InterruptedException, ChannelException, TimeoutException, ExecutionException { + + String setValue = "MyTestString"; + Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)); + ChannelAccessPut action = new ChannelAccessPut(channel, setValue); + action.execute(); + + String value = channel.getValue(); + + if(!value.equals(setValue)){ + fail("Test string was not set correctly"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringPut#execute()}. + * @throws CAException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + */ + @Test + public void testExecuteString() throws InterruptedException, ChannelException, TimeoutException, ExecutionException { + + String setValue = "MyTestString"; + + Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)); + // Set channel to something else + channel.setValue(setValue+"test"); + + ChannelAccessPut action = new ChannelAccessPut(channel, setValue); + action.execute(); + + + String value = channel.getValue(); + + if(!value.equals(setValue)){ + fail("Test string was not set correctly"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ChannelAccessStringPut#execute()}. + * @throws CAException + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + */ + @Test + public void testExecuteMulti() throws InterruptedException, ChannelException, TimeoutException { + + List> actions = new ArrayList>(); + for(int i=0;i<20;i++){ + actions.add(new ChannelAccessPut(cservice.createChannel(new ChannelDescriptor<>(String.class, TestChannels.STRING_OUT)), i+"")); + } + + for(ChannelAccessPut action: actions){ + action.execute(); + } + + } + +} diff --git a/src/test/java/ch/psi/fda/core/actions/DelayTest.java b/src/test/java/ch/psi/fda/core/actions/DelayTest.java new file mode 100644 index 0000000..86a9d52 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actions/DelayTest.java @@ -0,0 +1,83 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import static org.junit.Assert.*; + +import java.util.logging.Logger; + +import org.junit.Test; + +import ch.psi.fda.core.actions.Delay; + +public class DelayTest { + + private static Logger logger = Logger.getLogger(DelayTest.class.getName()); + + /** + * Test method for {@link ch.psi.fda.core.actions.Delay#Delay(long)}. + * Test correct initialization + */ + @Test + public void testDelay() { + new Delay(1000); + new Delay(1); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.Delay#Delay(long)}. + * Test initialization with time = 0 + */ + @Test(expected=IllegalArgumentException.class) + public void testDelayZeroWait() { + new Delay(0); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.Delay#Delay(long)}. + * Test initialization with time < 0 + */ + @Test(expected=IllegalArgumentException.class) + public void testDelayNegativeWait() { + new Delay(-10000); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.Delay#execute()}. + * @throws InterruptedException + */ + @Test + public void testExecute() throws InterruptedException { + long wait = 1000; + Delay action = new Delay(wait); + long start = System.currentTimeMillis(); + action.execute(); + long end = System.currentTimeMillis(); + + // Check whether wait was of the correct length + // Accept 10% "jitter" because of the delays of the function calls, etc. + long elapsed = end-start; + logger.fine("Elapsed wait time: "+elapsed+" - Specified wait time: "+wait); + if(elapsed>(wait*1.1)){ + fail("Wait was not of the correct length"); + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/actions/JythonActionTest.java b/src/test/java/ch/psi/fda/core/actions/JythonActionTest.java new file mode 100644 index 0000000..edbdc3f --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actions/JythonActionTest.java @@ -0,0 +1,111 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeoutException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actions.JythonAction; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class JythonActionTest { + + private static String SCRIPT = "import math\ndef process(o):\n print o.getValue()\n o.setValue(-1.0)\n print o.getValue()\n val=math.cos(10.0) + math.sin(o.getValue())\n print val\n o.setValue(val)"; + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + @Test + public void testExecute() throws ChannelException, InterruptedException, TimeoutException { + try{ + Map mapping = new HashMap<>(); + mapping.put("o", cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT))); + + JythonAction action = new JythonAction(SCRIPT, mapping); + + action.execute(); + } + catch(Exception e){ + e.printStackTrace(); + throw e; + } + } + + @Test(expected=IllegalArgumentException.class) + public void testWrongMapping1() throws ChannelException, InterruptedException, TimeoutException { + Map mapping = new HashMap<>(); + mapping.put("o", cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT))); + mapping.put("b", cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT))); + + String script = "import math\ndef process(o):\n print o.getValue()\n o.setValue(-1.0)\n print o.getValue()\n val=math.cos(10.0) + math.sin(o.getValue())\n print val\n o.setValue(val)"; + JythonAction action = new JythonAction(script, mapping); + + action.execute(); + } + + @Test(expected=IllegalArgumentException.class) + public void testWrongMapping2() { + Map mapping = new HashMap<>(); + mapping.put("ooo", new String()); + String script = "import math\ndef process(o):\n print o.getValue()\n o.setValue(-1.0)\n print o.getValue()\n val=math.cos(10.0) + math.sin(o.getValue())\n print val\n o.setValue(val)"; + JythonAction action = new JythonAction(script, mapping); + + action.execute(); + } + + @Test + public void testExecuteGlobalObjects() throws ChannelException, InterruptedException, TimeoutException { + try{ + Map mapping = new HashMap<>(); + mapping.put("o", cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT))); + + Map gobjects = new HashMap<>(); + gobjects.put("TESTVAR", "salli!"); + + + String script = SCRIPT = "def process(o):\n print TESTVAR"; + JythonAction action = new JythonAction(script, mapping, gobjects); + + action.execute(); + } + catch(Exception e){ + e.printStackTrace(); + throw e; + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/actions/ShellActionTest.java b/src/test/java/ch/psi/fda/core/actions/ShellActionTest.java new file mode 100644 index 0000000..ecfb651 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actions/ShellActionTest.java @@ -0,0 +1,132 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actions; + +import java.io.File; +import java.net.URI; +import java.net.URL; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.actions.ShellAction; + +public class ShellActionTest { + + /** + * Test Shell Script that returns with an exit code of 0 + */ + private String[] testscripts; + /** + * Test Shell Script that returns with an exit code of 1 + */ + private String testscriptError; + /** + * Test Shell Script that does not exist + */ + private String testscriptNotExist; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + URL url = this.getClass().getClassLoader().getResource("testscripts"); + String file = new File(new URI(url.toString())).getAbsolutePath()+"/../../../src/test/resources/testscripts"; + // need to perform this hack as the copied scripts in the target directory do not have the execute permissions + + // test scripts also need to be able to accept options !!!! + testscripts = new String[]{file+"/testscript1.sh", file+"/testscript1.sh -option opt a b "}; + testscriptError = file+"/testscript2-error.sh"; + testscriptNotExist = file+"/testscriptNotExist.sh"; + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ShellAction#ScriptAction(java.lang.String)}. + * Test the creation of a ScriptAction object with an existing script. + */ + @Test + public void testScriptAction() { + for(String testscript: testscripts){ + new ShellAction(testscript); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ShellAction#ScriptAction(java.lang.String)}. + * Test the creation of a ScriptAction object with a non existing script. IllegalArgumentException expected ... + */ + @Test(expected=IllegalArgumentException.class) + public void testScriptActionScriptNotExit() { + new ShellAction(testscriptNotExist); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ShellAction#execute()}. + * @throws InterruptedException + */ + @Test + public void testExecute() throws InterruptedException { + for(String testscript: testscripts){ + File f = new File(testscript); + f.setExecutable(true); // Make file executable + ShellAction action = new ShellAction(testscript); + action.execute(); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ShellAction#execute()}. + * @throws InterruptedException + */ + @Test(expected=RuntimeException.class) + public void testExecuteError() throws InterruptedException { + ShellAction action = new ShellAction(testscriptError); + action.execute(); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ShellAction#execute()}. + * @throws InterruptedException + */ + @Test + public void testExecuteErrorDisabledCheck() throws InterruptedException { + ShellAction action = new ShellAction(testscriptError); + action.setCheckExitValue(false); + action.execute(); + } + + /** + * Test method for {@link ch.psi.fda.core.actions.ShellAction#abort()}. + */ + @Test + public void testAbort() { + // Abort function in ScriptAction is not implemented - therefore there is nothing to test. + } + +} diff --git a/src/test/java/ch/psi/fda/core/actors/ChannelAccessFunctionActuatorTest.java b/src/test/java/ch/psi/fda/core/actors/ChannelAccessFunctionActuatorTest.java new file mode 100644 index 0000000..664a071 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actors/ChannelAccessFunctionActuatorTest.java @@ -0,0 +1,778 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + + +import static org.junit.Assert.fail; + +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actors.ChannelAccessFunctionActuator; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +/** + * Test class specific for the FunctionActuatorChannelAccess implementation. + */ +public class ChannelAccessFunctionActuatorTest { + + private static Logger logger = Logger.getLogger(ChannelAccessFunctionActuatorTest.class.getName()); + + private static final String channelName = TestChannels.ANALOG_OUT; + private static final String channelNameDone = TestChannels.BINARY_OUT; + private static final int doneValue = 1; + private static final double doneDelay = 0; + private static final Long timeout = 1800000l; // 30 minutes + + private ChannelService cservice; + private Channel channel; + private Channel doneChannel; + + private Function function; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + channel = cservice.createChannel(new ChannelDescriptor<>(Double.class, channelName)); + doneChannel = cservice.createChannel(new ChannelDescriptor<>(Integer.class, channelNameDone)); + + function = new Function() { + + @Override + public double calculate(double parameter) { + return parameter+2; // Translation by 2 + } + }; + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a negative step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessFunctionActuatorNegativeStepSize() throws SocketException, Exception { + // Need to throw exception because of negative step size + new ChannelAccessFunctionActuator(channel, function,0, 1, -0.1, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + */ + @Test + public void testChannelAccessFunctionActuatorTimeout() throws SocketException, Exception { + + // Negative timeout + boolean flag = false; + try{ + // Need to return IllegalArgumentException due to negative Timeout + new ChannelAccessFunctionActuator(channel, function, 0, 1, 0.1, -1l); + } + catch(IllegalArgumentException e){ + flag=true; + } + if(!flag){ + fail("Negative timeout is not handeled correctly"); + } + + // 0 timeout + flag=false; + try{ + // Need to return IllegalArgumentException + new ChannelAccessFunctionActuator(channel, function, 0, 1, 0.1, -0l); + } + catch(IllegalArgumentException e){ + flag=true; + } + if(!flag){ + fail("0 timeout is not handled correctly"); + } + + // Accept null timeout + new ChannelAccessFunctionActuator(channel, function, 0, 1, 0.1, null); + + // Accept positive timeout + new ChannelAccessFunctionActuator(channel, function, 0, 1, 0.1, 1l); + + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a zero step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessFunctionActuatorZeroStepSize() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessFunctionActuator(channel, function, 0, 1, 0, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + * Check correct initialization + */ + public void testChannelAccessFunctionActuator() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessFunctionActuator(channel, function, 0, 10, 1, timeout); + } + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#set()}. + * @throws InterruptedException + */ + @Test + public void testSet() throws InterruptedException { + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, 0, 0.09999, 0.1, timeout); + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#hasNext()}. + * Check whether the actuator throws an Exception if there is no next point but set() is called + * @throws InterruptedException + */ + @Test(expected=IllegalStateException.class) + public void testSetNoNext() throws InterruptedException { + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, 0, 0.09999, 0.1, timeout); + actuator.set(); + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#hasNext()}. + * @throws InterruptedException + */ + @Test + public void testHasNextOneStep() throws InterruptedException { + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, 0, 0.09999, 0.1, timeout); + // Execute first set (because there is always a first move) + actuator.set(); + + // Check whether actuator returns that there is no next point + boolean next = actuator.hasNext(); + if(next){ + fail("There must be no next step"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#hasNext()}. + * @throws InterruptedException + */ + @Test + public void testHasNext() throws InterruptedException { + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, 0, 10, 0.1, timeout); + + int count = 0; + int steps = (int) ((10-0)/0.1)+1; + while(actuator.hasNext()){ + actuator.set(); + count++; + } + + logger.fine("Actual steps: "+count+" - Needed steps: "+steps); + if(count != steps){ + fail("Actuator set more steps than specified"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#set()}. + * Test actuator move startend ... + * @throws InterruptedException + */ + @Test + public void testSetLoop() throws InterruptedException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, svalue[0], svalue[1], svalue[2], timeout); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#set()}. + * Test actuator move startend ... + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testReverse() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, svalue[0], svalue[1], svalue[2], timeout); + actuator.init(); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[0] 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + else{ + double sval = function.calculate(svalue[0]-(count*svalue[2])); + if(Math.abs(val-sval) > 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + actuator.reverse(); + actuator.init(); + count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[1] 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + else{ + double sval = function.calculate(svalue[1]-(count*svalue[2])); + if(Math.abs(val-sval) > 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + + count++; + } + + cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessFunctionActuator#set()}. + * Test actuator move startend ... + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testReverseReset() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator(channel, function, svalue[0], svalue[1], svalue[2], timeout); + actuator.init(); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[0] 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + else{ + double sval = function.calculate(svalue[0]-(count*svalue[2])); + if(Math.abs(val-sval) > 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + actuator.reverse(); + actuator.reset(); + actuator.init(); + count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[0] 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + else{ + double sval = function.calculate(svalue[0]-(count*svalue[2])); + if(Math.abs(val-sval) > 0.001){ // 0.001 is precision + fail("Set value ["+sval+"] does not match actual value ["+val+"]"); + } + } + count++; + } + + cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + } + } + + + // TODO Check if actuator makes the required number of steps + // TODO Check if actuator makes the steps in the right direction + // TODO Check if actuator really blocks until motor/actuator is moved. + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a negative step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessGPFunctionActuatorNegativeStepSize() throws SocketException, Exception { + // Need to throw exception because of negative step size + new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, 0, 1, -0.1, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a zero step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessGPFunctionActuatorZeroStepSize() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, 0, 1, 0, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#FunctionActuatorChannelAccess(String, double, double, double)}. + * Check correct initialization + */ + public void testChannelAccessGPFunctionActuator() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, 0, 10, 1, timeout); + } + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneSet() throws InterruptedException, ExecutionException, ChannelException { + + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, 0, 0.09999, 0.1, timeout); + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneDelay() throws InterruptedException, ExecutionException, ChannelException { + + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, 1.5, function, 0, 1, 0.1, timeout); + + // Simulate done channel + doneChannel.setValue(1); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(1000); + doneChannel.setValue(0); + Thread.sleep(4000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + long start = System.currentTimeMillis(); + actuator.set(); + long end = System.currentTimeMillis(); + + logger.fine("Elapsed time: "+(end-start)); + if((end-start)<4000){ // Check whether all the moves took less than 6 seconds (thats the delay the done is set to 1) + fail("Done delay does not work"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#hasNext()}. + * Check whether the actuator throws an Exception if there is no next point but set() is called + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test(expected=IllegalStateException.class) + public void testDoneSetNoNext() throws InterruptedException, ExecutionException, ChannelException { + + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function,0, 0.09999, 0.1, timeout); + + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#hasNext()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneHasNextOneStep() throws InterruptedException, ExecutionException, ChannelException { + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, 0, 0.09999, 0.1, timeout); + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + // Execute first set (because there is always a first move) + actuator.set(); + + // Check whether actuator returns that there is no next point + boolean next = actuator.hasNext(); + if(next){ + fail("There must be no next step"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#hasNext()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneHasNext() throws InterruptedException, ExecutionException, ChannelException { + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function,0, 10, 0.1, timeout); + + int count = 0; + int steps = (int) ((10-0)/0.1)+1; + while(actuator.hasNext()){ + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(10); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + count++; + } + + logger.fine("Actual steps: "+count+" - Needed steps: "+steps); + if(count != steps){ + fail("Actuator set more steps than specified"); + } + } + + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#set()}. + * Test actuator move startend ... + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneSetLoop() throws InterruptedException, ExecutionException, ChannelException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, svalue[0], svalue[1], svalue[2], timeout); + + int count =0; + while(actuator.hasNext()){ + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(10); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPFunctionActuator#set()}. + * Test whether the actuator returns if the actuator is already on the position it should be before the move ... + * (see issue XASEC-278) + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testMoveToActualPosition() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + + double start = 0; + double end = 2; + double stepSize = 1; + + channel.setValue(start); + + Thread.sleep(1000); + + logger.info("Current channel value: "+channel.getValue()); + + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, doneChannel, doneValue, doneDelay, function, start, end, stepSize, timeout); + while(actuator.hasNext()){ + + // Simulate done channel + doneChannel.setValue(1); +// new Thread(new Runnable() { +// +// @Override +// public void run() { +// +// try { +// Thread.sleep(10); +//// doneChannel.setValue(1); +// } catch (Exception e) { +// } +// +// } +// }).start(); + + actuator.set(); + } + + } + + + @Test + public void testSetLoop2() throws InterruptedException { + + Function function = new Function() { + + @Override + public double calculate(double parameter) { + return parameter+2; + } + }; + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessFunctionActuator actuator = new ChannelAccessFunctionActuator<>(channel, function, svalue[0], svalue[1], svalue[2], timeout); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/actors/ChannelAccessLinearActuatorTest.java b/src/test/java/ch/psi/fda/core/actors/ChannelAccessLinearActuatorTest.java new file mode 100644 index 0000000..71d05a5 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actors/ChannelAccessLinearActuatorTest.java @@ -0,0 +1,729 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + + +import static org.junit.Assert.fail; + +import java.net.SocketException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actors.ChannelAccessLinearActuator; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +/** + * Test class specific for the LinearActuatorChannelAccess implementation. + */ +public class ChannelAccessLinearActuatorTest { + + // Get Logger + private static Logger logger = Logger.getLogger(ChannelAccessLinearActuatorTest.class.getName()); + + private static final String channelName = TestChannels.ANALOG_OUT; + private static final String channelNameDone = TestChannels.BINARY_OUT; + private static final int doneValue = 1; + private static final double doneDelay = 0; + private static final Long timeout = 1800000l; // 30 minutes + + private ChannelService cservice; + private Channel channel; + private Channel doneChannel; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + channel = cservice.createChannel(new ChannelDescriptor<>(Double.class, channelName)); + doneChannel = cservice.createChannel(new ChannelDescriptor<>(Integer.class, channelNameDone)); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a negative step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessLinearActuatorNegativeStepSize() throws SocketException, Exception { + // Need to throw exception because of negative step size + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 1, -0.1, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + */ + @Test + public void testChannelAccessLinearActuatorTimeout() throws SocketException, Exception { + + // Negative timeout + boolean flag = false; + try{ + // Need to return IllegalArgumentException due to negative Timeout + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 1, 0.1, -1l); + } + catch(IllegalArgumentException e){ + flag=true; + } + if(!flag){ + fail("Negative timeout is not handeled correctly"); + } + + // 0 timeout + flag=false; + try{ + // Need to return IllegalArgumentException + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 1, 0.1, -0l); + } + catch(IllegalArgumentException e){ + flag=true; + } + if(!flag){ + fail("0 timeout is not handled correctly"); + } + + // Accept null timeout + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 1, 0.1, null); + + // Accept positive timeout + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 1, 0.1, 1l); + + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a zero step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessLinearActuatorZeroStepSize() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 1, 0, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + * Check correct initialization + */ + public void testChannelAccessLinearActuator() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessLinearActuator(channel, null, 1,0, 0, 10, 1, timeout); + } + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#set()}. + * @throws InterruptedException + */ + @Test + public void testSet() throws InterruptedException { + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, 0, 0.09999, 0.1, timeout); + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#hasNext()}. + * Check whether the actuator throws an Exception if there is no next point but set() is called + * @throws InterruptedException + */ + @Test(expected=IllegalStateException.class) + public void testSetNoNext() throws InterruptedException { + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, 0, 0.09999, 0.1, timeout); + actuator.set(); + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#hasNext()}. + * @throws InterruptedException + */ + @Test + public void testHasNextOneStep() throws InterruptedException { + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, 0, 0.09999, 0.1, timeout); + // Execute first set (because there is always a first move) + actuator.set(); + + // Check whether actuator returns that there is no next point + boolean next = actuator.hasNext(); + if(next){ + fail("There must be no next step"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#hasNext()}. + * @throws InterruptedException + */ + @Test + public void testHasNext() throws InterruptedException { + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, 0, 10, 0.1, timeout); + + int count = 0; + int steps = (int) ((10-0)/0.1)+1; + while(actuator.hasNext()){ + actuator.set(); + count++; + } + + logger.fine("Actual steps: "+count+" - Needed steps: "+steps); + if(count != steps){ + fail("Actuator set more steps than specified"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#set()}. + * Test actuator move startend ... + * @throws InterruptedException + */ + @Test + public void testSetLoop() throws InterruptedException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, svalue[0], svalue[1], svalue[2], timeout); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#set()}. + * Test actuator move startend ... + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testReverse() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, svalue[0], svalue[1], svalue[2], timeout); + actuator.init(); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[0] 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + else{ + if(Math.abs(val-(svalue[0]-(count*svalue[2]))) > 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + actuator.reverse(); + actuator.init(); + count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[1] 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + else{ + if(Math.abs(val-(svalue[1]-(count*svalue[2]))) > 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + + count++; + } + + cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessLinearActuator#set()}. + * Test actuator move startend ... + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testReverseReset() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator(channel, null, 1,0, svalue[0], svalue[1], svalue[2], timeout); + actuator.init(); + + int count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[0] 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + else{ + if(Math.abs(val-(svalue[0]-(count*svalue[2]))) > 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + actuator.reverse(); + actuator.reset(); + actuator.init(); + count =0; + while(actuator.hasNext()){ + actuator.set(); + + // Check set value + double val = channel.getValue(); + logger.info("Value: "+val); + if(svalue[0] 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + else{ + if(Math.abs(val-(svalue[0]-(count*svalue[2]))) > 0.001){ // 0.001 is precision + fail("Set value does not match actual value"); + } + } + count++; + } + + cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + + + } + } + + + // TODO Check if actuator makes the required number of steps + // TODO Check if actuator makes the steps in the right direction + // TODO Check if actuator really blocks until motor/actuator is moved. + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a negative step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessGPLinearActuatorNegativeStepSize() throws SocketException, Exception { + // Need to throw exception because of negative step size + new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 1, -0.1, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + * Check whether Exception is thrown if a zero step size is specified. + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessGPLinearActuatorZeroStepSize() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 1, 0, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#LinearActuatorChannelAccess(String, double, double, double)}. + * Check correct initialization + */ + public void testChannelAccessGPLinearActuator() throws SocketException, Exception { + // Zero step size need to cause an exception + new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 10, 1, timeout); + } + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneSet() throws InterruptedException, ExecutionException, ChannelException { + + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 0.09999, 0.1, timeout); + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneDelay() throws InterruptedException, ExecutionException, ChannelException { + + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, 1.5, 0, 1, 0.1, timeout); + + // Simulate done channel + doneChannel.setValue(1); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(1000); + doneChannel.setValue(0); + Thread.sleep(4000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + long start = System.currentTimeMillis(); + actuator.set(); + long end = System.currentTimeMillis(); + + logger.fine("Elapsed time: "+(end-start)); + if((end-start)<4000){ // Check whether all the moves took less than 6 seconds (thats the delay the done is set to 1) + fail("Done delay does not work"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#hasNext()}. + * Check whether the actuator throws an Exception if there is no next point but set() is called + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test(expected=IllegalStateException.class) + public void testDoneSetNoNext() throws InterruptedException, ExecutionException, ChannelException { + + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 0.09999, 0.1, timeout); + + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#hasNext()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneHasNextOneStep() throws InterruptedException, ExecutionException, ChannelException { + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 0.09999, 0.1, timeout); + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + // Execute first set (because there is always a first move) + actuator.set(); + + // Check whether actuator returns that there is no next point + boolean next = actuator.hasNext(); + if(next){ + fail("There must be no next step"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#hasNext()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneHasNext() throws InterruptedException, ExecutionException, ChannelException { + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, 0, 10, 0.1, timeout); + + int count = 0; + int steps = (int) ((10-0)/0.1)+1; + while(actuator.hasNext()){ + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(10); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + count++; + } + + logger.fine("Actual steps: "+count+" - Needed steps: "+steps); + if(count != steps){ + fail("Actuator set more steps than specified"); + } + } + + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#set()}. + * Test actuator move startend ... + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneSetLoop() throws InterruptedException, ExecutionException, ChannelException { + + List settings = new ArrayList(); + // start end stepsize + settings.add(new double[] {0, 10, 0.1}); + settings.add(new double[] {0, -1, 0.1}); + + + for(double[] svalue: settings){ + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, svalue[0], svalue[1], svalue[2], timeout); + + int count =0; + while(actuator.hasNext()){ + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(10); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + + actuator.set(); + count++; + } + + int cnt = (int) Math.floor(Math.abs(svalue[0]-svalue[1])/svalue[2])+1; + + if(count != cnt){ + fail("Actuator did not move required steps [actual count: "+count+" needed count: "+cnt+" ]"); + } + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessGPLinearActuator#set()}. + * Test whether the actuator returns if the actuator is already on the position it should be before the move ... + * (see issue XASEC-278) + * @throws ChannelException + * @throws ExecutionException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testMoveToActualPosition() throws InterruptedException, ExecutionException, ChannelException, TimeoutException { + + double start = 0; + double end = 2; + double stepSize = 1; + + channel.setValue(start); + + Thread.sleep(1000); + + logger.info("Current channel value: "+channel.getValue()); + + ChannelAccessLinearActuator actuator = new ChannelAccessLinearActuator<>(channel, doneChannel, doneValue, doneDelay, start, end, stepSize, timeout); + while(actuator.hasNext()){ + + // Simulate done channel + doneChannel.setValue(1); +// new Thread(new Runnable() { +// +// @Override +// public void run() { +// +// try { +// Thread.sleep(10); +//// doneChannel.setValue(1); +// } catch (Exception e) { +// } +// +// } +// }).start(); + + actuator.set(); + } + + } +} diff --git a/src/test/java/ch/psi/fda/core/actors/ChannelAccessTableActuatorTest.java b/src/test/java/ch/psi/fda/core/actors/ChannelAccessTableActuatorTest.java new file mode 100644 index 0000000..1f48169 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actors/ChannelAccessTableActuatorTest.java @@ -0,0 +1,490 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import static org.junit.Assert.*; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actors.ChannelAccessTableActuator; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class ChannelAccessTableActuatorTest { + + private static Logger logger = Logger.getLogger(ChannelAccessTableActuatorTest.class.getName()); + + private static final String channelName = TestChannels.BINARY_OUT; + private static final String channelNameDone = TestChannels.BINARY_OUT_TWO; + + private static final int doneValue = 1; + private static final double doneDelay = 0; + private static final Long timeout = 1800000l; // 30 minutes + + private ChannelService cservice; + private Channel channel; + private Channel doneChannel; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + channel = cservice.createChannel(new ChannelDescriptor<>(Double.class, channelName)); + doneChannel = cservice.createChannel(new ChannelDescriptor<>(Integer.class, channelNameDone)); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + */ + @Test + public void testChannelAccessTableActuator() { + new ChannelAccessTableActuator<>(channel, new double[]{1}, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + */ + @Test + public void testChannelAccessTableActuatorTimeout() { + // Negative timeout + boolean flag = false; + try{ + // Need to return IllegalArgumentException due to negative Timeout + new ChannelAccessTableActuator<>(channel, new double[]{1}, -1l); + } + catch(IllegalArgumentException e){ + flag=true; + } + if(!flag){ + fail("Negative timeout is not handeled correctly"); + } + + // 0 timeout + flag=false; + try{ + // Need to return IllegalArgumentException + new ChannelAccessTableActuator<>(channel, new double[]{1}, 0l); + } + catch(IllegalArgumentException e){ + flag=true; + } + if(!flag){ + fail("0 timeout is not handled correctly"); + } + + // Accept null timeout + new ChannelAccessTableActuator<>(channel, new double[]{1}, null); + + // Accept positive timeout + new ChannelAccessTableActuator<>(channel, new double[]{1}, 1l); + + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + * Test that constructor fails while providing a null table + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessTableActuatorNull() { + new ChannelAccessTableActuator<>(channel, null, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + * Test that constructor fails while providing an empty table + */ + @Test(expected=IllegalArgumentException.class) + public void testChannelAccessTableActuatorEmptyTable() { + new ChannelAccessTableActuator<>(channel, new double[0], timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testSet() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + double[] table = new double[]{1,2,3,4,5,6}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, table, timeout); + + int count=0; + while(actor.hasNext()){ + actor.set(); + + if(channel.getValue()!=table[count]){ + fail("Channel value does not match expected set value"); + } + + count++; + } + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws InterruptedException + */ + @Test(expected=RuntimeException.class) + public void testSetFail() throws InterruptedException { + double[] table = new double[]{1}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, table, timeout); + actor.set(); + + // This set() call has to fail with an RuntimeException + actor.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#hasNext()}. + * @throws InterruptedException + */ + @Test + public void testHasNext() throws InterruptedException { + + double[] table = new double[]{1}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, table, timeout); + actor.set(); + + boolean next = actor.hasNext(); + if(next){ + fail("There must be no next step"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testReverse() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + double[] table = new double[]{1,2,3,4,5,6}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator(channel, table, timeout); + actor.init(); + + int count=0; + while(actor.hasNext()){ + actor.set(); + + if(channel.getValue()!=table[count]){ + fail("Channel value does not match expected set value"); + } + + count++; + } + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + + actor.reverse(); + actor.init(); + + count=0; + while(actor.hasNext()){ + actor.set(); + + if(channel.getValue()!=table[table.length-1-count]){ + fail("Channel value does not match expected set value"); + } + + count++; + } + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + } + + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws ExecutionException + * @throws ChannelException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testReverseReset() throws InterruptedException, TimeoutException, ChannelException, ExecutionException { + double[] table = new double[]{1,2,3,4,5,6}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator(channel, table, timeout); + actor.init(); + + int count=0; + while(actor.hasNext()){ + actor.set(); + + if(channel.getValue()!=table[count]){ + fail("Channel value does not match expected set value"); + } + + count++; + } + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + + actor.reverse(); + actor.reset(); + actor.init(); + + count=0; + while(actor.hasNext()){ + actor.set(); + + if(channel.getValue()!=table[count]){ + fail("Channel value does not match expected set value"); + } + + count++; + } + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + } + + // DONE + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + */ + @Test + public void testDoneChannelAccessTableActuator() { + new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, doneDelay, new double[]{1}, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + * Test that constructor fails while providing a null table + */ + @Test(expected=IllegalArgumentException.class) + public void testDoneChannelAccessTableActuatorNull() { + new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, doneDelay, null, timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#ChannelAccessTableActuator(java.lang.String, double[])}. + * Test that constructor fails while providing an empty table + */ + @Test(expected=IllegalArgumentException.class) + public void testDoneChannelAccessTableActuatorEmptyTable() { + new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, doneDelay, new double[0], timeout); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testDoneSet() throws InterruptedException, ExecutionException, ChannelException, TimeoutException { + double[] table = new double[]{1,2,3,4,5,6}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, doneDelay, table, timeout); + + int count=0; + while(actor.hasNext()){ + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(1000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + actor.set(); + + if(channel.getValue()!=table[count]){ + fail("Channel value does not match expected set value"); + } + count++; + } + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testDoneDelay() throws InterruptedException, ExecutionException, ChannelException, TimeoutException { + double[] table = new double[]{1,2,3,4,5,6}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, 0.1, table, timeout); + + int count=0; + long start = System.currentTimeMillis(); + while(actor.hasNext()){ + // Simulate done channel + doneChannel.setValue(1); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(10); + doneChannel.setValue(0); + Thread.sleep(1000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + actor.set(); + + if(channel.getValue()!=table[count]){ + fail("Channel value does not match expected set value"); + } + count++; + } + long end = System.currentTimeMillis(); + + + if(count != table.length){ + fail("Not all points given in the table were set"); + } + + logger.fine("Elapsed time: "+(end-start)); + if((end-start)<6000){ // check whether all the moves took less than 6 seconds (thats the delay the done is set to 1) + fail("Done delay does not work"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#set()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test(expected=RuntimeException.class) + public void testDoneSetFail() throws InterruptedException, ExecutionException, ChannelException { + double[] table = new double[]{1}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, doneDelay, table, timeout); + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + actor.set(); + + // This set() call has to fail with an RuntimeException + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + actor.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.ChannelAccessTableActuator#hasNext()}. + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testDoneHasNext() throws InterruptedException, ExecutionException, ChannelException { + + double[] table = new double[]{1}; + ChannelAccessTableActuator actor = new ChannelAccessTableActuator<>(channel, doneChannel, doneValue, doneDelay, table, timeout); + + // Simulate done channel + doneChannel.setValue(0); + new Thread(new Runnable() { + + @Override + public void run() { + + try { + Thread.sleep(3000); + doneChannel.setValue(1); + } catch (Exception e) { + } + + } + }).start(); + actor.set(); + + boolean next = actor.hasNext(); + if(next){ + fail("There must be no next step"); + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/actors/ComplexActorTest.java b/src/test/java/ch/psi/fda/core/actors/ComplexActorTest.java new file mode 100644 index 0000000..a328057 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actors/ComplexActorTest.java @@ -0,0 +1,86 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + + +import static org.junit.Assert.fail; + +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actors.ChannelAccessLinearActuator; +import ch.psi.fda.core.actors.ComplexActuator; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +/** + * Prerequisites for this test are: + * All Actors that are used inside this test (except the ComplexActuator) need to be tested and + * fully compliant to their specification. + */ +public class ComplexActorTest { + + private static Logger logger = Logger.getLogger(ComplexActorTest.class.getName()); + + private static final Long timeout = 1800000l; + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + @Test + public void testSet() throws InterruptedException, ChannelException, TimeoutException { + + ComplexActuator actuator = new ComplexActuator(); + + actuator.getActors().add(new ChannelAccessLinearActuator(cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT)), null, 1,0, 0, 0.09999, 0.1, timeout)); + actuator.getActors().add(new ChannelAccessLinearActuator(cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT)), null, 1,0, 1, 2, 0.1, timeout)); + actuator.getActors().add(new ChannelAccessLinearActuator(cservice.createChannel(new ChannelDescriptor<>(Double.class, TestChannels.ANALOG_OUT)), null, 1,0, -1, -2, 0.1, timeout)); + + // Initialize actuator + actuator.init(); + + int count = 0; + while(actuator.hasNext()){ + actuator.set(); + count++; + } + + logger.fine("Execution count: "+count); + if(count!=23){ // There are 23 steps to do with the registered actors + fail("Actor did not perform all the steps"); + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/actors/JythonFunctionTest.java b/src/test/java/ch/psi/fda/core/actors/JythonFunctionTest.java new file mode 100644 index 0000000..275172e --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actors/JythonFunctionTest.java @@ -0,0 +1,99 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Logger; + +import org.junit.Test; + +import ch.psi.fda.core.scripting.JythonGlobalVariable; + +public class JythonFunctionTest { + + private static final Logger logger = Logger.getLogger(JythonFunctionTest.class.getName()); + + /** + * Test method for {@link ch.psi.fda.core.actors.JythonFunction#calculate(double)}. + */ + @Test + public void testCalculate() { + Map map = new HashMap(); + + String script = "import math\ndef "+JythonFunction.ENTRY_FUNCTION_NAME+"(parameter):\n return math.sqrt(parameter)"; + JythonFunction f = new JythonFunction(script, map); + + double rval = f.calculate(4); + logger.info("Return value: "+rval); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.JythonFunction#calculate(double)}. + */ + @Test(expected=IllegalArgumentException.class) + public void testCalculateNoVariableMapping() { + Map map = new HashMap(); + String script = "import math\ndef "+JythonFunction.ENTRY_FUNCTION_NAME+"(parameter, a):\n return math.sqrt(parameter)+a.getValue()"; + new JythonFunction(script, map); // Must throw IllegalArgumentException !!!! + } + + /** + * Test method for {@link ch.psi.fda.core.actors.JythonFunction#calculate(double)}. + */ + @Test + public void testCalculateVariableMapping() { + Map map = new HashMap(); + JythonGlobalVariable v = new JythonGlobalVariable(); + v.setName("a"); + v.setValue(1.5); + map.put("a", v); + + String script = "import math\ndef "+JythonFunction.ENTRY_FUNCTION_NAME+"(parameter, a):\n return math.sqrt(parameter)+a.getValue()"; + JythonFunction f = new JythonFunction(script, map); + + double rval = f.calculate(4); + logger.info("Return value: "+rval); + } + + /** + * Test whether there is a mapping of global variable that is not declared in the entry function as parameter + */ + @Test + public void testVariableMappingMissingParameterDeclaration() { + // Add two global variables a and b. + Map map = new HashMap(); + JythonGlobalVariable v = new JythonGlobalVariable(); + v.setName("a"); + v.setValue(1.5); + map.put(v.getName(), v); + v = new JythonGlobalVariable(); + v.setName("b"); + v.setValue(3.5); + map.put(v.getName(), v); + + String script = "import math\ndef "+JythonFunction.ENTRY_FUNCTION_NAME+"(parameter, b):\n return b.getValue()"; + JythonFunction f = new JythonFunction(script, map); + + double rval = f.calculate(4); + logger.info("Return value: "+rval); + } + +} diff --git a/src/test/java/ch/psi/fda/core/actors/PseudoActorSensorTest.java b/src/test/java/ch/psi/fda/core/actors/PseudoActorSensorTest.java new file mode 100644 index 0000000..8cdc70e --- /dev/null +++ b/src/test/java/ch/psi/fda/core/actors/PseudoActorSensorTest.java @@ -0,0 +1,118 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.actors; + +import static org.junit.Assert.*; + +import org.junit.Test; + +import ch.psi.fda.core.actors.PseudoActuatorSensor; + +public class PseudoActorSensorTest { + + private final String id = "xid"; + + /** + * Test method for {@link ch.psi.fda.core.actors.PseudoActuatorSensor#PseudoActor(int)}. + */ + @Test + public void testPseudoActor() { + new PseudoActuatorSensor(id, 1); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.PseudoActuatorSensor#PseudoActor(int)}. + */ + @Test(expected=IllegalArgumentException.class) + public void testPseudoActorFail() { + new PseudoActuatorSensor(id, 0); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.PseudoActuatorSensor#set()}. + */ + @Test + public void testSet() { + PseudoActuatorSensor actor = new PseudoActuatorSensor(id, 1); + actor.set(); + if(((Double)actor.read()).intValue()!=1){ + fail("Read value does not match set value"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.PseudoActuatorSensor#set()}. + * Check whether IllegalStateException is thrown if set() called more than allowed + */ + @Test(expected=IllegalStateException.class) + public void testSetFail() { + PseudoActuatorSensor actor = new PseudoActuatorSensor(id, 1); + actor.set(); + if(((Double)actor.read()).intValue()!=1){ + fail("Read value does not match set value"); + } + actor.set(); + } + + /** + * Test method for {@link ch.psi.fda.core.actors.PseudoActuatorSensor#hasNext()}. + */ + @Test + public void testHasNext() { + PseudoActuatorSensor actor = new PseudoActuatorSensor(id, 1); + + if(!actor.hasNext()){ + fail("Actor has no step when he should have one"); + } + actor.set(); + if(((Double)actor.read()).intValue()!=1){ + fail("Read value does not match set value"); + } + if(actor.hasNext()){ + fail("Actor has a step when he should not have one"); + } + } + + /** + * Test method for {@link ch.psi.fda.core.actors.PseudoActuatorSensor#init()}. + */ + @Test + public void testInit() { + PseudoActuatorSensor actor = new PseudoActuatorSensor(id, 1); + actor.init(); + actor.set(); + if(((Double)actor.read()).intValue()!=1){ + fail("Read value does not match set value"); + } + if(actor.hasNext()){ + fail("Actor has a step when he should not have one"); + } + // Initialize actor to start + actor.init(); + actor.set(); + if(((Double)actor.read()).intValue()!=1){ + fail("Read value does not match set value"); + } + if(actor.hasNext()){ + fail("Actor has a step when he should not have one"); + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/guard/ChannelAccessGuardTest.java b/src/test/java/ch/psi/fda/core/guard/ChannelAccessGuardTest.java new file mode 100644 index 0000000..7558c0c --- /dev/null +++ b/src/test/java/ch/psi/fda/core/guard/ChannelAccessGuardTest.java @@ -0,0 +1,115 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.guard; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.Guard; +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.guard.ChannelAccessGuard; +import ch.psi.fda.core.guard.ChannelAccessGuardCondition; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class ChannelAccessGuardTest { + + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.guard.ChannelAccessGuard#check()}. + * @throws InterruptedException + * @throws ChannelException + * @throws ExecutionException + * @throws TimeoutException + * @throws CAException + */ + @Test + public void testCheck() throws InterruptedException, ExecutionException, ChannelException, TimeoutException { + + String guardChannel = TestChannels.ANALOG_OUT; + Channel channel = cservice.createChannel(new ChannelDescriptor<>(Integer.class, guardChannel)); + + + Integer channelOkValue = 10; + List> conditions = new ArrayList<>(); + conditions.add(new ChannelAccessGuardCondition<>(channel, channelOkValue)); + Guard guard = new ChannelAccessGuard(conditions); + + + channel.setValue(channelOkValue); + + guard.init(); + + if(!guard.check()){ + fail("Guard not in correct state"); + } + + channel.setValue(channelOkValue+1); + channel.setValue(channelOkValue); + + Thread.sleep(1000); + + // Check whether guard is really on NOT OK + if(guard.check()){ + fail("Guard not in correct state"); + } + + channel.setValue(channelOkValue); + + // Check if after init the guard is ok again + guard.init(); + + if(!guard.check()){ + fail("Guard not in correct state"); + } + + // Check if after init the guard is not ok + channel.setValue(channelOkValue+1); + guard.init(); + + if(guard.check()){ + fail("Guard not in correct state"); + } + + } + +} diff --git a/src/test/java/ch/psi/fda/core/loops/ActorSensorLoopTest.java b/src/test/java/ch/psi/fda/core/loops/ActorSensorLoopTest.java new file mode 100644 index 0000000..98e442a --- /dev/null +++ b/src/test/java/ch/psi/fda/core/loops/ActorSensorLoopTest.java @@ -0,0 +1,302 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops; + +import static org.junit.Assert.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.core.Actor; +import ch.psi.fda.core.Guard; +import ch.psi.fda.core.Sensor; +import ch.psi.fda.core.TestChannels; +import ch.psi.fda.core.actions.Delay; +import ch.psi.fda.core.actors.ChannelAccessLinearActuator; +import ch.psi.fda.core.guard.ChannelAccessGuard; +import ch.psi.fda.core.guard.ChannelAccessGuardCondition; +import ch.psi.fda.core.loops.ActorSensorLoop; +import ch.psi.fda.core.sensors.ChannelAccessSensor; +import ch.psi.fda.messages.Message; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class ActorSensorLoopTest { + + // Get Logger + private static Logger logger = Logger.getLogger(ActorSensorLoopTest.class.getName()); + + private static final String boChannel = TestChannels.BINARY_OUT; + private static final String aoChannel = TestChannels.ANALOG_OUT; + private static final String wfChannel = TestChannels.DOUBLE_WAVEFORM; + private static final Long timeout = 1800000l; // 30 minutes + + + private ChannelService cservice; + private ActorSensorLoop loopOne; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + + loopOne = new ActorSensorLoop(); + + ChannelAccessLinearActuator a = new ChannelAccessLinearActuator<>(cservice.createChannel(new ChannelDescriptor<>(Double.class, aoChannel)), null, 1,0, 0, 10, 0.1, timeout); // Positioner + Sensor s = new ChannelAccessSensor<>("id0", cservice.createChannel(new ChannelDescriptor<>(Double.class, boChannel))); // Positioner Readback + Sensor s1 = new ChannelAccessSensor<>("id1", cservice.createChannel(new ChannelDescriptor<>(Double.class, aoChannel))); // Scalar Detector + Sensor s1string = new ChannelAccessSensor<>("id3", cservice.createChannel(new ChannelDescriptor<>(String.class, boChannel+".NAME"))); // Scalar String Detector + Sensor s2 = new ChannelAccessSensor<>("id2", cservice.createChannel(new ChannelDescriptor<>(double[].class, wfChannel,false, 10))); + + loopOne.getActors().add(a); + loopOne.getSensors().add(s); + loopOne.getSensors().add(s1); + loopOne.getSensors().add(s1string); + loopOne.getSensors().add(s2); + + loopOne.prepare(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.loops.ActorSensorLoop#execute()}. + * @throws InterruptedException + */ + @Test + public void testExecute() throws InterruptedException { + + loopOne.getEventBus().register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info( m.toString() ); + } + + }); + + loopOne.execute(); + } + + /** + * Test method for {@link ch.psi.fda.core.loops.ActorSensorLoop#execute()}. + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + * @throws ExecutionException + * @throws CAException + */ + @Test + public void testExecuteGuard() throws InterruptedException, ChannelException, TimeoutException, ExecutionException { + + ActorSensorLoop loop = new ActorSensorLoop(); + + ChannelAccessLinearActuator a = new ChannelAccessLinearActuator<>(cservice.createChannel(new ChannelDescriptor<>(Double.class, aoChannel)), null, 1,0, 0, 1.5, 0.1,timeout); // Positioner + + Sensor s = new ChannelAccessSensor<>("id0", cservice.createChannel(new ChannelDescriptor<>(Double.class, aoChannel))); + Sensor s1 = new ChannelAccessSensor<>("id1", cservice.createChannel(new ChannelDescriptor<>(Double.class, boChannel))); + Sensor s2 = new ChannelAccessSensor<>("id2", cservice.createChannel(new ChannelDescriptor<>(double[].class,wfChannel, false,10))); + + loop.getActors().add(a); + loop.getPostActorActions().add(new Delay(500)); + loop.getSensors().add(s); + loop.getSensors().add(s1); + loop.getSensors().add(s2); + + // Guard + final Integer okValue = 0; + final Channel b = cservice.createChannel(new ChannelDescriptor<>(Integer.class, boChannel)); + List> conditions = new ArrayList<>(); + conditions.add(new ChannelAccessGuardCondition(b, 0)); + Guard guard = new ChannelAccessGuard(conditions); + loop.setGuard(guard); + + + + Thread tguard = new Thread(new Runnable() { + + @Override + public void run() { + try { + // Wait some seconds and set channel to ok + Thread.sleep(1000); + b.setValue(okValue); + + // Set channel to not ok + Thread.sleep(1000); + b.setValue(okValue+1); + + // Wait some seconds and set channel to ok again + Thread.sleep(4000); + b.setValue(okValue); + } catch (Exception e) { + logger.log(Level.SEVERE, "An Exception occured while setting guard channel", e); + } + } + }); + + + loop.getEventBus().register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info( m.toString() ); + } + + }); + + // Set the guard channel to not OK + b.setValue(okValue+1); + + tguard.start(); + loop.prepare(); + loop.execute(); + + logger.info("End of test"); + } + + + + @Test + public void testParallelSet() throws InterruptedException, ChannelException, TimeoutException { + + final int steps = 2; + final HashMap timestamps = new HashMap(); + + ActorSensorLoop loop = new ActorSensorLoop(); + + ChannelAccessLinearActuator a = new ChannelAccessLinearActuator<>(cservice.createChannel(new ChannelDescriptor<>(Double.class, aoChannel)), null, 1,0, 0, (steps*0.1), 0.1, timeout); // Positioner + + Sensor s = new ChannelAccessSensor<>("id0", cservice.createChannel(new ChannelDescriptor<>(Double.class, aoChannel))); + + loop.getActors().add(a); + loop.getActors().add(new Actor() { + int c = 0; + @Override + public void set() { + logger.info("Start actor 1"); + timestamps.put("sa1", System.currentTimeMillis()); + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + logger.info("Done actor 1"); + timestamps.put("da1", System.currentTimeMillis()); + c++; + } + + @Override + public void reverse() { + } + + @Override + public void reset() { + } + + @Override + public void init() { + } + + @Override + public boolean hasNext() { + if(ctimestamps.get("sa2")) ){ + fail("Done 1 occured before start 2"); + } + + if(! (timestamps.get("da2")>timestamps.get("sa1")) ){ + fail("Done 2 occured before start 1"); + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/loops/SambaTest.java b/src/test/java/ch/psi/fda/core/loops/SambaTest.java new file mode 100644 index 0000000..d1c31d4 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/loops/SambaTest.java @@ -0,0 +1,62 @@ +/** + * + * Copyright 2012 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify it under + * the terms of the GNU Lesser General Public License as published by the Free + * Software Foundation, either version 3 of the License, or (at your option) any + * later version. + * + * This code 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 Lesser General Public License for more + * details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ +package ch.psi.fda.core.loops; + +//import java.util.logging.Logger; + +//import jcifs.smb.SmbFile; +// +//import org.junit.Ignore; +//import org.junit.Test; + +public class SambaTest { + +// private static final Logger logger = Logger.getLogger(SambaTest.class.getName()); + +// @Test +// public void testSamba() throws Exception{ +//// System.setProperty("jcifs.smb.client.dfs.disabled", "true"); +//// System.setProperty("jcifs.smb.client.ssnLimit", "1"); +// +//// System.setProperty("jcifs.smb.client.domain", "x05la.psi.ch"); +//// System.setProperty("jcifs.smb.client.username", "x05laop"); +//// System.setProperty("jcifs.smb.client.password", "mXAS"); +//// +//// System.setProperty("jcifs.util.loglevel", "6"); +// +//// System.setProperty("jcifs.smb.lmCompatibility", "0"); +// +//// jcifs.smb.client.{domain,username,password +// +// String smbShare = ""; +//// smbShare = "smb://x05laop:mXAS@x05la/x05laop/Data1/otfTmp/"; +//// smbShare = "smb://x05la.psi.ch/x05laop/Data1/otfTmp/"; +//// smbShare = "smb://x05laop:mXAS@x05la/x05laop/Data1/otfTmp/000000.txt.lock"; +// smbShare = "smb://:@yoke.psi.ch/nfs/"; +//// NtlmPasswordAuthentication auth = new NtlmPasswordAuthentication("x05laop:mXAS"); +//// SmbFile tmpDir = new SmbFile(smbShare,auth); +//// SmbFile tmpDir = new SmbFile(smbShare); +// +//// tmpDir.createNewFile(); +//// logger.info(""+tmpDir.exists()); +//// logger.info(""+tmpDir.isDirectory()); +//// SmbFile tmpFile = new SmbFile(tmpDir, "000000.txt.lock"); +//// tmpFile.exists(); +// } +} diff --git a/src/test/java/ch/psi/fda/core/loops/cr/CrlogicLoopStreamTest.java b/src/test/java/ch/psi/fda/core/loops/cr/CrlogicLoopStreamTest.java new file mode 100644 index 0000000..196e231 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/loops/cr/CrlogicLoopStreamTest.java @@ -0,0 +1,97 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.Test; + +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.core.TestConfiguration; +import ch.psi.fda.messages.Message; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +public class CrlogicLoopStreamTest { + + private static final Logger logger = Logger.getLogger(CrlogicLoopStreamTest.class.getName()); + + + @Test(timeout=60000) + public void testExecute() throws InterruptedException, ChannelException, TimeoutException, ExecutionException{ + + ChannelService cservice = new DefaultChannelService(); + TestConfiguration c = TestConfiguration.getInstance(); + + boolean zigZag = false; + + String readback = null; + double start = 0; + double end = 2; + double stepSize = 0.01; + double integrationTime = 0.01; + double additionalBacklash = 0; + + CrlogicLoopStream crlogic = new CrlogicLoopStream(cservice, c.getCrlogicPrefix(), c.getIoc(), zigZag); + crlogic.setActuator("cmot", c.getMotor1(), readback, start, end, stepSize, integrationTime, additionalBacklash); + crlogic.getSensors().add(new CrlogicResource("trigger", "TRIGGER0")); + crlogic.getSensors().add(new CrlogicResource("scaler0", "SCALER0", true)); + crlogic.getSensors().add(new CrlogicResource("scaler1", "SCALER1", true)); + crlogic.getSensors().add(new CrlogicResource("timestamp", "TIMESTAMP")); + + +// Initialize scaler template + TemplateVSC16Scaler scalertemplate = new TemplateVSC16Scaler(); + Map macros = new HashMap<>(); + macros.put("PREFIX", c.getPrefixScaler()); + cservice.createAnnotatedChannels(scalertemplate, macros); + + crlogic.getEventBus().register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info(m.toString()); + } + }); + + logger.info("Start scaler"); + scalertemplate.getCommand().setValueNoWait(TemplateVSC16Scaler.Command.Count.ordinal()); + + + logger.info("Execute CRLOGIC"); + crlogic.prepare(); + crlogic.execute(); + crlogic.cleanup(); + + + logger.info("Stop scaler"); + scalertemplate.getCommand().setValue(TemplateVSC16Scaler.Command.Done.ordinal()); + + // Destroy scaler template + cservice.destroyAnnotatedChannels(scalertemplate); + cservice.destroy(); + } + +} diff --git a/src/test/java/ch/psi/fda/core/loops/cr/ParallelCrlogicStreamMergeTest.java b/src/test/java/ch/psi/fda/core/loops/cr/ParallelCrlogicStreamMergeTest.java new file mode 100644 index 0000000..018c15a --- /dev/null +++ b/src/test/java/ch/psi/fda/core/loops/cr/ParallelCrlogicStreamMergeTest.java @@ -0,0 +1,212 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.ArrayList; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.logging.Logger; + +import org.junit.Assert; +import org.junit.Test; + +import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.messages.DataMessage; +import ch.psi.fda.messages.EndOfStreamMessage; +import ch.psi.fda.messages.Message; +import ch.psi.fda.messages.Metadata; + +public class ParallelCrlogicStreamMergeTest { + + private static final Logger logger = Logger.getLogger(ParallelCrlogicStreamMergeTest.class.getName()); + + @Test + public void testMerge() throws InterruptedException { + + final Queue expectedValues = new ArrayBlockingQueue<>(3); + expectedValues.add(new double[]{0.0035, 1.0}); + expectedValues.add(new double[]{0.015, 1.0}); + expectedValues.add(new double[]{0.026, 3.0}); + + List dmm = new ArrayList<>(); + dmm.add(new Metadata("cractuator")); + dmm.add(new Metadata("tstamp")); + + EventBus dataQueue2 = new EventBus(); + EventBus dataQueue = new EventBus(); + + EventBus b = new EventBus(); + b.register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info(m.toString()); + if(m instanceof DataMessage){ + DataMessage dm = (DataMessage) m; + double[] expected = expectedValues.remove(); + + Assert.assertEquals(expected[0], (double)dm.getData().get(0), 0.001); + Assert.assertEquals(expected[1], (double)dm.getData().get(1), 0.001); + } + } + }); + + MergeLogic streamMerge = new MergeLogic(dataQueue, dataQueue2, b); + streamMerge.enable(); + + // We need to fill the second queue first as otherwise the logic will fail + List dmm2 = new ArrayList<>(); + dmm2.add(new Metadata("milli")); + dmm2.add(new Metadata("nano")); + dmm2.add(new Metadata("sensor1")); + + + DataMessage dm2 = new DataMessage(dmm2); + dm2.getData().add(9000d); + dm2.getData().add(122d); + dm2.getData().add(0.1d); + dataQueue2.post(dm2); + + dm2 = new DataMessage(dmm2); + dm2.getData().add(10000d); + dm2.getData().add(122d); + dm2.getData().add(1d); + dataQueue2.post(dm2); + + dm2 = new DataMessage(dmm2); + dm2.getData().add(10000d); + dm2.getData().add(153d); + dm2.getData().add(2d); + dataQueue2.post(dm2); + + dm2 = new DataMessage(dmm2); + dm2.getData().add(10000d); + dm2.getData().add(162d); + dm2.getData().add(3d); + dataQueue2.post(dm2); + + dataQueue2.post(new EndOfStreamMessage()); + + + DataMessage dm = new DataMessage(dmm); + dm.getData().add(0.0035d); + dm.getData().add(10.000000123); + dataQueue.post(dm); + + dm = new DataMessage(dmm); + dm.getData().add(0.015); + dm.getData().add(10.000000143); + dataQueue.post(dm); + + dm = new DataMessage(dmm); + dm.getData().add(0.026); + dm.getData().add(10.000000163); + dataQueue.post(dm); + + dataQueue.post(new EndOfStreamMessage()); + + streamMerge.disable(); + + } + + /** + * For this test the second queue value timestamp must not be before the timestamp of the first queue + * @throws InterruptedException + */ + @Test + public void testMergeSecondQueueLessElements() throws InterruptedException { + + EventBus dataQueue = new EventBus(); + EventBus dataQueue2 = new EventBus(); + + EventBus b = new EventBus(); + b.register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info(m.toString()); + } + }); + MergeLogic streamMerge = new MergeLogic(dataQueue, dataQueue2, b); + + streamMerge.enable(); + + + // We need to fill the second queue first as otherwise the logic will fail + List dmm2 = new ArrayList<>(); + dmm2.add(new Metadata("milli")); + dmm2.add(new Metadata("nano")); + dmm2.add(new Metadata("sensor1")); + + DataMessage dm2 = new DataMessage(dmm2); + dm2.getData().add(9000d); + dm2.getData().add(122d); + dm2.getData().add(0.1d); + dataQueue2.post(dm2); +// +// dm2 = new DataMessage(dmm2); +// dm2.getData().add(10000d); +// dm2.getData().add(122d); +// dm2.getData().add(1d); +// dataQueue2.add(dm2); + +// dm2 = new DataMessage(dmm2); +// dm2.getData().add(10000d); +// dm2.getData().add(153d); +// dm2.getData().add(2d); +// dataQueue2.post(dm2); + +// dm2 = new DataMessage(dmm2); +// dm2.getData().add(10000d); +// dm2.getData().add(170d); +// dm2.getData().add(3d); +// dataQueue2.add(dm2); + + dataQueue2.post(new EndOfStreamMessage()); + + + + List dmm = new ArrayList<>(); + dmm.add(new Metadata("cractuator")); + dmm.add(new Metadata("tstamp")); + + + DataMessage dm = new DataMessage(dmm); + dm.getData().add(0.0035d); + dm.getData().add(10.000000123); + dataQueue.post(dm); + + dm = new DataMessage(dmm); + dm.getData().add(0.015); + dm.getData().add(10.000000143); + dataQueue.post(dm); + +// dm = new DataMessage(dmm); +// dm.getData().add(0.026); +// dm.getData().add(10.000000163); +// dataQueue.add(dm); + + dataQueue.post(new EndOfStreamMessage()); + + streamMerge.disable(); + } + +} diff --git a/src/test/java/ch/psi/fda/core/loops/cr/ParallelCrlogicTest.java b/src/test/java/ch/psi/fda/core/loops/cr/ParallelCrlogicTest.java new file mode 100644 index 0000000..41a069c --- /dev/null +++ b/src/test/java/ch/psi/fda/core/loops/cr/ParallelCrlogicTest.java @@ -0,0 +1,137 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.core.TestConfiguration; +import ch.psi.fda.messages.Message; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; +import ch.psi.jcae.impl.type.DoubleTimestamp; + +public class ParallelCrlogicTest { + + private static final Logger logger = Logger.getLogger(ParallelCrlogicTest.class.getName()); + + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + +// @Ignore + @Test(timeout=60000) + public void testExecute() throws InterruptedException, ChannelException, TimeoutException, ExecutionException{ + + TestConfiguration c = TestConfiguration.getInstance(); + + boolean zigZag = false; + + String readback = null; + double start = 0; + double end = 2; + double stepSize = 0.01; + double integrationTime = 0.01; + double additionalBacklash = 0; + + List ids = new ArrayList<>(); + List> sensors = new ArrayList<>(); + + ids.add("mot1"); + ids.add("mot2"); +// ids.add("mot3"); + sensors.add(cservice.createChannel(new ChannelDescriptor<>(DoubleTimestamp.class, c.getMotor1()+".RVAL"))); + sensors.add(cservice.createChannel(new ChannelDescriptor<>(DoubleTimestamp.class, c.getMotor1()+".RBV"))); +// sensors.add(cservice.createChannel(new ChannelDescriptor<>(DoubleTimestamp.class, "ARIDI-PCT:CURRENT"))); + + + ScrlogicLoop scrlogic = new ScrlogicLoop(ids, sensors); + + + CrlogicLoopStream crlogic = new CrlogicLoopStream(cservice, c.getCrlogicPrefix(), c.getServer(), zigZag); + crlogic.setActuator("cmot", c.getMotor1(), readback, start, end, stepSize, integrationTime, additionalBacklash); + crlogic.getSensors().add(new CrlogicResource("trigger", "TRIGGER0")); + crlogic.getSensors().add(new CrlogicResource("scaler0", "SCALER0", true)); + crlogic.getSensors().add(new CrlogicResource("scaler1", "SCALER1", true)); + crlogic.getSensors().add(new CrlogicResource("timestamp", "TIMESTAMP")); + + + // Initialize scaler template + TemplateVSC16Scaler scalertemplate = new TemplateVSC16Scaler(); + Map macros = new HashMap<>(); + macros.put("PREFIX", c.getPrefixScaler()); + cservice.createAnnotatedChannels(scalertemplate, macros); + + + ParallelCrlogic pcrlogic = new ParallelCrlogic(crlogic, scrlogic); + + + pcrlogic.getEventBus().register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info(m.toString()); + } + }); + + logger.info("Start scaler"); + scalertemplate.getCommand().setValueNoWait(TemplateVSC16Scaler.Command.Count.ordinal()); + + pcrlogic.prepare(); + pcrlogic.execute(); + pcrlogic.cleanup(); + + logger.info("Stop scaler"); + scalertemplate.getCommand().setValue(TemplateVSC16Scaler.Command.Done.ordinal()); + +// System.out.println("PARALLEL CRLOGIC data:"); +// BlockingQueue queue = pcrlogic.getDataQueue().getQueue(); +// Message m = queue.take(); +// while(! (m instanceof EndOfStreamMessage)){ +// System.out.println(m.toString()); +// m = queue.take(); +// } + + // Destroy scaler template + cservice.destroyAnnotatedChannels(scalertemplate); + } + +} diff --git a/src/test/java/ch/psi/fda/core/loops/cr/ScrlogicLoopTest.java b/src/test/java/ch/psi/fda/core/loops/cr/ScrlogicLoopTest.java new file mode 100644 index 0000000..29f9ae5 --- /dev/null +++ b/src/test/java/ch/psi/fda/core/loops/cr/ScrlogicLoopTest.java @@ -0,0 +1,143 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.loops.cr; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeoutException; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.google.common.eventbus.Subscribe; + +import ch.psi.fda.messages.Message; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; +import ch.psi.jcae.impl.type.DoubleTimestamp; + +public class ScrlogicLoopTest { + + private static final Logger logger = Logger.getLogger(ScrlogicLoopTest.class.getName()); + + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + /** + * Test method for {@link ch.psi.fda.core.loops.cr.ScrlogicLoop#execute()}. + * @throws InterruptedException + * @throws TimeoutException + * @throws ChannelException + * @throws CAException + */ + @Test + public void testExecute() throws InterruptedException, ChannelException, TimeoutException { + List ids = new ArrayList<>(); + List> sensors = new ArrayList<>(); + + ids.add("mot1"); + ids.add("mot2"); + sensors.add(cservice.createChannel(new ChannelDescriptor<>(DoubleTimestamp.class, "MTEST-HW3:MOT1"))); + sensors.add(cservice.createChannel(new ChannelDescriptor<>(DoubleTimestamp.class, "MTEST-HW3:MOT1.RBV"))); + + + + final ScrlogicLoop logic = new ScrlogicLoop(ids, sensors); + + logic.getEventBus().register(new Object(){ + @Subscribe + public void onMessage(Message m){ + logger.info(m.toString()); + } + + }); + + for(int i=0;i<2;i++){ + + logic.prepare(); + + Thread tt = new Thread(new Runnable(){ + + @Override + public void run() { + try { + logic.execute(); + } catch (InterruptedException e) { + logger.log(Level.SEVERE, "", e); + } + } + + }); + tt.start(); + + + final CountDownLatch l = new CountDownLatch(1); + Thread t = new Thread(new Runnable(){ + + @Override + public void run() { + try{ + System.out.println("MOVE 1"); + Channel channel = cservice.createChannel(new ChannelDescriptor<>(Double.class, "MTEST-HW3:MOT1", false)); + // Wait some time until + Thread.sleep(100); + channel.setValue(1.5); + Thread.sleep(100); + channel.setValue(2.5); + System.out.println("MOVE D"); + // Thread.sleep(100); + // channel.setValue(6.5); + // Thread.sleep(100); + + l.countDown(); + } + catch(Exception e){ + } + } + + }); + t.start(); + + l.await(); + + + logic.abort(); + + tt.join(); + } + } + +} diff --git a/src/test/java/ch/psi/fda/core/sensors/ChannelAccessSensorTest.java b/src/test/java/ch/psi/fda/core/sensors/ChannelAccessSensorTest.java new file mode 100644 index 0000000..215eb5a --- /dev/null +++ b/src/test/java/ch/psi/fda/core/sensors/ChannelAccessSensorTest.java @@ -0,0 +1,118 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.sensors; + +import static org.junit.Assert.*; +import gov.aps.jca.CAException; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.logging.Logger; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.Sensor; +import ch.psi.fda.core.TestChannels; +import ch.psi.jcae.Channel; +import ch.psi.jcae.ChannelDescriptor; +import ch.psi.jcae.ChannelException; +import ch.psi.jcae.ChannelService; +import ch.psi.jcae.impl.DefaultChannelService; + +/** + * Test class for the ScalarDoubleSensorChannelAccess class. + */ +public class ChannelAccessSensorTest { + + private static Logger logger = Logger.getLogger(ChannelAccessSensorTest.class.getName()); + + private ChannelService cservice; + + @Before + public void setUp() throws Exception { + cservice = new DefaultChannelService(); + } + + @After + public void tearDown() throws Exception { + cservice.destroy(); + } + + @Test + public void testReadDouble() throws InterruptedException, ChannelException, TimeoutException, ExecutionException { + final String channelName = TestChannels.ANALOG_OUT; + + Channel channel = cservice.createChannel(new ChannelDescriptor<>(Double.class, channelName)); + Sensor sensor = new ChannelAccessSensor<>("id0", channel); + + // Prepare sensor channel + Double setValue = 0.1d; + channel.setValue(setValue); + + // Get sensor readout value + Double value = (Double) sensor.read(); + logger.finest("Sensor value: "+value); + if(!value.equals(setValue)){ + fail("Sensor readout value does not match actual value"); + } + } + + @Test + public void testReadString() throws CAException, InterruptedException, ChannelException, TimeoutException { + final String channelName = TestChannels.ANALOG_OUT+".NAME"; + final String actualValue = TestChannels.ANALOG_OUT; + + Channel channel = cservice.createChannel(new ChannelDescriptor<>(String.class, channelName)); + Sensor sensor = new ChannelAccessSensor<>("id0", channel); + + // Get sensor readout value + String value = (String) sensor.read(); + logger.info("Sensor value: "+value); + if(!value.equals(actualValue)){ + fail("Sensor readout "+value+" value does not match actual value "+actualValue); + } + } + + + @Test + public void testReadDoubleArray() throws CAException, InterruptedException, ChannelException, TimeoutException, ExecutionException { + + final String channelName = TestChannels.DOUBLE_WAVEFORM; + final int numberOfPoints = 10; + + Channel channel = cservice.createChannel(new ChannelDescriptor<>(double[].class, channelName, false, numberOfPoints)); + Sensor sensor = new ChannelAccessSensor<>("id0", channel); + + // Prepare sensor channel + double[] setValue = new double[] { 0.1,0.2,0.3,4,5,6,77,88,99,10.2}; + channel.setValue(setValue); + + // Get sensor readout value + double[] value = (double[]) sensor.read(); + for(int i=0;i0.0000001){ // The precision of the channel is 6 + fail("Sensor readout value does not match actual value"); + } + } + } +} diff --git a/src/test/java/ch/psi/fda/core/sensors/MillisecondTimestampSensorTest.java b/src/test/java/ch/psi/fda/core/sensors/MillisecondTimestampSensorTest.java new file mode 100644 index 0000000..435edae --- /dev/null +++ b/src/test/java/ch/psi/fda/core/sensors/MillisecondTimestampSensorTest.java @@ -0,0 +1,68 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.core.sensors; + + +import java.util.logging.Logger; + +import gov.aps.jca.CAException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import ch.psi.fda.core.sensors.TimestampSensor; + +/** + * Test class for MillisecondsTimestampSensor + */ +public class MillisecondTimestampSensorTest { + + // Get Logger + private static Logger logger = Logger.getLogger(MillisecondTimestampSensorTest.class.getName()); + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + } + + /** + * @throws java.lang.Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test method for {@link ch.psi.fda.core.sensors.ChannelAccessDoubleSensor#read()}. + * @throws CAException + */ + @Test + public void testRead() throws CAException { + TimestampSensor sensor = new TimestampSensor("id0"); + + // Get sensor readout value + Double value = (Double) sensor.read(); + logger.finest("Sensor value: "+value); + + } +} diff --git a/src/test/java/ch/psi/fda/model/ModelManagerTest.java b/src/test/java/ch/psi/fda/model/ModelManagerTest.java new file mode 100644 index 0000000..806c657 --- /dev/null +++ b/src/test/java/ch/psi/fda/model/ModelManagerTest.java @@ -0,0 +1,93 @@ +/** + * + * Copyright 2010 Paul Scherrer Institute. All rights reserved. + * + * This code is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This code 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + * + */ + +package ch.psi.fda.model; + +import java.io.File; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.logging.Logger; + +import javax.xml.bind.JAXBException; +import javax.xml.bind.MarshalException; + +import org.junit.Test; +import org.xml.sax.SAXException; + +import ch.psi.fda.model.ModelManager; +import ch.psi.fda.model.v1.Configuration; +import ch.psi.fda.model.v1.LinePlot; +import ch.psi.fda.model.v1.Scan; + +public class ModelManagerTest { + + private static final String tmpDirectory = "target/tmp"; + + private static Logger logger = Logger.getLogger(ModelManagerTest.class.getName()); + + /** + * Test method for {@link ch.psi.fda.model.ModelManager#unmarshall(java.io.File)}. + * @throws SAXException + * @throws JAXBException + * @throws URISyntaxException + */ + @Test + public void testUnmarshall() throws JAXBException, SAXException, URISyntaxException { + URL url = this.getClass().getClassLoader().getResource("home/scans/templates/scan1d.xml"); + Configuration c = ModelManager.unmarshall(new File(new URI(url.toString()))); + logger.info(""+c.getData().getFormat()); + + logger.info("FILENAME: "+c.getData().getFileName()); + } + + /** + * Test method for {@link ch.psi.fda.model.ModelManager#marshall(ch.psi.fda.model.v1.Configuration, java.io.File)}. + * @throws SAXException + * @throws JAXBException + */ + @Test(expected=MarshalException.class) + public void testMarshallFail() throws JAXBException, SAXException { + + Configuration c = new Configuration(); + c.setDescription("My Description"); + Scan s = new Scan(); + c.setScan(s); + + // Add component that is not match to xsd + LinePlot v = new LinePlot(); + c.getVisualization().add(v); + + ModelManager.marshall(c, new File(tmpDirectory+"/scan.xml")); + + } + + @Test + public void testMarshall() throws JAXBException, SAXException { + + Configuration c = new Configuration(); + c.setDescription("My Description"); + Scan s = new Scan(); + c.setScan(s); + + ModelManager.marshall(c, new File(tmpDirectory+"/scan1234.xml")); + + } + +} diff --git a/src/test/resources/jcae.properties b/src/test/resources/jcae.properties new file mode 100644 index 0000000..7b445b4 --- /dev/null +++ b/src/test/resources/jcae.properties @@ -0,0 +1 @@ +ch.psi.jcae.ContextFactory.addressList=MTEST-VME-HW3.psi.ch \ No newline at end of file