updates to PAMGuard command line options and UDP control (#25)

* Fix network sender since it was hopelessly out of date and did not send
data in the correct format. OK now, though only tested on NARW.

* Mods to support command line and Network control of PAMGuard and to
retrieve summary information from some modules.
This commit is contained in:
Douglas Gillespie 2022-03-18 14:52:47 +00:00 committed by GitHub
parent f48579ff22
commit 8d6aeb735a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 399 additions and 11 deletions

View File

@ -70,6 +70,7 @@ import PamController.PamSettings;
import PamModel.PamModel;
import PamModel.SMRUEnable;
import PamUtils.FrequencyFormat;
import PamUtils.PamCalendar;
import PamUtils.PlatformInfo;
import PamUtils.PlatformInfo.OSType;
import PamView.MenuItemEnabler;
@ -776,9 +777,11 @@ public class AcquisitionControl extends PamControlledUnit implements PamSettings
return null;
}
@Override
public String getModuleSummary() {
/**
* Get a summary of the daq settings for the QA module.
* @return summary of DAQ settings.
*/
public String getDaqSummary() {
DaqSystem daqSys = findDaqSystem(null);
if (daqSys == null) {
return String.format("%s - currently unavailable", acquisitionParameters.daqSystemType);
@ -794,9 +797,41 @@ public class AcquisitionControl extends PamControlledUnit implements PamSettings
return str;
}
@Override
public String tellModule(String command) {
/**
* Get timing summary and return as a string.
*/
switch (command) {
case "gettimeinfo":
return getTimeInfoString();
}
return super.tellModule(command);
}
/**
* Get a summary of time information as to what's going on in the DAQ
* @return time summary
*/
private String getTimeInfoString() {
/*
*
sprintf(returned,"%s:%lld,%lld,%ld,%lld",getModuleName(),calendar->getRawStartTime(),
calendar->getMillisecondTime(),(int)daqProcess->getSampleRate(),acquiredSamples);
*/
return String.format("%s:%d,%d,%d,%d", getUnitName(), PamCalendar.getSessionStartTime(), PamCalendar.getTimeInMillis(),
(int) acquisitionProcess.getSampleRate(), acquisitionProcess.getTotalSamples(0));
}
@Override
public void pamHasStopped() {
acquisitionProcess.pamHasStopped();
}
@Override
public String getModuleSummary(boolean clear) {
return getDaqProcess().getRawDataBlock().getSummaryString(clear);
}
}

View File

@ -697,11 +697,33 @@ public abstract class PamControlledUnit implements SettingsNameProvider {
}
/**
* Get a module summary text string for shorthand output to
* summary QC systems.
* Get a module summary text string for shorthand output to anyting wanting a
* short summary of data state / numbers of detections. <br> You should not
* override this version of the function, but instead override getModuleSummary(boolean clear)
* which allows for optional clearing of summary data.
* @return module summary string - goings on since the last call to this function
*/
public String getModuleSummary() {
return getModuleSummary(true);
}
/**
* Get a module summary text string for shorthand output to anyting wanting a
* short summary of data state / numbers of detections.
* @param clear clear data after generating string, so that counts of detections, etc. start again from 0.
* @return module summary string - goings on since the last call to this function
*/
public String getModuleSummary(boolean clear) {
return null;
}
/**
* Handle a module specific command sent by the
* tellmodule command.
* @param command command line (stripped of the command and the module type and name)
* @return response to command
*/
public String tellModule(String command) {
return null;
}

View File

@ -911,6 +911,29 @@ public class PamController implements PamControllerInterface, PamSettings {
return l;
}
/**
* Get a list of PamControlledUnit units of a given type and name, allowing for nulls.
* @param unitType Controlled unit type, can be null for all units of name
* @param unitName Controlled unit name, can be null for all units of type
* @return list of units.
*/
public ArrayList<PamControlledUnit> findControlledUnits(String unitType, String unitName) {
ArrayList<PamControlledUnit> l = new ArrayList<PamControlledUnit>();
int n = getNumControlledUnits();
PamControlledUnit pcu;
for (int i = 0; i < n; i++) {
pcu = getControlledUnit(i);
if (unitType != null && !unitType.equals(pcu.getUnitType())) {
continue;
}
if (unitName != null && !unitName.equals(pcu.getUnitName())) {
continue;
}
l.add(pcu);
}
return l;
}
@Override
public PamControlledUnit findControlledUnit(String unitType, String unitName) {

View File

@ -32,6 +32,8 @@ public abstract class CommandManager extends PamControlledUnit {
commandsList.add(new PingCommand());
commandsList.add(new StatusCommand());
commandsList.add(new SummaryCommand());
commandsList.add(new SummaryPeekCommand());
commandsList.add(new TellModuleCommand());
commandsList.add(new ExitCommand());
commandsList.add(new KillCommand());
commandsList.add(new HelpCommand(this));
@ -120,6 +122,34 @@ public abstract class CommandManager extends PamControlledUnit {
}
return sbits;
}
/**
* Get the command string left from the given index. This can be used to get
* everything left in the string that follows the command name, module type and module name
* fields for example.
* @param command full command string
* @param index how many items to skip
* @return remainder of the string, warts n all.
*/
public static String getCommandFromIndex(String command, int index) {
if (command == null) {
return null;
}
int startCharIndex = 0;
int foundBits = 0;
Matcher m = Pattern.compile("([^\"]\\S*|\".+?\")\\s*").matcher(command);
List<String> bits = new ArrayList<>();
while (m.find() && foundBits++ < index) {
String bit = m.group(1);
startCharIndex = command.indexOf(bit, startCharIndex) + bit.length();
if (startCharIndex >= command.length()) {
return null;
}
}
String wanted = command.substring(startCharIndex+1).trim();
return wanted;
}
/**
* Reply to data called from InterpredData

View File

@ -1,9 +1,13 @@
package PamController.command;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.SwingUtilities;
import PamController.PamControlledUnit;
import PamController.PamController;
public abstract class ExtCommand {
private String name;
@ -121,4 +125,21 @@ public abstract class ExtCommand {
public String getHint() {
return null;
};
/**
* Find a set of controlled units, allowing for wild cards or nulls.
* @param unitType unit type, can be null or * for wildcards
* @param unitName unit name, can be null or * for wildcards
* @return
*/
public ArrayList<PamControlledUnit> findControlledUnits(String unitType, String unitName) {
if (unitType != null && unitType.equals("*")) {
unitType = null;
}
if (unitName != null && unitName.equals("*")) {
unitName = null;
}
PamController pc = PamController.getInstance();
return pc.findControlledUnits(unitType, unitName);
}
}

View File

@ -43,6 +43,8 @@ public class NetworkController extends CommandManager {
private byte[] byteBuffer = new byte[MAX_COMMAND_LENGTH];
private NetworkRecorderTrigger[] recorderTriggers;
private static NetworkController singleInstance;
private static String unitName = "Network Controller";
@ -53,6 +55,8 @@ public class NetworkController extends CommandManager {
listenerThread = new ListenerThread();
Thread aThread = new Thread(listenerThread);
aThread.start();
singleInstance = this;
}
class ListenerThread implements Runnable {

View File

@ -21,10 +21,20 @@ public class SummaryCommand extends ExtCommand {
@Override
public String execute(String command) {
return getReturnString();
// String[] cmdBits = CommandManager.splitCommandLine(command);
// boolean clear = true;
// // first word is the command, we want one after that.
// if (cmdBits.length >= 2 && cmdBits[1] != null) {
// String bit = cmdBits[1].trim().toLowerCase();
// if (bit.equals("0") || bit.equals("false")) {
// clear = false;
// }
//
// }
return getModulesSummary(true);
}
public String getReturnString() {
public String getModulesSummary(boolean clear) {
PamController pamController = PamController.getInstance();
int nMod = pamController.getNumControlledUnits();
PamControlledUnit aModule;
@ -38,7 +48,7 @@ public class SummaryCommand extends ExtCommand {
int usedModules = 0;
for (int i = 0; i < nMod; i++) {
aModule = pamController.getControlledUnit(i);
aString = aModule.getModuleSummary();
aString = aModule.getModuleSummary(clear);
if (aString == null) {
continue;
}

View File

@ -0,0 +1,19 @@
package PamController.command;
public class SummaryPeekCommand extends SummaryCommand {
public SummaryPeekCommand() {
super();
setName("summarypeek");
}
@Override
public String execute(String command) {
return getModulesSummary(false);
}
@Override
public String getHint() {
return "Get summary information about each running process, don't clear data";
}
}

View File

@ -0,0 +1,46 @@
package PamController.command;
import java.util.ArrayList;
import PamController.PamControlledUnit;
import PamController.PamSettingManager;
public class TellModuleCommand extends ExtCommand {
public TellModuleCommand() {
super("tellmodule", true);
// TODO Auto-generated constructor stub
}
@Override
public String execute(String command) {
String[] commandWords = CommandManager.splitCommandLine(command);
if (commandWords.length < 3) {
return "Unspecified modules for tellmodule. Command must be followed by module name and type";
}
PamSettingManager settingsManager = PamSettingManager.getInstance();
String unitType = commandWords[1];
String unitName = commandWords[2];
ArrayList<PamControlledUnit> pamControlledUnits = findControlledUnits(unitType, unitName);
if (pamControlledUnits.size() == 0) {
return "tellmodule: no modules found for command " + command;
}
String commandPart = CommandManager.getCommandFromIndex(command, 3);
if (commandPart == null) {
return "tellmodule: No command. Must have a module type, module name and the thing to send to modules";
}
String retStr = "";
for (int i = 0; i < pamControlledUnits.size(); i++) {
if (i > 0) {
retStr += ";";
}
retStr += pamControlledUnits.get(i).tellModule(commandPart);
}
return retStr;
}
@Override
public String getHint() {
return "Send a command to a module.";
}
}

View File

@ -700,8 +700,11 @@ public class PamDetectionOverlayGraphics extends PanelOverlayDraw {
pamDetection.getSampleDuration() * 1000./parentDataBlock.getSampleRate(),
frequency[0], 0);
boolean isWrapped = false;
if (botRight.x < topLeft.x){
botRight.x = g.getClipBounds().width;
// this means it's wrapped.
isWrapped = true;
botRight.x += g.getClipBounds().width;
}
if (generalProjector.isViewer()) {
Coordinate3d middle = new Coordinate3d();
@ -723,8 +726,13 @@ public class PamDetectionOverlayGraphics extends PanelOverlayDraw {
// Not actually drawing on a spectrogramProjector, so don't have any info on the background color
}
if (isWrapped) {
g.drawRect((int) topLeft.x-g.getClipBounds().width, (int) topLeft.y,
(int) botRight.x - (int) topLeft.x, (int) botRight.y - (int) topLeft.y);
}
g.drawRect((int) topLeft.x, (int) topLeft.y,
(int) botRight.x - (int) topLeft.x, (int) botRight.y - (int) topLeft.y);
return new Rectangle((int) topLeft.x, (int) topLeft.y,
(int) botRight.x - (int) topLeft.x, (int) botRight.y - (int) topLeft.y);
}

View File

@ -50,6 +50,11 @@ public class PamRawDataBlock extends AcousticDataBlock<RawDataUnit> {
private long desiredSample = -1;
private long[] prevChannelSample = new long[PamConstants.MAX_CHANNELS];
private double[] summaryTotals = new double[PamConstants.MAX_CHANNELS];
private double[] summaryTotals2 = new double[PamConstants.MAX_CHANNELS];
private double[] summaryMaxVal = new double[PamConstants.MAX_CHANNELS];
private int[] summaryCount = new int[PamConstants.MAX_CHANNELS];
/**
* Keep a record of the last sample added.
@ -167,10 +172,12 @@ public class PamRawDataBlock extends AcousticDataBlock<RawDataUnit> {
prevChannelSample[thisChannel] = pamDataUnit.getStartSample();
// System.out.println(String.format("Sample %d channel %d in %s is in synch - expected sample %d",
// pamDataUnit.getStartSample(), thisChannel, getDataName(), desiredSample));
addSummaryData(thisChannel, pamDataUnit);
super.addPamData(pamDataUnit);
}
/**
* Get available data from the raw data block. Similar to the functionality of
* getSamplesforMillis, but this will not throw an exception if not all of the samples
@ -517,6 +524,66 @@ public class PamRawDataBlock extends AcousticDataBlock<RawDataUnit> {
super.addObserver(o, reThread);
}
private void addSummaryData(int channel, RawDataUnit pamDataUnit) {
double[] data = pamDataUnit.getRawData();
synchronized(summaryTotals) {
double tot = 0, tot2 = 0, mx = 0;
for (int i = 0; i < data.length; i++) {
double dat = data[i];
tot += dat;
tot2 += dat*dat;
mx = Math.max(mx, dat);
}
summaryTotals[channel] += tot/data.length;
summaryTotals2[channel] += tot2/data.length;
summaryMaxVal[channel] = Math.max(summaryMaxVal[channel], mx);
summaryCount[channel] ++;
}
}
public String getSummaryString(boolean clear) {
/*
*
sumString[0] = 0;
float rms, max, mean;
for (int i = 0; i < ringBufferChannels; i++) {
// rms = sqrt(summaryTotals[i]/summaryCount);
rms = sqrt(summaryTotals2[i]/summaryCount) + 0.01;
max = (double)summaryMaxVal[i] + 0.01;
mean = summaryTotals[i]/summaryCount;
// sprintf(sumString+strlen(sumString), "ch%d,%3.1f,%3.1f,", i, 20.*log10((double)summaryMaxVal[i]/32768.),
// 20*log10(rms/32768.));
sprintf(sumString+strlen(sumString), "ch%d,%3.1f,%3.1f,%3.1f,", i, mean, 20.*log10(max/32768.),
20.*log10(rms/32768.));
}
*/
String str = "";
int nChan = PamUtils.getNumChannels(getChannelMap());
synchronized(summaryTotals) {
for (int i = 0; i < nChan; i++) {
double rms = Math.sqrt(summaryTotals2[i]/summaryCount[i])+1e-6;
double max = summaryMaxVal[i]+1e-6;
double mean = summaryTotals[i]/summaryCount[i];
str += String.format("ch%d,%3.1f,%3.1f,%3.1f,", i, mean, 20.*Math.log10(max),
20.*Math.log10(rms));
}
}
if (clear) {
clearSummaryData();
}
return str;
}
public void clearSummaryData() {
synchronized(summaryTotals) {
for (int i = 0; i < summaryTotals.length; i++) {
summaryTotals[i] = summaryTotals2[i] = summaryMaxVal[i] = 0;
summaryCount[i] = 0;
}
}
}
// @Override
// protected void findParentSource() {
// super.findParentSource();

View File

@ -147,4 +147,10 @@ public class RWEControl extends PamControlledUnit implements PamSettings {
return rweParameters;
}
@Override
public String getModuleSummary(boolean clear) {
return rweProcess.getModuleSummary(clear);
}
}

View File

@ -154,6 +154,8 @@ public class RWEProcess extends PamProcess {
private int minSoundType;
private RWClassifier classifier = new RWStandardClassifier();
private int[] soundCounts = new int[RWStandardClassifier.MAXSOUNDTYPE+1];
public RWEChannelProcess(RWEProcess rweProcess, int iChannel) {
this.rweProcess = rweProcess;
this.iChannel = iChannel;
@ -216,11 +218,19 @@ public class RWEProcess extends PamProcess {
f[1] = aSound.maxFreq * getSampleRate()/sourceDataBlock.getFftLength();
rweDataUnit.setFrequency(f);
rweDataBlock.addPamData(rweDataUnit);
soundCounts[rweDataUnit.rweSound.soundType]++;
// System.out.printf("RW %d at %s\n", rweDataUnit.rweSound.soundType, PamCalendar.formatTime(rweDataUnit.getTimeMilliseconds()));
}
}
}
/**
* Clear sound counts
*/
private void clearSoundCounts() {
Arrays.fill(soundCounts, 0);
}
private RWEDetectionPeak[] findPeaks(ComplexArray complexArray) {
RWEDetectionPeak[] detectedPeaks = null;
int nPeaks = 0;
@ -481,6 +491,24 @@ public class RWEProcess extends PamProcess {
// }
}
public String getModuleSummary(boolean clear) {
String str = String.format("%d", RWStandardClassifier.MAXSOUNDTYPE+1);
for (int c = 0; c < rweChannelProcesses.length; c++) {
if (rweChannelProcesses[c] == null) {
continue;
}
str += String.format("ch%d", c);
int[] counts = rweChannelProcesses[c].soundCounts;
for (int i = 0; i < counts.length; i++) {
str += String.format(",%d", counts[i]);
}
if (clear) {
rweChannelProcesses[c].clearSoundCounts();
}
}
return str;
}
// private void recalculateAllAngles() {
// synchronized(rweDataBlock.getSynchLock()) {
// ListIterator<RWEDataUnit> it = rweDataBlock.getListIterator(0);

View File

@ -3,6 +3,7 @@ package SoundRecorder;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Calendar;
@ -22,6 +23,7 @@ import PamController.PamController;
import PamController.PamControllerInterface;
import PamController.PamSettingManager;
import PamController.PamSettings;
import PamController.command.CommandManager;
import PamUtils.PamCalendar;
import PamUtils.PamUtils;
import PamView.MenuItemEnabler;
@ -563,4 +565,71 @@ public class RecorderControl extends PamControlledUnit implements PamSettings {
return backupInformation;
}
@Override
public String tellModule(String command) {
String[] parts = command.split(" ");
if (parts.length < 1) {
return "Unknown command to sound recorder: " + command;
}
switch(parts[0]) {
case "start":
buttonCommand(RecorderView.BUTTON_START);
break;
case "startbuffered":
buttonCommand(RecorderView.BUTTON_START_BUFFERED);
break;
case "stop":
buttonCommand(RecorderView.BUTTON_OFF);
break;
case "cycle":
buttonCommand(RecorderView.BUTTON_AUTO);
break;
case "outputfolder":
return setOutputFolder(command);
default:
return "Unknown command to sound recorder: " + command;
}
return getUnitName() + " executed " + command;
}
/**
* set output folder. Trim off the command first.
* @param command
* @return
*/
private String setOutputFolder(String command) {
String[] parts = CommandManager.splitCommandLine(command);
if (parts.length < 2) {
return "Unspecified output folder for sound recorder";
}
File path = new File(parts[1].trim());
if (path.exists() == false) {
path.mkdirs();
}
if (path.isDirectory()) {
recorderSettings.outputFolder = path.getAbsolutePath();
return getUnitName() + "storing recordings in " + path.getAbsolutePath();
}
else {
return getUnitName() + "unable to switch to storage folder " + path.getAbsolutePath();
}
}
@Override
public String getModuleSummary(boolean clear) {
File path = new File(recorderSettings.outputFolder);
long space = -1;
double freeSpace = -1;
try {
space = path.getFreeSpace();
freeSpace = (double) space / 1048576.;
}
catch (SecurityException e) {
freeSpace = -9999;
}
int currButton = pressedButton;
int currState = recorderStatus;
return String.format("%d,%d,%3.1f", currButton, currState, freeSpace);
}
}

View File

@ -47,7 +47,7 @@ public class QASystemReport {
section.addSectionText("Configuration error: No acqusition system found");
}
else {
section.addSectionText("Acquisition System: " + daqControl.getModuleSummary());
section.addSectionText("Acquisition System: " + daqControl.getDaqSummary());
}
report.addSection(section);
report.addSection(makeArraySection(ArrayManager.getArrayManager().getCurrentArray(), daqControl));