Java tutorial
/* * Copyright 2016 John Preston<byhisdeeds@gmail.com> NURAS. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.nuras.mcpha; import java.util.concurrent.ConcurrentHashMap; import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; import java.io.DataInputStream; import java.io.DataOutputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.Map; import java.util.Timer; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.eclipse.jetty.websocket.api.RemoteEndpoint; import org.eclipse.jetty.websocket.api.Session; import org.json.JSONArray; import org.json.JSONObject; import static spark.Spark.*; /** * * @author John Preston<byhisdeeds@gmail.com> */ public class Client { static class ROI { public int start = 0; public int end = 0; public long counts = -1; } public static final long TIMER_FREQ = 125000000L; public static final double TIME_PER_TICK = 1.0 / (double) TIMER_FREQ; private static final int SHIFT_CODE = 56; private static final int SHIFT_CHAN = 52; public static final Long MCPHA_COMMAND_RESET_TIMER = 0L; public static final Long MCPHA_COMMAND_RESET_HISTOGRAM = 1L; public static final Long MCPHA_COMMAND_RESET_OSCILLOSCOPE = 2L; public static final Long MCPHA_COMMAND_RESET_GENERATOR = 3L; public static final Long MCPHA_COMMAND_SET_SAMPLE_RATE = 4L; public static final Long MCPHA_COMMAND_SET_NEGATOR_MODE = 5L; public static final Long MCPHA_COMMAND_SET_BASELINE_MODE = 6L; public static final Long MCPHA_COMMAND_SET_BASELINE_LEVEL = 7L; public static final Long MCPHA_COMMAND_SET_PHA_DELAY = 8L; public static final Long MCPHA_COMMAND_SET_PHA_MIN_THRESHOLD = 9L; public static final Long MCPHA_COMMAND_SET_PHA_MAX_THRESHOLD = 10L; public static final Long MCPHA_COMMAND_SET_TIMER_VALUE = 11L; public static final Long MCPHA_COMMAND_SET_TIMER_MODE = 12L; public static final Long MCPHA_COMMAND_READ_TIMER = 13L; public static final Long MCPHA_COMMAND_READ_HISTOGRAM_DATA = 14L; public static final Long MCPHA_COMMAND_SET_NUMBER_OF_SAMPLES_BEFORE_TRIGGER = 19L; public static final Long MCPHA_COMMAND_SET_TOTAL_NUMBER_OF_SAMPLES_TO_ACQUIRE = 20L; public static final Long MCPHA_COMMAND_START_OSCILLOSCOPE = 21L; public static final Long MCPHA_COMMAND_READ_OSCILLOSCOPE_STATUS = 22L; public static final Long MCPHA_COMMAND_READ_OSCILLOSCOPE_DATA = 23L; // this map is shared between sessions and threads, // so it needs to be thread-safe (http://stackoverflow.com/a/2688817) static Map<Session, String> userUsernameMap = new ConcurrentHashMap<>(); static Socket deviceSocket = null; static IntBuffer histogram_data = null; static boolean acquisition_state_active = false; // Assign to username for next connecting user static int nextUserNumber = 1; static ROI[] rois = new ROI[] { new ROI(), new ROI(), new ROI() }; static AcquisitionUpdateTask acquisitionUpdateTask = null; static Timer acquisitionMonitorTimer = null; static long aquisitionTime = 0L; static boolean debug = false; /** * * @param args * @throws org.apache.commons.cli.ParseException */ public static void main(String[] args) throws ParseException { Option helpOption = Option.builder("h").longOpt("help").required(false).desc("shows this message").build(); Option debugOption = Option.builder("d").longOpt("debug").required(false).desc("show debug messages") .build(); Option wsurlOption = Option.builder("u").longOpt("wsurl").numberOfArgs(1).required(false).type(String.class) .desc("websocket url").build(); Options options = new Options(); options.addOption(helpOption); options.addOption(debugOption); options.addOption(wsurlOption); CommandLineParser parser = new DefaultParser(); CommandLine cmdLine = parser.parse(options, args); if (cmdLine.hasOption("help")) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("mcpha-client", options); } else { debug = cmdLine.hasOption("debug"); String wsurl = cmdLine.hasOption("wsurl") ? ((String) cmdLine.getParsedOptionValue("/wsurl")) : "mcpha"; // staticFiles.externalLocation("/html5"); staticFiles.location("/html5"); //index.html is served at localhost:4567 (default port) staticFiles.expireTime(600); webSocket(wsurl.startsWith("/") ? wsurl : "/" + wsurl, WebsocketHandler.class); init(); } } /** * * @param message */ public static void logDebugMessage(String message) { if (debug) { System.out.println(message); } } /** * * @param user * @param chan * @throws java.io.IOException */ synchronized public static void mcphaStartAcquisition(Session user, int chan) throws IOException { // if device not connected then return if (deviceSocket == null || !deviceSocket.isConnected()) { return; } // start acquisition // device inactive mcphaSetAquisitionState(user, chan, 1L); if (acquisitionUpdateTask != null) { acquisitionUpdateTask.cancel(); } // inistantiate new task acquisitionUpdateTask = new AcquisitionUpdateTask(user, chan, 1000); // cancel any existing timer tasks. if (acquisitionMonitorTimer != null) { acquisitionMonitorTimer.cancel(); } // create new timer task acquisitionMonitorTimer = new Timer(); acquisitionMonitorTimer.schedule(acquisitionUpdateTask, 0); } /** * * @param user * @param chan * @throws java.io.IOException */ public static void mcphaStopAcquisition(Session user, int chan) throws IOException { // if device not connected then return if (deviceSocket == null || !deviceSocket.isConnected()) { return; } if (acquisitionUpdateTask != null) { acquisitionUpdateTask.exitLoop(); } else { // stop data acquisition // device inactive mcphaSetAquisitionState(user, chan, 0L); } // cancel any existing timer tasks. if (acquisitionMonitorTimer != null) { acquisitionMonitorTimer.cancel(); } // help out garbage collector acquisitionUpdateTask = null; acquisitionMonitorTimer = null; } /** * Sends a message from one user to all users, along with a list * of current usernames * * @param sender * @param message */ public static void broadcastMessage(String sender, String message) { userUsernameMap.keySet().stream().filter(Session::isOpen).forEach(session -> { try { // session.getRemote().sendString(String.valueOf(new JSONObject() // .put("userMessage", createHtmlMessageFromSender(sender, message)) // .put("userlist", userUsernameMap.values()) // )); session.getRemote().sendString("sender=" + sender + ", message=" + message); } catch (IOException e) { e.printStackTrace(); } }); } /** * * @param dest * @param json * @throws IOException */ public static void sendJSONObjectMessage(RemoteEndpoint dest, JSONObject json) throws IOException { sendJSONTextMessage(dest, json.toString()); } /** * * @param dest * @param json * @throws IOException */ public static void sendJSONTextMessage(RemoteEndpoint dest, String json) throws IOException { logDebugMessage("PUSH_MESSAGE:" + json); dest.sendString(json); } /** * * @return */ private static JSONObject createJSONResponseObject() { JSONObject json = new JSONObject(); json.put("type", "resp"); return json; } /** * * @param user * @param deviceip * @param port */ synchronized public static void connectToDevice(Session user, String deviceip, int port) { try { if (deviceSocket == null || !deviceSocket.isConnected()) { deviceSocket = new Socket(); deviceSocket.connect(new InetSocketAddress(deviceip, port), 8000); deviceSocket.setSoTimeout(60000); } JSONObject json = createJSONResponseObject(); json.put("command", "connect"); json.put("message", "Connection established"); json.put("status", 0); sendJSONObjectMessage(user.getRemote(), json); // initialise device mcphaSetSampleRate(4L); mcphaSetPhaDelay(0L, 100L); mcphaSetPhaMinThreshold(0L, 300L); mcphaSetPhaMaxThreshold(0L, 16300L); mcphaSetNegatorMode(0L, 0L); mcphaSetNegatorMode(1L, 0L); // mcphaResetHistogram(0); // get histgram data getHistogramData(user, 0); } catch (IOException ex) { if (deviceSocket != null) { try { deviceSocket.close(); } catch (IOException ex1) { Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex1); } } deviceSocket = null; try { JSONObject json = createJSONResponseObject(); json.put("command", "connect"); json.put("message", ex.toString()); json.put("status", 1); sendJSONObjectMessage(user.getRemote(), json); } catch (IOException ex1) { Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex1); } } } /** * Close socket and disconnect from Red Pitaya device. * * @param user * @throws java.io.IOException */ synchronized public static void disconnectFromDevice(Session user) throws IOException { JSONObject json = createJSONResponseObject(); json.put("command", "disconnect"); json.put("status", 0); if (deviceSocket == null) { json.put("message", "Nothing to do. Device not connected"); } else { deviceSocket.close(); deviceSocket = null; json.put("message", "Device disconnected"); } sendJSONObjectMessage(user.getRemote(), json); } /** * Start/Stop data acquisition of the connected device. When state is 0 * then the acquisition is stopped, and when it is 1 then it is active. * * @param user * @param chan * @param state * @throws java.io.IOException */ synchronized public static void mcphaSetAquisitionState(Session user, int chan, long state) throws IOException { if (deviceSocket != null && deviceSocket.isConnected()) { mcphaSetTimerMode(chan, state); acquisition_state_active = state == 1; JSONObject json = createJSONResponseObject(); json.put("command", "set_acquisition_state"); json.put("message", ""); json.put("state", acquisition_state_active ? "active" : "inactive"); json.put("status", 0); sendJSONObjectMessage(user.getRemote(), json); } } /** * * @param user * @throws java.io.IOException */ synchronized public static void mcphaGetAquisitionState(Session user) throws IOException { if (deviceSocket != null) { JSONObject json = createJSONResponseObject(); json.put("command", "get_acquisition_state"); json.put("message", ""); json.put("state", acquisition_state_active ? "active" : "inactive"); json.put("status", 0); sendJSONObjectMessage(user.getRemote(), json); } } /** * * @param user * @param chan * @return timer value * @throws IOException */ synchronized public static double getHistogramData(Session user, long chan) throws IOException { // get elapsed time double t = mcphaGetTimerValue(chan); // get histogram data histogram_data = mcphaGetHistogramData(chan); // push data JSONObject json = createJSONResponseObject(); json.put("command", "get_histogram_data"); json.put("message", ""); json.put("status", 0); json.put("timer", String.format("%.2f", t)); json.put("label", "histogram"); JSONArray arr = new JSONArray(); for (int i = 0; i < histogram_data.capacity(); i++) { JSONArray xy = new JSONArray(); xy.put(i).put(histogram_data.get(i)); arr.put(xy); } json.put("data", arr); sendJSONObjectMessage(user.getRemote(), json); // if ROI's have been defined then we push their data for (int i = 1; i <= 3; i++) { getRoiData(user, i); } return t; } /** * * @param user * @param chan * @throws IOException */ synchronized public static void clearSpectrumData(Session user, long chan) throws IOException { mcphaResetHistogram(chan); // get histogram data getHistogramData(user, chan); } /** * * @param user * @param roi * @throws java.io.IOException */ synchronized public static void getRoiData(Session user, int roi) throws IOException { if (histogram_data != null && rois[roi - 1].counts != -1) { long counts = 0; JSONArray data = new JSONArray(); for (int i = rois[roi - 1].start; i <= rois[roi - 1].end; i++) { JSONArray o = new JSONArray(); int y = histogram_data.get(i); o.put(i).put(y); data.put(o); counts += y; } JSONObject json = new JSONObject(); json.put("label", "ROI #" + roi); json.put("counts", counts); json.put("start", rois[roi - 1].start); json.put("end", rois[roi - 1].end); json.put("data", data); json.put("roi", roi); json.put("command", "get_roi_data"); json.put("message", ""); json.put("status", 0); sendJSONObjectMessage(user.getRemote(), json); } } /** * * @param user * @param roi * @param start * @param end * @throws IOException */ synchronized public static void mcphaSetRoi(Session user, int roi, int start, int end) throws IOException { ROI r = null; if (roi < 1 || roi > 3) { JSONObject json = createJSONResponseObject(); json.put("command", "set_roi"); json.put("message", String.format("ROI number [%d] outside range of 1 to 3.", roi)); json.put("status", 1); sendJSONObjectMessage(user.getRemote(), json); } rois[roi - 1].start = start; rois[roi - 1].end = end; rois[roi - 1].counts = 0L; getRoiData(user, roi); // if (json != null) // { // JSONObject o = createJSONResponseObject(); // o.put("command", "get_roi_data"); // o.put("message", ""); // o.put("roi"+roi, json); // o.put("status", 0); // sendJSONObjectMessage(user.getRemote(), o); // } } /** * * @param user * @param channels * @param trigger_mode * @param trigger_level * @param trigger_slope * @param trigger_source * @throws IOException */ synchronized public static void acquireOscilloscopeData(Session user, int channels, String trigger_mode, int trigger_level, String trigger_slope, int trigger_source) throws IOException { mcphaResetOscilloscope(); // Set number of samples to skip before trigger mcphaSetNumberOfSamplesBeforeTrigger(5000); // Set total number of samples to acquire for this run mcphaSetTotalNumberOfSamplesToAcquire(65536); // Start oscilloscope mcphaStartOscilloscope(); // wait for 200 milliseconds try { Thread.sleep(200); } catch (InterruptedException ex) { Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex); } // Read oscilloscope status for (int i = 0; i < 5; i++) { try { Thread.sleep(100); } catch (InterruptedException ex) { Logger.getLogger(Client.class.getName()).log(Level.SEVERE, null, ex); } System.out.println("Oscilloscope status:" + mcphaReadOscilloscopeStatus()); } // get oscillsocope data ShortBuffer data = mcphaGetOsilloscopeData(); boolean channel_1_requested = (channels & 0x01) != 0; boolean channel_2_requested = (channels & 0x02) != 0; // push data JSONObject json = createJSONResponseObject(); json.put("command", "get_oscilloscope_data"); json.put("message", ""); json.put("status", 0); JSONArray arr1 = new JSONArray(); JSONArray arr2 = new JSONArray(); data.rewind(); for (int i = 0, n1 = 0, n2 = 0; i < data.capacity(); i += 2) { // channel 1 data JSONArray xy = new JSONArray(); short d = data.get(); if (channel_1_requested) { arr1.put(xy.put(n1++).put(d)); } // channel 2 data xy = new JSONArray(); d = data.get(); if (channel_2_requested) { arr2.put(xy.put(n2++).put(d)); } } json.put("data1", arr1); json.put("label1", channel_1_requested ? "Channel 1" : ""); json.put("data2", arr2); json.put("label2", channel_2_requested ? "Channel 2" : ""); sendJSONObjectMessage(user.getRemote(), json); } /** * Set sample rate * * @param rate * @throws java.io.IOException */ synchronized public static void mcphaSetSampleRate(long rate) throws IOException { if (rate < 4) { rate = 4; } sendCommand(MCPHA_COMMAND_SET_SAMPLE_RATE, 0L, rate); } /** * Reset histogram * * @param chan * @throws java.io.IOException */ synchronized public static void mcphaResetHistogram(long chan) throws IOException { sendCommand(MCPHA_COMMAND_RESET_HISTOGRAM, chan, 0L); } /** * Reset oscilloscope * * @throws java.io.IOException */ synchronized public static void mcphaResetOscilloscope() throws IOException { sendCommand(MCPHA_COMMAND_RESET_OSCILLOSCOPE, 0L, 0L); } /** * Reset generator * * @throws java.io.IOException */ synchronized public static void mcphaResetGenerator() throws IOException { sendCommand(MCPHA_COMMAND_RESET_GENERATOR, 0L, 0L); } /** * Set negator mode, 0 for disabled and 1 for enabled * * @param chan * @param mode * @throws java.io.IOException */ synchronized public static void mcphaSetNegatorMode(long chan, long mode) throws IOException { sendCommand(MCPHA_COMMAND_SET_NEGATOR_MODE, chan, mode); } /** * Set baseline mode, 0 for none and 1 for auto * * @param chan * @param mode * @throws java.io.IOException */ synchronized public static void mcphaSetBaselineMode(long chan, long mode) throws IOException { sendCommand(MCPHA_COMMAND_SET_BASELINE_MODE, chan, mode); } /** * Set baseline level * * @param chan * @param level * @throws java.io.IOException */ synchronized public static void mcphaSetBaselineLevel(long chan, long level) throws IOException { sendCommand(MCPHA_COMMAND_SET_BASELINE_LEVEL, chan, level); } /** * Set PHA delay * * @param chan * @param delay * @throws java.io.IOException */ synchronized public static void mcphaSetPhaDelay(long chan, long delay) throws IOException { sendCommand(MCPHA_COMMAND_SET_PHA_DELAY, chan, delay); } /** * Set PHA min threshold * * @param chan * @param threshold * @throws java.io.IOException */ synchronized public static void mcphaSetPhaMinThreshold(long chan, long threshold) throws IOException { sendCommand(MCPHA_COMMAND_SET_PHA_MIN_THRESHOLD, chan, threshold); } /** * Set PHA max threshold * * @param chan * @param threshold * @throws java.io.IOException */ synchronized public static void mcphaSetPhaMaxThreshold(long chan, long threshold) throws IOException { sendCommand(MCPHA_COMMAND_SET_PHA_MAX_THRESHOLD, chan, threshold); } /** * Reset timer * * @param chan * @throws java.io.IOException */ synchronized public static void mcphaResetTimer(long chan) throws IOException { sendCommand(MCPHA_COMMAND_RESET_TIMER, chan, 0L); } /** * Set timer value * * @param chan * @param value * @throws java.io.IOException */ synchronized public static void mcphaSetTimerValue(long chan, long value) throws IOException { sendCommand(MCPHA_COMMAND_SET_TIMER_VALUE, chan, value); } /** * Set timer mode, 0 for stop and 1 for running * * @param chan * @param mode * @throws java.io.IOException */ synchronized public static void mcphaSetTimerMode(long chan, long mode) throws IOException { sendCommand(MCPHA_COMMAND_SET_TIMER_MODE, chan, mode); } /** * Get timer value in seconds which is derived from the 64-bit unsigned * integer value returned from the server, that is the number of counts * at 125MHz from the start of the acquisition. * * @param chan * @return the timer value in seconds since the start of acquisition * @throws java.io.IOException */ synchronized public static double mcphaGetTimerValue(long chan) throws IOException { sendCommand(MCPHA_COMMAND_READ_TIMER, chan, 0L); // read response DataInputStream in = new DataInputStream(deviceSocket.getInputStream()); long number = Long.reverseBytes(in.readLong()); return (double) number * TIME_PER_TICK; } /** * Set number of samples to skip before triggering. * * @param samples * @throws java.io.IOException */ synchronized public static void mcphaSetNumberOfSamplesBeforeTrigger(long samples) throws IOException { sendCommand(MCPHA_COMMAND_SET_NUMBER_OF_SAMPLES_BEFORE_TRIGGER, 0L, samples); } /** * Set total number of samples to acquire. * * @param samples * @throws java.io.IOException */ synchronized public static void mcphaSetTotalNumberOfSamplesToAcquire(long samples) throws IOException { sendCommand(MCPHA_COMMAND_SET_TOTAL_NUMBER_OF_SAMPLES_TO_ACQUIRE, 0L, samples); } /** * Start oscilloscope. * * @throws java.io.IOException */ synchronized public static void mcphaStartOscilloscope() throws IOException { sendCommand(MCPHA_COMMAND_START_OSCILLOSCOPE, 0L, 0L); } /** * Return the status of the oscilloscope. * * @return the status of the oscilloscope * @throws java.io.IOException */ synchronized public static int mcphaReadOscilloscopeStatus() throws IOException { sendCommand(MCPHA_COMMAND_READ_OSCILLOSCOPE_STATUS, 0L, 4L); // read response DataInputStream in = new DataInputStream(deviceSocket.getInputStream()); return Integer.reverseBytes(in.readInt()); } /** * Get histogram data * * @param chan * @return * @throws java.io.IOException */ synchronized public static IntBuffer mcphaGetHistogramData(long chan) throws IOException { sendCommand(MCPHA_COMMAND_READ_HISTOGRAM_DATA, chan, 0); DataInputStream in = new DataInputStream(deviceSocket.getInputStream()); ByteBuffer data = ByteBuffer.allocate(65536); data.order(ByteOrder.nativeOrder()); in.readFully(data.array()); return data.asIntBuffer(); } /** * Get oscilloscope data which are 16-bit signed integer values. * The channels are interleaved sample-by-sample (ch1, ch2, ch1, ch2, etc). * * @return a ShortBuffer of channel data values. * @throws java.io.IOException */ synchronized public static ShortBuffer mcphaGetOsilloscopeData() throws IOException { sendCommand(MCPHA_COMMAND_READ_OSCILLOSCOPE_DATA, 0L, 0L); DataInputStream in = new DataInputStream(deviceSocket.getInputStream()); ByteBuffer data = ByteBuffer.allocate(65536); data.order(ByteOrder.nativeOrder()); in.readFully(data.array()); return data.asShortBuffer(); } /** * Send command to device * * @param code * @param chan * @param data */ private static void sendCommand(long code, long chan, long data) throws IOException { logDebugMessage("sendCommand - code=" + code + ", chan=" + chan + ", data=" + data); DataOutputStream out = new DataOutputStream(deviceSocket.getOutputStream()); long b = (long) (code << SHIFT_CODE) | (validateChannel(chan) << SHIFT_CHAN) | data; out.writeLong(Long.reverseBytes(b)); } /** * * @param chan * @return */ private static long validateChannel(long chan) { if (chan < 0) { return 0; } else if (chan > 1) { return 1; } return chan; } }