[no commit message]

This commit is contained in:
darksnake 2016-04-14 12:45:41 +03:00
parent 2e0cbaee97
commit 0c0d9f2aa4
15 changed files with 182 additions and 71 deletions

View File

@ -1,6 +1,6 @@
apply plugin: 'application'
version = "0.2.3"
version = "0.2.4"
if (!hasProperty('mainClass')) {
ext.mainClass = 'inr.numass.control.msp.fx.MspApp'

View File

@ -18,6 +18,7 @@ package inr.numass.control.msp;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.control.devices.SingleMeasurementDevice;
import hep.dataforge.control.devices.annotations.RoleDef;
import hep.dataforge.control.measurements.AbstractMeasurement;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.ports.PortHandler;
@ -48,6 +49,7 @@ import java.util.function.Consumer;
*
* @author Alexander Nozik
*/
@RoleDef(name = Roles.STORAGE_ROLE)
public class MspDevice extends SingleMeasurementDevice implements PortHandler.PortController {
// private static final String PEAK_SET_PATH = "peakJump.peak";
@ -64,7 +66,6 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
// public MspDevice(String name, Context context, Meta config) {
// super(name, context, config);
// }
@Override
public void init() throws ControlException {
super.init();
@ -126,7 +127,7 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
@Override
public void command(String commandName, Value argument) throws ControlException {
switch(commandName){
switch (commandName) {
case "connect":
setConnected(argument.booleanValue());
case "setFilamentOn":
@ -139,6 +140,9 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
/**
* Startup MSP: get available sensors, select sensor and control.
*
* @param connected
* @return
* @throws hep.dataforge.exceptions.ControlException
*/
public boolean setConnected(boolean connected) throws ControlException {
String sensorName;
@ -216,7 +220,7 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
/**
* Send specific command and wait for its results (the result must begin
with command name)
* with command name)
*
* @param commandName
* @param paremeters
@ -393,7 +397,7 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
Format format = builder.build();
//TODO Переделать!!!
String run = meta().getString("numass.run", "");
String run = meta().getString("storage.run", "");
String suffix = Integer.toString((int) Instant.now().toEpochMilli());
PointLoader loader = LoaderFactory
@ -423,6 +427,7 @@ public class MspDevice extends SingleMeasurementDevice implements PortHandler.Po
throw new ControlException("Can't add mass to measurement measurement for msp");
}
}
prepareLoaders();
} else {
throw new ControlException("Can't create measurement for msp");
}

View File

@ -17,6 +17,8 @@ package inr.numass.control.msp.fx;
import hep.dataforge.context.Context;
import hep.dataforge.context.GlobalContext;
import hep.dataforge.control.connections.Roles;
import hep.dataforge.control.connections.StorageConnection;
import hep.dataforge.points.MapPoint;
import hep.dataforge.exceptions.ControlException;
import hep.dataforge.exceptions.PortException;
@ -28,6 +30,8 @@ import hep.dataforge.meta.MetaBuilder;
import hep.dataforge.plots.data.DynamicPlottable;
import hep.dataforge.plots.data.DynamicPlottableSet;
import hep.dataforge.plots.jfreechart.JFreeChartFrame;
import hep.dataforge.storage.commons.StorageManager;
import hep.dataforge.storage.filestorage.FileStorage;
import hep.dataforge.values.Value;
import inr.numass.control.msp.MspDevice;
import inr.numass.control.msp.MspListener;
@ -155,14 +159,17 @@ public class MspViewController implements Initializable, MspListener {
device.setName(mspName);
device.setContext(context);
device.setMeta(mspConfig);
if (mspConfig.hasNode("storage")) {
device.connect(new StorageConnection(StorageManager.buildFrom(context).buildStorage(mspConfig.getNode("storage"))), Roles.STORAGE_ROLE);
}
try {
getDevice().setListener(this);
getDevice().init();
// getDevice().startMeasurement("peakJump");
} catch (ControlException ex) {
showError(String.format("Can't connect to %s:%d. The port is either busy or not the MKS mass-spectrometer port",
config.getString("connection.ip", "127.0.0.1"),
config.getInt("connection.port", 10014)));
device.meta().getString("connection.ip", "127.0.0.1"),
device.meta().getInt("connection.port", 10014)));
throw new RuntimeException("Can't connect to device");
}
} else {

View File

@ -3,7 +3,7 @@ apply plugin: 'application'
version = "0.4.0"
if (!hasProperty('mainClass')) {
ext.mainClass = 'inr.numass.readvac.Main'
ext.mainClass = 'inr.numass.readvac.app.ReadVac'
}
mainClassName = mainClass

View File

@ -55,21 +55,21 @@ public class ReadVac extends Application {
Sensor<Double> p1 = new MKSVacDevice(config.getString("p1.port", "com::/dev/ttyUSB0"));
p1.configure(config.getNode("p1", Meta.empty()));
p1.setName(config.getString("p1.name", "P1"));
p1.getConfig().putValue("powerButton", true);
Sensor<Double> p2 = new CM32Device(config.getString("p2.port", "tcp::192.168.111.32:4002"));
p1.configure(config.getNode("p2", Meta.empty()));
p2.configure(config.getNode("p2", Meta.empty()));
p2.setName(config.getString("p2.name", "P2"));
Sensor<Double> p3 = new CM32Device(config.getString("p3.port", "tcp::192.168.111.32:4003"));
p1.configure(config.getNode("p3", Meta.empty()));
p3.configure(config.getNode("p3", Meta.empty()));
p3.setName(config.getString("p3.name", "P3"));
Sensor<Double> px = new VITVacDevice(config.getString("px.port", "com::/dev/ttyUSB1"));
p1.configure(config.getNode("px", Meta.empty()));
px.configure(config.getNode("px", Meta.empty()));
px.setName(config.getString("px.name", "Px"));
Sensor<Double> baratron = new MKSBaratronDevice(config.getString("baratron.port", "tcp::192.168.111.33:4004"));
baratron.configure(config.getNode("baratron", Meta.empty()));
baratron.setName(config.getString("baratron.name", "Baratron"));
p1.configure(config.getNode("baratron", Meta.empty()));
VacCollectorDevice collector = new VacCollectorDevice();
collector.configure(config);
collector.setSensors(p1, p2, p3, px, baratron);
collector.init();
@ -81,30 +81,32 @@ public class ReadVac extends Application {
controller.setLoaderFactory((VacCollectorDevice device, Storage localStorage) -> {
try {
String runName = "";
try {
logger.info("Obtaining run information from cetral server...");
NumassClient client = new NumassClient(config.getString("numass.ip", "192.168.111.1"),
config.getInt("numass.port", 8335));
runName = client.getCurrentRun().getString("path", "");
logger.info("Run name is '{}'", runName);
} catch (Exception ex) {
logger.warn("Failed to download current run information", ex);
String runName = device.meta().getString("storage.run", "");
if (config.hasNode("numass")) {
try {
logger.info("Obtaining run information from cetral server...");
NumassClient client = new NumassClient(config.getString("numass.ip", "192.168.111.1"),
config.getInt("numass.port", 8335));
runName = client.getCurrentRun().getString("path", "");
logger.info("Run name is '{}'", runName);
} catch (Exception ex) {
logger.warn("Failed to download current run information", ex);
}
}
FormatBuilder format = new FormatBuilder().setFormat("timestamp", ValueType.TIME);
device.getSensors().stream().forEach((s) -> {
format.setFormat(s.getName(), ValueType.NUMBER);
});
PointLoader pl = LoaderFactory.buildPointLoder(localStorage, "vactms", runName, "timestamp", format.build());
return pl;
} catch (StorageException ex) {
throw new RuntimeException(ex);
}
});
Scene scene = new Scene(loader.getRoot(), 800, 600);
Scene scene = new Scene(loader.getRoot(), 800, 700);
primaryStage.setTitle("Numass vacuum measurements");
primaryStage.setScene(scene);

View File

@ -9,6 +9,7 @@ import hep.dataforge.control.devices.PortSensor;
import hep.dataforge.control.measurements.Measurement;
import hep.dataforge.control.measurements.SimpleMeasurement;
import hep.dataforge.control.ports.ComPortHandler;
import hep.dataforge.control.ports.PortFactory;
import hep.dataforge.control.ports.PortHandler;
import hep.dataforge.description.ValueDef;
import hep.dataforge.exceptions.ControlException;
@ -28,9 +29,14 @@ public class CM32Device extends PortSensor<Double> {
@Override
protected PortHandler buildHandler(String portName) throws ControlException {
String port = meta().getString("port", portName);
PortHandler newHandler = new ComPortHandler(port, 2400, 8, 1, 0);
newHandler.setDelimeter("T--");
getLogger().info("Connecting to port {}", portName);
PortHandler newHandler;
if (portName.startsWith("com")) {
newHandler = new ComPortHandler(portName, 2400, 8, 1, 0);
} else {
newHandler = PortFactory.getdPort(portName);
}
newHandler.setDelimeter("T--\r");
return newHandler;
}
@ -44,6 +50,10 @@ public class CM32Device extends PortSensor<Double> {
return meta().getString("type", "Leibold CM32");
}
// @Override
// protected int timeout() {
// return meta().getInt("timeout", 1000);
// }
@Override
protected Object calculateState(String stateName) throws ControlException {
if (getHandler() == null) {
@ -63,7 +73,6 @@ public class CM32Device extends PortSensor<Double> {
// }
}
private class CMVacMeasurement extends SimpleMeasurement<Double> {
private static final String CM32_QUERY = "MES R PM 1\r\n";

View File

@ -23,10 +23,12 @@ import javafx.beans.property.adapter.JavaBeanBooleanPropertyBuilder;
*/
@ValueDef(name = "address", def = "253")
@ValueDef(name = "channel", def = "5")
@ValueDef(name = "powerButton", type = "BOOLEAN", def = "true")
public class MKSVacDevice extends PortSensor<Double> {
public MKSVacDevice(String portName) {
super(portName);
super.getConfig().setValue("powerButton", true);
}
private String talk(String requestContent) throws ControlException {
@ -84,6 +86,14 @@ public class MKSVacDevice extends PortSensor<Double> {
}
}
@Override
public void shutdown() throws ControlException {
setPowerOn(false);
super.shutdown();
}
public boolean isPowerOn() {
return getState("power").booleanValue();
}
@ -134,21 +144,25 @@ public class MKSVacDevice extends PortSensor<Double> {
@Override
protected synchronized Double doMeasure() throws Exception {
String answer = talk("PR" + getChannel() + "?");
if (answer == null || answer.isEmpty()) {
invalidateState("connection");
this.progressUpdate("No connection");
return null;
}
double res = Double.parseDouble(answer);
if (res <= 0) {
this.progressUpdate("Non positive");
invalidateState("power");
return null;
} else {
this.progressUpdate("OK");
return res;
}
// if (getState("power").booleanValue()) {
String answer = talk("PR" + getChannel() + "?");
if (answer == null || answer.isEmpty()) {
invalidateState(CONNECTION_STATE);
this.progressUpdate("No connection");
return null;
}
double res = Double.parseDouble(answer);
if (res <= 0) {
this.progressUpdate("No power");
invalidateState("power");
return null;
} else {
this.progressUpdate("OK");
return res;
}
// } else {
// return null;
// }
}
}

View File

@ -51,6 +51,14 @@ public class VacCollectorDevice extends Sensor<DataPoint> {
}
}
@Override
public void init() throws ControlException {
super.init();
for (Sensor s : sensorMap.values()) {
s.init();
}
}
@Override
protected Object calculateState(String stateName) throws ControlException {
//TODO add dot path notation for states
@ -101,7 +109,12 @@ public class VacCollectorDevice extends Sensor<DataPoint> {
currentTask = executor.scheduleWithFixedDelay(() -> {
sensorMap.entrySet().stream().parallel().forEach((entry) -> {
try {
Object value = entry.getValue().read();
Object value;
if (entry.getValue().meta().getBoolean("disabled", false)) {
value = null;
} else {
value = entry.getValue().read();
}
collector.put(entry.getKey(), value);
} catch (Exception ex) {
collector.put(entry.getKey(), Value.NULL);

View File

@ -125,7 +125,11 @@ public class VacCollectorController implements Initializable, DeviceListener, Me
consolePane = new TextArea();
consolePane.setEditable(false);
consolePane.setWrapText(true);
ConsoleDude.hookStdStreams(consolePane);
// consolePane.textProperty().addListener((ObservableValue<? extends String> observable, String oldValue, String newValue) -> {
// if (newValue.length() > 10000) {
// consolePane.clear();
// }
// });
consoleWindow = new Stage();
consoleWindow.setTitle("Vacuum measurement console");
consoleWindow.setScene(new Scene(consolePane, 800, 200));
@ -173,7 +177,7 @@ public class VacCollectorController implements Initializable, DeviceListener, Me
});
plottables.setEachConfigValue("thickness", 3);
//TODO make history length edittable
plottables.setMaxAge(3*60*60*1000);
plottables.setMaxAge(3 * 60 * 60 * 1000);
plotContainer.setPlot(setupPlot(plottables));
}
@ -255,6 +259,7 @@ public class VacCollectorController implements Initializable, DeviceListener, Me
DirectoryChooser chooser = new DirectoryChooser();
File storageDir = chooser.showDialog(plotHolder.getScene().getWindow());
if (storageDir == null) {
storeButton.setSelected(false);
throw new RuntimeException("User canceled directory selection");
}
device.getConfig().putNode(new MetaBuilder("storage")
@ -294,10 +299,12 @@ public class VacCollectorController implements Initializable, DeviceListener, Me
@FXML
private void onLogToggle(ActionEvent event) {
if (logButton.isSelected() && logButton.isSelected()!=consoleWindow.isShowing()) {
if (logButton.isSelected() && logButton.isSelected() != consoleWindow.isShowing()) {
consoleWindow.show();
ConsoleDude.hookStdStreams(consolePane);
} else {
consoleWindow.hide();
ConsoleDude.restoreStdStreams();
}
}

View File

@ -22,12 +22,15 @@ import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.paint.Color;
import org.controlsfx.control.ToggleSwitch;
/**
*
@ -48,10 +51,13 @@ public class VacuumeterView extends DeviceViewController implements MeasurementL
@FXML
Label valueLabel;
@FXML
Label status;
@FXML
ToggleSwitch disableButton;
@Override
@SuppressWarnings("unchecked")
public void accept(Device device, String measurementName, Measurement measurement) {
@ -84,6 +90,13 @@ public class VacuumeterView extends DeviceViewController implements MeasurementL
Platform.runLater(() -> {
unitLabel.setText(getDevice().meta().getString("units", "mbar"));
deviceNameLabel.setText(getDevice().getName());
disableButton.setSelected(!getDevice().meta().getBoolean("disabled",false));
disableButton.selectedProperty().addListener((ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) -> {
getDevice().getConfig().setValue("disabled", !newValue);
if(!newValue){
valueLabel.setText("---");
}
});
});
}
@ -96,7 +109,7 @@ public class VacuumeterView extends DeviceViewController implements MeasurementL
public void onMeasurementFailed(Measurement measurement, Throwable exception) {
Platform.runLater(() -> {
valueLabel.setText("Err");
setStatus("Error: " + exception.getMessage());
// setStatus("Error: " + exception.getMessage());
});
}

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import org.controlsfx.control.*?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
@ -10,7 +16,7 @@
<?import javafx.scene.layout.VBox?>
<?import org.controlsfx.control.ToggleSwitch?>
<AnchorPane styleClass="vacBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane styleClass="vacBox" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<stylesheets>
<URL value="@/styles/vacstyles.css" />
</stylesheets>
@ -19,16 +25,23 @@
<children>
<AnchorPane styleClass="namePane">
<children>
<Label id="name" fx:id="deviceNameLabel" alignment="CENTER" prefHeight="40.0" text="device Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<Label id="name" fx:id="deviceNameLabel" alignment="CENTER" prefHeight="40.0" text="device Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="0.0" />
<ToggleSwitch fx:id="disableButton" alignment="CENTER" layoutX="130.0" layoutY="10.0" prefWidth="40.0" selected="true" AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0" />
</children>
</AnchorPane>
<Separator />
<BorderPane>
<left>
<Label id="pressure" fx:id="valueLabel" alignment="CENTER_RIGHT" minWidth="110.0" prefHeight="60.0" text="--- " BorderPane.alignment="CENTER" />
<Label id="pressure" fx:id="valueLabel" alignment="CENTER_RIGHT" minWidth="110.0" prefHeight="60.0" text="---" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Label>
</left>
<right>
<Label id="units" fx:id="unitLabel" prefHeight="60.0" prefWidth="75.0" text=" mbar" BorderPane.alignment="CENTER" />
<Label id="units" fx:id="unitLabel" prefHeight="60.0" prefWidth="75.0" text="mbar" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Label>
</right>
</BorderPane>
<Separator />

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import org.controlsfx.control.*?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
@ -8,7 +14,7 @@
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.VBox?>
<AnchorPane styleClass="vacBox" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1">
<AnchorPane styleClass="vacBox" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1">
<stylesheets>
<URL value="@/styles/vacstyles.css" />
</stylesheets>
@ -17,16 +23,26 @@
<children>
<AnchorPane styleClass="namePane">
<children>
<Label id="name" fx:id="deviceNameLabel" alignment="CENTER" prefHeight="40.0" text="device Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />
<Label id="name" fx:id="deviceNameLabel" alignment="CENTER" prefHeight="40.0" text="device Name" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="50.0" AnchorPane.topAnchor="0.0" />
<ToggleSwitch fx:id="disableButton" alignment="CENTER" layoutX="140.0" layoutY="20.0" prefWidth="40.0" selected="true" AnchorPane.bottomAnchor="10.0" AnchorPane.rightAnchor="10.0" AnchorPane.topAnchor="10.0" />
</children>
</AnchorPane>
<Separator />
<BorderPane VBox.vgrow="ALWAYS">
<left>
<Label id="pressure" fx:id="valueLabel" alignment="CENTER_RIGHT" minWidth="110.0" prefHeight="60.0" text="--- " BorderPane.alignment="CENTER" />
<Label id="pressure" fx:id="valueLabel" alignment="CENTER_RIGHT" minWidth="110.0" prefHeight="60.0" text="---" BorderPane.alignment="CENTER">
<BorderPane.margin>
<Insets />
</BorderPane.margin>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Label>
</left>
<right>
<Label id="units" fx:id="unitLabel" prefHeight="60.0" prefWidth="75.0" text=" mbar" BorderPane.alignment="CENTER" />
<Label id="units" fx:id="unitLabel" prefHeight="60.0" prefWidth="75.0" text="mbar" BorderPane.alignment="CENTER">
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
</padding></Label>
</right>
</BorderPane>
<Separator />

View File

@ -1,5 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>
<?import java.net.URL?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.ChoiceBox?>
@ -13,18 +19,13 @@
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>
<AnchorPane id="root" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.65" xmlns:fx="http://javafx.com/fxml/1" fx:controller="inr.numass.readvac.fx.VacCollectorController">
<AnchorPane id="root" prefHeight="600.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="inr.numass.readvac.fx.VacCollectorController">
<stylesheets>
<URL value="@/styles/vacstyles.css" />
</stylesheets>
<children>
<HBox AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children>
<VBox fx:id="vacBoxHolder" minWidth="200.0" spacing="2.0">
<HBox.margin>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</HBox.margin></VBox>
<Separator orientation="VERTICAL" />
<children>
<VBox alignment="TOP_CENTER" HBox.hgrow="ALWAYS">
<children>
<ToolBar>
@ -57,6 +58,11 @@
</HBox>
</children>
</VBox>
<Separator orientation="VERTICAL" />
<VBox fx:id="vacBoxHolder" minWidth="200.0" spacing="2.0">
<HBox.margin>
<Insets bottom="2.0" left="2.0" right="2.0" top="2.0" />
</HBox.margin></VBox>
</children>
</HBox>
</children>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<config>
<storage path="/home/numass-storage/"/>
<serialconfig P1="/dev/ttyUSB0" P2="/dev/ttyS5" P3="/dev/ttyS4" Px="/dev/ttyUSB1"/>
<run>12_2015</run>
</config>

View File

@ -0,0 +1,12 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package inr.numass.scripts
import inr.numass.client.NumassClient
NumassClient client = new NumassClient("192.168.111.1", 8335);
print client.startRun("2016_04")