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.URL; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.logging.Logger; import org.apache.commons.lang.exception.ExceptionUtils; import com.velasolaris.plugin.controller.flowrate.FlowratePluginController; 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: *
* 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 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 = 60000;
/** Read timeout [ms]. 0 may mean wait forever. */
protected int readTimeout = 0;
/**
* 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;
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
* 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 com.velasolaris.plugin.controller.rpc.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[plugin.dataPath] = plugins/com.velasolaris.plugin.controller.rpc.SimpleRpcPluginController
"
+ "[plugin.dataPath]/config.properties
."
+ "controlFunctions.py
describes the RPC function interface." + "