
com.velasolaris.plugin.controller.rpc.SimpleRpcPluginController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of polysun-public-plugin Show documentation
Show all versions of polysun-public-plugin Show documentation
Polysun plugin with controllers for Polysun simulations.
The newest version!
package com.velasolaris.plugin.controller.rpc;
import static com.velasolaris.plugin.controller.spi.PluginControllerConfiguration.DEFAULT_TIMESTEP;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.logging.Logger;
import com.velasolaris.plugin.controller.flowrate.FlowratePluginController;
import com.velasolaris.plugin.util.PluginUtils;
import org.apache.commons.lang3.SystemUtils;
import com.velasolaris.plugin.controller.spi.AbstractPluginController;
import com.velasolaris.plugin.controller.spi.PluginControllerConfiguration;
import com.velasolaris.plugin.controller.spi.PluginControllerConfiguration.ControlSignal;
import com.velasolaris.plugin.controller.spi.PluginControllerConfiguration.Log;
import com.velasolaris.plugin.controller.spi.PluginControllerConfiguration.Property;
import com.velasolaris.plugin.controller.spi.PluginControllerConfiguration.Sensor;
import com.velasolaris.plugin.controller.spi.PluginControllerException;
import com.velasolaris.plugin.controller.spi.PolysunSettings;
/**
* Simple RPC plugin controller that delegates control() to a user-defined functions through JSON-RPC or XML-RPC.
*
* Python supports natively XML-RPC. JSON-RPC libraries are available for Python.
*
* Performance:
* JSON-RPC stream (one TCP connection per simulation) is much faster than standard JSON-RPC over HTTP.
* JSON-RPC is faster than XML-RPC.
* Python 2.7 is faster then Python 3.4.
* PyPy 5.4.1 (Python 2.7) is faster than CPython 2.7.
* However, a Java implementation is much faster. The time per function call is not measurable.
* If performance is an important, the controller logic should directly implemented in Java,
* see {@link FlowratePluginController}.
*
* Measured average time per function call for control_flowrate() in Python and {@link FlowratePluginController} in Java
* on a Intel Core i7-4500U CPU 1.80GHz (dual core) are about:
* JSON-RPC stream JSON-RPC XML-RPC Java Matlab
* RPC type custom standard standard native library
* Communication protocol: TCP socket HTTP HTTP native RMI
* TCP connection per simulation timestep timestep N/A simulation
* PyPy 5.4.1 and Polysun compiled: 0.06ms 0.3ms 0.6ms 0ms 0.6ms
* PyPy 5.4.1 and Eclipse debug: 0.06ms 0.5ms 0.9ms 0ms 0.6ms
* Python 2.7 and Eclipse debug: 0.09ms 0.7ms 1.3ms 0ms 0.6ms
* Python 3.4 and Eclipse debug: 0.08ms 0.9ms 1.4ms 0ms 0.6ms
*
* PyPy is an Python interpreter using Just in Time (JIT) compilation.
* PyPy is the best way to start the RPC Server.
*
* Jython (http://www.jython.org) is package running Python in a JVM. It works, see below, but it is quite slow.
* Functions calls are about 20ms. The Jython.jar is about 27MB.
*
* In conclusion, Python remote calls are much slower than JAVA or Matlab calls.
*
* This simple RPC plugin controller could be enhanced:
*
* - Start the RPC server automatically if port is not bound.
* The server startup command string can be set as property. ProcessBuilder can be used.
*
*
* Jython example:
*
* Properties props = new Properties();
* props.put("python.console.encoding", "UTF-8"); // Used to prevent: console: Failed to install '': java.nio.charset.UnsupportedCharsetException: cp0.
* props.put("python.security.respectJavaAccessibility", "false"); //don't respect java accessibility, so that we can access protected members on subclasses
* props.put("python.import.site","false");
*
* Properties preprops = System.getProperties();
*
* PythonInterpreter.initialize(preprops, props, new String[0]);
* PythonInterpreter pi = new PythonInterpreter();
* pi.execfile("~/polysun/production/plugin/Plugins/PublicPolysunPlugin/src/main/python/controlFunctions.py");
* pi.exec("print(ping())");
* pi.exec("result = ping()");
* PyString result = (PyString)pi.get("result");
* System.out.println(result);
* long start = System.nanoTime() / 1000000;
* pi.set("simulationTime", 1);
* pi.set("status", true);
* pi.set("sensors", new Double[] {1d, 2d, 3d});
* pi.exec("result = controlTest(simulationTime, status, sensors)");
* PyList resultList = (PyList)pi.get("result");
* System.out.println(resultList);
* long stop = System.nanoTime() / 1000000;
* System.out.println("controlTest: " + (stop - start) + "ms");
*
*
* @author rkurmann
* @since Polysun 9.1
*
*/
public class SimpleRpcPluginController extends AbstractPluginController {
/** RPC Type */
enum RpcType {
/**
* JSON-RPC as stream using one TCP socket. Non standard behaviour.
* Very fast and avoiding "java.net.BindException: Address already in use: connect" due to ephemeral TCP ports exhaustion on Windows PSA-4571.
*/
JSON_STREAM,
/**
* JSON-RPC over HTTP as defined by standard.
* Could run into "java.net.BindException: Address already in use: connect" due to ephemeral TCP ports exhaustion, see PSA-4571.
*/
JSON,
/** XML-RPC over HTTP as defined by standard. */
XML
}
/** Call before simulation, i.e. initaliseSimulation() */
public static final int FUNCTION_STAGE_INIT = 0;
/** Call during simulation */
public static final int FUNCTION_STAGE_SIMULATION = 1;
/** Call after simulation, i.e. initaliseSimulation() */
public static final int FUNCTION_STAGE_TERMINATE = 2;
/** Property name for fixed timestep in the controller element GUI. */
private static final String PROPERTY_FIXED_TIMESTEP = "Fixed timestep";
/** Property name for the remote function in the controller element GUI. */
private static final String PROPERTY_FUNCTION = "RPC function";
/** Property name for RPC server automatic start [no/yes]. */
private static final String PROPERTY_RPC_SERVER_START_AUTOMATIC = "Start RPC server automatically";
/** Enum for RPC server automatic start. */
private enum RpcServerStartAutomatic {No, Yes};
/** Property name for RPC server interpreter, e.g. python. */
private static final String PROPERTY_RPC_SERVER_INTERPETER = "Interpreter for RPC server";
/** Property name for RPC server interpreter, e.g. python. */
private static final String PROPERTY_RPC_SERVER_SHOW_CONSOLE = "Show console RPC server";
/** Enum for RPC server show console. */
private enum RpcServerShowConsole {No, Yes};
/** Property name for RPC server start script. */
private static final String PROPERTY_RPC_SERVER_START_SCRIPT = "Start script RPC server";
/** Property name for RPC server functions module, where the functions are found. */
private static final String PROPERTY_RPC_SERVER_FUNCTIONS_MODULE = "Functions module RPC server";
/** Property name for RPC server path to the functions module. */
private static final String PROPERTY_RPC_SERVER_MODULE_PATH = "Module path RPC server";
/**
* Property name for number of generic control signals in the controller
* element GUI.
*/
private static final String PROPERTY_NUM_GENERIC_CONTROL_SIGNALS = "Number of controls signals";
/**
* Property name for number of generic log values in the controller element
* GUI.
*/
private static final String PROPERTY_NUM_GENERIC_LOGS = "Number of logs";
/**
* Property name for number of generic sensors in the controller element
* GUI.
*/
private static final String PROPERTY_NUM_GENERIC_SENSORS = "Number of sensors";
/** Property name for the RPC Server URL. */
private static final String PROPERTY_RPC_SERVER_URL = "RPC server URL";
/** Property name for the RPC Type, XML or JSON. */
private static final String PROPERTY_RPC_TYPE = "RPC type";
/** Property name for verbose in the controller element GUI. */
private static final String PROPERTY_VERBOSE_LEVEL = "Verbose level";
/** Static instance of the Logger for this class */
protected static Logger sLog = Logger.getLogger(SimpleRpcPluginController.class.getName());
/** Debug verbose level. */
public static final int VERBOSE_LEVEL_DEBUG = 2;
/** Standard verbose level. */
public static final int VERBOSE_LEVEL_STANDARD = 0;
/** Verbose level: Verbose (more thans standard output). */
public static final int VERBOSE_LEVEL_VERBOSE = 1;
/**
* Fixed timestep. For each timepoint which is a multiple of this
* fixedTimestep, the simulation does a timestep. The Polysun solver can do
* more timesteps if necessary. Example, for fixedTimestep of 180s, the
* simulation solver does a simulation at least at 0s, 180s, 360s, 480s,
* 720s, ... 0 means no fixed timestep and Polysun uses the default
* timesteps (240s during the day and 720s during the night).
*/
private int fixedTimestep = DEFAULT_TIMESTEP;
/**
* Has the invalid number of control signals message already be shown? Avoid
* repeating output.
*/
private boolean invalidControlSignalsMsgShown;
/**
* Has the invalid number of logs already be shown? Avoid repeating output.
*/
private boolean invalidLogsMsgShown;
/** Last day of logging. */
private int lastLogDay;
/**
* Number of configured properties. These number of properties will be
* removed from properties to create rpcPropertiesFloat
and
* rpcPropertiesString
.
*/
protected int numInternProperties;
/** Connection timeout [ms]. 0 may mean wait forever. */
protected int connectionTimeout = 5000;
/** Read timeout [ms]. 0 may mean wait forever. */
protected int readTimeout = 5000;
/** Wait RPC server process started [ms]. 0 means no waiting, -1 means waiting forever. */
protected int waitServerProcessStarted = 5000;
/**
* Properties as String that will be passed to the RPC function. Properties that are
* used for the SimpleRpcPluginController itself are removed.
*/
protected String[] rpcContollerPropertiesString;
/**
* Properties that will be passed to the RPC function. Properties that are used for
* the SimpleRpcPluginController itself are removed.
*/
protected float[] rpcControllerPropertiesFloat;
/**
* Name of the RPC function to call. Comes from the controller element GUI.
*/
protected String rpcFunction;
/** The RPC proxy that calls the RPC server. */
private RpcProxy rpcProxy = null;
/** Type of RPC, JSON or XML. */
private RpcType rpcType;
/** URL of the webservice having the RPC control functions. */
protected String serverURL = null;
/**
* Verbose level. 0 = default 1 = verbose 2 = debug Comes from the
* controller element GUI.
*
* @see #VERBOSE_LEVEL_STANDARD
* @see #VERBOSE_LEVEL_VERBOSE
* @see #VERBOSE_LEVEL_DEBUG
*/
protected int verboseLevel;
/** RPC server process used if RPC server is started automatically. */
private Process rpcServerProcess;
/**
* Enum for checking and starting a Linux console.
*
* http://askubuntu.com/questions/484993/run-command-on-anothernew-terminal-window
* @author rkurmann
*
*/
enum LinuxConsole {
/** KDE konsole */
konsole("konsole --hold -e"),
/** Gnome terminal */
gnomeTerminal("gnome-terminal --"),
/** X-Window terminal */
xterm("xterm -hold -e"),
/** No console, fallback solution if no terminal has been found. */
noConsole("");
/** Console start command. */
private String consoleStart;
/**
* Constructor.
* @param consoleStart command to start the console
*/
LinuxConsole(String consoleStart) {
this.consoleStart = consoleStart;
}
/**
* Checks if the console exits on this system.
* @return true
if console exists, otherwise false
*/
public boolean exists() {
try {
Process p = new ProcessBuilder("which", consoleStart.split(" ", 2)[0]).start();
p.waitFor();
return p.exitValue() == 0 || this == noConsole;
} catch (Exception e) {
return false;
}
}
/**
* Returns the console start command for the ProcessBuilder.
* @param scriptCmd the script command
* @return the full command as list of strings for ProcessBuilder
*/
public List getStartConsoleCmd(String scriptCmd) {
List list = new ArrayList<>();
if (!"".equals(consoleStart)) {
list.addAll(Arrays.asList(consoleStart.split(" ")));
}
list.addAll(Arrays.asList(scriptCmd.split(" ")));
return list;
}
};
static {
// Add the jsonrpc2 protocol to the URL stream handler
// URL URL.setURLStreamHandlerFactory must not be called twice in the whole application
URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
@Override
public URLStreamHandler createURLStreamHandler(String protocol) {
return "jsonrpc2".equals(protocol) ? new URLStreamHandler() {
@Override
protected URLConnection openConnection(URL url) throws IOException {
return new URLConnection(url) {
@Override
public void connect() throws IOException {
// Do nothing
}
};
}
} : null;
}
});
}
@Override
public void build(PolysunSettings polysunSettings, Map parameters)
throws PluginControllerException {
super.build(polysunSettings, parameters);
rpcFunction = getProperty(PROPERTY_FUNCTION).getString();
serverURL = getProperty(PROPERTY_RPC_SERVER_URL).getString();
rpcType = RpcType.values()[getProperty(PROPERTY_RPC_TYPE).getInt()];
fixedTimestep = getProperty(PROPERTY_FIXED_TIMESTEP).getInt();
verboseLevel = getProperty(PROPERTY_VERBOSE_LEVEL).getInt();
rpcControllerPropertiesFloat = new float[propertiesFloat.length - numInternProperties];
System.arraycopy(propertiesFloat, numInternProperties, rpcControllerPropertiesFloat, 0,
propertiesFloat.length - numInternProperties);
rpcContollerPropertiesString = new String[propertiesString.length - numInternProperties];
System.arraycopy(propertiesString, numInternProperties, rpcContollerPropertiesString, 0,
propertiesString.length - numInternProperties);
try {
setupRpc(parameters);
} catch (PluginControllerException e) {
// sLog.fine(ExceptionUtils.getFullStackTrace(e)); // We log exceptions in PluginController
disconnectProxy();
throw e; // rethrow and avoid double wrapping
} catch (Exception e) {
// sLog.fine(ExceptionUtils.getFullStackTrace(e)); // We log exceptions in PluginController
disconnectProxy();
throw new PluginControllerException(e);
}
}
/**
* Calls the remote function.
*
* Signature:
*
* control(simulationTime, status, sensors, sensorsUsed, properties,
* propertiesStr, preRun, controlSignalsUsed, numLogValues, stage,
* fixedTimestep, verboseLevel, parameters)
* => controlSignals, logValues, timepoints
*
*
* @param simulationTime
* simulationTime, int: The simulation time in [s] beginning from
* the 1. January 00:00 (no leap year).
* @param status
* 0/1: The status of this controller according to user settings,
* 1 means enabled, 0 disabled. The status originates from the
* timer setting of the controller dialog. The user can enable or
* disable the controller for certain hours, days or month. This
* value should be respected by the controller implementation,
* otherwise it could lead to an unexpected user experience.
* @param sensors
* float vector: The values of the sensors configured by the user
* (Input parameter) (length is available during init stage = 0)
* @param sensorsUsed
* vector 0/1: 1 indicates that the sensor is used in Polysun
* (available during init stage = 0)
* @param properties
* float vector: The properties set in Polysun (available during
* init stage = 0)
* @param propertiesStr
* String vector: The properties set in Polysun as string array
* (available during init stage = 0)
* @param preRun
* 0/1: Is this the real simulation or a pre run phase? This
* value can be ignored.
* @param controlSignalsUsed
* float vector: 1 indicates that the control signal is used in
* Polysun (available during init stage = 0)
* @param logValues
* float vector: The log values that can be returned.
* Configurable in Polysun.
* @param stage
* int: Stage of the function call 0 = Init, called before the
* simulation to init (initSimulation), results will be ignored 1
* = during the simulation (simulation), 2 = after the simulation
* (terminateSimulation), results will be ignored
* @param fixedTimestep
* int: Fixed timestep. For each timepoint which is a multiple of
* this fixedTimestep, the simulation does a timestep. The
* Polysun solver can do more timesteps if necessary. Example,
* for fixedTimestep of 180s, the simulation solver does a
* simulation at least at 0s, 180s, 360s, 480s, 720s, ... 0 means
* no fixed timestep and Polysun uses the default timesteps (240s
* during the day and 720s during the night).
* @param verboseLevel
* int: How much output should this function display to the
* console? (available during init stage = 0) 0 = standard output
* 1 = verbose output 2 = debug output
* @param parameters
* Map<String, Object>: Generic parameters
* @return controlSignals The control signals set by this plugin controller
* (Output parameter). logValues: The values to log in Polysun, e.g
* intermediate results. This value can be ignored. These values are
* shown in the Simulation Analysis or in the Log and Parameterizing
* output. timepoints [s]. Registers these timepoints in the future,
* where the simulation have to do a timestep. It doesn't matter, if
* the same timepoint will be registered several times. Timepoint in
* the array is in seconds from the 1. Jan. 00:00, or
* null
if no additional timesteps are required. These
* timesteps can be used for time based controlling strategies.
*
* @throws Exception
* For any problems
* @see RpcProxy#callRemoteFunction(int,
* boolean, float[], boolean[], float[], java.lang.String[], boolean,
* boolean[], float[], int, int, int, java.util.Map)
*/
public ControlFunctionResponse callRemoteFunction(int simulationTime, boolean status, float[] sensors, boolean[] sensorsUsed,
float[] properties, String[] propertiesStr, boolean preRun, boolean[] controlSignalsUsed, float[] logValues,
int stage, int fixedTimestep, int verboseLevel, Map parameters) throws Exception {
return rpcProxy.callRemoteFunction(simulationTime, status, sensors, sensorsUsed, properties, propertiesStr,
preRun, controlSignalsUsed, logValues, stage, fixedTimestep, verboseLevel, parameters);
}
@Override
public void closeResources() {
stopRpcServer();
disconnectProxy();
}
@Override
public int[] control(int simulationTime, boolean status, float[] sensors, float[] controlSignals, float[] logValues,
boolean preRun, Map parameters) throws PluginControllerException {
try {
startMeasureFunctionCall();
ControlFunctionResponse response = callRemoteFunction(simulationTime, status, sensors, sensorsUsed,
rpcControllerPropertiesFloat, rpcContollerPropertiesString, preRun, controlSignalsUsed, logValues,
FUNCTION_STAGE_SIMULATION, fixedTimestep, verboseLevel, parameters);
stopMeasureFunctionCall();
float[] rpcControlSignals = response.getControlSignals();
float[] rpcLogs = response.getLogValues();
int[] rpcTimepoints = response.getTimepoints();
if (rpcControlSignals.length != controlSignals.length && verboseLevel > VERBOSE_LEVEL_STANDARD
&& !invalidControlSignalsMsgShown) {
writeMsgToServer("Wrong number of control signals. Using smaller value. Expected: "
+ controlSignals.length + ", actual: " + rpcControlSignals.length);
invalidControlSignalsMsgShown = true;
}
if (rpcLogs.length != logValues.length && verboseLevel > VERBOSE_LEVEL_STANDARD
&& !invalidLogsMsgShown) {
writeMsgToServer("Wrong number of log values. Using smaller value. Expected: " + logValues.length
+ ", actual: " + rpcLogs.length);
invalidLogsMsgShown = true;
}
for (int i = 0; i < Math.min(controlSignals.length, rpcControlSignals.length); i++) {
controlSignals[i] = rpcControlSignals[i];
}
for (int i = 0; i < Math.min(logValues.length, rpcLogs.length); i++) {
logValues[i] = rpcLogs[i];
}
int[] timepoints = new int[rpcTimepoints.length];
for (int i = 0; i < timepoints.length; i++) {
timepoints[i] = rpcTimepoints[i];
}
int day = simulationTime / (3600 * 24);
if (verboseLevel >= 3 && !preRun && day > lastLogDay + 7) {
logPerfMeasure("Week " + day / 7 + ": ", parameters);
lastLogDay = day;
} else if (verboseLevel >= 2 && !preRun && day > lastLogDay + 30) {
logPerfMeasure("Month " + day / 30 + ": ", parameters);
lastLogDay = day;
}
return timepoints;
} catch (PluginControllerException e) {
// sLog.fine(ExceptionUtils.getFullStackTrace(e)); // We log exceptions in PluginController
disconnectProxy();
throw e; // rethrow and avoid double wrapping
} catch (Exception e) {
// sLog.fine(ExceptionUtils.getFullStackTrace(e)); // We log exceptions in PluginController
disconnectProxy();
throw new PluginControllerException(e);
}
}
/**
* Disconnects the RPC proxy.
*
* @see RpcProxy#disconnectProxy()
*/
public void disconnectProxy() {
if (rpcProxy != null) {
rpcProxy.disconnectProxy();
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
closeResources();
}
@Override
public PluginControllerConfiguration getConfiguration(Map parameters)
throws PluginControllerException {
setConfiguration(parameters);
Properties config = new Properties();
int defaultNumGenericSensors = 3;
int defaultNumGenericControlSignals = 3;
int defaultNumGenericProperties = 5;
int defaultNumLogValues = 1;
int defaultFixedTimestep = DEFAULT_TIMESTEP;
String defaultFunction = "control_";
Integer defaultVerboseLevel = 1;
List printMessage = new ArrayList<>();
File configFile = new File(pluginDataPath + File.separator + "config.properties");
String defaultServerURL = "http://localhost:2102";
RpcType defaultRpcType = RpcType.JSON_STREAM;
RpcServerStartAutomatic defaultRpcServerStartAutomatic = RpcServerStartAutomatic.No;
String defaultRpcServerInterpreter = "python";
RpcServerShowConsole defaultRpcServerShowConsole = RpcServerShowConsole.Yes;
String defaultRpcServerStartScript = "[pluginDataPath]" + File.separator + "controlRpcServer.py";
String defaultRpcServerFunctionsModule = "controlFunctions";
String defaultRpcServerFunctionsModulePath = "[pluginDataPath]";
try {
if (configFile.exists()) {
try (FileReader fileReader = new FileReader(configFile);
BufferedReader reader = new BufferedReader(fileReader);) {
config.load(reader);
Object obj;
defaultNumGenericProperties = (obj = config.get("defaultNumGenericProperties")) != null
&& !"".equals(obj) ? Integer.parseInt(obj.toString()) : defaultNumGenericProperties;
defaultNumGenericSensors = (obj = config.get("defaultNumGenericSensors")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : defaultNumGenericSensors;
defaultNumGenericControlSignals = (obj = config.get("defaultNumGenericControlSignals")) != null
&& !"".equals(obj) ? Integer.parseInt(obj.toString()) : defaultNumGenericControlSignals;
defaultFixedTimestep = (obj = config.get("defaultFixedTimestep")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : defaultFixedTimestep;
defaultNumLogValues = (obj = config.get("defaultNumLogValues")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : defaultNumLogValues;
defaultFunction = (obj = config.get("defaultFunction")) != null && !"".equals(obj)
? obj.toString() : defaultFunction;
defaultVerboseLevel = (obj = config.get("defaultVerboseLevel")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : defaultVerboseLevel;
connectionTimeout = (obj = config.get("connectionTimeout")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : connectionTimeout;
readTimeout = (obj = config.get("readTimeout")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : readTimeout;
waitServerProcessStarted = (obj = config.get("waitServerProcessStarted")) != null && !"".equals(obj)
? Integer.parseInt(obj.toString()) : waitServerProcessStarted;
defaultServerURL = (obj = config.get("defaultServerURL")) != null && !"".equals(obj)
? obj.toString() : "";
defaultRpcType = (obj = config.get("defaultRpcType")) != null && !"".equals(obj)
? RpcType.values()[Integer.parseInt(obj.toString())] : defaultRpcType;
defaultRpcServerStartAutomatic = (obj = config.get("defaultRpcServerStartAutomatic")) != null && !"".equals(obj)
? RpcServerStartAutomatic.values()[Integer.parseInt(obj.toString())] : defaultRpcServerStartAutomatic;
defaultRpcServerShowConsole = (obj = config.get("defaultRpcServerShowConsole")) != null && !"".equals(obj)
? RpcServerShowConsole.values()[Integer.parseInt(obj.toString())] : defaultRpcServerShowConsole;
defaultRpcServerInterpreter = (obj = config.get("defaultRpcServerInterpreter")) != null && !"".equals(obj)
? obj.toString() : defaultRpcServerInterpreter;
defaultRpcServerStartScript = (obj = config.get("defaultRpcServerStartScript")) != null && !"".equals(obj)
? obj.toString() : defaultRpcServerStartScript;
defaultRpcServerFunctionsModule = (obj = config.get("defaultRpcServerFunctionsModule")) != null && !"".equals(obj)
? obj.toString() : defaultRpcServerFunctionsModule;;
defaultRpcServerFunctionsModulePath = (obj = config.get("defaultRpcServerFunctionsModulePath")) != null && !"".equals(obj)
? obj.toString() : defaultFunction;
sLog.info("Config.properties read: " + configFile);
} catch (IOException e) {
sLog.warning(PluginUtils.getRootCauseStackTrace(e));
}
} else {
configFile.getParentFile().mkdirs();
try (FileWriter fileWriter = new FileWriter(configFile);
BufferedWriter writer = new BufferedWriter(fileWriter)) {
config.put("defaultNumGenericProperties", "" + defaultNumGenericProperties);
config.put("defaultNumGenericSensors", "" + defaultNumGenericSensors);
config.put("defaultNumGenericControlSignals", "" + defaultNumGenericControlSignals);
config.put("defaultNumLogValues", "" + defaultNumLogValues);
config.put("defaultFixedTimestep", "" + defaultFixedTimestep);
config.put("defaultFunction", "" + defaultFunction);
config.put("defaultVerboseLevel", "" + defaultVerboseLevel);
config.put("defaultServerURL", "" + defaultServerURL);
config.put("defaultRpcType", "" + defaultRpcType.ordinal());
config.put("defaultRpcServerStartAutomatic", "" + defaultRpcServerStartAutomatic.ordinal());
config.put("defaultRpcServerShowConsole", "" + defaultRpcServerShowConsole.ordinal());
config.put("defaultRpcServerInterpreter", "" + defaultRpcServerInterpreter);
config.put("defaultRpcServerStartScript", "" + defaultRpcServerStartScript);
config.put("defaultRpcServerFunctionsModule", "" + defaultRpcServerFunctionsModule);
config.put("defaultRpcServerFunctionsModulePath", "" + defaultRpcServerFunctionsModulePath);
config.put("connectionTimeout", "" + connectionTimeout);
config.put("readTimeout", "" + readTimeout);
config.put("waitServerProcessStarted", "" + waitServerProcessStarted);
config.store(writer, "Properties of the Polysun SimpleRpcPluginController + [" + getId() + "]");
sLog.info("Config file written: " + configFile);
exportResource("/com/velasolaris/plugin/controller/python/controlFunctions.py", pluginDataPath,
true);
exportResource("/com/velasolaris/plugin/controller/python/utils.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/controlRpcServer.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/controlXmlRpcServer.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/controlJsonRpcServer.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/controlJsonRpcStreamServer.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/bottle.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/bottle_jsonrpc.py", pluginDataPath, true);
exportResource("/com/velasolaris/plugin/controller/python/request_jsonrpc.py", pluginDataPath, true);
printMessage.add("Configuration file and .py files written to " + pluginDataPath);
} catch (IOException e) {
sLog.warning(PluginUtils.getRootCauseStackTrace(e));
}
}
} catch (Exception e) {
sLog.warning(PluginUtils.getRootCauseStackTrace(e));
}
List properties = new ArrayList<>();
properties.add(new Property(PROPERTY_FUNCTION, defaultFunction));
properties.add(new Property(PROPERTY_RPC_TYPE, new String[] { "JSON-RPC Stream", "JSON-RPC", "XML-RPC" }, defaultRpcType.ordinal()));
properties.add(new Property(PROPERTY_RPC_SERVER_URL, defaultServerURL));
properties.add(new Property(PROPERTY_RPC_SERVER_START_AUTOMATIC, new String[] { "No", "Yes" }, defaultRpcServerStartAutomatic.ordinal()));
properties.add(new Property(PROPERTY_RPC_SERVER_INTERPETER, defaultRpcServerInterpreter));
properties.add(new Property(PROPERTY_RPC_SERVER_SHOW_CONSOLE, new String[] { "No", "Yes" }, defaultRpcServerShowConsole.ordinal()));
properties.add(new Property(PROPERTY_RPC_SERVER_START_SCRIPT, defaultRpcServerStartScript));
properties.add(new Property(PROPERTY_RPC_SERVER_FUNCTIONS_MODULE, defaultRpcServerFunctionsModule));
properties.add(new Property(PROPERTY_RPC_SERVER_MODULE_PATH, defaultRpcServerFunctionsModulePath));
properties.add(new Property(PROPERTY_FIXED_TIMESTEP, defaultFixedTimestep, 0f, 900, "s"));
properties.add(new Property(PROPERTY_NUM_GENERIC_SENSORS, defaultNumGenericSensors, 0, 100));
properties.add(new Property(PROPERTY_NUM_GENERIC_CONTROL_SIGNALS, defaultNumGenericControlSignals, 0, 100));
properties.add(new Property(PROPERTY_NUM_GENERIC_LOGS, defaultNumLogValues, 0, 100));
properties.add(new Property(PROPERTY_VERBOSE_LEVEL, new String[] { "Standard", "Verbose", "Debug" },
defaultVerboseLevel));
numInternProperties = properties.size();
List sensors = new ArrayList<>();
List controlSignals = new ArrayList<>();
List logs = new ArrayList<>();
parameters.put("Plugin.PrintMessage", printMessage);
return new PluginControllerConfiguration(properties, sensors, controlSignals, logs, defaultNumGenericProperties,
defaultNumGenericSensors, defaultNumGenericControlSignals, "plugin/images/controller_plugin.png",
PROPERTY_NUM_GENERIC_SENSORS, PROPERTY_NUM_GENERIC_CONTROL_SIGNALS, PROPERTY_NUM_GENERIC_LOGS, null);
}
@Override
public String getDescription() {
return "RPC plugin controller that delegates controller calls to "
+ "user-defined functions via Remote Procedure Call (XML-RPC "
+ "or JSON-RPC), e.g. to Python.";
}
@Override
public String getDocumentation() {
return "RPC plugin controller that delegates controller calls to a user-defined function "
+ "via Remote Procedure Call (XML-RPC or JSON-RPC) to a RPC server." + "
"
+ "This RPC plugin controller allows to write controllers in progamming languages "
+ "supporting the JSON-RPC or XML-RPC protocol such as Python. "
+ "The RPC server must provide a RPC function having the RPC function interface for plugin controllers. "
+ "The function set by name in the controller properties will be called by this RPC plugin controller "
+ "on the destination RPC server.
"
+ "
[pluginDataPath] = plugins/com.velasolaris.plugin.controller.rpc.SimpleRpcPluginController
"
+ "
Basic configuration is read from [pluginDataPath]/config.properties
."
+ "
" + "
Python files are copied on the first usage to [pluginDataPath]."
+ "
The file controlFunctions.py
describes the RPC function interface." + "
"
+ "
The communication with the RPC server is done with"
+ "
XML-RPC stream (fast, non-standard), XML-RPC (very slow) or JSON-RPC (slow)."
+ "
For example start JSON-RPC stream server with"
+ "
python controlJsonRpcStreamServer.py"
+ "
or: pypy controlJsonRpcStreamServer.py"
+ "
Find more information in the Polysun user manual.";
}
@Override
public int getFixedTimestep(Map parameters) {
return fixedTimestep;
}
@Override
public String getName() {
return "SimpleRpc";
}
@Override
public List getPropertiesToHide(PolysunSettings polysunSettings, Map parameters) {
List propertiesToHide = super.getPropertiesToHide(polysunSettings, parameters);
if (getProperty(PROPERTY_RPC_SERVER_START_AUTOMATIC).getInt() == RpcServerStartAutomatic.No.ordinal()) {
propertiesToHide.add(PROPERTY_RPC_SERVER_INTERPETER);
propertiesToHide.add(PROPERTY_RPC_SERVER_FUNCTIONS_MODULE);
propertiesToHide.add(PROPERTY_RPC_SERVER_MODULE_PATH);
propertiesToHide.add(PROPERTY_RPC_SERVER_START_SCRIPT);
propertiesToHide.add(PROPERTY_RPC_SERVER_SHOW_CONSOLE);
} else if (!(SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_UNIX)) {
propertiesToHide.add(PROPERTY_RPC_SERVER_SHOW_CONSOLE);
}
return propertiesToHide;
}
@Override
public void initialiseSimulation(Map aParameters) throws PluginControllerException {
super.initialiseSimulation(aParameters);
invalidControlSignalsMsgShown = false;
invalidLogsMsgShown = false;
try {
if (verboseLevel > VERBOSE_LEVEL_STANDARD) {
writeMsgToServer("Polysun simulation started");
}
callRemoteFunction(0, false, new float[sensorsUsed.length], sensorsUsed, rpcControllerPropertiesFloat,
rpcContollerPropertiesString, true, controlSignalsUsed, new float[0], FUNCTION_STAGE_INIT,
fixedTimestep, verboseLevel, null);
} catch (PluginControllerException e) {
// sLog.fine(ExceptionUtils.getFullStackTrace(e)); // We log exceptions in PluginController
disconnectProxy();
throw e; // rethrow and avoid double wrapping
} catch (Exception e) {
// sLog.fine(ExceptionUtils.getFullStackTrace(e)); // We log exceptions in PluginController
disconnectProxy();
throw new PluginControllerException(e);
}
lastLogDay = 0;
}
/**
* Prints performance statistics about the (remote) function call.
*
* @param prefix
* A prefix to write before the log
* @param parameters
* General parameters for a Polysun print message.
*
* @return the performance measure message, it can be used in sub classes
* @throws Exception
* For any problems
*/
@Override
protected String logPerfMeasure(String prefix, Map parameters) throws Exception {
String msg = super.logPerfMeasure(prefix, parameters);
writeMsgToServer(msg);
return msg;
}
/**
* Sets up the RPC proxy.
*
* @param parameters
* Generic parameters
* @throws Exception
* For any problems
* @see RpcProxy#setupRpc(int, java.util.Map)
*/
public void setupRpc(Map parameters) throws Exception {
if (rpcProxy != null) {
rpcProxy.disconnectProxy();
}
URL url = new URL(serverURL);
// Time to wait until the server is ready to be connected [ms]. -1 = no waiting, 0 = wait forever
int waitConnected = RpcProxy.WAIT_CONNECTED_NO_WAIT;
if (serverListening(url.getHost(), url.getPort())) {
String msg = "Server is listening on " + url.getHost() + ":" + url.getPort();
sLog.info(msg);
parameters.put("Plugin.PrintMessage", msg);
waitConnected = RpcProxy.WAIT_CONNECTED_NO_WAIT;
} else if (getProperty(PROPERTY_RPC_SERVER_START_AUTOMATIC).getInt() == RpcServerStartAutomatic.Yes.ordinal()) {
stopRpcServer(); // Try to stop stale server process, e.g. if port changed and old process is still living
String interpreter = getProperty(PROPERTY_RPC_SERVER_INTERPETER).getString();
String script = replacePathPlaceholders(getProperty(PROPERTY_RPC_SERVER_START_SCRIPT).getString(), parameters);
if (script == null || !(new File(script)).exists()) {
throw new PluginControllerException("RPC server script does not exist: " + script);
}
String port = "-p" + url.getPort();
String functionsFile = "-f" + getProperty(PROPERTY_RPC_SERVER_FUNCTIONS_MODULE).getString();
String modulePathDir = replacePathPlaceholders(getProperty(PROPERTY_RPC_SERVER_MODULE_PATH).getString(), parameters);
if (modulePathDir == null || !(new File(modulePathDir)).exists()) {
throw new PluginControllerException("RPC server module path does not exist: " + modulePathDir);
}
String modulePath = "-m" + modulePathDir;
String rpcType = "-t" + RpcType.values()[getProperty(PROPERTY_RPC_TYPE).getInt()];
String cmd = interpreter + " " + script + " " + port + " " + modulePath + " " + functionsFile + " " + rpcType;
if (SystemUtils.IS_OS_WINDOWS && getProperty(PROPERTY_RPC_SERVER_SHOW_CONSOLE).getInt() == RpcServerShowConsole.Yes.ordinal()) {
rpcServerProcess = new ProcessBuilder("cmd.exe", "/c", "start " + cmd) // Open console in Windows
// .redirectErrorStream(true).inheritIO() Do not redirect IO in case of a console, this allows the console to catch Ctrl-C
.start();
} else if (SystemUtils.IS_OS_UNIX && getProperty(PROPERTY_RPC_SERVER_SHOW_CONSOLE).getInt() == RpcServerShowConsole.Yes.ordinal()) {
for (LinuxConsole console : LinuxConsole.values()) {
// if (console == LinuxConsole.konsole) continue; // Debug code
if (console.exists()) {
rpcServerProcess = new ProcessBuilder(console.getStartConsoleCmd(cmd)) // Open console
.redirectErrorStream(true).inheritIO()
.start();
break;
}
}
} else { // Background process, Linux, macOS, Windows
rpcServerProcess = new ProcessBuilder(interpreter, script, port, modulePath, functionsFile, rpcType)
.redirectErrorStream(true)
.inheritIO()
.start();
// processBuilder = new ProcessBuilder("/bin/bash", "-c", interpreter, script, functionsPath, functionsFile);
}
String msg = "Server process starting: " + cmd;
sLog.info(msg);
parameters.put("Plugin.PrintMessage", msg);
waitConnected = waitServerProcessStarted;
}
if (rpcType == RpcType.JSON) {
rpcProxy = new JsonRpcProxy(url, rpcFunction, connectionTimeout, readTimeout, verboseLevel);
} else if (rpcType == RpcType.JSON_STREAM) {
rpcProxy = new JsonRpcStreamProxy(url, rpcFunction, connectionTimeout, readTimeout, verboseLevel);
} else {
rpcProxy = new XmlRpcProxy(url, rpcFunction, connectionTimeout, readTimeout, verboseLevel);
}
rpcProxy.setupRpc(waitConnected, parameters);
}
@Override
public void terminateSimulation(Map parameters) {
super.terminateSimulation(parameters);
try {
if (verboseLevel > VERBOSE_LEVEL_STANDARD) {
writeMsgToServer("Polysun simulation terminated");
logPerfMeasure("Overall: ", parameters);
}
callRemoteFunction(0, false, new float[0], sensorsUsed, rpcControllerPropertiesFloat,
rpcContollerPropertiesString, true, controlSignalsUsed, new float[0], FUNCTION_STAGE_TERMINATE,
fixedTimestep, verboseLevel, null);
} catch (Exception e) {
sLog.warning(PluginUtils.getRootCauseStackTrace(e));
disconnectProxy();
}
}
/**
* Writes a message to the RPC Server console.
*
* @param str the message to write to the server
*
* @throws Exception
* For any problems
* @see RpcProxy#writeMsgToServer(java.lang.String)
*/
public void writeMsgToServer(String str) throws Exception {
rpcProxy.writeMsgToServer(str);
}
/** Checks if a server is listening on host:port.
*
* @param host host name
* @param port port number
* @return true
if a server is already listening, otherwise false
*/
public static boolean serverListening(String host, int port) {
try (Socket s = new Socket(host, port)) {
return true;
} catch (Exception e) {
return false;
}
}
/**
* Stops a RpcServer if it has been started.
*/
public void stopRpcServer() {
try {
if (rpcProxy != null && rpcServerProcess != null) {
sLog.info("Stop RPC server process...");
rpcProxy.stopServer();
}
} catch (Exception e) {
// ignore
} finally {
if (rpcServerProcess != null) {
try {
Thread.sleep(150); // Give some time to stop, this could be checked more beautiful with checking for termination in a loop with timeout
} catch (InterruptedException e) {
// ignore
}
rpcServerProcess.destroy();
rpcServerProcess = null;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy