/*******************************************************************************
* Copyright (c) 2011 The Board of Trustees of the Leland Stanford Junior University
* as Operator of the SLAC National Accelerator Laboratory.
* Copyright (c) 2011 Brookhaven National Laboratory.
* EPICS archiver appliance is distributed subject to a Software License Agreement found
* in file LICENSE that is included with this distribution.
*******************************************************************************/
package org.epics.archiverappliance.engine.LargeSIOC;
import gov.aps.jca.CAException;
import gov.aps.jca.Channel;
import gov.aps.jca.Context;
import gov.aps.jca.JCALibrary;
import gov.aps.jca.configuration.Configuration;
import gov.aps.jca.configuration.DefaultConfigurationBuilder;
import gov.aps.jca.event.ConnectionEvent;
import gov.aps.jca.event.ConnectionListener;
import gov.aps.jca.event.PutEvent;
import gov.aps.jca.event.PutListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.config.ConfigServiceForTests;
import org.epics.archiverappliance.engine.epics.JCAConfigGen;
/**
* Update a specified number of sine_* PV's generated by GenerateLargeDB once a second using ca_put at a certain rate
* The values are set to generate sine waves with a frequency of 1/3600Hz and a phase related to the pv number
* @author mshankar
*/
public class UpdatePVsatRate {
private static final Logger logger = Logger.getLogger(UpdatePVsatRate.class);
private static Context JCAContext = null;
private static LinkedBlockingQueue<Runnable> contextTasks = new LinkedBlockingQueue<Runnable>();
private static ScheduledThreadPoolExecutor eventGenerator = new ScheduledThreadPoolExecutor(1);
private ConcurrentHashMap<String, UpdateValue> connectedChannels = null;
private ConfigServiceForTests configService = new ConfigServiceForTests(new File("./bin"));
public static void main(String[] args) throws Exception {
if(args.length < 2) {
System.err.println("Usage: java org.epics.archiverappliance.engine.LargeSIOC.UpdatePVsatRate <TotalPVs> <Rate>");
return;
}
int totalpvcount = Integer.parseInt(args[0]);
int rate = Integer.parseInt(args[1]);
UpdatePVsatRate upv = new UpdatePVsatRate(totalpvcount, rate);
upv.processContextTasks();
}
public UpdatePVsatRate(final int totalpvcount, final int rate) throws Exception {
logger.info("Creating the JCA context as part of UpdatePVsatRate initialization");
// Get the JCALibrary instance.
JCALibrary jca = JCALibrary.getInstance();
ByteArrayInputStream bis = JCAConfigGen.generateJCAConfig(configService);
DefaultConfigurationBuilder configBuilder = new DefaultConfigurationBuilder();
Configuration configuration = configBuilder.build(bis);
// Now, we'll create a context with this configuration.
JCAContext = jca.createContext(configuration);
logger.info("Successfully created the JCA context as part of UpdatePVsatRate initialization");
connectedChannels = new ConcurrentHashMap<String, UpdatePVsatRate.UpdateValue>(totalpvcount);
for(int i = 0; i < totalpvcount; i++) {
// The "test" comes from a macro but we hardcode it here.
String pvName = "test:sine_" + i;
ConnCallback cb = new ConnCallback(i);
Channel channel = JCAContext.createChannel(pvName, cb);
cb.setChannel(channel);
}
eventGenerator.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
// Every second , we pick up "rate" number of connected PV's at random and update them to their next value.
System.out.print("Begin update");
Collection<UpdateValue> values = connectedChannels.values();
ArrayList<UpdateValue> shuffle = new ArrayList<UpdateValue>(values);
Collections.shuffle(shuffle);
for(int i = 0; i < rate && i < shuffle.size(); i++) {
UpdateValue pvupdate = shuffle.get(i);
pvupdate.scheduleUpdate();
}
System.out.println(" and end update");
}
}, 10, 1, TimeUnit.SECONDS);
}
public void processContextTasks() {
while(true) {
try {
Runnable task = contextTasks.take();
task.run();
JCAContext.flushIO();
} catch(Throwable t) {
logger.error("Exception processing context tasks", t);
}
}
}
private class ConnCallback implements ConnectionListener {
private Channel channel;
private int pvindex;
public ConnCallback(int pvindex) {
this.pvindex = pvindex;
}
@Override
public void connectionChanged(ConnectionEvent connEvent) {
if(connEvent.isConnected()) {
connectedChannels.putIfAbsent("test:sine_" + pvindex, new UpdateValue(channel, pvindex));
} else {
connectedChannels.remove("test:sine_" + pvindex);
}
}
void setChannel(Channel channel) {
this.channel = channel;
}
}
private class UpdateValue {
private Channel channel;
private int pvindex;
UpdateValue(Channel channel, int pvindex) {
this.channel = channel;
this.pvindex = pvindex;
}
public void scheduleUpdate() {
contextTasks.add(new JCAPut(channel, pvindex));
}
}
private class JCAPut implements Runnable {
private Channel channel;
private int pvindex;
private int phase;
JCAPut(Channel channel, int pvindex) {
this.channel = channel;
this.pvindex = pvindex;
phase = (this.pvindex%10)*36;
}
@Override
public void run() {
try {
double degrees = ((System.currentTimeMillis()/1000) % 3600) * (360.0/3600.0);
logger.debug("Curr for " + pvindex + " = " + (degrees + phase));
double radians = (degrees + phase)*(Math.PI/180.0);
double value = Math.sin(radians);
channel.put(value, new JCAPutListener());
} catch(CAException ex) {
logger.warn("Exception ", ex);
}
}
}
private class JCAPutListener implements PutListener {
@Override
public void putCompleted(PutEvent putEvent) {
// No need to do anything yet
}
}
}