All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.velasolaris.plugin.controller.rpc.SimpleRpcPluginController Maven / Gradle / Ivy

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