diff --git a/README.md b/README.md index ae2f457c713..e7462c96dca 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ A number of devices, protocols and services are already implemented in OpenEMS: ### Inbound protocols - * JSON/REST + * [JSON/REST](doc/rest-api.md) * JSON/Websocket * [Modbus/TCP (in development)](/OpenEMS/openems/issues/2) @@ -127,6 +127,93 @@ The target of this short guide is to quickly setup a development environment on 8. Open a browser at http://localhost:4200 +### How to configure OpenEMS + +The configuration of OpenEMS is placed in the json file /etc/openems.d/config.json. +To parameterize a Bridge,Device,DeviceNature, Scheduler, Controller or Persistance you have to use ConfigChannels. A ConfigChannel represents one parameter the Thing needs. The value of the ConfigChannel will be automatically parsed by OpenEMS on the configuration read. Each thing will be instantiated by reflection so you have to define a "class" property, which is the qualified name of the desired class. +The file is split into three sections: +1. `things`: + In the things section you need to set all devices to communicate with. + Therefore you have to use a so called "Bridge". A Bridge connects several devices(Hardware) with the same protocol to openems. For example you want to read the data of a Socomec Meter and FeneconPro which are connected to the same RS485 Bus, you have to use a ModbusRtu bridge and set the Socomec Meter and FeneconPro as devices for this bridge. Each bridge/device has ConfigChannels to provide the paremters to establish the connection. The required parameters can be found in the according class. A device has DeviceNatures, where each nature requires a unique id. This id is used as reference by the controllers. +2. `scheduler`: The Scheduler executes the controllers according to the programmed behaviour. For example the SimpleScheduler orders and executes the Controller by the configured priority. Controllers are the smallest divisible logical control unit. Each Controller needs at least one device reference to do the work. +3. `persistance`: + +Example configuration: +``` +{ + "things": [ + { + "class": "io.openems.impl.protocol.modbus.ModbusRtu", + "serialinterface": "/dev/ttyUSB0", + "baudrate": 9600, + "databits": 8, + "parity": "none", + "stopbits": 1, + "devices": [ + { + "class": "io.openems.impl.device.pro.FeneconPro", + "modbusUnitId": 4, + "ess": { + "id": "ess0", + "minSoc": 15 + }, + "meter": { + "id": "meter1" + } + }, + { + "class": "io.openems.impl.device.socomec.Socomec", + "modbusUnitId": 5, + "meter": { + "id": "meter0", + "type": "grid" + } + } + ] + } + ], + "scheduler": { + "class": "io.openems.impl.scheduler.SimpleScheduler", + "controllers": [ + { + "priority": 150, + "class": "io.openems.impl.controller.debuglog.DebugLogController", + "esss": [ "ess0" ], + "meters": [ "meter0", "meter1" ], + "rtc": "ess0" + }, + { + "priority": 100, + "class": "io.openems.impl.controller.asymmetric.avoidtotaldischarge.AvoidTotalDischargeController", + "esss": "ess0" + }, + { + "priority": 50, + "class": "io.openems.impl.controller.asymmetric.balancing.BalancingController", + "esss": "ess0", + "meter": "meter0" + }, + { + "priority": 1, + "class": "io.openems.impl.controller.clocksync.ClockSyncController", + "rtc": "ess0" + }, + { + "priority": 0, + "class": "io.openems.impl.controller.feneconprosetup.FeneconProSetupController", + "esss": "ess0" + } + ] + }, + "persistence": [ + { + "class": "io.openems.impl.persistence.influxdb.InfluxdbPersistence", + "ip": "127.0.0.1", + "fems": 0 + } + ] +} +``` ## Professional services and support diff --git a/backend/.classpath b/backend/.classpath index 4c4f58f2a22..caa0a4ec100 100644 --- a/backend/.classpath +++ b/backend/.classpath @@ -23,5 +23,6 @@ + diff --git a/backend/.project b/backend/.project index aeb96c06aef..dc292d6fae8 100644 --- a/backend/.project +++ b/backend/.project @@ -1,6 +1,6 @@ - femsserver + backend diff --git a/backend/OpenEMS-Backend (Dummy).launch b/backend/OpenEMS-Backend (Dummy).launch new file mode 100644 index 00000000000..6deb1e95c7a --- /dev/null +++ b/backend/OpenEMS-Backend (Dummy).launch @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/OpenEMS-Backend.launch b/backend/OpenEMS-Backend.launch new file mode 100644 index 00000000000..b19ef24708e --- /dev/null +++ b/backend/OpenEMS-Backend.launch @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/backend/pom.xml b/backend/pom.xml index 0ae840d28da..786895aea5a 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -10,11 +10,14 @@ femsserver http://maven.apache.org + UTF-8 - 1.6.4 + 1.0.0 + 1.7.15 1.0.1 2.6.2 + 23.0 @@ -62,5 +65,10 @@ java-websocket 1.3.3 + + com.google.guava + guava + ${guava.version} + diff --git a/backend/resources/logback.xml b/backend/resources/logback.xml index 81b5e4303c3..482ab17a2bc 100644 --- a/backend/resources/logback.xml +++ b/backend/resources/logback.xml @@ -27,7 +27,6 @@ [%-8.8thread] [%-5level] [%-20.20logger{36}:%-3line] %msg%ex{10}%n - diff --git a/backend/src/main/java/io/openems/backend/App.java b/backend/src/main/java/io/openems/backend/App.java new file mode 100644 index 00000000000..a7b6d00b9f0 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/App.java @@ -0,0 +1,78 @@ +package io.openems.backend; + +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.backend.browserwebsocket.BrowserWebsocket; +import io.openems.backend.metadata.Metadata; +import io.openems.backend.openemswebsocket.OpenemsWebsocket; +import io.openems.backend.timedata.Timedata; +import io.openems.common.utils.EnvUtils; + +public class App { + private static Logger log = LoggerFactory.getLogger(App.class); + + public static void main(String[] args) throws Exception { + log.info("OpenEMS-Backend starting..."); + + // Configure everything + initMetadataProvider(); + initTimedataProvider(); + initOpenemsWebsocket(); + initBrowserWebsocket(); + + log.info("OpenEMS Backend started."); + log.info("================================================================================"); + } + + /** + * Configures a metadata provider. It uses either Odoo as backend or a simple Dummy provider. + * + * @throws Exception + */ + private static void initMetadataProvider() throws Exception { + Optional metadataOpt = EnvUtils.getAsOptionalString("METADATA"); + if (metadataOpt.isPresent() && metadataOpt.get().equals("DUMMY")) { + log.info("Start Dummy Metadata provider"); + Metadata.initializeDummy(); + } else { + int port = EnvUtils.getAsInt("ODOO_PORT"); + String url = EnvUtils.getAsString("ODOO_URL"); + String database = EnvUtils.getAsString("ODOO_DATABASE"); + log.info("Connect to Odoo. Url [" + url + ":" + port + "] Database [" + database + "]"); + String username = EnvUtils.getAsString("ODOO_USERNAME"); + String password = EnvUtils.getAsString("ODOO_PASSWORD"); + Metadata.initializeOdoo(url, port, database, username, password); + } + } + + private static void initTimedataProvider() throws Exception { + Optional timedataOpt = EnvUtils.getAsOptionalString("TIMEDATA"); + if (timedataOpt.isPresent() && timedataOpt.get().equals("DUMMY")) { + log.info("Start Dummy Timedata provider"); + Timedata.initializeDummy(); + } else { + int port = Integer.valueOf(System.getenv("INFLUX_PORT")); + String url = EnvUtils.getAsString("INFLUX_URL"); + String database = EnvUtils.getAsString("INFLUX_DATABASE"); + log.info("Connect to InfluxDB. Url [" + url + ":" + port + "], Database [" + database + "]"); + String username = EnvUtils.getAsString("INFLUX_USERNAME"); + String password = EnvUtils.getAsString("INFLUX_PASSWORD"); + Timedata.initializeInfluxdb(database, url, port, username, password); + } + } + + private static void initOpenemsWebsocket() throws Exception { + int port = EnvUtils.getAsInt("OPENEMS_WEBSOCKET_PORT"); + log.info("Start OpenEMS Websocket server on port [" + port + "]"); + OpenemsWebsocket.initialize(port); + } + + private static void initBrowserWebsocket() throws Exception { + int port = EnvUtils.getAsInt("BROWSER_WEBSOCKET_PORT"); + log.info("Start Browser Websocket server on port [" + port + "]"); + BrowserWebsocket.initialize(port); + } +} diff --git a/backend/src/main/java/io/openems/backend/browserwebsocket/BrowserWebsocket.java b/backend/src/main/java/io/openems/backend/browserwebsocket/BrowserWebsocket.java new file mode 100644 index 00000000000..16356b9e42d --- /dev/null +++ b/backend/src/main/java/io/openems/backend/browserwebsocket/BrowserWebsocket.java @@ -0,0 +1,32 @@ +package io.openems.backend.browserwebsocket; + +/** + * Provider for OpenemsWebsocketServer singleton + * + * @author stefan.feilmeier + * + */ +public class BrowserWebsocket { + + private static BrowserWebsocketSingleton instance; + + /** + * Initialize and start the Websocketserver + * + * @param port + * @throws Exception + */ + public static synchronized void initialize(int port) throws Exception { + BrowserWebsocket.instance = new BrowserWebsocketSingleton(port); + BrowserWebsocket.instance.start(); + } + + /** + * Returns the singleton instance + * + * @return + */ + public static synchronized BrowserWebsocketSingleton instance() { + return BrowserWebsocket.instance; + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/browserwebsocket/BrowserWebsocketSingleton.java b/backend/src/main/java/io/openems/backend/browserwebsocket/BrowserWebsocketSingleton.java new file mode 100644 index 00000000000..ae8dbfbd344 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/browserwebsocket/BrowserWebsocketSingleton.java @@ -0,0 +1,270 @@ +package io.openems.backend.browserwebsocket; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.java_websocket.WebSocket; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.backend.browserwebsocket.session.BrowserSession; +import io.openems.backend.browserwebsocket.session.BrowserSessionData; +import io.openems.backend.browserwebsocket.session.BrowserSessionManager; +import io.openems.backend.metadata.Metadata; +import io.openems.backend.openemswebsocket.OpenemsWebsocket; +import io.openems.backend.openemswebsocket.OpenemsWebsocketSingleton; +import io.openems.backend.timedata.Timedata; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.Device; +import io.openems.common.utils.JsonUtils; +import io.openems.common.websocket.AbstractWebsocketServer; +import io.openems.common.websocket.DefaultMessages; +import io.openems.common.websocket.Notification; +import io.openems.common.websocket.WebSocketUtils; + +/** + * Handles connections from a browser. + * + * @author stefan.feilmeier + * + */ +public class BrowserWebsocketSingleton + extends AbstractWebsocketServer { + private final Logger log = LoggerFactory.getLogger(BrowserWebsocketSingleton.class); + + protected BrowserWebsocketSingleton(int port) throws Exception { + super(port, new BrowserSessionManager()); + } + + /** + * Open event of websocket. Parses the Odoo "session_id" and stores it in a new Session. + */ + @Override + public void onOpen(WebSocket websocket, ClientHandshake handshake) { + // Prepare session information + String error = ""; + BrowserSession session = null; + String sessionId = null; + + try { + // get cookie information + JsonObject jCookie = parseCookieFromHandshake(handshake); + sessionId = JsonUtils.getAsString(jCookie, "session_id"); + + // try to get token of an existing, valid session from cookie + if (jCookie.has("token")) { + String token = JsonUtils.getAsString(jCookie, "token"); + Optional existingSessionOpt = sessionManager.getSessionByToken(token); + if (existingSessionOpt.isPresent()) { + BrowserSession existingSession = existingSessionOpt.get(); + // test if it is the same Odoo session_id + if (Optional.ofNullable(sessionId).equals(existingSession.getData().getOdooSessionId())) { + session = existingSession; + } + } + } + } catch (OpenemsException e) { + error = e.getMessage(); + } + + if (session == null) { + // create new session if no existing one was found + BrowserSessionData sessionData = new BrowserSessionData(); + sessionData.setOdooSessionId(sessionId); + session = sessionManager.createNewSession(sessionData); + } + + // check Odoo session and refresh info from Odoo + try { + Metadata.instance().getInfoWithSession(session); + } catch (OpenemsException e) { + error = e.getMessage(); + } + + // check if the session is now valid and send reply to browser + BrowserSessionData data = session.getData(); + if (error.isEmpty() && session.isValid()) { + // add isOnline information + OpenemsWebsocketSingleton openemsWebsocket = OpenemsWebsocket.instance(); + for (Device device : data.getDevices()) { + device.setOnline(openemsWebsocket.isOpenemsWebsocketConnected(device.getName())); + } + + // send connection successful to browser + JsonObject jReply = DefaultMessages.browserConnectionSuccessfulReply(session.getToken(), Optional.empty(), + data.getDevices()); + log.info("Browser connected. User [" + data.getUserId().orElse(-1) + "] Session [" + + data.getOdooSessionId().orElse("") + "]"); + WebSocketUtils.send(websocket, jReply); + + // add websocket to local cache + this.websockets.forcePut(websocket, session); + + } else { + // send connection failed to browser + JsonObject jReply = DefaultMessages.browserConnectionFailedReply(); + WebSocketUtils.send(websocket, jReply); + log.info("Browser connection failed. User [" + data.getUserId().orElse(-1) + "] Session [" + + data.getOdooSessionId().orElse("") + "] Error [" + error + "]"); + + websocket.closeConnection(CloseFrame.REFUSE, error); + } + } + + /** + * Message event of websocket. Handles a new message. + */ + @Override + protected void onMessage(WebSocket websocket, JsonObject jMessage, Optional jMessageIdOpt, + Optional deviceNameOpt) { + /* + * With existing device name + */ + if (deviceNameOpt.isPresent()) { + String deviceName = deviceNameOpt.get(); + /* + * Query historic data + */ + if (jMessage.has("historicData")) { + // parse deviceId + Matcher matcher = Pattern.compile("\\d+").matcher(deviceName); // extracts '0' from 'openems0' + matcher.find(); + Optional deviceIdOpt = Optional.ofNullable(Integer.valueOf(matcher.group())); + JsonArray jMessageId = jMessageIdOpt.get(); + try { + JsonObject jHistoricData = JsonUtils.getAsJsonObject(jMessage, "historicData"); + JsonObject jReply = WebSocketUtils.historicData(jMessageId, jHistoricData, deviceIdOpt, + Timedata.instance()); + WebSocketUtils.send(websocket, jReply); + } catch (OpenemsException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + /* + * Forward to OpenEMS Edge + */ + if (jMessage.has("config") || jMessage.has("currentData") || jMessage.has("log")) { + try { + forwardMessageToOpenems(websocket, jMessage, deviceName); + } catch (OpenemsException e) { + WebSocketUtils.send(websocket, DefaultMessages.notification(Notification.EDGE_UNABLE_TO_FORWARD, + deviceName, e.getMessage())); + log.error(deviceName + ": Unable to forward message: " + e.getMessage()); + } + } + } + } + + /** + * Forward message to OpenEMS websocket. + * + * @throws OpenemsException + */ + private void forwardMessageToOpenems(WebSocket websocket, JsonObject jMessage, String deviceName) + throws OpenemsException { + // remove device from message + if (jMessage.has("device")) { + jMessage.remove("device"); + } + + // add session token to message id for identification + BrowserSession session = this.websockets.get(websocket); + JsonArray jId; + if (jMessage.has("id")) { + jId = JsonUtils.getAsJsonArray(jMessage, "id"); + } else { + jId = new JsonArray(); + } + jId.add(session.getToken()); + jMessage.add("id", jId); + + // get OpenEMS websocket and forward message + Optional openemsWebsocketOpt = OpenemsWebsocket.instance().getOpenemsWebsocket(deviceName); + if (openemsWebsocketOpt.isPresent()) { + WebSocket openemsWebsocket = openemsWebsocketOpt.get(); + if (WebSocketUtils.send(openemsWebsocket, jMessage)) { + return; + } else { + throw new OpenemsException("Sending failed"); + } + } else { + throw new OpenemsException("Device is not connected."); + } + } + + // TODO notification handling + // /** + // * Generates a generic notification message + // * + // * @param message + // * @return + // */ + // private JsonObject generateNotification(String message) { + // JsonObject j = new JsonObject(); + // JsonObject jNotification = new JsonObject(); + // jNotification.addProperty("message", message); + // j.add("notification", jNotification); + // return j; + // } + + // TODO system command + // /** + // * System command + // * + // * @param j + // */ + // private synchronized void system(String deviceName, JsonElement jSubscribeElement) { + // JsonObject j = new JsonObject(); + // j.add("system", jSubscribeElement); + // Optional openemsWebsocketOpt = ConnectionManager.instance() + // .getOpenemsWebsocketFromDeviceName(deviceName); + // if (!openemsWebsocketOpt.isPresent()) { + // log.warn("Trying to forward system call to [" + deviceName + "], but it is not online"); + // } + // WebSocket openemsWebsocket = openemsWebsocketOpt.get(); + // log.info(deviceName + ": forward system call to OpenEMS " + StringUtils.toShortString(j, 100)); + // WebSocketUtils.send(openemsWebsocket, j); + // } + + /** + * OpenEMS Websocket tells us, when the connection to an OpenEMS Edge is closed + * + * @param name + */ + public void openemsConnectionClosed(String name) { + for (BrowserSession session : this.sessionManager.getSessions()) { + for (Device device : session.getData().getDevices()) { + if (name.equals(device.getName())) { + WebSocket ws = this.websockets.inverse().get(session); + JsonObject j = DefaultMessages.notification(Notification.EDGE_CONNECTION_ClOSED, name); + WebSocketUtils.send(ws, j); + } + } + } + } + + /** + * OpenEMS Websocket tells us, when the connection to an OpenEMS Edge is openend + * + * @param name + */ + public void openemsConnectionOpened(String name) { + for (BrowserSession session : this.sessionManager.getSessions()) { + for (Device device : session.getData().getDevices()) { + if (name.equals(device.getName())) { + WebSocket ws = this.websockets.inverse().get(session); + JsonObject j = DefaultMessages.notification(Notification.EDGE_CONNECTION_OPENED, name); + WebSocketUtils.send(ws, j); + } + } + } + } +} diff --git a/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSession.java b/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSession.java new file mode 100644 index 00000000000..9fe35d3c150 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSession.java @@ -0,0 +1,15 @@ +package io.openems.backend.browserwebsocket.session; + +import io.openems.common.session.Session; + +public class BrowserSession extends Session { + + protected BrowserSession(String token, BrowserSessionData data) { + super(token, data); + } + + @Override + public String toString() { + return "User [" + getData().getUserId() + "] Session [" + getData().getOdooSessionId() + "]"; + } +} diff --git a/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSessionData.java b/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSessionData.java new file mode 100644 index 00000000000..947fe58db86 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSessionData.java @@ -0,0 +1,38 @@ +package io.openems.backend.browserwebsocket.session; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import io.openems.common.session.SessionData; +import io.openems.common.types.Device; + +public class BrowserSessionData extends SessionData { + private Optional userId = Optional.empty(); + private Optional odooSessionId = Optional.empty(); + private List devices = new ArrayList<>(); + + public Optional getOdooSessionId() { + return odooSessionId; + } + + public void setOdooSessionId(String odooSessionId) { + this.odooSessionId = Optional.ofNullable(odooSessionId); + } + + public void setDevices(List deviceInfos) { + this.devices = deviceInfos; + } + + public void setUserId(Integer userId) { + this.userId = Optional.of(userId); + } + + public Optional getUserId() { + return userId; + } + + public List getDevices() { + return devices; + } +} diff --git a/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSessionManager.java b/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSessionManager.java new file mode 100644 index 00000000000..b91f4ce04a8 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/browserwebsocket/session/BrowserSessionManager.java @@ -0,0 +1,11 @@ +package io.openems.backend.browserwebsocket.session; + +import io.openems.common.session.SessionManager; + +public class BrowserSessionManager extends SessionManager { + + @Override + public BrowserSession _createNewSession(String token, BrowserSessionData data) { + return new BrowserSession(token, data); + } +} diff --git a/backend/src/main/java/io/openems/backend/core/ConnectionManager.java b/backend/src/main/java/io/openems/backend/core/ConnectionManager.java new file mode 100644 index 00000000000..e6f09880f3d --- /dev/null +++ b/backend/src/main/java/io/openems/backend/core/ConnectionManager.java @@ -0,0 +1,24 @@ +package io.openems.backend.core; + +/** + * Provider for ConnectionManager singleton + * + * @author stefan.feilmeier + * + */ +public class ConnectionManager { + + private static ConnectionManagerSingleton instance = null; + + /** + * Returns the singleton instance + * + * @return + */ + public static synchronized ConnectionManagerSingleton instance() { + if (ConnectionManager.instance == null) { + ConnectionManager.instance = new ConnectionManagerSingleton(); + } + return ConnectionManager.instance; + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/core/ConnectionManagerSingleton.java b/backend/src/main/java/io/openems/backend/core/ConnectionManagerSingleton.java new file mode 100644 index 00000000000..9129e2e2177 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/core/ConnectionManagerSingleton.java @@ -0,0 +1,81 @@ +package io.openems.backend.core; + +import java.util.Optional; + +import org.java_websocket.WebSocket; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; + +import io.openems.backend.utilities.ManyToMany; +import io.openems.common.session.Session; + +public class ConnectionManagerSingleton { + + protected ConnectionManagerSingleton() {}; + + /** + * Stores active websockets to browsers + */ + private final BiMap browserWebsockets = Maps.synchronizedBiMap(HashBiMap.create()); + + /** + * Stores active websockets to openems devices (value = deviceName, e.g. 'fems5') + */ + private final BiMap openemsWebsockets = Maps.synchronizedBiMap(HashBiMap.create()); + + /** + * Stores active interconnections between browser (key) and openems (value) websockets + */ + private final ManyToMany websocketInterconnection = new ManyToMany<>(); + + /* + * Helper methods for Browser <-> OpenEMS interconnection + */ + public void addWebsocketInterconnection(WebSocket browserWebsocket, WebSocket openemsWebsocket) { + websocketInterconnection.put(browserWebsocket, openemsWebsocket); + } + + public void removeWebsocketInterconnection(WebSocket browserWebsocket, WebSocket openemsWebsocket) { + websocketInterconnection.remove(browserWebsocket, openemsWebsocket); + } + + /* + * Helper methods for Browser websockets + */ + public void addBrowserWebsocket(WebSocket websocket, Session session) { + this.browserWebsockets.put(websocket, session); + } + + public void removeBrowserWebsocket(WebSocket websocket) { + this.browserWebsockets.remove(websocket); + this.websocketInterconnection.removeAllKeys(websocket); + } + + /* + * Helper methods for OpenEMS websockets + */ + public void addOpenemsWebsocket(WebSocket websocket, String deviceName) { + this.openemsWebsockets.put(websocket, deviceName); + } + + public void removeOpenemsWebsocket(WebSocket websocket) { + this.openemsWebsockets.remove(websocket); + this.websocketInterconnection.removeAllValues(websocket); + } + + public Optional getDeviceNameFromOpenemsWebsocket(WebSocket websocket) { + String deviceName = this.openemsWebsockets.get(websocket); + return Optional.ofNullable(deviceName); + } + + public Optional getOpenemsWebsocketFromDeviceName(String deviceName) { + WebSocket websocket = this.openemsWebsockets.inverse().get(deviceName); + return Optional.ofNullable(websocket); + } + + public boolean isOpenemsWebsocketConnected(String deviceName) { + return this.openemsWebsockets.containsValue(deviceName); + } +} diff --git a/backend/src/main/java/io/openems/backend/metadata/Metadata.java b/backend/src/main/java/io/openems/backend/metadata/Metadata.java new file mode 100644 index 00000000000..c0dd8b67d48 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/Metadata.java @@ -0,0 +1,50 @@ +package io.openems.backend.metadata; + +import io.openems.backend.metadata.api.MetadataSingleton; +import io.openems.backend.metadata.dummy.MetadataDummySingleton; +import io.openems.backend.metadata.odoo.OdooSingleton; + +/** + * Provider for Metadata singleton + * + * @author stefan.feilmeier + * + */ +public class Metadata { + + private static MetadataSingleton instance = null; + + /** + * Initialize Odoo object + * + * @param port + * @throws Exception + */ + public static synchronized void initializeOdoo(String url, int port, String database, String username, + String password) throws Exception { + if (url == null || database == null || username == null || password == null) { + throw new Exception("Config missing: database [" + database + "], url [" + url + "], port [" + port + + "] username [" + username + "], password [" + password + "]"); + } + Metadata.instance = new OdooSingleton(url, port, database, username, password); + } + + /** + * Initialize Dummy provider + * + * @param port + * @throws Exception + */ + public static synchronized void initializeDummy() { + Metadata.instance = new MetadataDummySingleton(); + } + + /** + * Returns the singleton instance + * + * @return + */ + public static synchronized MetadataSingleton instance() { + return Metadata.instance; + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/metadata/api/MetadataSingleton.java b/backend/src/main/java/io/openems/backend/metadata/api/MetadataSingleton.java new file mode 100644 index 00000000000..ad5b52af5ac --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/api/MetadataSingleton.java @@ -0,0 +1,11 @@ +package io.openems.backend.metadata.api; + +import io.openems.backend.browserwebsocket.session.BrowserSession; +import io.openems.backend.metadata.api.device.MetadataDeviceModel; +import io.openems.common.exceptions.OpenemsException; + +public interface MetadataSingleton { + public void getInfoWithSession(BrowserSession session) throws OpenemsException; + + public MetadataDeviceModel getDeviceModel(); +} diff --git a/backend/src/main/java/io/openems/backend/metadata/api/device/MetadataDevice.java b/backend/src/main/java/io/openems/backend/metadata/api/device/MetadataDevice.java new file mode 100644 index 00000000000..2a9ce241cf7 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/api/device/MetadataDevice.java @@ -0,0 +1,39 @@ +package io.openems.backend.metadata.api.device; + +import java.util.Optional; + +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsException; + +public interface MetadataDevice { + + Integer getId(); + + Optional getNameNumber(); + + String getName(); + + String getComment(); + + String getState(); + + String getProductType(); + + JsonObject getOpenemsConfig(); + + void setOpenemsConfig(JsonObject j); + + void setState(String active); + + void setSoc(int value); + + void setLastMessage(); + + void setLastUpdate(); + + void setIpV4(String value); + + void writeObject() throws OpenemsException; + +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/metadata/api/device/MetadataDeviceModel.java b/backend/src/main/java/io/openems/backend/metadata/api/device/MetadataDeviceModel.java new file mode 100644 index 00000000000..a8b3bcf0b7b --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/api/device/MetadataDeviceModel.java @@ -0,0 +1,18 @@ +package io.openems.backend.metadata.api.device; + +import java.util.Optional; + +import io.openems.common.exceptions.OpenemsException; + +public interface MetadataDeviceModel { + /** + * Gets the device for this apikey. + * + * Note: if there is more than one matching device it returns the first match. + * + * @param apikey + * @return device or null + * @throws OpenemsException + */ + public Optional getDeviceForApikey(String apikey) throws OpenemsException; +} diff --git a/backend/src/main/java/io/openems/backend/metadata/dummy/MetadataDummySingleton.java b/backend/src/main/java/io/openems/backend/metadata/dummy/MetadataDummySingleton.java new file mode 100644 index 00000000000..5a10c37553c --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/dummy/MetadataDummySingleton.java @@ -0,0 +1,52 @@ +package io.openems.backend.metadata.dummy; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.backend.browserwebsocket.session.BrowserSession; +import io.openems.backend.browserwebsocket.session.BrowserSessionData; +import io.openems.backend.metadata.api.MetadataSingleton; +import io.openems.backend.metadata.api.device.MetadataDeviceModel; +import io.openems.backend.metadata.dummy.device.MetadataDummyDeviceModel; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.session.SessionData; +import io.openems.common.types.Device; + +public class MetadataDummySingleton implements MetadataSingleton { + private final Logger log = LoggerFactory.getLogger(MetadataDummySingleton.class); + + private MetadataDummyDeviceModel deviceModel; + + public MetadataDummySingleton() { + this.deviceModel = new MetadataDummyDeviceModel(); + } + + /** + * Returns static device data + * + * @return + * @throws OpenemsException + */ + @Override + public void getInfoWithSession(BrowserSession session) throws OpenemsException { + SessionData sessionData = session.getData(); + BrowserSessionData data = (BrowserSessionData) sessionData; + data.setUserId(0); + // Allow access to all available devices + List deviceInfos = new ArrayList<>(); + for (Device device : this.deviceModel.getAllDevices()) { + deviceInfos.add(device); + } + data.setDevices(deviceInfos); + session.setValid(); + return; + } + + @Override + public MetadataDeviceModel getDeviceModel() { + return deviceModel; + } +} diff --git a/backend/src/main/java/io/openems/backend/metadata/dummy/device/MetadataDummyDevice.java b/backend/src/main/java/io/openems/backend/metadata/dummy/device/MetadataDummyDevice.java new file mode 100644 index 00000000000..5f1debc78eb --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/dummy/device/MetadataDummyDevice.java @@ -0,0 +1,109 @@ +package io.openems.backend.metadata.dummy.device; + +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import io.openems.backend.metadata.api.device.MetadataDevice; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.Device; + +public class MetadataDummyDevice extends Device implements MetadataDevice { + + private final Logger log = LoggerFactory.getLogger(MetadataDummyDevice.class); + + private final int id; + private final String apikey; + + public MetadataDummyDevice(String name, String comment, String producttype, String role, int id, String apikey) { + super(name, comment, producttype, role); + this.id = id; + this.apikey = apikey; + } + + @Override + public Integer getId() { + return this.id; + } + + public String getApikey() { + return apikey; + } + + @Override + public Optional getNameNumber() { + try { + Matcher matcher = Pattern.compile("\\d+").matcher(this.getName()); // extracts '0' from 'openems0' + matcher.find(); + return Optional.ofNullable(Integer.valueOf(matcher.group())); + } catch (Exception e) { + return Optional.empty(); + } + } + + @Override + public String getName() { + return super.getName(); + } + + @Override + public String getComment() { + return super.getComment(); + } + + @Override + public String getState() { + return "active"; + } + + @Override + public String getProductType() { + return super.getProducttype(); + } + + @Override + public JsonObject getOpenemsConfig() { + return new JsonObject(); + } + + @Override + public void setOpenemsConfig(JsonObject j) { + log.info("Metadata Dummy. Would set OpenEMS config: " + j.toString()); + } + + @Override + public void setState(String state) { + log.info("Metadata Dummy. Would set state: " + state); + } + + @Override + public void setSoc(int value) { + log.info("Metadata Dummy. Would set SOC: " + value); + } + + @Override + public void setLastMessage() { + log.info("Metadata Dummy. Would set LastMessage"); + } + + @Override + public void setLastUpdate() { + log.debug("Metadata Dummy. Would set LastUpdate"); + } + + @Override + public void setIpV4(String value) { + log.info("Metadata Dummy. Would set IPv4: " + value); + } + + @Override + public void writeObject() throws OpenemsException { + log.debug("Metadata Dummy. Would write object"); + } + +} diff --git a/backend/src/main/java/io/openems/backend/metadata/dummy/device/MetadataDummyDeviceModel.java b/backend/src/main/java/io/openems/backend/metadata/dummy/device/MetadataDummyDeviceModel.java new file mode 100644 index 00000000000..4736f63eebf --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/dummy/device/MetadataDummyDeviceModel.java @@ -0,0 +1,40 @@ +package io.openems.backend.metadata.dummy.device; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import io.openems.backend.metadata.api.device.MetadataDevice; +import io.openems.backend.metadata.api.device.MetadataDeviceModel; +import io.openems.common.exceptions.OpenemsException; + +public class MetadataDummyDeviceModel implements MetadataDeviceModel { + + private static int lastId = 0; + + private final List devices = new ArrayList<>(); + + public MetadataDummyDeviceModel() {} + + @Override + public Optional getDeviceForApikey(String apikey) throws OpenemsException { + for (MetadataDummyDevice device : this.devices) { + if (device.getApikey().equals(apikey)) { + return Optional.of(device); + } + } + return Optional.of(addNewDevice(apikey)); + } + + private MetadataDevice addNewDevice(String apikey) { + int id = MetadataDummyDeviceModel.lastId++; + MetadataDummyDevice device = new MetadataDummyDevice("openems" + id, "OpenEMS " + id, "Dummy Product", "admin", + id, apikey); + this.devices.add(device); + return device; + } + + public List getAllDevices() { + return this.devices; + } +} diff --git a/backend/src/main/java/io/openems/backend/metadata/odoo/OdooModel.java b/backend/src/main/java/io/openems/backend/metadata/odoo/OdooModel.java new file mode 100644 index 00000000000..2f54cee9d6d --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/odoo/OdooModel.java @@ -0,0 +1,78 @@ +package io.openems.backend.metadata.odoo; + +import java.util.List; + +import org.apache.xmlrpc.XmlRpcException; + +import com.abercap.odoo.FilterCollection; +import com.abercap.odoo.ObjectAdapter; +import com.abercap.odoo.OdooApiException; +import com.abercap.odoo.Row; +import com.abercap.odoo.RowCollection; +import com.abercap.odoo.Session; + +/** + * Represents an abstract model in Odoo object relational mapper + * + * @author stefan.feilmeier + * + * @param + */ +public abstract class OdooModel { + private final ObjectAdapter oa; + + /** + * Initializes the model with a Odoo session + * + * @param session + * @throws XmlRpcException + * @throws OdooApiException + */ + public OdooModel(Session session) throws XmlRpcException, OdooApiException { + oa = session.getObjectAdapter(getModelId()); + } + + /** + * Reads all objects of this model + * + * @return + * @throws XmlRpcException + * @throws OdooApiException + */ + public List readAllObjects() throws XmlRpcException, OdooApiException { + FilterCollection filter = new FilterCollection(); + RowCollection rows = oa.searchAndReadObject(filter, getFields()); + return convertRowCollectionToList(rows); + } + + /** + * Reads all objects of this model + * + * @return + * @throws XmlRpcException + * @throws OdooApiException + */ + public List readObjectsWhere(String fieldName, String comparison, Object value) + throws XmlRpcException, OdooApiException { + FilterCollection filter = new FilterCollection(); + filter.add(fieldName, comparison, value); + RowCollection rows = oa.searchAndReadObject(filter, getFields()); + return convertRowCollectionToList(rows); + } + + /** + * Converts a RowCollection to a list of POJOs + * + * @param rows + * @return + */ + protected abstract List convertRowCollectionToList(RowCollection rows); + + protected void writeObject(Row row, boolean changesOnly) throws OdooApiException, XmlRpcException { + oa.writeObject(row, changesOnly); + } + + protected abstract String getModelId(); + + protected abstract String[] getFields(); +} diff --git a/backend/src/main/java/io/openems/femsserver/odoo/OdooObject.java b/backend/src/main/java/io/openems/backend/metadata/odoo/OdooObject.java similarity index 59% rename from backend/src/main/java/io/openems/femsserver/odoo/OdooObject.java rename to backend/src/main/java/io/openems/backend/metadata/odoo/OdooObject.java index 53547e75c7c..5a61f11b21f 100644 --- a/backend/src/main/java/io/openems/femsserver/odoo/OdooObject.java +++ b/backend/src/main/java/io/openems/backend/metadata/odoo/OdooObject.java @@ -1,4 +1,4 @@ -package io.openems.femsserver.odoo; +package io.openems.backend.metadata.odoo; import java.time.Instant; import java.time.LocalDateTime; @@ -14,6 +14,14 @@ import com.abercap.odoo.OdooApiException; import com.abercap.odoo.Row; +import io.openems.common.exceptions.OpenemsException; + +/** + * Represents a record object in Odoo + * + * @author stefan.feilmeier + * + */ public abstract class OdooObject { private final Logger log = LoggerFactory.getLogger(OdooObject.class); private Row row; @@ -31,11 +39,33 @@ public void refreshFrom(OdooObject o) { this.model = o.model; } + /** + * Gets the value of the given field + * + * @param fieldName + * @return + */ public Object get(String fieldName) { return this.row.get(fieldName); } - public void put(String fieldName, Object value) { + /** + * Gets the value of the given field or a defaultValue (to avoid null) + * + * @param fieldName + * @param defaultValue + * @return + */ + public Object getOr(String fieldName, Object defaultValue) { + Object value = this.row.get(fieldName); + if (value == null) { + return defaultValue; + } else { + return value; + } + } + + protected void put(String fieldName, Object value) { try { this.row.put(fieldName, value); } catch (OdooApiException e) { @@ -44,11 +74,11 @@ public void put(String fieldName, Object value) { isChangedSinceLastWrite = true; } - public void writeObject() throws OdooApiException, XmlRpcException { + public void writeObject() throws OpenemsException { this.writeObject(true); } - public void writeObject(boolean changesOnly) throws OdooApiException, XmlRpcException { + public void writeObject(boolean changesOnly) throws OpenemsException { long now = System.currentTimeMillis(); try { if (isChangedSinceLastWrite && now - lastWrite > 60000) { @@ -56,12 +86,14 @@ public void writeObject(boolean changesOnly) throws OdooApiException, XmlRpcExce this.model.writeObject(this.row, changesOnly); this.lastWrite = now; } + } catch (OdooApiException | XmlRpcException e) { + throw new OpenemsException("Unable to write to Odoo: " + e.getMessage()); } finally { isChangedSinceLastWrite = false; } } - public Date odooCompatibleNow() { + protected Date odooCompatibleNow() { Instant instant = Instant.now(); int seconds = ZonedDateTime.of(LocalDateTime.ofInstant(instant, ZoneOffset.UTC), ZoneId.systemDefault()) .getOffset().getTotalSeconds(); diff --git a/backend/src/main/java/io/openems/backend/metadata/odoo/OdooSingleton.java b/backend/src/main/java/io/openems/backend/metadata/odoo/OdooSingleton.java new file mode 100644 index 00000000000..b213421a08f --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/odoo/OdooSingleton.java @@ -0,0 +1,127 @@ +package io.openems.backend.metadata.odoo; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.List; + +import com.abercap.odoo.Session; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import io.openems.backend.browserwebsocket.session.BrowserSession; +import io.openems.backend.browserwebsocket.session.BrowserSessionData; +import io.openems.backend.metadata.api.MetadataSingleton; +import io.openems.backend.metadata.api.device.MetadataDeviceModel; +import io.openems.backend.metadata.odoo.device.OdooDeviceModel; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.session.SessionData; +import io.openems.common.types.Device; +import io.openems.common.utils.JsonUtils; + +public class OdooSingleton implements MetadataSingleton { + private Session session; + private MetadataDeviceModel deviceModel; + private final String url; + + public OdooSingleton(String url, int port, String database, String username, String password) throws Exception { + this.session = new Session(url, port, database, username, password); + this.connect(); + this.deviceModel = new OdooDeviceModel(this.session); + this.url = "http://" + url + ":" + port; + } + + private void connect() throws Exception { + session.startSession(); + } + + @Override + public MetadataDeviceModel getDeviceModel() { + return deviceModel; + } + + /** + * Tries to authenticate at the Odoo server using a sessionId from a cookie. Updates the Session object accordingly. + * + * @param sessionId + * @return + * @throws OpenemsException + */ + @Override + public void getInfoWithSession(BrowserSession session) throws OpenemsException { + HttpURLConnection connection = null; + try { + // get session_id from Session + SessionData sessionData = session.getData(); + if (!(sessionData instanceof BrowserSessionData)) { + throw new OpenemsException("Session is of wrong type."); + } + BrowserSessionData data = (BrowserSessionData) sessionData; + if (!(data.getOdooSessionId().isPresent())) { + throw new OpenemsException("Session-ID is missing."); + } + String sessionId = data.getOdooSessionId().get(); + + // send request to Odoo + String charset = "US-ASCII"; + String query = String.format("session_id=%s", URLEncoder.encode(sessionId, charset)); + connection = (HttpURLConnection) new URL(this.url + "/openems_backend/info?" + query).openConnection(); + connection.setConnectTimeout(5000);// 5 secs + connection.setReadTimeout(5000);// 5 secs + connection.setRequestProperty("Accept-Charset", charset); + connection.setRequestMethod("POST"); + connection.setDoOutput(true); + connection.setRequestProperty("Content-Type", "application/json"); + + OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream()); + out.write("{}"); + out.flush(); + out.close(); + + InputStream is = connection.getInputStream(); + BufferedReader br = new BufferedReader(new InputStreamReader(is)); + String line = null; + while ((line = br.readLine()) != null) { + JsonObject j = (new JsonParser()).parse(line).getAsJsonObject(); + if (j.has("error")) { + JsonObject jError = JsonUtils.getAsJsonObject(j, "error"); + String errorMessage = JsonUtils.getAsString(jError, "message"); + throw new OpenemsException(errorMessage); + } + + if (j.has("result")) { + // parse the result + JsonObject jResult = JsonUtils.getAsJsonObject(j, "result"); + data.setUserId(JsonUtils.getAsInt(jResult, "user")); + JsonArray jDevices = JsonUtils.getAsJsonArray(jResult, "devices"); + List deviceInfos = new ArrayList<>(); + for (JsonElement jDevice : jDevices) { + deviceInfos.add(new Device( // + JsonUtils.getAsString(jDevice, "name"), // + JsonUtils.getAsString(jDevice, "comment"), // + JsonUtils.getAsString(jDevice, "producttype"), // + JsonUtils.getAsString(jDevice, "role"))); + } + data.setDevices(deviceInfos); + session.setValid(); + return; + } + } + } catch (IOException e) { + throw new OpenemsException(e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + throw new OpenemsException("No result from Odoo"); + } +} diff --git a/backend/src/main/java/io/openems/backend/metadata/odoo/device/Field.java b/backend/src/main/java/io/openems/backend/metadata/odoo/device/Field.java new file mode 100644 index 00000000000..b28a5a6770c --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/odoo/device/Field.java @@ -0,0 +1,15 @@ +package io.openems.backend.metadata.odoo.device; + +public class Field { + protected static final String ID = "id"; + protected static final String NAME = "name"; + protected static final String NAME_NUMBER = "name_number"; + protected static final String COMMENT = "comment"; + protected static final String SOC = "soc"; + protected static final String LASTMESSAGE = "lastmessage"; + protected static final String LASTUPDATE = "lastupdate"; + protected static final String IPV4 = "ipv4"; + protected static final String OPENEMS_CONFIG = "openems_config"; + protected static final String STATE = "state"; + protected static final String PRODUCT_TYPE = "producttype"; +} diff --git a/backend/src/main/java/io/openems/backend/metadata/odoo/device/OdooDevice.java b/backend/src/main/java/io/openems/backend/metadata/odoo/device/OdooDevice.java new file mode 100644 index 00000000000..b3b774cbf10 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/odoo/device/OdooDevice.java @@ -0,0 +1,158 @@ +package io.openems.backend.metadata.odoo.device; + +import java.util.Optional; + +import com.abercap.odoo.Row; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import io.openems.backend.metadata.api.device.MetadataDevice; +import io.openems.backend.metadata.odoo.OdooModel; +import io.openems.backend.metadata.odoo.OdooObject; + +public class OdooDevice extends OdooObject implements MetadataDevice { + public OdooDevice(OdooModel model, Row row) { + super(model, row); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getId() + */ + @Override + public Integer getId() { + return (Integer) get(Field.ID); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getNameNumber() + */ + @Override + public Optional getNameNumber() { + try { + return Optional.ofNullable(Integer.valueOf(Field.NAME_NUMBER)); + } catch (Exception e) { /* ignore */ } + return Optional.empty(); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getName() + */ + @Override + public String getName() { + return getOr(Field.NAME, "UNKNOWN").toString(); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getComment() + */ + @Override + public String getComment() { + return getOr(Field.COMMENT, "").toString(); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getState() + */ + @Override + public String getState() { + return getOr(Field.STATE, "").toString(); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getProductType() + */ + @Override + public String getProductType() { + return getOr(Field.PRODUCT_TYPE, "").toString(); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#getOpenemsConfig() + */ + @Override + public JsonObject getOpenemsConfig() { + Object config = get(Field.OPENEMS_CONFIG); + if (config != null) { + return (new JsonParser()).parse(get(Field.OPENEMS_CONFIG).toString()).getAsJsonObject(); + } else { + return new JsonObject(); + } + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#setOpenemsConfig(com.google.gson.JsonObject) + */ + @Override + public void setOpenemsConfig(JsonObject j) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + put(Field.OPENEMS_CONFIG, gson.toJson(j)); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#setState(java.lang.String) + */ + @Override + public void setState(String active) { + put(Field.STATE, active); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#setSoc(int) + */ + @Override + public void setSoc(int value) { + put(Field.SOC, value); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#setLastMessage() + */ + @Override + public void setLastMessage() { + put(Field.LASTMESSAGE, this.odooCompatibleNow()); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#setLastUpdate() + */ + @Override + public void setLastUpdate() { + put(Field.LASTUPDATE, this.odooCompatibleNow()); + } + + /* + * (non-Javadoc) + * + * @see io.openems.backend.metadata.odoo.device.Device#setIpV4(java.lang.String) + */ + @Override + public void setIpV4(String value) { + put(Field.IPV4, value); + } +} diff --git a/backend/src/main/java/io/openems/backend/metadata/odoo/device/OdooDeviceModel.java b/backend/src/main/java/io/openems/backend/metadata/odoo/device/OdooDeviceModel.java new file mode 100644 index 00000000000..82cfbff6dc1 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/metadata/odoo/device/OdooDeviceModel.java @@ -0,0 +1,58 @@ +package io.openems.backend.metadata.odoo.device; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.apache.xmlrpc.XmlRpcException; + +import com.abercap.odoo.OdooApiException; +import com.abercap.odoo.RowCollection; +import com.abercap.odoo.Session; + +import io.openems.backend.metadata.api.device.MetadataDevice; +import io.openems.backend.metadata.api.device.MetadataDeviceModel; +import io.openems.backend.metadata.odoo.OdooModel; +import io.openems.common.exceptions.OpenemsException; + +public class OdooDeviceModel extends OdooModel implements MetadataDeviceModel { + + public OdooDeviceModel(Session session) throws XmlRpcException, OdooApiException { + super(session); + } + + @Override + protected String getModelId() { + return "fems.device"; + } + + @Override + protected String[] getFields() { + return new String[] { Field.NAME, Field.NAME_NUMBER, Field.COMMENT, Field.SOC, Field.LASTMESSAGE, + Field.LASTUPDATE, Field.IPV4, Field.OPENEMS_CONFIG, Field.STATE, Field.PRODUCT_TYPE }; + } + + @Override + public Optional getDeviceForApikey(String apikey) throws OpenemsException { + List devices; + try { + devices = this.readObjectsWhere("apikey", "=", apikey); + } catch (XmlRpcException | OdooApiException e) { + throw new OpenemsException("Unable to find device for apikey: " + e.getMessage()); + } + if (devices.size() > 0) { + return Optional.of(devices.get(0)); + } else { + return Optional.empty(); + } + } + + @Override + protected List convertRowCollectionToList(RowCollection rows) { + List result = new ArrayList<>(); + rows.forEach(row -> { + result.add(new OdooDevice(this, row)); + }); + return result; + } +} diff --git a/backend/src/main/java/io/openems/backend/openemswebsocket/OpenemsWebsocket.java b/backend/src/main/java/io/openems/backend/openemswebsocket/OpenemsWebsocket.java new file mode 100644 index 00000000000..2f45e8086de --- /dev/null +++ b/backend/src/main/java/io/openems/backend/openemswebsocket/OpenemsWebsocket.java @@ -0,0 +1,32 @@ +package io.openems.backend.openemswebsocket; + +/** + * Provider for OpenemsWebsocketServer singleton + * + * @author stefan.feilmeier + * + */ +public class OpenemsWebsocket { + + private static OpenemsWebsocketSingleton instance; + + /** + * Initialize and start the Websocketserver + * + * @param port + * @throws Exception + */ + public static synchronized void initialize(int port) throws Exception { + OpenemsWebsocket.instance = new OpenemsWebsocketSingleton(port); + OpenemsWebsocket.instance.start(); + } + + /** + * Returns the singleton instance + * + * @return + */ + public static synchronized OpenemsWebsocketSingleton instance() { + return OpenemsWebsocket.instance; + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/openemswebsocket/OpenemsWebsocketSingleton.java b/backend/src/main/java/io/openems/backend/openemswebsocket/OpenemsWebsocketSingleton.java new file mode 100644 index 00000000000..9ccf93c1db3 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/openemswebsocket/OpenemsWebsocketSingleton.java @@ -0,0 +1,258 @@ +package io.openems.backend.openemswebsocket; + +import java.util.Optional; + +import org.java_websocket.WebSocket; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.backend.browserwebsocket.BrowserWebsocket; +import io.openems.backend.metadata.Metadata; +import io.openems.backend.metadata.api.device.MetadataDevice; +import io.openems.backend.openemswebsocket.session.OpenemsSession; +import io.openems.backend.openemswebsocket.session.OpenemsSessionData; +import io.openems.backend.openemswebsocket.session.OpenemsSessionManager; +import io.openems.backend.timedata.Timedata; +import io.openems.backend.utilities.StringUtils; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.websocket.AbstractWebsocketServer; +import io.openems.common.websocket.DefaultMessages; +import io.openems.common.websocket.WebSocketUtils; + +/** + * Handles connections to OpenEMS-Devices. + * + * @author stefan.feilmeier + * + */ +public class OpenemsWebsocketSingleton + extends AbstractWebsocketServer { + private final Logger log = LoggerFactory.getLogger(OpenemsWebsocketSingleton.class); + + protected OpenemsWebsocketSingleton(int port) throws Exception { + super(port, new OpenemsSessionManager()); + } + + /** + * Open event of websocket. Parses the "apikey" and stores it in a new Session. + */ + @Override + public void onOpen(WebSocket websocket, ClientHandshake handshake) { + super.onOpen(websocket, handshake); + String apikey = ""; + String deviceName = ""; + try { + // get apikey from handshake + Optional apikeyOpt = parseApikeyFromHandshake(handshake); + if (!apikeyOpt.isPresent()) { + throw new OpenemsException("Apikey is missing in handshake"); + } + apikey = apikeyOpt.get(); + + // get device for apikey + Optional deviceOpt = Metadata.instance().getDeviceModel().getDeviceForApikey(apikey); + if (!deviceOpt.isPresent()) { + throw new OpenemsException("Unable to find device for apikey [" + apikey + "]"); + } + MetadataDevice device = deviceOpt.get(); + deviceName = device.getName(); + + // create new session + OpenemsSessionData sessionData = new OpenemsSessionData(device); + OpenemsSession session = sessionManager.createNewSession(apikey, sessionData); + session.setValid(); + + // send successful reply to openems + JsonObject jReply = DefaultMessages.openemsConnectionSuccessfulReply(); + log.info("OpenEMS connected. Device [" + deviceName + "]"); + WebSocketUtils.send(websocket, jReply); + + // add websocket to local cache + this.websockets.forcePut(websocket, session); + + try { + // set device active (in Odoo) + if (device.getState().equals("inactive")) { + device.setState("active"); + } + device.setLastMessage(); + device.writeObject(); + } catch (OpenemsException e) { + // this error does not stop the connection + log.warn(e.getMessage()); + } + + // announce browserWebsocket that this OpenEMS Edge was connected + BrowserWebsocket.instance().openemsConnectionOpened(deviceName); + + } catch (OpenemsException e) { + // send connection failed to OpenEMS + JsonObject jReply = DefaultMessages.openemsConnectionFailedReply(e.getMessage()); + WebSocketUtils.send(websocket, jReply); + // close websocket + websocket.closeConnection(CloseFrame.REFUSE, + "OpenEMS connection failed. Device [" + deviceName + "] Apikey [" + apikey + "]"); + } + } + + /** + * Close event of websocket. Removes the session and the websocket. + */ + @Override + public void onClose(WebSocket websocket, int code, String reason, boolean remote) { + OpenemsSession session = this.websockets.get(websocket); + sessionManager.removeSession(session); + super.onClose(websocket, code, reason, remote); + } + + /** + * Message event of websocket. Handles a new message. At this point the device is already authenticated. + */ + @Override + protected void onMessage(WebSocket websocket, JsonObject jMessage, Optional jMessageIdOpt, + Optional deviceNameOpt) { + MetadataDevice device = websockets.get(websocket).getData().getDevice(); + + // if (!jMessage.has("timedata") && !jMessage.has("currentData") && !jMessage.has("log") + // && !jMessage.has("config")) { + // log.info("Received from " + device.getName() + ": " + jMessage.toString()); + // } + + // Is this a reply? + if (jMessage.has("id")) { + forwardReplyToBrowser(websocket, device.getName(), jMessage); + } + + /* + * New timestamped data + */ + if (jMessage.has("timedata")) { + timedata(device, jMessage.get("timedata")); + } + + // Save data to Odoo + try { + device.writeObject(); + } catch (OpenemsException e) { + log.error(device.getName() + ": " + e.getMessage()); + } + } + + private void forwardReplyToBrowser(WebSocket openemsWebsocket, String deviceName, JsonObject jMessage) { + try { + // get browser websocket + JsonArray jId = JsonUtils.getAsJsonArray(jMessage, "id"); + String token = JsonUtils.getAsString(jId.get(jId.size() - 1)); + Optional browserWebsocketOpt = BrowserWebsocket.instance().getWebsocketByToken(token); + if (!browserWebsocketOpt.isPresent()) { + log.warn("Browser websocket is not connected: " + jMessage); + if (jMessage.has("currentData")) { + // unsubscribe obsolete browser websocket + WebSocketUtils.send(openemsWebsocket, DefaultMessages.currentDataSubscribe(jId, new JsonObject())); + } + if (jMessage.has("log")) { + // unsubscribe obsolete browser websocket + WebSocketUtils.send(openemsWebsocket, DefaultMessages.logUnsubscribe(jId)); + } + return; + } + WebSocket browserWebsocket = browserWebsocketOpt.get(); + + // remove token from message id + jId.remove(jId.size() - 1); + jMessage.add("id", jId); + // always add device name + jMessage.addProperty("device", deviceName); + + // send + WebSocketUtils.send(browserWebsocket, jMessage); + } catch (OpenemsException e) { + log.warn(e.getMessage()); + } + } + + private void timedata(MetadataDevice device, JsonElement jTimedataElement) { + try { + JsonObject jTimedata = JsonUtils.getAsJsonObject(jTimedataElement); + // Write to InfluxDB + try { + Timedata.instance().write(device.getNameNumber(), jTimedata); + log.debug(device.getName() + ": wrote " + jTimedata.entrySet().size() + " timestamps " + + StringUtils.toShortString(jTimedata, 120)); + } catch (Exception e) { + log.error("No InfluxDB-connection: ", e); + } + // Write some data to Odoo + // This is only to provide feedback for FENECON Service-Team that the device is online. + device.setLastUpdate(); + device.setLastMessage(); + jTimedata.entrySet().forEach(entry -> { + try { + JsonObject jChannels = JsonUtils.getAsJsonObject(entry.getValue()); + if (jChannels.has("ess0/Soc")) { + int soc = JsonUtils.getAsPrimitive(jChannels, "ess0/Soc").getAsInt(); + device.setSoc(soc); + } + if (jChannels.has("system0/PrimaryIpAddress")) { + String ipv4 = JsonUtils.getAsPrimitive(jChannels, "system0/PrimaryIpAddress").getAsString(); + device.setIpV4(ipv4); + } + } catch (OpenemsException e) { + log.error(e.getMessage()); + } + }); + } catch (OpenemsException e) { + log.error(e.getMessage()); + } + } + + /** + * Parses the apikey from websocket onOpen handshake + * + * @param handshake + * @return + */ + private Optional parseApikeyFromHandshake(ClientHandshake handshake) { + if (handshake.hasFieldValue("apikey")) { + String apikey = handshake.getFieldValue("apikey"); + return Optional.ofNullable(apikey); + } + return Optional.empty(); + } + + /** + * Returns true if this device is currently connected + * + * @param name + * @return + */ + public boolean isOpenemsWebsocketConnected(String deviceName) { + Optional sessionOpt = this.sessionManager.getSessionByDeviceName(deviceName); + if (!sessionOpt.isPresent()) { + return false; + } + return true; + } + + /** + * Returns the OpenemsWebsocket for the given device + * + * @param name + * @return + */ + public Optional getOpenemsWebsocket(String deviceName) { + Optional sessionOpt = this.sessionManager.getSessionByDeviceName(deviceName); + if (!sessionOpt.isPresent()) { + return Optional.empty(); + } + OpenemsSession session = sessionOpt.get(); + return Optional.ofNullable(this.websockets.inverse().get(session)); + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSession.java b/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSession.java new file mode 100644 index 00000000000..8ab9edbdacc --- /dev/null +++ b/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSession.java @@ -0,0 +1,15 @@ +package io.openems.backend.openemswebsocket.session; + +import io.openems.common.session.Session; + +public class OpenemsSession extends Session { + + protected OpenemsSession(String token, OpenemsSessionData data) { + super(token, data); + } + + @Override + public String toString() { + return "Device [" + getData().getDevice().getName() + "]"; + } +} diff --git a/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSessionData.java b/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSessionData.java new file mode 100644 index 00000000000..a24d4f697de --- /dev/null +++ b/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSessionData.java @@ -0,0 +1,16 @@ +package io.openems.backend.openemswebsocket.session; + +import io.openems.backend.metadata.api.device.MetadataDevice; +import io.openems.common.session.SessionData; + +public class OpenemsSessionData extends SessionData { + private final MetadataDevice device; + + public OpenemsSessionData(MetadataDevice device) { + this.device = device; + } + + public MetadataDevice getDevice() { + return device; + } +} diff --git a/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSessionManager.java b/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSessionManager.java new file mode 100644 index 00000000000..9f528d4010d --- /dev/null +++ b/backend/src/main/java/io/openems/backend/openemswebsocket/session/OpenemsSessionManager.java @@ -0,0 +1,40 @@ +package io.openems.backend.openemswebsocket.session; + +import java.util.Optional; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; + +import io.openems.common.session.SessionManager; + +public class OpenemsSessionManager extends SessionManager { + + // mapping between Token (apikey) and device name + private final BiMap token2name = Maps.synchronizedBiMap(HashBiMap.create()); + + @Override + public OpenemsSession _createNewSession(String token, OpenemsSessionData data) { + return new OpenemsSession(token, data); + } + + @Override + protected void _putSession(String token, OpenemsSession session) { + super._putSession(token, session); + this.token2name.put(token, session.getData().getDevice().getName()); + } + + @Override + protected void _removeSession(String token) { + super._removeSession(token); + this.token2name.remove(token); + } + + public Optional getSessionByDeviceName(String name) { + String token = this.token2name.inverse().get(name); + if (token == null) { + return Optional.empty(); + } + return super.getSessionByToken(token); + } +} diff --git a/backend/src/main/java/io/openems/backend/timedata/Timedata.java b/backend/src/main/java/io/openems/backend/timedata/Timedata.java new file mode 100644 index 00000000000..8c6cfb9985e --- /dev/null +++ b/backend/src/main/java/io/openems/backend/timedata/Timedata.java @@ -0,0 +1,50 @@ +package io.openems.backend.timedata; + +import io.openems.backend.timedata.api.TimedataSingleton; +import io.openems.backend.timedata.dummy.TimedataDummySingleton; +import io.openems.backend.timedata.influx.InfluxdbSingleton; + +/** + * Provider for Timedata singleton + * + * @author stefan.feilmeier + * + */ +public class Timedata { + + private static TimedataSingleton instance = null; + + /** + * Initialize InfluxDB object + * + * @param port + * @throws Exception + */ + public static void initializeInfluxdb(String database, String url, int port, String username, String password) + throws Exception { + if (database == null || url == null || username == null || password == null) { + throw new Exception("Config missing: database [" + database + "], url [" + url + "], port [" + port + + "] username [" + username + "], password [" + password + "]"); + } + Timedata.instance = new InfluxdbSingleton(database, url, port, username, password); + } + + /** + * Initialize Dummy provider + * + * @param port + * @throws Exception + */ + public static synchronized void initializeDummy() { + Timedata.instance = new TimedataDummySingleton(); + } + + /** + * Returns the singleton instance + * + * @return + */ + public static synchronized TimedataSingleton instance() { + return Timedata.instance; + } +} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/backend/timedata/api/TimedataSingleton.java b/backend/src/main/java/io/openems/backend/timedata/api/TimedataSingleton.java new file mode 100644 index 00000000000..68da7b15237 --- /dev/null +++ b/backend/src/main/java/io/openems/backend/timedata/api/TimedataSingleton.java @@ -0,0 +1,27 @@ +package io.openems.backend.timedata.api; + +import java.util.Optional; + +import com.google.gson.JsonObject; + +import io.openems.common.api.TimedataSource; + +public interface TimedataSingleton extends TimedataSource { + /** + * Takes a JsonObject and writes the points to database. + * + *
+	 * 	{
+	 * 		"timestamp1" {
+	 * 			"channel1": value,
+	 * 			"channel2": value
+	 * 		},
+	 * 		"timestamp2" {
+	 * 			"channel1": value,
+	 * 			"channel2": value
+	 *		}
+	 *	}
+	 * 
+ */ + public void write(Optional deviceId, JsonObject jData); +} diff --git a/backend/src/main/java/io/openems/backend/timedata/dummy/TimedataDummySingleton.java b/backend/src/main/java/io/openems/backend/timedata/dummy/TimedataDummySingleton.java new file mode 100644 index 00000000000..110b4fa377c --- /dev/null +++ b/backend/src/main/java/io/openems/backend/timedata/dummy/TimedataDummySingleton.java @@ -0,0 +1,31 @@ +package io.openems.backend.timedata.dummy; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.backend.timedata.api.TimedataSingleton; +import io.openems.backend.utilities.StringUtils; +import io.openems.common.exceptions.OpenemsException; + +public class TimedataDummySingleton implements TimedataSingleton { + private final Logger log = LoggerFactory.getLogger(TimedataDummySingleton.class); + + @Override + public void write(Optional deviceId, JsonObject jData) { + log.debug("Timedata Dummy. Would write data: " + StringUtils.toShortString(jData, 100)); + } + + @Override + public JsonArray queryHistoricData(Optional deviceIdOpt, ZonedDateTime fromDate, ZonedDateTime toDate, + JsonObject channels, int resolution) throws OpenemsException { + log.info("Timedata Dummy. Would query data: From [" + fromDate + "], To [" + toDate + "] Channels [" + channels + + "] Resolution [" + resolution + "]"); + return new JsonArray(); + } +} diff --git a/backend/src/main/java/io/openems/backend/timedata/influx/InfluxdbSingleton.java b/backend/src/main/java/io/openems/backend/timedata/influx/InfluxdbSingleton.java new file mode 100644 index 00000000000..77840de204a --- /dev/null +++ b/backend/src/main/java/io/openems/backend/timedata/influx/InfluxdbSingleton.java @@ -0,0 +1,177 @@ +package io.openems.backend.timedata.influx; + +import java.time.ZonedDateTime; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; + +import org.influxdb.InfluxDB; +import org.influxdb.InfluxDBFactory; +import org.influxdb.dto.BatchPoints; +import org.influxdb.dto.Point; +import org.influxdb.dto.Point.Builder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Table; +import com.google.common.collect.Tables; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.backend.timedata.api.TimedataSingleton; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.InfluxdbUtils; +import io.openems.common.utils.JsonUtils; + +public class InfluxdbSingleton implements TimedataSingleton { + + private final Logger log = LoggerFactory.getLogger(InfluxdbSingleton.class); + + private String database; + private String url; + private int port; + private String username; + private String password; + + private InfluxDB influxDB; + // 1st: deviceId; 2nd: channel; 3rd: value + private Table lastDataCache = Tables.synchronizedTable(HashBasedTable.create()); + // key: deviceId; value: timestamp + private Map lastTimestampMap = new ConcurrentHashMap(); + + public InfluxdbSingleton(String database, String url, int port, String username, String password) throws Exception { + this.database = database; + this.url = url; + this.port = port; + this.username = username; + this.password = password; + this.connect(); + } + + private void connect() throws Exception { + InfluxDB influxDB = InfluxDBFactory.connect("http://" + url + ":" + port, username, password); + this.influxDB = influxDB; + try { + influxDB.ping(); + } catch (RuntimeException e) { + log.error("Unable to connect to InfluxDB: " + e.getMessage()); + throw new Exception(e.getMessage()); + } + /* + * try { + * influxDB.createDatabase(DB_NAME); + * } catch (RuntimeException e) { + * log.error("Unable to create InfluxDB database: " + DB_NAME); + * throw new Exception(e.getMessage()); + * } + */ + } + + /** + * Takes a JsonObject and writes the points to influxDB. + * + * Format: { "timestamp1" { "channel1": value, "channel2": value }, + * "timestamp2" { "channel1": value, "channel2": value } } + */ + @Override + public void write(Optional deviceIdOpt, JsonObject jData) { + int deviceId = deviceIdOpt.orElse(0); + long lastTimestamp = this.lastTimestampMap.getOrDefault(deviceId, 0l); + + BatchPoints batchPoints = BatchPoints.database(database) // + .tag("fems", String.valueOf(deviceId)) // + .build(); + + // Sort data by timestamp + TreeMap data = new TreeMap(); + jData.entrySet().forEach(timestampEntry -> { + String timestampString = timestampEntry.getKey(); + Long timestamp = Long.valueOf(timestampString); + JsonObject jChannels; + try { + jChannels = JsonUtils.getAsJsonObject(timestampEntry.getValue()); + data.put(timestamp, jChannels); + } catch (OpenemsException e) { + log.error("Data error: " + e.getMessage()); + } + }); + + // Prepare data for writing to InfluxDB + for (Entry dataEntry : data.entrySet()) { + Long timestamp = dataEntry.getKey(); + // use lastDataCache only if we receive the latest data and cache is not elder than 1 minute + boolean useLastDataCache = timestamp > lastTimestamp && timestamp < lastTimestamp + 60000; + this.lastTimestampMap.put(deviceId, timestamp); + Builder builder = Point.measurement("data") // this builds an InfluxDB record ("point") for a given + // timestamp + .time(timestamp, TimeUnit.MILLISECONDS); + + JsonObject jChannels = dataEntry.getValue(); + if (jChannels.entrySet().size() > 0) { + jChannels.entrySet().forEach(channelEntry -> { + String channel = channelEntry.getKey(); + JsonPrimitive jValue; + try { + jValue = JsonUtils.getAsPrimitive(channelEntry.getValue()); + if (jValue.isNumber()) { + Number value = jValue.getAsNumber(); + builder.addField(channel, value); + if (useLastDataCache) { + this.lastDataCache.put(deviceId, channel, value); + } + + } else if (jValue.isString()) { + String value = jValue.getAsString(); + builder.addField(channel, value); + if (useLastDataCache) { + this.lastDataCache.put(deviceId, channel, value); + } + + } else { + log.warn(deviceId + ": Ignore unknown type [" + jValue + "] for channel [" + channel + "]"); + } + } catch (OpenemsException e) { + log.error("Data error: " + e.getMessage()); + } + + }); + + // only for latest data: add the cached data to the InfluxDB point. + if (useLastDataCache) { + this.lastDataCache.row(deviceId).entrySet().forEach(cacheEntry -> { + String field = cacheEntry.getKey(); + Object value = cacheEntry.getValue(); + if (value instanceof Number) { + builder.addField(field, (Number) value); + } else if (value instanceof String) { + builder.addField(field, (String) value); + } else { + log.warn("Unknown type in InfluxDB. This should never happen."); + } + }); + } + + // add the point to the batch + batchPoints.point(builder.build()); + + // set last timestamp + lastTimestamp = timestamp; + } + } + // write to DB + influxDB.write(batchPoints); + + } + + @Override + public JsonArray queryHistoricData(Optional deviceIdOpt, ZonedDateTime fromDate, ZonedDateTime toDate, + JsonObject channels, int resolution) throws OpenemsException { + return InfluxdbUtils.queryHistoricData(influxDB, this.database, deviceIdOpt, fromDate, toDate, channels, + resolution); + } +} diff --git a/backend/src/main/java/io/openems/femsserver/utilities/ManyToMany.java b/backend/src/main/java/io/openems/backend/utilities/ManyToMany.java similarity index 72% rename from backend/src/main/java/io/openems/femsserver/utilities/ManyToMany.java rename to backend/src/main/java/io/openems/backend/utilities/ManyToMany.java index 7384c11a820..319bfb3840d 100644 --- a/backend/src/main/java/io/openems/femsserver/utilities/ManyToMany.java +++ b/backend/src/main/java/io/openems/backend/utilities/ManyToMany.java @@ -1,4 +1,4 @@ -package io.openems.femsserver.utilities; +package io.openems.backend.utilities; // Source: http://stackoverflow.com/questions/20390923/do-we-have-a-multibimap import java.util.Set; @@ -31,10 +31,21 @@ public boolean putAll(K key, Iterable values) { return changed; } + public boolean remove(K key, V value) { + return keysToValues.remove(key, value) && valuesToKeys.remove(value, key); + } + public void removeAllKeys(K key) { keysToValues.get(key).forEach(value -> { valuesToKeys.removeAll(value); }); keysToValues.removeAll(key); } + + public void removeAllValues(V value) { + valuesToKeys.get(value).forEach(key -> { + keysToValues.removeAll(key); + }); + valuesToKeys.removeAll(value); + } } diff --git a/backend/src/main/java/io/openems/backend/utilities/MultiKeyMap.java b/backend/src/main/java/io/openems/backend/utilities/MultiKeyMap.java new file mode 100644 index 00000000000..f174c88b26f --- /dev/null +++ b/backend/src/main/java/io/openems/backend/utilities/MultiKeyMap.java @@ -0,0 +1,34 @@ +package io.openems.backend.utilities; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; + +public class MultiKeyMap { + private Map k1Map = new ConcurrentHashMap<>(); + private BiMap k2Map = Maps.synchronizedBiMap(HashBiMap.create()); + + public MultiKeyMap() {} + + public void put(K1 key1, K2 key2, V value) { + k1Map.put(key1, value); + k2Map.put(key2, key1); + } + + public void put(K1 key1, V value) { + k1Map.put(key1, value); + k2Map.inverse().remove(key1); + } + + public V getWithKey1(K1 key1) { + return k1Map.get(key1); + } + + public V getWithKey2(K2 key2) { + K1 key1 = k2Map.get(key2); + return getWithKey1(key1); + } +} diff --git a/backend/src/main/java/io/openems/femsserver/utilities/StringUtils.java b/backend/src/main/java/io/openems/backend/utilities/StringUtils.java similarity index 82% rename from backend/src/main/java/io/openems/femsserver/utilities/StringUtils.java rename to backend/src/main/java/io/openems/backend/utilities/StringUtils.java index 9eb02e76c5d..48517f67e37 100644 --- a/backend/src/main/java/io/openems/femsserver/utilities/StringUtils.java +++ b/backend/src/main/java/io/openems/backend/utilities/StringUtils.java @@ -1,4 +1,4 @@ -package io.openems.femsserver.utilities; +package io.openems.backend.utilities; import com.google.gson.JsonObject; diff --git a/backend/src/main/java/io/openems/femsserver/App.java b/backend/src/main/java/io/openems/femsserver/App.java deleted file mode 100644 index db034a282c4..00000000000 --- a/backend/src/main/java/io/openems/femsserver/App.java +++ /dev/null @@ -1,75 +0,0 @@ -package io.openems.femsserver; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Properties; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.femsserver.browserwebsocket.BrowserWebsocket; -import io.openems.femsserver.femswebsocket.FemsWebsocket; -import io.openems.femsserver.influx.Influxdb; -import io.openems.femsserver.odoo.Odoo; - -public class App { - private static Logger log = LoggerFactory.getLogger(App.class); - - public static void main(String[] args) throws Exception { - log.info("FEMS-Server starting..."); - - // Configure everything - Properties config = getConfig(); - initOdoo(config); - initInfluxdb(config); - initFemsWebsocket(config); - initBrowserWebsocket(config); - - log.info("FEMS-Server started."); - } - - private static Properties getConfig() throws IOException { - log.info("Read config"); - Path configLocation = Paths.get("config.properties"); - try (InputStream stream = Files.newInputStream(configLocation)) { - Properties config = new Properties(); - config.load(stream); - return config; - } - } - - private static void initOdoo(Properties config) throws Exception { - int port = Integer.valueOf(config.getProperty("odoo.port")); - log.info("Connect to Odoo on port [" + port + "]"); - String url = config.getProperty("odoo.url"); - String database = config.getProperty("odoo.database"); - String username = config.getProperty("odoo.username"); - String password = config.getProperty("odoo.password"); - Odoo.initialize(url, port, database, username, password); - } - - private static void initInfluxdb(Properties config) throws Exception { - int port = Integer.valueOf(config.getProperty("influx.port")); - log.info("Connect to InfluxDB on port [" + port + "]"); - String database = config.getProperty("influx.database"); - String url = config.getProperty("influx.url"); - String username = config.getProperty("influx.username"); - String password = config.getProperty("influx.password"); - Influxdb.initialize(database, url, port, username, password); - } - - private static void initFemsWebsocket(Properties config) throws Exception { - int port = Integer.valueOf(config.getProperty("femswebsocket.port")); - log.info("Start FEMS Websocket server on port [" + port + "]"); - FemsWebsocket.initialize(port); - } - - private static void initBrowserWebsocket(Properties config) throws Exception { - int port = Integer.valueOf(config.getProperty("browserwebsocket.port")); - log.info("Start Browser Websocket server on port [" + port + "]"); - BrowserWebsocket.initialize(port); - } -} diff --git a/backend/src/main/java/io/openems/femsserver/browserwebsocket/BrowserWebsocket.java b/backend/src/main/java/io/openems/femsserver/browserwebsocket/BrowserWebsocket.java deleted file mode 100644 index f00a4c810fa..00000000000 --- a/backend/src/main/java/io/openems/femsserver/browserwebsocket/BrowserWebsocket.java +++ /dev/null @@ -1,354 +0,0 @@ -package io.openems.femsserver.browserwebsocket; - -import java.net.InetSocketAddress; -import java.time.Period; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; - -import org.apache.xmlrpc.XmlRpcException; -import org.java_websocket.WebSocket; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.server.WebSocketServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.abercap.odoo.OdooApiException; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.femsserver.core.ConnectionManager; -import io.openems.femsserver.influx.Influxdb; -import io.openems.femsserver.odoo.Odoo; -import io.openems.femsserver.odoo.fems.device.FemsDevice; -import io.openems.femsserver.utilities.JsonUtils; -import io.openems.femsserver.utilities.OpenemsException; -import io.openems.femsserver.utilities.StringUtils; -import io.openems.femsserver.utilities.WebSocketUtils; - -/** - * Handles connections from a browser (for FENECON Online-Monitoring). - * Needs to be initialized before it can be used as a singleton. - * - * @author stefan.feilmeier - * - */ -public class BrowserWebsocket extends WebSocketServer { - - private static Logger log = LoggerFactory.getLogger(BrowserWebsocket.class); - - private static BrowserWebsocket instance; - - /** - * Initialize and start the Websocketserver - * - * @param port - * @throws Exception - */ - public static synchronized void initialize(int port) throws Exception { - BrowserWebsocket ws = new BrowserWebsocket(port); - ws.start(); - } - - /** - * Returns the singleton instance - * - * @return - */ - public static synchronized BrowserWebsocket getInstance() { - return BrowserWebsocket.instance; - } - - /** - * Holds a reference to the ConnectionManager singleton - */ - private final ConnectionManager connectionManager; - - /** - * Holds a reference to the Odoo singleton - */ - private final Odoo odoo; - - private BrowserWebsocket(int port) throws Exception { - super(new InetSocketAddress(port)); - this.connectionManager = ConnectionManager.getInstance(); - this.odoo = Odoo.getInstance(); - } - - /** - * Open event of websocket. Expects an open Odoo "session_id". On success tells the ConnectionManager - * to keep the websocket. On failure closes the websocket. Sends an initial message to the browser. - */ - @Override - public void onOpen(WebSocket websocket, ClientHandshake handshake) { - try { - String sessionId = parseSessionId(handshake); - if (sessionId != null) { - try { - log.info("Incoming browser websocket using session [" + sessionId + "]."); - JsonObject jOdooResult = this.odoo.getFemsInfo(sessionId); - // successfully logged in (otherwise an exception was thrown) - JsonArray jOdooDevices = JsonUtils.getAsJsonArray(jOdooResult, "devices"); - List devices = this.odoo.getDevicesForNames(jOdooDevices); - connectionManager.addBrowserWebsocket(websocket, devices); - /** - * send initial message - * - *
-					 {
-					   authenticate: {
-					     mode: "allow",
-					     username: "..."
-					   }, metadata: {
-					     devices: [{
-					       name, online,...
-					     }]
-					
-					   }
-					 }
-					 * 
- */ - JsonObject j = new JsonObject(); - JsonObject jAuthenticate = new JsonObject(); - jAuthenticate.addProperty("mode", "allow"); - j.add("authenticate", jAuthenticate); - JsonArray jDevices = new JsonArray(); - devices.forEach(device -> { - try { - JsonObject jDevice = device.toJsonObject(); - jDevice.addProperty("online", connectionManager.isFemsOnline(device.getName())); - jDevices.add(jDevice); - } catch (Exception e) { - e.printStackTrace(); - } - }); - JsonObject jMetadata = new JsonObject(); - jMetadata.add("devices", jDevices); - jMetadata.addProperty("backend", "femsserver"); - j.add("metadata", jMetadata); - WebSocketUtils.send(websocket, j); - - } catch (OpenemsException | OdooApiException | XmlRpcException e) { - // Authentication failed - JsonObject j = new JsonObject(); - - JsonObject jAuthenticate = new JsonObject(); - jAuthenticate.addProperty("mode", "deny"); - - j.add("authenticate", jAuthenticate); - WebSocketUtils.send(websocket, j); - throw new OpenemsException( - "Connection using session [" + sessionId + "] failed: " + e.getMessage()); - } - } else { - throw new OpenemsException("Connection failed. No session_id given."); - } - } catch (OpenemsException e) { - log.error(e.getMessage()); - JsonObject j = generateNotification(e.getMessage()); - WebSocketUtils.send(websocket, j); - try { - Thread.sleep(1000); // give some time to send data - } catch (InterruptedException e1) {} - websocket.close(); - } - } - - /** - * Close event of websocket. Tells the ConnectionManager to remove the websocket. - */ - @Override - public void onClose(WebSocket websocket, int code, String reason, boolean remote) { - log.info("Close connection to [" + websocket + "]" // - + " Code [" + code + "] Reason [" + reason + "]"); - this.connectionManager.removeBrowserWebsocket(websocket); - } - - /** - * Error event of websocket. Logs the error. - */ - @Override - public void onError(WebSocket websocket, Exception ex) { - log.info("Error on connection to [" + websocket + "]: " + ex.getMessage()); - } - - /** - * Message event of websocket. Handles a new message. - */ - @Override - public void onMessage(WebSocket websocket, String message) { - try { - String requestId = ""; - JsonObject jMessage = (new JsonParser()).parse(message).getAsJsonObject(); - if (jMessage.has("device")) { - String deviceName = JsonUtils.getAsString(jMessage, "device"); - jMessage.remove("device"); - - if (jMessage.has("requestId")) { - try { - requestId = JsonUtils.getAsString(jMessage, "requestId"); - } catch (Exception e) { - log.warn("Invalid requestId: " + e.getMessage()); - } - } - - /* - * Forward Subscribe to data - */ - if (jMessage.has("subscribe")) { - subscribe(deviceName, jMessage.get("subscribe")); - } - - /* - * Forward System command - */ - if (jMessage.has("system")) { - system(deviceName, jMessage.get("system")); - } - - /* - * Query command - */ - if (jMessage.has("query")) { - query(requestId, deviceName, websocket, jMessage.get("query")); - } - } - } catch ( - - OpenemsException e) { - log.error(e.getMessage()); - } - } - - /** - * Tries to find a "session_id" in the handshake - * - * @param handshake - * @return session_id or null - */ - private String parseSessionId(ClientHandshake handshake) { - String sessionId = null; - if (handshake.hasFieldValue("cookie")) { - String cookieString = handshake.getFieldValue("cookie"); - for (String cookieVariable : cookieString.split("; ")) { - String[] keyValue = cookieVariable.split("="); - if (keyValue.length == 2 && keyValue[0].equals("session_id")) { - sessionId = keyValue[1]; - } - } - } - return sessionId; - } - - /** - * Generates a generic notification message - * - * @param message - * @return - */ - private JsonObject generateNotification(String message) { - JsonObject j = new JsonObject(); - JsonObject jNotification = new JsonObject(); - jNotification.addProperty("message", message); - j.add("notification", jNotification); - return j; - } - - /** - * Handle subscriptions - * - * @param j - */ - private synchronized void subscribe(String deviceName, JsonElement jSubscribeElement) { - JsonObject j = new JsonObject(); - j.add("subscribe", jSubscribeElement); - Optional websocketOptional = this.connectionManager.getFemsWebsocket(deviceName); - if (websocketOptional.isPresent()) { - WebSocket websocket = websocketOptional.get(); - log.info(deviceName + ": forward subscribe to FEMS " + StringUtils.toShortString(j, 100)); - WebSocketUtils.send(websocket, j); - } - } - - /** - * System command - * - * @param j - */ - private synchronized void system(String deviceName, JsonElement jSubscribeElement) { - JsonObject j = new JsonObject(); - j.add("system", jSubscribeElement); - Optional websocketOptional = this.connectionManager.getFemsWebsocket(deviceName); - if (websocketOptional.isPresent()) { - WebSocket websocket = websocketOptional.get(); - log.info(deviceName + ": forward system call to FEMS " + StringUtils.toShortString(j, 100)); - WebSocketUtils.send(websocket, j); - } - } - - /** - * Query command - * - * @param j - */ - private synchronized void query(String requestId, String deviceName, WebSocket websocket, - JsonElement jQueryElement) { - try { - JsonObject jQuery = JsonUtils.getAsJsonObject(jQueryElement); - String mode = JsonUtils.getAsString(jQuery, "mode"); - int fems = Integer.parseInt(deviceName.substring(4)); - if (mode.equals("history")) { - /* - * History query - */ - int timezoneDiff = JsonUtils.getAsInt(jQuery, "timezone"); - ZoneId timezone = ZoneId.ofOffset("", ZoneOffset.ofTotalSeconds(timezoneDiff * -1)); - ZonedDateTime fromDate = JsonUtils.getAsZonedDateTime(jQuery, "fromDate", timezone); - ZonedDateTime toDate = JsonUtils.getAsZonedDateTime(jQuery, "toDate", timezone); - JsonObject channels = JsonUtils.getAsJsonObject(jQuery, "channels"); - // JsonObject kWh = JsonUtils.getAsJsonObject(jQuery, "kWh"); - int days = Period.between(fromDate.toLocalDate(), toDate.toLocalDate()).getDays(); - // TODO: better calculation of sensible resolution - int resolution = 5 * 60; // 5 Minutes - if (days > 25) { - resolution = 24 * 60 * 60; // 1 Day - } else if (days > 6) { - resolution = 3 * 60 * 60; // 3 Hours - } else if (days > 2) { - resolution = 60 * 60; // 60 Minutes - } - JsonObject jQueryreply = Influxdb.getInstance().query(fems, fromDate, toDate, channels, resolution/* - * , - * kWh - */); - - JsonObject j = bootstrapReply(requestId); - // Send result - if (jQueryreply != null) { - j.add("queryreply", jQueryreply); - } else { - j.addProperty("error", "No Queryable persistence found!"); - } - WebSocketUtils.sendAsDevice(websocket, j, fems); - } - } catch (Exception e) { - log.error("Error", e); - e.printStackTrace(); - } - } - - private JsonObject bootstrapReply(String requestId) { - JsonObject j = new JsonObject(); - j.addProperty("requestId", requestId); - return j; - } - - @Override - public void onStart() { - - } -} diff --git a/backend/src/main/java/io/openems/femsserver/core/ConnectionManager.java b/backend/src/main/java/io/openems/femsserver/core/ConnectionManager.java deleted file mode 100644 index 4531da44efa..00000000000 --- a/backend/src/main/java/io/openems/femsserver/core/ConnectionManager.java +++ /dev/null @@ -1,195 +0,0 @@ -package io.openems.femsserver.core; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; - -import org.java_websocket.WebSocket; -import org.java_websocket.framing.CloseFrame; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Multimap; - -import io.openems.femsserver.odoo.fems.device.FemsDevice; -import io.openems.femsserver.utilities.ManyToMany; - -public class ConnectionManager { - - private static Logger log = LoggerFactory.getLogger(ConnectionManager.class); - - private static ConnectionManager instance; - - public static synchronized ConnectionManager getInstance() { - if (ConnectionManager.instance == null) { - ConnectionManager.instance = new ConnectionManager(); - } - return ConnectionManager.instance; - } - - private ConnectionManager() {} - - /** - * Stores info about FEMS devices - * Key: fems-name (e.g. "fems7") - Value: FemsDevice object - */ - private HashMap femsDevices = new HashMap<>(); - - /** - * Stores all active websockets to FEMS devices - * Key: Websocket to FEMS - Value: fems-name (e.g. "fems7") - */ - private Multimap femsWebsockets = HashMultimap.create(); - - /** - * Stores all active websockets to browsers - * Key: Websocket to browser - Value: fems-name (e.g. "fems7") - */ - private ManyToMany browserWebsockets = new ManyToMany<>(); - - /** - * Stores a websocket connection to FEMS and the connected FemsDevice objects - * - * @param webSocket - * @param device - */ - public synchronized void addFemsWebsocket(WebSocket websocket, List devices) { - devices.forEach(device -> { - String name = device.getName(); - /* - * Store the FemsDevice - */ - // Check if femsDevice already existed in cache. If so, refresh and reuse it. Otherwise add it. - if (this.femsDevices.containsKey(name)) { - // refresh an existing FemsDevice object - FemsDevice existingDevice = this.femsDevices.get(name); - existingDevice.refreshFrom(device); - } else { - // put new object - this.femsDevices.put(name, device); - } - /* - * Store the websocket connection - */ - // close old websocket connection(s) to this device - for (Iterator> it = this.femsWebsockets.entries().iterator(); it.hasNext();) { - Entry entry = it.next(); - if (entry.getValue().equals(name)) { - WebSocket oldWebsocket = entry.getKey(); - oldWebsocket.close(CloseFrame.POLICY_VALIDATION, "Another websocket [" - + websocket.getRemoteSocketAddress().getHostString() + "] connected for [" + name + "]."); - it.remove(); - } - } - // add new websocket for this device - this.femsWebsockets.put(websocket, name); - }); - } - - /** - * Remove a websocket connection to FEMS - * - * @param WebSocket - */ - public synchronized void removeFemsWebsocket(WebSocket websocket) { - this.femsWebsockets.removeAll(websocket); - } - - /** - * Returns all devices for this websocket - * - * @param websocket - * @return - */ - public synchronized List getFemsWebsocketDevices(WebSocket websocket) { - List devices = new ArrayList<>(); - this.getFemsWebsocketDeviceNames(websocket).forEach(name -> { - devices.add(this.femsDevices.get(name)); - }); - return Collections.unmodifiableList(devices); - } - - /** - * Returns all fems websockets for a device name - * - * @param name - * @return - */ - public synchronized Optional getFemsWebsocket(String name) { - for (Iterator> it = this.femsWebsockets.entries().iterator(); it.hasNext();) { - Entry entry = it.next(); - if (entry.getValue().equals(name)) { - return Optional.of(entry.getKey()); - } - } - return Optional.empty(); - } - - /** - * Returns all device names for this websocket - * - * @param websocket - * @return - */ - public synchronized Collection getFemsWebsocketDeviceNames(WebSocket websocket) { - return Collections.unmodifiableCollection(this.femsWebsockets.get(websocket)); - } - - /** - * Stores a websocket connection to a browser together with the allowed FemsDevice objects - * - * @param webSocket - * @param device - */ - public synchronized void addBrowserWebsocket(WebSocket websocket, List devices) { - devices.forEach(device -> { - String name = device.getName(); - /* - * Check if femsDevice already existed in cache. If so, refresh and reuse it. Otherwise add it. - */ - if (this.femsDevices.containsKey(name)) { - // refresh an existing FemsDevice object - FemsDevice existingDevice = this.femsDevices.get(name); - existingDevice.refreshFrom(device); - device = existingDevice; - } else { - // put new object - this.femsDevices.put(name, device); - } - /* - * Store the websocket connection - */ - this.browserWebsockets.put(websocket, name); - }); - } - - /** - * Remove a websocket connection to a browser - * - * @param WebSocket - */ - public synchronized void removeBrowserWebsocket(WebSocket websocket) { - this.browserWebsockets.removeAllKeys(websocket); - } - - public synchronized boolean isFemsOnline(String name) { - return this.femsWebsockets.containsValue(name); - } - - /** - * Returns all browser websockets for a device name - * - * @param name - * @return - */ - public synchronized Set getBrowserWebsockets(String name) { - return Collections.unmodifiableSet(this.browserWebsockets.getKeys(name)); - } -} diff --git a/backend/src/main/java/io/openems/femsserver/exception/FemsException.java b/backend/src/main/java/io/openems/femsserver/exception/FemsException.java deleted file mode 100644 index 32db4591bfa..00000000000 --- a/backend/src/main/java/io/openems/femsserver/exception/FemsException.java +++ /dev/null @@ -1,11 +0,0 @@ -package io.openems.femsserver.exception; - -public class FemsException extends Exception { - - public FemsException(String message) { - super(message); - } - - private static final long serialVersionUID = 1L; - -} diff --git a/backend/src/main/java/io/openems/femsserver/femswebsocket/FemsWebsocket.java b/backend/src/main/java/io/openems/femsserver/femswebsocket/FemsWebsocket.java deleted file mode 100644 index 14dfbd3fed7..00000000000 --- a/backend/src/main/java/io/openems/femsserver/femswebsocket/FemsWebsocket.java +++ /dev/null @@ -1,288 +0,0 @@ -package io.openems.femsserver.femswebsocket; - -import java.net.InetSocketAddress; -import java.util.List; -import java.util.StringJoiner; - -import org.apache.xmlrpc.XmlRpcException; -import org.java_websocket.WebSocket; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.server.WebSocketServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.abercap.odoo.OdooApiException; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.femsserver.core.ConnectionManager; -import io.openems.femsserver.exception.FemsException; -import io.openems.femsserver.influx.Influxdb; -import io.openems.femsserver.odoo.Odoo; -import io.openems.femsserver.odoo.fems.device.FemsDevice; -import io.openems.femsserver.utilities.JsonUtils; -import io.openems.femsserver.utilities.OpenemsException; -import io.openems.femsserver.utilities.StringUtils; -import io.openems.femsserver.utilities.WebSocketUtils; - -/** - * Handles connections to FEMS/OpenEMS-Devices. - * Needs to be initialized before it can be used as a singleton. - * - * @author stefan.feilmeier - * - */ -public class FemsWebsocket extends WebSocketServer { - - private static Logger log = LoggerFactory.getLogger(FemsWebsocket.class); - - private static FemsWebsocket instance; - - /** - * Initialize and start the Websocketserver - * - * @param port - * @throws Exception - */ - public static synchronized void initialize(int port) throws Exception { - FemsWebsocket ws = new FemsWebsocket(port); - ws.start(); - } - - /** - * Returns the singleton instance - * - * @return - */ - public static synchronized FemsWebsocket getInstance() { - return FemsWebsocket.instance; - } - - /** - * Holds a reference to InfluxDB server - */ - private final Influxdb influxdb; - - /** - * Holds a reference to the ConnectionManager singleton - */ - private final ConnectionManager connectionManager; - - /** - * Holds a reference to the Odoo singleton - */ - private final Odoo odoo; - - private FemsWebsocket(int port) throws Exception { - super(new InetSocketAddress(port)); - this.influxdb = Influxdb.getInstance(); - this.connectionManager = ConnectionManager.getInstance(); - this.odoo = Odoo.getInstance(); - } - - /** - * Open event of websocket. Expects an "apikey" authentication. On success tells the ConnectionManager - * to keep the websocket. On failure closes the websocket. - */ - @Override - public void onOpen(WebSocket websocket, ClientHandshake handshake) { - try { - if (handshake.hasFieldValue("apikey")) { - String apikey = handshake.getFieldValue("apikey"); - try { - List devices = this.odoo.getDevicesForApikey(apikey); - if (devices.isEmpty()) { - throw new FemsException("Unable to find device for apikey [" + apikey + "]"); - } else { - // found devices. Add all of them to ConnectionManager - this.connectionManager.addFemsWebsocket(websocket, devices); - } - log.info("Opened connection to [" + this.getFemsName(websocket) + "]"); - } catch (OdooApiException | XmlRpcException e) { - throw new FemsException("Unable to query for apikey [" + apikey + "]: " + e.getMessage()); - } - } - } catch (FemsException e) { - log.warn("Connection failed: " + e.getMessage()); - websocket.close(); - } - } - - /** - * Close event of websocket. Tells the ConnectionManager to remove the websocket. - */ - @Override - public void onClose(WebSocket websocket, int code, String reason, boolean remote) { - log.info("Close connection to [" + this.getFemsName(websocket) + "]" // - + " Code [" + code + "] Reason [" + reason + "]"); - this.connectionManager.removeFemsWebsocket(websocket); - } - - /** - * Error event of websocket. Logs the error. - */ - @Override - public void onError(WebSocket websocket, Exception ex) { - log.info("Error on connection to [" + this.getFemsName(websocket) + "]: " + ex.getMessage()); - } - - /** - * Message event of websocket. Handles a new message. At this point the FEMS device is already authenticated. - */ - @Override - public void onMessage(WebSocket websocket, String message) { - this.connectionManager.getFemsWebsocketDevices(websocket).forEach(device -> { - /** - * set active in Odoo - */ - if (device.getState().equals("inactive")) { - device.setState("active"); - } - device.setLastMessage(); - JsonObject jMessage = (new JsonParser()).parse(message).getAsJsonObject(); - - /* - * New timestamped data - */ - if (jMessage.has("timedata")) { - timedata(device, jMessage.get("timedata")); - } - - /* - * New currentdata data -> forward to browserWebsockets - */ - if (jMessage.has("currentdata")) { - currentdata(websocket, jMessage.get("currentdata")); - } - - /* - * New log -> forward to browserWebsockets - */ - if (jMessage.has("log")) { - log(websocket, jMessage.get("log")); - } - - /* - * New metadata - */ - if (jMessage.has("metadata")) { - metadata(device, websocket, jMessage.get("metadata")); - } - - // Save data to Odoo - try { - device.writeObject(); - } catch (OdooApiException | XmlRpcException e) { - log.error(device.getName() + ": Updating Odoo failed: " + e.getMessage()); - } - }); - } - - /** - * Builds a string with all FEMS names: "fems7/fems8" - * - * @param websocket - * @return - */ - private String getFemsName(WebSocket websocket) { - StringJoiner joiner = new StringJoiner("/"); - this.connectionManager.getFemsWebsocketDeviceNames(websocket).forEach(name -> { - joiner.add(name); - }); - return joiner.toString(); - } - - private void timedata(FemsDevice device, JsonElement jTimedataElement) { - try { - JsonObject jTimedata = JsonUtils.getAsJsonObject(jTimedataElement); - // Write to InfluxDB - try { - influxdb.write(device.getNameNumber(), jTimedata); - log.info(device.getName() + ": wrote " + jTimedata.entrySet().size() + " timestamps " - + StringUtils.toShortString(jTimedata, 120)); - } catch (Exception e) { - log.error("No InfluxDB-connection: ", e); - } - // Write some data to Odoo - // TODO: this is only to provide feedback for FENECON - // Service-Team that the device is online. Replace with - // something based on the actual websocket connection - device.setLastUpdate(); - jTimedata.entrySet().forEach(entry -> { - try { - JsonObject jChannels = JsonUtils.getAsJsonObject(entry.getValue()); - if (jChannels.has("ess0/Soc")) { - int soc = JsonUtils.getAsPrimitive(jChannels, "ess0/Soc").getAsInt(); - device.setSoc(soc); - } - if (jChannels.has("system0/PrimaryIpAddress")) { - String ipv4 = JsonUtils.getAsPrimitive(jChannels, "system0/PrimaryIpAddress").getAsString(); - device.setIpV4(ipv4); - } - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - }); - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - - /** - * Forward currentdata to browserWebsockets - */ - private void currentdata(WebSocket websocket, JsonElement jCurrentdataElement) { - try { - JsonObject jCurrentdata = JsonUtils.getAsJsonObject(jCurrentdataElement); - JsonObject j = new JsonObject(); - j.add("currentdata", jCurrentdata); - // log.info("FemsWS: " + websocket + ", " + websocket.isOpen()); - this.connectionManager.getFemsWebsocketDeviceNames(websocket).forEach(name -> { - j.addProperty("device", name); - this.connectionManager.getBrowserWebsockets(name).forEach(browserWebsocket -> { - // log.info("BrowserWS: " + browserWebsocket + ", " + browserWebsocket.isOpen()); - log.info(name + ": forward currentdata to Browser: " + StringUtils.toShortString(j, 100)); - WebSocketUtils.send(browserWebsocket, j); - }); - }); - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - - private void log(WebSocket websocket, JsonElement jLogElement) { - try { - JsonObject jLog = JsonUtils.getAsJsonObject(jLogElement); - JsonObject j = new JsonObject(); - j.add("log", jLog); - this.connectionManager.getFemsWebsocketDeviceNames(websocket).forEach(name -> { - j.addProperty("device", name); - this.connectionManager.getBrowserWebsockets(name).forEach(browserWebsocket -> { - log.info(name + ": forward log to Browser: " + StringUtils.toShortString(j, 100)); - WebSocketUtils.send(browserWebsocket, j); - }); - }); - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - - private void metadata(FemsDevice device, WebSocket websocket, JsonElement jMetadataElement) { - try { - JsonObject jMetadata = JsonUtils.getAsJsonObject(jMetadataElement); - if (jMetadata.has("config")) { - JsonObject jConfig = JsonUtils.getAsJsonObject(jMetadata, "config"); - log.info(getFemsName(websocket) + ": got config " + StringUtils.toShortString(jConfig, 120)); - device.setOpenemsConfig(jConfig); - } - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - - @Override - public void onStart() { - - } -} \ No newline at end of file diff --git a/backend/src/main/java/io/openems/femsserver/influx/Influxdb.java b/backend/src/main/java/io/openems/femsserver/influx/Influxdb.java deleted file mode 100644 index 9feb1ac626d..00000000000 --- a/backend/src/main/java/io/openems/femsserver/influx/Influxdb.java +++ /dev/null @@ -1,144 +0,0 @@ -package io.openems.femsserver.influx; - -import java.time.ZonedDateTime; -import java.util.HashMap; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import org.influxdb.InfluxDB; -import org.influxdb.InfluxDBFactory; -import org.influxdb.dto.BatchPoints; -import org.influxdb.dto.Point; -import org.influxdb.dto.Point.Builder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import io.openems.femsserver.utilities.JsonUtils; -import io.openems.femsserver.utilities.OpenemsException; - -public class Influxdb { - - protected String database; - protected String url; - protected int port; - protected String username; - protected String password; - - private final String DB_NAME = "db"; - - private static Logger log = LoggerFactory.getLogger(Influxdb.class); - - private static Influxdb instance; - - private static HashMap hmapData = new HashMap(); - - public static void initialize(String database, String url, int port, String username, String password) - throws Exception { - if (database == null || url == null || username == null || password == null) { - throw new Exception("Config missing: database [" + database + "], url [" + url + "], port [" + port - + "] username [" + username + "], password [" + password + "]"); - } - Influxdb influxdb = getInstance(); - influxdb.database = database; - influxdb.url = url; - influxdb.port = port; - influxdb.username = username; - influxdb.password = password; - influxdb.connect(); - } - - public static synchronized Influxdb getInstance() throws Exception { - if (Influxdb.instance == null) { - Influxdb.instance = new Influxdb(); - } - return Influxdb.instance; - } - - private InfluxDB influxDB; - - private Influxdb() { - - } - - private void connect() throws Exception { - InfluxDB influxDB = InfluxDBFactory.connect("http://" + url + ":" + port, username, password); - this.influxDB = influxDB; - try { - influxDB.ping(); - } catch (RuntimeException e) { - log.error("Unable to connect to InfluxDB: " + e.getMessage()); - throw new Exception(e.getMessage()); - } - } - - /** - * Takes a JsonObject and writes the points to influxDB. - * - * Format: { "timestamp1" { "channel1": value, "channel2": value }, - * "timestamp2" { "channel1": value, "channel2": value } } - */ - public void write(String fems, JsonObject jData) { - BatchPoints batchPoints = BatchPoints.database(database) // - .tag("fems", fems) // - .retentionPolicy("default").build(); - - jData.entrySet().forEach(timestampEntry -> { - String timestampString = timestampEntry.getKey(); - JsonObject jChannels; - try { - jChannels = JsonUtils.getAsJsonObject(timestampEntry.getValue()); - if (jChannels.entrySet().size() > 0) { - Long timestamp = Long.valueOf(timestampString); - Builder builder = Point.measurement("data") // - .time(timestamp, TimeUnit.MILLISECONDS); - jChannels.entrySet().forEach(jChannelEntry -> { - try { - String channel = jChannelEntry.getKey(); - JsonPrimitive jValue = JsonUtils.getAsPrimitive(jChannelEntry.getValue()); - - if (hmapData.containsKey(channel)) { - hmapData.replace(channel, hmapData.get(channel), - (hmapData.get(channel) instanceof Number) ? jValue.getAsNumber() - : jValue.getAsString()); - } else { - if (jValue.isNumber()) { - hmapData.put(channel, jValue.getAsNumber()); - } else if (jValue.isString()) { - hmapData.put(channel, jValue.getAsString()); - } else { - log.warn(fems + ": Ignore unknown type [" + jValue + "] for channel [" + channel - + "]"); - } - } - - for (String key : hmapData.keySet()) { - if (hmapData.get(key) instanceof Number) { - builder.addField(key, (Number) hmapData.get(key)); - } else if (hmapData.get(key) instanceof String) { - builder.addField(key, (String) hmapData.get(key)); - } - } - } catch (OpenemsException e) { - log.error("InfluxDB data error: " + e.getMessage()); - } - }); - batchPoints.point(builder.build()); - } - } catch (OpenemsException e) { - log.error("InfluxDB data error: " + e.getMessage()); - } - }); - // write to DB - influxDB.write(batchPoints); - } - - public JsonObject query(int _fems, ZonedDateTime fromDate, ZonedDateTime toDate, JsonObject channels, - int resolution/* , JsonObject kWh */) throws OpenemsException { - Optional fems = Optional.of(_fems); - Optional influxdb = Optional.of(influxDB); - return InfluxdbQueryWrapper.query(influxdb, fems, fromDate, toDate, channels, resolution/* , kWh */); - } -} diff --git a/backend/src/main/java/io/openems/femsserver/odoo/Odoo.java b/backend/src/main/java/io/openems/femsserver/odoo/Odoo.java deleted file mode 100644 index 39ee642edf9..00000000000 --- a/backend/src/main/java/io/openems/femsserver/odoo/Odoo.java +++ /dev/null @@ -1,176 +0,0 @@ -package io.openems.femsserver.odoo; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStreamWriter; -import java.net.HttpURLConnection; -import java.net.URL; -import java.net.URLEncoder; -import java.util.ArrayList; -import java.util.List; - -import org.apache.xmlrpc.XmlRpcException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.abercap.odoo.OdooApiException; -import com.abercap.odoo.Session; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.femsserver.odoo.fems.device.FemsDevice; -import io.openems.femsserver.odoo.fems.device.FemsDeviceModel; -import io.openems.femsserver.utilities.JsonUtils; -import io.openems.femsserver.utilities.OpenemsException; - -public class Odoo { - - private static Logger log = LoggerFactory.getLogger(Odoo.class); - - private static Odoo instance; - - public static synchronized void initialize(String url, int port, String database, String username, String password) - throws Exception { - if (url == null || database == null || username == null || password == null) { - throw new Exception("Config missing: database [" + database + "], url [" + url + "], port [" + port - + "] username [" + username + "], password [" + password + "]"); - } - Odoo odoo = getInstance(); - odoo.url = url; - odoo.port = port; - odoo.database = database; - odoo.username = username; - odoo.password = password; - - odoo.connect(); - } - - public static synchronized Odoo getInstance() { - if (Odoo.instance == null) { - Odoo.instance = new Odoo(); - } - return Odoo.instance; - } - - private String url; - private int port; - private String database; - private String username; - private String password; - private Session session; - private FemsDeviceModel femsDeviceModel; - - private Odoo() {} - - private void connect() throws Exception { - session = new Session(url, port, database, username, password); - // startSession logs into the server and keeps the userid of the logged - // in user - session.startSession(); - femsDeviceModel = new FemsDeviceModel(session); - } - - public List getDevicesForApikey(String apikey) throws OdooApiException, XmlRpcException { - List devices = femsDeviceModel.searchAndReadObject("apikey", "=", apikey); - return devices; - } - - public List getDevicesForName(String name) throws OdooApiException, XmlRpcException { - List devices = femsDeviceModel.searchAndReadObject("name", "=", name); - return devices; - } - - public List getDevicesForNames(List names) throws OdooApiException, XmlRpcException { - // TODO optimize: use only one call to searchAndReadObject - List devices = new ArrayList<>(); - for (String name : names) { - devices.addAll(femsDeviceModel.searchAndReadObject("name", "=", name)); - } - return devices; - } - - /** - * - * @param jNames - * [{ name: 'fems1', role: 'guest' }] - * @return - * @throws OdooApiException - * @throws XmlRpcException - * @throws OpenemsException - */ - public List getDevicesForNames(JsonArray jDevices) - throws OdooApiException, XmlRpcException, OpenemsException { - // TODO optimize: use only one call to searchAndReadObject - List devices = new ArrayList<>(); - for (JsonElement jDeviceElement : jDevices) { - JsonObject jDevice = JsonUtils.getAsJsonObject(jDeviceElement); - String name = JsonUtils.getAsString(jDevice, "name"); - String role = JsonUtils.getAsString(jDevice, "role"); - try { - List nameDevices = femsDeviceModel.searchAndReadObject("name", "=", name); - nameDevices.forEach(device -> { - device.setRole(role); - }); - devices.addAll(nameDevices); - } catch (XmlRpcException | OdooApiException e) { - log.error(e.getMessage()); - } - } - return devices; - } - - /** - * Tries to authenticate at the Odoo server using a sessionId from a cookie. On success, returns a JsonObject with - * fems device information. - * - * @param sessionId - * @return - * @throws OpenemsException - */ - public JsonObject getFemsInfo(String sessionId) throws OpenemsException { - try { - String charset = "US-ASCII"; - String query = String.format("session_id=%s", URLEncoder.encode(sessionId, charset)); - HttpURLConnection connection = (HttpURLConnection) new URL( - "http://" + this.url + ":" + this.port + "/fems/info?" + query).openConnection(); - try { - connection.setConnectTimeout(5000);// 5 secs - connection.setReadTimeout(5000);// 5 secs - connection.setRequestProperty("Accept-Charset", charset); - connection.setRequestMethod("POST"); - connection.setDoOutput(true); - connection.setRequestProperty("Content-Type", "application/json"); - - OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream()); - out.write("{}"); - out.flush(); - out.close(); - - InputStream is = connection.getInputStream(); - BufferedReader br = new BufferedReader(new InputStreamReader(is)); - String line = null; - while ((line = br.readLine()) != null) { - // parse the result - JsonObject j = (new JsonParser()).parse(line).getAsJsonObject(); - if (j.has("error")) { - JsonObject jError = JsonUtils.getAsJsonObject(j, "error"); - String errorMessage = JsonUtils.getAsString(jError, "message"); - throw new OpenemsException(errorMessage); - } - if (j.has("result")) { - return JsonUtils.getAsJsonObject(j, "result"); - } - } - } finally { - connection.disconnect(); - } - throw new OpenemsException("No result from Odoo"); - } catch (IOException e) { - throw new OpenemsException(e.getMessage()); - } - } -} diff --git a/backend/src/main/java/io/openems/femsserver/odoo/OdooModel.java b/backend/src/main/java/io/openems/femsserver/odoo/OdooModel.java deleted file mode 100644 index dfc0af1a8bb..00000000000 --- a/backend/src/main/java/io/openems/femsserver/odoo/OdooModel.java +++ /dev/null @@ -1,40 +0,0 @@ -package io.openems.femsserver.odoo; - -import java.util.List; - -import org.apache.xmlrpc.XmlRpcException; - -import com.abercap.odoo.FilterCollection; -import com.abercap.odoo.ObjectAdapter; -import com.abercap.odoo.OdooApiException; -import com.abercap.odoo.Row; -import com.abercap.odoo.RowCollection; -import com.abercap.odoo.Session; - -public abstract class OdooModel { - private final ObjectAdapter oa; - - public OdooModel(Session session) throws XmlRpcException, OdooApiException { - oa = session.getObjectAdapter(getObjectName()); - } - - protected void writeObject(Row row, boolean changesOnly) throws OdooApiException, XmlRpcException { - oa.writeObject(row, changesOnly); - } - - protected RowCollection _searchAndReadObject(FilterCollection filters) throws XmlRpcException, OdooApiException { - return oa.searchAndReadObject(filters, getFields()); - } - - public abstract List searchAndReadObject(FilterCollection filters) throws XmlRpcException, OdooApiException; - - public List searchAndReadObject(String fieldName, String comparison, Object value) throws XmlRpcException, OdooApiException { - FilterCollection filters = new FilterCollection(); - filters.add(fieldName, comparison, value); - return searchAndReadObject(filters); - } - - protected abstract String getObjectName(); - - protected abstract String[] getFields(); -} diff --git a/backend/src/main/java/io/openems/femsserver/odoo/fems/device/FemsDevice.java b/backend/src/main/java/io/openems/femsserver/odoo/fems/device/FemsDevice.java deleted file mode 100644 index e2d5ad12ff8..00000000000 --- a/backend/src/main/java/io/openems/femsserver/odoo/fems/device/FemsDevice.java +++ /dev/null @@ -1,130 +0,0 @@ -package io.openems.femsserver.odoo.fems.device; - -import org.java_websocket.WebSocket; - -import com.abercap.odoo.Row; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.femsserver.odoo.OdooModel; -import io.openems.femsserver.odoo.OdooObject; - -public class FemsDevice extends OdooObject { - private static final String NAME = "name"; - private static final String NAME_NUMBER = "name_number"; - private static final String COMMENT = "comment"; - private static final String SOC = "soc"; - private static final String LASTMESSAGE = "lastmessage"; - private static final String LASTUPDATE = "lastupdate"; - private static final String IPV4 = "ipv4"; - private static final String OPENEMS_CONFIG = "openems_config"; - private static final String STATE = "state"; - private static final String PRODUCT_TYPE = "producttype"; - - protected static String[] getFields() { - return new String[] { NAME, NAME_NUMBER, COMMENT, SOC, LASTMESSAGE, LASTUPDATE, IPV4, OPENEMS_CONFIG, STATE, - PRODUCT_TYPE }; - } - - private WebSocket ws = null; - private String role = "guest"; - - public FemsDevice(OdooModel model, Row row) { - super(model, row); - } - - public String getNameNumber() { - return get(NAME_NUMBER).toString(); - } - - public String getName() { - try { - return get(NAME).toString(); - } catch (Exception e) { - return "UNKNOWN"; - } - } - - public String getComment() { - return get(COMMENT).toString(); - } - - public String getState() { - return get(STATE).toString(); - } - - public String getProductType() { - return get(PRODUCT_TYPE).toString(); - } - - public JsonObject getOpenemsConfig() { - Object config = get(OPENEMS_CONFIG); - if (config != null) { - return (new JsonParser()).parse(get(OPENEMS_CONFIG).toString()).getAsJsonObject(); - } else { - return new JsonObject(); - } - } - - public void setOpenemsConfig(JsonObject j) { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - put(OPENEMS_CONFIG, gson.toJson(j)); - } - - public void setState(String active) { - put(STATE, active); - } - - public void setSoc(int value) { - put(SOC, value); - } - - public void setLastMessage() { - put(LASTMESSAGE, this.odooCompatibleNow()); - } - - public void setLastUpdate() { - put(LASTUPDATE, this.odooCompatibleNow()); - } - - public void setIpV4(String value) { - put(IPV4, value); - } - - public void setWebSocket(WebSocket ws) { - this.ws = ws; - } - - public WebSocket getWebSocket() { - return this.ws; - } - - public boolean isWebSocketConnected() { - return getWebSocket() != null; - } - - public void removeWebSocket() { - setWebSocket(null); - } - - public void setRole(String role) { - this.role = role; - } - - public String getRole() { - return role; - } - - public JsonObject toJsonObject() { - JsonObject j = new JsonObject(); - j.addProperty("name", getName()); - j.addProperty("comment", getComment()); - j.add("config", getOpenemsConfig()); - j.addProperty("role", getRole()); - j.addProperty("state", getState()); - j.addProperty("producttype", getProductType()); - return j; - } -} diff --git a/backend/src/main/java/io/openems/femsserver/odoo/fems/device/FemsDeviceModel.java b/backend/src/main/java/io/openems/femsserver/odoo/fems/device/FemsDeviceModel.java deleted file mode 100644 index 175ba6ecc66..00000000000 --- a/backend/src/main/java/io/openems/femsserver/odoo/fems/device/FemsDeviceModel.java +++ /dev/null @@ -1,39 +0,0 @@ -package io.openems.femsserver.odoo.fems.device; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.xmlrpc.XmlRpcException; - -import com.abercap.odoo.FilterCollection; -import com.abercap.odoo.OdooApiException; -import com.abercap.odoo.RowCollection; -import com.abercap.odoo.Session; - -import io.openems.femsserver.odoo.OdooModel; - -public class FemsDeviceModel extends OdooModel { - - public FemsDeviceModel(Session session) throws XmlRpcException, OdooApiException { - super(session); - } - - protected String getObjectName() { - return "fems.device"; - } - - @Override - public List searchAndReadObject(FilterCollection filters) throws XmlRpcException, OdooApiException { - RowCollection rows = super._searchAndReadObject(filters); - List result = new ArrayList<>(); - rows.forEach(row -> { - result.add(new FemsDevice(this, row)); - }); - return result; - } - - @Override - protected String[] getFields() { - return FemsDevice.getFields(); - } -} diff --git a/backend/src/main/java/io/openems/femsserver/utilities/JsonUtils.java b/backend/src/main/java/io/openems/femsserver/utilities/JsonUtils.java deleted file mode 100644 index 243d46d5f05..00000000000 --- a/backend/src/main/java/io/openems/femsserver/utilities/JsonUtils.java +++ /dev/null @@ -1,102 +0,0 @@ -package io.openems.femsserver.utilities; - -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -public class JsonUtils { - public static JsonArray getAsJsonArray(JsonElement jElement) throws OpenemsException { - if (!jElement.isJsonArray()) { - throw new OpenemsException("Config is not a JsonArray: " + jElement); - } - return jElement.getAsJsonArray(); - }; - - public static JsonArray getAsJsonArray(JsonElement jElement, String memberName) throws OpenemsException { - JsonElement jSubElement = getSubElement(jElement, memberName); - if (!jSubElement.isJsonArray()) { - throw new OpenemsException("Config [" + memberName + "] is not a JsonArray: " + jSubElement); - } - return jSubElement.getAsJsonArray(); - }; - - public static JsonObject getAsJsonObject(JsonElement jElement) throws OpenemsException { - if (!jElement.isJsonObject()) { - throw new OpenemsException("Config is not a JsonObject: " + jElement); - } - return jElement.getAsJsonObject(); - }; - - public static JsonObject getAsJsonObject(JsonElement jElement, String memberName) throws OpenemsException { - JsonElement jsubElement = getSubElement(jElement, memberName); - if (!jsubElement.isJsonObject()) { - throw new OpenemsException("Config is not a JsonObject: " + jsubElement); - } - return jsubElement.getAsJsonObject(); - }; - - public static JsonPrimitive getAsPrimitive(JsonElement jElement, String memberName) throws OpenemsException { - JsonElement jSubElement = getSubElement(jElement, memberName); - return getAsPrimitive(jSubElement); - } - - public static JsonPrimitive getAsPrimitive(JsonElement jElement) throws OpenemsException { - if (!jElement.isJsonPrimitive()) { - throw new OpenemsException("Element is not a JsonPrimitive: " + jElement); - } - return jElement.getAsJsonPrimitive(); - } - - public static String getAsString(JsonElement jElement) throws OpenemsException { - JsonPrimitive jPrimitive = getAsPrimitive(jElement); - if (!jPrimitive.isString()) { - throw new OpenemsException("Config is not a String: " + jPrimitive); - } - return jPrimitive.getAsString(); - } - - public static String getAsString(JsonElement jElement, String memberName) throws OpenemsException { - JsonPrimitive jPrimitive = getAsPrimitive(jElement, memberName); - if (!jPrimitive.isString()) { - throw new OpenemsException("[" + memberName + "] is not a String: " + jPrimitive); - } - return jPrimitive.getAsString(); - } - - public static int getAsInt(JsonElement jElement, String memberName) throws OpenemsException { - JsonPrimitive jPrimitive = getAsPrimitive(jElement, memberName); - if (jPrimitive.isNumber()) { - return jPrimitive.getAsInt(); - } else if (jPrimitive.isString()) { - String string = jPrimitive.getAsString(); - return Integer.parseInt(string); - } - throw new OpenemsException("[" + memberName + "] is not a Number: " + jPrimitive); - } - - public static ZonedDateTime getAsZonedDateTime(JsonElement jElement, String memberName, ZoneId timezone) - throws OpenemsException { - String[] date = JsonUtils.getAsString(jElement, memberName).split("-"); - try { - int year = Integer.valueOf(date[0]); - int month = Integer.valueOf(date[1]); - int day = Integer.valueOf(date[2]); - return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, timezone); - } catch (ArrayIndexOutOfBoundsException e) { - throw new OpenemsException("Unable to parse date [" + memberName + "] from [" + jElement + "]: " + e); - } - } - - public static JsonElement getSubElement(JsonElement jElement, String memberName) throws OpenemsException { - JsonObject jObject = getAsJsonObject(jElement); - if (!jObject.has(memberName)) { - throw new OpenemsException("[" + memberName + "] is missing in Config: " + jElement); - } - return jObject.get(memberName); - } - -} diff --git a/backend/src/main/java/io/openems/femsserver/utilities/WebSocketUtils.java b/backend/src/main/java/io/openems/femsserver/utilities/WebSocketUtils.java deleted file mode 100644 index ec9594c0199..00000000000 --- a/backend/src/main/java/io/openems/femsserver/utilities/WebSocketUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package io.openems.femsserver.utilities; - -import org.java_websocket.WebSocket; -import org.java_websocket.exceptions.WebsocketNotConnectedException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonObject; - -public class WebSocketUtils { - - private static Logger log = LoggerFactory.getLogger(WebSocketUtils.class); - - /** - * Send a message to a websocket - * - * @param j - * @return true if successful, otherwise false - */ - public static boolean send(WebSocket websocket, JsonObject j) { - try { - // log.info("Send: " + j.toString()); - websocket.send(j.toString()); - return true; - } catch (WebsocketNotConnectedException e) { - return false; - } - } - - public static boolean sendAsDevice(WebSocket websocket, JsonObject j, int fems) { - j.addProperty("device", "fems" + fems); - return send(websocket, j); - } -} diff --git a/common/.classpath b/common/.classpath new file mode 100644 index 00000000000..35bca5eaed5 --- /dev/null +++ b/common/.classpath @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 00000000000..4b824fb52ad --- /dev/null +++ b/common/.gitignore @@ -0,0 +1,13 @@ +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* +/target/ +/openems diff --git a/common/.project b/common/.project new file mode 100644 index 00000000000..d546e76feba --- /dev/null +++ b/common/.project @@ -0,0 +1,23 @@ + + + common + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/common/.settings/org.eclipse.core.resources.prefs b/common/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 00000000000..4c28b1a898a --- /dev/null +++ b/common/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/test/java=UTF-8 +encoding/=UTF-8 diff --git a/common/.settings/org.eclipse.jdt.core.prefs b/common/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..fd9afef6e9e --- /dev/null +++ b/common/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning diff --git a/common/.settings/org.eclipse.m2e.core.prefs b/common/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000000..14b697b7bbb --- /dev/null +++ b/common/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/common/pom.xml b/common/pom.xml new file mode 100644 index 00000000000..5191c6fa7e6 --- /dev/null +++ b/common/pom.xml @@ -0,0 +1,55 @@ + + 4.0.0 + + io.openems + common + 1.0.0 + jar + + OpenEMS Common + http://openems.io + + + UTF-8 + 2.4 + 2.6.2 + 22.0 + 1.3.3 + 1.7.15 + + + + + junit + junit + 3.8.1 + test + + + com.google.code.gson + gson + ${gson.version} + + + com.google.guava + guava + ${guava.version} + + + org.java-websocket + Java-WebSocket + ${websocket.version} + + + org.influxdb + influxdb-java + ${influxdb.version} + + + org.slf4j + slf4j-api + ${slf4j.version} + + + diff --git a/common/src/main/java/io/openems/common/api/TimedataSource.java b/common/src/main/java/io/openems/common/api/TimedataSource.java new file mode 100644 index 00000000000..0744d3a81d4 --- /dev/null +++ b/common/src/main/java/io/openems/common/api/TimedataSource.java @@ -0,0 +1,36 @@ +package io.openems.common.api; + +import java.time.ZonedDateTime; +import java.util.Optional; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsException; + +public interface TimedataSource { + /** + * Queries the database and returns a JsonArray of the form + * + *
+	 *	[{
+	 *  	timestamp: "2017-03-21T08:55:20Z",
+	 *  	channels: {
+	 *			'thing': {
+	 *				'channel': 'value'
+	 *			}
+	 *		}
+	 * 	}]
+	 * 
+ * + * @param deviceId + * @param fromDate + * @param toDate + * @param channels + * @param resolution + * @return + * @throws OpenemsException + */ + public JsonArray queryHistoricData(Optional deviceIdOpt, ZonedDateTime fromDate, ZonedDateTime toDate, JsonObject channels, + int resolution/* , JsonObject kWh */) throws OpenemsException; +} diff --git a/backend/src/main/java/io/openems/femsserver/utilities/OpenemsException.java b/common/src/main/java/io/openems/common/exceptions/OpenemsException.java similarity index 56% rename from backend/src/main/java/io/openems/femsserver/utilities/OpenemsException.java rename to common/src/main/java/io/openems/common/exceptions/OpenemsException.java index 355597278ff..aabebeee623 100644 --- a/backend/src/main/java/io/openems/femsserver/utilities/OpenemsException.java +++ b/common/src/main/java/io/openems/common/exceptions/OpenemsException.java @@ -1,4 +1,4 @@ -package io.openems.femsserver.utilities; +package io.openems.common.exceptions; public class OpenemsException extends Exception { private static final long serialVersionUID = 5015013132334439401L; @@ -6,4 +6,8 @@ public class OpenemsException extends Exception { public OpenemsException(String message) { super(message); } + + public OpenemsException(String message, Throwable cause) { + super(message, cause); + } } diff --git a/common/src/main/java/io/openems/common/session/Role.java b/common/src/main/java/io/openems/common/session/Role.java new file mode 100644 index 00000000000..8a2f9c98922 --- /dev/null +++ b/common/src/main/java/io/openems/common/session/Role.java @@ -0,0 +1,19 @@ +package io.openems.common.session; + +public enum Role { + ADMIN, INSTALLER, OWNER, GUEST; + + public static Role getRole(String name) { + switch (name.toLowerCase()) { + case "admin": + return ADMIN; + case "installer": + return INSTALLER; + case "owner": + return OWNER; + case "guest": + default: + return GUEST; + } + } +} diff --git a/common/src/main/java/io/openems/common/session/Session.java b/common/src/main/java/io/openems/common/session/Session.java new file mode 100644 index 00000000000..0aeba0bda2e --- /dev/null +++ b/common/src/main/java/io/openems/common/session/Session.java @@ -0,0 +1,41 @@ +package io.openems.common.session; + +public class Session { + private final String token; + private boolean valid = false; + + /** + * store additional metadata to this session + */ + private final D data; + + protected Session(String token, D data) { + this.token = token; + this.data = data; + } + + public String getToken() { + return token; + } + + public void setValid() { + this.valid = true; + } + + public void setInvalid() { + this.valid = false; + } + + public boolean isValid() { + return this.valid; + } + + public D getData() { + return data; + } + + @Override + public String toString() { + return "Session [token=" + token + ", valid=" + valid + ", data=" + data + "]"; + } +} diff --git a/common/src/main/java/io/openems/common/session/SessionData.java b/common/src/main/java/io/openems/common/session/SessionData.java new file mode 100644 index 00000000000..671af32ba0e --- /dev/null +++ b/common/src/main/java/io/openems/common/session/SessionData.java @@ -0,0 +1,5 @@ +package io.openems.common.session; + +public class SessionData { + +} diff --git a/common/src/main/java/io/openems/common/session/SessionManager.java b/common/src/main/java/io/openems/common/session/SessionManager.java new file mode 100644 index 00000000000..d16231627d3 --- /dev/null +++ b/common/src/main/java/io/openems/common/session/SessionManager.java @@ -0,0 +1,113 @@ +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.common.session; + +import java.math.BigInteger; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +import org.java_websocket.WebSocket; + +import io.openems.common.utils.SecureRandomSingleton; + +public abstract class SessionManager, D extends SessionData> { + + private final static int SESSION_ID_LENGTH = 130; + + // TODO: invalidate old sessions in separate thread: call _removeSession to do so + private final Map sessions = new ConcurrentHashMap<>(); + + protected SessionManager() {} + + public S createNewSession(String token, D data) { + S session = this._createNewSession(token, data); + this._putSession(token, session); + return session; + } + + public S createNewSession(D data) { + String token = this.generateToken(); + return this.createNewSession(token, data); + } + + public Optional getSessionByToken(String token) { + return Optional.ofNullable(this.sessions.get(token)); + } + + public void removeSession(String token) { + S session = this.sessions.get(token); + if(session != null) { + session.setInvalid(); + this._removeSession(token); + } + } + + public void removeSession(Session session) { + session.setInvalid(); + this.removeSession(session.getToken()); + } + + protected String generateToken() { + // Source: http://stackoverflow.com/a/41156 + SecureRandom sr = SecureRandomSingleton.getInstance(); + return new BigInteger(SESSION_ID_LENGTH, sr).toString(32); + } + + public Collection getSessions() { + return Collections.unmodifiableCollection(this.sessions.values()); + } + + /* + * Those methods are prone to be overwritten by inheritance + */ + /** + * Replies a Session object of type T + * + * @param token + * @param data + * @return + */ + protected abstract S _createNewSession(String token, D data); + + /** + * This method is always called when adding a session to local database + * + * @param token + * @param session + */ + protected void _putSession(String token, S session) { + this.sessions.put(token, session); + } + + /** + * This method is always called when removing a session from local database + * + * @param token + * @param session + */ + protected void _removeSession(String token) { + this.sessions.remove(token); + } +} diff --git a/backend/src/main/java/io/openems/femsserver/utilities/Address.java b/common/src/main/java/io/openems/common/types/ChannelAddress.java similarity index 63% rename from backend/src/main/java/io/openems/femsserver/utilities/Address.java rename to common/src/main/java/io/openems/common/types/ChannelAddress.java index a522b669e38..fc5270c5060 100644 --- a/backend/src/main/java/io/openems/femsserver/utilities/Address.java +++ b/common/src/main/java/io/openems/common/types/ChannelAddress.java @@ -1,10 +1,10 @@ -package io.openems.femsserver.utilities; +package io.openems.common.types; -public class Address { +public class ChannelAddress { private final String thingId; private final String channelId; - public Address(String thingId, String channelId) { + public ChannelAddress(String thingId, String channelId) { super(); this.thingId = thingId; this.channelId = channelId; @@ -23,10 +23,10 @@ public String toString() { return thingId + "/" + channelId; } - public static Address fromString(String address) { + public static ChannelAddress fromString(String address) { String[] addressArray = address.split("/"); String thingId = addressArray[0]; String channelId = addressArray[1]; - return new Address(thingId, channelId); + return new ChannelAddress(thingId, channelId); } } diff --git a/common/src/main/java/io/openems/common/types/Device.java b/common/src/main/java/io/openems/common/types/Device.java new file mode 100644 index 00000000000..a76f2ffea9d --- /dev/null +++ b/common/src/main/java/io/openems/common/types/Device.java @@ -0,0 +1,48 @@ +package io.openems.common.types; + +import io.openems.common.session.Role; + +/** + * Helper class to store tuple of device name and role + * + * @author stefan.feilmeier + * + */ +public class Device { + private final String name; + private final String comment; + private final String producttype; + private final Role role; + private boolean online = false; + + public Device(String name, String comment, String producttype, String role) { + this.name = name; + this.comment = comment; + this.producttype = producttype; + this.role = Role.getRole(role); + } + + public String getName() { + return name; + } + + public Role getRole() { + return role; + } + + public void setOnline(boolean online) { + this.online = online; + } + + public boolean isOnline() { + return online; + } + + public String getComment() { + return comment; + } + + public String getProducttype() { + return producttype; + } +} diff --git a/edge/src/io/openems/impl/persistence/fenecon/FieldValue.java b/common/src/main/java/io/openems/common/types/FieldValue.java similarity index 94% rename from edge/src/io/openems/impl/persistence/fenecon/FieldValue.java rename to common/src/main/java/io/openems/common/types/FieldValue.java index 7c6846ef883..6f352879fb6 100644 --- a/edge/src/io/openems/impl/persistence/fenecon/FieldValue.java +++ b/common/src/main/java/io/openems/common/types/FieldValue.java @@ -18,7 +18,7 @@ * Contributors: * FENECON GmbH - initial API and implementation and initial documentation *******************************************************************************/ -package io.openems.impl.persistence.fenecon; +package io.openems.common.types; public abstract class FieldValue { public final String field; diff --git a/edge/src/io/openems/impl/persistence/fenecon/NullFieldValue.java b/common/src/main/java/io/openems/common/types/NullFieldValue.java similarity index 93% rename from edge/src/io/openems/impl/persistence/fenecon/NullFieldValue.java rename to common/src/main/java/io/openems/common/types/NullFieldValue.java index d5bfcd98cb3..ac8819aad60 100644 --- a/edge/src/io/openems/impl/persistence/fenecon/NullFieldValue.java +++ b/common/src/main/java/io/openems/common/types/NullFieldValue.java @@ -18,7 +18,7 @@ * Contributors: * FENECON GmbH - initial API and implementation and initial documentation *******************************************************************************/ -package io.openems.impl.persistence.fenecon; +package io.openems.common.types; public class NullFieldValue extends FieldValue { diff --git a/edge/src/io/openems/impl/persistence/fenecon/NumberFieldValue.java b/common/src/main/java/io/openems/common/types/NumberFieldValue.java similarity index 93% rename from edge/src/io/openems/impl/persistence/fenecon/NumberFieldValue.java rename to common/src/main/java/io/openems/common/types/NumberFieldValue.java index 698f94d395c..d5d775d5094 100644 --- a/edge/src/io/openems/impl/persistence/fenecon/NumberFieldValue.java +++ b/common/src/main/java/io/openems/common/types/NumberFieldValue.java @@ -18,7 +18,7 @@ * Contributors: * FENECON GmbH - initial API and implementation and initial documentation *******************************************************************************/ -package io.openems.impl.persistence.fenecon; +package io.openems.common.types; public class NumberFieldValue extends FieldValue { diff --git a/edge/src/io/openems/impl/persistence/fenecon/StringFieldValue.java b/common/src/main/java/io/openems/common/types/StringFieldValue.java similarity index 93% rename from edge/src/io/openems/impl/persistence/fenecon/StringFieldValue.java rename to common/src/main/java/io/openems/common/types/StringFieldValue.java index 46a3668d210..52404153e16 100644 --- a/edge/src/io/openems/impl/persistence/fenecon/StringFieldValue.java +++ b/common/src/main/java/io/openems/common/types/StringFieldValue.java @@ -18,7 +18,7 @@ * Contributors: * FENECON GmbH - initial API and implementation and initial documentation *******************************************************************************/ -package io.openems.impl.persistence.fenecon; +package io.openems.common.types; public class StringFieldValue extends FieldValue { diff --git a/common/src/main/java/io/openems/common/utils/EnvUtils.java b/common/src/main/java/io/openems/common/utils/EnvUtils.java new file mode 100644 index 00000000000..9836e007d67 --- /dev/null +++ b/common/src/main/java/io/openems/common/utils/EnvUtils.java @@ -0,0 +1,33 @@ +package io.openems.common.utils; + +import java.util.Optional; + +import io.openems.common.exceptions.OpenemsException; + +public class EnvUtils { + public static boolean isSet(String name) { + return System.getenv(name) != null; + }; + + public static String getAsString(String name) throws OpenemsException { + String value = System.getenv(name); + if (value == null || value.isEmpty()) { + throw new OpenemsException("ENV [" + name + "] is not set"); + } + return value; + }; + + public static Optional getAsOptionalString(String name) { + String value = System.getenv(name); + return Optional.ofNullable(value); + }; + + public static int getAsInt(String name) throws OpenemsException { + String valueString = EnvUtils.getAsString(name); + try { + return Integer.valueOf(valueString); + } catch (NumberFormatException e) { + throw new OpenemsException("ENV [" + name + "] value [" + valueString + "] is not an integer"); + } + }; +} diff --git a/backend/src/main/java/io/openems/femsserver/influx/InfluxdbQueryWrapper.java b/common/src/main/java/io/openems/common/utils/InfluxdbUtils.java similarity index 71% rename from backend/src/main/java/io/openems/femsserver/influx/InfluxdbQueryWrapper.java rename to common/src/main/java/io/openems/common/utils/InfluxdbUtils.java index a83f5d52608..baaa5465003 100644 --- a/backend/src/main/java/io/openems/femsserver/influx/InfluxdbQueryWrapper.java +++ b/common/src/main/java/io/openems/common/utils/InfluxdbUtils.java @@ -1,9 +1,8 @@ -package io.openems.femsserver.influx; +package io.openems.common.utils; import java.time.Instant; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.List; import java.util.Map.Entry; @@ -15,69 +14,26 @@ import org.influxdb.dto.QueryResult; import org.influxdb.dto.QueryResult.Result; import org.influxdb.dto.QueryResult.Series; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonNull; import com.google.gson.JsonObject; -import io.openems.femsserver.utilities.Address; -import io.openems.femsserver.utilities.JsonUtils; -import io.openems.femsserver.utilities.OpenemsException; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.types.ChannelAddress; -public class InfluxdbQueryWrapper { +public class InfluxdbUtils { - private final static Logger log = LoggerFactory.getLogger(InfluxdbQueryWrapper.class); - private final static String DB_NAME = "db"; - - public static JsonObject query(Optional _influxdb, Optional fems, ZonedDateTime fromDate, - ZonedDateTime toDate, JsonObject channels, int resolution/* , JsonObject kWh */) throws OpenemsException { - // Prepare return object - JsonObject jQueryreply = new JsonObject(); - jQueryreply.addProperty("mode", "history"); - JsonArray jData = new JsonArray(); - JsonObject jkWh = new JsonObject(); - - // Prepare date - toDate = toDate.plusDays(1).truncatedTo(ChronoUnit.DAYS); - - ZonedDateTime nowDate = ZonedDateTime.now(); - if (nowDate.isBefore(toDate)) { - toDate = nowDate; - } - if (fromDate.isAfter(toDate)) { - fromDate = toDate; - } - - if (_influxdb.isPresent()) { - InfluxDB influxdb = _influxdb.get(); - // TODO fix data - if (fromDate.toLocalDate().equals(toDate.minusWeeks(1).toLocalDate()) - || (fromDate.toLocalDate().isAfter(toDate.minusWeeks(1).toLocalDate()) && fromDate.isBefore(toDate)) - || fromDate.toLocalDate().equals(toDate.toLocalDate())) { - jData = InfluxdbQueryWrapper.queryData(influxdb, fems, fromDate, toDate, channels, resolution); - // jkWh = InfluxdbQueryWrapper.querykWh(influxdb, fems, fromDate, toDate, channels, resolution, kWh); - } - } else { - jData = new JsonArray(); - jkWh = new JsonObject(); - } - jQueryreply.add("data", jData); - jQueryreply.add("kWh", jkWh); - return jQueryreply; - } - - private static JsonArray queryData(InfluxDB influxdb, Optional fems, ZonedDateTime fromDate, - ZonedDateTime toDate, JsonObject channels, int resolution) throws OpenemsException { + public static JsonArray queryHistoricData(InfluxDB influxdb, String database, Optional deviceId, + ZonedDateTime fromDate, ZonedDateTime toDate, JsonObject channels, int resolution) throws OpenemsException { // Prepare query string StringBuilder query = new StringBuilder("SELECT "); query.append(toChannelAddressList(channels)); query.append(" FROM data WHERE "); - if (fems.isPresent()) { + if (deviceId.isPresent()) { query.append("fems = '"); - query.append(fems.get()); + query.append(deviceId.get()); query.append("' AND "); } query.append("time > "); @@ -88,9 +44,9 @@ private static JsonArray queryData(InfluxDB influxdb, Optional fems, Zo query.append("s"); query.append(" GROUP BY time("); query.append(resolution); - query.append("s) fill(previous)"); + query.append("s) fill(null)"); - QueryResult queryResult = executeQuery(influxdb, query.toString()); + QueryResult queryResult = executeQuery(influxdb, database, query.toString()); JsonArray j = new JsonArray(); for (Result result : queryResult.getResults()) { @@ -98,12 +54,12 @@ private static JsonArray queryData(InfluxDB influxdb, Optional fems, Zo if (seriess != null) { for (Series series : seriess) { // create thing/channel index - ArrayList
addressIndex = new ArrayList<>(); + ArrayList addressIndex = new ArrayList<>(); for (String column : series.getColumns()) { if (column.equals("time")) { continue; } - addressIndex.add(Address.fromString(column)); + addressIndex.add(ChannelAddress.fromString(column)); } // first: create empty timestamp objects for (List values : series.getValues()) { @@ -132,7 +88,7 @@ private static JsonArray queryData(InfluxDB influxdb, Optional fems, Zo for (int columnIndex = 1; columnIndex < series.getColumns().size(); columnIndex++) { for (int timeIndex = 0; timeIndex < series.getValues().size(); timeIndex++) { Double value = (Double) series.getValues().get(timeIndex).get(columnIndex); - Address address = addressIndex.get(columnIndex - 1); + ChannelAddress address = addressIndex.get(columnIndex - 1); j.get(timeIndex).getAsJsonObject().get("channels").getAsJsonObject() .get(address.getThingId()).getAsJsonObject() .addProperty(address.getChannelId(), value); @@ -144,8 +100,9 @@ private static JsonArray queryData(InfluxDB influxdb, Optional fems, Zo return j; } - private static JsonObject querykWh(InfluxDB influxdb, Optional fems, ZonedDateTime fromDate, - ZonedDateTime toDate, JsonObject channels, int resolution, JsonObject kWh) throws OpenemsException { + private static JsonObject querykWh(InfluxDB influxdb, String database, Optional fems, + ZonedDateTime fromDate, ZonedDateTime toDate, JsonObject channels, int resolution, JsonObject kWh) + throws OpenemsException { JsonArray gridThing = getGridThing(kWh); JsonArray storageThing = getStorageThing(kWh); JsonArray things = new JsonArray(); @@ -175,7 +132,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo query.append("s"); query.append(" GROUP BY time(1s) fill(previous))"); - QueryResult queryResult = executeQuery(influxdb, query.toString()); + QueryResult queryResult = executeQuery(influxdb, database, query.toString()); Double sumProduction = 0.0; try { @@ -187,7 +144,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo } } } catch (Exception e) { - log.warn("Error parsing SUM production: " + e); + System.out.println("Error parsing SUM production: " + e); } /* @@ -208,7 +165,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo query.append(String.valueOf(toDate.toEpochSecond())); query.append("s"); - queryResult = executeQuery(influxdb, query.toString()); + queryResult = executeQuery(influxdb, database, query.toString()); int second = 0; try { @@ -218,7 +175,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo Instant timestampInstant = Instant.ofEpochMilli((long) ((Double) l.get(0)).doubleValue()); ZonedDateTime timestamp = ZonedDateTime.ofInstant(timestampInstant, fromDate.getZone()); if (timestamp.equals(fromDate)) { - log.info("Parsing FIRST: nothing null"); + System.out.println("Parsing FIRST: nothing null"); } else { second = timestamp.getSecond(); } @@ -226,7 +183,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo } } } catch (Exception e) { - log.warn("Error parsing FIRST production: " + e); + System.out.println("Error parsing FIRST production: " + e); } /* @@ -244,7 +201,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo query.append(String.valueOf(fromDate.toEpochSecond())); query.append("s"); - queryResult = executeQuery(influxdb, query.toString()); + queryResult = executeQuery(influxdb, query.toString(), database); try { if (queryResult.getResults() != null) { @@ -263,7 +220,7 @@ private static JsonObject querykWh(InfluxDB influxdb, Optional fems, Zo } } } catch (Exception e) { - log.warn("Error parsing LAST production: " + e); + System.out.println("Error parsing LAST production: " + e); } Double avg = sumProduction / 3600 / 1000; @@ -338,16 +295,16 @@ private static String toChannelAddressList(JsonObject channels) throws OpenemsEx return String.join(", ", channelAddresses); } - private static QueryResult executeQuery(InfluxDB influxdb, String query) throws OpenemsException { + private static QueryResult executeQuery(InfluxDB influxdb, String database, String query) throws OpenemsException { // Parse result QueryResult queryResult; try { - queryResult = influxdb.query(new Query(query, DB_NAME), TimeUnit.MILLISECONDS); + queryResult = influxdb.query(new Query(query, database), TimeUnit.MILLISECONDS); } catch (RuntimeException e) { - throw new OpenemsException("InfluxDB query runtime error: " + e.getMessage()); + throw new OpenemsException("InfluxDB query runtime error. Query: " + query + ", Error: " + e.getMessage()); } if (queryResult.hasError()) { - throw new OpenemsException("InfluxDB query error: " + queryResult.getError()); + throw new OpenemsException("InfluxDB query error. Query: " + query + ", Error: " + queryResult.getError()); } return queryResult; } diff --git a/common/src/main/java/io/openems/common/utils/JsonUtils.java b/common/src/main/java/io/openems/common/utils/JsonUtils.java new file mode 100644 index 00000000000..f24a53d9c7a --- /dev/null +++ b/common/src/main/java/io/openems/common/utils/JsonUtils.java @@ -0,0 +1,246 @@ +package io.openems.common.utils; + +import java.net.Inet4Address; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsException; + +// TODO use getAsOptional***() as basis for getAs***() to avoid unnecessary exceptions +public class JsonUtils { + public static JsonArray getAsJsonArray(JsonElement jElement) throws OpenemsException { + if (!jElement.isJsonArray()) { + throw new OpenemsException("This is not a JsonArray: " + jElement); + } + return jElement.getAsJsonArray(); + }; + + public static JsonArray getAsJsonArray(JsonElement jElement, String memberName) throws OpenemsException { + JsonElement jSubElement = getSubElement(jElement, memberName); + if (!jSubElement.isJsonArray()) { + throw new OpenemsException("Element [" + memberName + "] is not a JsonArray: " + jSubElement); + } + return jSubElement.getAsJsonArray(); + }; + + public static Optional getAsOptionalJsonArray(JsonElement jElement, String memberName) { + try { + return Optional.of(getAsJsonArray(jElement, memberName)); + } catch (OpenemsException e) { + return Optional.empty(); + } + } + + public static JsonObject getAsJsonObject(JsonElement jElement) throws OpenemsException { + if (!jElement.isJsonObject()) { + throw new OpenemsException("This is not a JsonObject: " + jElement); + } + return jElement.getAsJsonObject(); + }; + + public static JsonObject getAsJsonObject(JsonElement jElement, String memberName) throws OpenemsException { + JsonElement jsubElement = getSubElement(jElement, memberName); + if (!jsubElement.isJsonObject()) { + throw new OpenemsException("Element [" + memberName + "] is not a JsonObject: " + jsubElement); + } + return jsubElement.getAsJsonObject(); + }; + + public static Optional getAsOptionalJsonObject(JsonElement jElement, String memberName) { + try { + return Optional.of(getAsJsonObject(jElement, memberName)); + } catch (OpenemsException e) { + return Optional.empty(); + } + } + + public static JsonPrimitive getAsPrimitive(JsonElement jElement, String memberName) throws OpenemsException { + JsonElement jSubElement = getSubElement(jElement, memberName); + return getAsPrimitive(jSubElement); + } + + public static JsonPrimitive getAsPrimitive(JsonElement jElement) throws OpenemsException { + if (!jElement.isJsonPrimitive()) { + throw new OpenemsException("This is not a JsonPrimitive: " + jElement); + } + return jElement.getAsJsonPrimitive(); + } + + public static String getAsString(JsonElement jElement) throws OpenemsException { + JsonPrimitive jPrimitive = getAsPrimitive(jElement); + if (!jPrimitive.isString()) { + throw new OpenemsException("This is not a String: " + jPrimitive); + } + return jPrimitive.getAsString(); + } + + public static boolean getAsBoolean(JsonElement jElement) throws OpenemsException { + JsonPrimitive jPrimitive = getAsPrimitive(jElement); + if (!jPrimitive.isBoolean()) { + throw new OpenemsException("This is not a Boolean: " + jPrimitive); + } + return jPrimitive.getAsBoolean(); + } + + public static Optional getAsOptionalString(JsonElement jElement, String memberName) { + try { + return Optional.of(getAsString(jElement, memberName)); + } catch (OpenemsException e) { + return Optional.empty(); + } + } + + public static String getAsString(JsonElement jElement, String memberName) throws OpenemsException { + JsonPrimitive jPrimitive = getAsPrimitive(jElement, memberName); + if (!jPrimitive.isString()) { + throw new OpenemsException("Element [" + memberName + "] is not a String: " + jPrimitive); + } + return jPrimitive.getAsString(); + } + + public static int getAsInt(JsonElement jElement, String memberName) throws OpenemsException { + JsonPrimitive jPrimitive = getAsPrimitive(jElement, memberName); + if (jPrimitive.isNumber()) { + return jPrimitive.getAsInt(); + } else if (jPrimitive.isString()) { + String string = jPrimitive.getAsString(); + return Integer.parseInt(string); + } + throw new OpenemsException("Element [" + memberName + "] is not an Integer: " + jPrimitive); + } + + public static boolean getAsBoolean(JsonElement jElement, String memberName) throws OpenemsException { + JsonPrimitive jPrimitive = getAsPrimitive(jElement, memberName); + if (!jPrimitive.isBoolean()) { + throw new OpenemsException("Element [" + memberName + "] is not a Boolean: " + jPrimitive); + } + return jPrimitive.getAsBoolean(); + } + + /** + * Takes a json in the form 'YYYY-MM-DD' and converts it to a ZonedDateTime with + * hour, minute and second set to zero. + * + * @param jElement + * @param memberName + * @param timezone + * @return + * @throws OpenemsException + */ + public static ZonedDateTime getAsZonedDateTime(JsonElement jElement, String memberName, ZoneId timezone) + throws OpenemsException { + String[] date = JsonUtils.getAsString(jElement, memberName).split("-"); + try { + int year = Integer.valueOf(date[0]); + int month = Integer.valueOf(date[1]); + int day = Integer.valueOf(date[2]); + return ZonedDateTime.of(year, month, day, 0, 0, 0, 0, timezone); + } catch (ArrayIndexOutOfBoundsException e) { + throw new OpenemsException("Element [" + memberName + "] is not a Date: " + jElement + ". Error: " + e); + } + } + + public static long getAsLong(JsonElement jElement, String memberName) throws OpenemsException { + JsonPrimitive jPrimitive = getAsPrimitive(jElement, memberName); + if (jPrimitive.isNumber()) { + return jPrimitive.getAsLong(); + } else if (jPrimitive.isString()) { + String string = jPrimitive.getAsString(); + return Long.parseLong(string); + } + throw new OpenemsException("[" + memberName + "] is not a Number: " + jPrimitive); + } + + public static JsonElement getSubElement(JsonElement jElement, String memberName) throws OpenemsException { + JsonObject jObject = getAsJsonObject(jElement); + if (!jObject.has(memberName)) { + throw new OpenemsException("Element [" + memberName + "] is not a Subelement of: " + jElement); + } + return jObject.get(memberName); + } + + /** + * Merges the second Object into the first object + * + * @param j1 + * @param j2 + * @return + */ + public static JsonObject merge(JsonObject j1, JsonObject j2) { + // TODO be smarter: merge down the tree + for (Entry entry : j2.entrySet()) { + j1.add(entry.getKey(), entry.getValue()); + } + return j1; + } + + public static Optional merge(Optional j1Opt, Optional j2Opt) { + if (j1Opt.isPresent() && j2Opt.isPresent()) { + return Optional.of(JsonUtils.merge(j1Opt.get(), j2Opt.get())); + } + if (j1Opt.isPresent()) { + return j1Opt; + } + return j2Opt; + } + + public static boolean hasElement(JsonElement j, String... paths) { + return getMatchingElements(j, paths).size() > 0; + } + + public static Set getMatchingElements(JsonElement j, String... paths) { + Set result = new HashSet(); + if (paths.length == 0) { + // last path element + result.add(j); + return result; + } + String path = paths[0]; + if (j.isJsonObject()) { + JsonObject jO = j.getAsJsonObject(); + if (jO.has(path)) { + List nextPathsList = new ArrayList(Arrays.asList(paths)); + nextPathsList.remove(0); + String[] nextPaths = nextPathsList.toArray(new String[0]); + result.addAll(getMatchingElements(jO.get(path), nextPaths)); + } + } else if (j.isJsonArray()) { + for (JsonElement jE : j.getAsJsonArray()) { + result.addAll(getMatchingElements(jE, paths)); + } + } else if (j.isJsonPrimitive()) { + JsonPrimitive jP = j.getAsJsonPrimitive(); + if (jP.isString()) { + if (jP.getAsString().equals(path)) { + result.add(jP); + } + } + } + return result; + } + + /** + * Pretty print a JsonElement + * + * @param j + */ + public static void prettyPrint(JsonElement j) { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String json = gson.toJson(j); + System.out.println(json); + } +} diff --git a/edge/src/io/openems/core/utilities/SecureRandomSingleton.java b/common/src/main/java/io/openems/common/utils/SecureRandomSingleton.java similarity index 95% rename from edge/src/io/openems/core/utilities/SecureRandomSingleton.java rename to common/src/main/java/io/openems/common/utils/SecureRandomSingleton.java index 49e99a8817b..52e99c20563 100644 --- a/edge/src/io/openems/core/utilities/SecureRandomSingleton.java +++ b/common/src/main/java/io/openems/common/utils/SecureRandomSingleton.java @@ -18,7 +18,7 @@ * Contributors: * FENECON GmbH - initial API and implementation and initial documentation *******************************************************************************/ -package io.openems.core.utilities; +package io.openems.common.utils; import java.security.SecureRandom; diff --git a/common/src/main/java/io/openems/common/websocket/AbstractWebsocketServer.java b/common/src/main/java/io/openems/common/websocket/AbstractWebsocketServer.java new file mode 100644 index 00000000000..d4ec3b8b82d --- /dev/null +++ b/common/src/main/java/io/openems/common/websocket/AbstractWebsocketServer.java @@ -0,0 +1,128 @@ +package io.openems.common.websocket; + +import java.net.InetSocketAddress; +import java.util.Optional; + +import org.java_websocket.WebSocket; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.server.WebSocketServer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Maps; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import io.openems.common.session.Session; +import io.openems.common.session.SessionData; +import io.openems.common.session.SessionManager; +import io.openems.common.utils.JsonUtils; + +public abstract class AbstractWebsocketServer, D extends SessionData, M extends SessionManager> + extends WebSocketServer { + private final Logger log = LoggerFactory.getLogger(AbstractWebsocketServer.class); + protected final M sessionManager; + protected final BiMap websockets = Maps.synchronizedBiMap(HashBiMap.create()); + + protected abstract void onMessage(WebSocket websocket, JsonObject jMessage, Optional jMessageIdOpt, Optional deviceNameOpt); + + public AbstractWebsocketServer(int port, M sessionManager) { + super(new InetSocketAddress(port)); + this.sessionManager = sessionManager; + } + + /** + * Open event of websocket. + */ + @Override + public void onOpen(WebSocket arg0, ClientHandshake arg1) { + log.info("Websocket opened."); + } + + /** + * Close event of websocket. Removes the websocket. Keeps the session + */ + @Override + public void onClose(WebSocket websocket, int code, String reason, boolean remote) { + S session = this.websockets.get(websocket); + String sessionString; + if (session == null) { + sessionString = ""; + } else { + sessionString = session.toString(); + } + log.info("Websocket closed. " + sessionString + " Code [" + code + "] Reason [" + reason + "]"); + this.websockets.remove(websocket); + } + + /** + * Error event of websocket. Logs the error. + */ + @Override + public final void onError(WebSocket websocket, Exception ex) { + S session = this.websockets.get(websocket); + String sessionString; + if (session == null) { + sessionString = ""; + } else { + sessionString = session.toString(); + } + log.warn("Websocket error. " + sessionString + ": " + ex.getMessage()); + } + + /** + * Message event of websocket. Handles a new message. + */ + @Override + public final void onMessage(WebSocket websocket, String message) { + JsonObject jMessage = (new JsonParser()).parse(message).getAsJsonObject(); + Optional jMessageId = JsonUtils.getAsOptionalJsonArray(jMessage, "id"); + Optional deviceNameOpt = JsonUtils.getAsOptionalString(jMessage, "device"); + this.onMessage(websocket, jMessage, jMessageId, deviceNameOpt); + } + + + /** + * Get cookie from handshake + * + * @param handshake + * @return cookie as JsonObject + */ + protected JsonObject parseCookieFromHandshake(ClientHandshake handshake) { + JsonObject j = new JsonObject(); + if (handshake.hasFieldValue("cookie")) { + String cookieString = handshake.getFieldValue("cookie"); + for (String cookieVariable : cookieString.split("; ")) { + String[] keyValue = cookieVariable.split("="); + if (keyValue.length == 2) { + j.addProperty(keyValue[0], keyValue[1]); + } + } + } + return j; + } + + @Override + public final void onStart() { + // nothing to do + } + + /** + * Returns the Websocket for the given token + * + * @param name + * @return + */ + public Optional getWebsocketByToken(String token) { + Optional sessionOpt = (Optional) this.sessionManager.getSessionByToken(token); + if (!sessionOpt.isPresent()) { + return Optional.empty(); + } + S session = sessionOpt.get(); + return Optional.ofNullable(this.websockets.inverse().get(session)); + } + +} diff --git a/common/src/main/java/io/openems/common/websocket/DefaultMessages.java b/common/src/main/java/io/openems/common/websocket/DefaultMessages.java new file mode 100644 index 00000000000..6a1dfae9293 --- /dev/null +++ b/common/src/main/java/io/openems/common/websocket/DefaultMessages.java @@ -0,0 +1,325 @@ +package io.openems.common.websocket; + +import java.util.List; +import java.util.Optional; + +import com.google.common.collect.Multimap; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.types.Device; +import io.openems.common.types.FieldValue; +import io.openems.common.types.NumberFieldValue; +import io.openems.common.types.StringFieldValue; + +public class DefaultMessages { + + /** + *
+	 *	{
+	 *		authenticate: {
+	 *			mode: "allow",
+	 *			token: String
+	 *		}, metadata: {
+	 *			devices: [{
+	 *				name: String,
+	 *				comment: String,
+	 *				producttype: String,
+	 *				role: "admin" | "installer" | "owner" | "guest",
+	 *				online: boolean
+	 *			}]
+	 *		}
+	 *	}
+	 * - authenticate.role is only sent for OpenEMS Edge
+	 * - metadata.devices is only sent for OpenEMS Backend
+	 * 
+ * + * @param token + * @return + */ + public static JsonObject browserConnectionSuccessfulReply(String token, Optional roleOpt, List devices) { + JsonObject jAuthenticate = new JsonObject(); + jAuthenticate.addProperty("mode", "allow"); + if(roleOpt.isPresent()) { + jAuthenticate.addProperty("role", roleOpt.get()); + } + jAuthenticate.addProperty("token", token); + JsonObject j = new JsonObject(); + j.add("authenticate", jAuthenticate); + JsonObject jMetadata = new JsonObject(); + if(!devices.isEmpty()) { + JsonArray jDevices = new JsonArray(); + for(Device device : devices) { + JsonObject jDevice = new JsonObject(); + jDevice.addProperty("name", device.getName()); + jDevice.addProperty("comment", device.getComment()); + jDevice.addProperty("producttype", device.getProducttype()); + jDevice.addProperty("role", device.getRole().toString()); + jDevice.addProperty("online", device.isOnline()); + jDevices.add(jDevice); + } + jMetadata.add("devices", jDevices); + } + j.add("metadata", jMetadata); + return j; + } + + /** + *
+	 *	{
+	 *		authenticate: {
+	 *			mode: "deny"
+	 *		}
+	 *	}
+	 * 
+ * + * @param token + * @return + */ + public static JsonObject browserConnectionFailedReply() { + JsonObject jAuthenticate = new JsonObject(); + jAuthenticate.addProperty("mode", "deny"); + JsonObject j = new JsonObject(); + j.add("authenticate", jAuthenticate); + return j; + } + + /** + *
+	 *	{
+	 *		authenticate: {
+	 *			mode: "allow"
+	 *		}
+	 *	}
+	 * 
+ * + * @param token + * @return + */ + public static JsonObject openemsConnectionSuccessfulReply() { + JsonObject jAuthenticate = new JsonObject(); + jAuthenticate.addProperty("mode", "allow"); + JsonObject j = new JsonObject(); + j.add("authenticate", jAuthenticate); + return j; + } + + /** + *
+	 *	{
+	 *		authenticate: {
+	 *			mode: "deny",
+	 *			message: String
+	 *		}
+	 *	}
+	 * 
+ * + * @param token + * @return + */ + public static JsonObject openemsConnectionFailedReply(String message) { + JsonObject jAuthenticate = new JsonObject(); + jAuthenticate.addProperty("mode", "deny"); + jAuthenticate.addProperty("message", message); + JsonObject j = new JsonObject(); + j.add("authenticate", jAuthenticate); + return j; + } + + /** + *
+	 *	{
+	 *		timedata: {
+	 *			timestamp (Long): {
+	 *				channel: String,
+	 *				value: String | Number
+	 *			}
+	 *		}
+	 *	}
+	 * 
+ * + * @param token + * @return + */ + public static JsonObject timestampedData(Multimap> data) { + JsonObject jTimedata = new JsonObject(); + data.asMap().forEach((timestamp, fieldValues) -> { + JsonObject jTimestamp = new JsonObject(); + fieldValues.forEach(fieldValue -> { + if (fieldValue instanceof NumberFieldValue) { + jTimestamp.addProperty(fieldValue.field, ((NumberFieldValue) fieldValue).value); + } else if (fieldValue instanceof StringFieldValue) { + jTimestamp.addProperty(fieldValue.field, ((StringFieldValue) fieldValue).value); + } + }); + jTimedata.add(String.valueOf(timestamp), jTimestamp); + }); + JsonObject j = new JsonObject(); + j.add("timedata", jTimedata); + return j; + } + + /** + *
+	 *	{
+	 *		config: {
+	 *			...
+	 *		}
+	 *	}
+	 * 
+ * + * @param token + * @return + */ + public static JsonObject configQueryReply(JsonObject config) { + JsonObject j = new JsonObject(); + j.add("config", config); + return j; + } + + /** + *
+	 *	{
+	 *		id: [string],
+	 *		currentData: {[{ 
+	 *			channel: string,
+	 *			value: any
+     *		}]}
+	 *	}
+	 * 
+ * + * @return + */ + public static JsonObject currentData(JsonArray jId, JsonObject jCurrentData) { + JsonObject j = new JsonObject(); + j.add("id", jId); + j.add("currentData", jCurrentData); + return j; + } + + /** + *
+	 *	{
+	 *		id: [string]
+	 *		historicData: {
+	 *			data: [{
+	 *				time: ...,
+	 *				channels: {
+	 *					thing: {
+	 *						channel: any
+	 *					} 
+	 *				}
+	 *			}]
+	 *		}
+	 *	}
+	 * 
+ * + * @return + */ + public static JsonObject historicDataQueryReply(JsonArray jId, JsonArray jData) { + JsonObject j = new JsonObject(); + j.add("id", jId); + JsonObject jHistoricData = new JsonObject(); + jHistoricData.add("data", jData); + j.add("historicData", jHistoricData); + return j; + } + + /** + *
+	 *	{
+	 *		notification: {
+	 *			status: string,
+	 *			message: string,
+	 *			code: number,
+	 *			params: string[]
+	 *		}
+	 *	}
+	 * 
+ * + * @return + */ + public static JsonObject notification(Notification code, Object... params) { + JsonObject j = new JsonObject(); + JsonObject jNotification = new JsonObject(); + jNotification.addProperty("status", code.getStatus()); + jNotification.addProperty("message", String.format(code.getMessage(), params)); + jNotification.addProperty("code", code.getValue()); + JsonArray jParams = new JsonArray(); + for(Object param : params) { + jParams.add(param.toString()); + } + jNotification.add("params", jParams); + j.add("notification", jNotification); + return j; + } + + /** + *
+	 *	{
+	 *		currentData: {
+	 *			mode: 'subscribe',
+	 *			channels: {}
+	 *		}
+	 *	}
+	 * 
+ * + * @return + */ + public static JsonObject currentDataSubscribe(JsonArray jId, JsonObject jChannels) { + JsonObject j = new JsonObject(); + j.add("id", jId); + JsonObject jCurrentData = new JsonObject(); + jCurrentData.addProperty("mode", "subscribe"); + jCurrentData.add("channels", jChannels); + j.add("currentData", jCurrentData); + return j; + } + + /** + *
+	 *	{
+	 *		id: [string],
+	 *		log: {
+	 *			times: number,
+	 *			level: string,
+	 *			source: string,
+	 *			message: string
+	 *		}
+	 *	}
+	 * 
+ * + * @return + */ + public static JsonObject log(JsonArray jId, long timestamp, String level, String source, String message) { + JsonObject j = new JsonObject(); + j.add("id", jId); + JsonObject jLog = new JsonObject(); + jLog.addProperty("time", timestamp); + jLog.addProperty("level", level); + jLog.addProperty("source", source); + jLog.addProperty("message", message); + j.add("log", jLog); + return j; + } + + /** + *
+	 *	{
+	 *		id: [string],
+	 *		log: {
+	 *			mode: "unsubscribe"
+	 *		}
+	 *	}
+	 * 
+ * + * @return + */ + public static JsonObject logUnsubscribe(JsonArray jId) { + JsonObject j = new JsonObject(); + j.add("id", jId); + JsonObject jLog = new JsonObject(); + jLog.addProperty("mode", "unsubscribe"); + return j; + } +} diff --git a/common/src/main/java/io/openems/common/websocket/Notification.java b/common/src/main/java/io/openems/common/websocket/Notification.java new file mode 100644 index 00000000000..6d3afa27d17 --- /dev/null +++ b/common/src/main/java/io/openems/common/websocket/Notification.java @@ -0,0 +1,31 @@ +package io.openems.common.websocket; + +public enum Notification { + // TODO warning, info, error should be an enum + EDGE_CONNECTION_ClOSED(100, "warning", "Connection [%s] was interrupted"), + EDGE_CONNECTION_OPENED(101, "info", "Connection [%s] was established"), + EDGE_UNABLE_TO_FORWARD(102, "error", "Unable to forward command to [%s]: %s"), + EDGE_AUTHENTICATION_BY_TOKEN_FAILED(103, "info", "Authentication by token [%s] failed"); + + private final int value; + private final String status; + private final String message; + + private Notification(int value, String status, String message) { + this.value = value; + this.status = status; + this.message = message; + } + + public int getValue() { + return value; + } + + public String getStatus() { + return status; + } + + public String getMessage() { + return message; + } +} diff --git a/common/src/main/java/io/openems/common/websocket/WebSocketUtils.java b/common/src/main/java/io/openems/common/websocket/WebSocketUtils.java new file mode 100644 index 00000000000..3c563100d62 --- /dev/null +++ b/common/src/main/java/io/openems/common/websocket/WebSocketUtils.java @@ -0,0 +1,79 @@ +package io.openems.common.websocket; + +import java.time.Period; +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.Optional; + +import org.java_websocket.WebSocket; +import org.java_websocket.exceptions.WebsocketNotConnectedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.common.api.TimedataSource; +import io.openems.common.utils.JsonUtils; + +public class WebSocketUtils { + + private static Logger log = LoggerFactory.getLogger(WebSocketUtils.class); + + /** + * Send a message to a websocket + * + * @param j + * @return true if successful, otherwise false + */ + public static boolean send(WebSocket websocket, JsonObject j) { + // System.out.println("SEND: websocket["+websocket+"]: " + j.toString()); + try { + websocket.send(j.toString()); + return true; + } catch (WebsocketNotConnectedException e) { + return false; + } + } + + /** + * Query history command + * + * @param j + */ + public static JsonObject historicData(JsonArray jMessageId, JsonObject jHistoricData, Optional deviceId, TimedataSource timedataSource) { + try { + String mode = JsonUtils.getAsString(jHistoricData, "mode"); + if (mode.equals("query")) { + /* + * Query historic data + */ + int timezoneDiff = JsonUtils.getAsInt(jHistoricData, "timezone"); + ZoneId timezone = ZoneId.ofOffset("", ZoneOffset.ofTotalSeconds(timezoneDiff * -1)); + ZonedDateTime fromDate = JsonUtils.getAsZonedDateTime(jHistoricData, "fromDate", timezone); + ZonedDateTime toDate = JsonUtils.getAsZonedDateTime(jHistoricData, "toDate", timezone).plusDays(1); + JsonObject channels = JsonUtils.getAsJsonObject(jHistoricData, "channels"); + // JsonObject kWh = JsonUtils.getAsJsonObject(jQuery, "kWh"); + int days = Period.between(fromDate.toLocalDate(), toDate.toLocalDate()).getDays(); + // TODO: better calculation of sensible resolution + int resolution = 5 * 60; // 5 Minutes + if (days > 25) { + resolution = 24 * 60 * 60; // 1 Day + } else if (days > 6) { + resolution = 3 * 60 * 60; // 3 Hours + } else if (days > 2) { + resolution = 60 * 60; // 60 Minutes + } + JsonArray jData = timedataSource.queryHistoricData(deviceId, fromDate, toDate, channels, + resolution); + // send reply + return DefaultMessages.historicDataQueryReply(jMessageId, jData); + } + } catch (Exception e) { + log.error("HistoricData Error: ", e); + e.printStackTrace(); + } + return new JsonObject(); + } +} diff --git a/backend/src/test/java/io/openems/femsserver/AppTest.java b/common/src/test/java/io/openems/common/AppTest.java similarity index 89% rename from backend/src/test/java/io/openems/femsserver/AppTest.java rename to common/src/test/java/io/openems/common/AppTest.java index 28450fa1c80..2fc153cafaa 100644 --- a/backend/src/test/java/io/openems/femsserver/AppTest.java +++ b/common/src/test/java/io/openems/common/AppTest.java @@ -1,4 +1,4 @@ -package io.openems.femsserver; +package io.openems.common; import junit.framework.Test; import junit.framework.TestCase; diff --git a/doc/readme.md b/doc/readme.md index 9f021052309..99785d03085 100644 --- a/doc/readme.md +++ b/doc/readme.md @@ -2,208 +2,292 @@ This chapter explains the communication protocol used between the different components. -## [1] Client (Browser) <-> Backend (OpenEMS/FemsServer) +## [1] OpenEMS UI <-> OpenEMS Edge/OpenEMS Backend ### [1.1] Authenticate -#### [1.1.1] At FemsServer +[1.1.1] Authenticate Client -> OpenEMS Backend -[1.1.1.1] Authenticate -Cookie: session_id +[1.1.1.1] Automatic -[1.1.1.2] On success -``` -{ result: { devices: ["...",] } } -``` +Using cookie information (session_id + token) in handshake + +[1.1.1.1] Manual login + +currently forwarded to Odoo login page + +[1.1.2] Authenticate Client -> OpenEMS Edge + +[1.1.2.1] Automatic -[1.1.1.3] On error +// TODO + +[1.1.2.2] Manual login ``` -{ error } +{ + authenticate: { + mode: "login", + username?: string, + password: string + } +} ``` -[1.1.1.4] Reply +[1.1.3] Authentication reply + +[1.1.3.1] Authentication successful + ``` { authenticate: { - mode: allow, username, token + mode: "allow", + token: string, + role?: "admin" | "installer" | "owner" | "guest" }, metadata: { - devices: [{ - name, config, online - }], - backend: "femsserver" + user: { + id: Integer + }, + devices?: [{ + name: string, + comment: string, + producttype: "Pro 9-12" | "MiniES 3-3" | "PRO Hybrid 9-10" | "PRO Compact 3-10" | "COMMERCIAL 40-45" | "INDUSTRIAL", + role: "admin" | "installer" | "owner" | "guest", + online: boolean + }] } } ``` -#### [1.1.2] At OpenEMS +- authenticate.role is only sent for OpenEMS Edge +- metadata.devices is only sent for OpenEMS Backend -[1.1.2.1] Authenticate -``` +[1.1.3.2] Authentication failed { authenticate: { - mode: login, - [password: "...",] - [token: "..."] + mode: "deny" } } + +## [2] OpenEMS UI <-> OpenEMS Backend <-> OpenEMS Edge + +Following commands are all the same, no matter if UI is connected to Edge or to Backend. + +Backend is transparently proxying requests to a connected Edge if necessary, adding the UI token as identifier to the message id: + +``` +{ + id: [..., token], + ... +} ``` -[1.1.2.2] Reply +### [2.1] Receive current configuration + +[2.1.1] UI -> Edge/Backend + ``` -successful { - authenticate: { - mode: allow, username, token - }, metadata: { - config: {}, - backend: "openems" + device: string, + id: [UUID], + config: { + mode: "query", + language: 'de' | 'en' | ... } } -failed +``` + +[2.1.2] Edge/Backend -> UI + +``` { - authenticate: { - mode: deny + id: [UUID], + config: { + things: { + [id: string]: { + id: string, + class: string | string[], + [channel: string]: any + } + }, + meta: { + [clazz: string]: { + implements: [string], + channels: { + [channel: string]: { + name: string, + title: string, + type: string | string[], + optional: boolean, + array: boolean, + accessLevel: string + } + } + } + } } } ``` -### [1.2] Current data +### [2.2] Current live data + +[2.2.1] UI -> Edge/Backend + +For 'unsubscribe' the channels object is empty. -[1.2.1] Subscribe ``` { - device: "...", - subscribe: { + id: [string], + device: string, + currentData: { + mode: "subscribe", channels: { - thing0: [ - channel - ] - }, - log: "all" | "info" | "warning" | "error" + [thingId: string]: string[] + } } } ``` -[1.2.2] Forward to OpenEMS +[2.2.2] Edge/Backend -> UI + ``` { - subscribe: ... + device?: string, + currentData: {[{ + channel: string, + value: any + }]} } ``` -[1.2.3] Reply from OpenEMS +### [2.3] Query historic data + +[2.3.1] UI -> Edge/Backend ``` { - currentdata: [{ - channel, value - }] + id: [string], + device: string, + historicData: { + mode: "query", + fromDate: date, + toDate: date, + timezone: number /* offset in seconds */, + channels: { + thing: [ + channel: string + ] + } + // kwhChannels: { + // address: 'grid' | 'production' | 'storage', + // } + } } ``` -[1.2.4] Reply +[2.3.2] Edge/Backend -> UI ``` { - device: "...", - currentdata: [{ - channel, value - }] + id: [UUID], + historicData: { + data: [{ + time: string, + channels: { + thing: { + 'channel': 'value' + } + } + }] + // kWh: { + // 'meter0/ActivePower': { + // 'sell': ..., + // 'buy': ..., + // 'type': 'grid' | 'production' | 'storage' + // }, + // 'meter1/ActivePower': { + // 'value': value, + // 'type': 'grid' | 'production' | 'storage' + // }, + // 'ess0/ActivePower': { + // 'charge: ..., + // 'discharge': ..., + // 'type': 'grid' | 'production' | 'storage' + // } + //} + } } ``` -[1.2.5] Unsubscribe +### [2.4] Notification + ``` { - device: "...", - subscribe: { - channels: {}, - log: "" + notification: { + type: "success" | "error" | "warning" | "info", + message: "...", + code?: number, + params?: string[] } } ``` -### [1.3] Notification +### [2.5] Current system log + +[2.2.1] UI -> Edge/Backend + ``` { - device: "...", - notification: { - message: "... + id: [string], + device: string, + log: { + mode: "subscribe" } } ``` -### [1.3] Log ``` { - device: "...", + id: [string], + device: string, log: { - timestamp: ..., - level: ..., - source: ..., - message: ... + mode: "unsubscribe" } } ``` -### [1.4] Query history data +[2.2.2] Edge/Backend -> UI -[1.4.1] ``` { - device: "...", - query: { - mode: "history", - fromDate: "01.01.2017", - toDate: "01.01.2017", - timezone: /* offset in seconds */, - data: { - channels: { - thing: [channel] - } - }, - kWh: { - "thing/channel": 'grid' | 'production' | 'storage', - } - } + log: { + timestamp: number, + level: string, + source: string, + message: string + } } ``` -[1.4.2] +## [3] OpenEMS Edge <-> OpenEMS Backend + +### [3.1] Timestamped data ``` { - queryreply: { - mode: "history", - fromDate: "2017-01-01", - toDate: "2017-01-01", - timezone: /* offset in seconds */, - data: [{ - time: ..., - channels: { - 'thing': { - 'channel': 'value' - } - } - }], - kWh: { - 'meter0/ActivePower': { - 'sell': ..., - 'buy': ..., - 'type': 'grid' | 'production' | 'storage' - }, - 'meter1/ActivePower': { - 'value': value, - 'type': 'grid' | 'production' | 'storage' - }, - 'ess0/ActivePower': { - 'charge: ..., - 'discharge': ..., - 'type': 'grid' | 'production' | 'storage' - } + timedata: { + timestamp (Long): { + channel: String, + value: String | Number } } } ``` + + + + +// TODO from here + + ### [1.5] Configuration ### [1.5.1] Update @@ -267,29 +351,6 @@ failed } ``` -## [2] OpenEMS <-> FemsServer - -### [2.1] Authenticate -``` -{ - metadata: { - config: {}, - backend: "openems" - } -} -``` - -### [2.2] timestamped data -``` -{ - timedata: { - timestamp: [{ - channel, value - }] - } -} -``` - # Generate the docs ## Charts diff --git a/doc/rest-api.md b/doc/rest-api.md new file mode 100644 index 00000000000..705d70fe2ea --- /dev/null +++ b/doc/rest-api.md @@ -0,0 +1,67 @@ +# OpenEMS REST-Api + +## Setup REST-Api-Controller + +OpenEMS Rest-Api is implemented as a Controller. To be active it needs to be activated in the Scheduler. Default port is 8084. + +Example configuration: +``` +{ + "class": "io.openems.impl.controller.api.rest.RestApiController", + "priority": 150, + ["port": 8084] +} +``` + + +## Channel + +Every "Channel" in OpenEMS can be accessed via REST-Api. + +To connect to the OpenEMS REST-Api you need a URL with following pattern: + +`http://x:@:8084/rest/channel//` + +* To read values you need the to use GET. +* For write commands you have to use POST. Write commands are only possible on ConfigChannels. +* ThingId: The id of the Controller/Device which contains the Channel you want to read/write. +* ChannelName: the channel address to read/write. + +### Read Channel value +Example: read State-of-charge (of first connected battery system/inverter named "ess0"): +``` +http://x:@:8084/rest/channel/ess0/Soc +``` + +Result: +``` +{ + "value": 55, + "type": "ModbusReadLongChannel", + "writeable": false +} +``` + +### Write Channel value +Example: set active-power of ess0 +1. You need to activate the FixValueController: +``` + { + "id": "FixValue", + "class": "io.openems.impl.controller.asymmetric.fixvalue.FixValueActivePowerController", + "esss": [ + "ess0" + ], + "priority": 150, + "activePowerL1": 0, + "activePowerL2": 0, + "activePowerL3": 0 + } +``` +2. Set the "activePowerL1" ConfigChannel to 1000W(Discharge) +``` +http://x:@:8084/rest/channel/FixValue/activePowerL1 +Content-Type: application/json +Method: POST +Body: {"value":1000} +``` diff --git a/edge/.classpath b/edge/.classpath index 7cc2b7d7c69..f2f6a05c0ac 100644 --- a/edge/.classpath +++ b/edge/.classpath @@ -18,5 +18,6 @@ + diff --git a/edge/resources/logback.xml b/edge/resources/logback.xml index c08f6e5a96a..72ee2a839ed 100644 --- a/edge/resources/logback.xml +++ b/edge/resources/logback.xml @@ -1,34 +1,42 @@ - + - [%-8.8thread] [%-5level] [%-20.20logger{36}:%-3line] %msg%ex{10}%n + [%-8.8thread] [%-5level] [%-20.20logger{36}:%-3line]%msg%ex{10}%n - + + + + + /var/log/openems/openems.%d{yyyy-MM-dd}.log + + + 7 + + + + [%-8.8thread] [%-5level] [%-20.20logger{36}:%-3line]%msg%ex{10}%n + + + + + diff --git a/edge/src/io/openems/App.java b/edge/src/io/openems/App.java index baa0d1f2945..a031bb45355 100644 --- a/edge/src/io/openems/App.java +++ b/edge/src/io/openems/App.java @@ -42,11 +42,14 @@ public static void main(String[] args) { Config config = Config.getInstance(); config.readConfigFile(); } catch (Exception e) { - log.error("OpenEMS start failed: " + e.getMessage()); + log.error("OpenEMS Edge start failed: " + e.getMessage()); e.printStackTrace(); System.exit(1); } - log.info("OpenEMS config loaded"); + + log.info("OpenEMS Edge started"); + log.info("================================================================================"); + // kick the watchdog: READY SDNotify.sendNotify(); } diff --git a/edge/src/io/openems/api/bridge/Bridge.java b/edge/src/io/openems/api/bridge/Bridge.java index 496fb31b01a..3c85f49af08 100644 --- a/edge/src/io/openems/api/bridge/Bridge.java +++ b/edge/src/io/openems/api/bridge/Bridge.java @@ -1,75 +1,333 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.api.bridge; - -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; - -import io.openems.api.device.Device; -import io.openems.api.thing.Thing; -import io.openems.core.utilities.AbstractWorker; - -public abstract class Bridge extends AbstractWorker implements Thing { - public final static String THINGID_PREFIX = "_bridge"; - private static int instanceCounter = 0; - protected final List devices = Collections.synchronizedList(new LinkedList()); - - /** - * Initialize the Thread with a name - * - * @param name - */ - public Bridge() { - super(THINGID_PREFIX + instanceCounter++); - } - - @Override - public String id() { - return getName(); - } - - public synchronized void addDevice(Device device) { - this.devices.add(device); - } - - public synchronized final void addDevices(Device... devices) { - for (Device device : devices) { - addDevice(device); - } - } - - public synchronized final void addDevices(List devices) { - for (Device device : devices) { - addDevice(device); - } - } - - public synchronized void removeDevice(Device device) { - this.devices.remove(device); - } - - public synchronized List getDevices() { - return Collections.unmodifiableList(this.devices); - } - - public abstract void triggerWrite(); -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.api.bridge; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.channel.DebugChannel; +import io.openems.api.device.Device; +import io.openems.api.scheduler.Scheduler; +import io.openems.api.thing.Thing; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.Mutex; + +public abstract class Bridge extends Thread implements Thing { + public final static String THINGID_PREFIX = "_bridge"; + private static int instanceCounter = 0; + private Scheduler scheduler; + private int readOtherTaskIndex = 0; + private int readOtherTaskCount = 0; + private AtomicBoolean isWriteTriggered = new AtomicBoolean(false); + private final AtomicBoolean initialize = new AtomicBoolean(true); + private final AtomicBoolean isStopped = new AtomicBoolean(false); + private long cycleStart = 0; + private Mutex initializedMutex = new Mutex(false); + private final AtomicBoolean isInitialized = new AtomicBoolean(false); + protected final List devices = Collections.synchronizedList(new LinkedList()); + protected final Logger log; + private DebugChannel requiredCycleTime = new DebugChannel<>("RequiredCycleTime", this); + + /** + * Initialize the Thread with a name + * + * @param name + */ + public Bridge() { + log = LoggerFactory.getLogger(this.getClass()); + setName(THINGID_PREFIX + instanceCounter++); + } + + @Override + public String id() { + return getName(); + } + + protected List getRequiredReadTasks() { + List readTasks = new ArrayList<>(); + for (Device d : devices) { + readTasks.addAll(d.getRequiredReadTasks()); + } + return readTasks; + } + + protected List getReadTasks() { + List readTasks = new ArrayList<>(); + for (Device d : devices) { + readTasks.addAll(d.getReadTasks()); + } + return readTasks; + } + + protected List getWriteTasks() { + List writeTasks = new ArrayList<>(); + for (Device d : devices) { + writeTasks.addAll(d.getWriteTasks()); + } + return writeTasks; + } + + public synchronized void addDevice(Device device) { + this.devices.add(device); + } + + public synchronized final void addDevices(Device... devices) { + for (Device device : devices) { + addDevice(device); + } + } + + public synchronized final void addDevices(List devices) { + for (Device device : devices) { + addDevice(device); + } + } + + public synchronized void removeDevice(Device device) { + this.devices.remove(device); + } + + public synchronized List getDevices() { + return Collections.unmodifiableList(this.devices); + } + + public void triggerWrite() { + // set the Write-flag + isWriteTriggered.set(true); + } + + /** + * This method is called when the Thread stops. Use it to close resources. + */ + protected abstract void dispose(); + + /** + * This method is called once before {@link forever()} and every time after {@link restart()} method was called. Use + * it to (re)initialize everything. + * + * @return false on initialization error + */ + protected abstract boolean initialize(); + + /** + * Little helper method: Sleep and don't let yourself interrupt by a ForceRun-Flag. It is not making sense anyway, + * because something is wrong with the setup if we landed here. + * + * @param duration + * in seconds + */ + private long bridgeExceptionSleep(long duration) { + if (duration < 60) { + duration += 1; + } + long targetTime = System.nanoTime() + (duration * 1000000); + do { + try { + long thisDuration = (targetTime - System.nanoTime()) / 1000000; + if (thisDuration > 0) { + Thread.sleep(thisDuration); + } + } catch (InterruptedException e1) {} + } while (targetTime > System.nanoTime()); + return duration; + } + + /** + * Executes the Thread. Calls {@link forever} till the Thread gets interrupted. + */ + @Override + public final void run() { + long bridgeExceptionSleep = 1; // seconds + this.initialize.set(true); + while (!isStopped.get()) { + cycleStart = System.currentTimeMillis(); + try { + /* + * Initialize Bridge + */ + while (initialize.get()) { + // get Scheduler + this.scheduler = ThingRepository.getInstance().getSchedulers().iterator().next(); + boolean initSuccessful = initialize(); + if (initSuccessful) { + isInitialized.set(true); + initializedMutex.release(); + initialize.set(false); + } else { + initializedMutex.awaitOrTimeout(10000, TimeUnit.MILLISECONDS); + } + } + + this.readOtherTaskCount = 0; + List readTasks = this.getReadTasks(); + List requiredReadTasks = this.getRequiredReadTasks(); + List writeTasks = this.getWriteTasks(); + // calculate startTime to run the read + long sleep = getNextReadTime() - System.currentTimeMillis() - 10; + if (sleep > 0) { + try { + Thread.sleep(sleep); + } catch (InterruptedException e) { + log.error("sleep failed.", e); + } + } else { + log.warn("cycleTime smaller than required time: " + sleep); + } + // run all tasks to read required Channels + for (BridgeReadTask task : requiredReadTasks) { + try { + task.runTask(); + } catch (Exception e) { + log.error("failed to execute ReadTask.", e); + } + } + long timeUntilWrite = scheduler.getCycleStartTime() + scheduler.getRequiredTime() + 10; + if (readTasks.size() > 0) { + // calculate time until write + // run tasks for not required channels + if (timeUntilWrite - System.currentTimeMillis() > 0) { + readOther(readTasks, timeUntilWrite); + } + } + // execute write Tasks + boolean written = false; + while (!written) { + if (isWriteTriggered.get()) { + for (BridgeWriteTask task : writeTasks) { + try { + task.runTask(); + } catch (Exception e) { + log.error("failed to execute WriteTask.", e); + } + } + isWriteTriggered.set(false); + written = true; + } else { + try { + Thread.sleep(20l); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + // execute additional readTasks if time left + if (readTasks.size() > 0) { + if (getNextReadTime() - 10 - System.currentTimeMillis() > 0) { + readOther(readTasks, getNextReadTime() - 10); + } + } + // Everything went ok: reset bridgeExceptionSleep + bridgeExceptionSleep = 1; + } catch (Throwable e) { + /* + * Handle Bridge-Exceptions + */ + log.error("Bridge-Exception! Retry later: ", e); + bridgeExceptionSleep = bridgeExceptionSleep(bridgeExceptionSleep); + } + requiredCycleTime.setValue(System.currentTimeMillis() - cycleStart); + } + dispose(); + System.out.println("BridgeWorker was interrupted. Exiting gracefully..."); + } + + private void readOther(List tasks, long timeFinished) { + BridgeReadTask nextReadTask = null; + if (readOtherTaskCount + 1 < tasks.size()) { + nextReadTask = tasks.get(readOtherTaskIndex); + } else { + nextReadTask = null; + } + while (nextReadTask != null) { + if (System.currentTimeMillis() + nextReadTask.getRequiredTime() >= timeFinished) { + break; + } + try { + nextReadTask.runTask(); + } catch (Exception e) { + log.error("failed to execute ReadTask.", e); + } + readOtherTaskCount++; + readOtherTaskIndex++; + readOtherTaskIndex %= tasks.size(); + if (readOtherTaskCount + 1 < tasks.size()) { + nextReadTask = tasks.get(readOtherTaskIndex); + } else { + nextReadTask = null; + } + } + } + + private long getWriteTime() { + long time = 0l; + for (BridgeWriteTask task : getWriteTasks()) { + time += task.getRequiredTime(); + } + return time; + } + + private long getNextReadTime() { + long readRequiredStartTime = scheduler.getNextCycleStart() - 10; + for (BridgeReadTask task : getRequiredReadTasks()) { + readRequiredStartTime -= task.getRequiredTime(); + } + return readRequiredStartTime; + } + + public long getRequiredCycleTime() { + long time = 50; + for (BridgeReadTask task : getRequiredReadTasks()) { + time += task.getRequiredTime(); + } + time += scheduler.getRequiredTime(); + for (BridgeWriteTask task : getWriteTasks()) { + time += task.getRequiredTime(); + } + long maxReadOtherTime = 0; + for (BridgeReadTask task : getReadTasks()) { + if (task.getRequiredTime() > maxReadOtherTime) { + maxReadOtherTime = task.getRequiredTime(); + } + } + time += maxReadOtherTime * 2; + return time; + } + + public void shutdown() { + isStopped.set(true); + } + + /** + * Triggers a call of {@link initialization()} in the next loop. Call this, if the configuration changes. + */ + public final void triggerInitialize() { + initialize.set(true); + initializedMutex.release(); + } +} diff --git a/edge/src/io/openems/api/bridge/BridgeReadTask.java b/edge/src/io/openems/api/bridge/BridgeReadTask.java new file mode 100644 index 00000000000..2c667951cd7 --- /dev/null +++ b/edge/src/io/openems/api/bridge/BridgeReadTask.java @@ -0,0 +1,5 @@ +package io.openems.api.bridge; + +public abstract class BridgeReadTask extends BridgeTask { + +} diff --git a/edge/src/io/openems/api/bridge/BridgeTask.java b/edge/src/io/openems/api/bridge/BridgeTask.java new file mode 100644 index 00000000000..2a27deafaaa --- /dev/null +++ b/edge/src/io/openems/api/bridge/BridgeTask.java @@ -0,0 +1,39 @@ +package io.openems.api.bridge; + +import java.util.Queue; + +import com.google.common.collect.EvictingQueue; +import com.google.common.collect.Queues; + +public abstract class BridgeTask { + + private Queue requiredTimes; + + public BridgeTask() { + requiredTimes = EvictingQueue.create(5); + for (int i = 0; i < 5; i++) { + requiredTimes.add(0L); + } + } + + public long getRequiredTime() { + synchronized (requiredTimes) { + long sum = 0; + for (Long l : requiredTimes) { + sum += l; + } + return sum / requiredTimes.size(); + } + } + + public void runTask() throws Exception { + long timeBefore = System.currentTimeMillis(); + this.run(); + synchronized (requiredTimes) { + this.requiredTimes.add(System.currentTimeMillis() - timeBefore); + } + } + + protected abstract void run() throws Exception; + +} diff --git a/edge/src/io/openems/api/bridge/BridgeWriteTask.java b/edge/src/io/openems/api/bridge/BridgeWriteTask.java new file mode 100644 index 00000000000..3c5360dea69 --- /dev/null +++ b/edge/src/io/openems/api/bridge/BridgeWriteTask.java @@ -0,0 +1,5 @@ +package io.openems.api.bridge; + +public abstract class BridgeWriteTask extends BridgeTask { + +} diff --git a/edge/src/io/openems/api/channel/ConfigChannel.java b/edge/src/io/openems/api/channel/ConfigChannel.java index 31b41875618..329645069c1 100644 --- a/edge/src/io/openems/api/channel/ConfigChannel.java +++ b/edge/src/io/openems/api/channel/ConfigChannel.java @@ -125,4 +125,9 @@ public ConfigChannel label(T value, String label) { super.label(value, label); return this; } + + @Override + public ConfigChannel doNotPersist() { + return (ConfigChannel) super.doNotPersist(); + } } diff --git a/edge/src/io/openems/api/channel/DebugChannel.java b/edge/src/io/openems/api/channel/DebugChannel.java new file mode 100644 index 00000000000..e58beb2f757 --- /dev/null +++ b/edge/src/io/openems/api/channel/DebugChannel.java @@ -0,0 +1,43 @@ +package io.openems.api.channel; + +import java.util.ArrayList; +import java.util.List; + +import io.openems.api.thing.Thing; +import io.openems.core.Databus; + +public class DebugChannel extends ReadChannel { + private static List> debugChannels = new ArrayList<>(); + private static boolean DEBUGENABLED = false; + private static Databus bus = Databus.getInstance(); + + public static void enableDebug() { + DEBUGENABLED = true; + for (DebugChannel channel : debugChannels) { + channel.addChangeListener(bus); + channel.addUpdateListener(bus); + } + } + + public static void disableDebug() { + DEBUGENABLED = false; + for (DebugChannel channel : debugChannels) { + channel.removeChangeListener(bus); + channel.removeUpdateListener(bus); + } + } + + public DebugChannel(String id, Thing parent) { + super(id, parent); + if (DEBUGENABLED) { + this.addChangeListener(bus); + this.addUpdateListener(bus); + } + debugChannels.add(this); + } + + public void setValue(T value) { + this.updateValue(value); + } + +} diff --git a/edge/src/io/openems/api/channel/ReadChannel.java b/edge/src/io/openems/api/channel/ReadChannel.java index 4e88f6264a2..910bd4dc269 100644 --- a/edge/src/io/openems/api/channel/ReadChannel.java +++ b/edge/src/io/openems/api/channel/ReadChannel.java @@ -55,6 +55,7 @@ public class ReadChannel implements Channel, Comparable> { private String unit = ""; private boolean isRequired = false; protected final Set users = new HashSet<>(); + protected boolean doNotPersist = false; private final Set updateListeners = ConcurrentHashMap.newKeySet(); private final Set changeListeners = ConcurrentHashMap.newKeySet(); @@ -132,6 +133,11 @@ public ReadChannel user(User... users) { return this; } + public ReadChannel doNotPersist() { + this.doNotPersist = true; + return this; + } + /* * Getter */ @@ -303,6 +309,10 @@ public boolean isRequired() { return this.isRequired; } + public boolean isDoNotPersist() { + return doNotPersist; + } + @Override public JsonObject toJsonObject() throws NotImplementedException { JsonObject j = new JsonObject(); diff --git a/edge/src/io/openems/api/device/Device.java b/edge/src/io/openems/api/device/Device.java index 7d7061b16c5..5202cf3b045 100644 --- a/edge/src/io/openems/api/device/Device.java +++ b/edge/src/io/openems/api/device/Device.java @@ -1,55 +1,101 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.api.device; - -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.bridge.Bridge; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.exception.OpenemsException; -import io.openems.api.thing.Thing; - -public abstract class Device implements Thing { - public final static String THINGID_PREFIX = "_device"; - private static int instanceCounter = 0; - protected final Logger log; - private Bridge bridge = null; - private final String thingId; - - public Device() throws OpenemsException { - this.thingId = THINGID_PREFIX + instanceCounter++; - log = LoggerFactory.getLogger(this.getClass()); - } - - public Bridge getBridge() { - return bridge; - } - - @Override - public String id() { - return this.thingId; - } - - protected abstract Set getDeviceNatures(); -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.api.device; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.bridge.Bridge; +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.exception.OpenemsException; +import io.openems.api.thing.Thing; + +public abstract class Device implements Thing { + public final static String THINGID_PREFIX = "_device"; + private static int instanceCounter = 0; + protected final Logger log; + private Bridge bridge = null; + private final String thingId; + + public Device(Bridge parent) throws OpenemsException { + this.thingId = THINGID_PREFIX + instanceCounter++; + log = LoggerFactory.getLogger(this.getClass()); + this.bridge = parent; + } + + public Bridge getBridge() { + return bridge; + } + + @Override + public String id() { + return this.thingId; + } + + @Override + public void init() { + Thing.super.init(); + for (DeviceNature nature : getDeviceNatures()) { + nature.init(); + } + } + + protected abstract Set getDeviceNatures(); + + public List getRequiredReadTasks() { + List readTasks = new ArrayList<>(); + for (DeviceNature nature : getDeviceNatures()) { + List natureRequiredReadTasks = nature.getRequiredReadTasks(); + if (natureRequiredReadTasks != null) { + readTasks.addAll(natureRequiredReadTasks); + } + } + return readTasks; + } + + public List getReadTasks() { + List readTasks = new ArrayList<>(); + for (DeviceNature nature : getDeviceNatures()) { + List natureReadTasks = nature.getReadTasks(); + if (natureReadTasks != null) { + readTasks.addAll(natureReadTasks); + } + } + return readTasks; + } + + public List getWriteTasks() { + List writeTasks = new ArrayList<>(); + for (DeviceNature nature : getDeviceNatures()) { + List natureWriteTasks = nature.getWriteTasks(); + if (natureWriteTasks != null) { + writeTasks.addAll(natureWriteTasks); + } + } + return writeTasks; + } +} diff --git a/edge/src/io/openems/api/device/nature/DeviceNature.java b/edge/src/io/openems/api/device/nature/DeviceNature.java index f9f1250ca11..c2f90211a8b 100644 --- a/edge/src/io/openems/api/device/nature/DeviceNature.java +++ b/edge/src/io/openems/api/device/nature/DeviceNature.java @@ -1,28 +1,42 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.api.device.nature; - -import io.openems.api.channel.Channel; -import io.openems.api.thing.Thing; - -public interface DeviceNature extends Thing { - public void setAsRequired(Channel channel); -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.api.device.nature; + +import java.util.List; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.device.Device; +import io.openems.api.thing.Thing; + +public interface DeviceNature extends Thing { + + public Device getParent(); + + public void setAsRequired(Channel channel); + + public List getRequiredReadTasks(); + + public List getReadTasks(); + + public List getWriteTasks(); +} diff --git a/edge/src/io/openems/api/doc/ConfigChannelDoc.java b/edge/src/io/openems/api/doc/ConfigChannelDoc.java index b140ce76956..4550c67cb47 100644 --- a/edge/src/io/openems/api/doc/ConfigChannelDoc.java +++ b/edge/src/io/openems/api/doc/ConfigChannelDoc.java @@ -22,7 +22,10 @@ import com.google.gson.JsonObject; +import io.openems.api.controller.IsThingMap; +import io.openems.api.controller.ThingMap; import io.openems.api.security.User; +import io.openems.core.utilities.InjectionUtils; public class ConfigChannelDoc { private final String name; @@ -50,7 +53,14 @@ public JsonObject getAsJsonObject() { JsonObject j = new JsonObject(); j.addProperty("name", name); j.addProperty("title", title); - j.addProperty("type", type.getSimpleName()); + if (ThingMap.class.isAssignableFrom(type)) { + // for ThingMap type: get the types from annotation and return JsonArray + IsThingMap isThingMapAnnotation = type.getAnnotation(IsThingMap.class); + j.add("type", InjectionUtils.getImplementsAsJson(isThingMapAnnotation.type())); + } else { + // for simple types, use only simple name (e.g. 'Long', 'Integer',...) + j.addProperty("type", type.getSimpleName()); + } j.addProperty("optional", optional); j.addProperty("array", array); j.addProperty("accessLevel", accessLevel.name().toLowerCase()); diff --git a/edge/src/io/openems/api/doc/ThingDoc.java b/edge/src/io/openems/api/doc/ThingDoc.java index 5213c86de19..0e311df9a38 100644 --- a/edge/src/io/openems/api/doc/ThingDoc.java +++ b/edge/src/io/openems/api/doc/ThingDoc.java @@ -26,6 +26,7 @@ import com.google.gson.JsonObject; import io.openems.api.thing.Thing; +import io.openems.core.utilities.InjectionUtils; public class ThingDoc { @@ -69,6 +70,7 @@ public JsonObject getAsJsonObject() { jChannels.add(config.getName(), config.getAsJsonObject()); } j.add("channels", jChannels); + j.add("implements", InjectionUtils.getImplementsAsJson(getClazz())); return j; } } diff --git a/edge/src/io/openems/api/exception/OpenemsException.java b/edge/src/io/openems/api/exception/OpenemsException.java index c1e2f779cc8..bcd3eb435f5 100644 --- a/edge/src/io/openems/api/exception/OpenemsException.java +++ b/edge/src/io/openems/api/exception/OpenemsException.java @@ -20,7 +20,7 @@ *******************************************************************************/ package io.openems.api.exception; -public class OpenemsException extends Exception { +public class OpenemsException extends io.openems.common.exceptions.OpenemsException { private static final long serialVersionUID = 2244921342821407476L; diff --git a/edge/src/io/openems/api/persistence/QueryablePersistence.java b/edge/src/io/openems/api/persistence/QueryablePersistence.java index 4eff80ff0d3..a40fc44ef33 100644 --- a/edge/src/io/openems/api/persistence/QueryablePersistence.java +++ b/edge/src/io/openems/api/persistence/QueryablePersistence.java @@ -20,38 +20,8 @@ *******************************************************************************/ package io.openems.api.persistence; -import java.time.ZonedDateTime; +import io.openems.common.api.TimedataSource; -import com.google.gson.JsonObject; +public abstract class QueryablePersistence extends Persistence implements TimedataSource { -import io.openems.api.exception.OpenemsException; - -public abstract class QueryablePersistence extends Persistence { - - /** - * - * - * @param fromDate - * @param toDate - * @param channels - * @param resolution - * in seconds - * @return - * @throws OpenemsException - * - *
-	 * Returns:
-	 * [{
-	 *   timestamp: "2017-03-21T08:55:20Z",
-	 *   channels: {
-	 *     'thing': {
-	 *       'channel': 'value'
-	 *     }
-	 *   }
-	 * }]
-	}
-	 *             
- */ - public abstract JsonObject query(ZonedDateTime fromDate, ZonedDateTime toDate, JsonObject channels, int resolution) - throws OpenemsException; } diff --git a/edge/src/io/openems/api/scheduler/Scheduler.java b/edge/src/io/openems/api/scheduler/Scheduler.java index 1dfeac574cc..dd0ce50841e 100644 --- a/edge/src/io/openems/api/scheduler/Scheduler.java +++ b/edge/src/io/openems/api/scheduler/Scheduler.java @@ -1,55 +1,114 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.api.scheduler; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import io.openems.api.controller.Controller; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.ReflectionException; -import io.openems.api.thing.Thing; -import io.openems.core.utilities.AbstractWorker; - -public abstract class Scheduler extends AbstractWorker implements Thing { - public final static String THINGID_PREFIX = "_scheduler"; - private static int instanceCounter = 0; - protected final Map controllers = new ConcurrentHashMap<>(); - - public Scheduler() { - super(THINGID_PREFIX + instanceCounter++); - } - - public synchronized void addController(Controller controller) throws ReflectionException, ConfigException { - controllers.put(controller.id(), controller); - } - - public synchronized void removeController(Controller controller) { - controllers.remove(controller.id()); - } - - public synchronized List getControllers() { - return Collections.unmodifiableList(new ArrayList<>(this.controllers.values())); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.api.scheduler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.Controller; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.ReflectionException; +import io.openems.api.thing.Thing; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.AbstractWorker; + +public abstract class Scheduler extends AbstractWorker implements Thing { + private final static int DEFAULT_CYCLETIME = 1000; + public final static String THINGID_PREFIX = "_scheduler"; + private static int instanceCounter = 0; + protected final Map controllers = new ConcurrentHashMap<>(); + private long requiredTime = 0; + private long cycleStartTime = 0; + protected final ThingRepository thingRepository; + private Integer actualCycleTime = null; + + /* + * Config + */ + @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class, isOptional = true) + public ConfigChannel cycleTime = new ConfigChannel("cycleTime", this) + .defaultValue(DEFAULT_CYCLETIME); + + @Override + protected int getCycleTime() { + int time = cycleTime.valueOptional().orElse(DEFAULT_CYCLETIME); + if (actualCycleTime != null) { + time = actualCycleTime; + } + return time; + } + + public Scheduler() { + super(THINGID_PREFIX + instanceCounter++); + thingRepository = ThingRepository.getInstance(); + } + + public synchronized void addController(Controller controller) throws ReflectionException, ConfigException { + controllers.put(controller.id(), controller); + } + + public synchronized void removeController(Controller controller) { + controllers.remove(controller.id()); + } + + public synchronized List getControllers() { + return Collections.unmodifiableList(new ArrayList<>(this.controllers.values())); + } + + @Override + protected void forever() { + cycleStartTime = System.currentTimeMillis(); + execute(); + requiredTime = System.currentTimeMillis() - cycleStartTime; + long maxTime = 0; + for (Bridge bridge : thingRepository.getBridges()) { + if (bridge.getRequiredCycleTime() > maxTime) { + maxTime = bridge.getRequiredCycleTime(); + } + } + maxTime = (maxTime + 100) / 100 * 100; + if (maxTime > cycleTime.valueOptional().orElse(500)) { + actualCycleTime = (int) maxTime; + } else { + actualCycleTime = cycleTime.valueOptional().orElse(500); + } + } + + protected abstract void execute(); + + public long getRequiredTime() { + return requiredTime; + } + + public long getNextCycleStart() { + return cycleStartTime + getCycleTime(); + } + + public long getCycleStartTime() { + return cycleStartTime; + } +} diff --git a/edge/src/io/openems/api/security/Authentication.java b/edge/src/io/openems/api/security/Authentication.java deleted file mode 100644 index 6fcc7334866..00000000000 --- a/edge/src/io/openems/api/security/Authentication.java +++ /dev/null @@ -1,93 +0,0 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.api.security; - -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class Authentication { - private final static Logger log = LoggerFactory.getLogger(Authentication.class); - - /* - * Singleton - */ - private static Authentication instance; - - public static synchronized Authentication getInstance() { - if (Authentication.instance == null) { - Authentication.instance = new Authentication(); - } - return Authentication.instance; - } - - /* - * This class - */ - private final ConcurrentHashMap sessions = new ConcurrentHashMap<>(); - - private Authentication() {} - - public Session byUserPassword(String username, String password) { - User user = User.authenticate(username, password); - Session session = addSession(user); - if (session != null) { - log.info("User[" + session.getUser().getName() + "] authenticated. " + // - "Created session[" + session.getToken() + "]."); - } else { - log.info("Authentication by username[" + username + "] + password failed."); - } - return session; - } - - public Session byPassword(String password) { - User user = User.authenticate(password); - Session session = addSession(user); - if (session != null) { - log.info("User[" + session.getUser().getName() + "] authenticated. " + // - "Created session[" + session.getToken() + "]."); - } else { - log.info("Authentication by password failed."); - } - return session; - } - - public Session bySession(String token) { - Session session = sessions.get(token); - if (session != null) { - log.info("User[" + session.getUser().getName() + "] authenticated by " + // - "session[" + session.getToken() + "]."); - } else { - log.info("Authentication by session failed."); - } - return session; - } - - private Session addSession(User user) { - if (user != null) { - Session session = new Session(user); - sessions.put(session.getToken(), session); - return session; - } - return null; - } -} diff --git a/edge/src/io/openems/api/security/User.java b/edge/src/io/openems/api/security/User.java index fc706cf29c6..490150ec17b 100644 --- a/edge/src/io/openems/api/security/User.java +++ b/edge/src/io/openems/api/security/User.java @@ -27,6 +27,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.Base64.Decoder; +import java.util.Optional; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; @@ -36,8 +37,8 @@ import org.slf4j.LoggerFactory; import io.openems.api.exception.OpenemsException; +import io.openems.common.utils.SecureRandomSingleton; import io.openems.core.Config; -import io.openems.core.utilities.SecureRandomSingleton; public enum User { /* @@ -129,24 +130,28 @@ private static byte[] getRandomSalt(int length) { * @param password * @return the authenticated User or null if authentication failed */ - public static User authenticate(String password) { + public static Optional authenticate(String password) { // Search for any user with the given password for (User user : USERS) { if (user.checkPassword(password)) { - return user; + log.info("Authentication successful with password only for user [" + user.getName() + "]."); + return Optional.ofNullable(user); } } - return null; + log.info("Authentication failed with password only."); + return Optional.empty(); } - public static User authenticate(String username, String password) { + public static Optional authenticate(String username, String password) { // Search for user with given username for (User user : USERS) { if (username.equals(user.getName())) { if (user.checkPassword(password)) { - return user; + log.info("Authentication successful for user[" + username + "]."); + return Optional.of(user); } else { - return null; + log.info("Authentication failed for user[" + username + "]: wrong password"); + return Optional.empty(); } } } diff --git a/edge/src/io/openems/core/Address.java b/edge/src/io/openems/core/Address.java index bc19f951750..9a7f3792261 100644 --- a/edge/src/io/openems/core/Address.java +++ b/edge/src/io/openems/core/Address.java @@ -1,5 +1,6 @@ package io.openems.core; +// TODO move this to common package (types/ChannelAddress) public class Address { private final String thingId; private final String channelId; diff --git a/edge/src/io/openems/core/ClassRepository.java b/edge/src/io/openems/core/ClassRepository.java index 567ca979043..f473cfe7aaa 100644 --- a/edge/src/io/openems/core/ClassRepository.java +++ b/edge/src/io/openems/core/ClassRepository.java @@ -34,6 +34,7 @@ import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashMultimap; +import com.google.common.collect.Iterables; import com.google.common.collect.Table; import io.openems.api.bridge.Bridge; @@ -99,6 +100,14 @@ public Map getThingConfigChannels(Class cla return Collections.unmodifiableMap(thingConfigChannels.row(clazz)); } + public Iterable getAvailableThings() throws ReflectionException { + return Iterables.concat( // + getAvailableBridges(), // + getAvailableControllers(), // + getAvailableDevices(), // + getAvailableSchedulers()); + } + public Collection getAvailableControllers() throws ReflectionException { if (controllers.isEmpty()) { for (Class clazz : ConfigUtils.getAvailableClasses("io.openems.impl.controller", @@ -144,41 +153,39 @@ public Collection getAvailableSchedulers() throws ReflectionException } private void parseClass(Class clazz) { - if (Thing.class.isAssignableFrom(Thing.class)) { - for (Method method : clazz.getMethods()) { - Class type = null; - if (method.getReturnType().isArray()) { - Class rtype = method.getReturnType(); - type = rtype.getComponentType(); + for (Method method : clazz.getMethods()) { + Class type = null; + if (method.getReturnType().isArray()) { + Class rtype = method.getReturnType(); + type = rtype.getComponentType(); + } else { + type = method.getReturnType(); + } + if (Channel.class.isAssignableFrom(type)) { + thingChannels.put(clazz, method); + } + if (ConfigChannel.class.isAssignableFrom(type)) { + ConfigInfo configAnnotation = getAnnotation(clazz, method.getName()); + if (configAnnotation != null) { + thingConfigChannels.put(clazz, method, configAnnotation); } else { - type = method.getReturnType(); - } - if (Channel.class.isAssignableFrom(type)) { - thingChannels.put(clazz, method); - } - if (ConfigChannel.class.isAssignableFrom(type)) { - ConfigInfo configAnnotation = getAnnotation(clazz, method.getName()); - if (configAnnotation != null) { - thingConfigChannels.put(clazz, method, configAnnotation); - } else { - log.error("Config-Annotation is missing for method [" + method.getName() + "] in class [" - + clazz.getName() + "]"); - } + log.error("Config-Annotation is missing for method [" + method.getName() + "] in class [" + + clazz.getName() + "]"); } } - for (Field field : clazz.getFields()) { - Class type = field.getType(); - if (Channel.class.isAssignableFrom(type)) { - thingChannels.put(clazz, field); - } - if (ConfigChannel.class.isAssignableFrom(type)) { - ConfigInfo configAnnotation = field.getAnnotation(ConfigInfo.class); - if (configAnnotation == null) { - log.error("Config-Annotation is missing for field [" + field.getName() + "] in class [" - + clazz.getName() + "]"); - } else { - thingConfigChannels.put(clazz, field, configAnnotation); - } + } + for (Field field : clazz.getFields()) { + Class type = field.getType(); + if (Channel.class.isAssignableFrom(type)) { + thingChannels.put(clazz, field); + } + if (ConfigChannel.class.isAssignableFrom(type)) { + ConfigInfo configAnnotation = field.getAnnotation(ConfigInfo.class); + if (configAnnotation == null) { + log.error("Config-Annotation is missing for field [" + field.getName() + "] in class [" + + clazz.getName() + "]"); + } else { + thingConfigChannels.put(clazz, field, configAnnotation); } } } diff --git a/edge/src/io/openems/core/Config.java b/edge/src/io/openems/core/Config.java index d1f7c959273..642d6e09549 100644 --- a/edge/src/io/openems/core/Config.java +++ b/edge/src/io/openems/core/Config.java @@ -1,556 +1,541 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.core; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.controller.Controller; -import io.openems.api.device.Device; -import io.openems.api.doc.ThingDoc; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.NotImplementedException; -import io.openems.api.exception.OpenemsException; -import io.openems.api.exception.ReflectionException; -import io.openems.api.exception.WriteChannelException; -import io.openems.api.persistence.Persistence; -import io.openems.api.scheduler.Scheduler; -import io.openems.api.security.User; -import io.openems.core.utilities.AbstractWorker; -import io.openems.core.utilities.ConfigUtils; -import io.openems.core.utilities.InjectionUtils; -import io.openems.core.utilities.JsonUtils; - -public class Config implements ChannelChangeListener { - - private final static Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); - private final static String CONFIG_PATH = "etc/openems.d"; - private final static String CONFIG_FILE_NAME = "config.json"; - private final static String CONFIG_BACKUP_FILE_NAME = "config.backup.json"; - - private final static Logger log = LoggerFactory.getLogger(Config.class); - private static Config instance; - - public static synchronized Config getInstance() throws ConfigException { - if (Config.instance == null) { - Config.instance = new Config(); - } - return Config.instance; - } - - private final ThingRepository thingRepository; - private final Path configFile; - private final Path configBackupFile; - private final ExecutorService writeConfigExecutor; - - public Config() throws ConfigException { - thingRepository = ThingRepository.getInstance(); - this.configFile = getConfigFile(); - this.configBackupFile = getConfigBackupFile(); - this.writeConfigExecutor = Executors.newSingleThreadExecutor(); - } - - public synchronized void readConfigFile() throws Exception { - // Read configuration from default config file - try { - readConfigFromFile(configFile); - log.info("Read configuration from file [" + configFile.toString() + "]"); - return; - } catch (Exception e) { - log.warn("Failed to read configuration from file [" + configFile.toString() + "] ", e); - } - // Read configuration from backup config file - try { - readConfigFromFile(configBackupFile); - log.info("Read configuration from backup file [" + configBackupFile.toString() + "]"); - } catch (Exception e) { - log.warn("Failed to read configuration backup file [" + configFile.toString() + "]", e); - throw e; - } - } - - private synchronized void readConfigFromFile(Path path) throws Exception { - JsonObject jConfig = new JsonObject(); - JsonParser parser = new JsonParser(); - String config = new String(Files.readAllBytes(path), DEFAULT_CHARSET); - JsonElement jsonElement = parser.parse(config); - jConfig = jsonElement.getAsJsonObject(); - jConfig = addDefaultConfig(jConfig); - // apply config - parseJsonConfig(jConfig); - } - - private JsonObject addDefaultConfig(JsonObject jConfig) { - try { - /* - * Things - */ - JsonArray jThings; - if (!jConfig.has("things") || !jConfig.get("things").isJsonArray()) { - jThings = new JsonArray(); - } else { - jThings = JsonUtils.getAsJsonArray(jConfig, "things"); - } - { - /* - * Add SystemBridge - */ - if (!JsonUtils.hasElement(jConfig, "things", "class", "io.openems.impl.protocol.system.SystemBridge")) { - JsonObject jBridge = new JsonObject(); - { - jBridge.addProperty("class", "io.openems.impl.protocol.system.SystemBridge"); - JsonArray jDevices = new JsonArray(); - { - JsonObject jSystem = new JsonObject(); - jSystem.addProperty("class", "io.openems.impl.device.system.System"); - { - JsonObject jSystemNature = new JsonObject(); - { - jSystemNature.addProperty("id", "system0"); - } - jSystem.add("system", jSystemNature); - } - jDevices.add(jSystem); - } - jBridge.add("devices", jDevices); - } - jThings.add(jBridge); - } - } - jConfig.add("things", jThings); - /* - * Scheduler - */ - JsonObject jScheduler; - if (!jConfig.has("scheduler") || !jConfig.get("scheduler").isJsonObject()) { - jScheduler = new JsonObject(); - jScheduler.addProperty("class", "io.openems.impl.scheduler.SimpleScheduler"); - } else { - jScheduler = JsonUtils.getAsJsonObject(jConfig, "scheduler"); - } - { - /* - * Controller - */ - JsonArray jControllers; - if (!jScheduler.has("controllers") || !jScheduler.get("controllers").isJsonArray()) { - jControllers = new JsonArray(); - } else { - jControllers = JsonUtils.getAsJsonArray(jScheduler, "controllers"); - } - { - /* - * WebsocketApiController - */ - if (!JsonUtils.hasElement(jControllers, "class", - "io.openems.impl.controller.api.websocket.WebsocketApiController")) { - JsonObject jWebsocketApiController = new JsonObject(); - jWebsocketApiController.addProperty("class", - "io.openems.impl.controller.api.websocket.WebsocketApiController"); - jWebsocketApiController.addProperty("priority", Integer.MIN_VALUE); - jControllers.add(jWebsocketApiController); - } - /* - * RestApiController - */ - if (!JsonUtils.hasElement(jControllers, "class", - "io.openems.impl.controller.api.rest.RestApiController")) { - JsonObject jRestApiController = new JsonObject(); - jRestApiController.addProperty("class", - "io.openems.impl.controller.api.rest.RestApiController"); - jRestApiController.addProperty("priority", Integer.MIN_VALUE); - jControllers.add(jRestApiController); - } - } - jScheduler.add("controllers", jControllers); - } - jConfig.add("scheduler", jScheduler); - } catch (ReflectionException e) { - log.warn("Error applying default config: " + e.getMessage()); - } - return jConfig; - } - - /** - * Writes the config file. Holds a backup config file and restores it on error. Method is executed asynchronously. - * - * @throws NotImplementedException - */ - public void writeConfigFile() throws NotImplementedException { - JsonObject jConfig = getJsonComplete(); - Runnable writeConfigRunnable = new Runnable() { - @Override - public void run() { - Gson gson = new GsonBuilder().setPrettyPrinting().create(); - String config = gson.toJson(jConfig); - try { - /* - * create backup of config file - */ - Files.copy(configFile, configBackupFile, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - ConfigException ex = new ConfigException( - "Unable to create backup file [" + configBackupFile.toString() + "]"); - log.error(ex.getMessage(), ex); - } - - try { - /* - * write config file - */ - Files.write(configFile, config.getBytes(DEFAULT_CHARSET)); - } catch (IOException e) { - ConfigException ex = new ConfigException( - "Unable to write config file [" + configFile.toString() + "]", e); - log.error(ex.getMessage(), ex); - - try { - /* - * On error: recover backup file - */ - Files.copy(configBackupFile, configFile, StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e2) { - ConfigException ex2 = new ConfigException( - "Unable to recover backup file [" + configBackupFile.toString() + "]"); - log.error(ex.getMessage(), ex); - } - } - } - }; - this.writeConfigExecutor.execute(writeConfigRunnable); - } - - public synchronized void parseJsonConfig(JsonObject jConfig) - throws ReflectionException, ConfigException, WriteChannelException { - /* - * read Users - */ - if (jConfig.has("users")) { - JsonObject jUsers = JsonUtils.getAsJsonObject(jConfig, "users"); - for (Entry jUsersElement : jUsers.entrySet()) { - JsonObject jUser = JsonUtils.getAsJsonObject(jUsersElement.getValue()); - String username = jUsersElement.getKey(); - String passwordBase64 = JsonUtils.getAsString(jUser, "password"); - String saltBase64 = JsonUtils.getAsString(jUser, "salt"); - try { - User.getUserByName(username).initialize(passwordBase64, saltBase64); - } catch (OpenemsException e) { - log.error("Error parsing config: " + e.getMessage()); - } - } - } - User.initializeFinished(); // important! no more setting of users allowed! - - /* - * read each Bridge in "things" array - */ - JsonArray jThings = JsonUtils.getAsJsonArray(jConfig, "things"); - for (JsonElement jBridgeElement : jThings) { - JsonObject jBridge = JsonUtils.getAsJsonObject(jBridgeElement); - String bridgeClass = JsonUtils.getAsString(jBridge, "class"); - Bridge bridge = (Bridge) InjectionUtils.getThingInstance(bridgeClass); - thingRepository.addThing(bridge); - log.debug("Add Bridge[" + bridge.id() + "], Implementation[" + bridge.getClass().getSimpleName() + "]"); - ConfigUtils.injectConfigChannels(thingRepository.getConfigChannels(bridge), jBridge); - /* - * read each Device in "things" array - */ - List devices = new ArrayList<>(); - JsonArray jDevices = JsonUtils.getAsJsonArray(jBridge, "devices"); - for (JsonElement jDeviceElement : jDevices) { - JsonObject jDevice = JsonUtils.getAsJsonObject(jDeviceElement); - Device device = thingRepository.createDevice(jDevice); - devices.add(device); - bridge.addDevice(device); - } - } - - /* - * read Scheduler - */ - if (jConfig.has("scheduler")) { - JsonObject jScheduler = JsonUtils.getAsJsonObject(jConfig, "scheduler"); - String schedulerClass = JsonUtils.getAsString(jScheduler, "class"); - Scheduler scheduler = (Scheduler) InjectionUtils.getThingInstance(schedulerClass); - thingRepository.addThing(scheduler); - log.debug("Add Scheduler[" + scheduler.id() + "], Implementation[" + scheduler.getClass().getSimpleName() - + "]"); - ConfigUtils.injectConfigChannels(thingRepository.getConfigChannels(scheduler), jScheduler); - /* - * read each Controller in "controllers" array - */ - JsonArray jControllers = JsonUtils.getAsJsonArray(jScheduler, "controllers"); - for (JsonElement jControllerElement : jControllers) { - JsonObject jController = JsonUtils.getAsJsonObject(jControllerElement); - Controller controller = thingRepository.createController(jController); - scheduler.addController(controller); - } - } - - /* - * read Persistence - */ - if (jConfig.has("persistence")) { - JsonArray jPersistences = JsonUtils.getAsJsonArray(jConfig, "persistence"); - for (JsonElement jPersistenceElement : jPersistences) { - JsonObject jPersistence = JsonUtils.getAsJsonObject(jPersistenceElement); - String persistenceClass = JsonUtils.getAsString(jPersistence, "class"); - Persistence persistence = (Persistence) InjectionUtils.getThingInstance(persistenceClass); - thingRepository.addThing(persistence); - log.debug("Add Persistence[" + persistence.id() + "], Implementation[" - + persistence.getClass().getSimpleName() + "]"); - ConfigUtils.injectConfigChannels(thingRepository.getConfigChannels(persistence), jPersistence); - } - } - - /* - * Configuration is finished -> start all worker threads - */ - thingRepository.getThings().forEach(thing -> { - if (thing instanceof Thread) { - ((AbstractWorker) thing).start(); - } - }); - - /* - * Register myself as onChangeListener on all ConfigChannels - */ - for (ConfigChannel channel : thingRepository.getConfigChannels()) { - channel.addChangeListener(this); - } - } - - public JsonArray getBridgesJson(boolean includeEverything) throws NotImplementedException { - JsonArray jBridges = new JsonArray(); - for (Bridge bridge : thingRepository.getBridges()) { - JsonObject jBridge = (JsonObject) ConfigUtils.getAsJsonElement(bridge, includeEverything); - /* - * Device - */ - JsonArray jDevices = new JsonArray(); - for (Device device : bridge.getDevices()) { - JsonObject jDevice = (JsonObject) ConfigUtils.getAsJsonElement(device, includeEverything); - jDevices.add(jDevice); - } - jBridge.add("devices", jDevices); - jBridges.add(jBridge); - } - return jBridges; - } - - public JsonObject getSchedulerJson(boolean includeEverything) throws NotImplementedException { - JsonObject jScheduler = null; - for (Scheduler scheduler : thingRepository.getSchedulers()) { - jScheduler = (JsonObject) ConfigUtils.getAsJsonElement(scheduler, includeEverything); - /* - * Controller - */ - JsonArray jControllers = new JsonArray(); - for (Controller controller : scheduler.getControllers()) { - jControllers.add(ConfigUtils.getAsJsonElement(controller, includeEverything)); - } - jScheduler.add("controllers", jControllers); - break; // TODO only one Scheduler supported - } - return jScheduler; - } - - public JsonArray getPersistenceJson(boolean includeEverything) throws NotImplementedException { - JsonArray jPersistences = new JsonArray(); - for (Persistence persistence : thingRepository.getPersistences()) { - JsonObject jPersistence = (JsonObject) ConfigUtils.getAsJsonElement(persistence, includeEverything); - jPersistences.add(jPersistence); - } - return jPersistences; - } - - private JsonObject getUsersJson() { - JsonObject jUsers = new JsonObject(); - for (User user : User.getUsers()) { - JsonObject jUser = new JsonObject(); - jUser.addProperty("password", user.getPasswordBase64()); - jUser.addProperty("salt", user.getSaltBase64()); - jUsers.add(user.getName(), jUser); - } - return jUsers; - } - - public synchronized JsonObject getJson(boolean includeEverything) throws NotImplementedException { - JsonObject jConfig = new JsonObject(); - /* - * Bridge - */ - jConfig.add("things", getBridgesJson(includeEverything)); - /* - * Scheduler - */ - jConfig.add("scheduler", getSchedulerJson(includeEverything)); - /* - * Persistence - */ - jConfig.add("persistence", getPersistenceJson(includeEverything)); - return jConfig; - } - - private synchronized JsonObject getJsonComplete() throws NotImplementedException { - JsonObject jConfig = getJson(false); - /* - * Users - */ - jConfig.add("users", getUsersJson()); - return jConfig; - } - - /** - * Receives update events for config channels and rewrites the json config - */ - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - // TODO: trigger ConfigUpdated event - try { - writeConfigFile(); - } catch (OpenemsException e) { - log.error("Config-Error.", e); - } - } - - /** - * Provides the File path of the config file ("/etc/openems.d/config.json") or a local file on a development machine - */ - private Path getConfigFile() { - String relativePath = CONFIG_PATH + "/" + CONFIG_FILE_NAME; - Path configFile = Paths.get(relativePath); - if (Files.isReadable(configFile)) { - // we are on development system - return configFile; - } - return Paths.get("/" + relativePath); - } - - /** - * Provides the File path of the config backup file ("/etc/openems.d/config.backup.json") or a local file on a - * development machine - */ - private Path getConfigBackupFile() { - Path configFile = getConfigFile(); - Path backupFile = configFile.getParent().resolve(CONFIG_BACKUP_FILE_NAME); - return backupFile; - } - - /** - * Generates a JsonObject including the current configuration as well as meta-data about available controllers, - * bridges,... - * - * @return - */ - public JsonObject getMetaConfigJson() { - try { - /* - * Json Config - */ - JsonObject j = getJson(true); - JsonObject jMeta = new JsonObject(); - /* - * Devices -> Natures - */ - JsonObject jDeviceNatures = new JsonObject(); - thingRepository.getDeviceNatures().forEach(nature -> { - JsonArray jNatureImplements = new JsonArray(); - /* - * get important classes/interfaces that are implemented by this nature - */ - for (Class iface : InjectionUtils.getImportantNatureInterfaces(nature.getClass())) { - jNatureImplements.add(iface.getSimpleName()); - } - JsonObject jDeviceNature = new JsonObject(); - jDeviceNature.add("implements", jNatureImplements); - JsonObject jChannels = new JsonObject(); - thingRepository.getConfigChannels(nature).forEach(channel -> { - try { - jChannels.add(channel.id(), channel.toJsonObject()); - } catch (NotImplementedException e) { - /* ignore */ - } - }); - jDeviceNature.add("channels", jChannels); - jDeviceNatures.add(nature.id(), jDeviceNature); - }); - jMeta.add("natures", jDeviceNatures); - /* - * Available - */ - ClassRepository classRepository = ClassRepository.getInstance(); - // Controllers - JsonObject jAvailableControllers = new JsonObject(); - for (ThingDoc description : classRepository.getAvailableControllers()) { - jAvailableControllers.add(description.getClazz().getName(), description.getAsJsonObject()); - } - jMeta.add("availableControllers", jAvailableControllers); - // Bridges - JsonObject jAvailableBridges = new JsonObject(); - for (ThingDoc description : classRepository.getAvailableBridges()) { - jAvailableBridges.add(description.getClazz().getName(), description.getAsJsonObject()); - } - jMeta.add("availableBridges", jAvailableBridges); - // Devices - JsonObject jAvailableDevices = new JsonObject(); - for (ThingDoc description : classRepository.getAvailableDevices()) { - jAvailableDevices.add(description.getClazz().getName(), description.getAsJsonObject()); - } - jMeta.add("availableDevices", jAvailableDevices); - // Schedulers - JsonObject jAvailableSchedulers = new JsonObject(); - for (ThingDoc description : classRepository.getAvailableSchedulers()) { - jAvailableSchedulers.add(description.getClazz().getName(), description.getAsJsonObject()); - } - jMeta.add("availableSchedulers", jAvailableSchedulers); - j.add("_meta", jMeta); - return j; - } catch (NotImplementedException | ReflectionException e) { - log.warn("Unable to create config: " + e.getMessage()); - return new JsonObject(); - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.core; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.Controller; +import io.openems.api.device.Device; +import io.openems.api.doc.ThingDoc; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.NotImplementedException; +import io.openems.api.exception.OpenemsException; +import io.openems.api.exception.ReflectionException; +import io.openems.api.exception.WriteChannelException; +import io.openems.api.persistence.Persistence; +import io.openems.api.scheduler.Scheduler; +import io.openems.api.security.User; +import io.openems.api.thing.Thing; +import io.openems.core.utilities.ConfigUtils; +import io.openems.core.utilities.InjectionUtils; +import io.openems.core.utilities.JsonUtils; + +public class Config implements ChannelChangeListener { + + private final static Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + private final static String CONFIG_PATH = "etc/openems.d"; + private final static String CONFIG_FILE_NAME = "config.json"; + private final static String CONFIG_BACKUP_FILE_NAME = "config.backup.json"; + + private final static Logger log = LoggerFactory.getLogger(Config.class); + private static Config instance; + + public static synchronized Config getInstance() throws ConfigException { + if (Config.instance == null) { + Config.instance = new Config(); + } + return Config.instance; + } + + private final ThingRepository thingRepository; + private final Path configFile; + private final Path configBackupFile; + private final ExecutorService writeConfigExecutor; + + public Config() throws ConfigException { + thingRepository = ThingRepository.getInstance(); + this.configFile = getConfigFile(); + this.configBackupFile = getConfigBackupFile(); + this.writeConfigExecutor = Executors.newSingleThreadExecutor(); + } + + public synchronized void readConfigFile() throws Exception { + // Read configuration from default config file + try { + readConfigFromFile(configFile); + log.info("Read configuration from file [" + configFile.toString() + "]"); + return; + } catch (Exception e) { + log.warn("Failed to read configuration from file [" + configFile.toString() + "] ", e); + } + // Read configuration from backup config file + try { + readConfigFromFile(configBackupFile); + log.info("Read configuration from backup file [" + configBackupFile.toString() + "]"); + } catch (Exception e) { + log.warn("Failed to read configuration backup file [" + configFile.toString() + "]", e); + throw e; + } + } + + private synchronized void readConfigFromFile(Path path) throws Exception { + JsonObject jConfig = new JsonObject(); + JsonParser parser = new JsonParser(); + String config = new String(Files.readAllBytes(path), DEFAULT_CHARSET); + JsonElement jsonElement = parser.parse(config); + jConfig = jsonElement.getAsJsonObject(); + jConfig = addDefaultConfig(jConfig); + // apply config + parseJsonConfig(jConfig); + } + + private JsonObject addDefaultConfig(JsonObject jConfig) { + try { + /* + * Things + */ + JsonArray jThings; + if (!jConfig.has("things") || !jConfig.get("things").isJsonArray()) { + jThings = new JsonArray(); + } else { + jThings = JsonUtils.getAsJsonArray(jConfig, "things"); + } + { + /* + * Add SystemBridge + */ + if (!JsonUtils.hasElement(jConfig, "things", "class", "io.openems.impl.protocol.system.SystemBridge")) { + JsonObject jBridge = new JsonObject(); + { + jBridge.addProperty("class", "io.openems.impl.protocol.system.SystemBridge"); + JsonArray jDevices = new JsonArray(); + { + JsonObject jSystem = new JsonObject(); + jSystem.addProperty("class", "io.openems.impl.device.system.System"); + { + JsonObject jSystemNature = new JsonObject(); + { + jSystemNature.addProperty("id", "system0"); + } + jSystem.add("system", jSystemNature); + } + jDevices.add(jSystem); + } + jBridge.add("devices", jDevices); + } + jThings.add(jBridge); + } + } + jConfig.add("things", jThings); + /* + * Scheduler + */ + JsonObject jScheduler; + if (!jConfig.has("scheduler") || !jConfig.get("scheduler").isJsonObject()) { + jScheduler = new JsonObject(); + jScheduler.addProperty("class", "io.openems.impl.scheduler.SimpleScheduler"); + } else { + jScheduler = JsonUtils.getAsJsonObject(jConfig, "scheduler"); + } + { + /* + * Controller + */ + JsonArray jControllers; + if (!jScheduler.has("controllers") || !jScheduler.get("controllers").isJsonArray()) { + jControllers = new JsonArray(); + } else { + jControllers = JsonUtils.getAsJsonArray(jScheduler, "controllers"); + } + { + /* + * WebsocketApiController + */ + if (!JsonUtils.hasElement(jControllers, "class", + "io.openems.impl.controller.api.websocket.WebsocketApiController")) { + JsonObject jWebsocketApiController = new JsonObject(); + jWebsocketApiController.addProperty("class", + "io.openems.impl.controller.api.websocket.WebsocketApiController"); + jWebsocketApiController.addProperty("priority", Integer.MIN_VALUE); + jControllers.add(jWebsocketApiController); + } + /* + * RestApiController + */ + if (!JsonUtils.hasElement(jControllers, "class", + "io.openems.impl.controller.api.rest.RestApiController")) { + JsonObject jRestApiController = new JsonObject(); + jRestApiController.addProperty("class", + "io.openems.impl.controller.api.rest.RestApiController"); + jRestApiController.addProperty("priority", Integer.MIN_VALUE); + jControllers.add(jRestApiController); + } + } + jScheduler.add("controllers", jControllers); + } + jConfig.add("scheduler", jScheduler); + } catch (ReflectionException e) { + log.warn("Error applying default config: " + e.getMessage()); + } + return jConfig; + } + + /** + * Writes the config file. Holds a backup config file and restores it on error. Method is executed asynchronously. + * + * @throws NotImplementedException + */ + public void writeConfigFile() throws NotImplementedException { + // get config as json + JsonObject jConfig = getJson(ConfigFormat.FILE); + + Runnable writeConfigRunnable = new Runnable() { + @Override + public void run() { + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + String config = gson.toJson(jConfig); + try { + /* + * create backup of config file + */ + Files.copy(configFile, configBackupFile, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + ConfigException ex = new ConfigException( + "Unable to create backup file [" + configBackupFile.toString() + "]"); + log.error(ex.getMessage(), ex); + } + + try { + /* + * write config file + */ + Files.write(configFile, config.getBytes(DEFAULT_CHARSET)); + } catch (IOException e) { + ConfigException ex = new ConfigException( + "Unable to write config file [" + configFile.toString() + "]", e); + log.error(ex.getMessage(), ex); + + try { + /* + * On error: recover backup file + */ + Files.copy(configBackupFile, configFile, StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e2) { + ConfigException ex2 = new ConfigException( + "Unable to recover backup file [" + configBackupFile.toString() + "]"); + log.error(ex.getMessage(), ex); + } + } + } + }; + this.writeConfigExecutor.execute(writeConfigRunnable); + } + + public synchronized void parseJsonConfig(JsonObject jConfig) + throws ReflectionException, ConfigException, WriteChannelException { + /* + * read Users + */ + if (jConfig.has("users")) { + JsonObject jUsers = JsonUtils.getAsJsonObject(jConfig, "users"); + for (Entry jUsersElement : jUsers.entrySet()) { + JsonObject jUser = JsonUtils.getAsJsonObject(jUsersElement.getValue()); + String username = jUsersElement.getKey(); + String passwordBase64 = JsonUtils.getAsString(jUser, "password"); + String saltBase64 = JsonUtils.getAsString(jUser, "salt"); + try { + User.getUserByName(username).initialize(passwordBase64, saltBase64); + } catch (OpenemsException e) { + log.error("Error parsing config: " + e.getMessage()); + } + } + } + User.initializeFinished(); // important! no more setting of users allowed! + + /* + * read each Bridge in "things" array + */ + JsonArray jThings = JsonUtils.getAsJsonArray(jConfig, "things"); + for (JsonElement jBridgeElement : jThings) { + JsonObject jBridge = JsonUtils.getAsJsonObject(jBridgeElement); + String bridgeClass = JsonUtils.getAsString(jBridge, "class"); + Bridge bridge = (Bridge) InjectionUtils.getThingInstance(bridgeClass); + thingRepository.addThing(bridge); + log.debug("Add Bridge[" + bridge.id() + "], Implementation[" + bridge.getClass().getSimpleName() + "]"); + ConfigUtils.injectConfigChannels(thingRepository.getConfigChannels(bridge), jBridge); + /* + * read each Device in "things" array + */ + List devices = new ArrayList<>(); + JsonArray jDevices = JsonUtils.getAsJsonArray(jBridge, "devices"); + for (JsonElement jDeviceElement : jDevices) { + JsonObject jDevice = JsonUtils.getAsJsonObject(jDeviceElement); + Device device = thingRepository.createDevice(jDevice, bridge); + devices.add(device); + bridge.addDevice(device); + } + /* + * Init bridge + */ + bridge.init(); + for (Device d : bridge.getDevices()) { + d.init(); + } + } + + /* + * read Scheduler + */ + if (jConfig.has("scheduler")) { + JsonObject jScheduler = JsonUtils.getAsJsonObject(jConfig, "scheduler"); + String schedulerClass = JsonUtils.getAsString(jScheduler, "class"); + Scheduler scheduler = (Scheduler) InjectionUtils.getThingInstance(schedulerClass); + thingRepository.addThing(scheduler); + log.debug("Add Scheduler[" + scheduler.id() + "], Implementation[" + scheduler.getClass().getSimpleName() + + "]"); + ConfigUtils.injectConfigChannels(thingRepository.getConfigChannels(scheduler), jScheduler); + /* + * read each Controller in "controllers" array + */ + JsonArray jControllers = JsonUtils.getAsJsonArray(jScheduler, "controllers"); + for (JsonElement jControllerElement : jControllers) { + JsonObject jController = JsonUtils.getAsJsonObject(jControllerElement); + Controller controller = thingRepository.createController(jController); + scheduler.addController(controller); + controller.init(); + } + scheduler.init(); + } + + /* + * read Persistence + */ + if (jConfig.has("persistence")) { + JsonArray jPersistences = JsonUtils.getAsJsonArray(jConfig, "persistence"); + for (JsonElement jPersistenceElement : jPersistences) { + JsonObject jPersistence = JsonUtils.getAsJsonObject(jPersistenceElement); + String persistenceClass = JsonUtils.getAsString(jPersistence, "class"); + Persistence persistence = (Persistence) InjectionUtils.getThingInstance(persistenceClass); + thingRepository.addThing(persistence); + log.debug("Add Persistence[" + persistence.id() + "], Implementation[" + + persistence.getClass().getSimpleName() + "]"); + ConfigUtils.injectConfigChannels(thingRepository.getConfigChannels(persistence), jPersistence); + persistence.init(); + } + } + + /* + * Configuration is finished -> start all worker threads + */ + thingRepository.getThings().forEach(thing -> { + if (thing instanceof Thread) { + ((Thread) thing).start(); + } + }); + + /* + * Register myself as onChangeListener on all ConfigChannels + */ + for (ConfigChannel channel : thingRepository.getConfigChannels()) { + channel.addChangeListener(this); + } + + /* + * After 10 seconds: build the ClassRepository cache to speed up future calls + * (this speeds up the first opening of the UI, as the cache does not need to be built) + */ + Executors.newScheduledThreadPool(1).schedule(() -> { + try { + ClassRepository.getInstance().getAvailableThings(); + } catch (ReflectionException e) { /* ignore */} + }, 10, TimeUnit.SECONDS); + } + + public JsonArray getBridgesJson(ConfigFormat format) throws NotImplementedException { + JsonArray jBridges = new JsonArray(); + for (Bridge bridge : thingRepository.getBridges()) { + JsonObject jBridge = (JsonObject) ConfigUtils.getAsJsonElement(bridge, format); + /* + * Device + */ + JsonArray jDevices = new JsonArray(); + for (Device device : bridge.getDevices()) { + JsonObject jDevice = (JsonObject) ConfigUtils.getAsJsonElement(device, format); + jDevices.add(jDevice); + } + jBridge.add("devices", jDevices); + jBridges.add(jBridge); + } + return jBridges; + } + + public JsonObject getSchedulerJson(ConfigFormat format) throws NotImplementedException { + JsonObject jScheduler = null; + for (Scheduler scheduler : thingRepository.getSchedulers()) { + jScheduler = (JsonObject) ConfigUtils.getAsJsonElement(scheduler, format); + /* + * Controller + */ + JsonArray jControllers = new JsonArray(); + for (Controller controller : scheduler.getControllers()) { + jControllers.add(ConfigUtils.getAsJsonElement(controller, format)); + } + jScheduler.add("controllers", jControllers); + break; // TODO only one Scheduler supported + } + return jScheduler; + } + + public JsonArray getPersistenceJson(ConfigFormat format) throws NotImplementedException { + JsonArray jPersistences = new JsonArray(); + for (Persistence persistence : thingRepository.getPersistences()) { + JsonObject jPersistence = (JsonObject) ConfigUtils.getAsJsonElement(persistence, format); + jPersistences.add(jPersistence); + } + return jPersistences; + } + + private JsonObject getUsersJson() { + JsonObject jUsers = new JsonObject(); + for (User user : User.getUsers()) { + JsonObject jUser = new JsonObject(); + jUser.addProperty("password", user.getPasswordBase64()); + jUser.addProperty("salt", user.getSaltBase64()); + jUsers.add(user.getName(), jUser); + } + return jUsers; + } + + /** + * Gets the Config as Json in the given format in english language + * + * @param format + * @return + * @throws NotImplementedException + */ + public synchronized JsonObject getJson(ConfigFormat format) throws NotImplementedException { + return this.getJson(format, "en"); // TODO use proper language tag + } + + /** + * Gets the Config as Json in the given format + * + * @param format + * @return + * @throws NotImplementedException + */ + // TODO make use of language tag + public synchronized JsonObject getJson(ConfigFormat format, String language) throws NotImplementedException { + JsonObject jConfig = new JsonObject(); + if (format == ConfigFormat.FILE) { + /* + * Prepare Json in format for config.json file + */ + // Bridge + jConfig.add("things", getBridgesJson(format)); + // Scheduler + jConfig.add("scheduler", getSchedulerJson(format)); + // Persistence + jConfig.add("persistence", getPersistenceJson(format)); + // Users + jConfig.add("users", getUsersJson()); + + } else { + /* + * Prepare Json in format for OpenEMS UI + */ + // things... + JsonObject jThings = new JsonObject(); + Set things = ThingRepository.getInstance().getThings(); + for (Thing thing : things) { + JsonObject jThing = (JsonObject) ConfigUtils.getAsJsonElement(thing, format); + jThings.add(thing.id(), jThing); + } + jConfig.add("things", jThings); + // meta... + JsonObject jMeta = new JsonObject(); + try { + Iterable availableThings = ClassRepository.getInstance().getAvailableThings(); + for (ThingDoc availableThing : availableThings) { + jMeta.add(availableThing.getClazz().getName(), availableThing.getAsJsonObject()); + } + } catch (ReflectionException e) { + log.error(e.getMessage()); + e.printStackTrace(); + } + jConfig.add("meta", jMeta); + } + return jConfig; + } + + /** + * Receives update events for config channels and rewrites the json config + */ + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + // TODO: trigger ConfigUpdated event + try { + writeConfigFile(); + } catch (OpenemsException e) { + log.error("Config-Error.", e); + } + } + + /** + * Provides the File path of the config file ("/etc/openems.d/config.json") or a local file on a development machine + */ + private Path getConfigFile() { + String relativePath = CONFIG_PATH + "/" + CONFIG_FILE_NAME; + Path configFile = Paths.get(relativePath); + if (Files.isReadable(configFile)) { + // we are on development system + return configFile; + } + return Paths.get("/" + relativePath); + } + + /** + * Provides the File path of the config backup file ("/etc/openems.d/config.backup.json") or a local file on a + * development machine + */ + private Path getConfigBackupFile() { + Path configFile = getConfigFile(); + Path backupFile = configFile.getParent().resolve(CONFIG_BACKUP_FILE_NAME); + return backupFile; + } +} \ No newline at end of file diff --git a/edge/src/io/openems/core/ConfigFormat.java b/edge/src/io/openems/core/ConfigFormat.java new file mode 100644 index 00000000000..d87ec33e4a7 --- /dev/null +++ b/edge/src/io/openems/core/ConfigFormat.java @@ -0,0 +1,10 @@ +package io.openems.core; + +/** + * Describes a format for JSON configuration output + * + * @author stefan.feilmeier + */ +public enum ConfigFormat { + FILE, OPENEMS_UI +} diff --git a/edge/src/io/openems/core/ThingRepository.java b/edge/src/io/openems/core/ThingRepository.java index f9621e76e86..f5de9965104 100644 --- a/edge/src/io/openems/core/ThingRepository.java +++ b/edge/src/io/openems/core/ThingRepository.java @@ -1,478 +1,480 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.core; - -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.BiMap; -import com.google.common.collect.HashBasedTable; -import com.google.common.collect.HashBiMap; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.Table; -import com.google.gson.JsonObject; - -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.controller.Controller; -import io.openems.api.device.Device; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.exception.ReflectionException; -import io.openems.api.persistence.Persistence; -import io.openems.api.persistence.QueryablePersistence; -import io.openems.api.scheduler.Scheduler; -import io.openems.api.thing.Thing; -import io.openems.api.thing.ThingChannelsUpdatedListener; -import io.openems.core.ThingsChangedListener.Action; -import io.openems.core.utilities.ConfigUtils; -import io.openems.core.utilities.InjectionUtils; -import io.openems.core.utilities.JsonUtils; - -public class ThingRepository implements ThingChannelsUpdatedListener { - private final static Logger log = LoggerFactory.getLogger(ThingRepository.class); - - private static ThingRepository instance; - - public static synchronized ThingRepository getInstance() { - if (ThingRepository.instance == null) { - ThingRepository.instance = new ThingRepository(); - } - return ThingRepository.instance; - } - - private ThingRepository() { - classRepository = ClassRepository.getInstance(); - } - - private final ClassRepository classRepository; - private final BiMap thingIds = HashBiMap.create(); - private HashMultimap, Thing> thingClasses = HashMultimap.create(); - private Set bridges = new HashSet<>(); - private Set schedulers = new HashSet<>(); - private Set persistences = new HashSet<>(); - private Set queryablePersistences = new HashSet<>(); - private Set deviceNatures = new HashSet<>(); - private final Table thingChannels = HashBasedTable.create(); - private HashMultimap> thingConfigChannels = HashMultimap.create(); - private HashMultimap> thingWriteChannels = HashMultimap.create(); - private List thingListeners = new LinkedList<>(); - - public void addThingChangedListener(ThingsChangedListener listener) { - this.thingListeners.add(listener); - } - - public void removeThingChangedListener(ThingsChangedListener listener) { - this.thingListeners.remove(listener); - } - - /** - * Add a Thing to the Repository and cache its Channels and other information for later usage. - * - * @param thing - */ - public synchronized void addThing(Thing thing) { - if (thingIds.containsValue(thing)) { - // Thing was already added - return; - } - // Add to thingIds - thingIds.forcePut(thing.id(), thing); - - // Add to thingClasses - thingClasses.put(thing.getClass(), thing); - - // Add to bridges - if (thing instanceof Bridge) { - bridges.add((Bridge) thing); - } - - // Add to schedulers - if (thing instanceof Scheduler) { - schedulers.add((Scheduler) thing); - } - - // Add to persistences - if (thing instanceof Persistence) { - persistences.add((Persistence) thing); - } - - // Add to queryablePersistences - if (thing instanceof QueryablePersistence) { - queryablePersistences.add((QueryablePersistence) thing); - } - - // Add to device natures - if (thing instanceof DeviceNature) { - deviceNatures.add((DeviceNature) thing); - } - - // Add Listener - thing.addListener(this); - - // Add Channels thingConfigChannels - Set members = classRepository.getThingChannels(thing.getClass()); - for (Member member : members) { - try { - List channels = new ArrayList<>(); - if (member instanceof Method) { - if (((Method) member).getReturnType().isArray()) { - Channel[] ch = (Channel[]) ((Method) member).invoke(thing); - for (Channel c : ch) { - channels.add(c); - } - } else { - // It's a Method with ReturnType Channel - channels.add((Channel) ((Method) member).invoke(thing)); - } - } else if (member instanceof Field) { - // It's a Field with Type Channel - channels.add((Channel) ((Field) member).get(thing)); - } else { - continue; - } - if (channels.isEmpty()) { - log.error( - "Channel is returning null! Thing [" + thing.id() + "], Member [" + member.getName() + "]"); - continue; - } - for (Channel channel : channels) { - if (channel instanceof ConfigChannel) { - // Add Channel to thingChannels - thingChannels.put(thing, channel.id(), channel); - - // Add Channel to configChannels - thingConfigChannels.put(thing, (ConfigChannel) channel); - } - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - log.warn("Unable to add Channel. Member [" + member.getName() + "]", e); - } - } - for (ThingsChangedListener listener : thingListeners) { - listener.thingChanged(thing, Action.ADD); - } - } - - /** - * Remove a Thing from the Repository. - * - * @param thing - */ - public synchronized void removeThing(String thingId) { - Thing thing = thingIds.get(thingId); - removeThing(thing); - } - - /** - * Remove a Thing from the Repository. - * - * @param thing - */ - public synchronized void removeThing(Thing thing) { - // Remove from thingIds - thingIds.remove(thing.id()); - - // Remove from thingClasses - thingClasses.remove(thing.getClass(), thing); - - // Remove from bridges - if (thing instanceof Bridge) { - bridges.remove(thing); - } - - // Remove from schedulers - if (thing instanceof Scheduler) { - schedulers.remove(thing); - } - - // Remove from persistences - if (thing instanceof Persistence) { - persistences.remove(thing); - } - - // Remove from queryablePersistences - if (thing instanceof QueryablePersistence) { - queryablePersistences.remove(thing); - } - - // Remove from deviceNatures - if (thing instanceof DeviceNature) { - deviceNatures.remove(thing); - } - - // Remove controller - if (thing instanceof Controller) { - Controller controller = (Controller) thing; - for (Scheduler scheduler : getSchedulers()) { - scheduler.removeController(controller); - } - } - - // Remove device - if (thing instanceof Device) { - for (Bridge bridge : bridges) { - bridge.removeDevice((Device) thing); - } - } - - // Remove Listener - thing.removeListener(this); - // TODO further cleaning if required - for (ThingsChangedListener listener : thingListeners) { - listener.thingChanged(thing, Action.REMOVE); - } - } - - public Thing getThing(String thingId) { - Thing thing = thingIds.get(thingId); - return thing; - } - - public Set getThings() { - return Collections.unmodifiableSet(this.thingIds.values()); - } - - /** - * Returns all Channels for this Thing. - * - * @param thing - * @return - */ - public synchronized Collection getChannels(Thing thing) { - return Collections.unmodifiableCollection(thingChannels.row(thing).values()); - } - - /** - * Returns all Config-Channels. - * - * @return - */ - public synchronized Collection> getConfigChannels() { - return Collections.unmodifiableCollection(thingConfigChannels.values()); - } - - /** - * Returns all Config-Channels for this Thing. - * - * @param thing - * @return - */ - public synchronized Set> getConfigChannels(Thing thing) { - return Collections.unmodifiableSet(thingConfigChannels.get(thing)); - } - - /** - * Returns all Write-Channels for this Thing. - * - * @param thing - * @return - */ - public synchronized Set> getWriteChannels(Thing thing) { - return Collections.unmodifiableSet(thingWriteChannels.get(thing)); - } - - /** - * Returns all Write-Channels. - * - * @param thing - * @return - */ - public synchronized Collection> getWriteChannels() { - return Collections.unmodifiableCollection(thingWriteChannels.values()); - } - - /** - * Returns all Persistence-Workers. - * - * @param thing - * @return - */ - public synchronized Set getPersistences() { - return Collections.unmodifiableSet(persistences); - } - - public synchronized Set getQueryablePersistences() { - return Collections.unmodifiableSet(queryablePersistences); - } - - public synchronized Set> getThingClasses() { - return Collections.unmodifiableSet(thingClasses.keySet()); - } - - public synchronized Set getThingsByClass(Class clazz) { - return Collections.unmodifiableSet(thingClasses.get(clazz)); - } - - public synchronized Set getThingsAssignableByClass(Class clazz) { - Set things = new HashSet<>(); - for (Class subclazz : thingClasses.keySet()) { - if (clazz.isAssignableFrom(subclazz)) { - things.addAll(thingClasses.get(subclazz)); - } - - } - return Collections.unmodifiableSet(things); - } - - public synchronized Optional getThingById(String id) { - return Optional.ofNullable(thingIds.get(id)); - } - - public synchronized Set getBridges() { - return Collections.unmodifiableSet(bridges); - } - - public synchronized Set getSchedulers() { - return Collections.unmodifiableSet(schedulers); - } - - public synchronized Set getDeviceNatures() { - return Collections.unmodifiableSet(deviceNatures); - } - - public Optional getChannel(String thingId, String channelId) { - Thing thing = thingIds.get(thingId); - if (thing == null) { - return Optional.empty(); - } - Channel channel = thingChannels.row(thing).get(channelId); - return Optional.ofNullable(channel); - } - - public Optional getChannelByAddress(String address) { - String[] args = address.split("/"); - if (args.length == 2) { - return getChannel(args[0], args[1]); - } - return Optional.empty(); - } - - public Controller createController(JsonObject jController) throws ReflectionException { - String controllerClass = JsonUtils.getAsString(jController, "class"); - Controller controller; - if (jController.has("id")) { - String id = JsonUtils.getAsString(jController, "id"); - controller = (Controller) InjectionUtils.getThingInstance(controllerClass, id); - } else { - controller = (Controller) InjectionUtils.getThingInstance(controllerClass); - } - log.info("Add Controller[" + controller.id() + "], Implementation[" + controller.getClass().getSimpleName() - + "]"); - this.addThing(controller); - ConfigUtils.injectConfigChannels(this.getConfigChannels(controller), jController); - return controller; - } - - public Device createDevice(JsonObject jDevice) throws ReflectionException { - String deviceClass = JsonUtils.getAsString(jDevice, "class"); - Device device = (Device) InjectionUtils.getThingInstance(deviceClass); - log.debug("Add Device[" + device.id() + "], Implementation[" + device.getClass().getSimpleName() + "]"); - this.addThing(device); - ConfigUtils.injectConfigChannels(this.getConfigChannels(device), jDevice); - return device; - } - - @Override - public void thingChannelsUpdated(Thing thing) { - // remove Channels from thingChannels, thingWriteChannels - Databus databus = Databus.getInstance(); - Set> thingRow = thingChannels.row(thing).entrySet(); - Iterator> i = thingRow.iterator(); - while (i.hasNext()) { - Entry thingChannel = i.next(); - if (!(thingChannel.getValue() instanceof ConfigChannel)) { - thingChannel.getValue().removeChangeListener(databus); - thingChannel.getValue().removeUpdateListener(databus); - i.remove(); - } - } - thingWriteChannels.removeAll(thing); - - // Add Channels to thingChannels, thingConfigChannels and thingWriteChannels - Set members = classRepository.getThingChannels(thing.getClass()); - for (Member member : members) { - try { - List channels = new ArrayList<>(); - if (member instanceof Method) { - if (((Method) member).getReturnType().isArray()) { - Channel[] ch = (Channel[]) ((Method) member).invoke(thing); - for (Channel c : ch) { - channels.add(c); - } - } else { - // It's a Method with ReturnType Channel - channels.add((Channel) ((Method) member).invoke(thing)); - } - } else if (member instanceof Field) { - // It's a Field with Type Channel - channels.add((Channel) ((Field) member).get(thing)); - } else { - continue; - } - if (channels.isEmpty()) { - log.error( - "Channel is returning null! Thing [" + thing.id() + "], Member [" + member.getName() + "]"); - continue; - } - for (Channel channel : channels) { - if (channel == null) { - log.error("Channel is returning null! Thing [" + thing.id() + "], Member [" + member.getName() - + "]"); - } else { - // Add Channel to thingChannels - thingChannels.put(thing, channel.id(), channel); - - // Add Channel to writeChannels - if (channel instanceof WriteChannel) { - thingWriteChannels.put(thing, (WriteChannel) channel); - } - - // Register Databus as listener - if (channel instanceof ReadChannel) { - ((ReadChannel) channel).addUpdateListener(databus); - ((ReadChannel) channel).addChangeListener(databus); - } - } - } - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - log.warn("Unable to add Channel. Member [" + member.getName() + "]", e); - } - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.core; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Table; +import com.google.gson.JsonObject; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.controller.Controller; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.exception.ReflectionException; +import io.openems.api.persistence.Persistence; +import io.openems.api.persistence.QueryablePersistence; +import io.openems.api.scheduler.Scheduler; +import io.openems.api.thing.Thing; +import io.openems.api.thing.ThingChannelsUpdatedListener; +import io.openems.core.ThingsChangedListener.Action; +import io.openems.core.utilities.ConfigUtils; +import io.openems.core.utilities.InjectionUtils; +import io.openems.core.utilities.JsonUtils; + +public class ThingRepository implements ThingChannelsUpdatedListener { + private final static Logger log = LoggerFactory.getLogger(ThingRepository.class); + + private static ThingRepository instance; + + public static synchronized ThingRepository getInstance() { + if (ThingRepository.instance == null) { + ThingRepository.instance = new ThingRepository(); + } + return ThingRepository.instance; + } + + private ThingRepository() { + classRepository = ClassRepository.getInstance(); + } + + private final ClassRepository classRepository; + private final BiMap thingIds = HashBiMap.create(); + private HashMultimap, Thing> thingClasses = HashMultimap.create(); + private Set bridges = new HashSet<>(); + // TODO scheduler should not be a set, but only one value + private Set schedulers = new HashSet<>(); + private Set persistences = new HashSet<>(); + private Set queryablePersistences = new HashSet<>(); + private Set deviceNatures = new HashSet<>(); + private final Table thingChannels = HashBasedTable.create(); + private HashMultimap> thingConfigChannels = HashMultimap.create(); + private HashMultimap> thingWriteChannels = HashMultimap.create(); + private List thingListeners = new LinkedList<>(); + + public void addThingChangedListener(ThingsChangedListener listener) { + this.thingListeners.add(listener); + } + + public void removeThingChangedListener(ThingsChangedListener listener) { + this.thingListeners.remove(listener); + } + + /** + * Add a Thing to the Repository and cache its Channels and other information for later usage. + * + * @param thing + */ + public synchronized void addThing(Thing thing) { + if (thingIds.containsValue(thing)) { + // Thing was already added + return; + } + // Add to thingIds + thingIds.forcePut(thing.id(), thing); + + // Add to thingClasses + thingClasses.put(thing.getClass(), thing); + + // Add to bridges + if (thing instanceof Bridge) { + bridges.add((Bridge) thing); + } + + // Add to schedulers + if (thing instanceof Scheduler) { + schedulers.add((Scheduler) thing); + } + + // Add to persistences + if (thing instanceof Persistence) { + persistences.add((Persistence) thing); + } + + // Add to queryablePersistences + if (thing instanceof QueryablePersistence) { + queryablePersistences.add((QueryablePersistence) thing); + } + + // Add to device natures + if (thing instanceof DeviceNature) { + deviceNatures.add((DeviceNature) thing); + } + + // Add Listener + thing.addListener(this); + + // Add Channels thingConfigChannels + Set members = classRepository.getThingChannels(thing.getClass()); + for (Member member : members) { + try { + List channels = new ArrayList<>(); + if (member instanceof Method) { + if (((Method) member).getReturnType().isArray()) { + Channel[] ch = (Channel[]) ((Method) member).invoke(thing); + for (Channel c : ch) { + channels.add(c); + } + } else { + // It's a Method with ReturnType Channel + channels.add((Channel) ((Method) member).invoke(thing)); + } + } else if (member instanceof Field) { + // It's a Field with Type Channel + channels.add((Channel) ((Field) member).get(thing)); + } else { + continue; + } + if (channels.isEmpty()) { + log.error( + "Channel is returning null! Thing [" + thing.id() + "], Member [" + member.getName() + "]"); + continue; + } + for (Channel channel : channels) { + if (channel instanceof ConfigChannel) { + // Add Channel to thingChannels + thingChannels.put(thing, channel.id(), channel); + + // Add Channel to configChannels + thingConfigChannels.put(thing, (ConfigChannel) channel); + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + log.warn("Unable to add Channel. Member [" + member.getName() + "]", e); + } + } + for (ThingsChangedListener listener : thingListeners) { + listener.thingChanged(thing, Action.ADD); + } + } + + /** + * Remove a Thing from the Repository. + * + * @param thing + */ + public synchronized void removeThing(String thingId) { + Thing thing = thingIds.get(thingId); + removeThing(thing); + } + + /** + * Remove a Thing from the Repository. + * + * @param thing + */ + public synchronized void removeThing(Thing thing) { + // Remove from thingIds + thingIds.remove(thing.id()); + + // Remove from thingClasses + thingClasses.remove(thing.getClass(), thing); + + // Remove from bridges + if (thing instanceof Bridge) { + bridges.remove(thing); + } + + // Remove from schedulers + if (thing instanceof Scheduler) { + schedulers.remove(thing); + } + + // Remove from persistences + if (thing instanceof Persistence) { + persistences.remove(thing); + } + + // Remove from queryablePersistences + if (thing instanceof QueryablePersistence) { + queryablePersistences.remove(thing); + } + + // Remove from deviceNatures + if (thing instanceof DeviceNature) { + deviceNatures.remove(thing); + } + + // Remove controller + if (thing instanceof Controller) { + Controller controller = (Controller) thing; + for (Scheduler scheduler : getSchedulers()) { + scheduler.removeController(controller); + } + } + + // Remove device + if (thing instanceof Device) { + for (Bridge bridge : bridges) { + bridge.removeDevice((Device) thing); + } + } + + // Remove Listener + thing.removeListener(this); + // TODO further cleaning if required + for (ThingsChangedListener listener : thingListeners) { + listener.thingChanged(thing, Action.REMOVE); + } + } + + public Thing getThing(String thingId) { + Thing thing = thingIds.get(thingId); + return thing; + } + + public Set getThings() { + return Collections.unmodifiableSet(this.thingIds.values()); + } + + /** + * Returns all Channels for this Thing. + * + * @param thing + * @return + */ + public synchronized Collection getChannels(Thing thing) { + return Collections.unmodifiableCollection(thingChannels.row(thing).values()); + } + + /** + * Returns all Config-Channels. + * + * @return + */ + public synchronized Collection> getConfigChannels() { + return Collections.unmodifiableCollection(thingConfigChannels.values()); + } + + /** + * Returns all Config-Channels for this Thing. + * + * @param thing + * @return + */ + public synchronized Set> getConfigChannels(Thing thing) { + return Collections.unmodifiableSet(thingConfigChannels.get(thing)); + } + + /** + * Returns all Write-Channels for this Thing. + * + * @param thing + * @return + */ + public synchronized Set> getWriteChannels(Thing thing) { + return Collections.unmodifiableSet(thingWriteChannels.get(thing)); + } + + /** + * Returns all Write-Channels. + * + * @param thing + * @return + */ + public synchronized Collection> getWriteChannels() { + return Collections.unmodifiableCollection(thingWriteChannels.values()); + } + + /** + * Returns all Persistence-Workers. + * + * @param thing + * @return + */ + public synchronized Set getPersistences() { + return Collections.unmodifiableSet(persistences); + } + + public synchronized Set getQueryablePersistences() { + return Collections.unmodifiableSet(queryablePersistences); + } + + public synchronized Set> getThingClasses() { + return Collections.unmodifiableSet(thingClasses.keySet()); + } + + public synchronized Set getThingsByClass(Class clazz) { + return Collections.unmodifiableSet(thingClasses.get(clazz)); + } + + public synchronized Set getThingsAssignableByClass(Class clazz) { + Set things = new HashSet<>(); + for (Class subclazz : thingClasses.keySet()) { + if (clazz.isAssignableFrom(subclazz)) { + things.addAll(thingClasses.get(subclazz)); + } + + } + return Collections.unmodifiableSet(things); + } + + public synchronized Optional getThingById(String id) { + return Optional.ofNullable(thingIds.get(id)); + } + + public synchronized Set getBridges() { + return Collections.unmodifiableSet(bridges); + } + + public synchronized Set getSchedulers() { + return Collections.unmodifiableSet(schedulers); + } + + public synchronized Set getDeviceNatures() { + return Collections.unmodifiableSet(deviceNatures); + } + + public Optional getChannel(String thingId, String channelId) { + Thing thing = thingIds.get(thingId); + if (thing == null) { + return Optional.empty(); + } + Channel channel = thingChannels.row(thing).get(channelId); + return Optional.ofNullable(channel); + } + + public Optional getChannelByAddress(String address) { + String[] args = address.split("/"); + if (args.length == 2) { + return getChannel(args[0], args[1]); + } + return Optional.empty(); + } + + public Controller createController(JsonObject jController) throws ReflectionException { + String controllerClass = JsonUtils.getAsString(jController, "class"); + Controller controller; + if (jController.has("id")) { + String id = JsonUtils.getAsString(jController, "id"); + controller = (Controller) InjectionUtils.getThingInstance(controllerClass, id); + } else { + controller = (Controller) InjectionUtils.getThingInstance(controllerClass); + } + log.info("Add Controller[" + controller.id() + "], Implementation[" + controller.getClass().getSimpleName() + + "]"); + this.addThing(controller); + ConfigUtils.injectConfigChannels(this.getConfigChannels(controller), jController); + return controller; + } + + public Device createDevice(JsonObject jDevice, Bridge parent) throws ReflectionException { + String deviceClass = JsonUtils.getAsString(jDevice, "class"); + Device device = (Device) InjectionUtils.getThingInstance(deviceClass, parent); + log.debug("Add Device[" + device.id() + "], Implementation[" + device.getClass().getSimpleName() + "]"); + this.addThing(device); + // instanciate DeviceNatures with Device reference + ConfigUtils.injectConfigChannels(this.getConfigChannels(device), jDevice, device); + return device; + } + + @Override + public void thingChannelsUpdated(Thing thing) { + // remove Channels from thingChannels, thingWriteChannels + Databus databus = Databus.getInstance(); + Set> thingRow = thingChannels.row(thing).entrySet(); + Iterator> i = thingRow.iterator(); + while (i.hasNext()) { + Entry thingChannel = i.next(); + if (!(thingChannel.getValue() instanceof ConfigChannel)) { + thingChannel.getValue().removeChangeListener(databus); + thingChannel.getValue().removeUpdateListener(databus); + i.remove(); + } + } + thingWriteChannels.removeAll(thing); + + // Add Channels to thingChannels, thingConfigChannels and thingWriteChannels + Set members = classRepository.getThingChannels(thing.getClass()); + for (Member member : members) { + try { + List channels = new ArrayList<>(); + if (member instanceof Method) { + if (((Method) member).getReturnType().isArray()) { + Channel[] ch = (Channel[]) ((Method) member).invoke(thing); + for (Channel c : ch) { + channels.add(c); + } + } else { + // It's a Method with ReturnType Channel + channels.add((Channel) ((Method) member).invoke(thing)); + } + } else if (member instanceof Field) { + // It's a Field with Type Channel + channels.add((Channel) ((Field) member).get(thing)); + } else { + continue; + } + if (channels.isEmpty()) { + log.error( + "Channel is returning null! Thing [" + thing.id() + "], Member [" + member.getName() + "]"); + continue; + } + for (Channel channel : channels) { + if (channel == null) { + log.error("Channel is returning null! Thing [" + thing.id() + "], Member [" + member.getName() + + "]"); + } else { + // Add Channel to thingChannels + thingChannels.put(thing, channel.id(), channel); + + // Add Channel to writeChannels + if (channel instanceof WriteChannel) { + thingWriteChannels.put(thing, (WriteChannel) channel); + } + + // Register Databus as listener + if (channel instanceof ReadChannel) { + ((ReadChannel) channel).addUpdateListener(databus); + ((ReadChannel) channel).addChangeListener(databus); + } + } + } + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + log.warn("Unable to add Channel. Member [" + member.getName() + "]", e); + } + } + } +} \ No newline at end of file diff --git a/edge/src/io/openems/core/utilities/AbstractWorker.java b/edge/src/io/openems/core/utilities/AbstractWorker.java index 26e0f5d7600..b2bf770ab25 100644 --- a/edge/src/io/openems/core/utilities/AbstractWorker.java +++ b/edge/src/io/openems/core/utilities/AbstractWorker.java @@ -1,191 +1,191 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.core.utilities; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.thing.Thing; - -public abstract class AbstractWorker extends Thread implements Thing { - private final AtomicBoolean initialize = new AtomicBoolean(true); - private Mutex initializedMutex = new Mutex(false); - private final AtomicBoolean isForceRun = new AtomicBoolean(false); - private final AtomicBoolean isInitialized = new AtomicBoolean(false); - private final AtomicBoolean isStopped = new AtomicBoolean(false); - private long cycleStart = 0; - protected final Logger log; - - /** - * Initialize the Thread with a name - * - * @param name - */ - public AbstractWorker(String name) { - log = LoggerFactory.getLogger(this.getClass()); - this.setName(name); - } - - @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class) - public abstract ConfigChannel cycleTime(); - - public boolean isInitialized() { - return isInitialized.get(); - } - - /** - * Little helper method: Sleep and don't let yourself interrupt by a ForceRun-Flag. It is not making sense anyway, - * because something is wrong with the setup if we landed here. - * - * @param duration - * in seconds - */ - private long bridgeExceptionSleep(long duration) { - if (duration < 60) { - duration += 1; - } - long targetTime = System.nanoTime() + (duration * 1000000); - do { - try { - long thisDuration = (targetTime - System.nanoTime()) / 1000000; - if (thisDuration > 0) { - Thread.sleep(thisDuration); - } - } catch (InterruptedException e1) {} - } while (targetTime > System.nanoTime()); - return duration; - } - - /** - * This method is called when the Thread stops. Use it to close resources. - */ - protected abstract void dispose(); - - /** - * This method is called in a loop forever until the Thread gets interrupted. - */ - protected abstract void forever(); - - @Override - public String id() { - return getName(); - }; - - /** - * This method is called once before {@link forever()} and every time after {@link restart()} method was called. Use - * it to (re)initialize everything. - * - * @return false on initialization error - */ - protected abstract boolean initialize(); - - @Override - public void interrupt() { - // TODO Auto-generated method stub - super.interrupt(); - } - - /** - * Executes the Thread. Calls {@link forever} till the Thread gets interrupted. - */ - @Override - public final void run() { - long bridgeExceptionSleep = 1; // seconds - this.initialize.set(true); - while (!isStopped.get()) { - cycleStart = System.currentTimeMillis(); - try { - /* - * Initialize Bridge - */ - while (initialize.get()) { - boolean initSuccessful = initialize(); - if (initSuccessful) { - isInitialized.set(true); - initializedMutex.release(); - initialize.set(false); - } else { - initializedMutex.awaitOrTimeout(cycleTime().valueOptional().get() * 10, TimeUnit.MILLISECONDS); - } - } - /* - * Call forever() forever. - */ - forever(); - /* - * Wait for next cycle - */ - try { - long sleep = cycleTime().valueOptional().get() - (System.currentTimeMillis() - cycleStart); - if (sleep > 0) { - Thread.sleep(sleep); // TODO add cycle time - } - } catch (InterruptedException e) { - if (isForceRun.get()) { - // check if a "forceRun" was triggereed. In that case Thread.sleep is interrupted and run() is - // starting again immediately - isForceRun.set(false); - } else { - // otherwise forward the exception - isStopped.set(true); - throw e; - } - } - // Everything went ok: reset bridgeExceptionSleep - bridgeExceptionSleep = 1; - } catch (Throwable e) { - /* - * Handle Bridge-Exceptions - */ - log.error("Bridge-Exception! Retry later: ", e); - bridgeExceptionSleep = bridgeExceptionSleep(bridgeExceptionSleep); - } - } - dispose(); - System.out.println("BridgeWorker was interrupted. Exiting gracefully..."); - } - - /** - * Causes the Worker to interrupt sleeping and start again the run() method immediately - */ - public final void triggerForceRun() { - if (!isForceRun.getAndSet(true)) { - this.interrupt(); - } - } - - public void shutdown() { - isStopped.set(true); - } - - /** - * Triggers a call of {@link initialization()} in the next loop. Call this, if the configuration changes. - */ - public final void triggerInitialize() { - initialize.set(true); - initializedMutex.release(); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.core.utilities; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.thing.Thing; + +public abstract class AbstractWorker extends Thread implements Thing { + private final AtomicBoolean initialize = new AtomicBoolean(true); + private Mutex initializedMutex = new Mutex(false); + private final AtomicBoolean isForceRun = new AtomicBoolean(false); + private final AtomicBoolean isInitialized = new AtomicBoolean(false); + private final AtomicBoolean isStopped = new AtomicBoolean(false); + private long cycleStart = 0; + protected final Logger log; + + /** + * Initialize the Thread with a name + * + * @param name + */ + public AbstractWorker(String name) { + log = LoggerFactory.getLogger(this.getClass()); + this.setName(name); + } + + // @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class) + // public abstract ConfigChannel cycleTime(); + + protected abstract int getCycleTime(); + + public boolean isInitialized() { + return isInitialized.get(); + } + + /** + * Little helper method: Sleep and don't let yourself interrupt by a ForceRun-Flag. It is not making sense anyway, + * because something is wrong with the setup if we landed here. + * + * @param duration + * in seconds + */ + private long bridgeExceptionSleep(long duration) { + if (duration < 60) { + duration += 1; + } + long targetTime = System.nanoTime() + (duration * 1000000); + do { + try { + long thisDuration = (targetTime - System.nanoTime()) / 1000000; + if (thisDuration > 0) { + Thread.sleep(thisDuration); + } + } catch (InterruptedException e1) {} + } while (targetTime > System.nanoTime()); + return duration; + } + + /** + * This method is called when the Thread stops. Use it to close resources. + */ + protected abstract void dispose(); + + /** + * This method is called in a loop forever until the Thread gets interrupted. + */ + protected abstract void forever(); + + @Override + public String id() { + return getName(); + }; + + /** + * This method is called once before {@link forever()} and every time after {@link restart()} method was called. Use + * it to (re)initialize everything. + * + * @return false on initialization error + */ + protected abstract boolean initialize(); + + @Override + public void interrupt() { + // TODO Auto-generated method stub + super.interrupt(); + } + + /** + * Executes the Thread. Calls {@link forever} till the Thread gets interrupted. + */ + @Override + public final void run() { + long bridgeExceptionSleep = 1; // seconds + this.initialize.set(true); + while (!isStopped.get()) { + cycleStart = System.currentTimeMillis(); + try { + /* + * Initialize Bridge + */ + while (initialize.get()) { + boolean initSuccessful = initialize(); + if (initSuccessful) { + isInitialized.set(true); + initializedMutex.release(); + initialize.set(false); + } else { + initializedMutex.awaitOrTimeout(getCycleTime() * 10, TimeUnit.MILLISECONDS); + } + } + /* + * Call forever() forever. + */ + forever(); + /* + * Wait for next cycle + */ + try { + long sleep = getCycleTime() - (System.currentTimeMillis() - cycleStart); + if (sleep > 0) { + Thread.sleep(sleep); // TODO add cycle time + } + } catch (InterruptedException e) { + if (isForceRun.get()) { + // check if a "forceRun" was triggereed. In that case Thread.sleep is interrupted and run() is + // starting again immediately + isForceRun.set(false); + } else { + // otherwise forward the exception + isStopped.set(true); + throw e; + } + } + // Everything went ok: reset bridgeExceptionSleep + bridgeExceptionSleep = 1; + } catch (Throwable e) { + /* + * Handle Bridge-Exceptions + */ + log.error("Bridge-Exception! Retry later: ", e); + bridgeExceptionSleep = bridgeExceptionSleep(bridgeExceptionSleep); + } + } + dispose(); + System.out.println("BridgeWorker was interrupted. Exiting gracefully..."); + } + + /** + * Causes the Worker to interrupt sleeping and start again the run() method immediately + */ + public final void triggerForceRun() { + if (!isForceRun.getAndSet(true)) { + this.interrupt(); + } + } + + public void shutdown() { + isStopped.set(true); + } + + /** + * Triggers a call of {@link initialization()} in the next loop. Call this, if the configuration changes. + */ + public final void triggerInitialize() { + initialize.set(true); + initializedMutex.release(); + } +} diff --git a/edge/src/io/openems/core/utilities/ConfigUtils.java b/edge/src/io/openems/core/utilities/ConfigUtils.java index eba22e6dea1..c6e45c8e690 100644 --- a/edge/src/io/openems/core/utilities/ConfigUtils.java +++ b/edge/src/io/openems/core/utilities/ConfigUtils.java @@ -1,381 +1,394 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.core.utilities; - -import java.io.IOException; -import java.lang.reflect.Field; -import java.lang.reflect.Member; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.net.Inet4Address; -import java.net.UnknownHostException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.reflect.ClassPath; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonPrimitive; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.controller.ThingMap; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigChannelDoc; -import io.openems.api.doc.ThingDoc; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.NotImplementedException; -import io.openems.api.exception.ReflectionException; -import io.openems.api.thing.Thing; -import io.openems.core.ClassRepository; -import io.openems.core.ThingRepository; - -public class ConfigUtils { - private final static Logger log = LoggerFactory.getLogger(ConfigUtils.class); - - /** - * Fill all Config-Channels from a JsonObject configuration - * - * @param channels - * @param jConfig - * @throws ConfigException - */ - public static void injectConfigChannels(Set> channels, JsonObject jConfig) - throws ReflectionException { - for (ConfigChannel channel : channels) { - if (!jConfig.has(channel.id()) && (channel.valueOptional().isPresent() || channel.isOptional())) { - // Element for this Channel is not existing existing in the configuration, but a default value was set - continue; - } - JsonElement jChannel = JsonUtils.getSubElement(jConfig, channel.id()); - Object parameter = getConfigObject(channel, jChannel); - channel.updateValue(parameter, true); - } - } - - /** - * Converts an object to a JsonElement - * - * @param value - * @return - * @throws NotImplementedException - */ - public static JsonElement getAsJsonElement(Object value) throws NotImplementedException { - return getAsJsonElement(value, false); - } - - /** - * Converts an object to a JsonElement - * - * @param value - * @return - * @throws NotImplementedException - */ - public static JsonElement getAsJsonElement(Object value, boolean includeEverything) throws NotImplementedException { - // null - if (value == null) { - return null; - } - // optional - if (value instanceof Optional) { - if (!((Optional) value).isPresent()) { - return null; - } else { - value = ((Optional) value).get(); - } - } - try { - /* - * test for simple types - */ - return JsonUtils.getAsJsonElement(value); - } catch (NotImplementedException e) { - ; - } - if (value instanceof Thing) { - /* - * type Thing - */ - Thing thing = (Thing) value; - JsonObject j = new JsonObject(); - if (includeEverything || !thing.id().startsWith("_")) { - // ignore generated id names starting with "_" - j.addProperty("id", thing.id()); - } - if (!(value instanceof DeviceNature)) { - // class is not needed for DeviceNatures - j.addProperty("class", thing.getClass().getCanonicalName()); - } - ThingRepository thingRepository = ThingRepository.getInstance(); - for (ConfigChannel channel : thingRepository.getConfigChannels(thing)) { - JsonElement jChannel = ConfigUtils.getAsJsonElement(channel, includeEverything); - if (jChannel != null) { - j.add(channel.id(), jChannel); - } - } - return j; - } else if (value instanceof ConfigChannel) { - /* - * type ConfigChannel - */ - ConfigChannel channel = (ConfigChannel) value; - if (!channel.valueOptional().isPresent()) { - // no value set - return null; - } else if (!includeEverything && channel.getDefaultValue().equals(channel.valueOptional())) { - // default value not changed - return null; - } else { - // recursive call - return ConfigUtils.getAsJsonElement(channel.valueOptional().get(), includeEverything); - } - } else if (value instanceof ThingMap) { - /* - * ThingMap (we need only id) - */ - return new JsonPrimitive(((ThingMap) value).id()); - } else if (value instanceof List) { - /* - * List - */ - JsonArray jArray = new JsonArray(); - for (Object v : (List) value) { - jArray.add(ConfigUtils.getAsJsonElement(v, includeEverything)); - } - return jArray; - } else if (value instanceof Set) { - /* - * Set - */ - JsonArray jArray = new JsonArray(); - for (Object v : (Set) value) { - jArray.add(ConfigUtils.getAsJsonElement(v, includeEverything)); - } - return jArray; - } - throw new NotImplementedException("Converter for [" + value + "]" + " of type [" // - + value.getClass().getSimpleName() + "]" // - + " to JSON is not implemented."); - } - - /** - * Receives a matching value for the ConfigChannel from a JsonElement - * - * @param channel - * @param j - * @return - * @throws ReflectionException - */ - private static Object getConfigObject(ConfigChannel channel, JsonElement j) throws ReflectionException { - Optional> typeOptional = channel.type(); - if (!typeOptional.isPresent()) { - String clazz = channel.parent() != null ? " in implementation [" + channel.parent().getClass() + "]" : ""; - throw new ReflectionException("Type is null for channel [" + channel.address() + "]" + clazz); - } - Class type = typeOptional.get(); - - /* - * test for simple types - */ - try { - return JsonUtils.getAsType(type, j); - } catch (NotImplementedException e1) { - ; - } - - if (Thing.class.isAssignableFrom(type)) { - /* - * Asking for a Thing - */ - return getThingFromConfig((Class) type, j); - - } else if (ThingMap.class.isAssignableFrom(type)) { - /* - * Asking for a ThingMap - */ - return InjectionUtils.getThingMapsFromConfig(channel, j); - - } else if (Inet4Address.class.isAssignableFrom(type)) { - /* - * Asking for an IPv4 - */ - try { - return Inet4Address.getByName(j.getAsString()); - } catch (UnknownHostException e) { - throw new ReflectionException("Unable to convert [" + j + "] to IPv4 address"); - } - } else if (Long[].class.isAssignableFrom(type)) { - /* - * Asking for an Array of Long - */ - return getLongArrayFromConfig(channel, j); - } - throw new ReflectionException("Unable to match config [" + j + "] to class type [" + type + "]"); - } - - private static Thing getThingFromConfig(Class type, JsonElement j) throws ReflectionException { - String thingId = JsonUtils.getAsString(j, "id"); - ThingRepository thingRepository = ThingRepository.getInstance(); - Optional existingThing = thingRepository.getThingById(thingId); - Thing thing; - if (existingThing.isPresent()) { - // reuse existing Thing - thing = existingThing.get(); - } else { - // Thing is not existing. Create a new instance - thing = InjectionUtils.getThingInstance(type, thingId); - log.debug("Add Thing[" + thing.id() + "], Implementation[" + thing.getClass().getSimpleName() + "]"); - thingRepository.addThing(thing); - } - // Recursive call to inject config parameters for the newly created Thing - injectConfigChannels(thingRepository.getConfigChannels(thing), j.getAsJsonObject()); - thing.init(); - return thing; - } - - private static Object getLongArrayFromConfig(ConfigChannel channel, JsonElement j) throws ReflectionException { - /* - * Get "Field" in Channels parent class - */ - Field field; - try { - field = channel.parent().getClass().getField(channel.id()); - } catch (NoSuchFieldException | SecurityException e) { - throw new ReflectionException("Field for ConfigChannel [" + channel.address() + "] is not named [" - + channel.id() + "] in [" + channel.getClass().getSimpleName() + "]"); - } - - /* - * Get expected Object Type (List, Set, simple Object) - */ - Type expectedObjectType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; - if (expectedObjectType instanceof ParameterizedType) { - expectedObjectType = ((ParameterizedType) expectedObjectType).getRawType(); - } - Class expectedObjectClass = (Class) expectedObjectType; - - if (Collection.class.isAssignableFrom(expectedObjectClass)) { - if (j.isJsonArray()) { - Set erg = new HashSet<>(); - for (JsonElement e : j.getAsJsonArray()) { - if (e.isJsonArray()) { - JsonArray arr = e.getAsJsonArray(); - Long[] larr = new Long[arr.size()]; - for (int i = 0; i < arr.size(); i++) { - larr[i] = arr.get(i).getAsLong(); - } - erg.add(larr); - } else { - throw new ReflectionException("The Json object for ConfigChannel [" + channel.address() - + "] is no twodimensional array!"); - } - } - if (Set.class.isAssignableFrom(expectedObjectClass)) { - return erg; - } else if (List.class.isAssignableFrom(expectedObjectClass)) { - return new ArrayList<>(erg); - } else { - throw new ReflectionException("Only List and Set ConfigChannels are currently implemented, not [" - + expectedObjectClass + "]. ConfigChannel [" + channel.address() + "]"); - } - } else { - throw new ReflectionException( - "The Json object for ConfigChannel [" + channel.address() + "] is no array!"); - } - } else { - if (j.isJsonArray()) { - JsonArray arr = j.getAsJsonArray(); - Long[] larr = new Long[arr.size()]; - for (int i = 0; i < arr.size(); i++) { - larr[i] = arr.get(i).getAsLong(); - } - return larr; - } else { - throw new ReflectionException( - "The Json object for ConfigChannel [" + channel.address() + "] is no array!"); - } - } - } - - public static ThingDoc getThingDescription(Class clazz) { - ThingDoc doc = new ThingDoc(clazz); - - ThingInfo thing = clazz.getAnnotation(ThingInfo.class); - if (thing == null) { - log.warn("Thing [" + clazz.getName() + "] has no @ThingInfo annotation"); - } else { - doc.setThingDescription(thing); - } - ClassRepository classRepository = ClassRepository.getInstance(); - classRepository.getThingConfigChannels(clazz).forEach((member, config) -> { - doc.addConfigChannel(new ConfigChannelDoc(member.getName(), config.title(), config.type(), - config.isOptional(), config.isArray(), config.accessLevel())); - }); - return doc; - } - - public static Set> getAvailableClasses(String topLevelPackage, Class clazz, - String suffix) throws ReflectionException { - Set> clazzes = new HashSet<>(); - try { - ClassPath classpath = ClassPath.from(ClassLoader.getSystemClassLoader()); - for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClassesRecursive(topLevelPackage)) { - if (classInfo.getName().endsWith(suffix)) { - Class thisClazz = classInfo.load(); - if (clazz.isAssignableFrom(thisClazz)) { - clazzes.add((Class) thisClazz); - } - } - } - } catch (IllegalArgumentException | IOException e) { - throw new ReflectionException(e.getMessage()); - } - return clazzes; - } - - /** - * Get all declared members of thing class. - * - * @param clazz - * @return - */ - public static List getMembers(Class clazz) { - List members = new LinkedList<>(); - for (Method method : clazz.getMethods()) { - members.add(method); - } - for (Field field : clazz.getFields()) { - members.add(field); - } - return Collections.unmodifiableList(members); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.core.utilities; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Member; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.Inet4Address; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.reflect.ClassPath; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.ThingMap; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigChannelDoc; +import io.openems.api.doc.ThingDoc; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.NotImplementedException; +import io.openems.api.exception.ReflectionException; +import io.openems.api.thing.Thing; +import io.openems.core.ClassRepository; +import io.openems.core.ConfigFormat; +import io.openems.core.ThingRepository; + +public class ConfigUtils { + private final static Logger log = LoggerFactory.getLogger(ConfigUtils.class); + + /** + * Fill all Config-Channels from a JsonObject configuration + * + * @param channels + * @param jConfig + * @throws ConfigException + */ + public static void injectConfigChannels(Set> channels, JsonObject jConfig, Object... args) + throws ReflectionException { + for (ConfigChannel channel : channels) { + if (!jConfig.has(channel.id()) && (channel.valueOptional().isPresent() || channel.isOptional())) { + // Element for this Channel is not existing existing in the configuration, but a default value was set + continue; + } + JsonElement jChannel = JsonUtils.getSubElement(jConfig, channel.id()); + Object parameter = getConfigObject(channel, jChannel, args); + channel.updateValue(parameter, true); + } + } + + /** + * Converts an object to a JsonElement + * + * @param value + * @return + * @throws NotImplementedException + */ + public static JsonElement getAsJsonElement(Object value, ConfigFormat format) throws NotImplementedException { + // null + if (value == null) { + return null; + } + // optional + if (value instanceof Optional) { + if (!((Optional) value).isPresent()) { + return null; + } else { + value = ((Optional) value).get(); + } + } + try { + /* + * test for simple types + */ + return JsonUtils.getAsJsonElement(value); + } catch (NotImplementedException e) { + ; + } + if (value instanceof Thing) { + /* + * type Thing + */ + Thing thing = (Thing) value; + JsonObject j = new JsonObject(); + if (format == ConfigFormat.OPENEMS_UI || !thing.id().startsWith("_")) { + // ignore generated id names starting with "_" + j.addProperty("id", thing.id()); + } + if (format == ConfigFormat.OPENEMS_UI && value instanceof DeviceNature) { + j.add("class", InjectionUtils.getImplementsAsJson(thing.getClass())); + } else { + // class is not needed for DeviceNatures + j.addProperty("class", thing.getClass().getCanonicalName()); + } + ThingRepository thingRepository = ThingRepository.getInstance(); + for (ConfigChannel channel : thingRepository.getConfigChannels(thing)) { + JsonElement jChannel = null; + if (format == ConfigFormat.FILE) { + jChannel = ConfigUtils.getAsJsonElement(channel, format); + + } else if (format == ConfigFormat.OPENEMS_UI) { + Optional> channelTypeOpt = channel.type(); + if (channelTypeOpt.isPresent()) { + Class channelType = channelTypeOpt.get(); + if (DeviceNature.class.isAssignableFrom(channelType)) { + // ignore + } else { + jChannel = ConfigUtils.getAsJsonElement(channel, format); + } + } + } + if (jChannel != null) { + j.add(channel.id(), jChannel); + } + } + return j; + } else if (value instanceof ConfigChannel) { + /* + * type ConfigChannel + */ + ConfigChannel channel = (ConfigChannel) value; + if (!channel.valueOptional().isPresent()) { + // no value set + return null; + } else if (format == ConfigFormat.FILE && channel.getDefaultValue().equals(channel.valueOptional())) { + // default value not changed + return null; + } else { + // recursive call + return ConfigUtils.getAsJsonElement(channel.valueOptional().get(), format); + } + } else if (value instanceof ThingMap) { + /* + * ThingMap (we need only id) + */ + return new JsonPrimitive(((ThingMap) value).id()); + } else if (value instanceof List) { + /* + * List + */ + JsonArray jArray = new JsonArray(); + for (Object v : (List) value) { + jArray.add(ConfigUtils.getAsJsonElement(v, format)); + } + return jArray; + } else if (value instanceof Set) { + /* + * Set + */ + JsonArray jArray = new JsonArray(); + for (Object v : (Set) value) { + jArray.add(ConfigUtils.getAsJsonElement(v, format)); + } + return jArray; + } + throw new NotImplementedException("Converter for [" + value + "]" + " of type [" // + + value.getClass().getSimpleName() + "]" // + + " to JSON is not implemented."); + } + + /** + * Receives a matching value for the ConfigChannel from a JsonElement + * + * @param channel + * @param j + * @return + * @throws ReflectionException + */ + private static Object getConfigObject(ConfigChannel channel, JsonElement j, Object... args) + throws ReflectionException { + Optional> typeOptional = channel.type(); + if (!typeOptional.isPresent()) { + String clazz = channel.parent() != null ? " in implementation [" + channel.parent().getClass() + "]" : ""; + throw new ReflectionException("Type is null for channel [" + channel.address() + "]" + clazz); + } + Class type = typeOptional.get(); + + /* + * test for simple types + */ + try { + return JsonUtils.getAsType(type, j); + } catch (NotImplementedException e1) { + ; + } + + if (Thing.class.isAssignableFrom(type)) { + /* + * Asking for a Thing + */ + return getThingFromConfig((Class) type, j, args); + + } else if (ThingMap.class.isAssignableFrom(type)) { + /* + * Asking for a ThingMap + */ + return InjectionUtils.getThingMapsFromConfig(channel, j); + + } else if (Inet4Address.class.isAssignableFrom(type)) { + /* + * Asking for an IPv4 + */ + try { + return Inet4Address.getByName(j.getAsString()); + } catch (UnknownHostException e) { + throw new ReflectionException("Unable to convert [" + j + "] to IPv4 address"); + } + } else if (Long[].class.isAssignableFrom(type)) { + /* + * Asking for an Array of Long + */ + return getLongArrayFromConfig(channel, j); + } + throw new ReflectionException("Unable to match config [" + j + "] to class type [" + type + "]"); + } + + private static Thing getThingFromConfig(Class type, JsonElement j, Object... objects) + throws ReflectionException { + String thingId = JsonUtils.getAsString(j, "id"); + ThingRepository thingRepository = ThingRepository.getInstance(); + Optional existingThing = thingRepository.getThingById(thingId); + Thing thing; + if (existingThing.isPresent()) { + // reuse existing Thing + thing = existingThing.get(); + } else { + // Thing is not existing. Create a new instance + Object[] args = new Object[objects.length + 1]; + args[0] = thingId; + for (int i = 1; i < objects.length + 1; i++) { + args[i] = objects[i - 1]; + } + thing = InjectionUtils.getThingInstance(type, args); + log.debug("Add Thing[" + thing.id() + "], Implementation[" + thing.getClass().getSimpleName() + "]"); + thingRepository.addThing(thing); + } + // Recursive call to inject config parameters for the newly created Thing + injectConfigChannels(thingRepository.getConfigChannels(thing), j.getAsJsonObject()); + // thing.init(); + return thing; + } + + private static Object getLongArrayFromConfig(ConfigChannel channel, JsonElement j) throws ReflectionException { + /* + * Get "Field" in Channels parent class + */ + Field field; + try { + field = channel.parent().getClass().getField(channel.id()); + } catch (NoSuchFieldException | SecurityException e) { + throw new ReflectionException("Field for ConfigChannel [" + channel.address() + "] is not named [" + + channel.id() + "] in [" + channel.getClass().getSimpleName() + "]"); + } + + /* + * Get expected Object Type (List, Set, simple Object) + */ + Type expectedObjectType = ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0]; + if (expectedObjectType instanceof ParameterizedType) { + expectedObjectType = ((ParameterizedType) expectedObjectType).getRawType(); + } + Class expectedObjectClass = (Class) expectedObjectType; + + if (Collection.class.isAssignableFrom(expectedObjectClass)) { + if (j.isJsonArray()) { + Set erg = new HashSet<>(); + for (JsonElement e : j.getAsJsonArray()) { + if (e.isJsonArray()) { + JsonArray arr = e.getAsJsonArray(); + Long[] larr = new Long[arr.size()]; + for (int i = 0; i < arr.size(); i++) { + larr[i] = arr.get(i).getAsLong(); + } + erg.add(larr); + } else { + throw new ReflectionException("The Json object for ConfigChannel [" + channel.address() + + "] is no twodimensional array!"); + } + } + if (Set.class.isAssignableFrom(expectedObjectClass)) { + return erg; + } else if (List.class.isAssignableFrom(expectedObjectClass)) { + return new ArrayList<>(erg); + } else { + throw new ReflectionException("Only List and Set ConfigChannels are currently implemented, not [" + + expectedObjectClass + "]. ConfigChannel [" + channel.address() + "]"); + } + } else { + throw new ReflectionException( + "The Json object for ConfigChannel [" + channel.address() + "] is no array!"); + } + } else { + if (j.isJsonArray()) { + JsonArray arr = j.getAsJsonArray(); + Long[] larr = new Long[arr.size()]; + for (int i = 0; i < arr.size(); i++) { + larr[i] = arr.get(i).getAsLong(); + } + return larr; + } else { + throw new ReflectionException( + "The Json object for ConfigChannel [" + channel.address() + "] is no array!"); + } + } + } + + public static ThingDoc getThingDescription(Class clazz) { + ThingDoc doc = new ThingDoc(clazz); + + ThingInfo thing = clazz.getAnnotation(ThingInfo.class); + if (thing == null) { + log.warn("Thing [" + clazz.getName() + "] has no @ThingInfo annotation"); + } else { + doc.setThingDescription(thing); + } + ClassRepository classRepository = ClassRepository.getInstance(); + classRepository.getThingConfigChannels(clazz).forEach((member, config) -> { + doc.addConfigChannel(new ConfigChannelDoc(member.getName(), config.title(), config.type(), + config.isOptional(), config.isArray(), config.accessLevel())); + }); + return doc; + } + + public static Set> getAvailableClasses(String topLevelPackage, Class clazz, + String suffix) throws ReflectionException { + Set> clazzes = new HashSet<>(); + try { + ClassPath classpath = ClassPath.from(ClassLoader.getSystemClassLoader()); + for (ClassPath.ClassInfo classInfo : classpath.getTopLevelClassesRecursive(topLevelPackage)) { + if (classInfo.getName().endsWith(suffix)) { + Class thisClazz = classInfo.load(); + if (clazz.isAssignableFrom(thisClazz)) { + clazzes.add((Class) thisClazz); + } + } + } + } catch (IllegalArgumentException | IOException e) { + throw new ReflectionException(e.getMessage()); + } + return clazzes; + } + + /** + * Get all declared members of thing class. + * + * @param clazz + * @return + */ + public static List getMembers(Class clazz) { + List members = new LinkedList<>(); + for (Method method : clazz.getMethods()) { + members.add(method); + } + for (Field field : clazz.getFields()) { + members.add(field); + } + return Collections.unmodifiableList(members); + } +} \ No newline at end of file diff --git a/edge/src/io/openems/core/utilities/InjectionUtils.java b/edge/src/io/openems/core/utilities/InjectionUtils.java index c9fcdfbc02d..8701da8a0c8 100644 --- a/edge/src/io/openems/core/utilities/InjectionUtils.java +++ b/edge/src/io/openems/core/utilities/InjectionUtils.java @@ -37,6 +37,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import io.openems.api.channel.Channel; @@ -263,24 +264,41 @@ public static Object getThingMapsFromConfig(ConfigChannel channel, JsonElemen * @param clazz * @return */ - public static Set> getImportantNatureInterfaces(Class clazz) { - Set> ifaces = new HashSet<>(); - if (clazz == null // at the top - || clazz.equals(DeviceNature.class) // we are at the DeviceNature interface - || !DeviceNature.class.isAssignableFrom(clazz) // clazz is not derived from DeviceNature - ) { + public static Set> getImplements(Class clazz) { + Set> ifaces = new HashSet<>(); + // stop at certain classes + if (clazz == null || clazz.equals(Thing.class) || clazz.equals(AbstractWorker.class) + || clazz.equals(DeviceNature.class)) { return ifaces; } // myself ifaces.add(clazz); // super interfaces for (Class iface : clazz.getInterfaces()) { - if (DeviceNature.class.isAssignableFrom(iface)) { - ifaces.addAll(getImportantNatureInterfaces(iface)); + if (Thing.class.isAssignableFrom(iface)) { + Class thingIface = (Class) iface; + ifaces.addAll(getImplements(thingIface)); } } // super classes - ifaces.addAll(getImportantNatureInterfaces(clazz.getSuperclass())); + Class superclazz = clazz.getSuperclass(); + if (superclazz != null && Thing.class.isAssignableFrom(superclazz)) { + Class thingSuperclazz = (Class) superclazz; + ifaces.addAll(getImplements(thingSuperclazz)); + } return ifaces; } + + public static JsonArray getImplementsAsJson(Class clazz) { + JsonArray j = new JsonArray(); + for (Class implement : InjectionUtils.getImplements(clazz)) { + if (DeviceNature.class.isAssignableFrom(clazz)) { + // use simple name for DeviceNatures for readability + j.add(implement.getSimpleName()); + } else { + j.add(implement.getCanonicalName()); + } + } + return j; + } } diff --git a/edge/src/io/openems/core/utilities/JsonUtils.java b/edge/src/io/openems/core/utilities/JsonUtils.java index 7330286d6f6..e2a2be4a390 100644 --- a/edge/src/io/openems/core/utilities/JsonUtils.java +++ b/edge/src/io/openems/core/utilities/JsonUtils.java @@ -191,6 +191,15 @@ public static JsonElement getAsJsonElement(Object value) throws NotImplementedEx * JsonElement */ return (JsonElement) value; + } else if (value instanceof Long[]){ + /* + * Long-Array + */ + JsonArray js = new JsonArray(); + for (Long l : (Long[]) value){ + js.add(new JsonPrimitive((Long) l)); + } + return js; } throw new NotImplementedException("Converter for [" + value + "]" + " of type [" // + value.getClass().getSimpleName() + "]" // @@ -233,7 +242,6 @@ public static Object getAsType(Class type, JsonElement j) throws NotImplement * Asking for a String */ return j.getAsString(); - } else if (JsonObject.class.isAssignableFrom(type)) { /* * Asking for a JsonObject @@ -244,6 +252,24 @@ public static Object getAsType(Class type, JsonElement j) throws NotImplement * Asking for a JsonArray */ return j.getAsJsonArray(); + } else if (type.isArray()){ + /** + * Asking for Array + */ + if(Long.class.isAssignableFrom(type.getComponentType())){ + /** + * Asking for ArrayOfLong + */ + if(j.isJsonArray()){ + JsonArray js = j.getAsJsonArray(); + Long[] la = new Long[js.size()]; + for(int i = 0; i < js.size(); i++){ + la[i] = js.get(i).getAsLong(); + } + return la; + } + + } } } catch (IllegalStateException e) { throw new IllegalStateException("Failed to parse JsonElement [" + j + "]", e); diff --git a/edge/src/io/openems/core/utilities/websocket/AuthenticatedWebsocketHandler.java b/edge/src/io/openems/core/utilities/websocket/AuthenticatedWebsocketHandler.java deleted file mode 100644 index 2701953c4a7..00000000000 --- a/edge/src/io/openems/core/utilities/websocket/AuthenticatedWebsocketHandler.java +++ /dev/null @@ -1,189 +0,0 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.core.utilities.websocket; - -import org.java_websocket.WebSocket; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import io.openems.api.exception.ReflectionException; -import io.openems.api.security.Authentication; -import io.openems.api.security.Session; -import io.openems.core.utilities.JsonUtils; -import io.openems.impl.controller.api.websocket.WebsocketApiController; - -/** - * Extends {@link WebsocketHandler} with authentication functionality, like session token - * - * @author stefan.feilmeier - * - */ -public class AuthenticatedWebsocketHandler extends WebsocketHandler { - - private static Logger log = LoggerFactory.getLogger(AuthenticatedWebsocketHandler.class); - - /** - * Holds the authenticated session - */ - private Session session = null; - - public AuthenticatedWebsocketHandler(WebSocket websocket, WebsocketApiController controller) { - super(websocket, controller); - } - - public boolean authenticationIsValid() { - if (this.session != null && this.session.isValid()) { - return true; - } - return false; - } - - /** - * Message event of websocket. Handles a new message. - */ - @Override - public void onMessage(JsonObject jMessage) { - // log.info(jMessage.toString()); - /* - * Authenticate user and send immediate reply - */ - if (jMessage.has("authenticate")) { - authenticate(jMessage.get("authenticate")); - } - - /* - * Check authentication - */ - if (!authenticationIsValid()) { - // no user authenticated till now -> exit - sendConnectionFailedReply(); - this.websocket.close(); - return; - } - - /* - * Send message on initial call - */ - if (jMessage.has("authenticate")) { - sendConnectionSuccessfulReply(); - } - - /* - * Rest -> forward to super class - */ - super.onMessage(jMessage); - } - - /** - * Authenticates a user according to the "authenticate" message. Stores the session if valid. - * - * @param jAuthenticateElement - * @param handler - */ - private void authenticate(JsonElement jAuthenticateElement) { - Authentication auth = Authentication.getInstance(); - try { - JsonObject jAuthenticate = JsonUtils.getAsJsonObject(jAuthenticateElement); - if (jAuthenticate.has("mode")) { - String mode = JsonUtils.getAsString(jAuthenticate, "mode"); - if (mode.equals("login")) { - if (jAuthenticate.has("password")) { - /* - * Authenticate using username and password - */ - String password = JsonUtils.getAsString(jAuthenticate, "password"); - if (jAuthenticate.has("username")) { - String username = JsonUtils.getAsString(jAuthenticate, "username"); - this.session = auth.byUserPassword(username, password); - } else { - this.session = auth.byPassword(password); - } - } else if (jAuthenticate.has("token")) { - /* - * Authenticate using session token - */ - String token = JsonUtils.getAsString(jAuthenticate, "token"); - this.session = auth.bySession(token); - } - } - } - } catch (ReflectionException e) { /* ignore */ } - } - - /** - * Creates an initial message to the browser after it was successfully connected and authenticated - * - *
-	 * {
-	 *   authenticate: {
-	 *     mode: "allow",
-	 *     [token: "...",]
-	 *     [username: "..."]
-	 *   }, metadata: {
-	 *     devices: [{
-	 *       name: {...},
-	 *       config: {...}
-	 *       online: true
-	 *     }],
-	 *     backend: "openems"
-	 *   }
-	 * }
-	 * 
- * - * @param handler - */ - @Override - protected JsonObject createConnectionSuccessfulReply() { - JsonObject j = super.createConnectionSuccessfulReply(); - - // Authentication data - JsonObject jAuthenticate = new JsonObject(); - jAuthenticate.addProperty("mode", "allow"); - jAuthenticate.addProperty("username", this.session.getUser().getName()); - jAuthenticate.addProperty("token", this.session.getToken()); - j.add("authenticate", jAuthenticate); - - return j; - } - - @Override - protected JsonObject createConnectionFailedReply() { - JsonObject j = super.createConnectionFailedReply(); - - return j; - } - - /** - * Gets the user name of this user, avoiding null - * - * @param conn - * @return - */ - public String getUserName() { - if (session != null && session.getUser() != null) { - return session.getUser().getName(); - } - return "NOT_CONNECTED"; - } -} diff --git a/edge/src/io/openems/core/utilities/websocket/CurrentDataWorker.java b/edge/src/io/openems/core/utilities/websocket/CurrentDataWorker.java new file mode 100644 index 00000000000..6934eda6751 --- /dev/null +++ b/edge/src/io/openems/core/utilities/websocket/CurrentDataWorker.java @@ -0,0 +1,89 @@ +package io.openems.core.utilities.websocket; + +import java.util.Optional; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.HashMultimap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.api.exception.NotImplementedException; +import io.openems.common.websocket.DefaultMessages; +import io.openems.common.websocket.WebSocketUtils; +import io.openems.core.Databus; +import io.openems.core.utilities.JsonUtils; + +public class CurrentDataWorker { + + private final static int UPDATE_INTERVAL_IN_SECONDS = 2; + + private Logger log = LoggerFactory.getLogger(CurrentDataWorker.class); + + /** + * Executor for subscriptions task + */ + private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); + + /** + * Holds thingId and channelId, subscribed by this websocket + */ + private final HashMultimap channels; + + /** + * Holds the scheduled task for currentData + */ + private final ScheduledFuture future; + + public CurrentDataWorker(JsonArray jId, HashMultimap channels, + EdgeWebsocketHandler edgeWebsocketHandler) { + this.channels = channels; + this.future = this.executor.scheduleWithFixedDelay(() -> { + /* + * This task is executed regularly. Sends data to websocket. + */ + if (!edgeWebsocketHandler.getWebsocket().isOpen()) { + // disconnected; stop worker + this.dispose(); + return; + } + WebSocketUtils.send(edgeWebsocketHandler.getWebsocket(), + DefaultMessages.currentData(jId, getSubscribedData())); + }, 0, UPDATE_INTERVAL_IN_SECONDS, TimeUnit.SECONDS); + } + + public void dispose() { + // unsubscribe regular task + future.cancel(true); + } + + /** + * Gets a json object with all subscribed channels + * + * @return + */ + private JsonObject getSubscribedData() { + Databus databus = Databus.getInstance(); + JsonObject jData = new JsonObject(); + for (String thingId : this.channels.keys()) { + JsonObject jThingData = new JsonObject(); + for (String channelId : this.channels.get(thingId)) { + Optional value = databus.getValue(thingId, channelId); + try { + JsonElement jValue = JsonUtils.getAsJsonElement(value.orElse(null)); + jThingData.add(channelId, jValue); + } catch (NotImplementedException e) { + log.error(e.getMessage()); + } + } + jData.add(thingId, jThingData); + } + return jData; + } +} diff --git a/edge/src/io/openems/core/utilities/websocket/EdgeWebsocketHandler.java b/edge/src/io/openems/core/utilities/websocket/EdgeWebsocketHandler.java new file mode 100644 index 00000000000..eaa719623a3 --- /dev/null +++ b/edge/src/io/openems/core/utilities/websocket/EdgeWebsocketHandler.java @@ -0,0 +1,513 @@ +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.core.utilities.websocket; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import org.java_websocket.WebSocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.HashMultimap; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.api.persistence.QueryablePersistence; +import io.openems.common.api.TimedataSource; +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.websocket.DefaultMessages; +import io.openems.common.websocket.WebSocketUtils; +import io.openems.core.Config; +import io.openems.core.ConfigFormat; +import io.openems.core.ThingRepository; + +/** + * Handles a Websocket connection to a browser, OpenEMS backend,... + * + * @author stefan.feilmeier + */ +public class EdgeWebsocketHandler { + + private Logger log = LoggerFactory.getLogger(EdgeWebsocketHandler.class); + + /** + * Holds the websocket connection + */ + protected WebSocket websocket; + + /** + * Holds subscribers to current data + */ + private final HashMap currentDataSubscribers = new HashMap<>(); + + /** + * Holds subscribers to system log + */ + private final Set logSubscribers = new HashSet<>(); + + /** + * Executor for system log task + */ + private final ExecutorService logExecutor = Executors.newCachedThreadPool(); + + public EdgeWebsocketHandler(WebSocket websocket) { + this.websocket = websocket; + } + + public void setWebsocket(WebSocket websocket) { + this.websocket = websocket; + } + + public WebSocket getWebsocket() { + return websocket; + } + + /** + * Handles a message from Websocket. + * + * @param jMessage + */ + public final void onMessage(JsonObject jMessage) { + // get message id -> used for reply + Optional jIdOpt = JsonUtils.getAsOptionalJsonArray(jMessage, "id"); + + // prepare reply (every reply is going to be merged into this object with this unique message id) + JsonObject jReply = new JsonObject(); + + // init deviceId as empty. It's only needed in backend + Optional deviceIdOpt = Optional.empty(); + + /* + * Config + */ + Optional jConfigOpt = JsonUtils.getAsOptionalJsonObject(jMessage, "config"); + if (jConfigOpt.isPresent()) { + jReply = JsonUtils.merge(jReply, // + config(jConfigOpt.get()) // + ); + } + + /* + * Subscribe to currentData + */ + Optional jCurrentDataOpt = JsonUtils.getAsOptionalJsonObject(jMessage, "currentData"); + if (jCurrentDataOpt.isPresent() && jIdOpt.isPresent()) { + jReply = JsonUtils.merge(jReply, // + currentData(jIdOpt.get(), jCurrentDataOpt.get()) // + ); + } + + /* + * Query historic data + */ + // Optional jMessageIdOpt, String deviceName, WebSocket websocket, JsonElement jHistoricDataElement + Optional jhistoricDataOpt = JsonUtils.getAsOptionalJsonObject(jMessage, "historicData"); + if (jhistoricDataOpt.isPresent() && jIdOpt.isPresent()) { + // select first QueryablePersistence (by default the running InfluxdbPersistence) + TimedataSource timedataSource = null; + for (QueryablePersistence queryablePersistence : ThingRepository.getInstance().getQueryablePersistences()) { + timedataSource = queryablePersistence; + break; + } + if (timedataSource == null) { + // TODO create notification that there is no datasource available + } else { + jReply = JsonUtils.merge(jReply, // + WebSocketUtils.historicData(jIdOpt.get(), jhistoricDataOpt.get(), deviceIdOpt, timedataSource) // + ); + } + } + + /* + * Subscribe to log + */ + Optional jLogOpt = JsonUtils.getAsOptionalJsonObject(jMessage, "log"); + if (jLogOpt.isPresent() && jIdOpt.isPresent()) { + jReply = JsonUtils.merge(jReply, // + log(jIdOpt.get(), jLogOpt.get()) // + ); + } + + // send reply + if (jReply.entrySet().size() > 0) { + if (jIdOpt.isPresent()) { + jReply.add("id", jIdOpt.get()); + } + WebSocketUtils.send(websocket, jReply); + } + } + + /** + * Handle "config" messages + * + * @param jConfig + * @return + */ + private synchronized JsonObject config(JsonObject jConfig) { + try { + String mode = JsonUtils.getAsString(jConfig, "mode"); + + if (mode.equals("query")) { + /* + * Query current config + */ + String language = JsonUtils.getAsString(jConfig, "language"); + JsonObject jReplyConfig = Config.getInstance().getJson(ConfigFormat.OPENEMS_UI, language); + return DefaultMessages.configQueryReply(jReplyConfig); + } + } catch (OpenemsException e) { + log.warn(e.getMessage()); + } + return new JsonObject(); + } + + /** + * Handle current data subscriptions + * + * @param j + */ + private synchronized JsonObject currentData(JsonArray jId, JsonObject jCurrentData) { + try { + String mode = JsonUtils.getAsString(jCurrentData, "mode"); + + if (mode.equals("subscribe")) { + /* + * Subscribe to channels + */ + String messageId = jId.get(jId.size() - 1).getAsString(); + + // remove old worker if existed + CurrentDataWorker worker = this.currentDataSubscribers.remove(messageId); + if (worker != null) { + worker.dispose(); + } + // parse subscribed channels + HashMultimap channels = HashMultimap.create(); + JsonObject jSubscribeChannels = JsonUtils.getAsJsonObject(jCurrentData, "channels"); + for (Entry entry : jSubscribeChannels.entrySet()) { + String thing = entry.getKey(); + JsonArray jChannels = JsonUtils.getAsJsonArray(entry.getValue()); + for (JsonElement jChannel : jChannels) { + String channel = JsonUtils.getAsString(jChannel); + channels.put(thing, channel); + } + } + if (!channels.isEmpty()) { + // create new worker + worker = new CurrentDataWorker(jId, channels, this); + this.currentDataSubscribers.put(messageId, worker); + } + } + } catch (OpenemsException e) { + log.warn(e.getMessage()); + } + return new JsonObject(); + } + + /** + * Handle system log subscriptions + * + * @param j + */ + private synchronized JsonObject log(JsonArray jId, JsonObject jLog) { + try { + String mode = JsonUtils.getAsString(jLog, "mode"); + String messageId = jId.get(jId.size() - 1).getAsString(); + + if (mode.equals("subscribe")) { + /* + * Subscribe to system log + */ + this.logSubscribers.add(messageId); + } else if (mode.equals("unsubscribe")) { + /* + * Unsubscribe from system log + */ + this.logSubscribers.remove(messageId); + } + } catch (OpenemsException e) { + log.warn(e.getMessage()); + } + return new JsonObject(); + } + + // TODO handle config command + // /** + // * Set configuration + // * + // * @param j + // */ + // private synchronized void configure(JsonElement jConfigsElement) { + // try { + // JsonArray jConfigs = JsonUtils.getAsJsonArray(jConfigsElement); + // ThingRepository thingRepository = ThingRepository.getInstance(); + // for (JsonElement jConfigElement : jConfigs) { + // JsonObject jConfig = JsonUtils.getAsJsonObject(jConfigElement); + // String mode = JsonUtils.getAsString(jConfig, "mode"); + // if (mode.equals("update")) { + // /* + // * Channel Set mode + // */ + // String thingId = JsonUtils.getAsString(jConfig, "thing"); + // String channelId = JsonUtils.getAsString(jConfig, "channel"); + // JsonElement jValue = JsonUtils.getSubElement(jConfig, "value"); + // Optional channelOptional = thingRepository.getChannel(thingId, channelId); + // if (channelOptional.isPresent()) { + // Channel channel = channelOptional.get(); + // if (channel instanceof ConfigChannel) { + // /* + // * ConfigChannel + // */ + // ConfigChannel configChannel = (ConfigChannel) channel; + // configChannel.updateValue(jValue, true); + // Notification.send(NotificationType.SUCCESS, + // "Successfully updated [" + channel.address() + "] to [" + jValue + "]"); + // + // } else if (channel instanceof WriteChannel) { + // /* + // * WriteChannel + // */ + // WriteChannel writeChannel = (WriteChannel) channel; + // writeChannel.pushWrite(jValue); + // Notification.send(NotificationType.SUCCESS, + // "Successfully set [" + channel.address() + "] to [" + jValue + "]"); + // } + // } else { + // throw new ConfigException("Unable to find " + jConfig.toString()); + // } + // } else if (mode.equals("create")) { + // /* + // * Create new Thing + // */ + // JsonObject jObject = JsonUtils.getAsJsonObject(jConfig, "object"); + // String parentId = JsonUtils.getAsString(jConfig, "parent"); + // String thingId = JsonUtils.getAsString(jObject, "id"); + // if (thingId.startsWith("_")) { + // throw new ConfigException("IDs starting with underscore are reserved for internal use."); + // } + // if (thingRepository.getThingById(thingId).isPresent()) { + // throw new ConfigException("Thing Id is already existing."); + // } + // String clazzName = JsonUtils.getAsString(jObject, "class"); + // Class clazz = Class.forName(clazzName); + // if (Device.class.isAssignableFrom(clazz)) { + // // Device + // Thing parentThing = thingRepository.getThing(parentId); + // if (parentThing instanceof Bridge) { + // Bridge parentBridge = (Bridge) parentThing; + // Device device = thingRepository.createDevice(jObject); + // parentBridge.addDevice(device); + // Config.getInstance().writeConfigFile(); + // Notification.send(NotificationType.SUCCESS, "Device [" + device.id() + "] wurde erstellt."); + // break; + // } + // } + // } else if (mode.equals("delete")) { + // /* + // * Delete a Thing + // */ + // String thingId = JsonUtils.getAsString(jConfig, "thing"); + // thingRepository.removeThing(thingId); + // Config.getInstance().writeConfigFile(); + // Notification.send(NotificationType.SUCCESS, "Controller [" + thingId + "] wurde " + " gel�scht."); + // } else { + // throw new OpenemsException("Modus [" + mode + "] ist nicht implementiert."); + // } + // } + // // Send new config + // JsonObject jMetadata = new JsonObject(); + // // TODO jMetadata.add("config", Config.getInstance().getMetaConfigJson()); + // JsonObject j = new JsonObject(); + // j.add("metadata", jMetadata); + // WebSocketUtils.send(this.websocket, j); + // } catch (OpenemsException | ClassNotFoundException e) { + // Notification.send(NotificationType.ERROR, e.getMessage()); + // } + // } + + // TODO handle system command + // /** + // * System command + // * + // * @param j + // */ + // private synchronized void system(JsonElement jSystemElement) { + // JsonObject jNotification = new JsonObject(); + // try { + // JsonObject jSystem = JsonUtils.getAsJsonObject(jSystemElement); + // String mode = JsonUtils.getAsString(jSystem, "mode"); + // if (mode.equals("systemd-restart")) { + // /* + // * Restart systemd service + // */ + // String service = JsonUtils.getAsString(jSystem, "service"); + // if (service.equals("fems-pagekite")) { + // ProcessBuilder builder = new ProcessBuilder("/bin/systemctl", "restart", "fems-pagekite"); + // Process p = builder.start(); + // if (p.waitFor() == 0) { + // log.info("Successfully restarted fems-pagekite"); + // } else { + // throw new OpenemsException("restart fems-pagekite failed"); + // } + // } else { + // throw new OpenemsException("Unknown systemd-restart service: " + jSystemElement.toString()); + // } + // + // } else if (mode.equals("manualpq")) { + // /* + // * Manual PQ settings + // */ + // String ess = JsonUtils.getAsString(jSystem, "ess"); + // Boolean active = JsonUtils.getAsBoolean(jSystem, "active"); + // if (active) { + // Long p = JsonUtils.getAsLong(jSystem, "p"); + // Long q = JsonUtils.getAsLong(jSystem, "q"); + // if (this.controller == null) { + // throw new OpenemsException("Local access only. Controller is null."); + // } + // this.controller.setManualPQ(ess, p, q); + // Notification.send(NotificationType.SUCCESS, + // "Leistungsvorgabe gesetzt: ess[" + ess + "], p[" + p + "], q[" + q + "]"); + // } else { + // this.controller.resetManualPQ(ess); + // Notification.send(NotificationType.SUCCESS, "Leistungsvorgabe gestoppt: ess[" + ess + "]"); + // } + // } else { + // throw new OpenemsException("Unknown system message: " + jSystemElement.toString()); + // } + // } catch (OpenemsException | IOException | InterruptedException e) { + // Notification.send(NotificationType.ERROR, e.getMessage()); + // } + // } + + // TODO handle manual PQ + // private void manualPQ(JsonElement j, AuthenticatedWebsocketHandler handler) { + // try { + // JsonObject jPQ = JsonUtils.getAsJsonObject(j); + // if (jPQ.has("p") && jPQ.has("q")) { + // long p = JsonUtils.getAsLong(jPQ, "p"); + // long q = JsonUtils.getAsLong(jPQ, "q"); + // this.controller.setManualPQ(p, q); + // handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabe gesetzt: P=" + p + ",Q=" + q); + // } else { + // // stop manual PQ + // this.controller.resetManualPQ(); + // handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabe zurückgesetzt"); + // } + // } catch (ReflectionException e) { + // handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabewerte falsch: " + e.getMessage()); + // } + // } + + // TODO handle channel commands + // private void channel(JsonElement jChannelElement, AuthenticatedWebsocketHandler handler) { + // try { + // JsonObject jChannel = JsonUtils.getAsJsonObject(jChannelElement); + // String thingId = JsonUtils.getAsString(jChannel, "thing"); + // String channelId = JsonUtils.getAsString(jChannel, "channel"); + // JsonElement jValue = JsonUtils.getSubElement(jChannel, "value"); + // + // // get channel + // Channel channel; + // Optional channelOptional = thingRepository.getChannel(thingId, channelId); + // if (channelOptional.isPresent()) { + // // get channel value + // channel = channelOptional.get(); + // } else { + // // Channel not found + // throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); + // } + // + // // check for writable channel + // if (!(channel instanceof WriteChannel)) { + // throw new ResourceException(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + // } + // + // // set channel value + // if (channel instanceof ConfigChannel) { + // // is a ConfigChannel + // ConfigChannel configChannel = (ConfigChannel) channel; + // try { + // configChannel.updateValue(jValue, true); + // log.info("Updated Channel [" + channel.address() + "] to value [" + jValue.toString() + "]."); + // handler.sendNotification(NotificationType.SUCCESS, + // "Channel [" + channel.address() + "] aktualisiert zu [" + jValue.toString() + "]."); + // } catch (NotImplementedException e) { + // throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Conversion not implemented"); + // } + // } else { + // // is a WriteChannel + // handler.sendNotification(NotificationType.WARNING, "WriteChannel nicht implementiert"); + // } + // } catch (ReflectionException e) { + // handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabewerte falsch: " + e.getMessage()); + // } + // } + + /** + * Send a notification message/error to the websocket + * + * @param mesage + * @return true if successful, otherwise false + */ + // TODO send notification + // public synchronized void sendNotification(NotificationType type, String message) { + // JsonObject jNotification = new JsonObject(); + // jNotification.addProperty("type", type.name().toLowerCase()); + // jNotification.addProperty("message", message); + // JsonObject j = new JsonObject(); + // j.add("notification", jNotification); + // new Thread(() -> { + // WebSocketUtils.send(websocket, j); + // }).start(); + // } + + /** + * Send a log message to the websocket. This method is called by logback + * + * @param message2 + * @param timestamp + */ + public void sendLog(long timestamp, String level, String source, String message) { + if (this.logSubscribers.isEmpty()) { + // nobody subscribed + return; + } + for (String id : this.logSubscribers) { + JsonArray jId = new JsonArray(); + jId.add("log"); + jId.add(id); + JsonObject j = DefaultMessages.log(jId, timestamp, level, source, message); + logExecutor.execute(() -> { + WebSocketUtils.send(websocket, j); + }); + } + } + +} diff --git a/edge/src/io/openems/core/utilities/websocket/Notification.java b/edge/src/io/openems/core/utilities/websocket/Notification.java index b47b77cb192..334a34db2ef 100644 --- a/edge/src/io/openems/core/utilities/websocket/Notification.java +++ b/edge/src/io/openems/core/utilities/websocket/Notification.java @@ -3,8 +3,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.impl.controller.api.websocket.WebsocketServer; - public class Notification { private Logger log = LoggerFactory.getLogger(Notification.class); @@ -38,7 +36,7 @@ public void send() { log.warn(this.message); break; } - WebsocketServer.broadcastNotification(this); + // TODO WebsocketServer.broadcastNotification(this); } public static void send(NotificationType type, String message) { diff --git a/edge/src/io/openems/core/utilities/websocket/WebsocketHandler.java b/edge/src/io/openems/core/utilities/websocket/WebsocketHandler.java deleted file mode 100644 index 7c7a4dd7251..00000000000 --- a/edge/src/io/openems/core/utilities/websocket/WebsocketHandler.java +++ /dev/null @@ -1,633 +0,0 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.core.utilities.websocket; - -import java.io.IOException; -import java.time.Period; -import java.time.ZoneId; -import java.time.ZoneOffset; -import java.time.ZonedDateTime; -import java.util.Optional; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; - -import org.java_websocket.WebSocket; -import org.java_websocket.exceptions.WebsocketNotConnectedException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.collect.HashMultimap; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.Device; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.NotImplementedException; -import io.openems.api.exception.OpenemsException; -import io.openems.api.exception.ReflectionException; -import io.openems.api.persistence.QueryablePersistence; -import io.openems.api.thing.Thing; -import io.openems.core.Config; -import io.openems.core.Databus; -import io.openems.core.ThingRepository; -import io.openems.core.utilities.JsonUtils; -import io.openems.core.utilities.StringUtils; -import io.openems.impl.controller.api.websocket.WebsocketApiController; - -/** - * Handles a Websocket connection to a browser, femsserver,... - * - * @author stefan.feilmeier - */ -public class WebsocketHandler { - - protected final static String DEFAULT_DEVICE_NAME = "fems"; - - private static Logger log = LoggerFactory.getLogger(WebsocketHandler.class); - - /** - * Holds thingId and channelId, subscribed by this websocket - */ - private final HashMultimap subscribedChannels = HashMultimap.create(); - - /** - * Executor for subscriptions task - */ - private static ScheduledExecutorService subscriptionExecutor = Executors.newScheduledThreadPool(1); - - /** - * Task regularly send subscriped to data - */ - private final Runnable subscriptionTask; - - /** - * Holds the scheduled subscription task - */ - private ScheduledFuture subscriptionFuture = null; - - /** - * Holds the databus singleton - */ - private final Databus databus; - - /** - * Holds the websocket connection - */ - protected final WebSocket websocket; - - /** - * Which log is currently subscribed? "" if none. - */ - private volatile String subscribeLog = ""; - - /** - * Connected WebsocketApiController - */ - private WebsocketApiController controller; - - private final ThingRepository thingRepository; - - private WebsocketHandler handler; - - public WebsocketHandler(WebSocket websocket, WebsocketApiController controller) { - this.databus = Databus.getInstance(); - this.websocket = websocket; - this.thingRepository = ThingRepository.getInstance(); - this.controller = controller; - this.subscriptionTask = () -> { - /* - * This task is executed regularly. Sends data to websocket. - */ - JsonObject j = new JsonObject(); - JsonObject jCurrentdata = getSubscribedData(); - j.add("currentdata", jCurrentdata); - this.send(j); - }; - } - - /** - * Message event of websocket. Handles a new message. - */ - public void onMessage(JsonObject jMessage) { - /* - * Get unique request id - */ - // TODO: pass requestId everywhere - String requestId = ""; - if (jMessage.has("requestId")) { - try { - requestId = JsonUtils.getAsString(jMessage, "requestId"); - } catch (ReflectionException e) { - log.warn("Invalid requestId: " + e.getMessage()); - } - } - - /* - * Subscribe to data - */ - if (jMessage.has("subscribe")) { - subscribe(jMessage.get("subscribe")); - } - - /* - * Configuration - */ - if (jMessage.has("configure")) { - configure(jMessage.get("configure")); - } - - /* - * System command - */ - if (jMessage.has("system")) { - system(jMessage.get("system")); - } - - /* - * Query command - */ - if (jMessage.has("query")) { - query(requestId, jMessage.get("query")); - } - } - - /** - * Handle subscriptions - * - * @param j - */ - private synchronized void subscribe(JsonElement jSubscribeElement) { - try { - JsonObject jSubscribe = JsonUtils.getAsJsonObject(jSubscribeElement); - if (jSubscribe.has("channels")) { - /* - * Subscribe to channels - */ - // unsubscribe regular task - if (subscriptionFuture != null) { - subscriptionFuture.cancel(true); - } - // clear subscriptions - this.subscribedChannels.clear(); - JsonObject jSubscribeChannels = JsonUtils.getAsJsonObject(jSubscribe, "channels"); - jSubscribeChannels.entrySet().forEach(entry -> { - try { - String thing = entry.getKey(); - JsonArray jChannels = JsonUtils.getAsJsonArray(entry.getValue()); - for (JsonElement jChannel : jChannels) { - String channel = JsonUtils.getAsString(jChannel); - this.subscribedChannels.put(thing, channel); - } - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - }); - // schedule task - if (!this.subscribedChannels.isEmpty()) { - subscriptionFuture = subscriptionExecutor.scheduleWithFixedDelay(this.subscriptionTask, 0, 3, - TimeUnit.SECONDS); - } - } else if (jSubscribe.has("log")) { - /* - * Subscribe to log - */ - String log = JsonUtils.getAsString(jSubscribe, "log"); - this.subscribeLog = log; - } - - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - - /** - * Gets a json object with all subscribed channels - * - * @return - */ - private JsonObject getSubscribedData() { - JsonObject jData = new JsonObject(); - subscribedChannels.keys().forEach(thingId -> { - JsonObject jThingData = new JsonObject(); - subscribedChannels.get(thingId).forEach(channelId -> { - Optional value = databus.getValue(thingId, channelId); - JsonElement jValue; - try { - jValue = JsonUtils.getAsJsonElement(value.orElse(null)); - jThingData.add(channelId, jValue); - } catch (NotImplementedException e) { - log.error(e.getMessage()); - } - }); - jData.add(thingId, jThingData); - }); - return jData; - } - - /** - * Sends a message to the websocket - * - * @param jMessage - */ - public boolean send(JsonObject jMessage) { - try { - this.websocket.send(jMessage.toString()); - return true; - } catch (WebsocketNotConnectedException e) { - return false; - } - } - - /** - * Sends an initial message to the browser after it was successfully connected - */ - public boolean sendConnectionSuccessfulReply() { - JsonObject j = this.createConnectionSuccessfulReply(); - log.info("Send Connection Successful Reply: " + StringUtils.toShortString(j, 100)); - return this.send(j); - } - - public boolean sendConnectionFailedReply() { - JsonObject j = this.createConnectionFailedReply(); - log.info("Send Connection Failed Reply: " + StringUtils.toShortString(j, 100)); - return this.send(j); - } - - /** - * Creates an initial message to the browser after it was successfully connected - * - *
-	 * {
-	 *   metadata: {
-	 *       config: {...},
-	 *       backend: "openems"
-	 *   }
-	 * }
-	 * 
- * - * @param handler - */ - protected JsonObject createConnectionSuccessfulReply() { - JsonObject j = new JsonObject(); - - // Metadata - JsonObject jMetadata = new JsonObject(); - try { - jMetadata.add("config", Config.getInstance().getMetaConfigJson()); - } catch (ConfigException e) { - log.error(e.getMessage()); - } - jMetadata.addProperty("backend", "openems"); - j.add("metadata", jMetadata); - - return j; - } - - protected JsonObject createConnectionFailedReply() { - JsonObject j = new JsonObject(); - - JsonObject jAuthenticate = new JsonObject(); - jAuthenticate.addProperty("mode", "deny"); - - j.add("authenticate", jAuthenticate); - - return j; - } - - /** - * Set configuration - * - * @param j - */ - private synchronized void configure(JsonElement jConfigsElement) { - try { - JsonArray jConfigs = JsonUtils.getAsJsonArray(jConfigsElement); - ThingRepository thingRepository = ThingRepository.getInstance(); - for (JsonElement jConfigElement : jConfigs) { - JsonObject jConfig = JsonUtils.getAsJsonObject(jConfigElement); - String mode = JsonUtils.getAsString(jConfig, "mode"); - if (mode.equals("update")) { - /* - * Channel Set mode - */ - String thingId = JsonUtils.getAsString(jConfig, "thing"); - String channelId = JsonUtils.getAsString(jConfig, "channel"); - JsonElement jValue = JsonUtils.getSubElement(jConfig, "value"); - Optional channelOptional = thingRepository.getChannel(thingId, channelId); - if (channelOptional.isPresent()) { - Channel channel = channelOptional.get(); - if (channel instanceof ConfigChannel) { - /* - * ConfigChannel - */ - ConfigChannel configChannel = (ConfigChannel) channel; - configChannel.updateValue(jValue, true); - Notification.send(NotificationType.SUCCESS, - "Successfully updated [" + channel.address() + "] to [" + jValue + "]"); - - } else if (channel instanceof WriteChannel) { - /* - * WriteChannel - */ - WriteChannel writeChannel = (WriteChannel) channel; - writeChannel.pushWrite(jValue); - Notification.send(NotificationType.SUCCESS, - "Successfully set [" + channel.address() + "] to [" + jValue + "]"); - } - } else { - throw new ConfigException("Unable to find " + jConfig.toString()); - } - } else if (mode.equals("create")) { - /* - * Create new Thing - */ - JsonObject jObject = JsonUtils.getAsJsonObject(jConfig, "object"); - String parentId = JsonUtils.getAsString(jConfig, "parent"); - String thingId = JsonUtils.getAsString(jObject, "id"); - if (thingId.startsWith("_")) { - throw new ConfigException("IDs starting with underscore are reserved for internal use."); - } - if (thingRepository.getThingById(thingId).isPresent()) { - throw new ConfigException("Thing Id is already existing."); - } - String clazzName = JsonUtils.getAsString(jObject, "class"); - Class clazz = Class.forName(clazzName); - if (Device.class.isAssignableFrom(clazz)) { - // Device - Thing parentThing = thingRepository.getThing(parentId); - if (parentThing instanceof Bridge) { - Bridge parentBridge = (Bridge) parentThing; - Device device = thingRepository.createDevice(jObject); - parentBridge.addDevice(device); - Config.getInstance().writeConfigFile(); - Notification.send(NotificationType.SUCCESS, "Device [" + device.id() + "] wurde erstellt."); - break; - } - } - } else if (mode.equals("delete")) { - /* - * Delete a Thing - */ - String thingId = JsonUtils.getAsString(jConfig, "thing"); - thingRepository.removeThing(thingId); - Config.getInstance().writeConfigFile(); - Notification.send(NotificationType.SUCCESS, "Controller [" + thingId + "] wurde " + " gel�scht."); - } else { - throw new OpenemsException("Modus [" + mode + "] ist nicht implementiert."); - } - } - // Send new config - JsonObject jMetadata = new JsonObject(); - jMetadata.add("config", Config.getInstance().getMetaConfigJson()); - JsonObject j = new JsonObject(); - j.add("metadata", jMetadata); - this.send(j); - } catch (OpenemsException | ClassNotFoundException e) { - Notification.send(NotificationType.ERROR, e.getMessage()); - } - } - - /** - * System command - * - * @param j - */ - private synchronized void system(JsonElement jSystemElement) { - JsonObject jNotification = new JsonObject(); - try { - JsonObject jSystem = JsonUtils.getAsJsonObject(jSystemElement); - String mode = JsonUtils.getAsString(jSystem, "mode"); - if (mode.equals("systemd-restart")) { - /* - * Restart systemd service - */ - String service = JsonUtils.getAsString(jSystem, "service"); - if (service.equals("fems-pagekite")) { - ProcessBuilder builder = new ProcessBuilder("/bin/systemctl", "restart", "fems-pagekite"); - Process p = builder.start(); - if (p.waitFor() == 0) { - log.info("Successfully restarted fems-pagekite"); - } else { - throw new OpenemsException("restart fems-pagekite failed"); - } - } else { - throw new OpenemsException("Unknown systemd-restart service: " + jSystemElement.toString()); - } - - } else if (mode.equals("manualpq")) { - /* - * Manual PQ settings - */ - String ess = JsonUtils.getAsString(jSystem, "ess"); - Boolean active = JsonUtils.getAsBoolean(jSystem, "active"); - if (active) { - Long p = JsonUtils.getAsLong(jSystem, "p"); - Long q = JsonUtils.getAsLong(jSystem, "q"); - if (this.controller == null) { - throw new OpenemsException("Local access only. Controller is null."); - } - this.controller.setManualPQ(ess, p, q); - Notification.send(NotificationType.SUCCESS, - "Leistungsvorgabe gesetzt: ess[" + ess + "], p[" + p + "], q[" + q + "]"); - } else { - this.controller.resetManualPQ(ess); - Notification.send(NotificationType.SUCCESS, "Leistungsvorgabe gestoppt: ess[" + ess + "]"); - } - } else { - throw new OpenemsException("Unknown system message: " + jSystemElement.toString()); - } - } catch (OpenemsException | IOException | InterruptedException e) { - Notification.send(NotificationType.ERROR, e.getMessage()); - } - } - - /** - * Query command - * - * @param j - */ - private synchronized void query(String requestId, JsonElement jQueryElement) { - try { - JsonObject jQuery = JsonUtils.getAsJsonObject(jQueryElement); - String mode = JsonUtils.getAsString(jQuery, "mode"); - if (mode.equals("history")) { - /* - * History query - */ - int timezoneDiff = JsonUtils.getAsInt(jQuery, "timezone"); - ZoneId timezone = ZoneId.ofOffset("", ZoneOffset.ofTotalSeconds(timezoneDiff * -1)); - ZonedDateTime fromDate = JsonUtils.getAsZonedDateTime(jQuery, "fromDate", timezone); - ZonedDateTime toDate = JsonUtils.getAsZonedDateTime(jQuery, "toDate", timezone); - JsonObject channels = JsonUtils.getAsJsonObject(jQuery, "channels"); - // TODO JsonObject kWh = JsonUtils.getAsJsonObject(jQuery, "kWh"); - // Calculate resolution - int days = Period.between(fromDate.toLocalDate(), toDate.toLocalDate()).getDays(); - // TODO: better calculation of sensible resolution - int resolution = 10 * 60; // 10 Minutes - if (days > 25) { - resolution = 24 * 60 * 60; // 1 Day - } else if (days > 6) { - resolution = 3 * 60 * 60; // 3 Hours - } else if (days > 2) { - resolution = 60 * 60; // 60 Minutes - } - JsonObject jQueryreply = null; - for (QueryablePersistence queryablePersistence : thingRepository.getQueryablePersistences()) { - // TODO jQueryreply = queryablePersistence.query(fromDate, toDate, channels, resolution, kWh); - jQueryreply = queryablePersistence.query(fromDate, toDate, channels, resolution); - if (jQueryreply != null) { - break; - } - } - JsonObject j = bootstrapReply(requestId); - // Check if queryable persistence is available - if (jQueryreply != null) { - // Send result - j.add("queryreply", jQueryreply); - } else { - j.addProperty("error", "No Queryable persistence found!"); - } - this.send(j); - - // log.info("RESULT: " + j); - } - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - - /* - * private void manualPQ(JsonElement j, AuthenticatedWebsocketHandler handler) { - * try { - * JsonObject jPQ = JsonUtils.getAsJsonObject(j); - * if (jPQ.has("p") && jPQ.has("q")) { - * long p = JsonUtils.getAsLong(jPQ, "p"); - * long q = JsonUtils.getAsLong(jPQ, "q"); - * this.controller.setManualPQ(p, q); - * handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabe gesetzt: P=" + p + ",Q=" + q); - * } else { - * // stop manual PQ - * this.controller.resetManualPQ(); - * handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabe zurückgesetzt"); - * } - * } catch (ReflectionException e) { - * handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabewerte falsch: " + e.getMessage()); - * } - * } - */ - // private void channel(JsonElement jChannelElement, AuthenticatedWebsocketHandler handler) { - // try { - // JsonObject jChannel = JsonUtils.getAsJsonObject(jChannelElement); - // String thingId = JsonUtils.getAsString(jChannel, "thing"); - // String channelId = JsonUtils.getAsString(jChannel, "channel"); - // JsonElement jValue = JsonUtils.getSubElement(jChannel, "value"); - // - // // get channel - // Channel channel; - // Optional channelOptional = thingRepository.getChannel(thingId, channelId); - // if (channelOptional.isPresent()) { - // // get channel value - // channel = channelOptional.get(); - // } else { - // // Channel not found - // throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); - // } - // - // // check for writable channel - // if (!(channel instanceof WriteChannel)) { - // throw new ResourceException(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - // } - // - // // set channel value - // if (channel instanceof ConfigChannel) { - // // is a ConfigChannel - // ConfigChannel configChannel = (ConfigChannel) channel; - // try { - // configChannel.updateValue(jValue, true); - // log.info("Updated Channel [" + channel.address() + "] to value [" + jValue.toString() + "]."); - // handler.sendNotification(NotificationType.SUCCESS, - // "Channel [" + channel.address() + "] aktualisiert zu [" + jValue.toString() + "]."); - // } catch (NotImplementedException e) { - // throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Conversion not implemented"); - // } - // } else { - // // is a WriteChannel - // handler.sendNotification(NotificationType.WARNING, "WriteChannel nicht implementiert"); - // } - // } catch (ReflectionException e) { - // handler.sendNotification(NotificationType.SUCCESS, "Leistungsvorgabewerte falsch: " + e.getMessage()); - // } - // } - - /** - * Send a notification message/error to the websocket - * - * @param mesage - * @return true if successful, otherwise false - */ - // TODO - public synchronized void sendNotification(NotificationType type, String message) { - JsonObject jNotification = new JsonObject(); - jNotification.addProperty("type", type.name().toLowerCase()); - jNotification.addProperty("message", message); - JsonObject j = new JsonObject(); - j.add("notification", jNotification); - new Thread(() -> { - this.send(j); - }).start(); - } - - /** - * Send a log message to the websocket - * - * @param message2 - * @param timestamp - */ - public void sendLog(long timestamp, String level, String source, String message) { - if (this.subscribeLog.isEmpty()) { - return; - } - // send notification to websocket - JsonObject j = new JsonObject(); - JsonObject jLog = new JsonObject(); - jLog.addProperty("timestamp", timestamp); - jLog.addProperty("level", level); - jLog.addProperty("source", source); - jLog.addProperty("message", message); - j.add("log", jLog); - new Thread(() -> { - this.send(j); - }).start(); - } - - private JsonObject bootstrapReply(String requestId) { - JsonObject j = new JsonObject(); - j.addProperty("requestId", requestId); - return j; - } -} diff --git a/edge/src/io/openems/core/utilities/websocket/WebsocketLogAppender.java b/edge/src/io/openems/core/utilities/websocket/WebsocketLogAppender.java index ab8fa980617..e30b0b6626a 100644 --- a/edge/src/io/openems/core/utilities/websocket/WebsocketLogAppender.java +++ b/edge/src/io/openems/core/utilities/websocket/WebsocketLogAppender.java @@ -4,8 +4,10 @@ import ch.qos.logback.classic.spi.ILoggingEvent; import ch.qos.logback.core.AppenderBase; +import io.openems.api.controller.Controller; +import io.openems.api.scheduler.Scheduler; import io.openems.core.ThingRepository; -import io.openems.impl.controller.api.websocket.WebsocketServer; +import io.openems.impl.controller.api.websocket.WebsocketApiController; import io.openems.impl.persistence.fenecon.FeneconPersistence; public class WebsocketLogAppender extends AppenderBase { @@ -17,14 +19,21 @@ protected void append(ILoggingEvent event) { String source = event.getLoggerName(); String message = event.getFormattedMessage(); - // send to websockets - WebsocketServer.broadcastLog(timestamp, level, source, message); + ThingRepository thingRepository = ThingRepository.getInstance(); + for (Scheduler scheduler : thingRepository.getSchedulers()) { + for (Controller controller : scheduler.getControllers()) { + if (controller instanceof WebsocketApiController) { + WebsocketApiController websocketApiController = (WebsocketApiController) controller; + websocketApiController.broadcastLog(timestamp, level, source, message); + } + } + } // send to fenecon persistence ThingRepository.getInstance().getPersistences().forEach((persistence) -> { if (persistence instanceof FeneconPersistence) { FeneconPersistence p = (FeneconPersistence) persistence; - Optional handler = p.getWebsocketHandler(); + Optional handler = p.getWebsocketHandler(); if (handler.isPresent()) { handler.get().sendLog(timestamp, level, source, message); } diff --git a/edge/src/io/openems/impl/controller/api/rest/internal/OpenemsVerifier.java b/edge/src/io/openems/impl/controller/api/rest/internal/OpenemsVerifier.java index 3303d3f8dbd..26131ecc63c 100644 --- a/edge/src/io/openems/impl/controller/api/rest/internal/OpenemsVerifier.java +++ b/edge/src/io/openems/impl/controller/api/rest/internal/OpenemsVerifier.java @@ -20,36 +20,38 @@ *******************************************************************************/ package io.openems.impl.controller.api.rest.internal; +import java.util.Optional; + import org.restlet.Request; import org.restlet.Response; import org.restlet.security.Verifier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import io.openems.api.security.Authentication; -import io.openems.api.security.Session; +import io.openems.api.security.User; public class OpenemsVerifier implements Verifier { private final static Logger log = LoggerFactory.getLogger(OpenemsVerifier.class); - @Override public int verify(Request request, Response response) { + @Override + public int verify(Request request, Response response) { if (request.getChallengeResponse() == null) { log.warn("Authentication failed: No authentication data available."); return RESULT_MISSING; } else { String username = getIdentifier(request, response); String password = new String(getSecret(request, response)); - Session session = Authentication.getInstance().byUserPassword(username, password); + Optional userOpt = User.authenticate(username, password); - if (session == null || !session.isValid()) { + if (userOpt.isPresent()) { + User user = userOpt.get(); + request.getClientInfo().setUser(new org.restlet.security.User(user.getName())); + request.getChallengeResponse().setIdentifier(user.getName()); + return RESULT_VALID; + } else { log.warn("Authentication failed."); return RESULT_INVALID; - } else { - // log.info("Authentication successful: logged in as " + user.getName()); - request.getClientInfo().setUser(new org.restlet.security.User(session.getUser().getName())); - request.getChallengeResponse().setIdentifier(session.getUser().getName()); - return RESULT_VALID; } } } diff --git a/edge/src/io/openems/impl/controller/api/rest/route/ChannelRestlet.java b/edge/src/io/openems/impl/controller/api/rest/route/ChannelRestlet.java index f5f73f29173..c062a5f0ba2 100644 --- a/edge/src/io/openems/impl/controller/api/rest/route/ChannelRestlet.java +++ b/edge/src/io/openems/impl/controller/api/rest/route/ChannelRestlet.java @@ -1,162 +1,162 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.controller.api.rest.route; - -import java.util.Map; -import java.util.Optional; - -import org.restlet.Request; -import org.restlet.Response; -import org.restlet.data.MediaType; -import org.restlet.data.Method; -import org.restlet.data.Status; -import org.restlet.representation.Representation; -import org.restlet.representation.StringRepresentation; -import org.restlet.resource.ResourceException; - -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.WriteChannel; +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.api.rest.route; + +import java.util.Map; +import java.util.Optional; + +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.MediaType; +import org.restlet.data.Method; +import org.restlet.data.Status; +import org.restlet.representation.Representation; +import org.restlet.representation.StringRepresentation; +import org.restlet.resource.ResourceException; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.WriteChannel; import io.openems.api.exception.NotImplementedException; -import io.openems.api.security.User; -import io.openems.core.ThingRepository; -import io.openems.impl.controller.api.rest.OpenemsRestlet; - -public class ChannelRestlet extends OpenemsRestlet { - - private final ThingRepository thingRepository; - - public ChannelRestlet() { - super(); - thingRepository = ThingRepository.getInstance(); - } - - @Override public void handle(Request request, Response response) { - super.handle(request, response); - - // check general permission - if (isAuthenticatedAsUser(request, User.GUEST) && request.getClientInfo().getRoles().size() == 1) { - // pfff... it's only a "GUEST"! Deny anything but GET requests - if (!request.getMethod().equals(Method.GET)) { - throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED); - } - } - - // get request attributes - Map attributes = request.getAttributes(); - String thingId = (String) attributes.get("thing"); - String channelId = (String) attributes.get("channel"); - - // get channel - Channel channel; - Optional channelOptional = thingRepository.getChannel(thingId, channelId); - if (channelOptional.isPresent()) { - // get channel value - channel = channelOptional.get(); - } else { - // Channel not found - throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); - } - - // check permission - if (!channel.users().isEmpty()) { - boolean allowed = false; - for (User user : channel.users()) { - if (isAuthenticatedAsUser(request, user)) { - allowed = true; - break; - } - } - if (!allowed) { - throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED); - } - } - - // call handler methods - if (request.getMethod().equals(Method.GET)) { - Representation entity = getValue(channel); - response.setEntity(entity); - - } else if (request.getMethod().equals(Method.POST)) { - JsonParser parser = new JsonParser(); - String httpPost = request.getEntityAsText(); - JsonObject jHttpPost = parser.parse(httpPost).getAsJsonObject(); - setValue(channel, jHttpPost); - } - } - - /** - * handle HTTP GET request - * - * @param thingId - * @param channelId - * @return - */ - private Representation getValue(Channel channel) { - try { - return new StringRepresentation(channel.toJsonObject().toString(), MediaType.APPLICATION_JSON); - } catch (NotImplementedException e) { - throw new ResourceException(Status.SERVER_ERROR_NOT_IMPLEMENTED, e); - } - } - - /** - * handle HTTP POST request - * - * @param thingId - * @param channelId - * @param jHttpPost - */ - private void setValue(Channel channel, JsonObject jHttpPost) { - // check for writable channel - if (!(channel instanceof WriteChannel)) { - throw new ResourceException(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); - } - - // parse value - JsonElement jValue; - if (jHttpPost.has("value")) { - jValue = jHttpPost.get("value"); - } else { - throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Value is missing"); - } - - // set channel value - if (channel instanceof ConfigChannel) { - - // is a ConfigChannel - ConfigChannel configChannel = (ConfigChannel) channel; - try { - configChannel.updateValue(jValue, true); - log.info("Updated Channel [" + channel.address() + "] to value [" + jValue.toString() + "]."); - } catch (NotImplementedException e) { - throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Conversion not implemented"); - } - - } else { - // is a WriteChannel - } - } -} +import io.openems.api.security.User; +import io.openems.core.ThingRepository; +import io.openems.impl.controller.api.rest.OpenemsRestlet; + +public class ChannelRestlet extends OpenemsRestlet { + + private final ThingRepository thingRepository; + + public ChannelRestlet() { + super(); + thingRepository = ThingRepository.getInstance(); + } + + @Override public void handle(Request request, Response response) { + super.handle(request, response); + + // check general permission + if (isAuthenticatedAsUser(request, User.GUEST) && request.getClientInfo().getRoles().size() == 1) { + // pfff... it's only a "GUEST"! Deny anything but GET requests + if (!request.getMethod().equals(Method.GET)) { + throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED); + } + } + + // get request attributes + Map attributes = request.getAttributes(); + String thingId = (String) attributes.get("thing"); + String channelId = (String) attributes.get("channel"); + + // get channel + Channel channel; + Optional channelOptional = thingRepository.getChannel(thingId, channelId); + if (channelOptional.isPresent()) { + // get channel value + channel = channelOptional.get(); + } else { + // Channel not found + throw new ResourceException(Status.CLIENT_ERROR_NOT_FOUND); + } + + // check permission + if (!channel.users().isEmpty()) { + boolean allowed = false; + for (User user : channel.users()) { + if (isAuthenticatedAsUser(request, user)) { + allowed = true; + break; + } + } + if (!allowed) { + throw new ResourceException(Status.CLIENT_ERROR_UNAUTHORIZED); + } + } + + // call handler methods + if (request.getMethod().equals(Method.GET)) { + Representation entity = getValue(channel); + response.setEntity(entity); + + } else if (request.getMethod().equals(Method.POST)) { + JsonParser parser = new JsonParser(); + String httpPost = request.getEntityAsText(); + JsonObject jHttpPost = parser.parse(httpPost).getAsJsonObject(); + setValue(channel, jHttpPost); + } + } + + /** + * handle HTTP GET request + * + * @param thingId + * @param channelId + * @return + */ + private Representation getValue(Channel channel) { + try { + return new StringRepresentation(channel.toJsonObject().toString(), MediaType.APPLICATION_JSON); + } catch (NotImplementedException e) { + throw new ResourceException(Status.SERVER_ERROR_NOT_IMPLEMENTED, e); + } + } + + /** + * handle HTTP POST request + * + * @param thingId + * @param channelId + * @param jHttpPost + */ + private void setValue(Channel channel, JsonObject jHttpPost) { + // check for writable channel + if (!(channel instanceof WriteChannel)) { + throw new ResourceException(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); + } + + // parse value + JsonElement jValue; + if (jHttpPost.has("value")) { + jValue = jHttpPost.get("value"); + } else { + throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Value is missing"); + } + + // set channel value + if (channel instanceof ConfigChannel) { + + // is a ConfigChannel + ConfigChannel configChannel = (ConfigChannel) channel; + try { + configChannel.updateValue(jValue, true); + log.info("Updated Channel [" + channel.address() + "] to value [" + jValue.toString() + "]."); + } catch (NotImplementedException e) { + throw new ResourceException(Status.CLIENT_ERROR_BAD_REQUEST, "Conversion not implemented"); + } + + } else { + // is a WriteChannel + } + } +} diff --git a/edge/src/io/openems/impl/controller/api/rest/route/UserChangePasswordRestlet.java b/edge/src/io/openems/impl/controller/api/rest/route/UserChangePasswordRestlet.java index af4fecd695f..cfe256d1f2f 100644 --- a/edge/src/io/openems/impl/controller/api/rest/route/UserChangePasswordRestlet.java +++ b/edge/src/io/openems/impl/controller/api/rest/route/UserChangePasswordRestlet.java @@ -41,7 +41,8 @@ public UserChangePasswordRestlet() { super(); } - @Override public void handle(Request request, Response response) { + @Override + public void handle(Request request, Response response) { super.handle(request, response); // get user diff --git a/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiController.java b/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiController.java index 6397d5457f3..c467a5d6a52 100644 --- a/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiController.java +++ b/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiController.java @@ -21,7 +21,6 @@ package io.openems.impl.controller.api.websocket; import java.io.IOException; -import java.util.HashMap; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -77,8 +76,7 @@ public WebsocketApiController(String thingId) { */ private final ConcurrentHashMap manualpq = new ConcurrentHashMap<>(); private final ThingRepository thingRepository; - private volatile WebsocketServer websocketServer = null; - private HashMap lastMessages = new HashMap<>(); + private volatile WebsocketApiServer websocketApiServer = null; /* * Methods @@ -86,10 +84,10 @@ public WebsocketApiController(String thingId) { @Override public void run() { // Start Websocket-Api server - if (websocketServer == null && port.valueOptional().isPresent()) { + if (websocketApiServer == null && port.valueOptional().isPresent()) { try { - websocketServer = new WebsocketServer(this, port.valueOptional().get()); - websocketServer.start(); + websocketApiServer = new WebsocketApiServer(this, port.valueOptional().get()); + websocketApiServer.start(); log.info("Websocket-Api started on port [" + port.valueOptional().orElse(0) + "]."); } catch (Exception e) { log.error(e.getMessage() + ": " + e.getCause()); @@ -137,14 +135,14 @@ public void run() { @Override public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { if (channel.equals(port)) { - if (this.websocketServer != null) { + if (this.websocketApiServer != null) { try { - this.websocketServer.stop(); + this.websocketApiServer.stop(); } catch (IOException | InterruptedException e) { log.error("Error closing websocket on port [" + oldValue + "]: " + e.getMessage()); } } - this.websocketServer = null; + this.websocketApiServer = null; } } @@ -155,4 +153,16 @@ public void setManualPQ(String ess, long p, long q) { public void resetManualPQ(String ess) { this.manualpq.remove(ess); } + + /** + * Send a log entry to all connected websockets + * + * @param string + * @param timestamp + * + * @param jMessage + */ + public void broadcastLog(long timestamp, String level, String source, String message) { + this.websocketApiServer.broadcastLog(timestamp, level, source, message); + } } diff --git a/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiServer.java b/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiServer.java new file mode 100644 index 00000000000..f635486df56 --- /dev/null +++ b/edge/src/io/openems/impl/controller/api/websocket/WebsocketApiServer.java @@ -0,0 +1,183 @@ +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.api.websocket; + +import java.util.ArrayList; +import java.util.Optional; + +import org.java_websocket.WebSocket; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.handshake.ClientHandshake; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.common.exceptions.OpenemsException; +import io.openems.common.utils.JsonUtils; +import io.openems.common.websocket.AbstractWebsocketServer; +import io.openems.common.websocket.DefaultMessages; +import io.openems.common.websocket.Notification; +import io.openems.common.websocket.WebSocketUtils; +import io.openems.impl.controller.api.websocket.session.WebsocketApiSession; +import io.openems.impl.controller.api.websocket.session.WebsocketApiSessionData; +import io.openems.impl.controller.api.websocket.session.WebsocketApiSessionManager; + +public class WebsocketApiServer + extends AbstractWebsocketServer { + + private static Logger log = LoggerFactory.getLogger(WebsocketApiServer.class); + + private final WebsocketApiController controller; + + public WebsocketApiServer(WebsocketApiController controller, int port) { + super(port, new WebsocketApiSessionManager()); + this.controller = controller; + } + + /** + * Open event of websocket. Parses the Odoo "session_id" and stores it in a new Session. + */ + @Override + public void onOpen(WebSocket websocket, ClientHandshake handshake) { + JsonObject jHandshake = this.parseCookieFromHandshake(handshake); + Optional tokenOpt = JsonUtils.getAsOptionalString(jHandshake, "token"); + if (tokenOpt.isPresent()) { + String token = tokenOpt.get(); + Optional sessionOpt = this.sessionManager.getSessionByToken(token); + if (sessionOpt.isPresent()) { + WebsocketApiSession session = sessionOpt.get(); + WebSocket oldWebsocket = session.getData().getWebsocketHandler().getWebsocket(); + if (oldWebsocket != null) { + // TODO to avoid this, websockets needs to be able to handle more than one websocket per session + oldWebsocket.closeConnection(CloseFrame.REFUSE, "Another client connected with this token"); + } + // refresh session + session.getData().getWebsocketHandler().setWebsocket(websocket); + // add to websockets + this.websockets.forcePut(websocket, session); + // send connection successful to browser + JsonObject jReply = DefaultMessages.browserConnectionSuccessfulReply(session.getToken(), + Optional.of(session.getData().getRole()), new ArrayList<>()); + log.info("Websocket [" + websocket + "] connected by session. User [" + session.getData().getUser() + + "] Session [" + session.getToken() + "]"); + WebSocketUtils.send(websocket, jReply); + return; + } + } + // if we are here, automatic authentication was not possible -> notify client + WebSocketUtils.send(websocket, + DefaultMessages.notification(Notification.EDGE_AUTHENTICATION_BY_TOKEN_FAILED, tokenOpt.orElse(""))); + } + + @Override + protected void onMessage(WebSocket websocket, JsonObject jMessage, Optional jMessageIdOpt, + Optional deviceNameOpt) { + // log.info("RECV: websocket[" + websocket + "]" + jMessage.toString()); + + /* + * Authenticate + */ + Optional sessionOpt = Optional.empty(); + if (jMessage.has("authenticate")) { + // authenticate by username/password + sessionOpt = authenticate(jMessage.get("authenticate"), websocket); + } + if (!sessionOpt.isPresent()) { + // check if there is an existing session + sessionOpt = Optional.ofNullable(this.websockets.get(websocket)); + } + if (!sessionOpt.isPresent()) { + /* + * send authentication failed reply + */ + JsonObject jReply = DefaultMessages.browserConnectionFailedReply(); + WebSocketUtils.send(websocket, jReply); + websocket.closeConnection(CloseFrame.REFUSE, "Authentication failed"); + return; + } + WebsocketApiSession session = sessionOpt.get(); + /* + * On initial authentication... + */ + if (jMessage.has("authenticate")) { + // add to websockets + this.websockets.put(websocket, session); + // send connection successful to browser + JsonObject jReply = DefaultMessages.browserConnectionSuccessfulReply(session.getToken(), + Optional.of(session.getData().getRole()), new ArrayList<>()); + log.info("Browser connected by authentication. User [" + session.getData().getUser() + "] Session [" + + session.getToken() + "]"); + WebSocketUtils.send(websocket, jReply); + } + + /* + * Rest -> forward to websocket handler + */ + session.getData().getWebsocketHandler().onMessage(jMessage); + } + + /** + * Authenticates a user according to the "authenticate" message. Stores the session if valid. + * + * @param jAuthenticateElement + * @param handler + */ + private Optional authenticate(JsonElement jAuthenticateElement, WebSocket websocket) { + try { + JsonObject jAuthenticate = JsonUtils.getAsJsonObject(jAuthenticateElement); + if (jAuthenticate.has("mode")) { + String mode = JsonUtils.getAsString(jAuthenticate, "mode"); + if (mode.equals("login")) { + if (jAuthenticate.has("password")) { + /* + * Authenticate using username and password + */ + String password = JsonUtils.getAsString(jAuthenticate, "password"); + if (jAuthenticate.has("username")) { + String username = JsonUtils.getAsString(jAuthenticate, "username"); + return this.sessionManager.authByUserPassword(username, password, websocket); + } else { + return this.sessionManager.authByPassword(password, websocket); + } + } + } + } + } catch (OpenemsException e) { /* ignore */ } + return Optional.empty(); + } + + /** + * Send a log entry to all connected websockets + * + * @param string + * @param timestamp + * + * @param jMessage + */ + public void broadcastLog(long timestamp, String level, String source, String message) { + for (WebsocketApiSession session : this.sessionManager.getSessions()) { + session.getData().getWebsocketHandler().sendLog(timestamp, level, source, message); + } + } +} diff --git a/edge/src/io/openems/impl/controller/api/websocket/WebsocketServer.java b/edge/src/io/openems/impl/controller/api/websocket/WebsocketServer.java deleted file mode 100644 index 0fdc3c97dc8..00000000000 --- a/edge/src/io/openems/impl/controller/api/websocket/WebsocketServer.java +++ /dev/null @@ -1,148 +0,0 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.controller.api.websocket; - -import java.net.InetSocketAddress; -import java.util.concurrent.ConcurrentHashMap; - -import org.java_websocket.WebSocket; -import org.java_websocket.handshake.ClientHandshake; -import org.java_websocket.server.WebSocketServer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; - -import io.openems.core.ClassRepository; -import io.openems.core.ThingRepository; -import io.openems.core.utilities.websocket.AuthenticatedWebsocketHandler; -import io.openems.core.utilities.websocket.Notification; - -public class WebsocketServer extends WebSocketServer { - - private static Logger log = LoggerFactory.getLogger(WebsocketServer.class); - - private final static ConcurrentHashMap websockets = new ConcurrentHashMap<>(); - private final ThingRepository thingRepository; - private final ClassRepository classRepository; - private final WebsocketApiController controller; - - public WebsocketServer(WebsocketApiController controller, int port) { - super(new InetSocketAddress(port)); - this.thingRepository = ThingRepository.getInstance(); - this.classRepository = ClassRepository.getInstance(); - this.controller = controller; - } - - @Override - public void onClose(WebSocket conn, int code, String reason, boolean remote) { - log.info("User[" + getUserName(conn) + "]: close connection." // - + " Code [" + code + "] Reason [" + reason + "]"); - websockets.remove(conn); - } - - @Override - public void onError(WebSocket conn, Exception ex) { - log.info("User[" + getUserName(conn) + "]: error on connection. " + ex); - } - - @Override - public void onMessage(WebSocket websocket, String message) { - AuthenticatedWebsocketHandler handler = websockets.get(websocket); - JsonObject jMessage = (new JsonParser()).parse(message).getAsJsonObject(); - handler.onMessage(jMessage); - - if (!handler.authenticationIsValid()) { - websockets.remove(websocket); - } - } - - @Override - public void onOpen(WebSocket conn, ClientHandshake handshake) { - log.info("Incoming connection..."); - websockets.put(conn, new AuthenticatedWebsocketHandler(conn, this.controller)); - } - - /** - * Gets the user name of this user, avoiding null - * - * @param conn - * @return - */ - private String getUserName(WebSocket conn) { - if (conn == null) { - return "NOT_CONNECTED"; - } - AuthenticatedWebsocketHandler handler = websockets.get(conn); - if (handler == null) { - return "NOT_CONNECTED"; - } else { - return handler.getUserName(); - } - } - - /** - * Returns true if at least one websocket connection is existing; otherwise false - * - * @return - */ - public static boolean isConnected() { - return !websockets.isEmpty(); - } - - /** - * Send a message to all connected websockets - * - * @param string - * @param timestamp - * - * @param jMessage - */ - public static void broadcastLog(long timestamp, String level, String source, String message) { - websockets.forEach((websocket, handler) -> { - if (handler.authenticationIsValid()) { - handler.sendLog(timestamp, level, source, message); - } - }); - } - - /** - * Send a notification to all connected websockets - * - * @param string - * @param timestamp - * - * @param jMessage - */ - public static void broadcastNotification(Notification notification) { - websockets.forEach((websocket, handler) -> { - if (handler.authenticationIsValid()) { - handler.sendNotification(notification.getType(), notification.getMessage()); - } - }); - } - - @Override - public void onStart() { - - } -} diff --git a/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSession.java b/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSession.java new file mode 100644 index 00000000000..8827c6469e3 --- /dev/null +++ b/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSession.java @@ -0,0 +1,11 @@ +package io.openems.impl.controller.api.websocket.session; + +import io.openems.common.session.Session; + +public class WebsocketApiSession extends Session { + + protected WebsocketApiSession(String token, WebsocketApiSessionData data) { + super(token, data); + } + +} diff --git a/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSessionData.java b/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSessionData.java new file mode 100644 index 00000000000..3603d848031 --- /dev/null +++ b/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSessionData.java @@ -0,0 +1,32 @@ +package io.openems.impl.controller.api.websocket.session; + +import io.openems.api.security.User; +import io.openems.common.session.SessionData; +import io.openems.core.utilities.websocket.EdgeWebsocketHandler; + +public class WebsocketApiSessionData extends SessionData { + private final User user; + private final EdgeWebsocketHandler websocketHandler; + + public WebsocketApiSessionData(User user, EdgeWebsocketHandler websocketHandler) { + this.user = user; + this.websocketHandler = websocketHandler; + } + + public User getUser() { + return user; + } + + public String getRole() { + return this.user.getName(); + } + + public EdgeWebsocketHandler getWebsocketHandler() { + return websocketHandler; + } + + @Override + public String toString() { + return "WebsocketApiSessionData [user=" + user + "]"; + } +} diff --git a/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSessionManager.java b/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSessionManager.java new file mode 100644 index 00000000000..369f0258332 --- /dev/null +++ b/edge/src/io/openems/impl/controller/api/websocket/session/WebsocketApiSessionManager.java @@ -0,0 +1,54 @@ +package io.openems.impl.controller.api.websocket.session; + +import java.util.Optional; + +import org.java_websocket.WebSocket; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.security.User; +import io.openems.common.session.SessionManager; +import io.openems.core.utilities.websocket.EdgeWebsocketHandler; + +public class WebsocketApiSessionManager extends SessionManager { + + private static Logger log = LoggerFactory.getLogger(WebsocketApiSessionManager.class); + + @Override + public WebsocketApiSession _createNewSession(String token, WebsocketApiSessionData data) { + return new WebsocketApiSession(token, data); + } + + private Optional createSessionForUser(Optional userOpt, WebSocket websocket) { + if (userOpt.isPresent()) { + WebsocketApiSessionData data = new WebsocketApiSessionData(userOpt.get(), + new EdgeWebsocketHandler(websocket)); + String token = generateToken(); + WebsocketApiSession session = createNewSession(token, data); + return Optional.of(session); + } + return Optional.empty(); + } + + public Optional authByUserPassword(String username, String password, WebSocket websocket) { + Optional user = User.authenticate(username, password); + return createSessionForUser(user, websocket); + } + + public Optional authByPassword(String password, WebSocket websocket) { + Optional user = User.authenticate(password); + return createSessionForUser(user, websocket); + } + + public Optional authBySession(String token) { + Optional sessionOpt = getSessionByToken(token); + if (sessionOpt.isPresent()) { + WebsocketApiSession session = sessionOpt.get(); + log.info("User[" + session.getData().getUser().getName() + "] authenticated by " + // + "session[" + token + "]."); + } else { + log.info("Authentication by session failed."); + } + return sessionOpt; + } +} diff --git a/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/AvoidTotalChargeController.java b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/AvoidTotalChargeController.java new file mode 100644 index 00000000000..9a640a73157 --- /dev/null +++ b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/AvoidTotalChargeController.java @@ -0,0 +1,176 @@ +package io.openems.impl.controller.asymmetric.avoidtotalcharge; + +/** + * Created by maxo2 on 29.08.2017. + */ + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.Controller; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.security.User; +import io.openems.impl.controller.symmetric.avoidtotalcharge.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +@ThingInfo(title = "Avoid total charge of battery. (Asymmetric)", description = "Provides control over the battery's maximum state of charge at a specific time of day. For symmetric Ess.") +public class AvoidTotalChargeController extends Controller { + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess devices.", type = io.openems.impl.controller.asymmetric.avoidtotalcharge.Ess.class, isArray = true) + public final ConfigChannel> esss = new ConfigChannel>("esss", this); + + @ConfigInfo(title = "Grid Meter", description = "Sets the grid meter.", type = io.openems.impl.controller.asymmetric.avoidtotalcharge.Meter.class, isOptional = false, isArray = false) + public final ConfigChannel gridMeter = new ConfigChannel<>("gridMeter", this); + + @ConfigInfo(title = "Production Meters", description = "Sets the production meter.", type = io.openems.impl.controller.asymmetric.avoidtotalcharge.Meter.class, isOptional = false, isArray = true) + public final ConfigChannel> productionMeters = new ConfigChannel<>("productionMeters", this); + + @ConfigInfo(title = "Graph 1", description = "Sets the socMaxVals.", defaultValue = "[100,100,100,100,100,60,60,60,60,60,60,60,70,80,90,100,100,100,100,100,100,100,100,100]", type = Long[].class, isArray = true, accessLevel = User.OWNER, isOptional = true) + public final ConfigChannel graph1 = new ConfigChannel<>("graph1", this); + //TODO: implement fixed length and min/max values (accessible by OWNER !) + + @ConfigInfo(title = "Graph 2", description = "Sets the socMaxVals.", defaultValue = "[100,100,100,100,100,60,60,60,60,60,60,60,60,60,70,80,90,100,100,100,100,100,100,100]", type = Long[].class, isArray = true, accessLevel = User.OWNER, isOptional = true) + public final ConfigChannel graph2 = new ConfigChannel<>("graph2", this); + //TODO: implement fixed length and min/max values (accessible by OWNER !) + + @ConfigInfo(title = "Critical Percentage", description = "If the productionMeter's power raises above this percentage of its peak value, the graph-value may be neglected.", type = Long.class, accessLevel = User.OWNER, defaultValue = "100", isArray = false) + public final ConfigChannel criticalPercentage = new ConfigChannel("criticalPercentage", this); + //TODO: implement min/max values (accessible by OWNER !) + + @ConfigInfo(title = "Graph 1 active", description = "Activate Graph 1 (If no graph is activated, all values are set to 100)", defaultValue = "true" ,type = Boolean.class, accessLevel = User.OWNER, isArray = false, isOptional = true) + public final ConfigChannel graph1active = new ConfigChannel<>("graph1active", this); + + @ConfigInfo(title = "Graph 2 active", description = "Activate Graph 2 (If no graph is activated, all values are set to 100)", defaultValue = "false" ,type = Boolean.class, accessLevel = User.OWNER, isArray = false, isOptional = true) + public final ConfigChannel graph2active = new ConfigChannel<>("graph2active", this); + + + + /* + * Constructors + */ + public AvoidTotalChargeController() { + super(); + } + + public AvoidTotalChargeController(String thingId) { + super(thingId); + } + + /* + * Methods + */ + @Override + public void run() { + + try { + /** + * calculate the average available charging power + */ + Long avgAllowedCharge = 0L; + + for (io.openems.impl.controller.asymmetric.avoidtotalcharge.Ess ess : esss.value()) { + avgAllowedCharge += ess.allowedCharge.value(); + } + avgAllowedCharge = avgAllowedCharge / esss.value().size(); + + /** + * generate ChargingGraph and get maxWantedSoc value + */ + int graphMode = 0; + Optional g1aOptional = graph1active.valueOptional(); + Optional g2aOptional = graph2active.valueOptional(); + + if (g1aOptional.isPresent() && g1aOptional.get()){ + graphMode = 1; + } else if (g2aOptional.isPresent() && g2aOptional.get()){ + graphMode = 2; + } + + Map m = new HashMap(0); + for (int i = 0; i < 24; i++) { + if (graphMode == 1){ + m.put(i, (double) graph1.value()[i] / 100.0); + }else if (graphMode == 2){ + m.put(i, (double) graph2.value()[i] / 100.0); + }else { + m.put(i, 1.0); + } + } + io.openems.impl.controller.symmetric.avoidtotalcharge.ManualGraph mg = new io.openems.impl.controller.symmetric.avoidtotalcharge.ManualGraph(m); + Long maxWantedSoc = (long) (100 * mg.getCurrentVal()); + + /** + * get the power feeded to the grid relatively to the producer's peak value + */ + Long maxAbsoluteProducablePower = 0L; + Long relativeFeededPower = 0L; + + for (io.openems.impl.controller.asymmetric.avoidtotalcharge.Meter meter : productionMeters.value()){ + maxAbsoluteProducablePower += meter.maxActivePower.value(); + } + + relativeFeededPower = -100 * gridMeter.value().totalActivePower() / maxAbsoluteProducablePower; + + for (Ess ess : esss.value()) { + + + /** + * check if state of charge is above the value specified by the user and deny charging in + * case. However, in case the critical percentage was reached by the + * relativeFeededPower and the spareProducedPower is negative (-> grid is taking the + * maxFeedablePower) allow charging nevertheless. + */ + if (ess.soc.value() >= maxWantedSoc) { + if(relativeFeededPower >= criticalPercentage.value()) { + long spareProducedPower = (long) ((((double) (relativeFeededPower - criticalPercentage.value())) / 100.0) * (double) (-1 * maxAbsoluteProducablePower)); + + if (spareProducedPower < 0L){ + try { + Long totalActivePower = (long) (((double) ess.allowedCharge.value() / (double) avgAllowedCharge) * ((double) spareProducedPower / (double) esss.value().size())); + + ess.setActivePowerL1.pushWrite(getAsymmetricActivePower(totalActivePower,gridMeter.value().activePowerL1.value(),gridMeter.value().totalActivePower())); + ess.setActivePowerL2.pushWrite(getAsymmetricActivePower(totalActivePower,gridMeter.value().activePowerL2.value(),gridMeter.value().totalActivePower())); + ess.setActivePowerL3.pushWrite(getAsymmetricActivePower(totalActivePower,gridMeter.value().activePowerL3.value(),gridMeter.value().totalActivePower())); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } else { + try { + ess.setActivePowerL1.pushWriteMin(getAsymmetricActivePower(0L,gridMeter.value().activePowerL1.value(),gridMeter.value().totalActivePower())); + ess.setActivePowerL2.pushWriteMin(getAsymmetricActivePower(0L,gridMeter.value().activePowerL2.value(),gridMeter.value().totalActivePower())); + ess.setActivePowerL3.pushWriteMin(getAsymmetricActivePower(0L,gridMeter.value().activePowerL3.value(),gridMeter.value().totalActivePower())); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + } else { + try { + ess.setActivePowerL1.pushWriteMin(getAsymmetricActivePower(0L,gridMeter.value().activePowerL1.value(),gridMeter.value().totalActivePower())); + ess.setActivePowerL2.pushWriteMin(getAsymmetricActivePower(0L,gridMeter.value().activePowerL2.value(),gridMeter.value().totalActivePower())); + ess.setActivePowerL3.pushWriteMin(getAsymmetricActivePower(0L,gridMeter.value().activePowerL3.value(),gridMeter.value().totalActivePower())); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + } + } + + } catch (InvalidValueException | IndexOutOfBoundsException e){ + log.error(e.getMessage(),e); + } + } + + private long getAsymmetricActivePower(long symmetricTotalActivePower, long gridMeterActivePower, long gridMeterTotalActivePower){ + /** + * Calculate the value for a phase so that they are balanced. + */ + return (long) (((double)(gridMeterTotalActivePower + symmetricTotalActivePower) / (double) 3) - (double) gridMeterActivePower); + } +} \ No newline at end of file diff --git a/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/ChargingGraph.java b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/ChargingGraph.java new file mode 100644 index 00000000000..2fa9316375b --- /dev/null +++ b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/ChargingGraph.java @@ -0,0 +1,12 @@ +package io.openems.impl.controller.asymmetric.avoidtotalcharge; + +import java.util.Calendar; + +/** + * Created by maxo2 on 29.08.2017. + */ +public interface ChargingGraph { + + Double getAccordingVal(Calendar c); + Double getCurrentVal(); +} diff --git a/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/Ess.java b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/Ess.java new file mode 100644 index 00000000000..e6441d48d83 --- /dev/null +++ b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/Ess.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.asymmetric.avoidtotalcharge; + +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.controller.IsThingMap; +import io.openems.api.controller.ThingMap; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.api.exception.InvalidValueException; + +@IsThingMap(type = AsymmetricEssNature.class) +public class Ess extends ThingMap { + public ReadChannel soc; + public ReadChannel minSoc; + public ReadChannel chargeSoc; + public WriteChannel setActivePowerL1; + public WriteChannel setActivePowerL2; + public WriteChannel setActivePowerL3; + public WriteChannel setReactivePowerL1; + public WriteChannel setReactivePowerL2; + public WriteChannel setReactivePowerL3; + public ReadChannel systemState; + public ReadChannel allowedCharge; + public State currentState = State.NORMAL; + + public enum State { + NORMAL, MINSOC, CHARGESOC, FULL; + } + + public Ess(AsymmetricEssNature ess) { + super(ess); + minSoc = ess.minSoc().required(); + chargeSoc = ess.chargeSoc().required(); + setActivePowerL1 = ess.setActivePowerL1().required(); + setActivePowerL2 = ess.setActivePowerL2().required(); + setActivePowerL3 = ess.setActivePowerL3().required(); + setReactivePowerL1 = ess.setReactivePowerL1().required(); + setReactivePowerL2 = ess.setReactivePowerL2().required(); + setReactivePowerL3 = ess.setReactivePowerL3().required(); + soc = ess.soc().required(); + systemState = ess.systemState().required(); + allowedCharge = ess.allowedCharge().required(); + } + + public long useableSoc() throws InvalidValueException { + return soc.value() - minSoc.value(); + } +} diff --git a/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/ManualGraph.java b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/ManualGraph.java new file mode 100644 index 00000000000..416005f1de3 --- /dev/null +++ b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/ManualGraph.java @@ -0,0 +1,48 @@ +package io.openems.impl.controller.asymmetric.avoidtotalcharge; + +import java.util.Calendar; +import java.util.Date; +import java.util.Map; + +/** + * Created by maxo2 on 29.08.2017. + */ +public class ManualGraph implements ChargingGraph { + + private Map percentages; + + public ManualGraph(Map percentages) { + for(Map.Entry entry: percentages.entrySet()){ + if(entry.getKey() < 0 || entry.getKey() >= 24 || entry.getValue() < 0 || entry.getValue() > 1){ + throw new IllegalArgumentException(); + } + } + this.percentages = percentages; + } + + public Double getAccordingVal(Calendar c){ + return calcVal(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE)); + } + + public Double getCurrentVal(){ + Calendar c = Calendar.getInstance(); + c.setTime(new Date()); + return calcVal(c.get(Calendar.HOUR_OF_DAY),c.get(Calendar.MINUTE)); + } + + private Double calcVal(int h, int m){ + Double v1 = percentages.get(h); + Double v2 = percentages.get(h < 23 ? h++ : 0); + double diff = v2 - v1; + return v1 + (m / 60) * diff; + } + + public void setPercentage(int hourOfDay, Double percentage) throws IllegalArgumentException { + if(hourOfDay < 0 || hourOfDay >= 24 || percentage < 0 || percentage > 1){ + throw new IllegalArgumentException(); + }else{ + percentages.put(hourOfDay,percentage); + } + } + +} diff --git a/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/Meter.java b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/Meter.java new file mode 100644 index 00000000000..c4daf3a4d00 --- /dev/null +++ b/edge/src/io/openems/impl/controller/asymmetric/avoidtotalcharge/Meter.java @@ -0,0 +1,48 @@ +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.asymmetric.avoidtotalcharge; + +import io.openems.api.channel.ReadChannel; +import io.openems.api.controller.IsThingMap; +import io.openems.api.controller.ThingMap; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; + +@IsThingMap(type = AsymmetricMeterNature.class) +public class Meter extends ThingMap { + + public final ReadChannel activePowerL1; + public final ReadChannel activePowerL2; + public final ReadChannel activePowerL3; + public final ReadChannel maxActivePower; + + public Meter(AsymmetricMeterNature meter) { + super(meter); + activePowerL1 = meter.activePowerL1().required(); + activePowerL2 = meter.activePowerL1().required(); + activePowerL3 = meter.activePowerL3().required(); + maxActivePower = meter.maxActivePower().required(); + } + + public final Long totalActivePower() throws io.openems.api.exception.InvalidValueException { + return activePowerL1.value() + activePowerL2.value() + activePowerL3.value(); + } + +} diff --git a/edge/src/io/openems/impl/controller/asymmetric/balancing/BalancingController.java b/edge/src/io/openems/impl/controller/asymmetric/balancing/BalancingController.java index 7cca9f06323..7154a4b8458 100644 --- a/edge/src/io/openems/impl/controller/asymmetric/balancing/BalancingController.java +++ b/edge/src/io/openems/impl/controller/asymmetric/balancing/BalancingController.java @@ -1,311 +1,324 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.controller.asymmetric.balancing; - -import java.util.Collections; -import java.util.List; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.controller.Controller; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.WriteChannelException; -import io.openems.core.utilities.ControllerUtils; - -@ThingInfo(title = "Self-consumption optimization (Asymmetric)", description = "Tries to keep the grid meter on zero. For asymmetric Ess.") -public class BalancingController extends Controller { - - /* - * Constructors - */ - public BalancingController() { - super(); - } - - public BalancingController(String thingId) { - super(thingId); - } - - /* - * Config - */ - @ConfigInfo(title = "Cos-Phi", type = Double.class, defaultValue = "0.95") - public ConfigChannel cosPhi = new ConfigChannel("cosPhi", this); - - @ConfigInfo(title = "Ess", description = "Sets the Ess devices.", type = Ess.class, isArray = true) - public ConfigChannel> esss = new ConfigChannel>("esss", this); - - @ConfigInfo(title = "Grid-Meter", description = "Sets the grid meter.", type = Meter.class) - public ConfigChannel meter = new ConfigChannel("meter", this); - - /* - * Fields - */ - private long[][] lastWriteValues = new long[3][5]; - private int index = 0; - - /* - * Methods - */ - @Override - public void run() { - try { - for (Ess ess : esss.value()) { - ess.setWorkState.pushWriteFromLabel(EssNature.START); - } - long[] calculatedPowers = new long[3]; - // calculateRequiredPower - Meter meter = this.meter.value(); - calculatedPowers[0] = meter.activePowerL1.value(); - calculatedPowers[1] = meter.activePowerL2.value(); - calculatedPowers[2] = meter.activePowerL3.value(); - for (Ess ess : esss.value()) { - calculatedPowers[0] += ess.activePowerL1.value(); - calculatedPowers[1] += ess.activePowerL2.value(); - calculatedPowers[2] += ess.activePowerL3.value(); - } - for (int i = 0; i < 3; i++) { - lastWriteValues[i][index] = calculatedPowers[i]; - calculatedPowers[i] = getAvgPower(i + 1); - } - index++; - index %= lastWriteValues[0].length; - // Calculate required sum values - long useableSoc = 0; - for (Ess ess : esss.value()) { - useableSoc += ess.useableSoc(); - } - // Loop each Phase - for (int i = 1; i <= 3; i++) { - long absolutePower = Math.abs(calculatedPowers[0]) + Math.abs(calculatedPowers[1]) - + Math.abs(calculatedPowers[2]); - double percentage = (double) calculatedPowers[i - 1] / absolutePower; - long maxChargePowerPhase = 0L; - long maxDischargePowerPhase = 0L; - for (Ess ess : esss.value()) { - Tupel minMax = calculateMinMaxValues(ess, percentage, cosPhi.value()); - maxDischargePowerPhase += minMax.b; - maxChargePowerPhase += minMax.a; - try { - ess.getSetActivePower(i).pushWriteMax(minMax.b); - } catch (WriteChannelException e) { - log.debug(e.getMessage()); - } - try { - ess.getSetActivePower(i).pushWriteMin(minMax.a); - } catch (WriteChannelException e) { - log.debug(e.getMessage()); - } - } - // reduce Power to possible power - if (calculatedPowers[i - 1] > maxDischargePowerPhase) { - calculatedPowers[i - 1] = maxDischargePowerPhase; - } else if (calculatedPowers[i - 1] < maxChargePowerPhase) { - calculatedPowers[i - 1] = maxChargePowerPhase; - } - calculatePower(calculatedPowers[i - 1], maxDischargePowerPhase, maxChargePowerPhase, i, useableSoc); - } - for (Ess ess : esss.value()) { - log.debug(ess.getSetValueLog()); - } - - } catch (InvalidValueException | WriteChannelException e) { - log.error(e.getMessage()); - } - } - - private long getAvgPower(int phase) { - int i = index; - long sum = 0; - do { - sum += lastWriteValues[phase - 1][i]; - i++; - i %= lastWriteValues[phase - 1].length; - } while (i != index); - return sum / lastWriteValues[phase - 1].length; - } - - /** - * calculates active and reactive power for a phase and set the calculated values to the ess - * - * @param calculatedPower - * @param maxDischargePower - * @param maxChargePower - * @param phase - * @param useableSoc - * @throws InvalidValueException - * @throws WriteChannelException - */ - private void calculatePower(long calculatedPower, long maxDischargePower, long maxChargePower, int phase, - long useableSoc) throws InvalidValueException, WriteChannelException { - if (calculatedPower >= 0) { - /* - * Discharge - */ - if (calculatedPower > maxDischargePower) { - calculatedPower = maxDischargePower; - } - // sort ess by useableSoc asc - Collections.sort(esss.value(), (a, b) -> { - try { - return (int) (a.useableSoc() - b.useableSoc()); - } catch (InvalidValueException e) { - log.error(e.getMessage()); - return 0; - } - }); - } else { - /* - * Charge - */ - if (calculatedPower < maxChargePower) { - calculatedPower = maxChargePower; - } - /* - * sort ess by 100 - useabelSoc - * 100 - 90 = 10 - * 100 - 45 = 55 - * 100 - (- 5) = 105 - * => ess with negative useableSoc will be charged much more then one with positive useableSoc - */ - Collections.sort(esss.value(), (a, b) -> { - try { - return (int) ((100 - a.useableSoc()) - (100 - b.useableSoc())); - } catch (InvalidValueException e) { - log.error(e.getMessage()); - return 0; - } - }); - } - - for (int i = 0; i < esss.value().size(); i++) { - Ess ess = esss.value().get(i); - // calculate minimal power needed to fulfill the calculatedPower - long minPower = 0; - long maxPower = 0; - long power = 0; - if (calculatedPower >= 0) { - /* - * Discharge - */ - minPower = calculatedPower; - for (int j = i + 1; j < esss.value().size(); j++) { - if (esss.value().get(j).useableSoc() > 0) { - minPower -= esss.value().get(j).getSetActivePower(phase).writeMax() - .orElse(esss.value().get(j).allowedDischarge.value() / 3); - } - } - if (minPower < 0) { - minPower = 0; - } - // check maximal power to avoid larger charges then calculatedPower - maxPower = ess.getSetActivePower(phase).writeMax().orElse(ess.allowedCharge.value() / 3); - if (calculatedPower < maxPower) { - maxPower = calculatedPower; - } - double diff = maxPower - minPower; - /* - * weight the range of possible power by the useableSoc - * if the useableSoc is negative the ess will be charged - */ - power = (long) (Math.ceil(minPower + diff / useableSoc * ess.useableSoc())); - } else { - /* - * Charge - */ - minPower = calculatedPower; - for (int j = i + 1; j < esss.value().size(); j++) { - minPower -= esss.value().get(j).getSetActivePower(phase).writeMin() - .orElse(esss.value().get(j).allowedCharge.value() / 3); - } - if (minPower > 0) { - minPower = 0; - } - // check maximal power to avoid larger charges then calculatedPower - maxPower = ess.getSetActivePower(phase).writeMin().orElse(ess.allowedCharge.value() / 3); - if (calculatedPower > maxPower) { - maxPower = calculatedPower; - } - double diff = maxPower - minPower; - /* - * weight the range of possible power by the useableSoc - * if the useableSoc is negative the ess will be charged - */ - power = (long) (Math - .ceil(minPower + diff / (esss.value().size() * 100 - useableSoc) * (100 - ess.useableSoc()))); - } - - if (power <= 100 && power >= -100) { - power = 0; - } - - long reactivePower = ControllerUtils.calculateReactivePower(power, cosPhi.value()); - - calculatedPower -= power; - - ess.getSetActivePower(phase).pushWrite(power); - ess.getSetReactivePower(phase).pushWrite(reactivePower); - } - } - - /** - * Calculates minimal and maximal value for an Phase - * with the reactivePower - * - * @param ess - * @param percentage - * of the power of the phase in realation to the whole power - * @param cosPhi - * @return a Tupel with value a minPower and value b maxPower - * @throws InvalidValueException - */ - private Tupel calculateMinMaxValues(Ess ess, double percentage, double cosPhi) throws InvalidValueException { - long maxPower = 0; - long minPower = 0; - percentage = Math.abs(percentage); - - maxPower = (long) ((double) ess.allowedDischarge.value() * percentage); - - minPower = (long) ((double) ess.allowedCharge.value() * percentage); - - if (ControllerUtils.calculateApparentPower(minPower, cosPhi) > ess.allowedApparent.value() / 3) { - minPower = ControllerUtils.calculateActivePowerFromApparentPower(ess.allowedApparent.value() / 3, cosPhi) - * -1; - } - if (ControllerUtils.calculateApparentPower(maxPower, cosPhi) > ess.allowedApparent.value() / 3) { - maxPower = ControllerUtils.calculateActivePowerFromApparentPower(ess.allowedApparent.value() / 3, cosPhi); - } - return new Tupel(minPower, maxPower); - } - - private class Tupel { - final T a; - final T b; - - private Tupel(T a, T b) { - this.a = a; - this.b = b; - } - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.asymmetric.balancing; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.Controller; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.WriteChannelException; +import io.openems.core.utilities.ControllerUtils; + +@ThingInfo(title = "Self-consumption optimization (Asymmetric)", description = "Tries to keep the grid meter on zero. For asymmetric Ess.") +public class BalancingController extends Controller { + + /* + * Constructors + */ + public BalancingController() { + super(); + } + + public BalancingController(String thingId) { + super(thingId); + } + + /* + * Config + */ + @ConfigInfo(title = "Cos-Phi", type = Double.class, defaultValue = "0.95") + public ConfigChannel cosPhi = new ConfigChannel("cosPhi", this); + + @ConfigInfo(title = "Ess", description = "Sets the Ess devices.", type = Ess.class, isArray = true) + public ConfigChannel> esss = new ConfigChannel>("esss", this); + + @ConfigInfo(title = "Grid-Meter", description = "Sets the grid meter.", type = Meter.class) + public ConfigChannel meter = new ConfigChannel("meter", this); + + /* + * Fields + */ + private long[][] lastWriteValues = new long[3][5]; + private int index = 0; + + /* + * Methods + */ + @Override + public void run() { + try { + for (Ess ess : esss.value()) { + ess.setWorkState.pushWriteFromLabel(EssNature.START); + } + long[] calculatedPowers = new long[3]; + // calculateRequiredPower + Meter meter = this.meter.value(); + calculatedPowers[0] = meter.activePowerL1.value(); + calculatedPowers[1] = meter.activePowerL2.value(); + calculatedPowers[2] = meter.activePowerL3.value(); + for (Ess ess : esss.value()) { + calculatedPowers[0] += ess.activePowerL1.value(); + calculatedPowers[1] += ess.activePowerL2.value(); + calculatedPowers[2] += ess.activePowerL3.value(); + } + for (int i = 0; i < 3; i++) { + lastWriteValues[i][index] = calculatedPowers[i]; + calculatedPowers[i] = getAvgPower(i + 1); + } + index++; + index %= lastWriteValues[0].length; + // Calculate required sum values + long useableSoc = 0; + for (Ess ess : esss.value()) { + useableSoc += ess.useableSoc(); + } + // Loop each Phase + for (int i = 1; i <= 3; i++) { + long absolutePower = Math.abs(calculatedPowers[0]) + Math.abs(calculatedPowers[1]) + + Math.abs(calculatedPowers[2]); + double percentage = (double) calculatedPowers[i - 1] / absolutePower; + long maxChargePowerPhase = 0L; + long maxDischargePowerPhase = 0L; + for (Ess ess : esss.value()) { + Tupel minMax = calculateMinMaxValues(ess, percentage, cosPhi.value(), i); + maxDischargePowerPhase += minMax.b; + maxChargePowerPhase += minMax.a; + try { + ess.getSetActivePower(i).pushWriteMax(minMax.b); + } catch (WriteChannelException e) { + log.debug(e.getMessage()); + } + try { + ess.getSetActivePower(i).pushWriteMin(minMax.a); + } catch (WriteChannelException e) { + log.debug(e.getMessage()); + } + } + // reduce Power to possible power + if (calculatedPowers[i - 1] > maxDischargePowerPhase) { + calculatedPowers[i - 1] = maxDischargePowerPhase; + } else if (calculatedPowers[i - 1] < maxChargePowerPhase) { + calculatedPowers[i - 1] = maxChargePowerPhase; + } + calculatePower(calculatedPowers[i - 1], maxDischargePowerPhase, maxChargePowerPhase, i, useableSoc); + } + for (Ess ess : esss.value()) { + log.debug(ess.getSetValueLog()); + } + + } catch (InvalidValueException | WriteChannelException e) { + log.error(e.getMessage()); + } + } + + private long getAvgPower(int phase) { + int i = index; + long sum = 0; + do { + sum += lastWriteValues[phase - 1][i]; + i++; + i %= lastWriteValues[phase - 1].length; + } while (i != index); + return sum / lastWriteValues[phase - 1].length; + } + + /** + * calculates active and reactive power for a phase and set the calculated values to the ess + * + * @param calculatedPower + * @param maxDischargePower + * @param maxChargePower + * @param phase + * @param useableSoc + * @throws InvalidValueException + * @throws WriteChannelException + */ + private void calculatePower(long calculatedPower, long maxDischargePower, long maxChargePower, int phase, + long useableSoc) throws InvalidValueException, WriteChannelException { + if (calculatedPower >= 0) { + /* + * Discharge + */ + if (calculatedPower > maxDischargePower) { + calculatedPower = maxDischargePower; + } + // sort ess by useableSoc asc + Collections.sort(esss.value(), (a, b) -> { + try { + return (int) (a.useableSoc() - b.useableSoc()); + } catch (InvalidValueException e) { + log.error(e.getMessage()); + return 0; + } + }); + } else { + /* + * Charge + */ + if (calculatedPower < maxChargePower) { + calculatedPower = maxChargePower; + } + /* + * sort ess by 100 - useabelSoc + * 100 - 90 = 10 + * 100 - 45 = 55 + * 100 - (- 5) = 105 + * => ess with negative useableSoc will be charged much more then one with positive useableSoc + */ + Collections.sort(esss.value(), (a, b) -> { + try { + return (int) ((100 - a.useableSoc()) - (100 - b.useableSoc())); + } catch (InvalidValueException e) { + log.error(e.getMessage()); + return 0; + } + }); + } + + for (int i = 0; i < esss.value().size(); i++) { + Ess ess = esss.value().get(i); + // calculate minimal power needed to fulfill the calculatedPower + long minPower = 0; + long maxPower = 0; + long power = 0; + if (calculatedPower >= 0) { + /* + * Discharge + */ + minPower = calculatedPower; + for (int j = i + 1; j < esss.value().size(); j++) { + if (esss.value().get(j).useableSoc() > 0) { + minPower -= esss.value().get(j).getSetActivePower(phase).writeMax() + .orElse(esss.value().get(j).allowedDischarge.value() / 3); + } + } + if (minPower < 0) { + minPower = 0; + } + // check maximal power to avoid larger charges then calculatedPower + maxPower = ess.getSetActivePower(phase).writeMax().orElse(ess.allowedCharge.value() / 3); + if (calculatedPower < maxPower) { + maxPower = calculatedPower; + } + double diff = maxPower - minPower; + /* + * weight the range of possible power by the useableSoc + * if the useableSoc is negative the ess will be charged + */ + power = (long) (Math.ceil(minPower + diff / useableSoc * ess.useableSoc())); + } else { + /* + * Charge + */ + minPower = calculatedPower; + for (int j = i + 1; j < esss.value().size(); j++) { + minPower -= esss.value().get(j).getSetActivePower(phase).writeMin() + .orElse(esss.value().get(j).allowedCharge.value() / 3); + } + if (minPower > 0) { + minPower = 0; + } + // check maximal power to avoid larger charges then calculatedPower + maxPower = ess.getSetActivePower(phase).writeMin().orElse(ess.allowedCharge.value() / 3); + if (calculatedPower > maxPower) { + maxPower = calculatedPower; + } + double diff = maxPower - minPower; + /* + * weight the range of possible power by the useableSoc + * if the useableSoc is negative the ess will be charged + */ + power = (long) (Math + .ceil(minPower + diff / (esss.value().size() * 100 - useableSoc) * (100 - ess.useableSoc()))); + } + + if (power <= 100 && power >= -100) { + power = 0; + } + + long reactivePower = ControllerUtils.calculateReactivePower(power, cosPhi.value()); + + calculatedPower -= power; + + ess.getSetActivePower(phase).pushWrite(power); + ess.getSetReactivePower(phase).pushWrite(reactivePower); + } + } + + /** + * Calculates minimal and maximal value for an Phase + * with the reactivePower + * + * @param ess + * @param percentage + * of the power of the phase in realation to the whole power + * @param cosPhi + * @return a Tupel with value a minPower and value b maxPower + * @throws InvalidValueException + */ + private Tupel calculateMinMaxValues(Ess ess, double percentage, double cosPhi, int phase) + throws InvalidValueException { + long maxPower = 0; + long minPower = 0; + percentage = Math.abs(percentage); + + Optional writeMin = ess.getSetActivePower(phase).writeMin(); + Optional writeMax = ess.getSetActivePower(phase).writeMax(); + + maxPower = (long) ((double) ess.allowedDischarge.value() * percentage); + + if (writeMax.isPresent() && maxPower > writeMax.get()) { + maxPower = writeMax.get(); + } + + minPower = (long) ((double) ess.allowedCharge.value() * percentage); + + if (writeMin.isPresent() && minPower < writeMin.get()) { + minPower = writeMin.get(); + } + + if (ControllerUtils.calculateApparentPower(minPower, cosPhi) > ess.allowedApparent.value() / 3) { + minPower = ControllerUtils.calculateActivePowerFromApparentPower(ess.allowedApparent.value() / 3, cosPhi) + * -1; + } + if (ControllerUtils.calculateApparentPower(maxPower, cosPhi) > ess.allowedApparent.value() / 3) { + maxPower = ControllerUtils.calculateActivePowerFromApparentPower(ess.allowedApparent.value() / 3, cosPhi); + } + return new Tupel(minPower, maxPower); + } + + private class Tupel { + final T a; + final T b; + + private Tupel(T a, T b) { + this.a = a; + this.b = b; + } + } + +} diff --git a/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/AvoidTotalChargeController.java b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/AvoidTotalChargeController.java new file mode 100644 index 00000000000..8ec3a762d00 --- /dev/null +++ b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/AvoidTotalChargeController.java @@ -0,0 +1,164 @@ +package io.openems.impl.controller.symmetric.avoidtotalcharge; + +/** + * Created by maxo2 on 29.08.2017. + */ +import java.util.*; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.Controller; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.meter.MeterNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.WriteChannelException; +import io.openems.api.security.User; +import io.openems.impl.controller.symmetric.avoidtotalcharge.Ess.State; +import io.openems.impl.controller.symmetric.balancing.Meter; + +@ThingInfo(title = "Avoid total charge of battery. (Symmetric)", description = "Provides control over the battery's maximum state of charge at a specific time of day. For symmetric Ess.") +public class AvoidTotalChargeController extends Controller { + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess devices.", type = io.openems.impl.controller.symmetric.avoidtotalcharge.Ess.class, isArray = true) + public final ConfigChannel> esss = new ConfigChannel>("esss", this); + + @ConfigInfo(title = "Grid Meter", description = "Sets the grid meter.", type = io.openems.impl.controller.symmetric.avoidtotalcharge.Meter.class, isOptional = false, isArray = false) + public final ConfigChannel gridMeter = new ConfigChannel<>("gridMeter", this); + + @ConfigInfo(title = "Production Meters", description = "Sets the production meter.", type = io.openems.impl.controller.symmetric.avoidtotalcharge.Meter.class, isOptional = false, isArray = true) + public final ConfigChannel> productionMeters = new ConfigChannel<>("productionMeters", this); + + @ConfigInfo(title = "Graph 1", description = "Sets the socMaxVals.", defaultValue = "[100,100,100,100,100,60,60,60,60,60,60,60,70,80,90,100,100,100,100,100,100,100,100,100]", type = Long[].class, isArray = true, accessLevel = User.OWNER, isOptional = true) + public final ConfigChannel graph1 = new ConfigChannel<>("graph1", this); + //TODO: implement fixed length and min/max values (accessible by OWNER !) + + @ConfigInfo(title = "Graph 2", description = "Sets the socMaxVals.", defaultValue = "[100,100,100,100,100,60,60,60,60,60,60,60,60,60,70,80,90,100,100,100,100,100,100,100]", type = Long[].class, isArray = true, accessLevel = User.OWNER, isOptional = true) + public final ConfigChannel graph2 = new ConfigChannel<>("graph2", this); + //TODO: implement fixed length and min/max values (accessible by OWNER !) + + @ConfigInfo(title = "Critical Percentage", description = "If the productionMeter's power raises above this percentage of its peak value, the graph-value may be neglected.", type = Long.class, accessLevel = User.OWNER, defaultValue = "100", isArray = false) + public final ConfigChannel criticalPercentage = new ConfigChannel("criticalPercentage", this); + //TODO: implement min/max values (accessible by OWNER !) + + @ConfigInfo(title = "Graph 1 active", description = "Activate Graph 1 (If no graph is activated, all values are set to 100)", defaultValue = "true" ,type = Boolean.class, accessLevel = User.OWNER, isArray = false, isOptional = true) + public final ConfigChannel graph1active = new ConfigChannel<>("graph1active", this); + + @ConfigInfo(title = "Graph 2 active", description = "Activate Graph 2 (If no graph is activated, all values are set to 100)", defaultValue = "false" ,type = Boolean.class, accessLevel = User.OWNER, isArray = false, isOptional = true) + public final ConfigChannel graph2active = new ConfigChannel<>("graph2active", this); + + + + + /* + * Constructors + */ + public AvoidTotalChargeController() { + super(); + } + + public AvoidTotalChargeController(String thingId) { + super(thingId); + } + + /* + * Methods + */ + @Override + public void run() { + + try { + /** + * calculate the average available charging power + */ + Long avgAllowedCharge = 0L; + + for (Ess ess : esss.value()) { + avgAllowedCharge += ess.allowedCharge.value(); + } + avgAllowedCharge = avgAllowedCharge / esss.value().size(); + + /** + * generate ChargingGraph and get maxWantedSoc value + */ + int graphMode = 0; + Optional g1aOptional = graph1active.valueOptional(); + Optional g2aOptional = graph2active.valueOptional(); + + if (g1aOptional.isPresent() && g1aOptional.get()){ + graphMode = 1; + } else if (g2aOptional.isPresent() && g2aOptional.get()){ + graphMode = 2; + } + + Map m = new HashMap(0); + for (int i = 0; i < 24; i++) { + if (graphMode == 1){ + m.put(i, (double) graph1.value()[i] / 100.0); + }else if (graphMode == 2){ + m.put(i, (double) graph2.value()[i] / 100.0); + }else { + m.put(i, 1.0); + } + } + ManualGraph mg = new ManualGraph(m); + Long maxWantedSoc = (long) (100 * mg.getCurrentVal()); + + /** + * get the power feeded to the grid relatively to the producer's peak value + */ + Long maxAbsoluteProducablePower = 0L; + Long relativeFeededPower = 0L; + + for (io.openems.impl.controller.symmetric.avoidtotalcharge.Meter meter : productionMeters.value()){ + maxAbsoluteProducablePower += meter.maxActivePower.value(); + } + + relativeFeededPower = -100 * gridMeter.value().activePower.value() / maxAbsoluteProducablePower; + + for (Ess ess : esss.value()) { + + + /** + * check if state of charge is above the value specified by the user and deny charging in + * case. However, in case the critical percentage was reached by the + * relativeFeededPower and the spareProducedPower is negative (-> grid is taking the + * maxFeedablePower) allow charging nevertheless. + */ + if (ess.soc.value() >= maxWantedSoc) { + if(relativeFeededPower >= criticalPercentage.value()) { + long spareProducedPower = (long) ((((double) (relativeFeededPower - criticalPercentage.value())) / 100.0) * (double) (-1 * maxAbsoluteProducablePower)); + + if (spareProducedPower < 0L){ + try { + Long totalActivePower = (long) (((double) ess.allowedCharge.value() / (double) avgAllowedCharge) * ((double) spareProducedPower / (double) esss.value().size())); + + ess.setActivePower.pushWrite(totalActivePower); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } else { + try { + ess.setActivePower.pushWriteMin(0L); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + } else { + try { + ess.setActivePower.pushWriteMin(0L); + } catch (Exception e) { + log.error(e.getMessage(),e); + } + } + } + } + + } catch (InvalidValueException | IndexOutOfBoundsException e){ + log.error(e.getMessage(),e); + } + } +} \ No newline at end of file diff --git a/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/ChargingGraph.java b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/ChargingGraph.java new file mode 100644 index 00000000000..7a0266d0358 --- /dev/null +++ b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/ChargingGraph.java @@ -0,0 +1,12 @@ +package io.openems.impl.controller.symmetric.avoidtotalcharge; + +import java.util.Calendar; + +/** + * Created by maxo2 on 29.08.2017. + */ +public interface ChargingGraph { + + Double getAccordingVal(Calendar c); + Double getCurrentVal(); +} diff --git a/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/Ess.java b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/Ess.java new file mode 100644 index 00000000000..134eeb42aa1 --- /dev/null +++ b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/Ess.java @@ -0,0 +1,62 @@ +package io.openems.impl.controller.symmetric.avoidtotalcharge; + +/** + * Created by maxo2 on 29.08.2017. + */ + +import java.util.Optional; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.controller.IsThingMap; +import io.openems.api.controller.ThingMap; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.core.utilities.hysteresis.Hysteresis; + +@IsThingMap(type = SymmetricEssNature.class) +public class Ess extends ThingMap { + + public final ReadChannel minSoc; + public final WriteChannel setActivePower; + public final ReadChannel soc; + public final ReadChannel systemState; + public int maxPowerPercent = 100; + public final ReadChannel allowedDischarge; + public final ReadChannel allowedCharge; + public final ReadChannel chargeSoc; + public Hysteresis socMinHysteresis; + public State + currentState = State.NORMAL; + + public enum State { + NORMAL, MINSOC, CHARGESOC, FULL; + } + + public Ess(SymmetricEssNature ess) { + super(ess); + setActivePower = ess.setActivePower().required(); + systemState = ess.systemState().required(); + soc = ess.soc().required(); + minSoc = ess.minSoc().required(); + allowedDischarge = ess.allowedDischarge().required(); + allowedCharge = ess.allowedCharge().required(); + chargeSoc = ess.chargeSoc().required(); + ChannelChangeListener hysteresisCreator = new ChannelChangeListener() { + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (minSoc.valueOptional().isPresent() && chargeSoc.valueOptional().isPresent()) { + socMinHysteresis = new Hysteresis(chargeSoc.valueOptional().get(), minSoc.valueOptional().get()); + } else if (minSoc.valueOptional().isPresent()) { + socMinHysteresis = new Hysteresis(minSoc.valueOptional().get() - 3, minSoc.valueOptional().get()); + } + } + }; + minSoc.addChangeListener(hysteresisCreator); + chargeSoc.addChangeListener(hysteresisCreator); + + hysteresisCreator.channelChanged(null, null, null); + } +} \ No newline at end of file diff --git a/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/ManualGraph.java b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/ManualGraph.java new file mode 100644 index 00000000000..a32ea4e12bc --- /dev/null +++ b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/ManualGraph.java @@ -0,0 +1,48 @@ +package io.openems.impl.controller.symmetric.avoidtotalcharge; + +import java.util.Calendar; +import java.util.Date; +import java.util.Map; + +/** + * Created by maxo2 on 29.08.2017. + */ +public class ManualGraph implements ChargingGraph { + + private Map percentages; + + public ManualGraph(Map percentages) { + for(Map.Entry entry: percentages.entrySet()){ + if(entry.getKey() < 0 || entry.getKey() >= 24 || entry.getValue() < 0 || entry.getValue() > 1){ + throw new IllegalArgumentException(); + } + } + this.percentages = percentages; + } + + public Double getAccordingVal(Calendar c){ + return calcVal(c.get(Calendar.HOUR_OF_DAY), c.get(Calendar.MINUTE)); + } + + public Double getCurrentVal(){ + Calendar c = Calendar.getInstance(); + c.setTime(new Date()); + return calcVal(c.get(Calendar.HOUR_OF_DAY),c.get(Calendar.MINUTE)); + } + + private Double calcVal(int h, int m){ + Double v1 = percentages.get(h); + Double v2 = percentages.get(h < 23 ? h++ : 0); + double diff = v2 - v1; + return v1 + (m / 60) * diff; + } + + public void setPercentage(int hourOfDay, Double percentage) throws IllegalArgumentException { + if(hourOfDay < 0 || hourOfDay >= 24 || percentage < 0 || percentage > 1){ + throw new IllegalArgumentException(); + }else{ + percentages.put(hourOfDay,percentage); + } + } + +} diff --git a/edge/src/io/openems/api/security/Session.java b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/Meter.java similarity index 51% rename from edge/src/io/openems/api/security/Session.java rename to edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/Meter.java index 463ba7e604c..03c0d0978cc 100644 --- a/edge/src/io/openems/api/security/Session.java +++ b/edge/src/io/openems/impl/controller/symmetric/avoidtotalcharge/Meter.java @@ -1,65 +1,40 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.api.security; - -import java.math.BigInteger; -import java.security.SecureRandom; - -import io.openems.core.utilities.SecureRandomSingleton; - -public class Session { - - private final static int SESSION_ID_LENGTH = 130; - - private final String token; - private final User user; - // TODO: timeout, IP address,... - - public Session(String token, User user) { - this.token = token; - this.user = user; - } - - public Session(User user) { - this(generateSessionId(), user); - } - - public String getToken() { - return token; - } - - public User getUser() { - return user; - } - - public boolean isValid() { - if (this.token != null && this.user != null) { - return true; - } - return false; - } - - private static String generateSessionId() { - // Source: http://stackoverflow.com/a/41156 - SecureRandom sr = SecureRandomSingleton.getInstance(); - return new BigInteger(SESSION_ID_LENGTH, sr).toString(32); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.symmetric.avoidtotalcharge; + +import io.openems.api.channel.ReadChannel; +import io.openems.api.controller.IsThingMap; +import io.openems.api.controller.ThingMap; +import io.openems.api.device.nature.meter.SymmetricMeterNature; + +@IsThingMap(type = SymmetricMeterNature.class) +public class Meter extends ThingMap { + + public final ReadChannel activePower; + public final ReadChannel maxActivePower; + + public Meter(SymmetricMeterNature meter) { + super(meter); + activePower = meter.activePower().required(); + maxActivePower = meter.maxActivePower().required(); + } + +} diff --git a/edge/src/io/openems/impl/controller/symmetric/balancingoffset/BalancingOffsetController.java b/edge/src/io/openems/impl/controller/symmetric/balancingoffset/BalancingOffsetController.java index 63dfa2ec1ed..e949f656480 100644 --- a/edge/src/io/openems/impl/controller/symmetric/balancingoffset/BalancingOffsetController.java +++ b/edge/src/io/openems/impl/controller/symmetric/balancingoffset/BalancingOffsetController.java @@ -1,138 +1,138 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.controller.symmetric.balancingoffset; - -import java.util.Optional; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.controller.Controller; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.WriteChannelException; - -/* - * this Controller calculates the power consumption of the house and charges or discharges the storages to reach zero power consumption from the grid - */ -@ThingInfo(title = "Balancing offset (Symmetric)", description = "Tries to keep the grid meter within an offset. For symmetric Ess.") -public class BalancingOffsetController extends Controller { - - /* - * Constructors - */ - public BalancingOffsetController() { - super(); - init(); - } - - public BalancingOffsetController(String thingId) { - super(thingId); - init(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess devices.", type = Ess.class) - public final ConfigChannel ess = new ConfigChannel<>("ess", this); - - @ConfigInfo(title = "Grid-Meter", description = "Sets the grid meter.", type = Meter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - @ConfigInfo(title = "Offset ActivePower", description = "The offset of the active power from zero to hold on the grid meter.", type = Integer.class) - public final ConfigChannel activePowerOffset = new ConfigChannel<>("activePowerOffset", this); - - @ConfigInfo(title = "Offset ReactivePower", description = "The offset of the reactive power from zero to hold on the grid meter.", type = Integer.class) - public final ConfigChannel reactivePowerOffset = new ConfigChannel<>("reactivePowerOffset", this); - - @ConfigInfo(title = "Enable ActivePower", description = "Indicates if active power is enabled.", type = Boolean.class, defaultValue = "true") - public final ConfigChannel activePowerActivated = new ConfigChannel("activePowerActivated", this); - - @ConfigInfo(title = "Enable ReactivePower", description = "Indicates if reactive power is enabled.", type = Boolean.class, defaultValue = "true") - public final ConfigChannel reactivePowerActivated = new ConfigChannel("reactivePowerActivated", - this); - - /* - * Methods - */ - @Override - public void init() { - activePowerActivated.addChangeListener(new ChannelChangeListener() { - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - try { - if (!activePowerActivated.value()) { - ess.value().setActivePower.pushWrite(0L); - } - } catch (WriteChannelException | InvalidValueException e) { - log.error(e.getMessage()); - } - } - }); - reactivePowerActivated.addChangeListener(new ChannelChangeListener() { - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - try { - if (!reactivePowerActivated.value()) { - ess.value().setReactivePower.pushWrite(0L); - } - } catch (WriteChannelException | InvalidValueException e) { - log.error(e.getMessage()); - } - } - }); - } - - @Override - public void run() { - try { - Ess ess = this.ess.value(); - // Calculate required sum values - long calculatedPower = meter.value().activePower.value() + ess.activePower.value() - - activePowerOffset.value(); - long calculatedReactivePower = meter.value().reactivePower.value() + ess.reactivePower.value() - - reactivePowerOffset.value(); - if (reactivePowerActivated.value()) { - ess.power.setReactivePower(calculatedReactivePower); - } - if (activePowerActivated.value()) { - ess.power.setActivePower(calculatedPower); - } - ess.power.writePower(); - // print info message to log - String message = ess.id(); - if (activePowerActivated.value()) { - message = message + " Set ActivePower [" + ess.power.getActivePower() + "]"; - } - if (reactivePowerActivated.value()) { - message = message + " Set ReactivePower [" + ess.power.getReactivePower() + "]"; - } - log.info(message); - } catch (InvalidValueException e) { - log.error(e.getMessage()); - } - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.controller.symmetric.balancingoffset; + +import java.util.Optional; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.controller.Controller; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.WriteChannelException; + +/* + * this Controller calculates the power consumption of the house and charges or discharges the storages to reach zero power consumption from the grid + */ +@ThingInfo(title = "Balancing offset (Symmetric)", description = "Tries to keep the grid meter within an offset. For symmetric Ess.") +public class BalancingOffsetController extends Controller { + + /* + * Constructors + */ + public BalancingOffsetController() { + super(); + // init(); + } + + public BalancingOffsetController(String thingId) { + super(thingId); + // init(); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess devices.", type = Ess.class) + public final ConfigChannel ess = new ConfigChannel<>("ess", this); + + @ConfigInfo(title = "Grid-Meter", description = "Sets the grid meter.", type = Meter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + @ConfigInfo(title = "Offset ActivePower", description = "The offset of the active power from zero to hold on the grid meter.", type = Integer.class) + public final ConfigChannel activePowerOffset = new ConfigChannel<>("activePowerOffset", this); + + @ConfigInfo(title = "Offset ReactivePower", description = "The offset of the reactive power from zero to hold on the grid meter.", type = Integer.class) + public final ConfigChannel reactivePowerOffset = new ConfigChannel<>("reactivePowerOffset", this); + + @ConfigInfo(title = "Enable ActivePower", description = "Indicates if active power is enabled.", type = Boolean.class, defaultValue = "true") + public final ConfigChannel activePowerActivated = new ConfigChannel("activePowerActivated", this); + + @ConfigInfo(title = "Enable ReactivePower", description = "Indicates if reactive power is enabled.", type = Boolean.class, defaultValue = "true") + public final ConfigChannel reactivePowerActivated = new ConfigChannel("reactivePowerActivated", + this); + + /* + * Methods + */ + @Override + public void init() { + activePowerActivated.addChangeListener(new ChannelChangeListener() { + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + try { + if (!activePowerActivated.value()) { + ess.value().setActivePower.pushWrite(0L); + } + } catch (WriteChannelException | InvalidValueException e) { + log.error(e.getMessage()); + } + } + }); + reactivePowerActivated.addChangeListener(new ChannelChangeListener() { + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + try { + if (!reactivePowerActivated.value()) { + ess.value().setReactivePower.pushWrite(0L); + } + } catch (WriteChannelException | InvalidValueException e) { + log.error(e.getMessage()); + } + } + }); + } + + @Override + public void run() { + try { + Ess ess = this.ess.value(); + // Calculate required sum values + long calculatedPower = meter.value().activePower.value() + ess.activePower.value() + - activePowerOffset.value(); + long calculatedReactivePower = meter.value().reactivePower.value() + ess.reactivePower.value() + - reactivePowerOffset.value(); + if (reactivePowerActivated.value()) { + ess.power.setReactivePower(calculatedReactivePower); + } + if (activePowerActivated.value()) { + ess.power.setActivePower(calculatedPower); + } + ess.power.writePower(); + // print info message to log + String message = ess.id(); + if (activePowerActivated.value()) { + message = message + " Set ActivePower [" + ess.power.getActivePower() + "]"; + } + if (reactivePowerActivated.value()) { + message = message + " Set ReactivePower [" + ess.power.getReactivePower() + "]"; + } + log.info(message); + } catch (InvalidValueException e) { + log.error(e.getMessage()); + } + } + +} diff --git a/edge/src/io/openems/impl/device/bcontrol/BControl.java b/edge/src/io/openems/impl/device/bcontrol/BControl.java index 54654343643..9e88d340c65 100644 --- a/edge/src/io/openems/impl/device/bcontrol/BControl.java +++ b/edge/src/io/openems/impl/device/bcontrol/BControl.java @@ -1,37 +1,38 @@ -package io.openems.impl.device.bcontrol; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "B-Control Energy Meter") -public class BControl extends ModbusDevice { - - public BControl() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = BControlMeter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } -} +package io.openems.impl.device.bcontrol; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "B-Control Energy Meter") +public class BControl extends ModbusDevice { + + public BControl(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = BControlMeter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } +} diff --git a/edge/src/io/openems/impl/device/bcontrol/BControlMeter.java b/edge/src/io/openems/impl/device/bcontrol/BControlMeter.java index 1d5fc4b9a45..13f13cf6357 100644 --- a/edge/src/io/openems/impl/device/bcontrol/BControlMeter.java +++ b/edge/src/io/openems/impl/device/bcontrol/BControlMeter.java @@ -1,322 +1,316 @@ -package io.openems.impl.device.bcontrol; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; - -public class BControlMeter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { - - public BControlMeter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - private FunctionalReadChannel activePower; - private FunctionalReadChannel apparentPower; - private FunctionalReadChannel reactivePower; - private FunctionalReadChannel activePowerL1; - private FunctionalReadChannel activePowerL2; - private FunctionalReadChannel activePowerL3; - private FunctionalReadChannel reactivePowerL1; - private FunctionalReadChannel reactivePowerL2; - private FunctionalReadChannel reactivePowerL3; - private ModbusReadLongChannel activePowerPos; - private ModbusReadLongChannel apparentPowerPos; - private ModbusReadLongChannel reactivePowerPos; - private ModbusReadLongChannel activePowerL1Pos; - private ModbusReadLongChannel activePowerL2Pos; - private ModbusReadLongChannel activePowerL3Pos; - private ModbusReadLongChannel reactivePowerL1Pos; - private ModbusReadLongChannel reactivePowerL2Pos; - private ModbusReadLongChannel reactivePowerL3Pos; - private ModbusReadLongChannel activePowerNeg; - private ModbusReadLongChannel apparentPowerNeg; - private ModbusReadLongChannel reactivePowerNeg; - private ModbusReadLongChannel activePowerL1Neg; - private ModbusReadLongChannel activePowerL2Neg; - private ModbusReadLongChannel activePowerL3Neg; - private ModbusReadLongChannel reactivePowerL1Neg; - private ModbusReadLongChannel reactivePowerL2Neg; - private ModbusReadLongChannel reactivePowerL3Neg; - private ModbusReadLongChannel voltageL1; - private ModbusReadLongChannel voltageL2; - private ModbusReadLongChannel voltageL3; - private ModbusReadLongChannel currentL1; - private ModbusReadLongChannel currentL2; - private ModbusReadLongChannel currentL3; - private ModbusReadLongChannel frequency; - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public ReadChannel currentL1() { - return currentL1; - } - - @Override - public ReadChannel currentL2() { - return currentL2; - } - - @Override - public ReadChannel currentL3() { - return currentL3; - } - - @Override - public ReadChannel voltageL1() { - return voltageL1; - } - - @Override - public ReadChannel voltageL2() { - return voltageL2; - } - - @Override - public ReadChannel voltageL3() { - return voltageL3; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - ModbusProtocol mp = new ModbusProtocol( // - new ModbusInputRegisterRange(0, // - new UnsignedDoublewordElement(0, - activePowerPos = new ModbusReadLongChannel("ActivePowerPos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(2, - activePowerNeg = new ModbusReadLongChannel("ActivePowerNeg", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(4, - reactivePowerPos = new ModbusReadLongChannel("ActivePowerPos", this).unit("Var") - .multiplier(-1)), - new UnsignedDoublewordElement(6, - reactivePowerNeg = new ModbusReadLongChannel("ActivePowerNeg", this).unit("Var") - .multiplier(-1)), - new DummyElement(8, 15), - new UnsignedDoublewordElement(16, - apparentPowerPos = new ModbusReadLongChannel("ApparentPowerPos", this).unit("VA") - .multiplier(-1)), - new UnsignedDoublewordElement(18, - apparentPowerNeg = new ModbusReadLongChannel("ApparentPowerNeg", this).unit("VA") - .multiplier(-1)), - new DummyElement(20, 25), - new UnsignedDoublewordElement(26, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ"))), - new ModbusInputRegisterRange(40, - new UnsignedDoublewordElement(40, // - activePowerL1Pos = new ModbusReadLongChannel("ActivePowerL1Pos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(42, // - activePowerL1Neg = new ModbusReadLongChannel("ActivePowerL1Neg", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(44, // - reactivePowerL1Pos = new ModbusReadLongChannel("ReactivePowerL1Pos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(46, // - reactivePowerL1Neg = new ModbusReadLongChannel("ActivePowerL1Neg", this).unit("W") - .multiplier(-1)), - new DummyElement(48, 59), - new UnsignedDoublewordElement(60, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), - new UnsignedDoublewordElement(62, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV"))), - new ModbusInputRegisterRange(80, - new UnsignedDoublewordElement(80, // - activePowerL2Pos = new ModbusReadLongChannel("ActivePowerL2Pos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(82, // - activePowerL2Neg = new ModbusReadLongChannel("ActivePowerL2Neg", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(84, // - reactivePowerL2Pos = new ModbusReadLongChannel("ReactivePowerL2Pos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(86, // - reactivePowerL2Neg = new ModbusReadLongChannel("ActivePowerL2Neg", this).unit("W") - .multiplier(-1)), - new DummyElement(88, 99), - new UnsignedDoublewordElement(100, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), - new UnsignedDoublewordElement(102, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV"))), - new ModbusInputRegisterRange(120, - new UnsignedDoublewordElement(120, // - activePowerL3Pos = new ModbusReadLongChannel("ActivePowerL3Pos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(122, // - activePowerL3Neg = new ModbusReadLongChannel("ActivePowerL3Neg", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(124, // - reactivePowerL3Pos = new ModbusReadLongChannel("ReactivePowerL3Pos", this).unit("W") - .multiplier(-1)), - new UnsignedDoublewordElement(126, // - reactivePowerL3Neg = new ModbusReadLongChannel("ActivePowerL3Neg", this).unit("W") - .multiplier(-1)), - new DummyElement(128, 139), - new UnsignedDoublewordElement(140, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")), - new UnsignedDoublewordElement(142, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV")))); - activePower = new FunctionalReadChannel("ActivePower", this, new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, activePowerPos, activePowerNeg); - activePowerL1 = new FunctionalReadChannel("ActivePowerL1", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, activePowerL1Pos, activePowerL1Neg); - activePowerL2 = new FunctionalReadChannel("ActivePowerL2", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, activePowerL2Pos, activePowerL2Neg); - activePowerL3 = new FunctionalReadChannel("ActivePowerL3", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, activePowerL3Pos, activePowerL3Neg); - reactivePower = new FunctionalReadChannel("ReactivePower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, reactivePowerPos, reactivePowerNeg); - reactivePowerL1 = new FunctionalReadChannel("ReactivePowerL1", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, reactivePowerL1Pos, reactivePowerL1Neg); - reactivePowerL2 = new FunctionalReadChannel("ReactivePowerL2", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, reactivePowerL2Pos, reactivePowerL2Neg); - reactivePowerL3 = new FunctionalReadChannel("ReactivePowerL3", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, reactivePowerL3Pos, reactivePowerL3Neg); - apparentPower = new FunctionalReadChannel("ApparentPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); - } - }, apparentPowerPos, apparentPowerNeg); - return mp; - } - -} +package io.openems.impl.device.bcontrol; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; + +public class BControlMeter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { + + public BControlMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + private FunctionalReadChannel activePower; + private FunctionalReadChannel apparentPower; + private FunctionalReadChannel reactivePower; + private FunctionalReadChannel activePowerL1; + private FunctionalReadChannel activePowerL2; + private FunctionalReadChannel activePowerL3; + private FunctionalReadChannel reactivePowerL1; + private FunctionalReadChannel reactivePowerL2; + private FunctionalReadChannel reactivePowerL3; + private ModbusReadLongChannel activePowerPos; + private ModbusReadLongChannel apparentPowerPos; + private ModbusReadLongChannel reactivePowerPos; + private ModbusReadLongChannel activePowerL1Pos; + private ModbusReadLongChannel activePowerL2Pos; + private ModbusReadLongChannel activePowerL3Pos; + private ModbusReadLongChannel reactivePowerL1Pos; + private ModbusReadLongChannel reactivePowerL2Pos; + private ModbusReadLongChannel reactivePowerL3Pos; + private ModbusReadLongChannel activePowerNeg; + private ModbusReadLongChannel apparentPowerNeg; + private ModbusReadLongChannel reactivePowerNeg; + private ModbusReadLongChannel activePowerL1Neg; + private ModbusReadLongChannel activePowerL2Neg; + private ModbusReadLongChannel activePowerL3Neg; + private ModbusReadLongChannel reactivePowerL1Neg; + private ModbusReadLongChannel reactivePowerL2Neg; + private ModbusReadLongChannel reactivePowerL3Neg; + private ModbusReadLongChannel voltageL1; + private ModbusReadLongChannel voltageL2; + private ModbusReadLongChannel voltageL3; + private ModbusReadLongChannel currentL1; + private ModbusReadLongChannel currentL2; + private ModbusReadLongChannel currentL3; + private ModbusReadLongChannel frequency; + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + ModbusProtocol mp = new ModbusProtocol( // + new ModbusInputRegisterRange(0, // + new UnsignedDoublewordElement(0, + activePowerPos = new ModbusReadLongChannel("ActivePowerPos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(2, + activePowerNeg = new ModbusReadLongChannel("ActivePowerNeg", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(4, + reactivePowerPos = new ModbusReadLongChannel("ActivePowerPos", this).unit("Var") + .multiplier(-1)), + new UnsignedDoublewordElement(6, + reactivePowerNeg = new ModbusReadLongChannel("ActivePowerNeg", this).unit("Var") + .multiplier(-1)), + new DummyElement(8, 15), + new UnsignedDoublewordElement(16, + apparentPowerPos = new ModbusReadLongChannel("ApparentPowerPos", this).unit("VA") + .multiplier(-1)), + new UnsignedDoublewordElement(18, + apparentPowerNeg = new ModbusReadLongChannel("ApparentPowerNeg", this) + .unit("VA").multiplier(-1)), + new DummyElement(20, 25), new UnsignedDoublewordElement(26, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ"))), + new ModbusInputRegisterRange(40, new UnsignedDoublewordElement(40, // + activePowerL1Pos = new ModbusReadLongChannel("ActivePowerL1Pos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(42, // + activePowerL1Neg = new ModbusReadLongChannel("ActivePowerL1Neg", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(44, // + reactivePowerL1Pos = new ModbusReadLongChannel("ReactivePowerL1Pos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(46, // + reactivePowerL1Neg = new ModbusReadLongChannel("ActivePowerL1Neg", this) + .unit("W").multiplier(-1)), + new DummyElement(48, 59), new UnsignedDoublewordElement(60, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), + new UnsignedDoublewordElement(62, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV"))), + new ModbusInputRegisterRange(80, new UnsignedDoublewordElement(80, // + activePowerL2Pos = new ModbusReadLongChannel("ActivePowerL2Pos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(82, // + activePowerL2Neg = new ModbusReadLongChannel("ActivePowerL2Neg", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(84, // + reactivePowerL2Pos = new ModbusReadLongChannel("ReactivePowerL2Pos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(86, // + reactivePowerL2Neg = new ModbusReadLongChannel("ActivePowerL2Neg", this) + .unit("W").multiplier(-1)), + new DummyElement(88, 99), new UnsignedDoublewordElement(100, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), + new UnsignedDoublewordElement(102, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV"))), + new ModbusInputRegisterRange(120, new UnsignedDoublewordElement(120, // + activePowerL3Pos = new ModbusReadLongChannel("ActivePowerL3Pos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(122, // + activePowerL3Neg = new ModbusReadLongChannel("ActivePowerL3Neg", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(124, // + reactivePowerL3Pos = new ModbusReadLongChannel("ReactivePowerL3Pos", this).unit("W") + .multiplier(-1)), + new UnsignedDoublewordElement(126, // + reactivePowerL3Neg = new ModbusReadLongChannel("ActivePowerL3Neg", this) + .unit("W").multiplier(-1)), + new DummyElement(128, 139), new UnsignedDoublewordElement(140, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")), + new UnsignedDoublewordElement(142, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV")))); + activePower = new FunctionalReadChannel("ActivePower", this, new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, activePowerPos, activePowerNeg); + activePowerL1 = new FunctionalReadChannel("ActivePowerL1", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, activePowerL1Pos, activePowerL1Neg); + activePowerL2 = new FunctionalReadChannel("ActivePowerL2", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, activePowerL2Pos, activePowerL2Neg); + activePowerL3 = new FunctionalReadChannel("ActivePowerL3", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, activePowerL3Pos, activePowerL3Neg); + reactivePower = new FunctionalReadChannel("ReactivePower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, reactivePowerPos, reactivePowerNeg); + reactivePowerL1 = new FunctionalReadChannel("ReactivePowerL1", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, reactivePowerL1Pos, reactivePowerL1Neg); + reactivePowerL2 = new FunctionalReadChannel("ReactivePowerL2", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, reactivePowerL2Pos, reactivePowerL2Neg); + reactivePowerL3 = new FunctionalReadChannel("ReactivePowerL3", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, reactivePowerL3Pos, reactivePowerL3Neg); + apparentPower = new FunctionalReadChannel("ApparentPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + return channels[0].valueOptional().orElse(0L) + (channels[1].valueOptional().orElse(0L) * -1); + } + }, apparentPowerPos, apparentPowerNeg); + return mp; + } + +} diff --git a/edge/src/io/openems/impl/device/byd/Bem125ktla01.java b/edge/src/io/openems/impl/device/byd/Bem125ktla01.java index 710e44a2354..3d680567561 100644 --- a/edge/src/io/openems/impl/device/byd/Bem125ktla01.java +++ b/edge/src/io/openems/impl/device/byd/Bem125ktla01.java @@ -1,69 +1,70 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.byd; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -/* - * Inverter used in project DE0504-Wien - */ -@ThingInfo(title = "BYD BEM 125 KTLA01") -public class Bem125ktla01 extends ModbusDevice { - - /* - * Constructors - */ - public Bem125ktla01() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = Bem125ktla01Ess.class) - public final ConfigChannel ess = new ConfigChannel("ess", this); - - /* - * Methods - */ - @Override - public String toString() { - return "Bem125ktla01Ess [ess=" + ess + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (ess.valueOptional().isPresent()) { - natures.add(ess.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.byd; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +/* + * Inverter used in project DE0504-Wien + */ +@ThingInfo(title = "BYD BEM 125 KTLA01") +public class Bem125ktla01 extends ModbusDevice { + + /* + * Constructors + */ + public Bem125ktla01(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = Bem125ktla01Ess.class) + public final ConfigChannel ess = new ConfigChannel("ess", this); + + /* + * Methods + */ + @Override + public String toString() { + return "Bem125ktla01Ess [ess=" + ess + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (ess.valueOptional().isPresent()) { + natures.add(ess.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/byd/Bem125ktla01Ess.java b/edge/src/io/openems/impl/device/byd/Bem125ktla01Ess.java index 7130d17ab1f..c69f5474c80 100644 --- a/edge/src/io/openems/impl/device/byd/Bem125ktla01Ess.java +++ b/edge/src/io/openems/impl/device/byd/Bem125ktla01Ess.java @@ -1,264 +1,264 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.byd; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadChannel; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.ModbusWriteChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "BYD BEM 125 KTLA01 ESS") -public class Bem125ktla01Ess extends ModbusDeviceNature implements SymmetricEssNature { - - /* - * Constructors - */ - public Bem125ktla01Ess(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - private ModbusReadChannel soc; - private StaticValueChannel allowedCharge = new StaticValueChannel("AllowedCharge", this, 0L); - private StaticValueChannel allowedDischarge = new StaticValueChannel("AllowedDischarge", this, 0L); - private StaticValueChannel allowedApparent = new StaticValueChannel<>("allowedApparent", this, 0L).unit("VA") - .unit("VA"); - private ModbusReadChannel apparentPower; - private StaticValueChannel gridMode = new StaticValueChannel("GridMode", this, 0L); - private StaticValueChannel activePower = new StaticValueChannel("ActivePower", this, 0L); - private StaticValueChannel reactivePower = new StaticValueChannel("ActivePower", this, 0L); - private StaticValueChannel systemState = new StaticValueChannel("SystemState", this, 0L);; - private ModbusWriteChannel setActivePower; - private ModbusWriteChannel setReactivePower; - private ModbusWriteChannel setWorkState; - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 0L); - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 170000L).unit("Wh"); - public StatusBitChannels warning; - - public ModbusReadChannel sysAlarmInfo; - public StatusBitChannel sysWorkStatus; - public StatusBitChannel sysControlMode; - public StatusBitChannel sysAlarmInfo2; - public ModbusReadChannel batteryStackVoltage; - public ModbusReadChannel batteryStackCurrent; - public ModbusReadChannel batteryStackPower; - public ModbusReadChannel batteryStackSoc; - public ModbusReadChannel batteryStackSoh; - public ModbusReadChannel batteryStackMaxChargeCurrent; - public ModbusReadChannel batteryStackMaxDischargeCurrent; - public ModbusReadChannel batteryStackMaxChargePower; - public ModbusReadChannel batteryStackMaxDischargePower; - public ModbusReadChannel batteryStackTotalCapacity; - public ModbusReadChannel batteryStackTotalCharge; - public ModbusReadChannel batteryStackTotalDischarge; - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - warning = new StatusBitChannels("Warning", this); - return new ModbusProtocol( // - new ModbusRegisterRange(0x0100, // - new UnsignedWordElement(0x100, // - sysAlarmInfo = new ModbusReadLongChannel("SysAlarmInfo", this)// - .label(0, "Warning State")// - .label(1, "Protection State")// - .label(2, "Derating State")// - .label(4, "Charge Forbidden").label(16, "Discharge Forbidden")), - new UnsignedWordElement(0x101, // - sysWorkStatus = new StatusBitChannel("SysWorkStatus", this)// - .label(0, "Initial") // - .label(1, "Fault") // - .label(2, "Stop") // - .label(4, "Hot Standby") // - .label(8, "Monitoring") // - .label(16, "Standby") // - .label(32, "Operation") // - .label(64, "Debug")), // - new UnsignedWordElement(0x102, // - sysControlMode = new StatusBitChannel("SysControlMode", this)// - .label(0, "Remote") // - .label(1, "Local")), // - new DummyElement(0x103)), - new ModbusRegisterRange(0x0110, // - new UnsignedWordElement(0x110, // - sysAlarmInfo = new StatusBitChannel("SysAlarmInfo", this)// - .label(0, "Status abnormal of AC surge protector") // - .label(1, "Close of control switch") // - .label(2, "Emergency stop") // - .label(4, "Status abnormal of frog detector") // - .label(8, "Serious leakage") // - .label(16, "Normal_leakage")), // - new UnsignedWordElement(0x111, // - sysAlarmInfo2 = new StatusBitChannel("SysAlarmInfo2", this)// - .label(0, "Failure of temperature sensor in control cabinet") // - .label(1, "Close of control switch") // - /* - * TODO new OnOffBitItem(9, "Failure_of_humidity_sensor_in_control_cabinet"), // - * new OnOffBitItem(12, "Failure_of_storage_device"), // - * new OnOffBitItem(13, "Exceeding_of_humidity_in_control_cabinet")))); - */ - )), new ModbusRegisterRange(0x1300, new UnsignedWordElement(0x1300, // - batteryStackVoltage = new ModbusReadLongChannel("BatteryStackVoltage", this) - .multiplier(2).unit("mV")), - new UnsignedWordElement(0x1301, // - batteryStackCurrent = new ModbusReadLongChannel("BatteryStackCurrent", this) - .multiplier(2).unit("mA")), - new UnsignedWordElement(0x1302, // - batteryStackPower = new ModbusReadLongChannel("BatteryStackPower", this) - .multiplier(2).unit("W")), - new UnsignedWordElement(0x1303, // - batteryStackSoc = soc = new ModbusReadLongChannel("BatteryStackSoc", this) - .unit("%")), - new UnsignedWordElement(0x1304, // - batteryStackSoh = new ModbusReadLongChannel("BatteryStackSoh", this).unit("%")), - new UnsignedWordElement(0x1305, // - batteryStackMaxChargeCurrent = new ModbusReadLongChannel( - "BatteryStackMaxChargeCurrent", this).multiplier(2).unit("mA")), - new UnsignedWordElement(0x1306, // - batteryStackMaxDischargeCurrent = new ModbusReadLongChannel( - "BatteryStackMaxDischargeCurrent", this).multiplier(2).unit("mA")), - new UnsignedWordElement(0x1307, // - batteryStackMaxChargePower = new ModbusReadLongChannel( - "BatteryStackMaxChargePower", this).multiplier(2).unit("W")), - new UnsignedWordElement(0x1308, // - batteryStackMaxDischargePower = new ModbusReadLongChannel( - "BatteryStackMaxDischargePower", this).multiplier(2).unit("W")), - new UnsignedWordElement(0x1309, // - batteryStackTotalCapacity = new ModbusReadLongChannel( - "BatteryStackTotalCapacity", this).unit("Wh")), - new UnsignedDoublewordElement(0x130A, // - batteryStackTotalCharge = new ModbusReadLongChannel("BatteryStackTotalCharge", - this).unit( - "kWh")), - new UnsignedDoublewordElement(0x130C, // - batteryStackTotalDischarge = new ModbusReadLongChannel( - "BatteryStackTotalDischarge", this).unit("kWh")))); - } - - @Override - public StaticValueChannel capacity() { - return capacity; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.byd; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadChannel; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.ModbusWriteChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "BYD BEM 125 KTLA01 ESS") +public class Bem125ktla01Ess extends ModbusDeviceNature implements SymmetricEssNature { + + /* + * Constructors + */ + public Bem125ktla01Ess(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + private ModbusReadChannel soc; + private StaticValueChannel allowedCharge = new StaticValueChannel("AllowedCharge", this, 0L); + private StaticValueChannel allowedDischarge = new StaticValueChannel("AllowedDischarge", this, 0L); + private StaticValueChannel allowedApparent = new StaticValueChannel<>("allowedApparent", this, 0L).unit("VA") + .unit("VA"); + private ModbusReadChannel apparentPower; + private StaticValueChannel gridMode = new StaticValueChannel("GridMode", this, 0L); + private StaticValueChannel activePower = new StaticValueChannel("ActivePower", this, 0L); + private StaticValueChannel reactivePower = new StaticValueChannel("ActivePower", this, 0L); + private StaticValueChannel systemState = new StaticValueChannel("SystemState", this, 0L);; + private ModbusWriteChannel setActivePower; + private ModbusWriteChannel setReactivePower; + private ModbusWriteChannel setWorkState; + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 0L); + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 170000L).unit("Wh"); + public StatusBitChannels warning; + + public ModbusReadChannel sysAlarmInfo; + public StatusBitChannel sysWorkStatus; + public StatusBitChannel sysControlMode; + public StatusBitChannel sysAlarmInfo2; + public ModbusReadChannel batteryStackVoltage; + public ModbusReadChannel batteryStackCurrent; + public ModbusReadChannel batteryStackPower; + public ModbusReadChannel batteryStackSoc; + public ModbusReadChannel batteryStackSoh; + public ModbusReadChannel batteryStackMaxChargeCurrent; + public ModbusReadChannel batteryStackMaxDischargeCurrent; + public ModbusReadChannel batteryStackMaxChargePower; + public ModbusReadChannel batteryStackMaxDischargePower; + public ModbusReadChannel batteryStackTotalCapacity; + public ModbusReadChannel batteryStackTotalCharge; + public ModbusReadChannel batteryStackTotalDischarge; + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + warning = new StatusBitChannels("Warning", this); + return new ModbusProtocol( // + new ModbusRegisterRange(0x0100, // + new UnsignedWordElement(0x100, // + sysAlarmInfo = new ModbusReadLongChannel("SysAlarmInfo", this)// + .label(0, "Warning State")// + .label(1, "Protection State")// + .label(2, "Derating State")// + .label(4, "Charge Forbidden").label(16, "Discharge Forbidden")), + new UnsignedWordElement(0x101, // + sysWorkStatus = new StatusBitChannel("SysWorkStatus", this)// + .label(0, "Initial") // + .label(1, "Fault") // + .label(2, "Stop") // + .label(4, "Hot Standby") // + .label(8, "Monitoring") // + .label(16, "Standby") // + .label(32, "Operation") // + .label(64, "Debug")), // + new UnsignedWordElement(0x102, // + sysControlMode = new StatusBitChannel("SysControlMode", this)// + .label(0, "Remote") // + .label(1, "Local")), // + new DummyElement(0x103)), + new ModbusRegisterRange(0x0110, // + new UnsignedWordElement(0x110, // + sysAlarmInfo = new StatusBitChannel("SysAlarmInfo", this)// + .label(0, "Status abnormal of AC surge protector") // + .label(1, "Close of control switch") // + .label(2, "Emergency stop") // + .label(4, "Status abnormal of frog detector") // + .label(8, "Serious leakage") // + .label(16, "Normal_leakage")), // + new UnsignedWordElement(0x111, // + sysAlarmInfo2 = new StatusBitChannel("SysAlarmInfo2", this)// + .label(0, "Failure of temperature sensor in control cabinet") // + .label(1, "Close of control switch") // + /* + * TODO new OnOffBitItem(9, "Failure_of_humidity_sensor_in_control_cabinet"), // + * new OnOffBitItem(12, "Failure_of_storage_device"), // + * new OnOffBitItem(13, "Exceeding_of_humidity_in_control_cabinet")))); + */ + )), new ModbusRegisterRange(0x1300, new UnsignedWordElement(0x1300, // + batteryStackVoltage = new ModbusReadLongChannel("BatteryStackVoltage", this) + .multiplier(2).unit("mV")), + new UnsignedWordElement(0x1301, // + batteryStackCurrent = new ModbusReadLongChannel("BatteryStackCurrent", this) + .multiplier(2).unit("mA")), + new UnsignedWordElement(0x1302, // + batteryStackPower = new ModbusReadLongChannel("BatteryStackPower", this) + .multiplier(2).unit("W")), + new UnsignedWordElement(0x1303, // + batteryStackSoc = soc = new ModbusReadLongChannel("BatteryStackSoc", this) + .unit("%")), + new UnsignedWordElement(0x1304, // + batteryStackSoh = new ModbusReadLongChannel("BatteryStackSoh", this).unit("%")), + new UnsignedWordElement(0x1305, // + batteryStackMaxChargeCurrent = new ModbusReadLongChannel( + "BatteryStackMaxChargeCurrent", this).multiplier(2).unit("mA")), + new UnsignedWordElement(0x1306, // + batteryStackMaxDischargeCurrent = new ModbusReadLongChannel( + "BatteryStackMaxDischargeCurrent", this).multiplier(2).unit("mA")), + new UnsignedWordElement(0x1307, // + batteryStackMaxChargePower = new ModbusReadLongChannel( + "BatteryStackMaxChargePower", this).multiplier(2).unit("W")), + new UnsignedWordElement(0x1308, // + batteryStackMaxDischargePower = new ModbusReadLongChannel( + "BatteryStackMaxDischargePower", this).multiplier(2).unit("W")), + new UnsignedWordElement(0x1309, // + batteryStackTotalCapacity = new ModbusReadLongChannel( + "BatteryStackTotalCapacity", this).unit("Wh")), + new UnsignedDoublewordElement(0x130A, // + batteryStackTotalCharge = new ModbusReadLongChannel("BatteryStackTotalCharge", + this).unit("kWh")), + new UnsignedDoublewordElement(0x130C, // + batteryStackTotalDischarge = new ModbusReadLongChannel( + "BatteryStackTotalDischarge", this).unit("kWh")))); + } + + @Override + public StaticValueChannel capacity() { + return capacity; + } +} diff --git a/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300.java b/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300.java index 1f03e5fb93d..24a287a7fcc 100644 --- a/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300.java +++ b/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.carlogavazzi.em300series; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "Carlog Gavazzi EM300") -public class EM300 extends ModbusDevice { - - /* - * Constructors - */ - public EM300() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = EM300Meter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.carlogavazzi.em300series; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "Carlog Gavazzi EM300") +public class EM300 extends ModbusDevice { + + /* + * Constructors + */ + public EM300(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = EM300Meter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300Meter.java b/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300Meter.java index 59d2fa8fdce..e0765d2b88e 100644 --- a/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300Meter.java +++ b/edge/src/io/openems/impl/device/carlogavazzi/em300series/EM300Meter.java @@ -1,251 +1,249 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.carlogavazzi.em300series; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.SignedWordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "Socomec Meter") -public class EM300Meter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { - - /* - * Constructors - */ - public EM300Meter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel activePower; - private ModbusReadLongChannel apparentPower; - private ModbusReadLongChannel reactivePower; - private ModbusReadLongChannel activePowerL1; - private ModbusReadLongChannel activePowerL2; - private ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel reactivePowerL1; - private ModbusReadLongChannel reactivePowerL2; - private ModbusReadLongChannel reactivePowerL3; - private ModbusReadLongChannel voltageL1; - private ModbusReadLongChannel voltageL2; - private ModbusReadLongChannel voltageL3; - private ModbusReadLongChannel currentL1; - private ModbusReadLongChannel currentL2; - private ModbusReadLongChannel currentL3; - private ModbusReadLongChannel frequency; - - @Override - public ModbusReadLongChannel activePower() { - return activePower; - } - - @Override - public ModbusReadLongChannel apparentPower() { - return apparentPower; - } - - @Override - public ModbusReadLongChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public ReadChannel currentL1() { - return currentL1; - } - - @Override - public ReadChannel currentL2() { - return currentL2; - } - - @Override - public ReadChannel currentL3() { - return currentL3; - } - - @Override - public ReadChannel voltageL1() { - return voltageL1; - } - - @Override - public ReadChannel voltageL2() { - return voltageL2; - } - - @Override - public ReadChannel voltageL3() { - return voltageL3; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - /* - * This Channels - */ - public ModbusReadLongChannel activeNegativeEnergy; - public ModbusReadLongChannel activePositiveEnergy; - public ModbusReadLongChannel reactiveNegativeEnergy; - public ModbusReadLongChannel reactivePositiveEnergy; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( // - new ModbusInputRegisterRange(1, // - - new SignedDoublewordElement(1, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), - new SignedDoublewordElement(3, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), - new SignedDoublewordElement(5, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2))), - new ModbusInputRegisterRange(13, - new SignedDoublewordElement(13, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), - new SignedDoublewordElement(15, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), - new SignedDoublewordElement(17, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")), - new SignedDoublewordElement(19, // - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W") - .multiplier(-1)), - new SignedDoublewordElement(21, // - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W") - .multiplier(-1)), - new SignedDoublewordElement(23, // - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W") - .multiplier(-1)), - new SignedDoublewordElement(25, // - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var") - .multiplier(-1)), - new SignedDoublewordElement(27, // - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("var") - .multiplier(-1)), - new SignedDoublewordElement(29, // - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("var") - .multiplier(-1)), - new DummyElement(31, 40), - new SignedDoublewordElement(41, // - activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(-1)), - new SignedDoublewordElement(43, // - apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA") - .multiplier(-1)), - new SignedDoublewordElement(45, // - reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("var") - .multiplier(-1))), - new ModbusInputRegisterRange(52, - new SignedWordElement(52, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ").multiplier(2)), - new UnsignedDoublewordElement(53, // - activePositiveEnergy = new ModbusReadLongChannel("ActivePositiveEnergy", this) - .unit("kWh").multiplier(1)), - new UnsignedDoublewordElement(55, // - reactivePositiveEnergy = new ModbusReadLongChannel("ReactivePositiveEnergy", this) - .unit("kvarh").multiplier(-1))), - new ModbusRegisterRange(79, // - new UnsignedDoublewordElement(79, // - activeNegativeEnergy = new ModbusReadLongChannel("ActiveNegativeEnergy", this) - .unit("kWh").multiplier(-1)), - new UnsignedDoublewordElement(81, // - reactiveNegativeEnergy = new ModbusReadLongChannel("ReactiveNegativeEnergy", this) - .unit("kvarh").multiplier(-1)))); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.carlogavazzi.em300series; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.SignedWordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "Socomec Meter") +public class EM300Meter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { + + /* + * Constructors + */ + public EM300Meter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel activePower; + private ModbusReadLongChannel apparentPower; + private ModbusReadLongChannel reactivePower; + private ModbusReadLongChannel activePowerL1; + private ModbusReadLongChannel activePowerL2; + private ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel reactivePowerL1; + private ModbusReadLongChannel reactivePowerL2; + private ModbusReadLongChannel reactivePowerL3; + private ModbusReadLongChannel voltageL1; + private ModbusReadLongChannel voltageL2; + private ModbusReadLongChannel voltageL3; + private ModbusReadLongChannel currentL1; + private ModbusReadLongChannel currentL2; + private ModbusReadLongChannel currentL3; + private ModbusReadLongChannel frequency; + + @Override + public ModbusReadLongChannel activePower() { + return activePower; + } + + @Override + public ModbusReadLongChannel apparentPower() { + return apparentPower; + } + + @Override + public ModbusReadLongChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + /* + * This Channels + */ + public ModbusReadLongChannel activeNegativeEnergy; + public ModbusReadLongChannel activePositiveEnergy; + public ModbusReadLongChannel reactiveNegativeEnergy; + public ModbusReadLongChannel reactivePositiveEnergy; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( // + new ModbusInputRegisterRange(1, // + + new SignedDoublewordElement(1, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), + new SignedDoublewordElement(3, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), + new SignedDoublewordElement(5, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2))), + new ModbusInputRegisterRange(13, new SignedDoublewordElement(13, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), + new SignedDoublewordElement(15, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), + new SignedDoublewordElement(17, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")), + new SignedDoublewordElement(19, // + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W") + .multiplier(-1)), + new SignedDoublewordElement(21, // + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W") + .multiplier(-1)), + new SignedDoublewordElement(23, // + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W") + .multiplier(-1)), + new SignedDoublewordElement(25, // + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var") + .multiplier(-1)), + new SignedDoublewordElement(27, // + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("var") + .multiplier(-1)), + new SignedDoublewordElement(29, // + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this) + .unit("var").multiplier(-1)), + new DummyElement(31, 40), new SignedDoublewordElement(41, // + activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(-1)), + new SignedDoublewordElement(43, // + apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA") + .multiplier(-1)), + new SignedDoublewordElement(45, // + reactivePower = new ModbusReadLongChannel("ReactivePower", this) + .unit("var").multiplier(-1))), + new ModbusInputRegisterRange(52, new SignedWordElement(52, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ").multiplier(2)), + new UnsignedDoublewordElement(53, // + activePositiveEnergy = new ModbusReadLongChannel("ActivePositiveEnergy", this) + .unit("kWh").multiplier(1)), + new UnsignedDoublewordElement(55, // + reactivePositiveEnergy = new ModbusReadLongChannel("ReactivePositiveEnergy", this) + .unit("kvarh").multiplier(-1))), + new ModbusRegisterRange(79, // + new UnsignedDoublewordElement(79, // + activeNegativeEnergy = new ModbusReadLongChannel( + "ActiveNegativeEnergy", this).unit("kWh").multiplier(-1)), + new UnsignedDoublewordElement(81, // + reactiveNegativeEnergy = new ModbusReadLongChannel("ReactiveNegativeEnergy", this) + .unit("kvarh").multiplier(-1)))); + } +} diff --git a/edge/src/io/openems/impl/device/commercial/FeneconCommercialAC.java b/edge/src/io/openems/impl/device/commercial/FeneconCommercialAC.java index 3d10c922c85..37fb72fc085 100644 --- a/edge/src/io/openems/impl/device/commercial/FeneconCommercialAC.java +++ b/edge/src/io/openems/impl/device/commercial/FeneconCommercialAC.java @@ -1,65 +1,66 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.commercial; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "FENECON Commercial AC") -public class FeneconCommercialAC extends ModbusDevice { - - /* - * Constructors - */ - public FeneconCommercialAC() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconCommercialEss.class) - public final ConfigChannel ess = new ConfigChannel<>("ess", this); - - /* - * Methods - */ - @Override - public String toString() { - return "FeneconCommercialAC [ess=" + ess + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (ess.valueOptional().isPresent()) { - natures.add(ess.valueOptional().get()); - } - return natures; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.commercial; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "FENECON Commercial AC") +public class FeneconCommercialAC extends ModbusDevice { + + /* + * Constructors + */ + public FeneconCommercialAC(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconCommercialEss.class) + public final ConfigChannel ess = new ConfigChannel<>("ess", this); + + /* + * Methods + */ + @Override + public String toString() { + return "FeneconCommercialAC [ess=" + ess + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (ess.valueOptional().isPresent()) { + natures.add(ess.valueOptional().get()); + } + return natures; + } +} diff --git a/edge/src/io/openems/impl/device/commercial/FeneconCommercialCharger.java b/edge/src/io/openems/impl/device/commercial/FeneconCommercialCharger.java index cb3d2de56a2..544d9a8cd74 100644 --- a/edge/src/io/openems/impl/device/commercial/FeneconCommercialCharger.java +++ b/edge/src/io/openems/impl/device/commercial/FeneconCommercialCharger.java @@ -1,1225 +1,1225 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.commercial; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.charger.ChargerNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadChannel; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.ModbusWriteChannel; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedWordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.WordOrder; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; - -@ThingInfo(title = "FENECON Commercial DC-Charger") -public class FeneconCommercialCharger extends ModbusDeviceNature implements ChargerNature { - - /* - * Constructors - */ - public FeneconCommercialCharger(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Inherited Channels - */ - - public StatusBitChannels warning; - - public ModbusWriteChannel pvPowerLimitCommand; - - public ReadChannel actualPower; - - public ReadChannel inputVoltage; - - private final ConfigChannel maxActualPower = new ConfigChannel("maxActualPower", this); - - @Override - public ConfigChannel maxActualPower() { - return maxActualPower; - } - - /* - * BMS DCDC - */ - - public ModbusReadChannel bmsDCDCWorkState; - public ModbusReadChannel bmsDCDCWorkMode; - public ModbusReadChannel bmsDCDCSuggestiveInformation1; - public ModbusReadChannel bmsDCDCSuggestiveInformation2; - public ModbusReadChannel bmsDCDCSuggestiveInformation3; - public ModbusReadChannel bmsDCDCSuggestiveInformation4; - public ModbusReadChannel bmsDCDCSuggestiveInformation5; - public ModbusReadChannel bmsDCDCAbnormity1; - public ModbusReadChannel bmsDCDCAbnormity2; - public ModbusReadChannel bmsDCDCAbnormity3; - public ModbusReadChannel bmsDCDCAbnormity4; - public ModbusReadChannel bmsDCDCAbnormity5; - public ModbusReadChannel bmsDCDCAbnormity6; - public ModbusReadChannel bmsDCDCAbnormity7; - public ModbusReadChannel bmsDCDCSwitchState; - public ModbusReadChannel bmsDCDCOutputVoltage; - public ModbusReadChannel bmsDCDCOutputCurrent; - public ModbusReadChannel bmsDCDCOutputPower; - public ModbusReadChannel bmsDCDCInputVoltage; - public ModbusReadChannel bmsDCDCInputCurrent; - public ModbusReadChannel bmsDCDCInputPower; - public ModbusReadChannel bmsDCDCInputEnergy; - public ModbusReadChannel bmsDCDCOutputEnergy; - public ModbusReadChannel bmsDCDCReactorTemperature; - public ModbusReadChannel bmsDCDCIgbtTemperature; - public ModbusReadChannel bmsDCDCInputTotalChargeEnergy; - public ModbusReadChannel bmsDCDCInputTotalDischargeEnergy; - public ModbusReadChannel bmsDCDCOutputTotalChargeEnergy; - public ModbusReadChannel bmsDCDCOutputTotalDischargeEnergy; - - /* - * BMS DCDC 1 - */ - - public ModbusReadChannel bmsDCDC1WorkState; - public ModbusReadChannel bmsDCDC1WorkMode; - public ModbusReadChannel bmsDCDC1SuggestiveInformation1; - public ModbusReadChannel bmsDCDC1SuggestiveInformation2; - public ModbusReadChannel bmsDCDC1SuggestiveInformation3; - public ModbusReadChannel bmsDCDC1SuggestiveInformation4; - public ModbusReadChannel bmsDCDC1SuggestiveInformation5; - public ModbusReadChannel bmsDCDC1Abnormity1; - public ModbusReadChannel bmsDCDC1Abnormity2; - public ModbusReadChannel bmsDCDC1Abnormity3; - public ModbusReadChannel bmsDCDC1Abnormity4; - public ModbusReadChannel bmsDCDC1Abnormity5; - public ModbusReadChannel bmsDCDC1Abnormity6; - public ModbusReadChannel bmsDCDC1Abnormity7; - public ModbusReadChannel bmsDCDC1SwitchState; - public ModbusReadChannel bmsDCDC1OutputVoltage; - public ModbusReadChannel bmsDCDC1OutputCurrent; - public ModbusReadChannel bmsDCDC1OutputPower; - public ModbusReadChannel bmsDCDC1InputVoltage; - public ModbusReadChannel bmsDCDC1InputCurrent; - public ModbusReadChannel bmsDCDC1InputPower; - public ModbusReadChannel bmsDCDC1InputEnergy; - public ModbusReadChannel bmsDCDC1OutputEnergy; - public ModbusReadChannel bmsDCDC1ReactorTemperature; - public ModbusReadChannel bmsDCDC1IgbtTemperature; - public ModbusReadChannel bmsDCDC1InputTotalChargeEnergy; - public ModbusReadChannel bmsDCDC1InputTotalDischargeEnergy; - public ModbusReadChannel bmsDCDC1OutputTotalChargeEnergy; - public ModbusReadChannel bmsDCDC1OutputTotalDischargeEnergy; - /* - * PV DCDC - */ - - public ModbusReadChannel pvDCDCWorkState; - public ModbusReadChannel pvDCDCWorkMode; - public ModbusReadChannel pvDCDCSuggestiveInformation1; - public ModbusReadChannel pvDCDCSuggestiveInformation2; - public ModbusReadChannel pvDCDCSuggestiveInformation3; - public ModbusReadChannel pvDCDCSuggestiveInformation4; - public ModbusReadChannel pvDCDCSuggestiveInformation5; - public ModbusReadChannel pvDCDCAbnormity1; - public ModbusReadChannel pvDCDCAbnormity2; - public ModbusReadChannel pvDCDCAbnormity3; - public ModbusReadChannel pvDCDCAbnormity4; - public ModbusReadChannel pvDCDCAbnormity5; - public ModbusReadChannel pvDCDCAbnormity6; - public ModbusReadChannel pvDCDCAbnormity7; - public ModbusReadChannel pvDCDCSwitchState; - public ModbusReadChannel pvDCDCOutputVoltage; - public ModbusReadChannel pvDCDCOutputCurrent; - public ModbusReadChannel pvDCDCOutputPower; - public ModbusReadChannel pvDCDCInputVoltage; - public ModbusReadChannel pvDCDCInputCurrent; - public ModbusReadChannel pvDCDCInputPower; - public ModbusReadChannel pvDCDCInputEnergy; - public ModbusReadChannel pvDCDCOutputEnergy; - public ModbusReadChannel pvDCDCReactorTemperature; - public ModbusReadChannel pvDCDCIgbtTemperature; - public ModbusReadChannel pvDCDCInputTotalChargeEnergy; - public ModbusReadChannel pvDCDCInputTotalDischargeEnergy; - public ModbusReadChannel pvDCDCOutputTotalChargeEnergy; - public ModbusReadChannel pvDCDCOutputTotalDischargeEnergy; - - /* - * PV DCDC 1 - */ - - public ModbusReadChannel pvDCDC1WorkState; - public ModbusReadChannel pvDCDC1WorkMode; - public ModbusReadChannel pvDCDC1SuggestiveInformation1; - public ModbusReadChannel pvDCDC1SuggestiveInformation2; - public ModbusReadChannel pvDCDC1SuggestiveInformation3; - public ModbusReadChannel pvDCDC1SuggestiveInformation4; - public ModbusReadChannel pvDCDC1SuggestiveInformation5; - public ModbusReadChannel pvDCDC1Abnormity1; - public ModbusReadChannel pvDCDC1Abnormity2; - public ModbusReadChannel pvDCDC1Abnormity3; - public ModbusReadChannel pvDCDC1Abnormity4; - public ModbusReadChannel pvDCDC1Abnormity5; - public ModbusReadChannel pvDCDC1Abnormity6; - public ModbusReadChannel pvDCDC1Abnormity7; - public ModbusReadChannel pvDCDC1SwitchState; - public ModbusReadChannel pvDCDC1OutputVoltage; - public ModbusReadChannel pvDCDC1OutputCurrent; - public ModbusReadChannel pvDCDC1OutputPower; - public ModbusReadChannel pvDCDC1InputVoltage; - public ModbusReadChannel pvDCDC1InputCurrent; - public ModbusReadChannel pvDCDC1InputPower; - public ModbusReadChannel pvDCDC1InputEnergy; - public ModbusReadChannel pvDCDC1OutputEnergy; - public ModbusReadChannel pvDCDC1ReactorTemperature; - public ModbusReadChannel pvDCDC1IgbtTemperature; - public ModbusReadChannel pvDCDC1InputTotalChargeEnergy; - public ModbusReadChannel pvDCDC1InputTotalDischargeEnergy; - public ModbusReadChannel pvDCDC1OutputTotalChargeEnergy; - public ModbusReadChannel pvDCDC1OutputTotalDischargeEnergy; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - warning = new StatusBitChannels("Warning", this); - ModbusProtocol protocol = new ModbusProtocol(// - new WriteableModbusRegisterRange(0x0503, - new UnsignedWordElement(0x0503, - pvPowerLimitCommand = new ModbusWriteLongChannel("PvPowerLimitCommand", this) - .multiplier(2).unit("W"))), - new ModbusRegisterRange(0xA000, // - new UnsignedWordElement(0xA000, - bmsDCDCWorkState = new ModbusReadLongChannel("BmsDCDCWorkState", this)// - .label(2, "Initial")// - .label(4, "Stop")// - .label(8, "Ready")// - .label(16, "Running")// - .label(32, "Fault")// - .label(64, "Debug")// - .label(128, "Locked")), - new UnsignedWordElement(0xA001, - bmsDCDCWorkMode = new ModbusReadLongChannel("BmsDCDCWorkMode", this)// - .label(128, "Constant Current")// - .label(256, "Constant Voltage")// - .label(512, "Boost MPPT"))), - new ModbusRegisterRange(0xA100, // - new UnsignedWordElement(0xA100, - bmsDCDCSuggestiveInformation1 = warning - .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation1", this)// - .label(1, "Current sampling channel abnormity on high voltage side")// - .label(2, "Current sampling channel abnormity on low voltage side")// - .label(64, "EEPROM parameters over range")// - .label(128, "Update EEPROM failed")// - .label(256, "Read EEPROM failed")// - .label(512, "Current sampling channel abnormity before inductance"))), - new UnsignedWordElement(0xA101, bmsDCDCSuggestiveInformation2 = warning - .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation2", this)// - .label(1, "Reactor power decrease caused by overtemperature")// - .label(2, "IGBT power decrease caused by overtemperature")// - .label(4, "Temperature chanel3 power decrease caused by overtemperature")// - .label(8, "Temperature chanel4 power decrease caused by overtemperature")// - .label(16, "Temperature chanel5 power decrease caused by overtemperature")// - .label(32, "Temperature chanel6 power decrease caused by overtemperature")// - .label(64, "Temperature chanel7 power decrease caused by overtemperature")// - .label(128, "Temperature chanel8 power decrease caused by overtemperature")// - .label(256, "Fan 1 stop failed")// - .label(512, "Fan 2 stop failed")// - .label(1024, "Fan 3 stop failed")// - .label(2048, "Fan 4 stop failed")// - .label(4096, "Fan 1 sartup failed")// - .label(8192, "Fan 2 sartup failed")// - .label(16384, "Fan 3 sartup failed")// - .label(32768, "Fan 4 sartup failed"))), - new UnsignedWordElement(0xA102, - bmsDCDCSuggestiveInformation3 = warning - .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation3", this)// - .label(1, "High voltage side overvoltage")// - .label(2, "High voltage side undervoltage")// - .label(4, "EEPROM parameters over range")// - .label(8, "High voltage side voltage change unconventionally"))), - new UnsignedWordElement(0xA103, bmsDCDCSuggestiveInformation4 = warning - .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation4", this)// - .label(1, "Current abnormity before DC Converter work on high voltage side")// - .label(2, "Current abnormity before DC Converter work on low voltage side")// - .label(4, "Initial Duty Ratio abnormity before DC Converter work")// - .label(8, "Voltage abnormity before DC Converter work on high voltage side")// - .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), - new UnsignedWordElement(0xA104, - bmsDCDCSuggestiveInformation5 = warning - .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation5", this)// - .label(1, "High voltage breaker inspection abnormity")// - .label(2, "Low voltage breaker inspection abnormity")// - .label(4, "DC precharge contactor inspection abnormity")// - .label(8, "DC precharge contactor open unsuccessfully")// - .label(16, "DC main contactor inspection abnormity")// - .label(32, "DC main contactor open unsuccessfully")// - .label(64, "Output contactor close unsuccessfully")// - .label(128, "Output contactor open unsuccessfully")// - .label(256, "AC main contactor close unsuccessfully")// - .label(512, "AC main contactor open unsuccessfully")// - .label(1024, "NegContactor open unsuccessfully")// - .label(2048, "NegContactor close unsuccessfully")// - .label(4096, "NegContactor state abnormal"))), - new DummyElement(0xA105, 0xA10F), - new UnsignedWordElement(0xA110, - bmsDCDCAbnormity1 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity1", this)// - .label(1, "High voltage side of DC Converter undervoltage")// - .label(2, "High voltage side of DC Converter overvoltage")// - .label(4, "Low voltage side of DC Converter undervoltage")// - .label(8, "Low voltage side of DC Converter overvoltage")// - .label(16, "High voltage side of DC Converter overcurrent fault")// - .label(32, "Low voltage side of DC Converter overcurrent fault")// - .label(64, "DC Converter IGBT fault")// - .label(128, "DC Converter Precharge unmet"))), - new UnsignedWordElement(0xA111, - bmsDCDCAbnormity2 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity2", this)// - .label(1, "BECU communication disconnected")// - .label(2, "DC Converter communication disconnected")// - .label(4, "Current configuration over range")// - .label(8, "The battery request stop")// - .label(32, "Overcurrent relay fault")// - .label(64, "Lightning protection device fault")// - .label(128, "DC Converter priamary contactor disconnected abnormally")// - .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// - .label(4096, "DC convetor EEPROM abnormity 1")// - .label(8192, "DC convetor EEPROM abnormity 1")// - .label(16384, "EDC convetor EEPROM abnormity 1"))), - new UnsignedWordElement(0xA112, - bmsDCDCAbnormity3 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity3", this)// - .label(1, "DC Convertor general overload")// - .label(2, "DC short circuit")// - .label(4, "Peak pulse current protection")// - .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// - .label(16, "Effective pulse value overhigh")// - .label(32, "DC Converte severe overload")// - .label(64, - "DC breaker disconnect abnormally on high voltage side of DC convetor")// - .label(128, - "DC breaker disconnect abnormally on low voltage side of DC convetor")// - .label(256, "DC convetor precharge contactor close failed ")// - .label(512, "DC convetor main contactor close failed")// - .label(1024, "AC contactor state abnormity of DC convetor")// - .label(2048, "DC convetor emergency stop")// - .label(4096, "DC converter charging gun disconnected")// - .label(8192, "DC current abnormity before DC convetor work")// - .label(16384, "Fuse disconnected")// - .label(32768, "DC converter hardware current or voltage fault"))), - new UnsignedWordElement(0xA113, - bmsDCDCAbnormity4 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity4", this)// - .label(1, "DC converter crystal oscillator circuit invalidation")// - .label(2, "DC converter reset circuit invalidation")// - .label(4, "DC converter sampling circuit invalidation")// - .label(8, "DC converter digital I/O circuit invalidation")// - .label(16, "DC converter PWM circuit invalidation")// - .label(32, "DC converter X5045 circuit invalidation")// - .label(64, "DC converter CAN circuit invalidation")// - .label(128, "DC converter software&hardware protection circuit invalidation")// - .label(256, "DC converter power circuit invalidation")// - .label(512, "DC converter CPU invalidation")// - .label(1024, "DC converter TINT0 interrupt invalidation")// - .label(2048, "DC converter ADC interrupt invalidation")// - .label(4096, "DC converter CAPITN4 interrupt invalidation")// - .label(8192, "DC converter CAPINT6 interrupt invalidation")// - .label(16384, "DC converter T3PINTinterrupt invalidation")// - .label(32768, "DC converter T4PINTinterrupt invalidation"))), - new UnsignedWordElement(0xA114, - bmsDCDCAbnormity5 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity5", this)// - .label(1, "DC converter PDPINTA interrupt invalidation")// - .label(2, "DC converter T1PINT interrupt invalidation")// - .label(4, "DC converter RESV interrupt invalidation")// - .label(8, "DC converter 100us task invalidation")// - .label(16, "DC converter clock invalidation")// - .label(32, "DC converter EMS memory invalidation")// - .label(64, "DC converter exterior communication invalidation")// - .label(128, "DC converter IO Interface invalidation")// - .label(256, "DC converter Input Voltage bound fault")// - .label(512, "DC converter Outter Voltage bound fault")// - .label(1024, "DC converter Output Voltage bound fault")// - .label(2048, "DC converter Induct Current bound fault")// - .label(4096, "DC converter Input Current bound fault")// - .label(8192, "DC converter Output Current bound fault"))), - new UnsignedWordElement(0xA115, - bmsDCDCAbnormity6 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity6", this)// - .label(1, "DC Reactor over temperature")// - .label(2, "DC IGBT over temperature")// - .label(4, "DC Converter chanel 3 over temperature")// - .label(8, "DC Converter chanel 4 over temperature")// - .label(16, "DC Converter chanel 5 over temperature")// - .label(32, "DC Converter chanel 6 over temperature")// - .label(64, "DC Converter chanel 7 over temperature")// - .label(128, "DC Converter chanel 8 over temperature")// - .label(256, "DC Reactor temperature sampling invalidation")// - .label(512, "DC IGBT temperature sampling invalidation")// - .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// - .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// - .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// - .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// - .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// - .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), - new UnsignedWordElement(0xA116, - bmsDCDCAbnormity7 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity7", this)// - .label(32, "DC Converter inductance current sampling invalidation")// - .label(64, - "Current sampling invalidation on the low voltage sideof DC Converter")// - .label(128, - "Voltage sampling invalidation on the low voltage side of DC Converter")// - .label(256, "Insulation inspection fault")// - .label(512, "NegContactor close unsuccessly")// - .label(1024, "NegContactor cut When running"))), - new DummyElement(0xA117, 0xA11F), - new UnsignedWordElement(0xA120, - bmsDCDCSwitchState = new StatusBitChannel("BmsDCDCSwitchState", this)// - .label(1, "DC precharge contactor")// - .label(2, "DC main contactor")// - .label(4, "Output contactor")// - .label(8, "Output breaker")// - .label(16, "Input breaker")// - .label(32, "AC contactor")// - .label(64, "Emergency stop button")// - .label(128, "NegContactor"))), - new ModbusRegisterRange(0xA130, // - new SignedWordElement(0xA130, - bmsDCDCOutputVoltage = new ModbusReadLongChannel("BmsDCDCOutputVoltage", this) - .unit("mV").multiplier(2)), - new SignedWordElement(0xA131, - bmsDCDCOutputCurrent = new ModbusReadLongChannel("BmsDCDCOutputCurrent", this) - .unit("mA").multiplier(2)), - new SignedWordElement(0xA132, - bmsDCDCOutputPower = new ModbusReadLongChannel("BmsDCDCOutputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xA133, - bmsDCDCInputVoltage = new ModbusReadLongChannel("BmsDCDCInputVoltage", this).unit("mV") - .multiplier(2)), - new SignedWordElement(0xA134, - bmsDCDCInputCurrent = new ModbusReadLongChannel("BmsDCDCInputCurrent", this).unit("mA") - .multiplier(2)), - new SignedWordElement(0xA135, - bmsDCDCInputPower = new ModbusReadLongChannel("BmsDCDCInputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xA136, - bmsDCDCInputEnergy = new ModbusReadLongChannel("BmsDCDCInputEnergy", this).unit("Wh") - .multiplier(2)), - new SignedWordElement(0xA137, - bmsDCDCOutputEnergy = new ModbusReadLongChannel("BmsDCDCOutputEnergy", this).unit("Wh") - .multiplier(2)), - new DummyElement(0xA138, 0xA13F), - new SignedWordElement(0xA140, - bmsDCDCReactorTemperature = new ModbusReadLongChannel("BmsDCDCReactorTemperature", this) - .unit("°C")), - new SignedWordElement(0xA141, - bmsDCDCIgbtTemperature = new ModbusReadLongChannel("BmsDCDCIgbtTemperature", this) - .unit("°C")), - new DummyElement(0xA142, 0xA14F), - new UnsignedDoublewordElement(0xA150, - bmsDCDCInputTotalChargeEnergy = new ModbusReadLongChannel( - "BmsDCDCInputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA152, - bmsDCDCInputTotalDischargeEnergy = new ModbusReadLongChannel( - "BmsDCDCInputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA154, - bmsDCDCOutputTotalChargeEnergy = new ModbusReadLongChannel( - "BmsDCDCOutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA156, - bmsDCDCOutputTotalDischargeEnergy = new ModbusReadLongChannel( - "BmsDCDCOutputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW)), - new ModbusRegisterRange(0xA300, // - new UnsignedWordElement(0xA300, - bmsDCDC1WorkState = new ModbusReadLongChannel("BmsDCDC1WorkState", this)// - .label(2, "Initial")// - .label(4, "Stop")// - .label(8, "Ready")// - .label(16, "Running")// - .label(32, "Fault")// - .label(64, "Debug")// - .label(128, "Locked")), - new UnsignedWordElement(0xA301, - bmsDCDC1WorkMode = new ModbusReadLongChannel("BmsDCDC1WorkMode", this)// - .label(128, "Constant Current")// - .label(256, "Constant Voltage")// - .label(512, "Boost MPPT"))), - new ModbusRegisterRange(0xA400, // - new UnsignedWordElement(0xA400, - bmsDCDC1SuggestiveInformation1 = warning - .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation1", this)// - .label(1, "Current sampling channel abnormity on high voltage side")// - .label(2, "Current sampling channel abnormity on low voltage side")// - .label(64, "EEPROM parameters over range")// - .label(128, "Update EEPROM failed")// - .label(256, "Read EEPROM failed")// - .label(512, "Current sampling channel abnormity before inductance"))), - new UnsignedWordElement(0xA401, bmsDCDC1SuggestiveInformation2 = warning - .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation2", this)// - .label(1, "Reactor power decrease caused by overtemperature")// - .label(2, "IGBT power decrease caused by overtemperature")// - .label(4, "Temperature chanel3 power decrease caused by overtemperature")// - .label(8, "Temperature chanel4 power decrease caused by overtemperature")// - .label(16, "Temperature chanel5 power decrease caused by overtemperature")// - .label(32, "Temperature chanel6 power decrease caused by overtemperature")// - .label(64, "Temperature chanel7 power decrease caused by overtemperature")// - .label(128, "Temperature chanel8 power decrease caused by overtemperature")// - .label(256, "Fan 1 stop failed")// - .label(512, "Fan 2 stop failed")// - .label(1024, "Fan 3 stop failed")// - .label(2048, "Fan 4 stop failed")// - .label(4096, "Fan 1 sartup failed")// - .label(8192, "Fan 2 sartup failed")// - .label(16384, "Fan 3 sartup failed")// - .label(32768, "Fan 4 sartup failed"))), - new UnsignedWordElement(0xA402, - bmsDCDC1SuggestiveInformation3 = warning - .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation3", this)// - .label(1, "High voltage side overvoltage")// - .label(2, "High voltage side undervoltage")// - .label(4, "EEPROM parameters over range")// - .label(8, "High voltage side voltage change unconventionally"))), - new UnsignedWordElement(0xA403, bmsDCDC1SuggestiveInformation4 = warning - .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation4", this)// - .label(1, "Current abnormity before DC Converter work on high voltage side")// - .label(2, "Current abnormity before DC Converter work on low voltage side")// - .label(4, "Initial Duty Ratio abnormity before DC Converter work")// - .label(8, "Voltage abnormity before DC Converter work on high voltage side")// - .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), - new UnsignedWordElement(0xA404, - bmsDCDC1SuggestiveInformation5 = warning - .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation5", this)// - .label(1, "High voltage breaker inspection abnormity")// - .label(2, "Low voltage breaker inspection abnormity")// - .label(4, "DC precharge contactor inspection abnormity")// - .label(8, "DC precharge contactor open unsuccessfully")// - .label(16, "DC main contactor inspection abnormity")// - .label(32, "DC main contactor open unsuccessfully")// - .label(64, "Output contactor close unsuccessfully")// - .label(128, "Output contactor open unsuccessfully")// - .label(256, "AC main contactor close unsuccessfully")// - .label(512, "AC main contactor open unsuccessfully")// - .label(1024, "NegContactor open unsuccessfully")// - .label(2048, "NegContactor close unsuccessfully")// - .label(4096, "NegContactor state abnormal"))), - new DummyElement(0xA405, 0xA40F), - new UnsignedWordElement(0xA410, - bmsDCDC1Abnormity1 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity1", this)// - .label(1, "High voltage side of DC Converter undervoltage")// - .label(2, "High voltage side of DC Converter overvoltage")// - .label(4, "Low voltage side of DC Converter undervoltage")// - .label(8, "Low voltage side of DC Converter overvoltage")// - .label(16, "High voltage side of DC Converter overcurrent fault")// - .label(32, "Low voltage side of DC Converter overcurrent fault")// - .label(64, "DC Converter IGBT fault")// - .label(128, "DC Converter Precharge unmet"))), - new UnsignedWordElement(0xA411, - bmsDCDC1Abnormity2 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity2", this)// - .label(1, "BECU communication disconnected")// - .label(2, "DC Converter communication disconnected")// - .label(4, "Current configuration over range")// - .label(8, "The battery request stop")// - .label(32, "Overcurrent relay fault")// - .label(64, "Lightning protection device fault")// - .label(128, "DC Converter priamary contactor disconnected abnormally")// - .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// - .label(4096, "DC convetor EEPROM abnormity 1")// - .label(8192, "DC convetor EEPROM abnormity 1")// - .label(16384, "EDC convetor EEPROM abnormity 1"))), - new UnsignedWordElement(0xA412, - bmsDCDC1Abnormity3 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity3", this)// - .label(1, "DC Convertor general overload")// - .label(2, "DC short circuit")// - .label(4, "Peak pulse current protection")// - .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// - .label(16, "Effective pulse value overhigh")// - .label(32, "DC Converte severe overload")// - .label(64, - "DC breaker disconnect abnormally on high voltage side of DC convetor")// - .label(128, - "DC breaker disconnect abnormally on low voltage side of DC convetor")// - .label(256, "DC convetor precharge contactor close failed ")// - .label(512, "DC convetor main contactor close failed")// - .label(1024, "AC contactor state abnormity of DC convetor")// - .label(2048, "DC convetor emergency stop")// - .label(4096, "DC converter charging gun disconnected")// - .label(8192, "DC current abnormity before DC convetor work")// - .label(16384, "Fuse disconnected")// - .label(32768, "DC converter hardware current or voltage fault"))), - new UnsignedWordElement(0xA413, - bmsDCDC1Abnormity4 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity4", this)// - .label(1, "DC converter crystal oscillator circuit invalidation")// - .label(2, "DC converter reset circuit invalidation")// - .label(4, "DC converter sampling circuit invalidation")// - .label(8, "DC converter digital I/O circuit invalidation")// - .label(16, "DC converter PWM circuit invalidation")// - .label(32, "DC converter X5045 circuit invalidation")// - .label(64, "DC converter CAN circuit invalidation")// - .label(128, "DC converter software&hardware protection circuit invalidation")// - .label(256, "DC converter power circuit invalidation")// - .label(512, "DC converter CPU invalidation")// - .label(1024, "DC converter TINT0 interrupt invalidation")// - .label(2048, "DC converter ADC interrupt invalidation")// - .label(4096, "DC converter CAPITN4 interrupt invalidation")// - .label(8192, "DC converter CAPINT6 interrupt invalidation")// - .label(16384, "DC converter T3PINTinterrupt invalidation")// - .label(32768, "DC converter T4PINTinterrupt invalidation"))), - new UnsignedWordElement(0xA414, - bmsDCDC1Abnormity5 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity5", this)// - .label(1, "DC converter PDPINTA interrupt invalidation")// - .label(2, "DC converter T1PINT interrupt invalidation")// - .label(4, "DC converter RESV interrupt invalidation")// - .label(8, "DC converter 100us task invalidation")// - .label(16, "DC converter clock invalidation")// - .label(32, "DC converter EMS memory invalidation")// - .label(64, "DC converter exterior communication invalidation")// - .label(128, "DC converter IO Interface invalidation")// - .label(256, "DC converter Input Voltage bound fault")// - .label(512, "DC converter Outter Voltage bound fault")// - .label(1024, "DC converter Output Voltage bound fault")// - .label(2048, "DC converter Induct Current bound fault")// - .label(4096, "DC converter Input Current bound fault")// - .label(8192, "DC converter Output Current bound fault"))), - new UnsignedWordElement(0xA415, - bmsDCDC1Abnormity6 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity6", this)// - .label(1, "DC Reactor over temperature")// - .label(2, "DC IGBT over temperature")// - .label(4, "DC Converter chanel 3 over temperature")// - .label(8, "DC Converter chanel 4 over temperature")// - .label(16, "DC Converter chanel 5 over temperature")// - .label(32, "DC Converter chanel 6 over temperature")// - .label(64, "DC Converter chanel 7 over temperature")// - .label(128, "DC Converter chanel 8 over temperature")// - .label(256, "DC Reactor temperature sampling invalidation")// - .label(512, "DC IGBT temperature sampling invalidation")// - .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// - .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// - .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// - .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// - .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// - .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), - new UnsignedWordElement(0xA416, - bmsDCDC1Abnormity7 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity7", this)// - .label(32, "DC Converter inductance current sampling invalidation")// - .label(64, - "Current sampling invalidation on the low voltage sideof DC Converter")// - .label(128, - "Voltage sampling invalidation on the low voltage side of DC Converter")// - .label(256, "Insulation inspection fault")// - .label(512, "NegContactor close unsuccessly")// - .label(1024, "NegContactor cut When running"))), - new DummyElement(0xA417, 0xA41F), - new UnsignedWordElement(0xA420, - bmsDCDC1SwitchState = new StatusBitChannel("BmsDCDC1SwitchState", this)// - .label(1, "DC precharge contactor")// - .label(2, "DC main contactor")// - .label(4, "Output contactor")// - .label(8, "Output breaker")// - .label(16, "Input breaker")// - .label(32, "AC contactor")// - .label(64, "Emergency stop button")// - .label(128, "NegContactor"))), - new ModbusRegisterRange(0xA430, // - new SignedWordElement(0xA430, - bmsDCDC1OutputVoltage = new ModbusReadLongChannel("BmsDCDC1OutputVoltage", this) - .unit("mV").multiplier(2)), - new SignedWordElement(0xA431, - bmsDCDC1OutputCurrent = new ModbusReadLongChannel("BmsDCDC1OutputCurrent", this) - .unit("mA").multiplier(2)), - new SignedWordElement(0xA432, - bmsDCDC1OutputPower = new ModbusReadLongChannel("BmsDCDC1OutputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xA433, - bmsDCDC1InputVoltage = new ModbusReadLongChannel("BmsDCDC1InputVoltage", this) - .unit("mV").multiplier(2)), - new SignedWordElement(0xA434, - bmsDCDC1InputCurrent = new ModbusReadLongChannel("BmsDCDC1InputCurrent", this) - .unit("mA").multiplier(2)), - new SignedWordElement(0xA435, - bmsDCDC1InputPower = new ModbusReadLongChannel("BmsDCDC1InputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xA436, - bmsDCDC1InputEnergy = new ModbusReadLongChannel("BmsDCDC1InputEnergy", this).unit("Wh") - .multiplier(2)), - new SignedWordElement(0xA437, - bmsDCDC1OutputEnergy = new ModbusReadLongChannel("BmsDCDC1OutputEnergy", this) - .unit("Wh").multiplier(2)), - new DummyElement(0xA438, 0xA43F), - new SignedWordElement(0xA440, - bmsDCDC1ReactorTemperature = new ModbusReadLongChannel("BmsDCDC1ReactorTemperature", - this).unit("°C")), - new SignedWordElement(0xA441, - bmsDCDC1IgbtTemperature = new ModbusReadLongChannel("BmsDCDC1IgbtTemperature", this) - .unit("°C")), - new DummyElement(0xA442, 0xA44F), - new UnsignedDoublewordElement(0xA450, - bmsDCDC1InputTotalChargeEnergy = new ModbusReadLongChannel( - "BmsDCDC1InputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA452, - bmsDCDC1InputTotalDischargeEnergy = new ModbusReadLongChannel( - "BmsDCDC1InputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA454, - bmsDCDC1OutputTotalChargeEnergy = new ModbusReadLongChannel( - "BmsDCDC1OutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA456, - bmsDCDC1OutputTotalDischargeEnergy = new ModbusReadLongChannel( - "BmsDCDC1OutputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW)), - new ModbusRegisterRange(0xA600, // - new UnsignedWordElement(0xA600, - pvDCDCWorkState = new ModbusReadLongChannel("PvDCDCWorkState", this)// - .label(2, "Initial")// - .label(4, "Stop")// - .label(8, "Ready")// - .label(16, "Running")// - .label(32, "Fault")// - .label(64, "Debug")// - .label(128, "Locked")), - new UnsignedWordElement(0xA601, - pvDCDCWorkMode = new ModbusReadLongChannel("PvDCDCWorkMode", this)// - .label(128, "Constant Current")// - .label(256, "Constant Voltage")// - .label(512, "Boost MPPT"))), - new ModbusRegisterRange(0xA700, // - new UnsignedWordElement(0xA700, - pvDCDCSuggestiveInformation1 = warning - .channel(new StatusBitChannel("PvDCDCSuggestiveInformation1", this)// - .label(1, "Current sampling channel abnormity on high voltage side")// - .label(2, "Current sampling channel abnormity on low voltage side")// - .label(64, "EEPROM parameters over range")// - .label(128, "Update EEPROM failed")// - .label(256, "Read EEPROM failed")// - .label(512, "Current sampling channel abnormity before inductance"))), - new UnsignedWordElement(0xA701, pvDCDCSuggestiveInformation2 = warning - .channel(new StatusBitChannel("PvDCDCSuggestiveInformation2", this)// - .label(1, "Reactor power decrease caused by overtemperature")// - .label(2, "IGBT power decrease caused by overtemperature")// - .label(4, "Temperature chanel3 power decrease caused by overtemperature")// - .label(8, "Temperature chanel4 power decrease caused by overtemperature")// - .label(16, "Temperature chanel5 power decrease caused by overtemperature")// - .label(32, "Temperature chanel6 power decrease caused by overtemperature")// - .label(64, "Temperature chanel7 power decrease caused by overtemperature")// - .label(128, "Temperature chanel8 power decrease caused by overtemperature")// - .label(256, "Fan 1 stop failed")// - .label(512, "Fan 2 stop failed")// - .label(1024, "Fan 3 stop failed")// - .label(2048, "Fan 4 stop failed")// - .label(4096, "Fan 1 sartup failed")// - .label(8192, "Fan 2 sartup failed")// - .label(16384, "Fan 3 sartup failed")// - .label(32768, "Fan 4 sartup failed"))), - new UnsignedWordElement(0xA702, - pvDCDCSuggestiveInformation3 = warning - .channel(new StatusBitChannel("PvDCDCSuggestiveInformation3", this)// - .label(1, "High voltage side overvoltage")// - .label(2, "High voltage side undervoltage")// - .label(4, "EEPROM parameters over range")// - .label(8, "High voltage side voltage change unconventionally"))), - new UnsignedWordElement(0xA703, pvDCDCSuggestiveInformation4 = warning - .channel(new StatusBitChannel("PvDCDCSuggestiveInformation4", this)// - .label(1, "Current abnormity before DC Converter work on high voltage side")// - .label(2, "Current abnormity before DC Converter work on low voltage side")// - .label(4, "Initial Duty Ratio abnormity before DC Converter work")// - .label(8, "Voltage abnormity before DC Converter work on high voltage side")// - .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), - new UnsignedWordElement(0xA704, - pvDCDCSuggestiveInformation5 = warning - .channel(new StatusBitChannel("PvDCDCSuggestiveInformation5", this)// - .label(1, "High voltage breaker inspection abnormity")// - .label(2, "Low voltage breaker inspection abnormity")// - .label(4, "DC precharge contactor inspection abnormity")// - .label(8, "DC precharge contactor open unsuccessfully")// - .label(16, "DC main contactor inspection abnormity")// - .label(32, "DC main contactor open unsuccessfully")// - .label(64, "Output contactor close unsuccessfully")// - .label(128, "Output contactor open unsuccessfully")// - .label(256, "AC main contactor close unsuccessfully")// - .label(512, "AC main contactor open unsuccessfully")// - .label(1024, "NegContactor open unsuccessfully")// - .label(2048, "NegContactor close unsuccessfully")// - .label(4096, "NegContactor state abnormal"))), - new DummyElement(0xA705, 0xA70F), - new UnsignedWordElement(0xA710, - pvDCDCAbnormity1 = warning.channel(new StatusBitChannel("PvDCDCAbnormity1", this)// - .label(1, "High voltage side of DC Converter undervoltage")// - .label(2, "High voltage side of DC Converter overvoltage")// - .label(4, "Low voltage side of DC Converter undervoltage")// - .label(8, "Low voltage side of DC Converter overvoltage")// - .label(16, "High voltage side of DC Converter overcurrent fault")// - .label(32, "Low voltage side of DC Converter overcurrent fault")// - .label(64, "DC Converter IGBT fault")// - .label(128, "DC Converter Precharge unmet"))), - new UnsignedWordElement(0xA711, - pvDCDCAbnormity2 = warning.channel(new StatusBitChannel("PvDCDCAbnormity2", this)// - .label(1, "BECU communication disconnected")// - .label(2, "DC Converter communication disconnected")// - .label(4, "Current configuration over range")// - .label(8, "The battery request stop")// - .label(32, "Overcurrent relay fault")// - .label(64, "Lightning protection device fault")// - .label(128, "DC Converter priamary contactor disconnected abnormally")// - .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// - .label(4096, "DC convetor EEPROM abnormity 1")// - .label(8192, "DC convetor EEPROM abnormity 1")// - .label(16384, "EDC convetor EEPROM abnormity 1"))), - new UnsignedWordElement(0xA712, - pvDCDCAbnormity3 = warning.channel(new StatusBitChannel("PvDCDCAbnormity3", this)// - .label(1, "DC Convertor general overload")// - .label(2, "DC short circuit")// - .label(4, "Peak pulse current protection")// - .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// - .label(16, "Effective pulse value overhigh")// - .label(32, "DC Converte severe overload")// - .label(64, - "DC breaker disconnect abnormally on high voltage side of DC convetor")// - .label(128, - "DC breaker disconnect abnormally on low voltage side of DC convetor")// - .label(256, "DC convetor precharge contactor close failed ")// - .label(512, "DC convetor main contactor close failed")// - .label(1024, "AC contactor state abnormity of DC convetor")// - .label(2048, "DC convetor emergency stop")// - .label(4096, "DC converter charging gun disconnected")// - .label(8192, "DC current abnormity before DC convetor work")// - .label(16384, "Fuse disconnected")// - .label(32768, "DC converter hardware current or voltage fault"))), - new UnsignedWordElement(0xA713, - pvDCDCAbnormity4 = warning.channel(new StatusBitChannel("PvDCDCAbnormity4", this)// - .label(1, "DC converter crystal oscillator circuit invalidation")// - .label(2, "DC converter reset circuit invalidation")// - .label(4, "DC converter sampling circuit invalidation")// - .label(8, "DC converter digital I/O circuit invalidation")// - .label(16, "DC converter PWM circuit invalidation")// - .label(32, "DC converter X5045 circuit invalidation")// - .label(64, "DC converter CAN circuit invalidation")// - .label(128, "DC converter software&hardware protection circuit invalidation")// - .label(256, "DC converter power circuit invalidation")// - .label(512, "DC converter CPU invalidation")// - .label(1024, "DC converter TINT0 interrupt invalidation")// - .label(2048, "DC converter ADC interrupt invalidation")// - .label(4096, "DC converter CAPITN4 interrupt invalidation")// - .label(8192, "DC converter CAPINT6 interrupt invalidation")// - .label(16384, "DC converter T3PINTinterrupt invalidation")// - .label(32768, "DC converter T4PINTinterrupt invalidation"))), - new UnsignedWordElement(0xA714, - pvDCDCAbnormity5 = warning.channel(new StatusBitChannel("PvDCDCAbnormity5", this)// - .label(1, "DC converter PDPINTA interrupt invalidation")// - .label(2, "DC converter T1PINT interrupt invalidation")// - .label(4, "DC converter RESV interrupt invalidation")// - .label(8, "DC converter 100us task invalidation")// - .label(16, "DC converter clock invalidation")// - .label(32, "DC converter EMS memory invalidation")// - .label(64, "DC converter exterior communication invalidation")// - .label(128, "DC converter IO Interface invalidation")// - .label(256, "DC converter Input Voltage bound fault")// - .label(512, "DC converter Outter Voltage bound fault")// - .label(1024, "DC converter Output Voltage bound fault")// - .label(2048, "DC converter Induct Current bound fault")// - .label(4096, "DC converter Input Current bound fault")// - .label(8192, "DC converter Output Current bound fault"))), - new UnsignedWordElement(0xA715, - pvDCDCAbnormity6 = warning.channel(new StatusBitChannel("PvDCDCAbnormity6", this)// - .label(1, "DC Reactor over temperature")// - .label(2, "DC IGBT over temperature")// - .label(4, "DC Converter chanel 3 over temperature")// - .label(8, "DC Converter chanel 4 over temperature")// - .label(16, "DC Converter chanel 5 over temperature")// - .label(32, "DC Converter chanel 6 over temperature")// - .label(64, "DC Converter chanel 7 over temperature")// - .label(128, "DC Converter chanel 8 over temperature")// - .label(256, "DC Reactor temperature sampling invalidation")// - .label(512, "DC IGBT temperature sampling invalidation")// - .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// - .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// - .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// - .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// - .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// - .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), - new UnsignedWordElement(0xA716, - pvDCDCAbnormity7 = warning.channel(new StatusBitChannel("PvDCDCAbnormity7", this)// - .label(32, "DC Converter inductance current sampling invalidation")// - .label(64, - "Current sampling invalidation on the low voltage sideof DC Converter")// - .label(128, - "Voltage sampling invalidation on the low voltage side of DC Converter")// - .label(256, "Insulation inspection fault")// - .label(512, "NegContactor close unsuccessly")// - .label(1024, "NegContactor cut When running"))), - new DummyElement(0xA717, 0xA71F), - new UnsignedWordElement(0xA720, - pvDCDCSwitchState = new StatusBitChannel("PvDCDCSwitchState", this)// - .label(1, "DC precharge contactor")// - .label(2, "DC main contactor")// - .label(4, "Output contactor")// - .label(8, "Output breaker")// - .label(16, "Input breaker")// - .label(32, "AC contactor")// - .label(64, "Emergency stop button")// - .label(128, "NegContactor"))), - new ModbusRegisterRange(0xA730, // - new SignedWordElement(0xA730, - pvDCDCOutputVoltage = new ModbusReadLongChannel("PvDCDCOutputVoltage", this).unit("mV") - .multiplier(2)), - new SignedWordElement(0xA731, - pvDCDCOutputCurrent = new ModbusReadLongChannel("PvDCDCOutputCurrent", this).unit("mA") - .multiplier(2)), - new SignedWordElement(0xA732, - pvDCDCOutputPower = new ModbusReadLongChannel("PvDCDCOutputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xA733, - pvDCDCInputVoltage = new ModbusReadLongChannel("PvDCDCInputVoltage", this).unit("mV") - .multiplier(2)), - new SignedWordElement(0xA734, - pvDCDCInputCurrent = new ModbusReadLongChannel("PvDCDCInputCurrent", this).unit("mA") - .multiplier(2)), - new SignedWordElement(0xA735, - pvDCDCInputPower = new ModbusReadLongChannel("PvDCDCInputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xA736, - pvDCDCInputEnergy = new ModbusReadLongChannel("PvDCDCInputEnergy", this).unit("Wh") - .multiplier(2)), - new SignedWordElement(0xA737, - pvDCDCOutputEnergy = new ModbusReadLongChannel("PvDCDCOutputEnergy", this).unit("Wh") - .multiplier(2)), - new DummyElement(0xA738, 0xA73F), - new SignedWordElement(0xA740, - pvDCDCReactorTemperature = new ModbusReadLongChannel("PvDCDCReactorTemperature", this) - .unit("°C")), - new SignedWordElement(0xA741, - pvDCDCIgbtTemperature = new ModbusReadLongChannel("PvDCDCIgbtTemperature", this) - .unit("°C")), - new DummyElement(0xA742, 0xA74F), - new UnsignedDoublewordElement(0xA750, - pvDCDCInputTotalChargeEnergy = new ModbusReadLongChannel("PvDCDCInputTotalChargeEnergy", - this).unit("Wh").multiplier(2)).wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA752, - pvDCDCInputTotalDischargeEnergy = new ModbusReadLongChannel( - "PvDCDCInputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA754, - pvDCDCOutputTotalChargeEnergy = new ModbusReadLongChannel( - "PvDCDCOutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xA756, - pvDCDCOutputTotalDischargeEnergy = new ModbusReadLongChannel( - "PvDCDCOutputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW)), - new ModbusRegisterRange(0xA900, // - new UnsignedWordElement(0xA900, - pvDCDC1WorkState = new ModbusReadLongChannel("PvDCDC1WorkState", this)// - .label(2, "Initial")// - .label(4, "Stop")// - .label(8, "Ready")// - .label(16, "Running")// - .label(32, "Fault")// - .label(64, "Debug")// - .label(128, "Locked")), - new UnsignedWordElement(0xA901, - pvDCDC1WorkMode = new ModbusReadLongChannel("PvDCDC1WorkMode", this)// - .label(128, "Constant Current")// - .label(256, "Constant Voltage")// - .label(512, "Boost MPPT"))), - new ModbusRegisterRange(0xAA00, // - new UnsignedWordElement(0xAA00, - pvDCDC1SuggestiveInformation1 = warning - .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation1", this)// - .label(1, "Current sampling channel abnormity on high voltage side")// - .label(2, "Current sampling channel abnormity on low voltage side")// - .label(64, "EEPROM parameters over range")// - .label(128, "Update EEPROM failed")// - .label(256, "Read EEPROM failed")// - .label(512, "Current sampling channel abnormity before inductance"))), - new UnsignedWordElement(0xAA01, pvDCDC1SuggestiveInformation2 = warning - .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation2", this)// - .label(1, "Reactor power decrease caused by overtemperature")// - .label(2, "IGBT power decrease caused by overtemperature")// - .label(4, "Temperature chanel3 power decrease caused by overtemperature")// - .label(8, "Temperature chanel4 power decrease caused by overtemperature")// - .label(16, "Temperature chanel5 power decrease caused by overtemperature")// - .label(32, "Temperature chanel6 power decrease caused by overtemperature")// - .label(64, "Temperature chanel7 power decrease caused by overtemperature")// - .label(128, "Temperature chanel8 power decrease caused by overtemperature")// - .label(256, "Fan 1 stop failed")// - .label(512, "Fan 2 stop failed")// - .label(1024, "Fan 3 stop failed")// - .label(2048, "Fan 4 stop failed")// - .label(4096, "Fan 1 sartup failed")// - .label(8192, "Fan 2 sartup failed")// - .label(16384, "Fan 3 sartup failed")// - .label(32768, "Fan 4 sartup failed"))), - new UnsignedWordElement(0xAA02, - pvDCDC1SuggestiveInformation3 = warning - .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation3", this)// - .label(1, "High voltage side overvoltage")// - .label(2, "High voltage side undervoltage")// - .label(4, "EEPROM parameters over range")// - .label(8, "High voltage side voltage change unconventionally"))), - new UnsignedWordElement(0xAA03, pvDCDC1SuggestiveInformation4 = warning - .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation4", this)// - .label(1, "Current abnormity before DC Converter work on high voltage side")// - .label(2, "Current abnormity before DC Converter work on low voltage side")// - .label(4, "Initial Duty Ratio abnormity before DC Converter work")// - .label(8, "Voltage abnormity before DC Converter work on high voltage side")// - .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), - new UnsignedWordElement(0xAA04, - pvDCDC1SuggestiveInformation5 = warning - .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation5", this)// - .label(1, "High voltage breaker inspection abnormity")// - .label(2, "Low voltage breaker inspection abnormity")// - .label(4, "DC precharge contactor inspection abnormity")// - .label(8, "DC precharge contactor open unsuccessfully")// - .label(16, "DC main contactor inspection abnormity")// - .label(32, "DC main contactor open unsuccessfully")// - .label(64, "Output contactor close unsuccessfully")// - .label(128, "Output contactor open unsuccessfully")// - .label(256, "AC main contactor close unsuccessfully")// - .label(512, "AC main contactor open unsuccessfully")// - .label(1024, "NegContactor open unsuccessfully")// - .label(2048, "NegContactor close unsuccessfully")// - .label(4096, "NegContactor state abnormal"))), - new DummyElement(0xAA05, 0xAA0F), - new UnsignedWordElement(0xAA10, - pvDCDC1Abnormity1 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity1", this)// - .label(1, "High voltage side of DC Converter undervoltage")// - .label(2, "High voltage side of DC Converter overvoltage")// - .label(4, "Low voltage side of DC Converter undervoltage")// - .label(8, "Low voltage side of DC Converter overvoltage")// - .label(16, "High voltage side of DC Converter overcurrent fault")// - .label(32, "Low voltage side of DC Converter overcurrent fault")// - .label(64, "DC Converter IGBT fault")// - .label(128, "DC Converter Precharge unmet"))), - new UnsignedWordElement(0xAA11, - pvDCDC1Abnormity2 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity2", this)// - .label(1, "BECU communication disconnected")// - .label(2, "DC Converter communication disconnected")// - .label(4, "Current configuration over range")// - .label(8, "The battery request stop")// - .label(32, "Overcurrent relay fault")// - .label(64, "Lightning protection device fault")// - .label(128, "DC Converter priamary contactor disconnected abnormally")// - .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// - .label(4096, "DC convetor EEPROM abnormity 1")// - .label(8192, "DC convetor EEPROM abnormity 1")// - .label(16384, "EDC convetor EEPROM abnormity 1"))), - new UnsignedWordElement(0xAA12, - pvDCDC1Abnormity3 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity3", this)// - .label(1, "DC Convertor general overload")// - .label(2, "DC short circuit")// - .label(4, "Peak pulse current protection")// - .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// - .label(16, "Effective pulse value overhigh")// - .label(32, "DC Converte severe overload")// - .label(64, - "DC breaker disconnect abnormally on high voltage side of DC convetor")// - .label(128, - "DC breaker disconnect abnormally on low voltage side of DC convetor")// - .label(256, "DC convetor precharge contactor close failed ")// - .label(512, "DC convetor main contactor close failed")// - .label(1024, "AC contactor state abnormity of DC convetor")// - .label(2048, "DC convetor emergency stop")// - .label(4096, "DC converter charging gun disconnected")// - .label(8192, "DC current abnormity before DC convetor work")// - .label(16384, "Fuse disconnected")// - .label(32768, "DC converter hardware current or voltage fault"))), - new UnsignedWordElement(0xAA13, - pvDCDC1Abnormity4 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity4", this)// - .label(1, "DC converter crystal oscillator circuit invalidation")// - .label(2, "DC converter reset circuit invalidation")// - .label(4, "DC converter sampling circuit invalidation")// - .label(8, "DC converter digital I/O circuit invalidation")// - .label(16, "DC converter PWM circuit invalidation")// - .label(32, "DC converter X5045 circuit invalidation")// - .label(64, "DC converter CAN circuit invalidation")// - .label(128, "DC converter software&hardware protection circuit invalidation")// - .label(256, "DC converter power circuit invalidation")// - .label(512, "DC converter CPU invalidation")// - .label(1024, "DC converter TINT0 interrupt invalidation")// - .label(2048, "DC converter ADC interrupt invalidation")// - .label(4096, "DC converter CAPITN4 interrupt invalidation")// - .label(8192, "DC converter CAPINT6 interrupt invalidation")// - .label(16384, "DC converter T3PINTinterrupt invalidation")// - .label(32768, "DC converter T4PINTinterrupt invalidation"))), - new UnsignedWordElement(0xAA14, - pvDCDC1Abnormity5 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity5", this)// - .label(1, "DC converter PDPINTA interrupt invalidation")// - .label(2, "DC converter T1PINT interrupt invalidation")// - .label(4, "DC converter RESV interrupt invalidation")// - .label(8, "DC converter 100us task invalidation")// - .label(16, "DC converter clock invalidation")// - .label(32, "DC converter EMS memory invalidation")// - .label(64, "DC converter exterior communication invalidation")// - .label(128, "DC converter IO Interface invalidation")// - .label(256, "DC converter Input Voltage bound fault")// - .label(512, "DC converter Outter Voltage bound fault")// - .label(1024, "DC converter Output Voltage bound fault")// - .label(2048, "DC converter Induct Current bound fault")// - .label(4096, "DC converter Input Current bound fault")// - .label(8192, "DC converter Output Current bound fault"))), - new UnsignedWordElement(0xAA15, - pvDCDC1Abnormity6 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity6", this)// - .label(1, "DC Reactor over temperature")// - .label(2, "DC IGBT over temperature")// - .label(4, "DC Converter chanel 3 over temperature")// - .label(8, "DC Converter chanel 4 over temperature")// - .label(16, "DC Converter chanel 5 over temperature")// - .label(32, "DC Converter chanel 6 over temperature")// - .label(64, "DC Converter chanel 7 over temperature")// - .label(128, "DC Converter chanel 8 over temperature")// - .label(256, "DC Reactor temperature sampling invalidation")// - .label(512, "DC IGBT temperature sampling invalidation")// - .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// - .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// - .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// - .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// - .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// - .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), - new UnsignedWordElement(0xAA16, - pvDCDC1Abnormity7 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity7", this)// - .label(32, "DC Converter inductance current sampling invalidation")// - .label(64, - "Current sampling invalidation on the low voltage sideof DC Converter")// - .label(128, - "Voltage sampling invalidation on the low voltage side of DC Converter")// - .label(256, "Insulation inspection fault")// - .label(512, "NegContactor close unsuccessly")// - .label(1024, "NegContactor cut When running"))), - new DummyElement(0xAA17, 0xAA1F), - new UnsignedWordElement(0xAA20, - pvDCDC1SwitchState = new StatusBitChannel("PvDCDC1SwitchState", this)// - .label(1, "DC precharge contactor")// - .label(2, "DC main contactor")// - .label(4, "Output contactor")// - .label(8, "Output breaker")// - .label(16, "Input breaker")// - .label(32, "AC contactor")// - .label(64, "Emergency stop button")// - .label(128, "NegContactor"))), - new ModbusRegisterRange(0xAA30, // - new SignedWordElement(0xAA30, - pvDCDC1OutputVoltage = new ModbusReadLongChannel("PvDCDC1OutputVoltage", this) - .unit("mV").multiplier(2)), - new SignedWordElement(0xAA31, - pvDCDC1OutputCurrent = new ModbusReadLongChannel("PvDCDC1OutputCurrent", this) - .unit("mA").multiplier(2)), - new SignedWordElement(0xAA32, - pvDCDC1OutputPower = new ModbusReadLongChannel("PvDCDC1OutputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xAA33, - pvDCDC1InputVoltage = new ModbusReadLongChannel("PvDCDC1InputVoltage", this).unit("mV") - .multiplier(2)), - new SignedWordElement(0xAA34, - pvDCDC1InputCurrent = new ModbusReadLongChannel("PvDCDC1InputCurrent", this).unit("mA") - .multiplier(2)), - new SignedWordElement(0xAA35, - pvDCDC1InputPower = new ModbusReadLongChannel("PvDCDC1InputPower", this).unit("W") - .multiplier(2)), - new SignedWordElement(0xAA36, - pvDCDC1InputEnergy = new ModbusReadLongChannel("PvDCDC1InputEnergy", this).unit("Wh") - .multiplier(2)), - new SignedWordElement(0xAA37, - pvDCDC1OutputEnergy = new ModbusReadLongChannel("PvDCDC1OutputEnergy", this).unit("Wh") - .multiplier(2)), - new DummyElement(0xAA38, 0xAA3F), - new SignedWordElement(0xAA40, - pvDCDC1ReactorTemperature = new ModbusReadLongChannel("PvDCDC1ReactorTemperature", this) - .unit("°C")), - new SignedWordElement(0xAA41, - pvDCDC1IgbtTemperature = new ModbusReadLongChannel("PvDCDC1IgbtTemperature", this) - .unit("°C")), - new DummyElement(0xAA42, 0xAA4F), - new UnsignedDoublewordElement(0xAA50, - pvDCDC1InputTotalChargeEnergy = new ModbusReadLongChannel( - "PvDCDC1InputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xAA52, - pvDCDC1InputTotalDischargeEnergy = new ModbusReadLongChannel( - "PvDCDC1InputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xAA54, - pvDCDC1OutputTotalChargeEnergy = new ModbusReadLongChannel( - "PvDCDC1OutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0xAA56, - pvDCDC1OutputTotalDischargeEnergy = new ModbusReadLongChannel( - "PvDCDC1OutputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) - .wordOrder(WordOrder.LSWMSW))); - actualPower = new FunctionalReadChannel("ActualPower", this, (channels) -> { - long erg = 0; - try { - for (ReadChannel ch : channels) { - erg += ch.value(); - } - return erg; - } catch (InvalidValueException e) { - return null; - } - }, pvDCDCInputPower, pvDCDC1InputPower); - inputVoltage = new FunctionalReadChannel("InputVoltage", this, (channels) -> { - long erg = 0; - try { - for (ReadChannel ch : channels) { - if (erg < ch.value()) { - erg = ch.value(); - } - } - return erg; - } catch (InvalidValueException e) { - return null; - } - }, pvDCDCInputVoltage, pvDCDC1InputVoltage); - return protocol; - } - - @Override - public WriteChannel setMaxPower() { - return pvPowerLimitCommand; - } - - @Override - public ReadChannel getNominalPower() { - return new StaticValueChannel("NominalPower", this, 60000l); - } - - @Override - public ReadChannel getActualPower() { - return actualPower; - } - - @Override - public ReadChannel getInputVoltage() { - return inputVoltage; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.commercial; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.charger.ChargerNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadChannel; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.ModbusWriteChannel; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedWordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.WordOrder; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; + +@ThingInfo(title = "FENECON Commercial DC-Charger") +public class FeneconCommercialCharger extends ModbusDeviceNature implements ChargerNature { + + /* + * Constructors + */ + public FeneconCommercialCharger(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Inherited Channels + */ + + public StatusBitChannels warning; + + public ModbusWriteChannel pvPowerLimitCommand; + + public ReadChannel actualPower; + + public ReadChannel inputVoltage; + + private final ConfigChannel maxActualPower = new ConfigChannel("maxActualPower", this); + + @Override + public ConfigChannel maxActualPower() { + return maxActualPower; + } + + /* + * BMS DCDC + */ + + public ModbusReadChannel bmsDCDCWorkState; + public ModbusReadChannel bmsDCDCWorkMode; + public ModbusReadChannel bmsDCDCSuggestiveInformation1; + public ModbusReadChannel bmsDCDCSuggestiveInformation2; + public ModbusReadChannel bmsDCDCSuggestiveInformation3; + public ModbusReadChannel bmsDCDCSuggestiveInformation4; + public ModbusReadChannel bmsDCDCSuggestiveInformation5; + public ModbusReadChannel bmsDCDCAbnormity1; + public ModbusReadChannel bmsDCDCAbnormity2; + public ModbusReadChannel bmsDCDCAbnormity3; + public ModbusReadChannel bmsDCDCAbnormity4; + public ModbusReadChannel bmsDCDCAbnormity5; + public ModbusReadChannel bmsDCDCAbnormity6; + public ModbusReadChannel bmsDCDCAbnormity7; + public ModbusReadChannel bmsDCDCSwitchState; + public ModbusReadChannel bmsDCDCOutputVoltage; + public ModbusReadChannel bmsDCDCOutputCurrent; + public ModbusReadChannel bmsDCDCOutputPower; + public ModbusReadChannel bmsDCDCInputVoltage; + public ModbusReadChannel bmsDCDCInputCurrent; + public ModbusReadChannel bmsDCDCInputPower; + public ModbusReadChannel bmsDCDCInputEnergy; + public ModbusReadChannel bmsDCDCOutputEnergy; + public ModbusReadChannel bmsDCDCReactorTemperature; + public ModbusReadChannel bmsDCDCIgbtTemperature; + public ModbusReadChannel bmsDCDCInputTotalChargeEnergy; + public ModbusReadChannel bmsDCDCInputTotalDischargeEnergy; + public ModbusReadChannel bmsDCDCOutputTotalChargeEnergy; + public ModbusReadChannel bmsDCDCOutputTotalDischargeEnergy; + + /* + * BMS DCDC 1 + */ + + public ModbusReadChannel bmsDCDC1WorkState; + public ModbusReadChannel bmsDCDC1WorkMode; + public ModbusReadChannel bmsDCDC1SuggestiveInformation1; + public ModbusReadChannel bmsDCDC1SuggestiveInformation2; + public ModbusReadChannel bmsDCDC1SuggestiveInformation3; + public ModbusReadChannel bmsDCDC1SuggestiveInformation4; + public ModbusReadChannel bmsDCDC1SuggestiveInformation5; + public ModbusReadChannel bmsDCDC1Abnormity1; + public ModbusReadChannel bmsDCDC1Abnormity2; + public ModbusReadChannel bmsDCDC1Abnormity3; + public ModbusReadChannel bmsDCDC1Abnormity4; + public ModbusReadChannel bmsDCDC1Abnormity5; + public ModbusReadChannel bmsDCDC1Abnormity6; + public ModbusReadChannel bmsDCDC1Abnormity7; + public ModbusReadChannel bmsDCDC1SwitchState; + public ModbusReadChannel bmsDCDC1OutputVoltage; + public ModbusReadChannel bmsDCDC1OutputCurrent; + public ModbusReadChannel bmsDCDC1OutputPower; + public ModbusReadChannel bmsDCDC1InputVoltage; + public ModbusReadChannel bmsDCDC1InputCurrent; + public ModbusReadChannel bmsDCDC1InputPower; + public ModbusReadChannel bmsDCDC1InputEnergy; + public ModbusReadChannel bmsDCDC1OutputEnergy; + public ModbusReadChannel bmsDCDC1ReactorTemperature; + public ModbusReadChannel bmsDCDC1IgbtTemperature; + public ModbusReadChannel bmsDCDC1InputTotalChargeEnergy; + public ModbusReadChannel bmsDCDC1InputTotalDischargeEnergy; + public ModbusReadChannel bmsDCDC1OutputTotalChargeEnergy; + public ModbusReadChannel bmsDCDC1OutputTotalDischargeEnergy; + /* + * PV DCDC + */ + + public ModbusReadChannel pvDCDCWorkState; + public ModbusReadChannel pvDCDCWorkMode; + public ModbusReadChannel pvDCDCSuggestiveInformation1; + public ModbusReadChannel pvDCDCSuggestiveInformation2; + public ModbusReadChannel pvDCDCSuggestiveInformation3; + public ModbusReadChannel pvDCDCSuggestiveInformation4; + public ModbusReadChannel pvDCDCSuggestiveInformation5; + public ModbusReadChannel pvDCDCAbnormity1; + public ModbusReadChannel pvDCDCAbnormity2; + public ModbusReadChannel pvDCDCAbnormity3; + public ModbusReadChannel pvDCDCAbnormity4; + public ModbusReadChannel pvDCDCAbnormity5; + public ModbusReadChannel pvDCDCAbnormity6; + public ModbusReadChannel pvDCDCAbnormity7; + public ModbusReadChannel pvDCDCSwitchState; + public ModbusReadChannel pvDCDCOutputVoltage; + public ModbusReadChannel pvDCDCOutputCurrent; + public ModbusReadChannel pvDCDCOutputPower; + public ModbusReadChannel pvDCDCInputVoltage; + public ModbusReadChannel pvDCDCInputCurrent; + public ModbusReadChannel pvDCDCInputPower; + public ModbusReadChannel pvDCDCInputEnergy; + public ModbusReadChannel pvDCDCOutputEnergy; + public ModbusReadChannel pvDCDCReactorTemperature; + public ModbusReadChannel pvDCDCIgbtTemperature; + public ModbusReadChannel pvDCDCInputTotalChargeEnergy; + public ModbusReadChannel pvDCDCInputTotalDischargeEnergy; + public ModbusReadChannel pvDCDCOutputTotalChargeEnergy; + public ModbusReadChannel pvDCDCOutputTotalDischargeEnergy; + + /* + * PV DCDC 1 + */ + + public ModbusReadChannel pvDCDC1WorkState; + public ModbusReadChannel pvDCDC1WorkMode; + public ModbusReadChannel pvDCDC1SuggestiveInformation1; + public ModbusReadChannel pvDCDC1SuggestiveInformation2; + public ModbusReadChannel pvDCDC1SuggestiveInformation3; + public ModbusReadChannel pvDCDC1SuggestiveInformation4; + public ModbusReadChannel pvDCDC1SuggestiveInformation5; + public ModbusReadChannel pvDCDC1Abnormity1; + public ModbusReadChannel pvDCDC1Abnormity2; + public ModbusReadChannel pvDCDC1Abnormity3; + public ModbusReadChannel pvDCDC1Abnormity4; + public ModbusReadChannel pvDCDC1Abnormity5; + public ModbusReadChannel pvDCDC1Abnormity6; + public ModbusReadChannel pvDCDC1Abnormity7; + public ModbusReadChannel pvDCDC1SwitchState; + public ModbusReadChannel pvDCDC1OutputVoltage; + public ModbusReadChannel pvDCDC1OutputCurrent; + public ModbusReadChannel pvDCDC1OutputPower; + public ModbusReadChannel pvDCDC1InputVoltage; + public ModbusReadChannel pvDCDC1InputCurrent; + public ModbusReadChannel pvDCDC1InputPower; + public ModbusReadChannel pvDCDC1InputEnergy; + public ModbusReadChannel pvDCDC1OutputEnergy; + public ModbusReadChannel pvDCDC1ReactorTemperature; + public ModbusReadChannel pvDCDC1IgbtTemperature; + public ModbusReadChannel pvDCDC1InputTotalChargeEnergy; + public ModbusReadChannel pvDCDC1InputTotalDischargeEnergy; + public ModbusReadChannel pvDCDC1OutputTotalChargeEnergy; + public ModbusReadChannel pvDCDC1OutputTotalDischargeEnergy; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + warning = new StatusBitChannels("Warning", this); + ModbusProtocol protocol = new ModbusProtocol(// + new WriteableModbusRegisterRange(0x0503, new UnsignedWordElement(0x0503, + pvPowerLimitCommand = new ModbusWriteLongChannel("PvPowerLimitCommand", this).multiplier(2) + .unit("W"))), + new ModbusRegisterRange(0xA000, // + new UnsignedWordElement(0xA000, + bmsDCDCWorkState = new ModbusReadLongChannel("BmsDCDCWorkState", this)// + .label(2, "Initial")// + .label(4, "Stop")// + .label(8, "Ready")// + .label(16, "Running")// + .label(32, "Fault")// + .label(64, "Debug")// + .label(128, "Locked")), + new UnsignedWordElement(0xA001, + bmsDCDCWorkMode = new ModbusReadLongChannel("BmsDCDCWorkMode", this)// + .label(128, "Constant Current")// + .label(256, "Constant Voltage")// + .label(512, "Boost MPPT"))), + new ModbusRegisterRange(0xA100, // + new UnsignedWordElement(0xA100, + bmsDCDCSuggestiveInformation1 = warning + .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation1", this)// + .label(1, "Current sampling channel abnormity on high voltage side")// + .label(2, "Current sampling channel abnormity on low voltage side")// + .label(64, "EEPROM parameters over range")// + .label(128, "Update EEPROM failed")// + .label(256, "Read EEPROM failed")// + .label(512, "Current sampling channel abnormity before inductance"))), + new UnsignedWordElement(0xA101, bmsDCDCSuggestiveInformation2 = warning + .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation2", this)// + .label(1, "Reactor power decrease caused by overtemperature")// + .label(2, "IGBT power decrease caused by overtemperature")// + .label(4, "Temperature chanel3 power decrease caused by overtemperature")// + .label(8, "Temperature chanel4 power decrease caused by overtemperature")// + .label(16, "Temperature chanel5 power decrease caused by overtemperature")// + .label(32, "Temperature chanel6 power decrease caused by overtemperature")// + .label(64, "Temperature chanel7 power decrease caused by overtemperature")// + .label(128, "Temperature chanel8 power decrease caused by overtemperature")// + .label(256, "Fan 1 stop failed")// + .label(512, "Fan 2 stop failed")// + .label(1024, "Fan 3 stop failed")// + .label(2048, "Fan 4 stop failed")// + .label(4096, "Fan 1 sartup failed")// + .label(8192, "Fan 2 sartup failed")// + .label(16384, "Fan 3 sartup failed")// + .label(32768, "Fan 4 sartup failed"))), + new UnsignedWordElement(0xA102, + bmsDCDCSuggestiveInformation3 = warning + .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation3", this)// + .label(1, "High voltage side overvoltage")// + .label(2, "High voltage side undervoltage")// + .label(4, "EEPROM parameters over range")// + .label(8, "High voltage side voltage change unconventionally"))), + new UnsignedWordElement(0xA103, bmsDCDCSuggestiveInformation4 = warning + .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation4", this)// + .label(1, "Current abnormity before DC Converter work on high voltage side")// + .label(2, "Current abnormity before DC Converter work on low voltage side")// + .label(4, "Initial Duty Ratio abnormity before DC Converter work")// + .label(8, "Voltage abnormity before DC Converter work on high voltage side")// + .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), + new UnsignedWordElement(0xA104, + bmsDCDCSuggestiveInformation5 = warning + .channel(new StatusBitChannel("BmsDCDCSuggestiveInformation5", this)// + .label(1, "High voltage breaker inspection abnormity")// + .label(2, "Low voltage breaker inspection abnormity")// + .label(4, "DC precharge contactor inspection abnormity")// + .label(8, "DC precharge contactor open unsuccessfully")// + .label(16, "DC main contactor inspection abnormity")// + .label(32, "DC main contactor open unsuccessfully")// + .label(64, "Output contactor close unsuccessfully")// + .label(128, "Output contactor open unsuccessfully")// + .label(256, "AC main contactor close unsuccessfully")// + .label(512, "AC main contactor open unsuccessfully")// + .label(1024, "NegContactor open unsuccessfully")// + .label(2048, "NegContactor close unsuccessfully")// + .label(4096, "NegContactor state abnormal"))), + new DummyElement(0xA105, 0xA10F), + new UnsignedWordElement(0xA110, + bmsDCDCAbnormity1 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity1", this)// + .label(1, "High voltage side of DC Converter undervoltage")// + .label(2, "High voltage side of DC Converter overvoltage")// + .label(4, "Low voltage side of DC Converter undervoltage")// + .label(8, "Low voltage side of DC Converter overvoltage")// + .label(16, "High voltage side of DC Converter overcurrent fault")// + .label(32, "Low voltage side of DC Converter overcurrent fault")// + .label(64, "DC Converter IGBT fault")// + .label(128, "DC Converter Precharge unmet"))), + new UnsignedWordElement(0xA111, + bmsDCDCAbnormity2 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity2", this)// + .label(1, "BECU communication disconnected")// + .label(2, "DC Converter communication disconnected")// + .label(4, "Current configuration over range")// + .label(8, "The battery request stop")// + .label(32, "Overcurrent relay fault")// + .label(64, "Lightning protection device fault")// + .label(128, "DC Converter priamary contactor disconnected abnormally")// + .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// + .label(4096, "DC convetor EEPROM abnormity 1")// + .label(8192, "DC convetor EEPROM abnormity 1")// + .label(16384, "EDC convetor EEPROM abnormity 1"))), + new UnsignedWordElement(0xA112, + bmsDCDCAbnormity3 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity3", this)// + .label(1, "DC Convertor general overload")// + .label(2, "DC short circuit")// + .label(4, "Peak pulse current protection")// + .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// + .label(16, "Effective pulse value overhigh")// + .label(32, "DC Converte severe overload")// + .label(64, + "DC breaker disconnect abnormally on high voltage side of DC convetor")// + .label(128, + "DC breaker disconnect abnormally on low voltage side of DC convetor")// + .label(256, "DC convetor precharge contactor close failed ")// + .label(512, "DC convetor main contactor close failed")// + .label(1024, "AC contactor state abnormity of DC convetor")// + .label(2048, "DC convetor emergency stop")// + .label(4096, "DC converter charging gun disconnected")// + .label(8192, "DC current abnormity before DC convetor work")// + .label(16384, "Fuse disconnected")// + .label(32768, "DC converter hardware current or voltage fault"))), + new UnsignedWordElement(0xA113, + bmsDCDCAbnormity4 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity4", this)// + .label(1, "DC converter crystal oscillator circuit invalidation")// + .label(2, "DC converter reset circuit invalidation")// + .label(4, "DC converter sampling circuit invalidation")// + .label(8, "DC converter digital I/O circuit invalidation")// + .label(16, "DC converter PWM circuit invalidation")// + .label(32, "DC converter X5045 circuit invalidation")// + .label(64, "DC converter CAN circuit invalidation")// + .label(128, "DC converter software&hardware protection circuit invalidation")// + .label(256, "DC converter power circuit invalidation")// + .label(512, "DC converter CPU invalidation")// + .label(1024, "DC converter TINT0 interrupt invalidation")// + .label(2048, "DC converter ADC interrupt invalidation")// + .label(4096, "DC converter CAPITN4 interrupt invalidation")// + .label(8192, "DC converter CAPINT6 interrupt invalidation")// + .label(16384, "DC converter T3PINTinterrupt invalidation")// + .label(32768, "DC converter T4PINTinterrupt invalidation"))), + new UnsignedWordElement(0xA114, + bmsDCDCAbnormity5 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity5", this)// + .label(1, "DC converter PDPINTA interrupt invalidation")// + .label(2, "DC converter T1PINT interrupt invalidation")// + .label(4, "DC converter RESV interrupt invalidation")// + .label(8, "DC converter 100us task invalidation")// + .label(16, "DC converter clock invalidation")// + .label(32, "DC converter EMS memory invalidation")// + .label(64, "DC converter exterior communication invalidation")// + .label(128, "DC converter IO Interface invalidation")// + .label(256, "DC converter Input Voltage bound fault")// + .label(512, "DC converter Outter Voltage bound fault")// + .label(1024, "DC converter Output Voltage bound fault")// + .label(2048, "DC converter Induct Current bound fault")// + .label(4096, "DC converter Input Current bound fault")// + .label(8192, "DC converter Output Current bound fault"))), + new UnsignedWordElement(0xA115, + bmsDCDCAbnormity6 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity6", this)// + .label(1, "DC Reactor over temperature")// + .label(2, "DC IGBT over temperature")// + .label(4, "DC Converter chanel 3 over temperature")// + .label(8, "DC Converter chanel 4 over temperature")// + .label(16, "DC Converter chanel 5 over temperature")// + .label(32, "DC Converter chanel 6 over temperature")// + .label(64, "DC Converter chanel 7 over temperature")// + .label(128, "DC Converter chanel 8 over temperature")// + .label(256, "DC Reactor temperature sampling invalidation")// + .label(512, "DC IGBT temperature sampling invalidation")// + .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// + .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// + .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// + .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// + .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// + .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), + new UnsignedWordElement(0xA116, + bmsDCDCAbnormity7 = warning.channel(new StatusBitChannel("BmsDCDCAbnormity7", this)// + .label(32, "DC Converter inductance current sampling invalidation")// + .label(64, + "Current sampling invalidation on the low voltage sideof DC Converter")// + .label(128, + "Voltage sampling invalidation on the low voltage side of DC Converter")// + .label(256, "Insulation inspection fault")// + .label(512, "NegContactor close unsuccessly")// + .label(1024, "NegContactor cut When running"))), + new DummyElement(0xA117, 0xA11F), + new UnsignedWordElement(0xA120, + bmsDCDCSwitchState = new StatusBitChannel("BmsDCDCSwitchState", this)// + .label(1, "DC precharge contactor")// + .label(2, "DC main contactor")// + .label(4, "Output contactor")// + .label(8, "Output breaker")// + .label(16, "Input breaker")// + .label(32, "AC contactor")// + .label(64, "Emergency stop button")// + .label(128, "NegContactor"))), + new ModbusRegisterRange(0xA130, // + new SignedWordElement(0xA130, + bmsDCDCOutputVoltage = new ModbusReadLongChannel("BmsDCDCOutputVoltage", this) + .unit("mV").multiplier(2)), + new SignedWordElement(0xA131, + bmsDCDCOutputCurrent = new ModbusReadLongChannel("BmsDCDCOutputCurrent", this) + .unit("mA").multiplier(2)), + new SignedWordElement(0xA132, + bmsDCDCOutputPower = new ModbusReadLongChannel("BmsDCDCOutputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xA133, + bmsDCDCInputVoltage = new ModbusReadLongChannel("BmsDCDCInputVoltage", this).unit("mV") + .multiplier(2)), + new SignedWordElement(0xA134, + bmsDCDCInputCurrent = new ModbusReadLongChannel("BmsDCDCInputCurrent", this).unit("mA") + .multiplier(2)), + new SignedWordElement(0xA135, + bmsDCDCInputPower = new ModbusReadLongChannel("BmsDCDCInputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xA136, + bmsDCDCInputEnergy = new ModbusReadLongChannel("BmsDCDCInputEnergy", this).unit("Wh") + .multiplier(2)), + new SignedWordElement(0xA137, + bmsDCDCOutputEnergy = new ModbusReadLongChannel("BmsDCDCOutputEnergy", this).unit("Wh") + .multiplier(2)), + new DummyElement(0xA138, 0xA13F), + new SignedWordElement(0xA140, + bmsDCDCReactorTemperature = new ModbusReadLongChannel("BmsDCDCReactorTemperature", this) + .unit("°C")), + new SignedWordElement(0xA141, + bmsDCDCIgbtTemperature = new ModbusReadLongChannel("BmsDCDCIgbtTemperature", this) + .unit("°C")), + new DummyElement(0xA142, 0xA14F), + new UnsignedDoublewordElement(0xA150, + bmsDCDCInputTotalChargeEnergy = new ModbusReadLongChannel( + "BmsDCDCInputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA152, + bmsDCDCInputTotalDischargeEnergy = new ModbusReadLongChannel( + "BmsDCDCInputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA154, + bmsDCDCOutputTotalChargeEnergy = new ModbusReadLongChannel( + "BmsDCDCOutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA156, + bmsDCDCOutputTotalDischargeEnergy = new ModbusReadLongChannel( + "BmsDCDCOutputTotalDischargeEnergy", this).unit("Wh") + .multiplier(2)).wordOrder(WordOrder.LSWMSW)), + new ModbusRegisterRange(0xA300, // + new UnsignedWordElement(0xA300, + bmsDCDC1WorkState = new ModbusReadLongChannel("BmsDCDC1WorkState", this)// + .label(2, "Initial")// + .label(4, "Stop")// + .label(8, "Ready")// + .label(16, "Running")// + .label(32, "Fault")// + .label(64, "Debug")// + .label(128, "Locked")), + new UnsignedWordElement(0xA301, + bmsDCDC1WorkMode = new ModbusReadLongChannel("BmsDCDC1WorkMode", this)// + .label(128, "Constant Current")// + .label(256, "Constant Voltage")// + .label(512, "Boost MPPT"))), + new ModbusRegisterRange(0xA400, // + new UnsignedWordElement(0xA400, + bmsDCDC1SuggestiveInformation1 = warning + .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation1", this)// + .label(1, "Current sampling channel abnormity on high voltage side")// + .label(2, "Current sampling channel abnormity on low voltage side")// + .label(64, "EEPROM parameters over range")// + .label(128, "Update EEPROM failed")// + .label(256, "Read EEPROM failed")// + .label(512, "Current sampling channel abnormity before inductance"))), + new UnsignedWordElement(0xA401, bmsDCDC1SuggestiveInformation2 = warning + .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation2", this)// + .label(1, "Reactor power decrease caused by overtemperature")// + .label(2, "IGBT power decrease caused by overtemperature")// + .label(4, "Temperature chanel3 power decrease caused by overtemperature")// + .label(8, "Temperature chanel4 power decrease caused by overtemperature")// + .label(16, "Temperature chanel5 power decrease caused by overtemperature")// + .label(32, "Temperature chanel6 power decrease caused by overtemperature")// + .label(64, "Temperature chanel7 power decrease caused by overtemperature")// + .label(128, "Temperature chanel8 power decrease caused by overtemperature")// + .label(256, "Fan 1 stop failed")// + .label(512, "Fan 2 stop failed")// + .label(1024, "Fan 3 stop failed")// + .label(2048, "Fan 4 stop failed")// + .label(4096, "Fan 1 sartup failed")// + .label(8192, "Fan 2 sartup failed")// + .label(16384, "Fan 3 sartup failed")// + .label(32768, "Fan 4 sartup failed"))), + new UnsignedWordElement(0xA402, + bmsDCDC1SuggestiveInformation3 = warning + .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation3", this)// + .label(1, "High voltage side overvoltage")// + .label(2, "High voltage side undervoltage")// + .label(4, "EEPROM parameters over range")// + .label(8, "High voltage side voltage change unconventionally"))), + new UnsignedWordElement(0xA403, bmsDCDC1SuggestiveInformation4 = warning + .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation4", this)// + .label(1, "Current abnormity before DC Converter work on high voltage side")// + .label(2, "Current abnormity before DC Converter work on low voltage side")// + .label(4, "Initial Duty Ratio abnormity before DC Converter work")// + .label(8, "Voltage abnormity before DC Converter work on high voltage side")// + .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), + new UnsignedWordElement(0xA404, + bmsDCDC1SuggestiveInformation5 = warning + .channel(new StatusBitChannel("BmsDCDC1SuggestiveInformation5", this)// + .label(1, "High voltage breaker inspection abnormity")// + .label(2, "Low voltage breaker inspection abnormity")// + .label(4, "DC precharge contactor inspection abnormity")// + .label(8, "DC precharge contactor open unsuccessfully")// + .label(16, "DC main contactor inspection abnormity")// + .label(32, "DC main contactor open unsuccessfully")// + .label(64, "Output contactor close unsuccessfully")// + .label(128, "Output contactor open unsuccessfully")// + .label(256, "AC main contactor close unsuccessfully")// + .label(512, "AC main contactor open unsuccessfully")// + .label(1024, "NegContactor open unsuccessfully")// + .label(2048, "NegContactor close unsuccessfully")// + .label(4096, "NegContactor state abnormal"))), + new DummyElement(0xA405, 0xA40F), + new UnsignedWordElement(0xA410, + bmsDCDC1Abnormity1 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity1", this)// + .label(1, "High voltage side of DC Converter undervoltage")// + .label(2, "High voltage side of DC Converter overvoltage")// + .label(4, "Low voltage side of DC Converter undervoltage")// + .label(8, "Low voltage side of DC Converter overvoltage")// + .label(16, "High voltage side of DC Converter overcurrent fault")// + .label(32, "Low voltage side of DC Converter overcurrent fault")// + .label(64, "DC Converter IGBT fault")// + .label(128, "DC Converter Precharge unmet"))), + new UnsignedWordElement(0xA411, + bmsDCDC1Abnormity2 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity2", this)// + .label(1, "BECU communication disconnected")// + .label(2, "DC Converter communication disconnected")// + .label(4, "Current configuration over range")// + .label(8, "The battery request stop")// + .label(32, "Overcurrent relay fault")// + .label(64, "Lightning protection device fault")// + .label(128, "DC Converter priamary contactor disconnected abnormally")// + .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// + .label(4096, "DC convetor EEPROM abnormity 1")// + .label(8192, "DC convetor EEPROM abnormity 1")// + .label(16384, "EDC convetor EEPROM abnormity 1"))), + new UnsignedWordElement(0xA412, + bmsDCDC1Abnormity3 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity3", this)// + .label(1, "DC Convertor general overload")// + .label(2, "DC short circuit")// + .label(4, "Peak pulse current protection")// + .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// + .label(16, "Effective pulse value overhigh")// + .label(32, "DC Converte severe overload")// + .label(64, + "DC breaker disconnect abnormally on high voltage side of DC convetor")// + .label(128, + "DC breaker disconnect abnormally on low voltage side of DC convetor")// + .label(256, "DC convetor precharge contactor close failed ")// + .label(512, "DC convetor main contactor close failed")// + .label(1024, "AC contactor state abnormity of DC convetor")// + .label(2048, "DC convetor emergency stop")// + .label(4096, "DC converter charging gun disconnected")// + .label(8192, "DC current abnormity before DC convetor work")// + .label(16384, "Fuse disconnected")// + .label(32768, "DC converter hardware current or voltage fault"))), + new UnsignedWordElement(0xA413, + bmsDCDC1Abnormity4 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity4", this)// + .label(1, "DC converter crystal oscillator circuit invalidation")// + .label(2, "DC converter reset circuit invalidation")// + .label(4, "DC converter sampling circuit invalidation")// + .label(8, "DC converter digital I/O circuit invalidation")// + .label(16, "DC converter PWM circuit invalidation")// + .label(32, "DC converter X5045 circuit invalidation")// + .label(64, "DC converter CAN circuit invalidation")// + .label(128, "DC converter software&hardware protection circuit invalidation")// + .label(256, "DC converter power circuit invalidation")// + .label(512, "DC converter CPU invalidation")// + .label(1024, "DC converter TINT0 interrupt invalidation")// + .label(2048, "DC converter ADC interrupt invalidation")// + .label(4096, "DC converter CAPITN4 interrupt invalidation")// + .label(8192, "DC converter CAPINT6 interrupt invalidation")// + .label(16384, "DC converter T3PINTinterrupt invalidation")// + .label(32768, "DC converter T4PINTinterrupt invalidation"))), + new UnsignedWordElement(0xA414, + bmsDCDC1Abnormity5 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity5", this)// + .label(1, "DC converter PDPINTA interrupt invalidation")// + .label(2, "DC converter T1PINT interrupt invalidation")// + .label(4, "DC converter RESV interrupt invalidation")// + .label(8, "DC converter 100us task invalidation")// + .label(16, "DC converter clock invalidation")// + .label(32, "DC converter EMS memory invalidation")// + .label(64, "DC converter exterior communication invalidation")// + .label(128, "DC converter IO Interface invalidation")// + .label(256, "DC converter Input Voltage bound fault")// + .label(512, "DC converter Outter Voltage bound fault")// + .label(1024, "DC converter Output Voltage bound fault")// + .label(2048, "DC converter Induct Current bound fault")// + .label(4096, "DC converter Input Current bound fault")// + .label(8192, "DC converter Output Current bound fault"))), + new UnsignedWordElement(0xA415, + bmsDCDC1Abnormity6 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity6", this)// + .label(1, "DC Reactor over temperature")// + .label(2, "DC IGBT over temperature")// + .label(4, "DC Converter chanel 3 over temperature")// + .label(8, "DC Converter chanel 4 over temperature")// + .label(16, "DC Converter chanel 5 over temperature")// + .label(32, "DC Converter chanel 6 over temperature")// + .label(64, "DC Converter chanel 7 over temperature")// + .label(128, "DC Converter chanel 8 over temperature")// + .label(256, "DC Reactor temperature sampling invalidation")// + .label(512, "DC IGBT temperature sampling invalidation")// + .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// + .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// + .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// + .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// + .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// + .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), + new UnsignedWordElement(0xA416, + bmsDCDC1Abnormity7 = warning.channel(new StatusBitChannel("BmsDCDC1Abnormity7", this)// + .label(32, "DC Converter inductance current sampling invalidation")// + .label(64, + "Current sampling invalidation on the low voltage sideof DC Converter")// + .label(128, + "Voltage sampling invalidation on the low voltage side of DC Converter")// + .label(256, "Insulation inspection fault")// + .label(512, "NegContactor close unsuccessly")// + .label(1024, "NegContactor cut When running"))), + new DummyElement(0xA417, 0xA41F), + new UnsignedWordElement(0xA420, + bmsDCDC1SwitchState = new StatusBitChannel("BmsDCDC1SwitchState", this)// + .label(1, "DC precharge contactor")// + .label(2, "DC main contactor")// + .label(4, "Output contactor")// + .label(8, "Output breaker")// + .label(16, "Input breaker")// + .label(32, "AC contactor")// + .label(64, "Emergency stop button")// + .label(128, "NegContactor"))), + new ModbusRegisterRange(0xA430, // + new SignedWordElement(0xA430, + bmsDCDC1OutputVoltage = new ModbusReadLongChannel("BmsDCDC1OutputVoltage", this) + .unit("mV").multiplier(2)), + new SignedWordElement(0xA431, + bmsDCDC1OutputCurrent = new ModbusReadLongChannel("BmsDCDC1OutputCurrent", this) + .unit("mA").multiplier(2)), + new SignedWordElement(0xA432, + bmsDCDC1OutputPower = new ModbusReadLongChannel("BmsDCDC1OutputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xA433, + bmsDCDC1InputVoltage = new ModbusReadLongChannel("BmsDCDC1InputVoltage", this) + .unit("mV").multiplier(2)), + new SignedWordElement(0xA434, + bmsDCDC1InputCurrent = new ModbusReadLongChannel("BmsDCDC1InputCurrent", this) + .unit("mA").multiplier(2)), + new SignedWordElement(0xA435, + bmsDCDC1InputPower = new ModbusReadLongChannel("BmsDCDC1InputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xA436, + bmsDCDC1InputEnergy = new ModbusReadLongChannel("BmsDCDC1InputEnergy", this).unit("Wh") + .multiplier(2)), + new SignedWordElement(0xA437, + bmsDCDC1OutputEnergy = new ModbusReadLongChannel("BmsDCDC1OutputEnergy", this) + .unit("Wh").multiplier(2)), + new DummyElement(0xA438, 0xA43F), + new SignedWordElement(0xA440, + bmsDCDC1ReactorTemperature = new ModbusReadLongChannel("BmsDCDC1ReactorTemperature", + this).unit("°C")), + new SignedWordElement(0xA441, + bmsDCDC1IgbtTemperature = new ModbusReadLongChannel("BmsDCDC1IgbtTemperature", this) + .unit("°C")), + new DummyElement(0xA442, 0xA44F), + new UnsignedDoublewordElement(0xA450, + bmsDCDC1InputTotalChargeEnergy = new ModbusReadLongChannel( + "BmsDCDC1InputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA452, + bmsDCDC1InputTotalDischargeEnergy = new ModbusReadLongChannel( + "BmsDCDC1InputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA454, + bmsDCDC1OutputTotalChargeEnergy = new ModbusReadLongChannel( + "BmsDCDC1OutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA456, + bmsDCDC1OutputTotalDischargeEnergy = new ModbusReadLongChannel( + "BmsDCDC1OutputTotalDischargeEnergy", this).unit("Wh") + .multiplier(2)).wordOrder(WordOrder.LSWMSW)), + new ModbusRegisterRange(0xA600, // + new UnsignedWordElement(0xA600, + pvDCDCWorkState = new ModbusReadLongChannel("PvDCDCWorkState", this)// + .label(2, "Initial")// + .label(4, "Stop")// + .label(8, "Ready")// + .label(16, "Running")// + .label(32, "Fault")// + .label(64, "Debug")// + .label(128, "Locked")), + new UnsignedWordElement(0xA601, + pvDCDCWorkMode = new ModbusReadLongChannel("PvDCDCWorkMode", this)// + .label(128, "Constant Current")// + .label(256, "Constant Voltage")// + .label(512, "Boost MPPT"))), + new ModbusRegisterRange(0xA700, // + new UnsignedWordElement(0xA700, + pvDCDCSuggestiveInformation1 = warning + .channel(new StatusBitChannel("PvDCDCSuggestiveInformation1", this)// + .label(1, "Current sampling channel abnormity on high voltage side")// + .label(2, "Current sampling channel abnormity on low voltage side")// + .label(64, "EEPROM parameters over range")// + .label(128, "Update EEPROM failed")// + .label(256, "Read EEPROM failed")// + .label(512, "Current sampling channel abnormity before inductance"))), + new UnsignedWordElement(0xA701, pvDCDCSuggestiveInformation2 = warning + .channel(new StatusBitChannel("PvDCDCSuggestiveInformation2", this)// + .label(1, "Reactor power decrease caused by overtemperature")// + .label(2, "IGBT power decrease caused by overtemperature")// + .label(4, "Temperature chanel3 power decrease caused by overtemperature")// + .label(8, "Temperature chanel4 power decrease caused by overtemperature")// + .label(16, "Temperature chanel5 power decrease caused by overtemperature")// + .label(32, "Temperature chanel6 power decrease caused by overtemperature")// + .label(64, "Temperature chanel7 power decrease caused by overtemperature")// + .label(128, "Temperature chanel8 power decrease caused by overtemperature")// + .label(256, "Fan 1 stop failed")// + .label(512, "Fan 2 stop failed")// + .label(1024, "Fan 3 stop failed")// + .label(2048, "Fan 4 stop failed")// + .label(4096, "Fan 1 sartup failed")// + .label(8192, "Fan 2 sartup failed")// + .label(16384, "Fan 3 sartup failed")// + .label(32768, "Fan 4 sartup failed"))), + new UnsignedWordElement(0xA702, + pvDCDCSuggestiveInformation3 = warning + .channel(new StatusBitChannel("PvDCDCSuggestiveInformation3", this)// + .label(1, "High voltage side overvoltage")// + .label(2, "High voltage side undervoltage")// + .label(4, "EEPROM parameters over range")// + .label(8, "High voltage side voltage change unconventionally"))), + new UnsignedWordElement(0xA703, pvDCDCSuggestiveInformation4 = warning + .channel(new StatusBitChannel("PvDCDCSuggestiveInformation4", this)// + .label(1, "Current abnormity before DC Converter work on high voltage side")// + .label(2, "Current abnormity before DC Converter work on low voltage side")// + .label(4, "Initial Duty Ratio abnormity before DC Converter work")// + .label(8, "Voltage abnormity before DC Converter work on high voltage side")// + .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), + new UnsignedWordElement(0xA704, + pvDCDCSuggestiveInformation5 = warning + .channel(new StatusBitChannel("PvDCDCSuggestiveInformation5", this)// + .label(1, "High voltage breaker inspection abnormity")// + .label(2, "Low voltage breaker inspection abnormity")// + .label(4, "DC precharge contactor inspection abnormity")// + .label(8, "DC precharge contactor open unsuccessfully")// + .label(16, "DC main contactor inspection abnormity")// + .label(32, "DC main contactor open unsuccessfully")// + .label(64, "Output contactor close unsuccessfully")// + .label(128, "Output contactor open unsuccessfully")// + .label(256, "AC main contactor close unsuccessfully")// + .label(512, "AC main contactor open unsuccessfully")// + .label(1024, "NegContactor open unsuccessfully")// + .label(2048, "NegContactor close unsuccessfully")// + .label(4096, "NegContactor state abnormal"))), + new DummyElement(0xA705, 0xA70F), + new UnsignedWordElement(0xA710, + pvDCDCAbnormity1 = warning.channel(new StatusBitChannel("PvDCDCAbnormity1", this)// + .label(1, "High voltage side of DC Converter undervoltage")// + .label(2, "High voltage side of DC Converter overvoltage")// + .label(4, "Low voltage side of DC Converter undervoltage")// + .label(8, "Low voltage side of DC Converter overvoltage")// + .label(16, "High voltage side of DC Converter overcurrent fault")// + .label(32, "Low voltage side of DC Converter overcurrent fault")// + .label(64, "DC Converter IGBT fault")// + .label(128, "DC Converter Precharge unmet"))), + new UnsignedWordElement(0xA711, + pvDCDCAbnormity2 = warning.channel(new StatusBitChannel("PvDCDCAbnormity2", this)// + .label(1, "BECU communication disconnected")// + .label(2, "DC Converter communication disconnected")// + .label(4, "Current configuration over range")// + .label(8, "The battery request stop")// + .label(32, "Overcurrent relay fault")// + .label(64, "Lightning protection device fault")// + .label(128, "DC Converter priamary contactor disconnected abnormally")// + .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// + .label(4096, "DC convetor EEPROM abnormity 1")// + .label(8192, "DC convetor EEPROM abnormity 1")// + .label(16384, "EDC convetor EEPROM abnormity 1"))), + new UnsignedWordElement(0xA712, + pvDCDCAbnormity3 = warning.channel(new StatusBitChannel("PvDCDCAbnormity3", this)// + .label(1, "DC Convertor general overload")// + .label(2, "DC short circuit")// + .label(4, "Peak pulse current protection")// + .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// + .label(16, "Effective pulse value overhigh")// + .label(32, "DC Converte severe overload")// + .label(64, + "DC breaker disconnect abnormally on high voltage side of DC convetor")// + .label(128, + "DC breaker disconnect abnormally on low voltage side of DC convetor")// + .label(256, "DC convetor precharge contactor close failed ")// + .label(512, "DC convetor main contactor close failed")// + .label(1024, "AC contactor state abnormity of DC convetor")// + .label(2048, "DC convetor emergency stop")// + .label(4096, "DC converter charging gun disconnected")// + .label(8192, "DC current abnormity before DC convetor work")// + .label(16384, "Fuse disconnected")// + .label(32768, "DC converter hardware current or voltage fault"))), + new UnsignedWordElement(0xA713, + pvDCDCAbnormity4 = warning.channel(new StatusBitChannel("PvDCDCAbnormity4", this)// + .label(1, "DC converter crystal oscillator circuit invalidation")// + .label(2, "DC converter reset circuit invalidation")// + .label(4, "DC converter sampling circuit invalidation")// + .label(8, "DC converter digital I/O circuit invalidation")// + .label(16, "DC converter PWM circuit invalidation")// + .label(32, "DC converter X5045 circuit invalidation")// + .label(64, "DC converter CAN circuit invalidation")// + .label(128, "DC converter software&hardware protection circuit invalidation")// + .label(256, "DC converter power circuit invalidation")// + .label(512, "DC converter CPU invalidation")// + .label(1024, "DC converter TINT0 interrupt invalidation")// + .label(2048, "DC converter ADC interrupt invalidation")// + .label(4096, "DC converter CAPITN4 interrupt invalidation")// + .label(8192, "DC converter CAPINT6 interrupt invalidation")// + .label(16384, "DC converter T3PINTinterrupt invalidation")// + .label(32768, "DC converter T4PINTinterrupt invalidation"))), + new UnsignedWordElement(0xA714, + pvDCDCAbnormity5 = warning.channel(new StatusBitChannel("PvDCDCAbnormity5", this)// + .label(1, "DC converter PDPINTA interrupt invalidation")// + .label(2, "DC converter T1PINT interrupt invalidation")// + .label(4, "DC converter RESV interrupt invalidation")// + .label(8, "DC converter 100us task invalidation")// + .label(16, "DC converter clock invalidation")// + .label(32, "DC converter EMS memory invalidation")// + .label(64, "DC converter exterior communication invalidation")// + .label(128, "DC converter IO Interface invalidation")// + .label(256, "DC converter Input Voltage bound fault")// + .label(512, "DC converter Outter Voltage bound fault")// + .label(1024, "DC converter Output Voltage bound fault")// + .label(2048, "DC converter Induct Current bound fault")// + .label(4096, "DC converter Input Current bound fault")// + .label(8192, "DC converter Output Current bound fault"))), + new UnsignedWordElement(0xA715, + pvDCDCAbnormity6 = warning.channel(new StatusBitChannel("PvDCDCAbnormity6", this)// + .label(1, "DC Reactor over temperature")// + .label(2, "DC IGBT over temperature")// + .label(4, "DC Converter chanel 3 over temperature")// + .label(8, "DC Converter chanel 4 over temperature")// + .label(16, "DC Converter chanel 5 over temperature")// + .label(32, "DC Converter chanel 6 over temperature")// + .label(64, "DC Converter chanel 7 over temperature")// + .label(128, "DC Converter chanel 8 over temperature")// + .label(256, "DC Reactor temperature sampling invalidation")// + .label(512, "DC IGBT temperature sampling invalidation")// + .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// + .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// + .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// + .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// + .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// + .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), + new UnsignedWordElement(0xA716, + pvDCDCAbnormity7 = warning.channel(new StatusBitChannel("PvDCDCAbnormity7", this)// + .label(32, "DC Converter inductance current sampling invalidation")// + .label(64, + "Current sampling invalidation on the low voltage sideof DC Converter")// + .label(128, + "Voltage sampling invalidation on the low voltage side of DC Converter")// + .label(256, "Insulation inspection fault")// + .label(512, "NegContactor close unsuccessly")// + .label(1024, "NegContactor cut When running"))), + new DummyElement(0xA717, 0xA71F), + new UnsignedWordElement(0xA720, + pvDCDCSwitchState = new StatusBitChannel("PvDCDCSwitchState", this)// + .label(1, "DC precharge contactor")// + .label(2, "DC main contactor")// + .label(4, "Output contactor")// + .label(8, "Output breaker")// + .label(16, "Input breaker")// + .label(32, "AC contactor")// + .label(64, "Emergency stop button")// + .label(128, "NegContactor"))), + new ModbusRegisterRange(0xA730, // + new SignedWordElement(0xA730, + pvDCDCOutputVoltage = new ModbusReadLongChannel("PvDCDCOutputVoltage", this).unit("mV") + .multiplier(2)), + new SignedWordElement(0xA731, + pvDCDCOutputCurrent = new ModbusReadLongChannel("PvDCDCOutputCurrent", this).unit("mA") + .multiplier(2)), + new SignedWordElement(0xA732, + pvDCDCOutputPower = new ModbusReadLongChannel("PvDCDCOutputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xA733, + pvDCDCInputVoltage = new ModbusReadLongChannel("PvDCDCInputVoltage", this).unit("mV") + .multiplier(2)), + new SignedWordElement(0xA734, + pvDCDCInputCurrent = new ModbusReadLongChannel("PvDCDCInputCurrent", this).unit("mA") + .multiplier(2)), + new SignedWordElement(0xA735, + pvDCDCInputPower = new ModbusReadLongChannel("PvDCDCInputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xA736, + pvDCDCInputEnergy = new ModbusReadLongChannel("PvDCDCInputEnergy", this).unit("Wh") + .multiplier(2)), + new SignedWordElement(0xA737, + pvDCDCOutputEnergy = new ModbusReadLongChannel("PvDCDCOutputEnergy", this).unit("Wh") + .multiplier(2)), + new DummyElement(0xA738, 0xA73F), + new SignedWordElement(0xA740, + pvDCDCReactorTemperature = new ModbusReadLongChannel("PvDCDCReactorTemperature", this) + .unit("°C")), + new SignedWordElement(0xA741, + pvDCDCIgbtTemperature = new ModbusReadLongChannel("PvDCDCIgbtTemperature", this) + .unit("°C")), + new DummyElement(0xA742, 0xA74F), + new UnsignedDoublewordElement(0xA750, + pvDCDCInputTotalChargeEnergy = new ModbusReadLongChannel("PvDCDCInputTotalChargeEnergy", + this).unit("Wh").multiplier(2)).wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA752, + pvDCDCInputTotalDischargeEnergy = new ModbusReadLongChannel( + "PvDCDCInputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA754, + pvDCDCOutputTotalChargeEnergy = new ModbusReadLongChannel( + "PvDCDCOutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xA756, + pvDCDCOutputTotalDischargeEnergy = new ModbusReadLongChannel( + "PvDCDCOutputTotalDischargeEnergy", this).unit("Wh") + .multiplier(2)).wordOrder(WordOrder.LSWMSW)), + new ModbusRegisterRange(0xA900, // + new UnsignedWordElement(0xA900, + pvDCDC1WorkState = new ModbusReadLongChannel("PvDCDC1WorkState", this)// + .label(2, "Initial")// + .label(4, "Stop")// + .label(8, "Ready")// + .label(16, "Running")// + .label(32, "Fault")// + .label(64, "Debug")// + .label(128, "Locked")), + new UnsignedWordElement(0xA901, + pvDCDC1WorkMode = new ModbusReadLongChannel("PvDCDC1WorkMode", this)// + .label(128, "Constant Current")// + .label(256, "Constant Voltage")// + .label(512, "Boost MPPT"))), + new ModbusRegisterRange(0xAA00, // + new UnsignedWordElement(0xAA00, + pvDCDC1SuggestiveInformation1 = warning + .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation1", this)// + .label(1, "Current sampling channel abnormity on high voltage side")// + .label(2, "Current sampling channel abnormity on low voltage side")// + .label(64, "EEPROM parameters over range")// + .label(128, "Update EEPROM failed")// + .label(256, "Read EEPROM failed")// + .label(512, "Current sampling channel abnormity before inductance"))), + new UnsignedWordElement(0xAA01, pvDCDC1SuggestiveInformation2 = warning + .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation2", this)// + .label(1, "Reactor power decrease caused by overtemperature")// + .label(2, "IGBT power decrease caused by overtemperature")// + .label(4, "Temperature chanel3 power decrease caused by overtemperature")// + .label(8, "Temperature chanel4 power decrease caused by overtemperature")// + .label(16, "Temperature chanel5 power decrease caused by overtemperature")// + .label(32, "Temperature chanel6 power decrease caused by overtemperature")// + .label(64, "Temperature chanel7 power decrease caused by overtemperature")// + .label(128, "Temperature chanel8 power decrease caused by overtemperature")// + .label(256, "Fan 1 stop failed")// + .label(512, "Fan 2 stop failed")// + .label(1024, "Fan 3 stop failed")// + .label(2048, "Fan 4 stop failed")// + .label(4096, "Fan 1 sartup failed")// + .label(8192, "Fan 2 sartup failed")// + .label(16384, "Fan 3 sartup failed")// + .label(32768, "Fan 4 sartup failed"))), + new UnsignedWordElement(0xAA02, + pvDCDC1SuggestiveInformation3 = warning + .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation3", this)// + .label(1, "High voltage side overvoltage")// + .label(2, "High voltage side undervoltage")// + .label(4, "EEPROM parameters over range")// + .label(8, "High voltage side voltage change unconventionally"))), + new UnsignedWordElement(0xAA03, pvDCDC1SuggestiveInformation4 = warning + .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation4", this)// + .label(1, "Current abnormity before DC Converter work on high voltage side")// + .label(2, "Current abnormity before DC Converter work on low voltage side")// + .label(4, "Initial Duty Ratio abnormity before DC Converter work")// + .label(8, "Voltage abnormity before DC Converter work on high voltage side")// + .label(16, "Voltage abnormity before DC Converter work on low voltage side"))), + new UnsignedWordElement(0xAA04, + pvDCDC1SuggestiveInformation5 = warning + .channel(new StatusBitChannel("PvDCDC1SuggestiveInformation5", this)// + .label(1, "High voltage breaker inspection abnormity")// + .label(2, "Low voltage breaker inspection abnormity")// + .label(4, "DC precharge contactor inspection abnormity")// + .label(8, "DC precharge contactor open unsuccessfully")// + .label(16, "DC main contactor inspection abnormity")// + .label(32, "DC main contactor open unsuccessfully")// + .label(64, "Output contactor close unsuccessfully")// + .label(128, "Output contactor open unsuccessfully")// + .label(256, "AC main contactor close unsuccessfully")// + .label(512, "AC main contactor open unsuccessfully")// + .label(1024, "NegContactor open unsuccessfully")// + .label(2048, "NegContactor close unsuccessfully")// + .label(4096, "NegContactor state abnormal"))), + new DummyElement(0xAA05, 0xAA0F), + new UnsignedWordElement(0xAA10, + pvDCDC1Abnormity1 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity1", this)// + .label(1, "High voltage side of DC Converter undervoltage")// + .label(2, "High voltage side of DC Converter overvoltage")// + .label(4, "Low voltage side of DC Converter undervoltage")// + .label(8, "Low voltage side of DC Converter overvoltage")// + .label(16, "High voltage side of DC Converter overcurrent fault")// + .label(32, "Low voltage side of DC Converter overcurrent fault")// + .label(64, "DC Converter IGBT fault")// + .label(128, "DC Converter Precharge unmet"))), + new UnsignedWordElement(0xAA11, + pvDCDC1Abnormity2 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity2", this)// + .label(1, "BECU communication disconnected")// + .label(2, "DC Converter communication disconnected")// + .label(4, "Current configuration over range")// + .label(8, "The battery request stop")// + .label(32, "Overcurrent relay fault")// + .label(64, "Lightning protection device fault")// + .label(128, "DC Converter priamary contactor disconnected abnormally")// + .label(512, "DC disconnected abnormally on low voltage side of DC convetor")// + .label(4096, "DC convetor EEPROM abnormity 1")// + .label(8192, "DC convetor EEPROM abnormity 1")// + .label(16384, "EDC convetor EEPROM abnormity 1"))), + new UnsignedWordElement(0xAA12, + pvDCDC1Abnormity3 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity3", this)// + .label(1, "DC Convertor general overload")// + .label(2, "DC short circuit")// + .label(4, "Peak pulse current protection")// + .label(8, "DC disconnect abnormally on high voltage side of DC convetor")// + .label(16, "Effective pulse value overhigh")// + .label(32, "DC Converte severe overload")// + .label(64, + "DC breaker disconnect abnormally on high voltage side of DC convetor")// + .label(128, + "DC breaker disconnect abnormally on low voltage side of DC convetor")// + .label(256, "DC convetor precharge contactor close failed ")// + .label(512, "DC convetor main contactor close failed")// + .label(1024, "AC contactor state abnormity of DC convetor")// + .label(2048, "DC convetor emergency stop")// + .label(4096, "DC converter charging gun disconnected")// + .label(8192, "DC current abnormity before DC convetor work")// + .label(16384, "Fuse disconnected")// + .label(32768, "DC converter hardware current or voltage fault"))), + new UnsignedWordElement(0xAA13, + pvDCDC1Abnormity4 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity4", this)// + .label(1, "DC converter crystal oscillator circuit invalidation")// + .label(2, "DC converter reset circuit invalidation")// + .label(4, "DC converter sampling circuit invalidation")// + .label(8, "DC converter digital I/O circuit invalidation")// + .label(16, "DC converter PWM circuit invalidation")// + .label(32, "DC converter X5045 circuit invalidation")// + .label(64, "DC converter CAN circuit invalidation")// + .label(128, "DC converter software&hardware protection circuit invalidation")// + .label(256, "DC converter power circuit invalidation")// + .label(512, "DC converter CPU invalidation")// + .label(1024, "DC converter TINT0 interrupt invalidation")// + .label(2048, "DC converter ADC interrupt invalidation")// + .label(4096, "DC converter CAPITN4 interrupt invalidation")// + .label(8192, "DC converter CAPINT6 interrupt invalidation")// + .label(16384, "DC converter T3PINTinterrupt invalidation")// + .label(32768, "DC converter T4PINTinterrupt invalidation"))), + new UnsignedWordElement(0xAA14, + pvDCDC1Abnormity5 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity5", this)// + .label(1, "DC converter PDPINTA interrupt invalidation")// + .label(2, "DC converter T1PINT interrupt invalidation")// + .label(4, "DC converter RESV interrupt invalidation")// + .label(8, "DC converter 100us task invalidation")// + .label(16, "DC converter clock invalidation")// + .label(32, "DC converter EMS memory invalidation")// + .label(64, "DC converter exterior communication invalidation")// + .label(128, "DC converter IO Interface invalidation")// + .label(256, "DC converter Input Voltage bound fault")// + .label(512, "DC converter Outter Voltage bound fault")// + .label(1024, "DC converter Output Voltage bound fault")// + .label(2048, "DC converter Induct Current bound fault")// + .label(4096, "DC converter Input Current bound fault")// + .label(8192, "DC converter Output Current bound fault"))), + new UnsignedWordElement(0xAA15, + pvDCDC1Abnormity6 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity6", this)// + .label(1, "DC Reactor over temperature")// + .label(2, "DC IGBT over temperature")// + .label(4, "DC Converter chanel 3 over temperature")// + .label(8, "DC Converter chanel 4 over temperature")// + .label(16, "DC Converter chanel 5 over temperature")// + .label(32, "DC Converter chanel 6 over temperature")// + .label(64, "DC Converter chanel 7 over temperature")// + .label(128, "DC Converter chanel 8 over temperature")// + .label(256, "DC Reactor temperature sampling invalidation")// + .label(512, "DC IGBT temperature sampling invalidation")// + .label(1024, "DC Converter chanel 3 temperature sampling invalidation")// + .label(2048, "DC Converter chanel 4 temperature sampling invalidation")// + .label(4096, "DC Converter chanel 5 temperature sampling invalidation")// + .label(8192, "DC Converter chanel 6 temperature sampling invalidation")// + .label(16384, "DC Converter chanel 7 temperature sampling invalidation")// + .label(32768, "DC Converter chanel 8 temperature sampling invalidation"))), + new UnsignedWordElement(0xAA16, + pvDCDC1Abnormity7 = warning.channel(new StatusBitChannel("PvDCDC1Abnormity7", this)// + .label(32, "DC Converter inductance current sampling invalidation")// + .label(64, + "Current sampling invalidation on the low voltage sideof DC Converter")// + .label(128, + "Voltage sampling invalidation on the low voltage side of DC Converter")// + .label(256, "Insulation inspection fault")// + .label(512, "NegContactor close unsuccessly")// + .label(1024, "NegContactor cut When running"))), + new DummyElement(0xAA17, 0xAA1F), + new UnsignedWordElement(0xAA20, + pvDCDC1SwitchState = new StatusBitChannel("PvDCDC1SwitchState", this)// + .label(1, "DC precharge contactor")// + .label(2, "DC main contactor")// + .label(4, "Output contactor")// + .label(8, "Output breaker")// + .label(16, "Input breaker")// + .label(32, "AC contactor")// + .label(64, "Emergency stop button")// + .label(128, "NegContactor"))), + new ModbusRegisterRange(0xAA30, // + new SignedWordElement(0xAA30, + pvDCDC1OutputVoltage = new ModbusReadLongChannel("PvDCDC1OutputVoltage", this) + .unit("mV").multiplier(2)), + new SignedWordElement(0xAA31, + pvDCDC1OutputCurrent = new ModbusReadLongChannel("PvDCDC1OutputCurrent", this) + .unit("mA").multiplier(2)), + new SignedWordElement(0xAA32, + pvDCDC1OutputPower = new ModbusReadLongChannel("PvDCDC1OutputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xAA33, + pvDCDC1InputVoltage = new ModbusReadLongChannel("PvDCDC1InputVoltage", this).unit("mV") + .multiplier(2)), + new SignedWordElement(0xAA34, + pvDCDC1InputCurrent = new ModbusReadLongChannel("PvDCDC1InputCurrent", this).unit("mA") + .multiplier(2)), + new SignedWordElement(0xAA35, + pvDCDC1InputPower = new ModbusReadLongChannel("PvDCDC1InputPower", this).unit("W") + .multiplier(2)), + new SignedWordElement(0xAA36, + pvDCDC1InputEnergy = new ModbusReadLongChannel("PvDCDC1InputEnergy", this).unit("Wh") + .multiplier(2)), + new SignedWordElement(0xAA37, + pvDCDC1OutputEnergy = new ModbusReadLongChannel("PvDCDC1OutputEnergy", this).unit("Wh") + .multiplier(2)), + new DummyElement(0xAA38, 0xAA3F), + new SignedWordElement(0xAA40, + pvDCDC1ReactorTemperature = new ModbusReadLongChannel("PvDCDC1ReactorTemperature", this) + .unit("°C")), + new SignedWordElement(0xAA41, + pvDCDC1IgbtTemperature = new ModbusReadLongChannel("PvDCDC1IgbtTemperature", this) + .unit("°C")), + new DummyElement(0xAA42, 0xAA4F), + new UnsignedDoublewordElement(0xAA50, + pvDCDC1InputTotalChargeEnergy = new ModbusReadLongChannel( + "PvDCDC1InputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xAA52, + pvDCDC1InputTotalDischargeEnergy = new ModbusReadLongChannel( + "PvDCDC1InputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xAA54, + pvDCDC1OutputTotalChargeEnergy = new ModbusReadLongChannel( + "PvDCDC1OutputTotalChargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0xAA56, + pvDCDC1OutputTotalDischargeEnergy = new ModbusReadLongChannel( + "PvDCDC1OutputTotalDischargeEnergy", this).unit("Wh").multiplier(2)) + .wordOrder(WordOrder.LSWMSW))); + actualPower = new FunctionalReadChannel("ActualPower", this, (channels) -> { + long erg = 0; + try { + for (ReadChannel ch : channels) { + erg += ch.value(); + } + return erg; + } catch (InvalidValueException e) { + return null; + } + }, pvDCDCInputPower, pvDCDC1InputPower); + inputVoltage = new FunctionalReadChannel("InputVoltage", this, (channels) -> { + long erg = 0; + try { + for (ReadChannel ch : channels) { + if (erg < ch.value()) { + erg = ch.value(); + } + } + return erg; + } catch (InvalidValueException e) { + return null; + } + }, pvDCDCInputVoltage, pvDCDC1InputVoltage); + return protocol; + } + + @Override + public WriteChannel setMaxPower() { + return pvPowerLimitCommand; + } + + @Override + public ReadChannel getNominalPower() { + return new StaticValueChannel("NominalPower", this, 60000l); + } + + @Override + public ReadChannel getActualPower() { + return actualPower; + } + + @Override + public ReadChannel getInputVoltage() { + return inputVoltage; + } + +} diff --git a/edge/src/io/openems/impl/device/commercial/FeneconCommercialDC.java b/edge/src/io/openems/impl/device/commercial/FeneconCommercialDC.java index 2bf30fe80b8..cf421a397c4 100644 --- a/edge/src/io/openems/impl/device/commercial/FeneconCommercialDC.java +++ b/edge/src/io/openems/impl/device/commercial/FeneconCommercialDC.java @@ -1,71 +1,72 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.commercial; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "FENECON Commercial DC/Hybrid") -public class FeneconCommercialDC extends ModbusDevice { - - /* - * Constructors - */ - public FeneconCommercialDC() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconCommercialEss.class) - public final ConfigChannel ess = new ConfigChannel("ess", this); - - @ConfigInfo(title = "Charger", description = "Sets the inverter nature.", type = FeneconCommercialCharger.class) - public final ConfigChannel charger = new ConfigChannel<>("charger", this); - - /* - * Methods - */ - @Override - public String toString() { - return "FeneconCommercialDC [ess=" + ess + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (ess.valueOptional().isPresent()) { - natures.add(ess.valueOptional().get()); - } - if (charger.valueOptional().isPresent()) { - natures.add(charger.valueOptional().get()); - } - return natures; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.commercial; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "FENECON Commercial DC/Hybrid") +public class FeneconCommercialDC extends ModbusDevice { + + /* + * Constructors + */ + public FeneconCommercialDC(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconCommercialEss.class) + public final ConfigChannel ess = new ConfigChannel("ess", this); + + @ConfigInfo(title = "Charger", description = "Sets the inverter nature.", type = FeneconCommercialCharger.class) + public final ConfigChannel charger = new ConfigChannel<>("charger", this); + + /* + * Methods + */ + @Override + public String toString() { + return "FeneconCommercialDC [ess=" + ess + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (ess.valueOptional().isPresent()) { + natures.add(ess.valueOptional().get()); + } + if (charger.valueOptional().isPresent()) { + natures.add(charger.valueOptional().get()); + } + return natures; + } +} diff --git a/edge/src/io/openems/impl/device/commercial/FeneconCommercialEss.java b/edge/src/io/openems/impl/device/commercial/FeneconCommercialEss.java index a407618fc9e..703fb1e7a98 100644 --- a/edge/src/io/openems/impl/device/commercial/FeneconCommercialEss.java +++ b/edge/src/io/openems/impl/device/commercial/FeneconCommercialEss.java @@ -1,1202 +1,1203 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.commercial; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedWordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.WordOrder; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; - -@ThingInfo(title = "FENECON Commercial ESS") -public class FeneconCommercialEss extends ModbusDeviceNature implements SymmetricEssNature { - - /* - * Constructors - */ - public FeneconCommercialEss(String thingId) throws ConfigException { - super(thingId); - minSoc.addUpdateListener((channel, newValue) -> { - // If chargeSoc was not set -> set it to minSoc minus 2 - if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { - chargeSoc.updateValue((Integer) newValue.get() - 2, false); - } - }); - } - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel soc; - private ModbusReadLongChannel inverterActivePower; - private ModbusReadLongChannel allowedCharge; - private ModbusReadLongChannel allowedDischarge; - private ModbusReadLongChannel apparentPower; - private ModbusReadLongChannel gridMode; - private ModbusReadLongChannel reactivePower; - private ModbusReadLongChannel systemState; - private ModbusWriteLongChannel setActivePower; - private ModbusWriteLongChannel setReactivePower; - private ModbusWriteLongChannel setWorkState; - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) - .unit("VA"); - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 40000L).unit("Wh"); - public StatusBitChannels warning; - - @Override - public ModbusReadLongChannel soc() { - return soc; - } - - @Override - public ModbusReadLongChannel activePower() { - return inverterActivePower; - } - - @Override - public ModbusReadLongChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ModbusReadLongChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ModbusReadLongChannel apparentPower() { - return apparentPower; - } - - @Override - public ModbusReadLongChannel gridMode() { - return gridMode; - } - - @Override - public ModbusReadLongChannel reactivePower() { - return reactivePower; - } - - @Override - public ModbusReadLongChannel systemState() { - return systemState; - } - - @Override - public ModbusWriteLongChannel setActivePower() { - return setActivePower; - } - - @Override - public ModbusWriteLongChannel setReactivePower() { - return setReactivePower; - } - - @Override - public ModbusWriteLongChannel setWorkState() { - return setWorkState; - } - - @Override - public ModbusReadLongChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - /* - * This Channels - */ - public ModbusReadLongChannel controlMode; - public ModbusReadLongChannel batteryMaintenanceState; - public ModbusReadLongChannel inverterState; - public ModbusReadLongChannel protocolVersion; - public ModbusReadLongChannel systemManufacturer; - public ModbusReadLongChannel systemType; - public StatusBitChannel switchState; - public ModbusReadLongChannel batteryVoltage; - public ModbusReadLongChannel batteryCurrent; - public ModbusReadLongChannel batteryPower; - public ModbusReadLongChannel acChargeEnergy; - public ModbusReadLongChannel acDischargeEnergy; - public ModbusReadLongChannel currentL1; - public ModbusReadLongChannel currentL2; - public ModbusReadLongChannel currentL3; - public ModbusReadLongChannel voltageL1; - public ModbusReadLongChannel voltageL2; - public ModbusReadLongChannel voltageL3; - public ModbusReadLongChannel frequency; - public ModbusReadLongChannel inverterVoltageL1; - public ModbusReadLongChannel inverterVoltageL2; - public ModbusReadLongChannel inverterVoltageL3; - public ModbusReadLongChannel inverterCurrentL1; - public ModbusReadLongChannel inverterCurrentL2; - public ModbusReadLongChannel inverterCurrentL3; - public ModbusReadLongChannel ipmTemperatureL1; - public ModbusReadLongChannel ipmTemperatureL2; - public ModbusReadLongChannel ipmTemperatureL3; - public ModbusReadLongChannel transformerTemperatureL2; - public ModbusReadLongChannel allowedApparent; - public ModbusReadLongChannel activePower; - public StatusBitChannel suggestiveInformation1; - public StatusBitChannel suggestiveInformation2; - public StatusBitChannel suggestiveInformation3; - public StatusBitChannel suggestiveInformation4; - public StatusBitChannel suggestiveInformation5; - public StatusBitChannel suggestiveInformation6; - public StatusBitChannel suggestiveInformation7; - public StatusBitChannel abnormity1; - public StatusBitChannel abnormity2; - public StatusBitChannel abnormity3; - public StatusBitChannel abnormity4; - public StatusBitChannel abnormity5; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - warning = new StatusBitChannels("Warning", this); - return new ModbusProtocol( // - new ModbusRegisterRange(0x0101, // - new UnsignedWordElement(0x0101, // - systemState = new ModbusReadLongChannel("SystemState", this) // - .label(2, STOP) // - .label(4, "PV-Charge") // - .label(8, "Standby") // - .label(16, START) // - .label(32, FAULT) // - .label(64, "Debug")), // - new UnsignedWordElement(0x0102, // - controlMode = new ModbusReadLongChannel("ControlMode", this) // - .label(1, "Remote") // - .label(2, "Local")), // - new DummyElement(0x0103), // WorkMode: RemoteDispatch - new UnsignedWordElement(0x0104, // - batteryMaintenanceState = new ModbusReadLongChannel("BatteryMaintenanceState", this) // - .label(0, OFF) // - .label(1, ON)), // - new UnsignedWordElement(0x0105, // - inverterState = new ModbusReadLongChannel("InverterState", this) // - .label(0, "Init") // - .label(2, "Fault") // - .label(4, STOP) // - .label(8, STANDBY) // - .label(16, "Grid-Monitor") // , - .label(32, "Ready") // - .label(64, START) // - .label(128, "Debug")), // - new UnsignedWordElement(0x0106, // - gridMode = new ModbusReadLongChannel("GridMode", this) // - .label(1, OFF_GRID) // - .label(2, ON_GRID)), // - new DummyElement(0x0107), // - new UnsignedWordElement(0x0108, // - protocolVersion = new ModbusReadLongChannel("ProtocolVersion", this)), // - new UnsignedWordElement(0x0109, // - systemManufacturer = new ModbusReadLongChannel("SystemManufacturer", this) // - .label(1, "BYD")), // - new UnsignedWordElement(0x010A, // - systemType = new ModbusReadLongChannel("SystemType", this) // - .label(1, "CESS")), // - new DummyElement(0x010B, 0x010F), // - new UnsignedWordElement(0x0110, // - suggestiveInformation1 = warning - .channel(new StatusBitChannel("SuggestiveInformation1", this) // - .label(4, "EmergencyStop") // - .label(64, "KeyManualStop"))), // - new UnsignedWordElement(0x0111, // - suggestiveInformation2 = warning - .channel(new StatusBitChannel("SuggestiveInformation2", this) // - .label(4, "EmergencyStop") // - .label(64, "KeyManualStop"))), // - new DummyElement(0x0112, 0x0124), // - new UnsignedWordElement(0x0125, // - suggestiveInformation3 = warning - .channel(new StatusBitChannel("SuggestiveInformation3", this) // - .label(1, "Inverter communication abnormity") // - .label(2, "Battery stack communication abnormity") // - .label(4, "Multifunctional ammeter communication abnormity") // - .label(16, "Remote communication abnormity")// - .label(256, "PV DC1 communication abnormity")// - .label(512, "PV DC2 communication abnormity")// - )), // - new UnsignedWordElement(0x0126, // - suggestiveInformation4 = warning - .channel(new StatusBitChannel("SuggestiveInformation4", this) // - .label(8, "Transformer severe overtemperature"))), // - new DummyElement(0x0127, 0x014F), // - new UnsignedWordElement(0x0150, // - switchState = new StatusBitChannel("BatteryStringSwitchState", this) // - .label(1, "Main contactor") // - .label(2, "Precharge contactor") // - .label(4, "FAN contactor") // - .label(8, "BMU power supply relay") // - .label(16, "Middle relay"))// - ), // - new ModbusRegisterRange(0x0180, // - new UnsignedWordElement(0x0180, - abnormity1 = warning.channel(new StatusBitChannel("Abnormity1", this)// - .label(1, "DC precharge contactor close unsuccessfully") // - .label(2, "AC precharge contactor close unsuccessfully") // - .label(4, "AC main contactor close unsuccessfully") // - .label(8, "DC electrical breaker 1 close unsuccessfully") // - .label(16, "DC main contactor close unsuccessfully") // - .label(32, "AC breaker trip") // - .label(64, "AC main contactor open when running") // - .label(128, "DC main contactor open when running") // - .label(256, "AC main contactor open unsuccessfully") // - .label(512, "DC electrical breaker 1 open unsuccessfully") // - .label(1024, "DC main contactor open unsuccessfully") // - .label(2048, "Hardware PDP fault") // - .label(4096, "Master stop suddenly"))), - new DummyElement(0x0181), - new UnsignedWordElement(0x0182, - abnormity2 = warning.channel(new StatusBitChannel("Abnormity2", this) // - .label(1, "DC short circuit protection") // - .label(2, "DC overvoltage protection") // - .label(4, "DC undervoltage protection") // - .label(8, "DC inverse/no connection protection") // - .label(16, "DC disconnection protection") // - .label(32, "Commuting voltage abnormity protection") // - .label(64, "DC overcurrent protection") // - .label(128, "Phase 1 peak current over limit protection") // - .label(256, "Phase 2 peak current over limit protection") // - .label(512, "Phase 3 peak current over limit protection") // - .label(1024, "Phase 1 grid voltage sampling invalidation") // - .label(2048, "Phase 2 virtual current over limit protection") // - .label(4096, "Phase 3 virtual current over limit protection") // - .label(8192, "Phase 1 grid voltage sampling invalidation2") // TODO same as - // above - .label(16384, "Phase 2 grid voltage sampling invalidation") // - .label(32768, "Phase 3 grid voltage sampling invalidation"))), - new UnsignedWordElement(0x0183, - abnormity3 = warning.channel(new StatusBitChannel("Abnormity3", this) // - .label(1, "Phase 1 invert voltage sampling invalidation") // - .label(2, "Phase 2 invert voltage sampling invalidation") // - .label(4, "Phase 3 invert voltage sampling invalidation") // - .label(8, "AC current sampling invalidation") // - .label(16, "DC current sampling invalidation") // - .label(32, "Phase 1 overtemperature protection") // - .label(64, "Phase 2 overtemperature protection") // - .label(128, "Phase 3 overtemperature protection") // - .label(256, "Phase 1 temperature sampling invalidation") // - .label(512, "Phase 2 temperature sampling invalidation") // - .label(1024, "Phase 3 temperature sampling invalidation") // - .label(2048, "Phase 1 precharge unmet protection") // - .label(4096, "Phase 2 precharge unmet protection") // - .label(8192, "Phase 3 precharge unmet protection") // - .label(16384, "Unadaptable phase sequence error protection")// - .label(132768, "DSP protection"))), - new UnsignedWordElement(0x0184, - abnormity4 = warning.channel(new StatusBitChannel("Abnormity4", this) // - .label(1, "Phase 1 grid voltage severe overvoltage protection") // - .label(2, "Phase 1 grid voltage general overvoltage protection") // - .label(4, "Phase 2 grid voltage severe overvoltage protection") // - .label(8, "Phase 2 grid voltage general overvoltage protection") // - .label(16, "Phase 3 grid voltage severe overvoltage protection") // - .label(32, "Phase 3 grid voltage general overvoltage protection") // - .label(64, "Phase 1 grid voltage severe undervoltage protection") // - .label(128, "Phase 1 grid voltage general undervoltage protection") // - .label(256, "Phase 2 grid voltage severe undervoltage protection") // - .label(512, "Phase 2 grid voltage general undervoltage protection") // - .label(1024, "Phase 2 Inverter voltage general overvoltage protection") // - .label(2048, "Phase 3 Inverter voltage severe overvoltage protection") // - .label(4096, "Phase 3 Inverter voltage general overvoltage protection") // - .label(8192, "Inverter peak voltage high protection cause by AC disconnect"))), - new UnsignedWordElement(0x0185, - abnormity5 = warning.channel(new StatusBitChannel("Abnormity5", this) // - .label(1, "Phase 1 grid loss") // - .label(2, "Phase 2 grid loss") // - .label(4, "Phase 3 grid loss") // - .label(8, "Islanding protection") // - .label(16, "Phase 1 under voltage ride through") // - .label(32, "Phase 2 under voltage ride through") // - .label(64, "Phase 3 under voltage ride through ") // - .label(128, "Phase 1 Inverter voltage severe overvoltage protection") // - .label(256, "Phase 1 Inverter voltage general overvoltage protection") // - .label(512, "Phase 2 Inverter voltage severe overvoltage protection") // - .label(1024, "Phase 2 Inverter voltage general overvoltage protection") // - .label(2048, "Phase 3 Inverter voltage severe overvoltage protection") // - .label(4096, "Phase 3 Inverter voltage general overvoltage protection") // - .label(8192, "Inverter peak voltage high protection cause by AC disconnect"))), - new UnsignedWordElement(0x0186, - suggestiveInformation5 = warning - .channel(new StatusBitChannel("SuggestiveInformation5", this) // - .label(1, "DC precharge contactor inspection abnormity") // - .label(2, "DC breaker 1 inspection abnormity ") // - .label(4, "DC breaker 2 inspection abnormity ") // - .label(8, "AC precharge contactor inspection abnormity ") // - .label(16, "AC main contactor inspection abnormity ") // - .label(32, "AC breaker inspection abnormity ") // - .label(64, "DC breaker 1 close unsuccessfully") // - .label(128, "DC breaker 2 close unsuccessfully") // - .label(256, "Control signal close abnormally inspected by system") // - .label(512, "Control signal open abnormally inspected by system") // - .label(1024, "Neutral wire contactor close unsuccessfully") // - .label(2048, "Neutral wire contactor open unsuccessfully") // - .label(4096, "Work door open") // - .label(8192, "Emergency stop") // - .label(16384, "AC breaker close unsuccessfully")// - .label(132768, "Control switch stop"))), - new UnsignedWordElement(0x0187, - suggestiveInformation6 = warning - .channel(new StatusBitChannel("SuggestiveInformation6", this) // - .label(1, "General overload") // - .label(2, "Severe overload") // - .label(4, "Battery current over limit") // - .label(8, "Power decrease caused by overtemperature") // - .label(16, "Inverter general overtemperature") // - .label(32, "AC three-phase current unbalance") // - .label(64, "Rstore factory setting unsuccessfully") // - .label(128, "Pole-board invalidation") // - .label(256, "Self-inspection failed") // - .label(512, "Receive BMS fault and stop") // - .label(1024, "Refrigeration equipment invalidation") // - .label(2048, "Large temperature difference among IGBT three phases") // - .label(4096, "EEPROM parameters over range") // - .label(8192, "EEPROM parameters backup failed") // - .label(16384, "DC breaker close unsuccessfully"))), - new UnsignedWordElement(0x0188, - suggestiveInformation7 = warning - .channel(new StatusBitChannel("SuggestiveInformation7", this) // - .label(1, "Communication between inverter and BSMU disconnected") // - .label(2, "Communication between inverter and Master disconnected") // - .label(4, "Communication between inverter and UC disconnected") // - .label(8, "BMS start overtime controlled by PCS") // - .label(16, "BMS stop overtime controlled by PCS") // - .label(32, "Sync signal invalidation") // - .label(64, "Sync signal continuous caputure fault") // - .label(128, "Sync signal several times caputure fault")))), - new ModbusRegisterRange(0x0200, // - new SignedWordElement(0x0200, // - batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV") - .multiplier(2)), - new SignedWordElement(0x0201, // - batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA") - .multiplier(2)), - new SignedWordElement(0x0202, // - batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W").multiplier(2)), - new DummyElement(0x0203, 0x0207), // - new UnsignedDoublewordElement(0x0208, // - acChargeEnergy = new ModbusReadLongChannel("AcChargeEnergy", this).unit("Wh") - .multiplier(2)).wordOrder(WordOrder.LSWMSW), - new UnsignedDoublewordElement(0x020A, // - acDischargeEnergy = new ModbusReadLongChannel("AcDischargeEnergy", this).unit("Wh") - .multiplier(2)).wordOrder(WordOrder.LSWMSW), - new DummyElement(0x020C, 0x020F), new SignedWordElement(0x0210, // - activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(2)), - new SignedWordElement(0x0211, // - reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("var") - .multiplier(2)), - new UnsignedWordElement(0x0212, // - apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA") - .multiplier(2)), - new SignedWordElement(0x0213, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), - new SignedWordElement(0x0214, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA").multiplier(2)), - new SignedWordElement(0x0215, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA").multiplier(2)), - new DummyElement(0x0216, 0x218), // - new UnsignedWordElement(0x0219, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), - new UnsignedWordElement(0x021A, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), - new UnsignedWordElement(0x021B, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2)), - new UnsignedWordElement(0x021C, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ").multiplier(1))), - new ModbusRegisterRange(0x0222, // - new UnsignedWordElement(0x0222, // - inverterVoltageL1 = new ModbusReadLongChannel("InverterVoltageL1", this).unit("mV") - .multiplier(2)), // - new UnsignedWordElement(0x0223, // - inverterVoltageL2 = new ModbusReadLongChannel("InverterVoltageL2", this).unit("mV") - .multiplier(2)), // - new UnsignedWordElement(0x0224, // - inverterVoltageL3 = new ModbusReadLongChannel("InverterVoltageL3", this).unit("mV") - .multiplier(2)), // - new UnsignedWordElement(0x0225, // - inverterCurrentL1 = new ModbusReadLongChannel("InverterCurrentL1", this).unit("mA") - .multiplier(2)), // - new UnsignedWordElement(0x0226, // - inverterCurrentL2 = new ModbusReadLongChannel("InverterCurrentL2", this).unit("mA") - .multiplier(2)), // - new UnsignedWordElement(0x0227, // - inverterCurrentL3 = new ModbusReadLongChannel("InverterCurrentL3", this).unit("mA") - .multiplier(2)), // - new SignedWordElement(0x0228, // - inverterActivePower = new ModbusReadLongChannel("InverterActivePower", this).unit("W") - .multiplier(2)), // - new DummyElement(0x0229, 0x022F), new SignedWordElement(0x0230, // - allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).unit("W") - .multiplier(2)), // - new UnsignedWordElement(0x0231, // - allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W") - .multiplier(2)), // - new UnsignedWordElement(0x0232, // - allowedApparent = new ModbusReadLongChannel("AllowedApparent", this).unit("VA") - .multiplier(2)), // - new DummyElement(0x0233, 0x23F), new SignedWordElement(0x0240, // - ipmTemperatureL1 = new ModbusReadLongChannel("IpmTemperatureL1", this).unit("�C")), // - new SignedWordElement(0x0241, // - ipmTemperatureL2 = new ModbusReadLongChannel("IpmTemperatureL2", this).unit("�C")), // - new SignedWordElement(0x0242, // - ipmTemperatureL3 = new ModbusReadLongChannel("IpmTemperatureL3", this).unit("�C")), // - new DummyElement(0x0243, 0x0248), new SignedWordElement(0x0249, // - transformerTemperatureL2 = new ModbusReadLongChannel("TransformerTemperatureL2", this) - .unit("�C"))), - new WriteableModbusRegisterRange(0x0500, // - new UnsignedWordElement(0x0500, // - setWorkState = new ModbusWriteLongChannel("SetWorkState", this) // - .label(4, STOP) // - .label(32, STANDBY) // - .label(64, START))), - new WriteableModbusRegisterRange(0x0501, // - new SignedWordElement(0x0501, // - setActivePower = new ModbusWriteLongChannel("SetActivePower", this).unit("W") - .multiplier(2).minWriteChannel(allowedCharge) - .maxWriteChannel(allowedDischarge)), - new SignedWordElement(0x0502, // - setReactivePower = new ModbusWriteLongChannel("SetReactivePower", this).unit("var") - .multiplier(2).minWriteChannel(allowedCharge) - .maxWriteChannel(allowedDischarge))), - new ModbusRegisterRange(0x1402, // - new UnsignedWordElement(0x1402, - soc = new ModbusReadLongChannel("Soc", this).unit("%").interval(0, 100)))); - - } - - @Override - public StaticValueChannel capacity() { - return capacity; - } - - // @IsChannel(id = "BatteryAccumulatedCharge") - // public final ModbusReadChannel _batteryAccumulatedCharge = new OldModbusChannelBuilder().nature(this).unit("Wh") - // .build(); - // @IsChannel(id = "BatteryAccumulatedDischarge") - // public final ModbusReadChannel _batteryAccumulatedDischarge = new - // OldModbusChannelBuilder().nature(this).unit("Wh") - // .build(); - // @IsChannel(id = "BatteryChargeCycles") - // public final ModbusReadChannel _batteryChargeCycles = new OldModbusChannelBuilder().nature(this).build(); - - // @IsChannel(id = "BatteryPower") - // public final ModbusReadChannel _batteryPower = new - // OldModbusChannelBuilder().nature(this).unit("W").multiplier(100) - // .build(); - // @IsChannel(id = "BatteryStringTotalCurrent") - // public final ModbusReadChannel _batteryStringTotalCurrent = new OldModbusChannelBuilder().nature(this).unit("mA") - // .multiplier(100).build(); - // @IsChannel(id = "BatteryStringAbnormity1") - // public final ModbusReadChannel _batteryStringAbnormity1 = new OldModbusChannelBuilder().nature(this) // - // .label(4, "Battery string voltage sampling route invalidation") // - // .label(16, "Battery string voltage sampling route disconnected") // - // .label(32, "Battery string temperature sampling route disconnected") // - // .label(64, "Battery string inside CAN disconnected") // - // .label(512, "Battery string current sampling circuit abnormity") // - // .label(1024, "Battery string battery cell invalidation") // - // .label(2048, "Battery string main contactor inspection abnormity") // - // .label(4096, "Battery string precharge contactor inspection abnormity") // - // .label(8192, "Battery string negative contactor inspection abnormity") // - // .label(16384, "Battery string power supply relay inspection abnormity")// - // .label(132768, "Battery string middle relay abnormity").build(); - // @IsChannel(id = "BatteryStringAbnormity2") - // public final ModbusReadChannel _batteryStringAbnormity2 = new OldModbusChannelBuilder().nature(this) // - // .label(4, "Battery string severe overtemperature") // - // .label(128, "Battery string smog fault") // - // .label(256, "Battery string blown fuse indicator fault") // - // .label(1024, "Battery string general leakage") // - // .label(2048, "Battery string severe leakage") // - // .label(4096, "Communication between BECU and periphery CAN disconnected") // - // .label(16384, "Battery string power supply relay contactor disconnected").build(); - // @IsChannel(id = "BatteryStringCellAverageTemperature") - // public final ModbusReadChannel _batteryStringCellAverageTemperature = new OldModbusChannelBuilder().nature(this) - // .unit("�C").multiplier(100).build(); - // @IsChannel(id = "BatteryStringChargeCurrentLimit") - // public final ModbusReadChannel _batteryStringChargeCurrentLimit = new OldModbusChannelBuilder().nature(this) - // .unit("mA").multiplier(100).build(); - // @IsChannel(id = "BatteryStringDischargeCurrentLimit") - // public final ModbusReadChannel _batteryStringDischargeCurrentLimit = new OldModbusChannelBuilder().nature(this) - // .unit("mA").multiplier(100).build(); - // @IsChannel(id = "BatteryStringPeripheralIoState") - // public final ModbusReadChannel _batteryStringPeripheralIoState = new OldModbusChannelBuilder().nature(this) - // .label(1, "Fuse state") // - // .label(2, "Isolated switch state").build(); - // @IsChannel(id = "BatteryStringSOH") - // public final ModbusReadChannel _batteryStringSOH = new OldModbusChannelBuilder().nature(this).unit("%") - // .multiplier(100).build(); - // @IsChannel(id = "BatteryStringSuggestiveInformation") - // public final ModbusReadChannel _batteryStringSuggestiveInformation = new OldModbusChannelBuilder().nature(this) - // .label(1, "Battery string charge general overcurrent") // - // .label(2, "Battery string discharge general overcurrent") // - // .label(4, "Battery string charge current over limit") // - // .label(8, "Battery string discharge current over limit") // - // .label(16, "Battery string general overvoltage") // - // .label(32, "Battery string general undervoltage") // - // .label(128, "Battery string general over temperature") // - // .label(256, "Battery string general under temperature") // - // .label(1024, "Battery string severe overvoltage") // - // .label(2048, "Battery string severe under voltage") // - // .label(4096, "Battery string severe under temperature") // - // .label(8192, "Battery string charge severe overcurrent") // - // .label(16384, "Battery string discharge severe overcurrent")// - // .label(132768, "Battery string capacity abnormity").build(); - - // @IsChannel(id = "BatteryStringTotalVoltage") - // public final ModbusReadChannel _batteryStringTotalVoltage = new OldModbusChannelBuilder().nature(this).unit("mV") - // .multiplier(100).build(); - // @IsChannel(id = "BatteryStringWorkState") - // public final ModbusReadChannel _batteryStringWorkState = new OldModbusChannelBuilder().nature(this) // - // .label(1, "Initial") // - // .label(2, "Stop") // - // .label(4, "Starting up") // - // .label(8, "Running") // - // .label(16, "Fault").build(); - - // private final OldConfigChannel _minSoc = new OldConfigChannelBuilder().nature(this).defaultValue(DEFAULT_MINSOC) - // .percentType().build(); - - // @IsChannel(id = "Abnormity1") - - // @IsChannel(id = "SwitchState") - // public final ModbusReadChannel _switchState = new OldModbusChannelBuilder().nature(this) // - // .label(2, "DC main contactor state") // - // .label(4, "DC precharge contactor state") // - // .label(8, "AC breaker state") // - // .label(16, "AC main contactor state") // - // .label(32, "AC precharge contactor state").build(); - - // @IsChannel(id = "TotalDateEnergy") - // public final ModbusReadChannel _totalDateEnergy = new OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalEnergy") - // public final ModbusReadChannel _totalEnergy = new OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy0") - // public final ModbusReadChannel _totalHourEnergy0 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy1") - // public final ModbusReadChannel _totalHourEnergy1 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy10") - // public final ModbusReadChannel _totalHourEnergy10 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy11") - // public final ModbusReadChannel _totalHourEnergy11 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy12") - // public final ModbusReadChannel _totalHourEnergy12 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy13") - // public final ModbusReadChannel _totalHourEnergy13 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy14") - // public final ModbusReadChannel _totalHourEnergy14 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy15") - // public final ModbusReadChannel _totalHourEnergy15 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy16") - // public final ModbusReadChannel _totalHourEnergy16 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy17") - // public final ModbusReadChannel _totalHourEnergy17 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy18") - // public final ModbusReadChannel _totalHourEnergy18 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy19") - // public final ModbusReadChannel _totalHourEnergy19 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy2") - // public final ModbusReadChannel _totalHourEnergy2 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy20") - // public final ModbusReadChannel _totalHourEnergy20 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy21") - // public final ModbusReadChannel _totalHourEnergy21 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy22") - // public final ModbusReadChannel _totalHourEnergy22 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy23") - // public final ModbusReadChannel _totalHourEnergy23 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy3") - // public final ModbusReadChannel _totalHourEnergy3 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy4") - // public final ModbusReadChannel _totalHourEnergy4 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy5") - // public final ModbusReadChannel _totalHourEnergy5 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy6") - // public final ModbusReadChannel _totalHourEnergy6 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy7") - // public final ModbusReadChannel _totalHourEnergy7 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy8") - // public final ModbusReadChannel _totalHourEnergy8 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalHourEnergy9") - // public final ModbusReadChannel _totalHourEnergy9 = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalMonthEnergy") - // public final ModbusReadChannel _totalMonthEnergy = new - // OldModbusChannelBuilder().nature(this).unit("kWh").build(); - // @IsChannel(id = "TotalYearEnergy") - // public final ModbusReadChannel _totalYearEnergy = new OldModbusChannelBuilder().nature(this).unit("kWh").build(); - - // @IsChannel(id = "MaxVoltageCellNo") - // public final ModbusReadChannel _maxVoltageCellNo = new OldModbusChannelBuilder().nature(this).build(); - // @IsChannel(id = "MaxVoltageCellVoltage") - // public final ModbusReadChannel _maxVoltageCellVoltage = new OldModbusChannelBuilder().nature(this).unit("mV") - // .build(); - // @IsChannel(id = "MaxVoltageCellTemp") - // public final ModbusReadChannel _maxVoltageCellTemp = new - // OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "MinVoltageCellNo") - // public final ModbusReadChannel _minVoltageCellNo = new OldModbusChannelBuilder().nature(this).build(); - // @IsChannel(id = "MinVoltageCellVoltage") - // public final ModbusReadChannel _minVoltageCellVoltage = new OldModbusChannelBuilder().nature(this).unit("mV") - // .build(); - // @IsChannel(id = "MinVoltageCellTemp") - // public final ModbusReadChannel _minVoltageCellTemp = new - // OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "MaxTempCellNo") - // public final ModbusReadChannel _maxTempCellNo = new OldModbusChannelBuilder().nature(this).build(); - // @IsChannel(id = "MaxTempCellVoltage") - // public final ModbusReadChannel _maxTempCellVoltage = new - // OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "MaxTempCellTemp") - // public final ModbusReadChannel _maxTempCellTemp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "MinTempCellNo") - // public final ModbusReadChannel _minTempCellNo = new OldModbusChannelBuilder().nature(this).build(); - // @IsChannel(id = "MinTempCellVoltage") - // public final ModbusReadChannel _minTempCellVoltage = new - // OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "MinTempCellTemp") - // public final ModbusReadChannel _minTempCellTemp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell1Voltage") - // public final ModbusReadChannel _cell1Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell2Voltage") - // public final ModbusReadChannel _cell2Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell3Voltage") - // public final ModbusReadChannel _cell3Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell4Voltage") - // public final ModbusReadChannel _cell4Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell5Voltage") - // public final ModbusReadChannel _cell5Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell6Voltage") - // public final ModbusReadChannel _cell6Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell7Voltage") - // public final ModbusReadChannel _cell7Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell8Voltage") - // public final ModbusReadChannel _cell8Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell9Voltage") - // public final ModbusReadChannel _cell9Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell10Voltage") - // public final ModbusReadChannel _cell10Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell11Voltage") - // public final ModbusReadChannel _cell11Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell12Voltage") - // public final ModbusReadChannel _cell12Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell13Voltage") - // public final ModbusReadChannel _cell13Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell14Voltage") - // public final ModbusReadChannel _cell14Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell15Voltage") - // public final ModbusReadChannel _cell15Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell16Voltage") - // public final ModbusReadChannel _cell16Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell17Voltage") - // public final ModbusReadChannel _cell17Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell18Voltage") - // public final ModbusReadChannel _cell18Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell19Voltage") - // public final ModbusReadChannel _cell19Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell20Voltage") - // public final ModbusReadChannel _cell20Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell21Voltage") - // public final ModbusReadChannel _cell21Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell22Voltage") - // public final ModbusReadChannel _cell22Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell23Voltage") - // public final ModbusReadChannel _cell23Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell24Voltage") - // public final ModbusReadChannel _cell24Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell25Voltage") - // public final ModbusReadChannel _cell25Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell26Voltage") - // public final ModbusReadChannel _cell26Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell27Voltage") - // public final ModbusReadChannel _cell27Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell28Voltage") - // public final ModbusReadChannel _cell28Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell29Voltage") - // public final ModbusReadChannel _cell29Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell30Voltage") - // public final ModbusReadChannel _cell30Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell31Voltage") - // public final ModbusReadChannel _cell31Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell32Voltage") - // public final ModbusReadChannel _cell32Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell33Voltage") - // public final ModbusReadChannel _cell33Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell34Voltage") - // public final ModbusReadChannel _cell34Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell35Voltage") - // public final ModbusReadChannel _cell35Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell36Voltage") - // public final ModbusReadChannel _cell36Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell37Voltage") - // public final ModbusReadChannel _cell37Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell38Voltage") - // public final ModbusReadChannel _cell38Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell39Voltage") - // public final ModbusReadChannel _cell39Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell40Voltage") - // public final ModbusReadChannel _cell40Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell41Voltage") - // public final ModbusReadChannel _cell41Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell42Voltage") - // public final ModbusReadChannel _cell42Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell43Voltage") - // public final ModbusReadChannel _cell43Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell44Voltage") - // public final ModbusReadChannel _cell44Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell45Voltage") - // public final ModbusReadChannel _cell45Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell46Voltage") - // public final ModbusReadChannel _cell46Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell47Voltage") - // public final ModbusReadChannel _cell47Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell48Voltage") - // public final ModbusReadChannel _cell48Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell49Voltage") - // public final ModbusReadChannel _cell49Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell50Voltage") - // public final ModbusReadChannel _cell50Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell51Voltage") - // public final ModbusReadChannel _cell51Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell52Voltage") - // public final ModbusReadChannel _cell52Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell53Voltage") - // public final ModbusReadChannel _cell53Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell54Voltage") - // public final ModbusReadChannel _cell54Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell55Voltage") - // public final ModbusReadChannel _cell55Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell56Voltage") - // public final ModbusReadChannel _cell56Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell57Voltage") - // public final ModbusReadChannel _cell57Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell58Voltage") - // public final ModbusReadChannel _cell58Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell59Voltage") - // public final ModbusReadChannel _cell59Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell60Voltage") - // public final ModbusReadChannel _cell60Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell61Voltage") - // public final ModbusReadChannel _cell61Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell62Voltage") - // public final ModbusReadChannel _cell62Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell63Voltage") - // public final ModbusReadChannel _cell63Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // @IsChannel(id = "Cell64Voltage") - // public final ModbusReadChannel _cell64Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); - // - // @IsChannel(id = "Cell1Temp") - // public final ModbusReadChannel _cell1Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell2Temp") - // public final ModbusReadChannel _cell2Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell3Temp") - // public final ModbusReadChannel _cell3Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell4Temp") - // public final ModbusReadChannel _cell4Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell5Temp") - // public final ModbusReadChannel _cell5Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell6Temp") - // public final ModbusReadChannel _cell6Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell7Temp") - // public final ModbusReadChannel _cell7Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell8Temp") - // public final ModbusReadChannel _cell8Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell9Temp") - // public final ModbusReadChannel _cell9Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell10Temp") - // public final ModbusReadChannel _cell10Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell11Temp") - // public final ModbusReadChannel _cell11Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell12Temp") - // public final ModbusReadChannel _cell12Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell13Temp") - // public final ModbusReadChannel _cell13Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell14Temp") - // public final ModbusReadChannel _cell14Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell15Temp") - // public final ModbusReadChannel _cell15Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell16Temp") - // public final ModbusReadChannel _cell16Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell17Temp") - // public final ModbusReadChannel _cell17Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell18Temp") - // public final ModbusReadChannel _cell18Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell19Temp") - // public final ModbusReadChannel _cell19Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell20Temp") - // public final ModbusReadChannel _cell20Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell21Temp") - // public final ModbusReadChannel _cell21Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell22Temp") - // public final ModbusReadChannel _cell22Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell23Temp") - // public final ModbusReadChannel _cell23Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell24Temp") - // public final ModbusReadChannel _cell24Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell25Temp") - // public final ModbusReadChannel _cell25Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell26Temp") - // public final ModbusReadChannel _cell26Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell27Temp") - // public final ModbusReadChannel _cell27Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell28Temp") - // public final ModbusReadChannel _cell28Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell29Temp") - // public final ModbusReadChannel _cell29Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell30Temp") - // public final ModbusReadChannel _cell30Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell31Temp") - // public final ModbusReadChannel _cell31Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell32Temp") - // public final ModbusReadChannel _cell32Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell33Temp") - // public final ModbusReadChannel _cell33Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell34Temp") - // public final ModbusReadChannel _cell34Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell35Temp") - // public final ModbusReadChannel _cell35Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell36Temp") - // public final ModbusReadChannel _cell36Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell37Temp") - // public final ModbusReadChannel _cell37Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell38Temp") - // public final ModbusReadChannel _cell38Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell39Temp") - // public final ModbusReadChannel _cell39Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell40Temp") - // public final ModbusReadChannel _cell40Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell41Temp") - // public final ModbusReadChannel _cell41Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell42Temp") - // public final ModbusReadChannel _cell42Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell43Temp") - // public final ModbusReadChannel _cell43Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell44Temp") - // public final ModbusReadChannel _cell44Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell45Temp") - // public final ModbusReadChannel _cell45Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell46Temp") - // public final ModbusReadChannel _cell46Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell47Temp") - // public final ModbusReadChannel _cell47Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell48Temp") - // public final ModbusReadChannel _cell48Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell49Temp") - // public final ModbusReadChannel _cell49Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell50Temp") - // public final ModbusReadChannel _cell50Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell51Temp") - // public final ModbusReadChannel _cell51Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell52Temp") - // public final ModbusReadChannel _cell52Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell53Temp") - // public final ModbusReadChannel _cell53Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell54Temp") - // public final ModbusReadChannel _cell54Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell55Temp") - // public final ModbusReadChannel _cell55Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell56Temp") - // public final ModbusReadChannel _cell56Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell57Temp") - // public final ModbusReadChannel _cell57Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell58Temp") - // public final ModbusReadChannel _cell58Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell59Temp") - // public final ModbusReadChannel _cell59Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell60Temp") - // public final ModbusReadChannel _cell60Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell61Temp") - // public final ModbusReadChannel _cell61Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell62Temp") - // public final ModbusReadChannel _cell62Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell63Temp") - // public final ModbusReadChannel _cell63Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - // @IsChannel(id = "Cell64Temp") - // public final ModbusReadChannel _cell64Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); - - // @Override - // protected ModbusProtocol defineModbusProtocol() throws ConfigException { - // - - // new ModbusRange(0x0300, // - // new ElementBuilder().address(0x0300).channel(_totalEnergy).doubleword().build(), - // new ElementBuilder().address(0x0302).channel(_totalYearEnergy).doubleword().build(), - // new ElementBuilder().address(0x0304).channel(_totalMonthEnergy).doubleword().build(), - // new ElementBuilder().address(0x0306).channel(_totalDateEnergy).build(), - // new ElementBuilder().address(0x0307).channel(_totalHourEnergy0).build(), - // new ElementBuilder().address(0x0308).channel(_totalHourEnergy1).build(), - // new ElementBuilder().address(0x0309).channel(_totalHourEnergy2).build(), - // new ElementBuilder().address(0x030A).channel(_totalHourEnergy3).build(), - // new ElementBuilder().address(0x030B).channel(_totalHourEnergy4).build(), - // new ElementBuilder().address(0x030C).channel(_totalHourEnergy5).build(), - // new ElementBuilder().address(0x030D).channel(_totalHourEnergy6).build(), - // new ElementBuilder().address(0x030E).channel(_totalHourEnergy7).build(), - // new ElementBuilder().address(0x030F).channel(_totalHourEnergy8).build(), - // new ElementBuilder().address(0x0310).channel(_totalHourEnergy9).build(), - // new ElementBuilder().address(0x0311).channel(_totalHourEnergy10).build(), - // new ElementBuilder().address(0x0312).channel(_totalHourEnergy11).build(), - // new ElementBuilder().address(0x0313).channel(_totalHourEnergy12).build(), - // new ElementBuilder().address(0x0314).channel(_totalHourEnergy13).build(), - // new ElementBuilder().address(0x0315).channel(_totalHourEnergy14).build(), - // new ElementBuilder().address(0x0316).channel(_totalHourEnergy15).build(), - // new ElementBuilder().address(0x0317).channel(_totalHourEnergy16).build(), - // new ElementBuilder().address(0x0318).channel(_totalHourEnergy17).build(), - // new ElementBuilder().address(0x0319).channel(_totalHourEnergy18).build(), - // new ElementBuilder().address(0x031A).channel(_totalHourEnergy19).build(), - // new ElementBuilder().address(0x031B).channel(_totalHourEnergy20).build(), - // new ElementBuilder().address(0x031C).channel(_totalHourEnergy21).build(), - // new ElementBuilder().address(0x031D).channel(_totalHourEnergy22).build(), - // new ElementBuilder().address(0x031E).channel(_totalHourEnergy23).build()), - - // new ModbusRange(0x1100, // - // new ElementBuilder().address(0x1100).channel(_batteryStringWorkState).build(), - // new ElementBuilder().address(0x1101).channel(_batteryStringSwitchState).build(), - // new ElementBuilder().address(0x1102).channel(_batteryStringPeripheralIoState).build(), - // new ElementBuilder().address(0x1103).channel(_batteryStringSuggestiveInformation).build(), - // new ElementBuilder().address(0x1104).dummy().build(), - // new ElementBuilder().address(0x1105).channel(_batteryStringAbnormity1).build(), - // new ElementBuilder().address(0x1106).channel(_batteryStringAbnormity2).build()), - // new ModbusRange(0x1400, // - // new ElementBuilder().address(0x1400).channel(_batteryStringTotalVoltage).build(), - // new ElementBuilder().address(0x1401).channel(_batteryStringTotalCurrent).signed().build(), - // new ElementBuilder().address(0x1402).channel(_soc).build(), - // new ElementBuilder().address(0x1403).channel(_batteryStringSOH).build(), - // new ElementBuilder().address(0x1404).channel(_batteryStringCellAverageTemperature).signed() - // .build(), - // new ElementBuilder().address(0x1405).dummy().build(), - // new ElementBuilder().address(0x1406).channel(_batteryStringChargeCurrentLimit).build(), - // new ElementBuilder().address(0x1407).channel(_batteryStringDischargeCurrentLimit).build(), - // new ElementBuilder().address(0x1408).dummy(0x140A - 0x1408).build(), - // new ElementBuilder().address(0x140A).channel(_batteryChargeCycles).doubleword().build(), - // new ElementBuilder().address(0x140C).dummy(0x1418 - 0x140C).build(), - // new ElementBuilder().address(0x1418).channel(_batteryAccumulatedCharge).doubleword().build(), - // new ElementBuilder().address(0x141A).channel(_batteryAccumulatedDischarge).doubleword().build(), - // new ElementBuilder().address(0x141C).dummy(0x1420 - 0x141C).build(), - // new ElementBuilder().address(0x1420).channel(_batteryPower).signed().build(), - // new ElementBuilder().address(0x1421).dummy(0x1430 - 0x1421).build(), - // new ElementBuilder().address(0x1430).channel(_maxVoltageCellNo).build(), - // new ElementBuilder().address(0x1431).channel(_maxVoltageCellVoltage).build(), - // new ElementBuilder().address(0x1432).channel(_maxVoltageCellTemp).signed().build(), - // new ElementBuilder().address(0x1433).channel(_minVoltageCellNo).build(), - // new ElementBuilder().address(0x1434).channel(_minVoltageCellVoltage).build(), - // new ElementBuilder().address(0x1435).channel(_minVoltageCellTemp).signed().build(), - // new ElementBuilder().address(0x1436).dummy(0x143A - 0x1436).build(), - // new ElementBuilder().address(0x143A).channel(_maxTempCellNo).build(), - // new ElementBuilder().address(0x143B).channel(_maxTempCellTemp).signed().build(), - // new ElementBuilder().address(0x143C).channel(_maxTempCellVoltage).build(), - // new ElementBuilder().address(0x143D).channel(_minTempCellNo).build(), - // new ElementBuilder().address(0x143E).channel(_minTempCellTemp).signed().build(), - // new ElementBuilder().address(0x143F).channel(_minTempCellVoltage).build()), // - // new ModbusRange(0x1500, new ElementBuilder().address(0x1500).channel(_cell1Voltage).build(), - // new ElementBuilder().address(0x1501).channel(_cell2Voltage).build(), - // new ElementBuilder().address(0x1502).channel(_cell3Voltage).build(), - // new ElementBuilder().address(0x1503).channel(_cell4Voltage).build(), - // new ElementBuilder().address(0x1504).channel(_cell5Voltage).build(), - // new ElementBuilder().address(0x1505).channel(_cell6Voltage).build(), - // new ElementBuilder().address(0x1506).channel(_cell7Voltage).build(), - // new ElementBuilder().address(0x1507).channel(_cell8Voltage).build(), - // new ElementBuilder().address(0x1508).channel(_cell9Voltage).build(), - // new ElementBuilder().address(0x1509).channel(_cell10Voltage).build(), - // new ElementBuilder().address(0x150a).channel(_cell11Voltage).build(), - // new ElementBuilder().address(0x150b).channel(_cell12Voltage).build(), - // new ElementBuilder().address(0x150c).channel(_cell13Voltage).build(), - // new ElementBuilder().address(0x150d).channel(_cell14Voltage).build(), - // new ElementBuilder().address(0x150e).channel(_cell15Voltage).build(), - // new ElementBuilder().address(0x150f).channel(_cell16Voltage).build(), - // new ElementBuilder().address(0x1510).channel(_cell17Voltage).build(), - // new ElementBuilder().address(0x1511).channel(_cell18Voltage).build(), - // new ElementBuilder().address(0x1512).channel(_cell19Voltage).build(), - // new ElementBuilder().address(0x1513).channel(_cell20Voltage).build(), - // new ElementBuilder().address(0x1514).channel(_cell21Voltage).build(), - // new ElementBuilder().address(0x1515).channel(_cell22Voltage).build(), - // new ElementBuilder().address(0x1516).channel(_cell23Voltage).build(), - // new ElementBuilder().address(0x1517).channel(_cell24Voltage).build(), - // new ElementBuilder().address(0x1518).channel(_cell25Voltage).build(), - // new ElementBuilder().address(0x1519).channel(_cell26Voltage).build(), - // new ElementBuilder().address(0x151a).channel(_cell27Voltage).build(), - // new ElementBuilder().address(0x151b).channel(_cell28Voltage).build(), - // new ElementBuilder().address(0x151c).channel(_cell29Voltage).build(), - // new ElementBuilder().address(0x151d).channel(_cell30Voltage).build(), - // new ElementBuilder().address(0x151e).channel(_cell31Voltage).build(), - // new ElementBuilder().address(0x151f).channel(_cell32Voltage).build(), - // new ElementBuilder().address(0x1520).channel(_cell33Voltage).build(), - // new ElementBuilder().address(0x1521).channel(_cell34Voltage).build(), - // new ElementBuilder().address(0x1522).channel(_cell35Voltage).build(), - // new ElementBuilder().address(0x1523).channel(_cell36Voltage).build(), - // new ElementBuilder().address(0x1524).channel(_cell37Voltage).build(), - // new ElementBuilder().address(0x1525).channel(_cell38Voltage).build(), - // new ElementBuilder().address(0x1526).channel(_cell39Voltage).build(), - // new ElementBuilder().address(0x1527).channel(_cell40Voltage).build(), - // new ElementBuilder().address(0x1528).channel(_cell41Voltage).build(), - // new ElementBuilder().address(0x1529).channel(_cell42Voltage).build(), - // new ElementBuilder().address(0x152a).channel(_cell43Voltage).build(), - // new ElementBuilder().address(0x152b).channel(_cell44Voltage).build(), - // new ElementBuilder().address(0x152c).channel(_cell45Voltage).build(), - // new ElementBuilder().address(0x152d).channel(_cell46Voltage).build(), - // new ElementBuilder().address(0x152e).channel(_cell47Voltage).build(), - // new ElementBuilder().address(0x152f).channel(_cell48Voltage).build(), - // new ElementBuilder().address(0x1530).channel(_cell49Voltage).build(), - // new ElementBuilder().address(0x1531).channel(_cell50Voltage).build(), - // new ElementBuilder().address(0x1532).channel(_cell51Voltage).build(), - // new ElementBuilder().address(0x1533).channel(_cell52Voltage).build(), - // new ElementBuilder().address(0x1534).channel(_cell53Voltage).build(), - // new ElementBuilder().address(0x1535).channel(_cell54Voltage).build(), - // new ElementBuilder().address(0x1536).channel(_cell55Voltage).build(), - // new ElementBuilder().address(0x1537).channel(_cell56Voltage).build(), - // new ElementBuilder().address(0x1538).channel(_cell57Voltage).build(), - // new ElementBuilder().address(0x1539).channel(_cell58Voltage).build(), - // new ElementBuilder().address(0x153a).channel(_cell59Voltage).build(), - // new ElementBuilder().address(0x153b).channel(_cell60Voltage).build(), - // new ElementBuilder().address(0x153c).channel(_cell61Voltage).build(), - // new ElementBuilder().address(0x153d).channel(_cell62Voltage).build(), - // new ElementBuilder().address(0x153e).channel(_cell63Voltage).build(), - // new ElementBuilder().address(0x153f).channel(_cell64Voltage).build()), - // new ModbusRange(0x1700, // - // new ElementBuilder().address(0x1700).channel(_cell1Temp).build(), - // new ElementBuilder().address(0x1701).channel(_cell2Temp).build(), - // new ElementBuilder().address(0x1702).channel(_cell3Temp).build(), - // new ElementBuilder().address(0x1703).channel(_cell4Temp).build(), - // new ElementBuilder().address(0x1704).channel(_cell5Temp).build(), - // new ElementBuilder().address(0x1705).channel(_cell6Temp).build(), - // new ElementBuilder().address(0x1706).channel(_cell7Temp).build(), - // new ElementBuilder().address(0x1707).channel(_cell8Temp).build(), - // new ElementBuilder().address(0x1708).channel(_cell9Temp).build(), - // new ElementBuilder().address(0x1709).channel(_cell10Temp).build(), - // new ElementBuilder().address(0x170a).channel(_cell11Temp).build(), - // new ElementBuilder().address(0x170b).channel(_cell12Temp).build(), - // new ElementBuilder().address(0x170c).channel(_cell13Temp).build(), - // new ElementBuilder().address(0x170d).channel(_cell14Temp).build(), - // new ElementBuilder().address(0x170e).channel(_cell15Temp).build(), - // new ElementBuilder().address(0x170f).channel(_cell16Temp).build(), - // new ElementBuilder().address(0x1710).channel(_cell17Temp).build(), - // new ElementBuilder().address(0x1711).channel(_cell18Temp).build(), - // new ElementBuilder().address(0x1712).channel(_cell19Temp).build(), - // new ElementBuilder().address(0x1713).channel(_cell20Temp).build(), - // new ElementBuilder().address(0x1714).channel(_cell21Temp).build(), - // new ElementBuilder().address(0x1715).channel(_cell22Temp).build(), - // new ElementBuilder().address(0x1716).channel(_cell23Temp).build(), - // new ElementBuilder().address(0x1717).channel(_cell24Temp).build(), - // new ElementBuilder().address(0x1718).channel(_cell25Temp).build(), - // new ElementBuilder().address(0x1719).channel(_cell26Temp).build(), - // new ElementBuilder().address(0x171a).channel(_cell27Temp).build(), - // new ElementBuilder().address(0x171b).channel(_cell28Temp).build(), - // new ElementBuilder().address(0x171c).channel(_cell29Temp).build(), - // new ElementBuilder().address(0x171d).channel(_cell30Temp).build(), - // new ElementBuilder().address(0x171e).channel(_cell31Temp).build(), - // new ElementBuilder().address(0x171f).channel(_cell32Temp).build(), - // new ElementBuilder().address(0x1720).channel(_cell33Temp).build(), - // new ElementBuilder().address(0x1721).channel(_cell34Temp).build(), - // new ElementBuilder().address(0x1722).channel(_cell35Temp).build(), - // new ElementBuilder().address(0x1723).channel(_cell36Temp).build(), - // new ElementBuilder().address(0x1724).channel(_cell37Temp).build(), - // new ElementBuilder().address(0x1725).channel(_cell38Temp).build(), - // new ElementBuilder().address(0x1726).channel(_cell39Temp).build(), - // new ElementBuilder().address(0x1727).channel(_cell40Temp).build(), - // new ElementBuilder().address(0x1728).channel(_cell41Temp).build(), - // new ElementBuilder().address(0x1729).channel(_cell42Temp).build(), - // new ElementBuilder().address(0x172a).channel(_cell43Temp).build(), - // new ElementBuilder().address(0x172b).channel(_cell44Temp).build(), - // new ElementBuilder().address(0x172c).channel(_cell45Temp).build(), - // new ElementBuilder().address(0x172d).channel(_cell46Temp).build(), - // new ElementBuilder().address(0x172e).channel(_cell47Temp).build(), - // new ElementBuilder().address(0x172f).channel(_cell48Temp).build(), - // new ElementBuilder().address(0x1730).channel(_cell49Temp).build(), - // new ElementBuilder().address(0x1731).channel(_cell50Temp).build(), - // new ElementBuilder().address(0x1732).channel(_cell51Temp).build(), - // new ElementBuilder().address(0x1733).channel(_cell52Temp).build(), - // new ElementBuilder().address(0x1734).channel(_cell53Temp).build(), - // new ElementBuilder().address(0x1735).channel(_cell54Temp).build(), - // new ElementBuilder().address(0x1736).channel(_cell55Temp).build(), - // new ElementBuilder().address(0x1737).channel(_cell56Temp).build(), - // new ElementBuilder().address(0x1738).channel(_cell57Temp).build(), - // new ElementBuilder().address(0x1739).channel(_cell58Temp).build(), - // new ElementBuilder().address(0x173a).channel(_cell59Temp).build(), - // new ElementBuilder().address(0x173b).channel(_cell60Temp).build(), - // new ElementBuilder().address(0x173c).channel(_cell61Temp).build(), - // new ElementBuilder().address(0x173d).channel(_cell62Temp).build(), - // new ElementBuilder().address(0x173e).channel(_cell63Temp).build(), - // new ElementBuilder().address(0x173f).channel(_cell64Temp).build())); - // } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.commercial; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedWordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.WordOrder; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; + +@ThingInfo(title = "FENECON Commercial ESS") +public class FeneconCommercialEss extends ModbusDeviceNature implements SymmetricEssNature { + + /* + * Constructors + */ + public FeneconCommercialEss(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + minSoc.addUpdateListener((channel, newValue) -> { + // If chargeSoc was not set -> set it to minSoc minus 2 + if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { + chargeSoc.updateValue((Integer) newValue.get() - 2, false); + } + }); + } + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel soc; + private ModbusReadLongChannel inverterActivePower; + private ModbusReadLongChannel allowedCharge; + private ModbusReadLongChannel allowedDischarge; + private ModbusReadLongChannel apparentPower; + private ModbusReadLongChannel gridMode; + private ModbusReadLongChannel reactivePower; + private ModbusReadLongChannel systemState; + private ModbusWriteLongChannel setActivePower; + private ModbusWriteLongChannel setReactivePower; + private ModbusWriteLongChannel setWorkState; + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) + .unit("VA"); + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 40000L).unit("Wh"); + public StatusBitChannels warning; + + @Override + public ModbusReadLongChannel soc() { + return soc; + } + + @Override + public ModbusReadLongChannel activePower() { + return inverterActivePower; + } + + @Override + public ModbusReadLongChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ModbusReadLongChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ModbusReadLongChannel apparentPower() { + return apparentPower; + } + + @Override + public ModbusReadLongChannel gridMode() { + return gridMode; + } + + @Override + public ModbusReadLongChannel reactivePower() { + return reactivePower; + } + + @Override + public ModbusReadLongChannel systemState() { + return systemState; + } + + @Override + public ModbusWriteLongChannel setActivePower() { + return setActivePower; + } + + @Override + public ModbusWriteLongChannel setReactivePower() { + return setReactivePower; + } + + @Override + public ModbusWriteLongChannel setWorkState() { + return setWorkState; + } + + @Override + public ModbusReadLongChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + /* + * This Channels + */ + public ModbusReadLongChannel controlMode; + public ModbusReadLongChannel batteryMaintenanceState; + public ModbusReadLongChannel inverterState; + public ModbusReadLongChannel protocolVersion; + public ModbusReadLongChannel systemManufacturer; + public ModbusReadLongChannel systemType; + public StatusBitChannel switchState; + public ModbusReadLongChannel batteryVoltage; + public ModbusReadLongChannel batteryCurrent; + public ModbusReadLongChannel batteryPower; + public ModbusReadLongChannel acChargeEnergy; + public ModbusReadLongChannel acDischargeEnergy; + public ModbusReadLongChannel currentL1; + public ModbusReadLongChannel currentL2; + public ModbusReadLongChannel currentL3; + public ModbusReadLongChannel voltageL1; + public ModbusReadLongChannel voltageL2; + public ModbusReadLongChannel voltageL3; + public ModbusReadLongChannel frequency; + public ModbusReadLongChannel inverterVoltageL1; + public ModbusReadLongChannel inverterVoltageL2; + public ModbusReadLongChannel inverterVoltageL3; + public ModbusReadLongChannel inverterCurrentL1; + public ModbusReadLongChannel inverterCurrentL2; + public ModbusReadLongChannel inverterCurrentL3; + public ModbusReadLongChannel ipmTemperatureL1; + public ModbusReadLongChannel ipmTemperatureL2; + public ModbusReadLongChannel ipmTemperatureL3; + public ModbusReadLongChannel transformerTemperatureL2; + public ModbusReadLongChannel allowedApparent; + public ModbusReadLongChannel activePower; + public StatusBitChannel suggestiveInformation1; + public StatusBitChannel suggestiveInformation2; + public StatusBitChannel suggestiveInformation3; + public StatusBitChannel suggestiveInformation4; + public StatusBitChannel suggestiveInformation5; + public StatusBitChannel suggestiveInformation6; + public StatusBitChannel suggestiveInformation7; + public StatusBitChannel abnormity1; + public StatusBitChannel abnormity2; + public StatusBitChannel abnormity3; + public StatusBitChannel abnormity4; + public StatusBitChannel abnormity5; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + warning = new StatusBitChannels("Warning", this); + return new ModbusProtocol( // + new ModbusRegisterRange(0x0101, // + new UnsignedWordElement(0x0101, // + systemState = new ModbusReadLongChannel("SystemState", this) // + .label(2, STOP) // + .label(4, "PV-Charge") // + .label(8, "Standby") // + .label(16, START) // + .label(32, FAULT) // + .label(64, "Debug")), // + new UnsignedWordElement(0x0102, // + controlMode = new ModbusReadLongChannel("ControlMode", this) // + .label(1, "Remote") // + .label(2, "Local")), // + new DummyElement(0x0103), // WorkMode: RemoteDispatch + new UnsignedWordElement(0x0104, // + batteryMaintenanceState = new ModbusReadLongChannel("BatteryMaintenanceState", this) // + .label(0, OFF) // + .label(1, ON)), // + new UnsignedWordElement(0x0105, // + inverterState = new ModbusReadLongChannel("InverterState", this) // + .label(0, "Init") // + .label(2, "Fault") // + .label(4, STOP) // + .label(8, STANDBY) // + .label(16, "Grid-Monitor") // , + .label(32, "Ready") // + .label(64, START) // + .label(128, "Debug")), // + new UnsignedWordElement(0x0106, // + gridMode = new ModbusReadLongChannel("GridMode", this) // + .label(1, OFF_GRID) // + .label(2, ON_GRID)), // + new DummyElement(0x0107), // + new UnsignedWordElement(0x0108, // + protocolVersion = new ModbusReadLongChannel("ProtocolVersion", this)), // + new UnsignedWordElement(0x0109, // + systemManufacturer = new ModbusReadLongChannel("SystemManufacturer", this) // + .label(1, "BYD")), // + new UnsignedWordElement(0x010A, // + systemType = new ModbusReadLongChannel("SystemType", this) // + .label(1, "CESS")), // + new DummyElement(0x010B, 0x010F), // + new UnsignedWordElement(0x0110, // + suggestiveInformation1 = warning + .channel(new StatusBitChannel("SuggestiveInformation1", this) // + .label(4, "EmergencyStop") // + .label(64, "KeyManualStop"))), // + new UnsignedWordElement(0x0111, // + suggestiveInformation2 = warning + .channel(new StatusBitChannel("SuggestiveInformation2", this) // + .label(4, "EmergencyStop") // + .label(64, "KeyManualStop"))), // + new DummyElement(0x0112, 0x0124), // + new UnsignedWordElement(0x0125, // + suggestiveInformation3 = warning + .channel(new StatusBitChannel("SuggestiveInformation3", this) // + .label(1, "Inverter communication abnormity") // + .label(2, "Battery stack communication abnormity") // + .label(4, "Multifunctional ammeter communication abnormity") // + .label(16, "Remote communication abnormity")// + .label(256, "PV DC1 communication abnormity")// + .label(512, "PV DC2 communication abnormity")// + )), // + new UnsignedWordElement(0x0126, // + suggestiveInformation4 = warning + .channel(new StatusBitChannel("SuggestiveInformation4", this) // + .label(8, "Transformer severe overtemperature"))), // + new DummyElement(0x0127, 0x014F), // + new UnsignedWordElement(0x0150, // + switchState = new StatusBitChannel("BatteryStringSwitchState", this) // + .label(1, "Main contactor") // + .label(2, "Precharge contactor") // + .label(4, "FAN contactor") // + .label(8, "BMU power supply relay") // + .label(16, "Middle relay"))// + ), // + new ModbusRegisterRange(0x0180, // + new UnsignedWordElement(0x0180, + abnormity1 = warning.channel(new StatusBitChannel("Abnormity1", this)// + .label(1, "DC precharge contactor close unsuccessfully") // + .label(2, "AC precharge contactor close unsuccessfully") // + .label(4, "AC main contactor close unsuccessfully") // + .label(8, "DC electrical breaker 1 close unsuccessfully") // + .label(16, "DC main contactor close unsuccessfully") // + .label(32, "AC breaker trip") // + .label(64, "AC main contactor open when running") // + .label(128, "DC main contactor open when running") // + .label(256, "AC main contactor open unsuccessfully") // + .label(512, "DC electrical breaker 1 open unsuccessfully") // + .label(1024, "DC main contactor open unsuccessfully") // + .label(2048, "Hardware PDP fault") // + .label(4096, "Master stop suddenly"))), + new DummyElement(0x0181), + new UnsignedWordElement(0x0182, + abnormity2 = warning.channel(new StatusBitChannel("Abnormity2", this) // + .label(1, "DC short circuit protection") // + .label(2, "DC overvoltage protection") // + .label(4, "DC undervoltage protection") // + .label(8, "DC inverse/no connection protection") // + .label(16, "DC disconnection protection") // + .label(32, "Commuting voltage abnormity protection") // + .label(64, "DC overcurrent protection") // + .label(128, "Phase 1 peak current over limit protection") // + .label(256, "Phase 2 peak current over limit protection") // + .label(512, "Phase 3 peak current over limit protection") // + .label(1024, "Phase 1 grid voltage sampling invalidation") // + .label(2048, "Phase 2 virtual current over limit protection") // + .label(4096, "Phase 3 virtual current over limit protection") // + .label(8192, "Phase 1 grid voltage sampling invalidation2") // TODO same as + // above + .label(16384, "Phase 2 grid voltage sampling invalidation") // + .label(32768, "Phase 3 grid voltage sampling invalidation"))), + new UnsignedWordElement(0x0183, + abnormity3 = warning.channel(new StatusBitChannel("Abnormity3", this) // + .label(1, "Phase 1 invert voltage sampling invalidation") // + .label(2, "Phase 2 invert voltage sampling invalidation") // + .label(4, "Phase 3 invert voltage sampling invalidation") // + .label(8, "AC current sampling invalidation") // + .label(16, "DC current sampling invalidation") // + .label(32, "Phase 1 overtemperature protection") // + .label(64, "Phase 2 overtemperature protection") // + .label(128, "Phase 3 overtemperature protection") // + .label(256, "Phase 1 temperature sampling invalidation") // + .label(512, "Phase 2 temperature sampling invalidation") // + .label(1024, "Phase 3 temperature sampling invalidation") // + .label(2048, "Phase 1 precharge unmet protection") // + .label(4096, "Phase 2 precharge unmet protection") // + .label(8192, "Phase 3 precharge unmet protection") // + .label(16384, "Unadaptable phase sequence error protection")// + .label(132768, "DSP protection"))), + new UnsignedWordElement(0x0184, + abnormity4 = warning.channel(new StatusBitChannel("Abnormity4", this) // + .label(1, "Phase 1 grid voltage severe overvoltage protection") // + .label(2, "Phase 1 grid voltage general overvoltage protection") // + .label(4, "Phase 2 grid voltage severe overvoltage protection") // + .label(8, "Phase 2 grid voltage general overvoltage protection") // + .label(16, "Phase 3 grid voltage severe overvoltage protection") // + .label(32, "Phase 3 grid voltage general overvoltage protection") // + .label(64, "Phase 1 grid voltage severe undervoltage protection") // + .label(128, "Phase 1 grid voltage general undervoltage protection") // + .label(256, "Phase 2 grid voltage severe undervoltage protection") // + .label(512, "Phase 2 grid voltage general undervoltage protection") // + .label(1024, "Phase 2 Inverter voltage general overvoltage protection") // + .label(2048, "Phase 3 Inverter voltage severe overvoltage protection") // + .label(4096, "Phase 3 Inverter voltage general overvoltage protection") // + .label(8192, "Inverter peak voltage high protection cause by AC disconnect"))), + new UnsignedWordElement(0x0185, + abnormity5 = warning.channel(new StatusBitChannel("Abnormity5", this) // + .label(1, "Phase 1 grid loss") // + .label(2, "Phase 2 grid loss") // + .label(4, "Phase 3 grid loss") // + .label(8, "Islanding protection") // + .label(16, "Phase 1 under voltage ride through") // + .label(32, "Phase 2 under voltage ride through") // + .label(64, "Phase 3 under voltage ride through ") // + .label(128, "Phase 1 Inverter voltage severe overvoltage protection") // + .label(256, "Phase 1 Inverter voltage general overvoltage protection") // + .label(512, "Phase 2 Inverter voltage severe overvoltage protection") // + .label(1024, "Phase 2 Inverter voltage general overvoltage protection") // + .label(2048, "Phase 3 Inverter voltage severe overvoltage protection") // + .label(4096, "Phase 3 Inverter voltage general overvoltage protection") // + .label(8192, "Inverter peak voltage high protection cause by AC disconnect"))), + new UnsignedWordElement(0x0186, + suggestiveInformation5 = warning + .channel(new StatusBitChannel("SuggestiveInformation5", this) // + .label(1, "DC precharge contactor inspection abnormity") // + .label(2, "DC breaker 1 inspection abnormity ") // + .label(4, "DC breaker 2 inspection abnormity ") // + .label(8, "AC precharge contactor inspection abnormity ") // + .label(16, "AC main contactor inspection abnormity ") // + .label(32, "AC breaker inspection abnormity ") // + .label(64, "DC breaker 1 close unsuccessfully") // + .label(128, "DC breaker 2 close unsuccessfully") // + .label(256, "Control signal close abnormally inspected by system") // + .label(512, "Control signal open abnormally inspected by system") // + .label(1024, "Neutral wire contactor close unsuccessfully") // + .label(2048, "Neutral wire contactor open unsuccessfully") // + .label(4096, "Work door open") // + .label(8192, "Emergency stop") // + .label(16384, "AC breaker close unsuccessfully")// + .label(132768, "Control switch stop"))), + new UnsignedWordElement(0x0187, + suggestiveInformation6 = warning + .channel(new StatusBitChannel("SuggestiveInformation6", this) // + .label(1, "General overload") // + .label(2, "Severe overload") // + .label(4, "Battery current over limit") // + .label(8, "Power decrease caused by overtemperature") // + .label(16, "Inverter general overtemperature") // + .label(32, "AC three-phase current unbalance") // + .label(64, "Rstore factory setting unsuccessfully") // + .label(128, "Pole-board invalidation") // + .label(256, "Self-inspection failed") // + .label(512, "Receive BMS fault and stop") // + .label(1024, "Refrigeration equipment invalidation") // + .label(2048, "Large temperature difference among IGBT three phases") // + .label(4096, "EEPROM parameters over range") // + .label(8192, "EEPROM parameters backup failed") // + .label(16384, "DC breaker close unsuccessfully"))), + new UnsignedWordElement(0x0188, + suggestiveInformation7 = warning + .channel(new StatusBitChannel("SuggestiveInformation7", this) // + .label(1, "Communication between inverter and BSMU disconnected") // + .label(2, "Communication between inverter and Master disconnected") // + .label(4, "Communication between inverter and UC disconnected") // + .label(8, "BMS start overtime controlled by PCS") // + .label(16, "BMS stop overtime controlled by PCS") // + .label(32, "Sync signal invalidation") // + .label(64, "Sync signal continuous caputure fault") // + .label(128, "Sync signal several times caputure fault")))), + new ModbusRegisterRange(0x0200, // + new SignedWordElement(0x0200, // + batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV") + .multiplier(2)), + new SignedWordElement(0x0201, // + batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA") + .multiplier(2)), + new SignedWordElement(0x0202, // + batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W").multiplier(2)), + new DummyElement(0x0203, 0x0207), // + new UnsignedDoublewordElement(0x0208, // + acChargeEnergy = new ModbusReadLongChannel("AcChargeEnergy", this).unit("Wh") + .multiplier(2)).wordOrder(WordOrder.LSWMSW), + new UnsignedDoublewordElement(0x020A, // + acDischargeEnergy = new ModbusReadLongChannel("AcDischargeEnergy", this).unit("Wh") + .multiplier(2)).wordOrder(WordOrder.LSWMSW), + new DummyElement(0x020C, 0x020F), new SignedWordElement(0x0210, // + activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(2)), + new SignedWordElement(0x0211, // + reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("var") + .multiplier(2)), + new UnsignedWordElement(0x0212, // + apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA") + .multiplier(2)), + new SignedWordElement(0x0213, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), + new SignedWordElement(0x0214, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA").multiplier(2)), + new SignedWordElement(0x0215, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA").multiplier(2)), + new DummyElement(0x0216, 0x218), // + new UnsignedWordElement(0x0219, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), + new UnsignedWordElement(0x021A, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), + new UnsignedWordElement(0x021B, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2)), + new UnsignedWordElement(0x021C, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ").multiplier(1))), + new ModbusRegisterRange(0x0222, // + new UnsignedWordElement(0x0222, // + inverterVoltageL1 = new ModbusReadLongChannel("InverterVoltageL1", this).unit("mV") + .multiplier(2)), // + new UnsignedWordElement(0x0223, // + inverterVoltageL2 = new ModbusReadLongChannel("InverterVoltageL2", this).unit("mV") + .multiplier(2)), // + new UnsignedWordElement(0x0224, // + inverterVoltageL3 = new ModbusReadLongChannel("InverterVoltageL3", this).unit("mV") + .multiplier(2)), // + new UnsignedWordElement(0x0225, // + inverterCurrentL1 = new ModbusReadLongChannel("InverterCurrentL1", this).unit("mA") + .multiplier(2)), // + new UnsignedWordElement(0x0226, // + inverterCurrentL2 = new ModbusReadLongChannel("InverterCurrentL2", this).unit("mA") + .multiplier(2)), // + new UnsignedWordElement(0x0227, // + inverterCurrentL3 = new ModbusReadLongChannel("InverterCurrentL3", this).unit("mA") + .multiplier(2)), // + new SignedWordElement(0x0228, // + inverterActivePower = new ModbusReadLongChannel("InverterActivePower", this).unit("W") + .multiplier(2)), // + new DummyElement(0x0229, 0x022F), new SignedWordElement(0x0230, // + allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).unit("W") + .multiplier(2)), // + new UnsignedWordElement(0x0231, // + allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W") + .multiplier(2)), // + new UnsignedWordElement(0x0232, // + allowedApparent = new ModbusReadLongChannel("AllowedApparent", this).unit("VA") + .multiplier(2)), // + new DummyElement(0x0233, 0x23F), new SignedWordElement(0x0240, // + ipmTemperatureL1 = new ModbusReadLongChannel("IpmTemperatureL1", this).unit("�C")), // + new SignedWordElement(0x0241, // + ipmTemperatureL2 = new ModbusReadLongChannel("IpmTemperatureL2", this).unit("�C")), // + new SignedWordElement(0x0242, // + ipmTemperatureL3 = new ModbusReadLongChannel("IpmTemperatureL3", this).unit("�C")), // + new DummyElement(0x0243, 0x0248), new SignedWordElement(0x0249, // + transformerTemperatureL2 = new ModbusReadLongChannel("TransformerTemperatureL2", this) + .unit("�C"))), + new WriteableModbusRegisterRange(0x0500, // + new UnsignedWordElement(0x0500, // + setWorkState = new ModbusWriteLongChannel("SetWorkState", this) // + .label(4, STOP) // + .label(32, STANDBY) // + .label(64, START))), + new WriteableModbusRegisterRange(0x0501, // + new SignedWordElement(0x0501, // + setActivePower = new ModbusWriteLongChannel("SetActivePower", this).unit("W") + .multiplier(2).minWriteChannel(allowedCharge) + .maxWriteChannel(allowedDischarge)), + new SignedWordElement(0x0502, // + setReactivePower = new ModbusWriteLongChannel("SetReactivePower", this).unit("var") + .multiplier(2).minWriteChannel(allowedCharge) + .maxWriteChannel(allowedDischarge))), + new ModbusRegisterRange(0x1402, // + new UnsignedWordElement(0x1402, + soc = new ModbusReadLongChannel("Soc", this).unit("%").interval(0, 100)))); + + } + + @Override + public StaticValueChannel capacity() { + return capacity; + } + + // @IsChannel(id = "BatteryAccumulatedCharge") + // public final ModbusReadChannel _batteryAccumulatedCharge = new OldModbusChannelBuilder().nature(this).unit("Wh") + // .build(); + // @IsChannel(id = "BatteryAccumulatedDischarge") + // public final ModbusReadChannel _batteryAccumulatedDischarge = new + // OldModbusChannelBuilder().nature(this).unit("Wh") + // .build(); + // @IsChannel(id = "BatteryChargeCycles") + // public final ModbusReadChannel _batteryChargeCycles = new OldModbusChannelBuilder().nature(this).build(); + + // @IsChannel(id = "BatteryPower") + // public final ModbusReadChannel _batteryPower = new + // OldModbusChannelBuilder().nature(this).unit("W").multiplier(100) + // .build(); + // @IsChannel(id = "BatteryStringTotalCurrent") + // public final ModbusReadChannel _batteryStringTotalCurrent = new OldModbusChannelBuilder().nature(this).unit("mA") + // .multiplier(100).build(); + // @IsChannel(id = "BatteryStringAbnormity1") + // public final ModbusReadChannel _batteryStringAbnormity1 = new OldModbusChannelBuilder().nature(this) // + // .label(4, "Battery string voltage sampling route invalidation") // + // .label(16, "Battery string voltage sampling route disconnected") // + // .label(32, "Battery string temperature sampling route disconnected") // + // .label(64, "Battery string inside CAN disconnected") // + // .label(512, "Battery string current sampling circuit abnormity") // + // .label(1024, "Battery string battery cell invalidation") // + // .label(2048, "Battery string main contactor inspection abnormity") // + // .label(4096, "Battery string precharge contactor inspection abnormity") // + // .label(8192, "Battery string negative contactor inspection abnormity") // + // .label(16384, "Battery string power supply relay inspection abnormity")// + // .label(132768, "Battery string middle relay abnormity").build(); + // @IsChannel(id = "BatteryStringAbnormity2") + // public final ModbusReadChannel _batteryStringAbnormity2 = new OldModbusChannelBuilder().nature(this) // + // .label(4, "Battery string severe overtemperature") // + // .label(128, "Battery string smog fault") // + // .label(256, "Battery string blown fuse indicator fault") // + // .label(1024, "Battery string general leakage") // + // .label(2048, "Battery string severe leakage") // + // .label(4096, "Communication between BECU and periphery CAN disconnected") // + // .label(16384, "Battery string power supply relay contactor disconnected").build(); + // @IsChannel(id = "BatteryStringCellAverageTemperature") + // public final ModbusReadChannel _batteryStringCellAverageTemperature = new OldModbusChannelBuilder().nature(this) + // .unit("�C").multiplier(100).build(); + // @IsChannel(id = "BatteryStringChargeCurrentLimit") + // public final ModbusReadChannel _batteryStringChargeCurrentLimit = new OldModbusChannelBuilder().nature(this) + // .unit("mA").multiplier(100).build(); + // @IsChannel(id = "BatteryStringDischargeCurrentLimit") + // public final ModbusReadChannel _batteryStringDischargeCurrentLimit = new OldModbusChannelBuilder().nature(this) + // .unit("mA").multiplier(100).build(); + // @IsChannel(id = "BatteryStringPeripheralIoState") + // public final ModbusReadChannel _batteryStringPeripheralIoState = new OldModbusChannelBuilder().nature(this) + // .label(1, "Fuse state") // + // .label(2, "Isolated switch state").build(); + // @IsChannel(id = "BatteryStringSOH") + // public final ModbusReadChannel _batteryStringSOH = new OldModbusChannelBuilder().nature(this).unit("%") + // .multiplier(100).build(); + // @IsChannel(id = "BatteryStringSuggestiveInformation") + // public final ModbusReadChannel _batteryStringSuggestiveInformation = new OldModbusChannelBuilder().nature(this) + // .label(1, "Battery string charge general overcurrent") // + // .label(2, "Battery string discharge general overcurrent") // + // .label(4, "Battery string charge current over limit") // + // .label(8, "Battery string discharge current over limit") // + // .label(16, "Battery string general overvoltage") // + // .label(32, "Battery string general undervoltage") // + // .label(128, "Battery string general over temperature") // + // .label(256, "Battery string general under temperature") // + // .label(1024, "Battery string severe overvoltage") // + // .label(2048, "Battery string severe under voltage") // + // .label(4096, "Battery string severe under temperature") // + // .label(8192, "Battery string charge severe overcurrent") // + // .label(16384, "Battery string discharge severe overcurrent")// + // .label(132768, "Battery string capacity abnormity").build(); + + // @IsChannel(id = "BatteryStringTotalVoltage") + // public final ModbusReadChannel _batteryStringTotalVoltage = new OldModbusChannelBuilder().nature(this).unit("mV") + // .multiplier(100).build(); + // @IsChannel(id = "BatteryStringWorkState") + // public final ModbusReadChannel _batteryStringWorkState = new OldModbusChannelBuilder().nature(this) // + // .label(1, "Initial") // + // .label(2, "Stop") // + // .label(4, "Starting up") // + // .label(8, "Running") // + // .label(16, "Fault").build(); + + // private final OldConfigChannel _minSoc = new OldConfigChannelBuilder().nature(this).defaultValue(DEFAULT_MINSOC) + // .percentType().build(); + + // @IsChannel(id = "Abnormity1") + + // @IsChannel(id = "SwitchState") + // public final ModbusReadChannel _switchState = new OldModbusChannelBuilder().nature(this) // + // .label(2, "DC main contactor state") // + // .label(4, "DC precharge contactor state") // + // .label(8, "AC breaker state") // + // .label(16, "AC main contactor state") // + // .label(32, "AC precharge contactor state").build(); + + // @IsChannel(id = "TotalDateEnergy") + // public final ModbusReadChannel _totalDateEnergy = new OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalEnergy") + // public final ModbusReadChannel _totalEnergy = new OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy0") + // public final ModbusReadChannel _totalHourEnergy0 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy1") + // public final ModbusReadChannel _totalHourEnergy1 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy10") + // public final ModbusReadChannel _totalHourEnergy10 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy11") + // public final ModbusReadChannel _totalHourEnergy11 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy12") + // public final ModbusReadChannel _totalHourEnergy12 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy13") + // public final ModbusReadChannel _totalHourEnergy13 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy14") + // public final ModbusReadChannel _totalHourEnergy14 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy15") + // public final ModbusReadChannel _totalHourEnergy15 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy16") + // public final ModbusReadChannel _totalHourEnergy16 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy17") + // public final ModbusReadChannel _totalHourEnergy17 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy18") + // public final ModbusReadChannel _totalHourEnergy18 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy19") + // public final ModbusReadChannel _totalHourEnergy19 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy2") + // public final ModbusReadChannel _totalHourEnergy2 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy20") + // public final ModbusReadChannel _totalHourEnergy20 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy21") + // public final ModbusReadChannel _totalHourEnergy21 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy22") + // public final ModbusReadChannel _totalHourEnergy22 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy23") + // public final ModbusReadChannel _totalHourEnergy23 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy3") + // public final ModbusReadChannel _totalHourEnergy3 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy4") + // public final ModbusReadChannel _totalHourEnergy4 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy5") + // public final ModbusReadChannel _totalHourEnergy5 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy6") + // public final ModbusReadChannel _totalHourEnergy6 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy7") + // public final ModbusReadChannel _totalHourEnergy7 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy8") + // public final ModbusReadChannel _totalHourEnergy8 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalHourEnergy9") + // public final ModbusReadChannel _totalHourEnergy9 = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalMonthEnergy") + // public final ModbusReadChannel _totalMonthEnergy = new + // OldModbusChannelBuilder().nature(this).unit("kWh").build(); + // @IsChannel(id = "TotalYearEnergy") + // public final ModbusReadChannel _totalYearEnergy = new OldModbusChannelBuilder().nature(this).unit("kWh").build(); + + // @IsChannel(id = "MaxVoltageCellNo") + // public final ModbusReadChannel _maxVoltageCellNo = new OldModbusChannelBuilder().nature(this).build(); + // @IsChannel(id = "MaxVoltageCellVoltage") + // public final ModbusReadChannel _maxVoltageCellVoltage = new OldModbusChannelBuilder().nature(this).unit("mV") + // .build(); + // @IsChannel(id = "MaxVoltageCellTemp") + // public final ModbusReadChannel _maxVoltageCellTemp = new + // OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "MinVoltageCellNo") + // public final ModbusReadChannel _minVoltageCellNo = new OldModbusChannelBuilder().nature(this).build(); + // @IsChannel(id = "MinVoltageCellVoltage") + // public final ModbusReadChannel _minVoltageCellVoltage = new OldModbusChannelBuilder().nature(this).unit("mV") + // .build(); + // @IsChannel(id = "MinVoltageCellTemp") + // public final ModbusReadChannel _minVoltageCellTemp = new + // OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "MaxTempCellNo") + // public final ModbusReadChannel _maxTempCellNo = new OldModbusChannelBuilder().nature(this).build(); + // @IsChannel(id = "MaxTempCellVoltage") + // public final ModbusReadChannel _maxTempCellVoltage = new + // OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "MaxTempCellTemp") + // public final ModbusReadChannel _maxTempCellTemp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "MinTempCellNo") + // public final ModbusReadChannel _minTempCellNo = new OldModbusChannelBuilder().nature(this).build(); + // @IsChannel(id = "MinTempCellVoltage") + // public final ModbusReadChannel _minTempCellVoltage = new + // OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "MinTempCellTemp") + // public final ModbusReadChannel _minTempCellTemp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell1Voltage") + // public final ModbusReadChannel _cell1Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell2Voltage") + // public final ModbusReadChannel _cell2Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell3Voltage") + // public final ModbusReadChannel _cell3Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell4Voltage") + // public final ModbusReadChannel _cell4Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell5Voltage") + // public final ModbusReadChannel _cell5Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell6Voltage") + // public final ModbusReadChannel _cell6Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell7Voltage") + // public final ModbusReadChannel _cell7Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell8Voltage") + // public final ModbusReadChannel _cell8Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell9Voltage") + // public final ModbusReadChannel _cell9Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell10Voltage") + // public final ModbusReadChannel _cell10Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell11Voltage") + // public final ModbusReadChannel _cell11Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell12Voltage") + // public final ModbusReadChannel _cell12Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell13Voltage") + // public final ModbusReadChannel _cell13Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell14Voltage") + // public final ModbusReadChannel _cell14Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell15Voltage") + // public final ModbusReadChannel _cell15Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell16Voltage") + // public final ModbusReadChannel _cell16Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell17Voltage") + // public final ModbusReadChannel _cell17Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell18Voltage") + // public final ModbusReadChannel _cell18Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell19Voltage") + // public final ModbusReadChannel _cell19Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell20Voltage") + // public final ModbusReadChannel _cell20Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell21Voltage") + // public final ModbusReadChannel _cell21Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell22Voltage") + // public final ModbusReadChannel _cell22Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell23Voltage") + // public final ModbusReadChannel _cell23Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell24Voltage") + // public final ModbusReadChannel _cell24Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell25Voltage") + // public final ModbusReadChannel _cell25Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell26Voltage") + // public final ModbusReadChannel _cell26Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell27Voltage") + // public final ModbusReadChannel _cell27Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell28Voltage") + // public final ModbusReadChannel _cell28Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell29Voltage") + // public final ModbusReadChannel _cell29Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell30Voltage") + // public final ModbusReadChannel _cell30Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell31Voltage") + // public final ModbusReadChannel _cell31Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell32Voltage") + // public final ModbusReadChannel _cell32Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell33Voltage") + // public final ModbusReadChannel _cell33Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell34Voltage") + // public final ModbusReadChannel _cell34Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell35Voltage") + // public final ModbusReadChannel _cell35Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell36Voltage") + // public final ModbusReadChannel _cell36Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell37Voltage") + // public final ModbusReadChannel _cell37Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell38Voltage") + // public final ModbusReadChannel _cell38Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell39Voltage") + // public final ModbusReadChannel _cell39Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell40Voltage") + // public final ModbusReadChannel _cell40Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell41Voltage") + // public final ModbusReadChannel _cell41Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell42Voltage") + // public final ModbusReadChannel _cell42Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell43Voltage") + // public final ModbusReadChannel _cell43Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell44Voltage") + // public final ModbusReadChannel _cell44Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell45Voltage") + // public final ModbusReadChannel _cell45Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell46Voltage") + // public final ModbusReadChannel _cell46Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell47Voltage") + // public final ModbusReadChannel _cell47Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell48Voltage") + // public final ModbusReadChannel _cell48Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell49Voltage") + // public final ModbusReadChannel _cell49Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell50Voltage") + // public final ModbusReadChannel _cell50Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell51Voltage") + // public final ModbusReadChannel _cell51Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell52Voltage") + // public final ModbusReadChannel _cell52Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell53Voltage") + // public final ModbusReadChannel _cell53Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell54Voltage") + // public final ModbusReadChannel _cell54Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell55Voltage") + // public final ModbusReadChannel _cell55Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell56Voltage") + // public final ModbusReadChannel _cell56Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell57Voltage") + // public final ModbusReadChannel _cell57Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell58Voltage") + // public final ModbusReadChannel _cell58Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell59Voltage") + // public final ModbusReadChannel _cell59Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell60Voltage") + // public final ModbusReadChannel _cell60Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell61Voltage") + // public final ModbusReadChannel _cell61Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell62Voltage") + // public final ModbusReadChannel _cell62Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell63Voltage") + // public final ModbusReadChannel _cell63Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // @IsChannel(id = "Cell64Voltage") + // public final ModbusReadChannel _cell64Voltage = new OldModbusChannelBuilder().nature(this).unit("mV").build(); + // + // @IsChannel(id = "Cell1Temp") + // public final ModbusReadChannel _cell1Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell2Temp") + // public final ModbusReadChannel _cell2Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell3Temp") + // public final ModbusReadChannel _cell3Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell4Temp") + // public final ModbusReadChannel _cell4Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell5Temp") + // public final ModbusReadChannel _cell5Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell6Temp") + // public final ModbusReadChannel _cell6Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell7Temp") + // public final ModbusReadChannel _cell7Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell8Temp") + // public final ModbusReadChannel _cell8Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell9Temp") + // public final ModbusReadChannel _cell9Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell10Temp") + // public final ModbusReadChannel _cell10Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell11Temp") + // public final ModbusReadChannel _cell11Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell12Temp") + // public final ModbusReadChannel _cell12Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell13Temp") + // public final ModbusReadChannel _cell13Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell14Temp") + // public final ModbusReadChannel _cell14Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell15Temp") + // public final ModbusReadChannel _cell15Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell16Temp") + // public final ModbusReadChannel _cell16Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell17Temp") + // public final ModbusReadChannel _cell17Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell18Temp") + // public final ModbusReadChannel _cell18Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell19Temp") + // public final ModbusReadChannel _cell19Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell20Temp") + // public final ModbusReadChannel _cell20Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell21Temp") + // public final ModbusReadChannel _cell21Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell22Temp") + // public final ModbusReadChannel _cell22Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell23Temp") + // public final ModbusReadChannel _cell23Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell24Temp") + // public final ModbusReadChannel _cell24Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell25Temp") + // public final ModbusReadChannel _cell25Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell26Temp") + // public final ModbusReadChannel _cell26Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell27Temp") + // public final ModbusReadChannel _cell27Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell28Temp") + // public final ModbusReadChannel _cell28Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell29Temp") + // public final ModbusReadChannel _cell29Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell30Temp") + // public final ModbusReadChannel _cell30Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell31Temp") + // public final ModbusReadChannel _cell31Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell32Temp") + // public final ModbusReadChannel _cell32Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell33Temp") + // public final ModbusReadChannel _cell33Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell34Temp") + // public final ModbusReadChannel _cell34Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell35Temp") + // public final ModbusReadChannel _cell35Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell36Temp") + // public final ModbusReadChannel _cell36Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell37Temp") + // public final ModbusReadChannel _cell37Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell38Temp") + // public final ModbusReadChannel _cell38Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell39Temp") + // public final ModbusReadChannel _cell39Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell40Temp") + // public final ModbusReadChannel _cell40Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell41Temp") + // public final ModbusReadChannel _cell41Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell42Temp") + // public final ModbusReadChannel _cell42Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell43Temp") + // public final ModbusReadChannel _cell43Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell44Temp") + // public final ModbusReadChannel _cell44Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell45Temp") + // public final ModbusReadChannel _cell45Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell46Temp") + // public final ModbusReadChannel _cell46Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell47Temp") + // public final ModbusReadChannel _cell47Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell48Temp") + // public final ModbusReadChannel _cell48Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell49Temp") + // public final ModbusReadChannel _cell49Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell50Temp") + // public final ModbusReadChannel _cell50Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell51Temp") + // public final ModbusReadChannel _cell51Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell52Temp") + // public final ModbusReadChannel _cell52Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell53Temp") + // public final ModbusReadChannel _cell53Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell54Temp") + // public final ModbusReadChannel _cell54Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell55Temp") + // public final ModbusReadChannel _cell55Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell56Temp") + // public final ModbusReadChannel _cell56Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell57Temp") + // public final ModbusReadChannel _cell57Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell58Temp") + // public final ModbusReadChannel _cell58Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell59Temp") + // public final ModbusReadChannel _cell59Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell60Temp") + // public final ModbusReadChannel _cell60Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell61Temp") + // public final ModbusReadChannel _cell61Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell62Temp") + // public final ModbusReadChannel _cell62Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell63Temp") + // public final ModbusReadChannel _cell63Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + // @IsChannel(id = "Cell64Temp") + // public final ModbusReadChannel _cell64Temp = new OldModbusChannelBuilder().nature(this).unit("�C").build(); + + // @Override + // protected ModbusProtocol defineModbusProtocol() throws ConfigException { + // + + // new ModbusRange(0x0300, // + // new ElementBuilder().address(0x0300).channel(_totalEnergy).doubleword().build(), + // new ElementBuilder().address(0x0302).channel(_totalYearEnergy).doubleword().build(), + // new ElementBuilder().address(0x0304).channel(_totalMonthEnergy).doubleword().build(), + // new ElementBuilder().address(0x0306).channel(_totalDateEnergy).build(), + // new ElementBuilder().address(0x0307).channel(_totalHourEnergy0).build(), + // new ElementBuilder().address(0x0308).channel(_totalHourEnergy1).build(), + // new ElementBuilder().address(0x0309).channel(_totalHourEnergy2).build(), + // new ElementBuilder().address(0x030A).channel(_totalHourEnergy3).build(), + // new ElementBuilder().address(0x030B).channel(_totalHourEnergy4).build(), + // new ElementBuilder().address(0x030C).channel(_totalHourEnergy5).build(), + // new ElementBuilder().address(0x030D).channel(_totalHourEnergy6).build(), + // new ElementBuilder().address(0x030E).channel(_totalHourEnergy7).build(), + // new ElementBuilder().address(0x030F).channel(_totalHourEnergy8).build(), + // new ElementBuilder().address(0x0310).channel(_totalHourEnergy9).build(), + // new ElementBuilder().address(0x0311).channel(_totalHourEnergy10).build(), + // new ElementBuilder().address(0x0312).channel(_totalHourEnergy11).build(), + // new ElementBuilder().address(0x0313).channel(_totalHourEnergy12).build(), + // new ElementBuilder().address(0x0314).channel(_totalHourEnergy13).build(), + // new ElementBuilder().address(0x0315).channel(_totalHourEnergy14).build(), + // new ElementBuilder().address(0x0316).channel(_totalHourEnergy15).build(), + // new ElementBuilder().address(0x0317).channel(_totalHourEnergy16).build(), + // new ElementBuilder().address(0x0318).channel(_totalHourEnergy17).build(), + // new ElementBuilder().address(0x0319).channel(_totalHourEnergy18).build(), + // new ElementBuilder().address(0x031A).channel(_totalHourEnergy19).build(), + // new ElementBuilder().address(0x031B).channel(_totalHourEnergy20).build(), + // new ElementBuilder().address(0x031C).channel(_totalHourEnergy21).build(), + // new ElementBuilder().address(0x031D).channel(_totalHourEnergy22).build(), + // new ElementBuilder().address(0x031E).channel(_totalHourEnergy23).build()), + + // new ModbusRange(0x1100, // + // new ElementBuilder().address(0x1100).channel(_batteryStringWorkState).build(), + // new ElementBuilder().address(0x1101).channel(_batteryStringSwitchState).build(), + // new ElementBuilder().address(0x1102).channel(_batteryStringPeripheralIoState).build(), + // new ElementBuilder().address(0x1103).channel(_batteryStringSuggestiveInformation).build(), + // new ElementBuilder().address(0x1104).dummy().build(), + // new ElementBuilder().address(0x1105).channel(_batteryStringAbnormity1).build(), + // new ElementBuilder().address(0x1106).channel(_batteryStringAbnormity2).build()), + // new ModbusRange(0x1400, // + // new ElementBuilder().address(0x1400).channel(_batteryStringTotalVoltage).build(), + // new ElementBuilder().address(0x1401).channel(_batteryStringTotalCurrent).signed().build(), + // new ElementBuilder().address(0x1402).channel(_soc).build(), + // new ElementBuilder().address(0x1403).channel(_batteryStringSOH).build(), + // new ElementBuilder().address(0x1404).channel(_batteryStringCellAverageTemperature).signed() + // .build(), + // new ElementBuilder().address(0x1405).dummy().build(), + // new ElementBuilder().address(0x1406).channel(_batteryStringChargeCurrentLimit).build(), + // new ElementBuilder().address(0x1407).channel(_batteryStringDischargeCurrentLimit).build(), + // new ElementBuilder().address(0x1408).dummy(0x140A - 0x1408).build(), + // new ElementBuilder().address(0x140A).channel(_batteryChargeCycles).doubleword().build(), + // new ElementBuilder().address(0x140C).dummy(0x1418 - 0x140C).build(), + // new ElementBuilder().address(0x1418).channel(_batteryAccumulatedCharge).doubleword().build(), + // new ElementBuilder().address(0x141A).channel(_batteryAccumulatedDischarge).doubleword().build(), + // new ElementBuilder().address(0x141C).dummy(0x1420 - 0x141C).build(), + // new ElementBuilder().address(0x1420).channel(_batteryPower).signed().build(), + // new ElementBuilder().address(0x1421).dummy(0x1430 - 0x1421).build(), + // new ElementBuilder().address(0x1430).channel(_maxVoltageCellNo).build(), + // new ElementBuilder().address(0x1431).channel(_maxVoltageCellVoltage).build(), + // new ElementBuilder().address(0x1432).channel(_maxVoltageCellTemp).signed().build(), + // new ElementBuilder().address(0x1433).channel(_minVoltageCellNo).build(), + // new ElementBuilder().address(0x1434).channel(_minVoltageCellVoltage).build(), + // new ElementBuilder().address(0x1435).channel(_minVoltageCellTemp).signed().build(), + // new ElementBuilder().address(0x1436).dummy(0x143A - 0x1436).build(), + // new ElementBuilder().address(0x143A).channel(_maxTempCellNo).build(), + // new ElementBuilder().address(0x143B).channel(_maxTempCellTemp).signed().build(), + // new ElementBuilder().address(0x143C).channel(_maxTempCellVoltage).build(), + // new ElementBuilder().address(0x143D).channel(_minTempCellNo).build(), + // new ElementBuilder().address(0x143E).channel(_minTempCellTemp).signed().build(), + // new ElementBuilder().address(0x143F).channel(_minTempCellVoltage).build()), // + // new ModbusRange(0x1500, new ElementBuilder().address(0x1500).channel(_cell1Voltage).build(), + // new ElementBuilder().address(0x1501).channel(_cell2Voltage).build(), + // new ElementBuilder().address(0x1502).channel(_cell3Voltage).build(), + // new ElementBuilder().address(0x1503).channel(_cell4Voltage).build(), + // new ElementBuilder().address(0x1504).channel(_cell5Voltage).build(), + // new ElementBuilder().address(0x1505).channel(_cell6Voltage).build(), + // new ElementBuilder().address(0x1506).channel(_cell7Voltage).build(), + // new ElementBuilder().address(0x1507).channel(_cell8Voltage).build(), + // new ElementBuilder().address(0x1508).channel(_cell9Voltage).build(), + // new ElementBuilder().address(0x1509).channel(_cell10Voltage).build(), + // new ElementBuilder().address(0x150a).channel(_cell11Voltage).build(), + // new ElementBuilder().address(0x150b).channel(_cell12Voltage).build(), + // new ElementBuilder().address(0x150c).channel(_cell13Voltage).build(), + // new ElementBuilder().address(0x150d).channel(_cell14Voltage).build(), + // new ElementBuilder().address(0x150e).channel(_cell15Voltage).build(), + // new ElementBuilder().address(0x150f).channel(_cell16Voltage).build(), + // new ElementBuilder().address(0x1510).channel(_cell17Voltage).build(), + // new ElementBuilder().address(0x1511).channel(_cell18Voltage).build(), + // new ElementBuilder().address(0x1512).channel(_cell19Voltage).build(), + // new ElementBuilder().address(0x1513).channel(_cell20Voltage).build(), + // new ElementBuilder().address(0x1514).channel(_cell21Voltage).build(), + // new ElementBuilder().address(0x1515).channel(_cell22Voltage).build(), + // new ElementBuilder().address(0x1516).channel(_cell23Voltage).build(), + // new ElementBuilder().address(0x1517).channel(_cell24Voltage).build(), + // new ElementBuilder().address(0x1518).channel(_cell25Voltage).build(), + // new ElementBuilder().address(0x1519).channel(_cell26Voltage).build(), + // new ElementBuilder().address(0x151a).channel(_cell27Voltage).build(), + // new ElementBuilder().address(0x151b).channel(_cell28Voltage).build(), + // new ElementBuilder().address(0x151c).channel(_cell29Voltage).build(), + // new ElementBuilder().address(0x151d).channel(_cell30Voltage).build(), + // new ElementBuilder().address(0x151e).channel(_cell31Voltage).build(), + // new ElementBuilder().address(0x151f).channel(_cell32Voltage).build(), + // new ElementBuilder().address(0x1520).channel(_cell33Voltage).build(), + // new ElementBuilder().address(0x1521).channel(_cell34Voltage).build(), + // new ElementBuilder().address(0x1522).channel(_cell35Voltage).build(), + // new ElementBuilder().address(0x1523).channel(_cell36Voltage).build(), + // new ElementBuilder().address(0x1524).channel(_cell37Voltage).build(), + // new ElementBuilder().address(0x1525).channel(_cell38Voltage).build(), + // new ElementBuilder().address(0x1526).channel(_cell39Voltage).build(), + // new ElementBuilder().address(0x1527).channel(_cell40Voltage).build(), + // new ElementBuilder().address(0x1528).channel(_cell41Voltage).build(), + // new ElementBuilder().address(0x1529).channel(_cell42Voltage).build(), + // new ElementBuilder().address(0x152a).channel(_cell43Voltage).build(), + // new ElementBuilder().address(0x152b).channel(_cell44Voltage).build(), + // new ElementBuilder().address(0x152c).channel(_cell45Voltage).build(), + // new ElementBuilder().address(0x152d).channel(_cell46Voltage).build(), + // new ElementBuilder().address(0x152e).channel(_cell47Voltage).build(), + // new ElementBuilder().address(0x152f).channel(_cell48Voltage).build(), + // new ElementBuilder().address(0x1530).channel(_cell49Voltage).build(), + // new ElementBuilder().address(0x1531).channel(_cell50Voltage).build(), + // new ElementBuilder().address(0x1532).channel(_cell51Voltage).build(), + // new ElementBuilder().address(0x1533).channel(_cell52Voltage).build(), + // new ElementBuilder().address(0x1534).channel(_cell53Voltage).build(), + // new ElementBuilder().address(0x1535).channel(_cell54Voltage).build(), + // new ElementBuilder().address(0x1536).channel(_cell55Voltage).build(), + // new ElementBuilder().address(0x1537).channel(_cell56Voltage).build(), + // new ElementBuilder().address(0x1538).channel(_cell57Voltage).build(), + // new ElementBuilder().address(0x1539).channel(_cell58Voltage).build(), + // new ElementBuilder().address(0x153a).channel(_cell59Voltage).build(), + // new ElementBuilder().address(0x153b).channel(_cell60Voltage).build(), + // new ElementBuilder().address(0x153c).channel(_cell61Voltage).build(), + // new ElementBuilder().address(0x153d).channel(_cell62Voltage).build(), + // new ElementBuilder().address(0x153e).channel(_cell63Voltage).build(), + // new ElementBuilder().address(0x153f).channel(_cell64Voltage).build()), + // new ModbusRange(0x1700, // + // new ElementBuilder().address(0x1700).channel(_cell1Temp).build(), + // new ElementBuilder().address(0x1701).channel(_cell2Temp).build(), + // new ElementBuilder().address(0x1702).channel(_cell3Temp).build(), + // new ElementBuilder().address(0x1703).channel(_cell4Temp).build(), + // new ElementBuilder().address(0x1704).channel(_cell5Temp).build(), + // new ElementBuilder().address(0x1705).channel(_cell6Temp).build(), + // new ElementBuilder().address(0x1706).channel(_cell7Temp).build(), + // new ElementBuilder().address(0x1707).channel(_cell8Temp).build(), + // new ElementBuilder().address(0x1708).channel(_cell9Temp).build(), + // new ElementBuilder().address(0x1709).channel(_cell10Temp).build(), + // new ElementBuilder().address(0x170a).channel(_cell11Temp).build(), + // new ElementBuilder().address(0x170b).channel(_cell12Temp).build(), + // new ElementBuilder().address(0x170c).channel(_cell13Temp).build(), + // new ElementBuilder().address(0x170d).channel(_cell14Temp).build(), + // new ElementBuilder().address(0x170e).channel(_cell15Temp).build(), + // new ElementBuilder().address(0x170f).channel(_cell16Temp).build(), + // new ElementBuilder().address(0x1710).channel(_cell17Temp).build(), + // new ElementBuilder().address(0x1711).channel(_cell18Temp).build(), + // new ElementBuilder().address(0x1712).channel(_cell19Temp).build(), + // new ElementBuilder().address(0x1713).channel(_cell20Temp).build(), + // new ElementBuilder().address(0x1714).channel(_cell21Temp).build(), + // new ElementBuilder().address(0x1715).channel(_cell22Temp).build(), + // new ElementBuilder().address(0x1716).channel(_cell23Temp).build(), + // new ElementBuilder().address(0x1717).channel(_cell24Temp).build(), + // new ElementBuilder().address(0x1718).channel(_cell25Temp).build(), + // new ElementBuilder().address(0x1719).channel(_cell26Temp).build(), + // new ElementBuilder().address(0x171a).channel(_cell27Temp).build(), + // new ElementBuilder().address(0x171b).channel(_cell28Temp).build(), + // new ElementBuilder().address(0x171c).channel(_cell29Temp).build(), + // new ElementBuilder().address(0x171d).channel(_cell30Temp).build(), + // new ElementBuilder().address(0x171e).channel(_cell31Temp).build(), + // new ElementBuilder().address(0x171f).channel(_cell32Temp).build(), + // new ElementBuilder().address(0x1720).channel(_cell33Temp).build(), + // new ElementBuilder().address(0x1721).channel(_cell34Temp).build(), + // new ElementBuilder().address(0x1722).channel(_cell35Temp).build(), + // new ElementBuilder().address(0x1723).channel(_cell36Temp).build(), + // new ElementBuilder().address(0x1724).channel(_cell37Temp).build(), + // new ElementBuilder().address(0x1725).channel(_cell38Temp).build(), + // new ElementBuilder().address(0x1726).channel(_cell39Temp).build(), + // new ElementBuilder().address(0x1727).channel(_cell40Temp).build(), + // new ElementBuilder().address(0x1728).channel(_cell41Temp).build(), + // new ElementBuilder().address(0x1729).channel(_cell42Temp).build(), + // new ElementBuilder().address(0x172a).channel(_cell43Temp).build(), + // new ElementBuilder().address(0x172b).channel(_cell44Temp).build(), + // new ElementBuilder().address(0x172c).channel(_cell45Temp).build(), + // new ElementBuilder().address(0x172d).channel(_cell46Temp).build(), + // new ElementBuilder().address(0x172e).channel(_cell47Temp).build(), + // new ElementBuilder().address(0x172f).channel(_cell48Temp).build(), + // new ElementBuilder().address(0x1730).channel(_cell49Temp).build(), + // new ElementBuilder().address(0x1731).channel(_cell50Temp).build(), + // new ElementBuilder().address(0x1732).channel(_cell51Temp).build(), + // new ElementBuilder().address(0x1733).channel(_cell52Temp).build(), + // new ElementBuilder().address(0x1734).channel(_cell53Temp).build(), + // new ElementBuilder().address(0x1735).channel(_cell54Temp).build(), + // new ElementBuilder().address(0x1736).channel(_cell55Temp).build(), + // new ElementBuilder().address(0x1737).channel(_cell56Temp).build(), + // new ElementBuilder().address(0x1738).channel(_cell57Temp).build(), + // new ElementBuilder().address(0x1739).channel(_cell58Temp).build(), + // new ElementBuilder().address(0x173a).channel(_cell59Temp).build(), + // new ElementBuilder().address(0x173b).channel(_cell60Temp).build(), + // new ElementBuilder().address(0x173c).channel(_cell61Temp).build(), + // new ElementBuilder().address(0x173d).channel(_cell62Temp).build(), + // new ElementBuilder().address(0x173e).channel(_cell63Temp).build(), + // new ElementBuilder().address(0x173f).channel(_cell64Temp).build())); + // } +} diff --git a/edge/src/io/openems/impl/device/custom/riedmann/Riedmann.java b/edge/src/io/openems/impl/device/custom/riedmann/Riedmann.java index fb2e0b60735..32ef49f577c 100644 --- a/edge/src/io/openems/impl/device/custom/riedmann/Riedmann.java +++ b/edge/src/io/openems/impl/device/custom/riedmann/Riedmann.java @@ -3,6 +3,7 @@ import java.util.HashSet; import java.util.Set; +import io.openems.api.bridge.Bridge; import io.openems.api.channel.ConfigChannel; import io.openems.api.device.nature.DeviceNature; import io.openems.api.doc.ConfigInfo; @@ -14,8 +15,8 @@ public class Riedmann extends ModbusDevice { @ConfigInfo(title = "", type = RiedmannNature.class) public final ConfigChannel device = new ConfigChannel("device", this); - public Riedmann() throws OpenemsException { - super(); + public Riedmann(Bridge parent) throws OpenemsException { + super(parent); } @Override diff --git a/edge/src/io/openems/impl/device/custom/riedmann/RiedmannNatureImpl.java b/edge/src/io/openems/impl/device/custom/riedmann/RiedmannNatureImpl.java index c2f3d41d290..f69871f9235 100644 --- a/edge/src/io/openems/impl/device/custom/riedmann/RiedmannNatureImpl.java +++ b/edge/src/io/openems/impl/device/custom/riedmann/RiedmannNatureImpl.java @@ -1,5 +1,6 @@ package io.openems.impl.device.custom.riedmann; +import io.openems.api.device.Device; import io.openems.api.exception.ConfigException; import io.openems.impl.protocol.modbus.ModbusDeviceNature; import io.openems.impl.protocol.modbus.ModbusReadChannel; @@ -255,8 +256,8 @@ public ModbusWriteChannel getSetWaterLevelBorehole3Off() { return setWaterLevelBorehole3Off; } - public RiedmannNatureImpl(String thingId) throws ConfigException { - super(thingId); + public RiedmannNatureImpl(String thingId, Device parent) throws ConfigException { + super(thingId, parent); } @Override diff --git a/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RME.java b/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RME.java index 7a8d647f792..8fc75827c79 100644 --- a/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RME.java +++ b/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RME.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.janitza; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "Janitza UMG96RM E") -public class JanitzaUMG96RME extends ModbusDevice { - - /* - * Constructors - */ - public JanitzaUMG96RME() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = JanitzaUMG96RMEMeter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.janitza; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "Janitza UMG96RM E") +public class JanitzaUMG96RME extends ModbusDevice { + + /* + * Constructors + */ + public JanitzaUMG96RME(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = JanitzaUMG96RMEMeter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RMEMeter.java b/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RMEMeter.java index 2069a20de7c..db2fe6004f8 100644 --- a/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RMEMeter.java +++ b/edge/src/io/openems/impl/device/janitza/JanitzaUMG96RMEMeter.java @@ -1,171 +1,231 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.janitza; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.FloatElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "Janitza UMG96RM Meter") -public class JanitzaUMG96RMEMeter extends ModbusDeviceNature implements SymmetricMeterNature { - - /* - * Constructors - */ - public JanitzaUMG96RMEMeter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel activePower; - public ModbusReadLongChannel activePowerL1; - public ModbusReadLongChannel activePowerL2; - public ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel apparentPower; - private ModbusReadLongChannel reactivePower; - public ModbusReadLongChannel reactivePowerL1; - public ModbusReadLongChannel reactivePowerL2; - public ModbusReadLongChannel reactivePowerL3; - public ModbusReadLongChannel current; - private ModbusReadLongChannel frequency; - public ModbusReadLongChannel voltageL1; - public ModbusReadLongChannel voltageL2; - public ModbusReadLongChannel voltageL3; - public ModbusReadLongChannel currentL1; - public ModbusReadLongChannel currentL2; - public ModbusReadLongChannel currentL3; - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( // - new ModbusRegisterRange(800, // - new FloatElement(800, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHz")) // - .multiplier(3), - new DummyElement(802, 807), - new FloatElement(808, voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV")) - .multiplier(3), - new FloatElement(810, voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV")) - .multiplier(3), - new FloatElement(812, voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV")) - .multiplier(3), - new DummyElement(814, 859), - new FloatElement(860, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")).multiplier(3), - new FloatElement(862, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")).multiplier(3), - new FloatElement(864, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")).multiplier(3), - new FloatElement(866, // - current = new ModbusReadLongChannel("Current", this).unit("mA")).multiplier(3), - new FloatElement(868, // - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this) // - .unit("W")), // - new FloatElement(870, // - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this) // - .unit("W")), // - new FloatElement(872, // - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this) // - .unit("W")), // - new FloatElement(874, // - activePower = new ModbusReadLongChannel("ActivePower", this) // - .unit("W")), // - new FloatElement(876, // - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this) // - .unit("Var")), // - new FloatElement(878, // - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this) // - .unit("Var")), // - new FloatElement(880, // - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this) // - .unit("Var")), // - new FloatElement(882, // - reactivePower = new ModbusReadLongChannel("ReactivePower", this) // - .unit("Var")), // - new DummyElement(884, 889), - new FloatElement(890, // - apparentPower = new ModbusReadLongChannel("ApparentPower", this) // - .unit("VA")) // - )); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.janitza; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.FloatElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "Janitza UMG96RM Meter") +public class JanitzaUMG96RMEMeter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { + + /* + * Constructors + */ + public JanitzaUMG96RMEMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel activePower; + private ModbusReadLongChannel activePowerL1; + private ModbusReadLongChannel activePowerL2; + private ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel apparentPower; + private ModbusReadLongChannel reactivePower; + private ModbusReadLongChannel reactivePowerL1; + private ModbusReadLongChannel reactivePowerL2; + private ModbusReadLongChannel reactivePowerL3; + private ModbusReadLongChannel current; + private ModbusReadLongChannel frequency; + private ModbusReadLongChannel voltageL1; + private ModbusReadLongChannel voltageL2; + private ModbusReadLongChannel voltageL3; + private ModbusReadLongChannel currentL1; + private ModbusReadLongChannel currentL2; + private ModbusReadLongChannel currentL3; + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( // + new ModbusRegisterRange(800, // + new FloatElement(800, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHz")) // + .multiplier(3), + new DummyElement(802, 807), + new FloatElement(808, voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV")) + .multiplier(3), + new FloatElement(810, voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV")) + .multiplier(3), + new FloatElement(812, voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV")) + .multiplier(3), + new DummyElement(814, 859), new FloatElement(860, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")).multiplier(3), + new FloatElement(862, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")).multiplier(3), + new FloatElement(864, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")).multiplier(3), + new FloatElement(866, // + current = new ModbusReadLongChannel("Current", this).unit("mA")).multiplier(3), + new FloatElement(868, // + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this) // + .unit("W")), // + new FloatElement(870, // + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this) // + .unit("W")), // + new FloatElement(872, // + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this) // + .unit("W")), // + new FloatElement(874, // + activePower = new ModbusReadLongChannel("ActivePower", this) // + .unit("W")), // + new FloatElement(876, // + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this) // + .unit("Var")), // + new FloatElement(878, // + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this) // + .unit("Var")), // + new FloatElement(880, // + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this) // + .unit("Var")), // + new FloatElement(882, // + reactivePower = new ModbusReadLongChannel("ReactivePower", this) // + .unit("Var")), // + new DummyElement(884, 889), new FloatElement(890, // + apparentPower = new ModbusReadLongChannel("ApparentPower", this) // + .unit("VA")) // + )); + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } +} diff --git a/edge/src/io/openems/impl/device/keba/Keba.java b/edge/src/io/openems/impl/device/keba/Keba.java index 4600a3ca19c..1abce0d12d3 100644 --- a/edge/src/io/openems/impl/device/keba/Keba.java +++ b/edge/src/io/openems/impl/device/keba/Keba.java @@ -24,6 +24,7 @@ import java.util.HashSet; import java.util.Set; +import io.openems.api.bridge.Bridge; import io.openems.api.channel.ConfigChannel; import io.openems.api.device.nature.DeviceNature; import io.openems.api.doc.ConfigInfo; @@ -45,8 +46,8 @@ public class Keba extends KebaDevice { /* * Constructors */ - public Keba() throws OpenemsException { - super(); + public Keba(Bridge parent) throws OpenemsException { + super(parent); log.info("Constructor Keba"); } diff --git a/edge/src/io/openems/impl/device/keba/KebaEvcs.java b/edge/src/io/openems/impl/device/keba/KebaEvcs.java index fab9d8a4a4e..4cdfca56354 100644 --- a/edge/src/io/openems/impl/device/keba/KebaEvcs.java +++ b/edge/src/io/openems/impl/device/keba/KebaEvcs.java @@ -24,6 +24,7 @@ import java.util.List; import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; import io.openems.api.device.nature.evcs.EvcsNature; import io.openems.api.doc.ThingInfo; import io.openems.api.exception.ConfigException; @@ -36,8 +37,8 @@ public class KebaEvcs extends KebaDeviceNature implements EvcsNature { /* * Constructors */ - public KebaEvcs(String thingId) throws ConfigException { - super(thingId); + public KebaEvcs(String thingId, Device parent) throws ConfigException { + super(thingId, parent); log.info("Constructor KebaEvcs"); } diff --git a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelay.java b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelay.java index 73813f7903f..2da830ea4a7 100644 --- a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelay.java +++ b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelay.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.kmtronic; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "KMTronic Relay board") -public class KMTronicRelay extends ModbusDevice { - - /* - * Constructors - */ - public KMTronicRelay() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Output", description = "Sets the output nature.", type = KMTronicRelayOutput.class) - public final ConfigChannel output = new ConfigChannel<>("output", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (output.valueOptional().isPresent()) { - natures.add(output.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.kmtronic; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "KMTronic Relay board") +public class KMTronicRelay extends ModbusDevice { + + /* + * Constructors + */ + public KMTronicRelay(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Output", description = "Sets the output nature.", type = KMTronicRelayOutput.class) + public final ConfigChannel output = new ConfigChannel<>("output", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (output.valueOptional().isPresent()) { + natures.add(output.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutput.java b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutput.java index d0642d92a18..769a86cf0a4 100644 --- a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutput.java +++ b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutput.java @@ -1,70 +1,71 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.kmtronic; - -import io.openems.api.device.nature.io.OutputNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusCoilWriteChannel; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.internal.CoilElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; - -@ThingInfo(title = "KMTronic Relay board Output") -public class KMTronicRelayOutput extends ModbusDeviceNature implements OutputNature { - - /* - * Constructors - */ - public KMTronicRelayOutput(String thingId) throws ConfigException { - super(thingId); - outputs = new ModbusCoilWriteChannel[8]; - } - - /* - * Fields - */ - private ModbusCoilWriteChannel[] outputs; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( - new WriteableModbusCoilRange(0, new CoilElement(0, outputs[0] = new ModbusCoilWriteChannel("1", this))), - new WriteableModbusCoilRange(1, new CoilElement(1, outputs[1] = new ModbusCoilWriteChannel("2", this))), - new WriteableModbusCoilRange(2, new CoilElement(2, outputs[2] = new ModbusCoilWriteChannel("3", this))), - new WriteableModbusCoilRange(3, new CoilElement(3, outputs[3] = new ModbusCoilWriteChannel("4", this))), - new WriteableModbusCoilRange(4, new CoilElement(4, outputs[4] = new ModbusCoilWriteChannel("5", this))), - new WriteableModbusCoilRange(5, new CoilElement(5, outputs[5] = new ModbusCoilWriteChannel("6", this))), - new WriteableModbusCoilRange(6, new CoilElement(6, outputs[6] = new ModbusCoilWriteChannel("7", this))), - new WriteableModbusCoilRange(7, - new CoilElement(7, outputs[7] = new ModbusCoilWriteChannel("8", this)))); - } - - @Override - public ModbusCoilWriteChannel[] setOutput() { - return outputs; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.kmtronic; + +import io.openems.api.device.Device; +import io.openems.api.device.nature.io.OutputNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusCoilWriteChannel; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.internal.CoilElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; + +@ThingInfo(title = "KMTronic Relay board Output") +public class KMTronicRelayOutput extends ModbusDeviceNature implements OutputNature { + + /* + * Constructors + */ + public KMTronicRelayOutput(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + outputs = new ModbusCoilWriteChannel[8]; + } + + /* + * Fields + */ + private ModbusCoilWriteChannel[] outputs; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( + new WriteableModbusCoilRange(0, new CoilElement(0, outputs[0] = new ModbusCoilWriteChannel("1", this))), + new WriteableModbusCoilRange(1, new CoilElement(1, outputs[1] = new ModbusCoilWriteChannel("2", this))), + new WriteableModbusCoilRange(2, new CoilElement(2, outputs[2] = new ModbusCoilWriteChannel("3", this))), + new WriteableModbusCoilRange(3, new CoilElement(3, outputs[3] = new ModbusCoilWriteChannel("4", this))), + new WriteableModbusCoilRange(4, new CoilElement(4, outputs[4] = new ModbusCoilWriteChannel("5", this))), + new WriteableModbusCoilRange(5, new CoilElement(5, outputs[5] = new ModbusCoilWriteChannel("6", this))), + new WriteableModbusCoilRange(6, new CoilElement(6, outputs[6] = new ModbusCoilWriteChannel("7", this))), + new WriteableModbusCoilRange(7, + new CoilElement(7, outputs[7] = new ModbusCoilWriteChannel("8", this)))); + } + + @Override + public ModbusCoilWriteChannel[] setOutput() { + return outputs; + } + +} diff --git a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutputRev1.java b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutputRev1.java index 0a718a05c2d..e5dadf7ddf4 100644 --- a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutputRev1.java +++ b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayOutputRev1.java @@ -1,69 +1,70 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.kmtronic; - -import io.openems.api.device.nature.io.OutputNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusCoilWriteChannel; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.internal.CoilElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; - -@ThingInfo(title = "KMTronic Relay board Output") -public class KMTronicRelayOutputRev1 extends ModbusDeviceNature implements OutputNature { - - /* - * Constructors - */ - public KMTronicRelayOutputRev1(String thingId) throws ConfigException { - super(thingId); - outputs = new ModbusCoilWriteChannel[7]; - } - - /* - * Fields - */ - private ModbusCoilWriteChannel[] outputs; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( - new WriteableModbusCoilRange(1, new CoilElement(1, outputs[0] = new ModbusCoilWriteChannel("2", this))), - new WriteableModbusCoilRange(2, new CoilElement(2, outputs[1] = new ModbusCoilWriteChannel("3", this))), - new WriteableModbusCoilRange(3, new CoilElement(3, outputs[2] = new ModbusCoilWriteChannel("4", this))), - new WriteableModbusCoilRange(4, new CoilElement(4, outputs[3] = new ModbusCoilWriteChannel("5", this))), - new WriteableModbusCoilRange(5, new CoilElement(5, outputs[4] = new ModbusCoilWriteChannel("6", this))), - new WriteableModbusCoilRange(6, new CoilElement(6, outputs[5] = new ModbusCoilWriteChannel("7", this))), - new WriteableModbusCoilRange(7, - new CoilElement(7, outputs[6] = new ModbusCoilWriteChannel("8", this)))); - } - - @Override - public ModbusCoilWriteChannel[] setOutput() { - return outputs; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.kmtronic; + +import io.openems.api.device.Device; +import io.openems.api.device.nature.io.OutputNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusCoilWriteChannel; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.internal.CoilElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; + +@ThingInfo(title = "KMTronic Relay board Output") +public class KMTronicRelayOutputRev1 extends ModbusDeviceNature implements OutputNature { + + /* + * Constructors + */ + public KMTronicRelayOutputRev1(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + outputs = new ModbusCoilWriteChannel[7]; + } + + /* + * Fields + */ + private ModbusCoilWriteChannel[] outputs; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( + new WriteableModbusCoilRange(1, new CoilElement(1, outputs[0] = new ModbusCoilWriteChannel("2", this))), + new WriteableModbusCoilRange(2, new CoilElement(2, outputs[1] = new ModbusCoilWriteChannel("3", this))), + new WriteableModbusCoilRange(3, new CoilElement(3, outputs[2] = new ModbusCoilWriteChannel("4", this))), + new WriteableModbusCoilRange(4, new CoilElement(4, outputs[3] = new ModbusCoilWriteChannel("5", this))), + new WriteableModbusCoilRange(5, new CoilElement(5, outputs[4] = new ModbusCoilWriteChannel("6", this))), + new WriteableModbusCoilRange(6, new CoilElement(6, outputs[5] = new ModbusCoilWriteChannel("7", this))), + new WriteableModbusCoilRange(7, + new CoilElement(7, outputs[6] = new ModbusCoilWriteChannel("8", this)))); + } + + @Override + public ModbusCoilWriteChannel[] setOutput() { + return outputs; + } + +} diff --git a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayRev1.java b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayRev1.java index e623af16a5e..85ca151c36b 100644 --- a/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayRev1.java +++ b/edge/src/io/openems/impl/device/kmtronic/KMTronicRelayRev1.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.kmtronic; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "KMTronic Relay board") -public class KMTronicRelayRev1 extends ModbusDevice { - - /* - * Constructors - */ - public KMTronicRelayRev1() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Output", description = "Sets the output nature.", type = KMTronicRelayOutputRev1.class) - public final ConfigChannel output = new ConfigChannel<>("output", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (output.valueOptional().isPresent()) { - natures.add(output.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.kmtronic; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "KMTronic Relay board") +public class KMTronicRelayRev1 extends ModbusDevice { + + /* + * Constructors + */ + public KMTronicRelayRev1(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Output", description = "Sets the output nature.", type = KMTronicRelayOutputRev1.class) + public final ConfigChannel output = new ConfigChannel<>("output", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (output.valueOptional().isPresent()) { + natures.add(output.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/mini/FeneconMini.java b/edge/src/io/openems/impl/device/mini/FeneconMini.java index 7314fa42d8d..b6d8a234276 100644 --- a/edge/src/io/openems/impl/device/mini/FeneconMini.java +++ b/edge/src/io/openems/impl/device/mini/FeneconMini.java @@ -1,65 +1,66 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.mini; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "FENECON Pro") -public class FeneconMini extends ModbusDevice { - - /* - * Constructors - */ - public FeneconMini() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconMiniEss.class) - public final ConfigChannel ess = new ConfigChannel<>("ess", this); - - /* - * Methods - */ - @Override - public String toString() { - return "FeneconPro [ess=" + ess + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (ess.valueOptional().isPresent()) { - natures.add(ess.valueOptional().get()); - } - return natures; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.mini; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "FENECON Pro") +public class FeneconMini extends ModbusDevice { + + /* + * Constructors + */ + public FeneconMini(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconMiniEss.class) + public final ConfigChannel ess = new ConfigChannel<>("ess", this); + + /* + * Methods + */ + @Override + public String toString() { + return "FeneconPro [ess=" + ess + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (ess.valueOptional().isPresent()) { + natures.add(ess.valueOptional().get()); + } + return natures; + } +} diff --git a/edge/src/io/openems/impl/device/mini/FeneconMiniEss.java b/edge/src/io/openems/impl/device/mini/FeneconMiniEss.java index 95304676b72..a98da1564ed 100644 --- a/edge/src/io/openems/impl/device/mini/FeneconMiniEss.java +++ b/edge/src/io/openems/impl/device/mini/FeneconMiniEss.java @@ -1,478 +1,468 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.mini; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.device.nature.realtimeclock.RealTimeClockNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.core.utilities.ControllerUtils; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedWordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; - -@ThingInfo(title = "FENECON Mini ESS") -public class FeneconMiniEss extends ModbusDeviceNature implements SymmetricEssNature, RealTimeClockNature { - - /* - * Constructors - */ - public FeneconMiniEss(String thingId) throws ConfigException { - super(thingId); - minSoc.addUpdateListener((channel, newValue) -> { - // If chargeSoc was not set -> set it to minSoc minus 2 - if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { - chargeSoc.updateValue((Integer) newValue.get() - 2, false); - } - }); - } - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - // ESS - private StatusBitChannels warning; - private ModbusReadLongChannel allowedCharge; - private ModbusReadLongChannel allowedDischarge; - private ReadChannel gridMode; - private ModbusReadLongChannel soc; - private ModbusReadLongChannel systemState; - private ModbusReadLongChannel activePower; - private ModbusReadLongChannel reactivePower; - private ModbusWriteLongChannel setWorkState; - private ModbusWriteLongChannel setActivePower; - private ModbusWriteLongChannel setReactivePower; - private ReadChannel apparentPower; - // RealTimeClock - private ModbusWriteLongChannel rtcYear; - private ModbusWriteLongChannel rtcMonth; - private ModbusWriteLongChannel rtcDay; - private ModbusWriteLongChannel rtcHour; - private ModbusWriteLongChannel rtcMinute; - private ModbusWriteLongChannel rtcSecond; - private StaticValueChannel nominalPower = new StaticValueChannel("maxNominalPower", this, 3000l) - .unit("VA"); - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 3000L).unit("Wh"); - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public ReadChannel allowedApparent() { - return phaseAllowedApparent; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public WriteChannel rtcYear() { - return rtcYear; - } - - @Override - public WriteChannel rtcMonth() { - return rtcMonth; - } - - @Override - public WriteChannel rtcDay() { - return rtcDay; - } - - @Override - public WriteChannel rtcHour() { - return rtcHour; - } - - @Override - public WriteChannel rtcMinute() { - return rtcMinute; - } - - @Override - public WriteChannel rtcSecond() { - return rtcSecond; - } - - /* - * This Channels - */ - public ModbusReadLongChannel phaseAllowedApparent; - public ModbusReadLongChannel frequencyL3; - public ModbusReadLongChannel frequencyL2; - public ModbusReadLongChannel frequencyL1; - public ModbusReadLongChannel currentL1; - public ModbusReadLongChannel currentL2; - public ModbusReadLongChannel currentL3; - public ModbusReadLongChannel voltageL1; - public ModbusReadLongChannel voltageL2; - public ModbusReadLongChannel voltageL3; - public ModbusReadLongChannel pcsOperationState; - public ModbusReadLongChannel batteryPower; - public ModbusReadLongChannel batteryGroupAlarm; - public ModbusReadLongChannel batteryCurrent; - public ModbusReadLongChannel batteryVoltage; - public ModbusReadLongChannel batteryGroupState; - public ModbusReadLongChannel totalBatteryDischargeEnergy; - public ModbusReadLongChannel totalBatteryChargeEnergy; - public ModbusReadLongChannel workMode; - public ModbusReadLongChannel controlMode; - public ModbusWriteLongChannel setPcsMode; - public ModbusWriteLongChannel setSetupMode; - public ModbusReadLongChannel setupMode; - public ModbusReadLongChannel pcsMode; - public StatusBitChannel pcsAlarm1L1; - public StatusBitChannel pcsAlarm2L1; - public StatusBitChannel pcsFault1L1; - public StatusBitChannel pcsFault2L1; - public StatusBitChannel pcsFault3L1; - public StatusBitChannel pcsAlarm1L2; - public StatusBitChannel pcsAlarm2L2; - public StatusBitChannel pcsFault1L2; - public StatusBitChannel pcsFault2L2; - public StatusBitChannel pcsFault3L2; - public StatusBitChannel pcsAlarm1L3; - public StatusBitChannel pcsAlarm2L3; - public StatusBitChannel pcsFault1L3; - public StatusBitChannel pcsFault2L3; - public StatusBitChannel pcsFault3L3; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - warning = new StatusBitChannels("Warning", this); - - ModbusProtocol protokol = new ModbusProtocol(new ModbusRegisterRange(100, // - new UnsignedWordElement(100, // - systemState = new ModbusReadLongChannel("SystemState", this) // - .label(0, STANDBY) // - .label(1, "Start Off-Grid") // - .label(2, START) // - .label(3, FAULT) // - .label(4, "Off-grid PV")), - new UnsignedWordElement(101, // - controlMode = new ModbusReadLongChannel("ControlMode", this) // - .label(1, "Remote") // - .label(2, "Local")), // - new DummyElement(102, 103), // - new UnsignedDoublewordElement(104, // - totalBatteryChargeEnergy = new ModbusReadLongChannel("TotalBatteryChargeEnergy", this) - .unit("Wh")), // - new UnsignedDoublewordElement(106, // - totalBatteryDischargeEnergy = new ModbusReadLongChannel("TotalBatteryDischargeEnergy", this) - .unit("Wh")), // - new UnsignedWordElement(108, // - batteryGroupState = new ModbusReadLongChannel("BatteryGroupState", this) // - .label(0, "Initial") // - .label(1, "Stop") // - .label(2, "Starting") // - .label(3, "Running") // - .label(4, "Stopping") // - .label(5, "Fail")), - new UnsignedWordElement(109, // - soc = new ModbusReadLongChannel("Soc", this).unit("%").interval(0, 100)), - new UnsignedWordElement(110, // - batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV").multiplier(2)), - new SignedWordElement(111, // - batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA").multiplier(2)), - new SignedWordElement(112, // - batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W")), - new UnsignedWordElement(113, // - batteryGroupAlarm = new ModbusReadLongChannel("BatteryGroupAlarm", this) - .label(1, "Fail, The system should be stopped") // - .label(2, "Common low voltage alarm") // - .label(4, "Common high voltage alarm") // - .label(8, "Charging over current alarm") // - .label(16, "Discharging over current alarm") // - .label(32, "Over temperature alarm")// - .label(64, "Interal communication abnormal")), - new UnsignedWordElement(114, // - pcsOperationState = new ModbusReadLongChannel("PcsOperationState", this) - .label(0, "Self-checking") // - .label(1, "Standby") // - .label(2, "Off grid PV") // - .label(3, "Off grid") // - .label(4, ON_GRID) // - .label(5, "Fail") // - .label(6, "bypass 1") // - .label(7, "bypass 2")), - new DummyElement(115, 117), // - new SignedWordElement(118, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), - new DummyElement(119, 120), - new UnsignedWordElement(121, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), - new DummyElement(122, 123), - new SignedWordElement(124, // - activePower = new ModbusReadLongChannel("ActivePowerL1", this).unit("W")), - new DummyElement(125, 126), - new SignedWordElement(127, // - reactivePower = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var")), - new DummyElement(128, 130), - new UnsignedWordElement(131, // - frequencyL1 = new ModbusReadLongChannel("FrequencyL1", this).unit("mHz").multiplier(1)), - new DummyElement(132, 133), - new UnsignedWordElement(134, // - phaseAllowedApparent = new ModbusReadLongChannel("PhaseAllowedApparentPower", this).unit("VA")), - new DummyElement(135, 140), - new UnsignedWordElement(141, // - allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).unit("W").negate()), - new UnsignedWordElement(142, // - allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W")), - new DummyElement(143, 149), - new UnsignedWordElement(150, - pcsAlarm1L1 = warning.channel(new StatusBitChannel("PcsAlarm1L1", this)// - .label(1, "Grid undervoltage") // - .label(2, "Grid overvoltage") // - .label(4, "Grid under frequency") // - .label(8, "Grid over frequency") // - .label(16, "Grid power supply off") // - .label(32, "Grid condition unmeet")// - .label(64, "DC under voltage")// - .label(128, "Input over resistance")// - .label(256, "Combination error")// - .label(512, "Comm with inverter error")// - .label(1024, "Tme error")// - )), new UnsignedWordElement(151, pcsAlarm2L1 = warning.channel(new StatusBitChannel("PcsAlarm2L1", this)// - )), new UnsignedWordElement(152, warning.channel(pcsFault1L1 = new StatusBitChannel("PcsFault1L1", this)// - .label(1, "Control current overload 100%")// - .label(2, "Control current overload 110%")// - .label(4, "Control current overload 150%")// - .label(8, "Control current overload 200%")// - .label(16, "Control current overload 120%")// - .label(32, "Control current overload 300%")// - .label(64, "Control transient load 300%")// - .label(128, "Grid over current")// - .label(256, "Locking waveform too many times")// - .label(512, "Inverter voltage zero drift error")// - .label(1024, "Grid voltage zero drift error")// - .label(2048, "Control current zero drift error")// - .label(4096, "Inverter current zero drift error")// - .label(8192, "Grid current zero drift error")// - .label(16384, "PDP protection")// - .label(32768, "Hardware control current protection")// - )), new UnsignedWordElement(153, warning.channel(pcsFault2L1 = new StatusBitChannel("PcsFault2L1", this)// - .label(1, "Hardware AC volt. protection")// - .label(2, "Hardware DC curr. protection")// - .label(4, "Hardware temperature protection")// - .label(8, "No capturing signal")// - .label(16, "DC overvoltage")// - .label(32, "DC disconnected")// - .label(64, "Inverter undervoltage")// - .label(128, "Inverter overvoltage")// - .label(256, "Current sensor fail")// - .label(512, "Voltage sensor fail")// - .label(1024, "Power uncontrollable")// - .label(2048, "Current uncontrollable")// - .label(4096, "Fan error")// - .label(8192, "Phase lack")// - .label(16384, "Inverter relay fault")// - .label(32768, "Grid relay fault")// - )), new UnsignedWordElement(154, warning.channel(pcsFault3L1 = new StatusBitChannel("PcsFault3L1", this)// - .label(1, "Control panel overtemp")// - .label(2, "Power panel overtemp")// - .label(4, "DC input overcurrent")// - .label(8, "Capacitor overtemp")// - .label(16, "Radiator overtemp")// - .label(32, "Transformer overtemp")// - .label(64, "Combination comm error")// - .label(128, "EEPROM error")// - .label(256, "Load current zero drift error")// - .label(512, "Current limit-R error")// - .label(1024, "Phase sync error")// - .label(2048, "External PV current zero drift error")// - .label(4096, "External grid current zero drift error")// - ))), // - new WriteableModbusRegisterRange(200, // - new UnsignedWordElement(200, - setWorkState = new ModbusWriteLongChannel("SetWorkState", this)// - .label(0, "Local control") // - .label(1, START) // "Remote control on grid starting" - .label(2, "Remote control off grid starting") // - .label(3, STOP)// - .label(4, "Emergency Stop"))), - new WriteableModbusRegisterRange(201, // - new SignedWordElement(201, - setActivePower = new ModbusWriteLongChannel("SetActivePower", this).unit("W")), // - new SignedWordElement(202, - setReactivePower = new ModbusWriteLongChannel("SetReactivePower", this).unit("Var"))), // - new WriteableModbusRegisterRange(9014, // - new UnsignedWordElement(9014, rtcYear = new ModbusWriteLongChannel("Year", this)), - new UnsignedWordElement(9015, rtcMonth = new ModbusWriteLongChannel("Month", this)), - new UnsignedWordElement(9016, rtcDay = new ModbusWriteLongChannel("Day", this)), - new UnsignedWordElement(9017, rtcHour = new ModbusWriteLongChannel("Hour", this)), - new UnsignedWordElement(9018, rtcMinute = new ModbusWriteLongChannel("Minute", this)), - new UnsignedWordElement(9019, rtcSecond = new ModbusWriteLongChannel("Second", this))), - new WriteableModbusRegisterRange(30558, - new UnsignedWordElement(30558, - setSetupMode = new ModbusWriteLongChannel("SetSetupMode", this).label(0, EssNature.OFF) - .label(1, EssNature.ON))), - new WriteableModbusRegisterRange(30559, - new UnsignedWordElement(30559, - setPcsMode = new ModbusWriteLongChannel("SetPcsMode", this)// - .label(0, "Emergency")// - .label(1, "ConsumersPeakPattern")// - .label(2, "Economic")// - .label(3, "Eco")// - .label(4, "Debug")// - .label(5, "SmoothPv")// - .label(6, "Remote"))), - new ModbusRegisterRange(30157, - new UnsignedWordElement(30157, - setupMode = new ModbusReadLongChannel("SetupMode", this)// - .label(0, EssNature.OFF)// - .label(1, EssNature.ON)), - new UnsignedWordElement(30158, - pcsMode = new ModbusReadLongChannel("PcsMode", this)// - .label(0, "Emergency")// - .label(1, "ConsumersPeakPattern")// - .label(2, "Economic")// - .label(3, "Eco")// - .label(4, "Debug")// - .label(5, "SmoothPv")// - .label(6, "Remote")))); - gridMode = new FunctionalReadChannel("GridMode", this, (channels) -> { - ReadChannel state = channels[0]; - try { - if (state.value() == 1L) { - return 0L; - } else { - return 1L; - } - } catch (InvalidValueException e) { - return null; - } - }, systemState).label(0L, OFF_GRID).label(1L, ON_GRID); - apparentPower = new FunctionalReadChannel("ApparentPower", this, (channels) -> { - ReadChannel activePower = channels[0]; - ReadChannel reactivePower = channels[1]; - try { - return ControllerUtils.calculateApparentPower(activePower.value(), reactivePower.value()); - } catch (InvalidValueException e) { - log.error("failed to calculate apparentPower. some value is missing.", e); - } - return 0l; - }, activePower, reactivePower); - - return protokol; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel maxNominalPower() { - return nominalPower; - } - - @Override - public StaticValueChannel capacity() { - // TODO Auto-generated method stub - return null; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.mini; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.device.nature.realtimeclock.RealTimeClockNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.core.utilities.ControllerUtils; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedWordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; + +@ThingInfo(title = "FENECON Mini ESS") +public class FeneconMiniEss extends ModbusDeviceNature implements SymmetricEssNature, RealTimeClockNature { + + /* + * Constructors + */ + public FeneconMiniEss(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + minSoc.addUpdateListener((channel, newValue) -> { + // If chargeSoc was not set -> set it to minSoc minus 2 + if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { + chargeSoc.updateValue((Integer) newValue.get() - 2, false); + } + }); + } + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + // ESS + private StatusBitChannels warning; + private ModbusReadLongChannel allowedCharge; + private ModbusReadLongChannel allowedDischarge; + private ReadChannel gridMode; + private ModbusReadLongChannel soc; + private ModbusReadLongChannel systemState; + private ModbusReadLongChannel activePower; + private ModbusReadLongChannel reactivePower; + private ModbusWriteLongChannel setWorkState; + private ModbusWriteLongChannel setActivePower; + private ModbusWriteLongChannel setReactivePower; + private ReadChannel apparentPower; + // RealTimeClock + private ModbusWriteLongChannel rtcYear; + private ModbusWriteLongChannel rtcMonth; + private ModbusWriteLongChannel rtcDay; + private ModbusWriteLongChannel rtcHour; + private ModbusWriteLongChannel rtcMinute; + private ModbusWriteLongChannel rtcSecond; + private StaticValueChannel nominalPower = new StaticValueChannel("maxNominalPower", this, 3000l) + .unit("VA"); + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 3000L).unit("Wh"); + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public ReadChannel allowedApparent() { + return phaseAllowedApparent; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public WriteChannel rtcYear() { + return rtcYear; + } + + @Override + public WriteChannel rtcMonth() { + return rtcMonth; + } + + @Override + public WriteChannel rtcDay() { + return rtcDay; + } + + @Override + public WriteChannel rtcHour() { + return rtcHour; + } + + @Override + public WriteChannel rtcMinute() { + return rtcMinute; + } + + @Override + public WriteChannel rtcSecond() { + return rtcSecond; + } + + /* + * This Channels + */ + public ModbusReadLongChannel phaseAllowedApparent; + public ModbusReadLongChannel frequencyL3; + public ModbusReadLongChannel frequencyL2; + public ModbusReadLongChannel frequencyL1; + public ModbusReadLongChannel currentL1; + public ModbusReadLongChannel currentL2; + public ModbusReadLongChannel currentL3; + public ModbusReadLongChannel voltageL1; + public ModbusReadLongChannel voltageL2; + public ModbusReadLongChannel voltageL3; + public ModbusReadLongChannel pcsOperationState; + public ModbusReadLongChannel batteryPower; + public ModbusReadLongChannel batteryGroupAlarm; + public ModbusReadLongChannel batteryCurrent; + public ModbusReadLongChannel batteryVoltage; + public ModbusReadLongChannel batteryGroupState; + public ModbusReadLongChannel totalBatteryDischargeEnergy; + public ModbusReadLongChannel totalBatteryChargeEnergy; + public ModbusReadLongChannel workMode; + public ModbusReadLongChannel controlMode; + public ModbusWriteLongChannel setPcsMode; + public ModbusWriteLongChannel setSetupMode; + public ModbusReadLongChannel setupMode; + public ModbusReadLongChannel pcsMode; + public StatusBitChannel pcsAlarm1L1; + public StatusBitChannel pcsAlarm2L1; + public StatusBitChannel pcsFault1L1; + public StatusBitChannel pcsFault2L1; + public StatusBitChannel pcsFault3L1; + public StatusBitChannel pcsAlarm1L2; + public StatusBitChannel pcsAlarm2L2; + public StatusBitChannel pcsFault1L2; + public StatusBitChannel pcsFault2L2; + public StatusBitChannel pcsFault3L2; + public StatusBitChannel pcsAlarm1L3; + public StatusBitChannel pcsAlarm2L3; + public StatusBitChannel pcsFault1L3; + public StatusBitChannel pcsFault2L3; + public StatusBitChannel pcsFault3L3; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + warning = new StatusBitChannels("Warning", this); + + ModbusProtocol protokol = new ModbusProtocol(new ModbusRegisterRange(100, // + new UnsignedWordElement(100, // + systemState = new ModbusReadLongChannel("SystemState", this) // + .label(0, STANDBY) // + .label(1, "Start Off-Grid") // + .label(2, START) // + .label(3, FAULT) // + .label(4, "Off-grid PV")), + new UnsignedWordElement(101, // + controlMode = new ModbusReadLongChannel("ControlMode", this) // + .label(1, "Remote") // + .label(2, "Local")), // + new DummyElement(102, 103), // + new UnsignedDoublewordElement(104, // + totalBatteryChargeEnergy = new ModbusReadLongChannel("TotalBatteryChargeEnergy", this) + .unit("Wh")), // + new UnsignedDoublewordElement(106, // + totalBatteryDischargeEnergy = new ModbusReadLongChannel("TotalBatteryDischargeEnergy", this) + .unit("Wh")), // + new UnsignedWordElement(108, // + batteryGroupState = new ModbusReadLongChannel("BatteryGroupState", this) // + .label(0, "Initial") // + .label(1, "Stop") // + .label(2, "Starting") // + .label(3, "Running") // + .label(4, "Stopping") // + .label(5, "Fail")), + new UnsignedWordElement(109, // + soc = new ModbusReadLongChannel("Soc", this).unit("%").interval(0, 100)), + new UnsignedWordElement(110, // + batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV").multiplier(2)), + new SignedWordElement(111, // + batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA").multiplier(2)), + new SignedWordElement(112, // + batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W")), + new UnsignedWordElement(113, // + batteryGroupAlarm = new ModbusReadLongChannel("BatteryGroupAlarm", this) + .label(1, "Fail, The system should be stopped") // + .label(2, "Common low voltage alarm") // + .label(4, "Common high voltage alarm") // + .label(8, "Charging over current alarm") // + .label(16, "Discharging over current alarm") // + .label(32, "Over temperature alarm")// + .label(64, "Interal communication abnormal")), + new UnsignedWordElement(114, // + pcsOperationState = new ModbusReadLongChannel("PcsOperationState", this) + .label(0, "Self-checking") // + .label(1, "Standby") // + .label(2, "Off grid PV") // + .label(3, "Off grid") // + .label(4, ON_GRID) // + .label(5, "Fail") // + .label(6, "bypass 1") // + .label(7, "bypass 2")), + new DummyElement(115, 117), // + new SignedWordElement(118, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), + new DummyElement(119, 120), new UnsignedWordElement(121, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), + new DummyElement(122, 123), new SignedWordElement(124, // + activePower = new ModbusReadLongChannel("ActivePowerL1", this).unit("W")), + new DummyElement(125, 126), new SignedWordElement(127, // + reactivePower = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var")), + new DummyElement(128, 130), new UnsignedWordElement(131, // + frequencyL1 = new ModbusReadLongChannel("FrequencyL1", this).unit("mHz").multiplier(1)), + new DummyElement(132, 133), new UnsignedWordElement(134, // + phaseAllowedApparent = new ModbusReadLongChannel("PhaseAllowedApparentPower", this).unit("VA")), + new DummyElement(135, 140), new UnsignedWordElement(141, // + allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).unit("W").negate()), + new UnsignedWordElement(142, // + allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W")), + new DummyElement(143, 149), + new UnsignedWordElement(150, pcsAlarm1L1 = warning.channel(new StatusBitChannel("PcsAlarm1L1", this)// + .label(1, "Grid undervoltage") // + .label(2, "Grid overvoltage") // + .label(4, "Grid under frequency") // + .label(8, "Grid over frequency") // + .label(16, "Grid power supply off") // + .label(32, "Grid condition unmeet")// + .label(64, "DC under voltage")// + .label(128, "Input over resistance")// + .label(256, "Combination error")// + .label(512, "Comm with inverter error")// + .label(1024, "Tme error")// + )), new UnsignedWordElement(151, pcsAlarm2L1 = warning.channel(new StatusBitChannel("PcsAlarm2L1", this)// + )), new UnsignedWordElement(152, warning.channel(pcsFault1L1 = new StatusBitChannel("PcsFault1L1", this)// + .label(1, "Control current overload 100%")// + .label(2, "Control current overload 110%")// + .label(4, "Control current overload 150%")// + .label(8, "Control current overload 200%")// + .label(16, "Control current overload 120%")// + .label(32, "Control current overload 300%")// + .label(64, "Control transient load 300%")// + .label(128, "Grid over current")// + .label(256, "Locking waveform too many times")// + .label(512, "Inverter voltage zero drift error")// + .label(1024, "Grid voltage zero drift error")// + .label(2048, "Control current zero drift error")// + .label(4096, "Inverter current zero drift error")// + .label(8192, "Grid current zero drift error")// + .label(16384, "PDP protection")// + .label(32768, "Hardware control current protection")// + )), new UnsignedWordElement(153, warning.channel(pcsFault2L1 = new StatusBitChannel("PcsFault2L1", this)// + .label(1, "Hardware AC volt. protection")// + .label(2, "Hardware DC curr. protection")// + .label(4, "Hardware temperature protection")// + .label(8, "No capturing signal")// + .label(16, "DC overvoltage")// + .label(32, "DC disconnected")// + .label(64, "Inverter undervoltage")// + .label(128, "Inverter overvoltage")// + .label(256, "Current sensor fail")// + .label(512, "Voltage sensor fail")// + .label(1024, "Power uncontrollable")// + .label(2048, "Current uncontrollable")// + .label(4096, "Fan error")// + .label(8192, "Phase lack")// + .label(16384, "Inverter relay fault")// + .label(32768, "Grid relay fault")// + )), new UnsignedWordElement(154, warning.channel(pcsFault3L1 = new StatusBitChannel("PcsFault3L1", this)// + .label(1, "Control panel overtemp")// + .label(2, "Power panel overtemp")// + .label(4, "DC input overcurrent")// + .label(8, "Capacitor overtemp")// + .label(16, "Radiator overtemp")// + .label(32, "Transformer overtemp")// + .label(64, "Combination comm error")// + .label(128, "EEPROM error")// + .label(256, "Load current zero drift error")// + .label(512, "Current limit-R error")// + .label(1024, "Phase sync error")// + .label(2048, "External PV current zero drift error")// + .label(4096, "External grid current zero drift error")// + ))), // + new WriteableModbusRegisterRange(200, // + new UnsignedWordElement(200, setWorkState = new ModbusWriteLongChannel("SetWorkState", this)// + .label(0, "Local control") // + .label(1, START) // "Remote control on grid starting" + .label(2, "Remote control off grid starting") // + .label(3, STOP)// + .label(4, "Emergency Stop"))), + new WriteableModbusRegisterRange(201, // + new SignedWordElement(201, + setActivePower = new ModbusWriteLongChannel("SetActivePower", this).unit("W")), // + new SignedWordElement(202, + setReactivePower = new ModbusWriteLongChannel("SetReactivePower", this).unit("Var"))), // + new WriteableModbusRegisterRange(9014, // + new UnsignedWordElement(9014, rtcYear = new ModbusWriteLongChannel("Year", this)), + new UnsignedWordElement(9015, rtcMonth = new ModbusWriteLongChannel("Month", this)), + new UnsignedWordElement(9016, rtcDay = new ModbusWriteLongChannel("Day", this)), + new UnsignedWordElement(9017, rtcHour = new ModbusWriteLongChannel("Hour", this)), + new UnsignedWordElement(9018, rtcMinute = new ModbusWriteLongChannel("Minute", this)), + new UnsignedWordElement(9019, rtcSecond = new ModbusWriteLongChannel("Second", this))), + new WriteableModbusRegisterRange(30558, + new UnsignedWordElement(30558, + setSetupMode = new ModbusWriteLongChannel("SetSetupMode", this).label(0, EssNature.OFF) + .label(1, EssNature.ON))), + new WriteableModbusRegisterRange(30559, + new UnsignedWordElement(30559, setPcsMode = new ModbusWriteLongChannel("SetPcsMode", this)// + .label(0, "Emergency")// + .label(1, "ConsumersPeakPattern")// + .label(2, "Economic")// + .label(3, "Eco")// + .label(4, "Debug")// + .label(5, "SmoothPv")// + .label(6, "Remote"))), + new ModbusRegisterRange(30157, + new UnsignedWordElement(30157, setupMode = new ModbusReadLongChannel("SetupMode", this)// + .label(0, EssNature.OFF)// + .label(1, EssNature.ON)), + new UnsignedWordElement(30158, pcsMode = new ModbusReadLongChannel("PcsMode", this)// + .label(0, "Emergency")// + .label(1, "ConsumersPeakPattern")// + .label(2, "Economic")// + .label(3, "Eco")// + .label(4, "Debug")// + .label(5, "SmoothPv")// + .label(6, "Remote")))); + gridMode = new FunctionalReadChannel("GridMode", this, (channels) -> { + ReadChannel state = channels[0]; + try { + if (state.value() == 1L) { + return 0L; + } else { + return 1L; + } + } catch (InvalidValueException e) { + return null; + } + }, systemState).label(0L, OFF_GRID).label(1L, ON_GRID); + apparentPower = new FunctionalReadChannel("ApparentPower", this, (channels) -> { + ReadChannel activePower = channels[0]; + ReadChannel reactivePower = channels[1]; + try { + return ControllerUtils.calculateApparentPower(activePower.value(), reactivePower.value()); + } catch (InvalidValueException e) { + log.error("failed to calculate apparentPower. some value is missing.", e); + } + return 0l; + }, activePower, reactivePower); + + return protokol; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel maxNominalPower() { + return nominalPower; + } + + @Override + public StaticValueChannel capacity() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97.java b/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97.java index 573c4b584d7..2b3eb45352a 100644 --- a/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97.java +++ b/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.pqplus; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "PQ Plus UMD 97") -public class PqPlusUMD97 extends ModbusDevice { - - /* - * Constructors - */ - public PqPlusUMD97() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = PqPlusUMD97Meter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.pqplus; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "PQ Plus UMD 97") +public class PqPlusUMD97 extends ModbusDevice { + + /* + * Constructors + */ + public PqPlusUMD97(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = PqPlusUMD97Meter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97Meter.java b/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97Meter.java index c00480ef748..da8f53ea73d 100644 --- a/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97Meter.java +++ b/edge/src/io/openems/impl/device/pqplus/PqPlusUMD97Meter.java @@ -1,171 +1,169 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.pqplus; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.FloatElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "PQ Plus UMD 97 Meter") -public class PqPlusUMD97Meter extends ModbusDeviceNature implements SymmetricMeterNature { - - /* - * Constructors - */ - public PqPlusUMD97Meter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel activePower; - public ModbusReadLongChannel activePowerL1; - public ModbusReadLongChannel activePowerL2; - public ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel apparentPower; - private ModbusReadLongChannel reactivePower; - public ModbusReadLongChannel reactivePowerL1; - public ModbusReadLongChannel reactivePowerL2; - public ModbusReadLongChannel reactivePowerL3; - public ModbusReadLongChannel current; - private ModbusReadLongChannel frequency; - public ModbusReadLongChannel voltageL1; - public ModbusReadLongChannel voltageL2; - public ModbusReadLongChannel voltageL3; - public ModbusReadLongChannel currentL1; - public ModbusReadLongChannel currentL2; - public ModbusReadLongChannel currentL3; - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( // - new ModbusRegisterRange(19000, // - new FloatElement(19000, voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV")) - .multiplier(3), - new FloatElement(19002, voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV")) - .multiplier(3), - new FloatElement(19004, voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV")) - .multiplier(3), - new DummyElement(19006, 19011), - new FloatElement(19012, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")).multiplier(3), - new FloatElement(19014, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")).multiplier(3), - new FloatElement(19016, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")).multiplier(3), - new FloatElement(19018, // - current = new ModbusReadLongChannel("Current", this).unit("mA")).multiplier(3), - new FloatElement(19020, // - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this) // - .unit("W")), // - new FloatElement(19022, // - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this) // - .unit("W")), // - new FloatElement(19024, // - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this) // - .unit("W")), // - new FloatElement(19026, // - activePower = new ModbusReadLongChannel("ActivePower", this) // - .unit("W")), // - new DummyElement(19028, 19033), - new FloatElement(19034, // - apparentPower = new ModbusReadLongChannel("ApparentPower", this) // - .unit("VA")), // - new FloatElement(19036, // - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this) // - .unit("Var")), // - new FloatElement(19038, // - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this) // - .unit("Var")), // - new FloatElement(19040, // - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this) // - .unit("Var")), // - new FloatElement(19042, // - reactivePower = new ModbusReadLongChannel("ReactivePower", this) // - .unit("Var")), // - new DummyElement(19044, 19049), - new FloatElement(19050, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHz")) // - .multiplier(3))); - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.pqplus; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.FloatElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "PQ Plus UMD 97 Meter") +public class PqPlusUMD97Meter extends ModbusDeviceNature implements SymmetricMeterNature { + + /* + * Constructors + */ + public PqPlusUMD97Meter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel activePower; + public ModbusReadLongChannel activePowerL1; + public ModbusReadLongChannel activePowerL2; + public ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel apparentPower; + private ModbusReadLongChannel reactivePower; + public ModbusReadLongChannel reactivePowerL1; + public ModbusReadLongChannel reactivePowerL2; + public ModbusReadLongChannel reactivePowerL3; + public ModbusReadLongChannel current; + private ModbusReadLongChannel frequency; + public ModbusReadLongChannel voltageL1; + public ModbusReadLongChannel voltageL2; + public ModbusReadLongChannel voltageL3; + public ModbusReadLongChannel currentL1; + public ModbusReadLongChannel currentL2; + public ModbusReadLongChannel currentL3; + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( // + new ModbusRegisterRange(19000, // + new FloatElement(19000, voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV")) + .multiplier(3), + new FloatElement(19002, voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV")) + .multiplier(3), + new FloatElement(19004, voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV")) + .multiplier(3), + new DummyElement(19006, 19011), new FloatElement(19012, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")).multiplier(3), + new FloatElement(19014, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")).multiplier(3), + new FloatElement(19016, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")).multiplier(3), + new FloatElement(19018, // + current = new ModbusReadLongChannel("Current", this).unit("mA")).multiplier(3), + new FloatElement(19020, // + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this) // + .unit("W")), // + new FloatElement(19022, // + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this) // + .unit("W")), // + new FloatElement(19024, // + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this) // + .unit("W")), // + new FloatElement(19026, // + activePower = new ModbusReadLongChannel("ActivePower", this) // + .unit("W")), // + new DummyElement(19028, 19033), new FloatElement(19034, // + apparentPower = new ModbusReadLongChannel("ApparentPower", this) // + .unit("VA")), // + new FloatElement(19036, // + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this) // + .unit("Var")), // + new FloatElement(19038, // + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this) // + .unit("Var")), // + new FloatElement(19040, // + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this) // + .unit("Var")), // + new FloatElement(19042, // + reactivePower = new ModbusReadLongChannel("ReactivePower", this) // + .unit("Var")), // + new DummyElement(19044, 19049), new FloatElement(19050, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHz")) // + .multiplier(3))); + } + +} diff --git a/edge/src/io/openems/impl/device/pro/FeneconPro.java b/edge/src/io/openems/impl/device/pro/FeneconPro.java index 0b2bcce992f..6352a0e40a6 100644 --- a/edge/src/io/openems/impl/device/pro/FeneconPro.java +++ b/edge/src/io/openems/impl/device/pro/FeneconPro.java @@ -1,71 +1,72 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.pro; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "FENECON Pro") -public class FeneconPro extends ModbusDevice { - - /* - * Constructors - */ - public FeneconPro() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconProEss.class) - public final ConfigChannel ess = new ConfigChannel<>("ess", this); - - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = FeneconProPvMeter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - public String toString() { - return "FeneconPro [ess=" + ess + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (ess.valueOptional().isPresent()) { - natures.add(ess.valueOptional().get()); - } - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.pro; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "FENECON Pro") +public class FeneconPro extends ModbusDevice { + + /* + * Constructors + */ + public FeneconPro(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = FeneconProEss.class) + public final ConfigChannel ess = new ConfigChannel<>("ess", this); + + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = FeneconProPvMeter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + public String toString() { + return "FeneconPro [ess=" + ess + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (ess.valueOptional().isPresent()) { + natures.add(ess.valueOptional().get()); + } + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } +} diff --git a/edge/src/io/openems/impl/device/pro/FeneconProEss.java b/edge/src/io/openems/impl/device/pro/FeneconProEss.java index 285604f3633..bc574f0e493 100644 --- a/edge/src/io/openems/impl/device/pro/FeneconProEss.java +++ b/edge/src/io/openems/impl/device/pro/FeneconProEss.java @@ -1,819 +1,820 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.pro; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.ess.AsymmetricEssNature; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.realtimeclock.RealTimeClockNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedWordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; - -@ThingInfo(title = "FENECON Pro ESS") -public class FeneconProEss extends ModbusDeviceNature implements AsymmetricEssNature, RealTimeClockNature { - - /* - * Constructors - */ - public FeneconProEss(String thingId) throws ConfigException { - super(thingId); - minSoc.addUpdateListener((channel, newValue) -> { - // If chargeSoc was not set -> set it to minSoc minus 2 - if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { - chargeSoc.updateValue((Integer) newValue.get() - 2, false); - } - }); - } - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - // ESS - private StatusBitChannels warning; - private ModbusReadLongChannel allowedCharge; - private ModbusReadLongChannel allowedDischarge; - private ReadChannel gridMode; - private ModbusReadLongChannel soc; - private ModbusReadLongChannel systemState; - private ModbusReadLongChannel activePowerL1; - private ModbusReadLongChannel activePowerL2; - private ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel reactivePowerL1; - private ModbusReadLongChannel reactivePowerL2; - private ModbusReadLongChannel reactivePowerL3; - private ModbusWriteLongChannel setWorkState; - private ModbusWriteLongChannel setActivePowerL1; - private ModbusWriteLongChannel setActivePowerL2; - private ModbusWriteLongChannel setActivePowerL3; - private ModbusWriteLongChannel setReactivePowerL1; - private ModbusWriteLongChannel setReactivePowerL2; - private ModbusWriteLongChannel setReactivePowerL3; - private ReadChannel allowedApparent; - // RealTimeClock - private ModbusWriteLongChannel rtcYear; - private ModbusWriteLongChannel rtcMonth; - private ModbusWriteLongChannel rtcDay; - private ModbusWriteLongChannel rtcHour; - private ModbusWriteLongChannel rtcMinute; - private ModbusWriteLongChannel rtcSecond; - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 12000L).unit("Wh"); - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 9000L) - .unit("VA"); - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public WriteChannel setActivePowerL1() { - return setActivePowerL1; - } - - @Override - public WriteChannel setActivePowerL2() { - return setActivePowerL2; - } - - @Override - public WriteChannel setActivePowerL3() { - return setActivePowerL3; - } - - @Override - public WriteChannel setReactivePowerL1() { - return setReactivePowerL1; - } - - @Override - public WriteChannel setReactivePowerL2() { - return setReactivePowerL2; - } - - @Override - public WriteChannel setReactivePowerL3() { - return setReactivePowerL3; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public WriteChannel rtcYear() { - return rtcYear; - } - - @Override - public WriteChannel rtcMonth() { - return rtcMonth; - } - - @Override - public WriteChannel rtcDay() { - return rtcDay; - } - - @Override - public WriteChannel rtcHour() { - return rtcHour; - } - - @Override - public WriteChannel rtcMinute() { - return rtcMinute; - } - - @Override - public WriteChannel rtcSecond() { - return rtcSecond; - } - - /* - * This Channels - */ - public ModbusReadLongChannel phaseAllowedApparent; - public ModbusReadLongChannel frequencyL3; - public ModbusReadLongChannel frequencyL2; - public ModbusReadLongChannel frequencyL1; - public ModbusReadLongChannel currentL1; - public ModbusReadLongChannel currentL2; - public ModbusReadLongChannel currentL3; - public ModbusReadLongChannel voltageL1; - public ModbusReadLongChannel voltageL2; - public ModbusReadLongChannel voltageL3; - public ModbusReadLongChannel pcsOperationState; - public ModbusReadLongChannel batteryPower; - public ModbusReadLongChannel batteryGroupAlarm; - public ModbusReadLongChannel batteryCurrent; - public ModbusReadLongChannel batteryVoltage; - public ModbusReadLongChannel batteryVoltageSection1; - public ModbusReadLongChannel batteryVoltageSection2; - public ModbusReadLongChannel batteryVoltageSection3; - public ModbusReadLongChannel batteryVoltageSection4; - public ModbusReadLongChannel batteryVoltageSection5; - public ModbusReadLongChannel batteryVoltageSection6; - public ModbusReadLongChannel batteryVoltageSection7; - public ModbusReadLongChannel batteryVoltageSection8; - public ModbusReadLongChannel batteryVoltageSection9; - public ModbusReadLongChannel batteryVoltageSection10; - public ModbusReadLongChannel batteryVoltageSection11; - public ModbusReadLongChannel batteryVoltageSection12; - public ModbusReadLongChannel batteryVoltageSection13; - public ModbusReadLongChannel batteryVoltageSection14; - public ModbusReadLongChannel batteryVoltageSection15; - public ModbusReadLongChannel batteryVoltageSection16; - public ModbusReadLongChannel batteryTemperatureSection1; - public ModbusReadLongChannel batteryTemperatureSection2; - public ModbusReadLongChannel batteryTemperatureSection3; - public ModbusReadLongChannel batteryTemperatureSection4; - public ModbusReadLongChannel batteryTemperatureSection5; - public ModbusReadLongChannel batteryTemperatureSection6; - public ModbusReadLongChannel batteryTemperatureSection7; - public ModbusReadLongChannel batteryTemperatureSection8; - public ModbusReadLongChannel batteryTemperatureSection9; - public ModbusReadLongChannel batteryTemperatureSection10; - public ModbusReadLongChannel batteryTemperatureSection11; - public ModbusReadLongChannel batteryTemperatureSection12; - public ModbusReadLongChannel batteryTemperatureSection13; - public ModbusReadLongChannel batteryTemperatureSection14; - public ModbusReadLongChannel batteryTemperatureSection15; - public ModbusReadLongChannel batteryTemperatureSection16; - public ModbusReadLongChannel batteryGroupState; - public ModbusReadLongChannel totalBatteryDischargeEnergy; - public ModbusReadLongChannel totalBatteryChargeEnergy; - public ModbusReadLongChannel workMode; - public ModbusReadLongChannel controlMode; - public ModbusWriteLongChannel setPcsMode; - public ModbusWriteLongChannel setSetupMode; - public ModbusReadLongChannel setupMode; - public ModbusReadLongChannel pcsMode; - public StatusBitChannel pcsAlarm1L1; - public StatusBitChannel pcsAlarm2L1; - public StatusBitChannel pcsFault1L1; - public StatusBitChannel pcsFault2L1; - public StatusBitChannel pcsFault3L1; - public StatusBitChannel pcsAlarm1L2; - public StatusBitChannel pcsAlarm2L2; - public StatusBitChannel pcsFault1L2; - public StatusBitChannel pcsFault2L2; - public StatusBitChannel pcsFault3L2; - public StatusBitChannel pcsAlarm1L3; - public StatusBitChannel pcsAlarm2L3; - public StatusBitChannel pcsFault1L3; - public StatusBitChannel pcsFault2L3; - public StatusBitChannel pcsFault3L3; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - warning = new StatusBitChannels("Warning", this); - - ModbusProtocol protokol = new ModbusProtocol(new ModbusRegisterRange(100, // - new UnsignedWordElement(100, // - systemState = new ModbusReadLongChannel("SystemState", this) // - .label(0, STANDBY) // - .label(1, "Start Off-Grid") // - .label(2, START) // - .label(3, FAULT) // - .label(4, "Off-grid PV")), - new UnsignedWordElement(101, // - controlMode = new ModbusReadLongChannel("ControlMode", this) // - .label(1, "Remote") // - .label(2, "Local")), // - new UnsignedWordElement(102, // - workMode = new ModbusReadLongChannel("WorkMode", this) // - .label(2, "Economy") // - .label(6, "Remote") // - .label(8, "Timing")), // - new DummyElement(103), // - new UnsignedDoublewordElement(104, // - totalBatteryChargeEnergy = new ModbusReadLongChannel("TotalBatteryChargeEnergy", this) - .unit("Wh")), // - new UnsignedDoublewordElement(106, // - totalBatteryDischargeEnergy = new ModbusReadLongChannel("TotalBatteryDischargeEnergy", this) - .unit("Wh")), // - new UnsignedWordElement(108, // - batteryGroupState = new ModbusReadLongChannel("BatteryGroupState", this) // - .label(0, "Initial") // - .label(1, "Stop") // - .label(2, "Starting") // - .label(3, "Running") // - .label(4, "Stopping") // - .label(5, "Fail")), - new UnsignedWordElement(109, // - soc = new ModbusReadLongChannel("Soc", this).unit("%").interval(0, 100)), - new UnsignedWordElement(110, // - batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV").multiplier(2)), - new SignedWordElement(111, // - batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA").multiplier(2)), - new SignedWordElement(112, // - batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W")), - new UnsignedWordElement(113, // - batteryGroupAlarm = new ModbusReadLongChannel("BatteryGroupAlarm", this) - .label(1, "Fail, The system should be stopped") // - .label(2, "Common low voltage alarm") // - .label(4, "Common high voltage alarm") // - .label(8, "Charging over current alarm") // - .label(16, "Discharging over current alarm") // - .label(32, "Over temperature alarm")// - .label(64, "Interal communication abnormal")), - new UnsignedWordElement(114, // - pcsOperationState = new ModbusReadLongChannel("PcsOperationState", this) - .label(0, "Self-checking") // - .label(1, "Standby") // - .label(2, "Off grid PV") // - .label(3, "Off grid") // - .label(4, ON_GRID) // - .label(5, "Fail") // - .label(6, "bypass 1") // - .label(7, "bypass 2")), - new DummyElement(115, 117), // - new SignedWordElement(118, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), - new SignedWordElement(119, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA").multiplier(2)), - new SignedWordElement(120, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA").multiplier(2)), - new UnsignedWordElement(121, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), - new UnsignedWordElement(122, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), - new UnsignedWordElement(123, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2)), - new SignedWordElement(124, // - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W")), - new SignedWordElement(125, // - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W")), - new SignedWordElement(126, // - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W")), - new SignedWordElement(127, // - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var")), - new SignedWordElement(128, // - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("var")), - new SignedWordElement(129, // - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("var")), - new DummyElement(130), new UnsignedWordElement(131, // - frequencyL1 = new ModbusReadLongChannel("FrequencyL1", this).unit("mHz").multiplier(1)), - new UnsignedWordElement(132, // - frequencyL2 = new ModbusReadLongChannel("FrequencyL2", this).unit("mHz").multiplier(1)), - new UnsignedWordElement(133, // - frequencyL3 = new ModbusReadLongChannel("FrequencyL3", this).unit("mHz").multiplier(1)), - new UnsignedWordElement(134, // - phaseAllowedApparent = new ModbusReadLongChannel("PhaseAllowedApparentPower", this).unit("VA")), - new DummyElement(135, 140), new UnsignedWordElement(141, // - allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).negate().unit("W")), - new UnsignedWordElement(142, // - allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W"))), - new ModbusRegisterRange(150, - new UnsignedWordElement(150, - pcsAlarm1L1 = warning.channel(new StatusBitChannel("PcsAlarm1L1", this)// - .label(1, "Grid undervoltage") // - .label(2, "Grid overvoltage") // - .label(4, "Grid under frequency") // - .label(8, "Grid over frequency") // - .label(16, "Grid power supply off") // - .label(32, "Grid condition unmeet")// - .label(64, "DC under voltage")// - .label(128, "Input over resistance")// - .label(256, "Combination error")// - .label(512, "Comm with inverter error")// - .label(1024, "Tme error")// - )), new UnsignedWordElement(151, - pcsAlarm2L1 = warning.channel(new StatusBitChannel("PcsAlarm2L1", this)// - )), - new UnsignedWordElement(152, - warning.channel(pcsFault1L1 = new StatusBitChannel("PcsFault1L1", this)// - .label(1, "Control current overload 100%")// - .label(2, "Control current overload 110%")// - .label(4, "Control current overload 150%")// - .label(8, "Control current overload 200%")// - .label(16, "Control current overload 120%")// - .label(32, "Control current overload 300%")// - .label(64, "Control transient load 300%")// - .label(128, "Grid over current")// - .label(256, "Locking waveform too many times")// - .label(512, "Inverter voltage zero drift error")// - .label(1024, "Grid voltage zero drift error")// - .label(2048, "Control current zero drift error")// - .label(4096, "Inverter current zero drift error")// - .label(8192, "Grid current zero drift error")// - .label(16384, "PDP protection")// - .label(32768, "Hardware control current protection")// - )), - new UnsignedWordElement(153, - warning.channel(pcsFault2L1 = new StatusBitChannel("PcsFault2L1", this)// - .label(1, "Hardware AC volt. protection")// - .label(2, "Hardware DC curr. protection")// - .label(4, "Hardware temperature protection")// - .label(8, "No capturing signal")// - .label(16, "DC overvoltage")// - .label(32, "DC disconnected")// - .label(64, "Inverter undervoltage")// - .label(128, "Inverter overvoltage")// - .label(256, "Current sensor fail")// - .label(512, "Voltage sensor fail")// - .label(1024, "Power uncontrollable")// - .label(2048, "Current uncontrollable")// - .label(4096, "Fan error")// - .label(8192, "Phase lack")// - .label(16384, "Inverter relay fault")// - .label(32768, "Grid relay fault")// - )), - new UnsignedWordElement(154, - warning.channel(pcsFault3L1 = new StatusBitChannel("PcsFault3L1", this)// - .label(1, "Control panel overtemp")// - .label(2, "Power panel overtemp")// - .label(4, "DC input overcurrent")// - .label(8, "Capacitor overtemp")// - .label(16, "Radiator overtemp")// - .label(32, "Transformer overtemp")// - .label(64, "Combination comm error")// - .label(128, "EEPROM error")// - .label(256, "Load current zero drift error")// - .label(512, "Current limit-R error")// - .label(1024, "Phase sync error")// - .label(2048, "External PV current zero drift error")// - .label(4096, "External grid current zero drift error")// - )), - new UnsignedWordElement(155, - warning.channel(pcsAlarm1L2 = new StatusBitChannel("PcsAlarm1L2", this)// - .label(1, "Grid undervoltage") // - .label(2, "Grid overvoltage") // - .label(4, "Grid under frequency") // - .label(8, "Grid over frequency") // - .label(16, "Grid power supply off") // - .label(32, "Grid condition unmeet")// - .label(64, "DC under voltage")// - .label(128, "Input over resistance")// - .label(256, "Combination error")// - .label(512, "Comm with inverter error")// - .label(1024, "Tme error")// - )), new UnsignedWordElement(156, - warning.channel(pcsAlarm2L2 = new StatusBitChannel("PcsAlarm2L2", this)// - )), - new UnsignedWordElement(157, - warning.channel(pcsFault1L2 = new StatusBitChannel("PcsFault1L2", this)// - .label(1, "Control current overload 100%")// - .label(2, "Control current overload 110%")// - .label(4, "Control current overload 150%")// - .label(8, "Control current overload 200%")// - .label(16, "Control current overload 120%")// - .label(32, "Control current overload 300%")// - .label(64, "Control transient load 300%")// - .label(128, "Grid over current")// - .label(256, "Locking waveform too many times")// - .label(512, "Inverter voltage zero drift error")// - .label(1024, "Grid voltage zero drift error")// - .label(2048, "Control current zero drift error")// - .label(4096, "Inverter current zero drift error")// - .label(8192, "Grid current zero drift error")// - .label(16384, "PDP protection")// - .label(32768, "Hardware control current protection")// - )), - new UnsignedWordElement(158, - warning.channel(pcsFault2L2 = new StatusBitChannel("PcsFault2L2", this)// - .label(1, "Hardware AC volt. protection")// - .label(2, "Hardware DC curr. protection")// - .label(4, "Hardware temperature protection")// - .label(8, "No capturing signal")// - .label(16, "DC overvoltage")// - .label(32, "DC disconnected")// - .label(64, "Inverter undervoltage")// - .label(128, "Inverter overvoltage")// - .label(256, "Current sensor fail")// - .label(512, "Voltage sensor fail")// - .label(1024, "Power uncontrollable")// - .label(2048, "Current uncontrollable")// - .label(4096, "Fan error")// - .label(8192, "Phase lack")// - .label(16384, "Inverter relay fault")// - .label(32768, "Grid relay fault")// - )), - new UnsignedWordElement(159, - warning.channel(pcsFault3L2 = new StatusBitChannel("PcsFault3L2", this)// - .label(1, "Control panel overtemp")// - .label(2, "Power panel overtemp")// - .label(4, "DC input overcurrent")// - .label(8, "Capacitor overtemp")// - .label(16, "Radiator overtemp")// - .label(32, "Transformer overtemp")// - .label(64, "Combination comm error")// - .label(128, "EEPROM error")// - .label(256, "Load current zero drift error")// - .label(512, "Current limit-R error")// - .label(1024, "Phase sync error")// - .label(2048, "External PV current zero drift error")// - .label(4096, "External grid current zero drift error")// - )), - new UnsignedWordElement(160, - warning.channel(pcsAlarm1L3 = new StatusBitChannel("PcsAlarm1L3", this)// - .label(1, "Grid undervoltage") // - .label(2, "Grid overvoltage") // - .label(4, "Grid under frequency") // - .label(8, "Grid over frequency") // - .label(16, "Grid power supply off") // - .label(32, "Grid condition unmeet")// - .label(64, "DC under voltage")// - .label(128, "Input over resistance")// - .label(256, "Combination error")// - .label(512, "Comm with inverter error")// - .label(1024, "Tme error")// - )), new UnsignedWordElement(161, - warning.channel(pcsAlarm2L3 = new StatusBitChannel("PcsAlarm2L3", this)// - )), - new UnsignedWordElement(162, - warning.channel(pcsFault1L3 = new StatusBitChannel("PcsFault1L3", this)// - .label(1, "Control current overload 100%")// - .label(2, "Control current overload 110%")// - .label(4, "Control current overload 150%")// - .label(8, "Control current overload 200%")// - .label(16, "Control current overload 120%")// - .label(32, "Control current overload 300%")// - .label(64, "Control transient load 300%")// - .label(128, "Grid over current")// - .label(256, "Locking waveform too many times")// - .label(512, "Inverter voltage zero drift error")// - .label(1024, "Grid voltage zero drift error")// - .label(2048, "Control current zero drift error")// - .label(4096, "Inverter current zero drift error")// - .label(8192, "Grid current zero drift error")// - .label(16384, "PDP protection")// - .label(32768, "Hardware control current protection")// - )), - new UnsignedWordElement(163, - warning.channel(pcsFault2L3 = new StatusBitChannel("PcsFault2L3", this)// - .label(1, "Hardware AC volt. protection")// - .label(2, "Hardware DC curr. protection")// - .label(4, "Hardware temperature protection")// - .label(8, "No capturing signal")// - .label(16, "DC overvoltage")// - .label(32, "DC disconnected")// - .label(64, "Inverter undervoltage")// - .label(128, "Inverter overvoltage")// - .label(256, "Current sensor fail")// - .label(512, "Voltage sensor fail")// - .label(1024, "Power uncontrollable")// - .label(2048, "Current uncontrollable")// - .label(4096, "Fan error")// - .label(8192, "Phase lack")// - .label(16384, "Inverter relay fault")// - .label(32768, "Grid relay fault")// - )), - new UnsignedWordElement(164, - warning.channel(pcsFault3L3 = new StatusBitChannel("PcsFault3L3", this)// - .label(1, "Control panel overtemp")// - .label(2, "Power panel overtemp")// - .label(4, "DC input overcurrent")// - .label(8, "Capacitor overtemp")// - .label(16, "Radiator overtemp")// - .label(32, "Transformer overtemp")// - .label(64, "Combination comm error")// - .label(128, "EEPROM error")// - .label(256, "Load current zero drift error")// - .label(512, "Current limit-R error")// - .label(1024, "Phase sync error")// - .label(2048, "External PV current zero drift error")// - .label(4096, "External grid current zero drift error")// - ))), // - new WriteableModbusRegisterRange(200, // - new UnsignedWordElement(200, setWorkState = new ModbusWriteLongChannel("SetWorkState", this)// - .label(0, "Local control") // - .label(1, START) // "Remote control on grid starting" - .label(2, "Remote control off grid starting") // - .label(3, STOP)// - .label(4, "Emergency Stop"))), - new WriteableModbusRegisterRange(206, // - new SignedWordElement(206, - setActivePowerL1 = new ModbusWriteLongChannel("SetActivePowerL1", this).unit("W")), // - new SignedWordElement(207, - setReactivePowerL1 = new ModbusWriteLongChannel("SetReactivePowerL1", this) - .unit("Var")), // - new SignedWordElement(208, - setActivePowerL2 = new ModbusWriteLongChannel("SetActivePowerL2", this).unit("W")), // - new SignedWordElement(209, - setReactivePowerL2 = new ModbusWriteLongChannel("SetReactivePowerL2", this) - .unit("Var")), // - new SignedWordElement(210, - setActivePowerL3 = new ModbusWriteLongChannel("SetActivePowerL3", this).unit("W")), // - new SignedWordElement(211, - setReactivePowerL3 = new ModbusWriteLongChannel("SetReactivePowerL3", this).unit("Var")// - )), // - new ModbusRegisterRange(3020, new UnsignedWordElement(3020, - batteryVoltageSection1 = new ModbusReadLongChannel("BatteryVoltageSection1", this).unit("mV")), - new UnsignedWordElement(3021, - batteryVoltageSection2 = new ModbusReadLongChannel("BatteryVoltageSection2", this) - .unit("mV")), - new UnsignedWordElement(3022, - batteryVoltageSection3 = new ModbusReadLongChannel("BatteryVoltageSection3", this) - .unit("mV")), - new UnsignedWordElement(3023, - batteryVoltageSection4 = new ModbusReadLongChannel("BatteryVoltageSection4", this) - .unit("mV")), - new UnsignedWordElement(3024, - batteryVoltageSection5 = new ModbusReadLongChannel("BatteryVoltageSection5", this) - .unit("mV")), - new UnsignedWordElement(3025, - batteryVoltageSection6 = new ModbusReadLongChannel("BatteryVoltageSection6", this) - .unit("mV")), - new UnsignedWordElement(3026, - batteryVoltageSection7 = new ModbusReadLongChannel("BatteryVoltageSection7", this) - .unit("mV")), - new UnsignedWordElement(3027, - batteryVoltageSection8 = new ModbusReadLongChannel("BatteryVoltageSection8", this) - .unit("mV")), - new UnsignedWordElement(3028, - batteryVoltageSection9 = new ModbusReadLongChannel("BatteryVoltageSection9", this) - .unit("mV")), - new UnsignedWordElement(3029, - batteryVoltageSection10 = new ModbusReadLongChannel("BatteryVoltageSection10", this) - .unit("mV")), - new UnsignedWordElement(3030, - batteryVoltageSection11 = new ModbusReadLongChannel("BatteryVoltageSection11", this) - .unit("mV")), - new UnsignedWordElement(3031, - batteryVoltageSection12 = new ModbusReadLongChannel("BatteryVoltageSection12", this) - .unit("mV")), - new UnsignedWordElement(3032, - batteryVoltageSection13 = new ModbusReadLongChannel("BatteryVoltageSection13", this) - .unit("mV")), - new UnsignedWordElement(3033, - batteryVoltageSection14 = new ModbusReadLongChannel("BatteryVoltageSection14", this) - .unit("mV")), - new UnsignedWordElement(3034, - batteryVoltageSection15 = new ModbusReadLongChannel("BatteryVoltageSection15", this) - .unit("mV")), - new UnsignedWordElement(3035, - batteryVoltageSection16 = new ModbusReadLongChannel("BatteryVoltageSection16", this) - .unit("mV")), - new UnsignedWordElement(3036, - batteryTemperatureSection1 = new ModbusReadLongChannel("BatteryTemperatureSection1", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3037, - batteryTemperatureSection2 = new ModbusReadLongChannel("BatteryTemperatureSection2", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3038, - batteryTemperatureSection3 = new ModbusReadLongChannel("BatteryTemperatureSection3", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3039, - batteryTemperatureSection4 = new ModbusReadLongChannel("BatteryTemperatureSection4", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3040, - batteryTemperatureSection5 = new ModbusReadLongChannel("BatteryTemperatureSection5", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3041, - batteryTemperatureSection6 = new ModbusReadLongChannel("BatteryTemperatureSection6", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3042, - batteryTemperatureSection7 = new ModbusReadLongChannel("BatteryTemperatureSection7", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3043, - batteryTemperatureSection8 = new ModbusReadLongChannel("BatteryTemperatureSection8", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3044, - batteryTemperatureSection9 = new ModbusReadLongChannel("BatteryTemperatureSection9", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3045, - batteryTemperatureSection10 = new ModbusReadLongChannel("BatteryTemperatureSection10", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3046, - batteryTemperatureSection11 = new ModbusReadLongChannel("BatteryTemperatureSection11", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3047, - batteryTemperatureSection12 = new ModbusReadLongChannel("BatteryTemperatureSection12", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3048, - batteryTemperatureSection13 = new ModbusReadLongChannel("BatteryTemperatureSection13", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3049, - batteryTemperatureSection14 = new ModbusReadLongChannel("BatteryTemperatureSection14", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3050, - batteryTemperatureSection15 = new ModbusReadLongChannel("BatteryTemperatureSection15", - this).unit("°C").delta(-40L)), - new UnsignedWordElement(3051, - batteryTemperatureSection16 = new ModbusReadLongChannel("BatteryTemperatureSection16", - this).unit("°C").delta(-40L))), - new WriteableModbusRegisterRange(9014, // - new UnsignedWordElement(9014, rtcYear = new ModbusWriteLongChannel("Year", this)), - new UnsignedWordElement(9015, rtcMonth = new ModbusWriteLongChannel("Month", this)), - new UnsignedWordElement(9016, rtcDay = new ModbusWriteLongChannel("Day", this)), - new UnsignedWordElement(9017, rtcHour = new ModbusWriteLongChannel("Hour", this)), - new UnsignedWordElement(9018, rtcMinute = new ModbusWriteLongChannel("Minute", this)), - new UnsignedWordElement(9019, rtcSecond = new ModbusWriteLongChannel("Second", this))), - new WriteableModbusRegisterRange(30558, - new UnsignedWordElement(30558, - setSetupMode = new ModbusWriteLongChannel("SetSetupMode", this).label(0, EssNature.OFF) - .label(1, EssNature.ON))), - new WriteableModbusRegisterRange(30559, - new UnsignedWordElement(30559, setPcsMode = new ModbusWriteLongChannel("SetPcsMode", this)// - .label(0, "Emergency")// - .label(1, "ConsumersPeakPattern")// - .label(2, "Economic")// - .label(3, "Eco")// - .label(4, "Debug")// - .label(5, "SmoothPv")// - .label(6, "Remote"))), - new ModbusRegisterRange(30157, - new UnsignedWordElement(30157, setupMode = new ModbusReadLongChannel("SetupMode", this)// - .label(0, EssNature.OFF)// - .label(1, EssNature.ON)), - new UnsignedWordElement(30158, pcsMode = new ModbusReadLongChannel("PcsMode", this)// - .label(0, "Emergency")// - .label(1, "ConsumersPeakPattern")// - .label(2, "Economic")// - .label(3, "Eco")// - .label(4, "Debug")// - .label(5, "SmoothPv")// - .label(6, "Remote")))); - gridMode = new FunctionalReadChannel("GridMode", this, (channels) -> { - ReadChannel state = channels[0]; - try { - if (state.value() == 1L) { - return 0L; - } else { - return 1L; - } - } catch (InvalidValueException e) { - return null; - } - }, systemState).label(0L, OFF_GRID).label(1L, ON_GRID); - allowedApparent = new FunctionalReadChannel("AllowedApparent", this, (channels) -> { - ReadChannel apparent = channels[0]; - try { - return apparent.value() * 3; - } catch (InvalidValueException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return 0l; - }, phaseAllowedApparent); - - return protokol; - } - - @Override - public StaticValueChannel capacity() { - return capacity; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.pro; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.realtimeclock.RealTimeClockNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedWordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; + +@ThingInfo(title = "FENECON Pro ESS") +public class FeneconProEss extends ModbusDeviceNature implements AsymmetricEssNature, RealTimeClockNature { + + /* + * Constructors + */ + public FeneconProEss(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + minSoc.addUpdateListener((channel, newValue) -> { + // If chargeSoc was not set -> set it to minSoc minus 2 + if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { + chargeSoc.updateValue((Integer) newValue.get() - 2, false); + } + }); + } + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + // ESS + private StatusBitChannels warning; + private ModbusReadLongChannel allowedCharge; + private ModbusReadLongChannel allowedDischarge; + private ReadChannel gridMode; + private ModbusReadLongChannel soc; + private ModbusReadLongChannel systemState; + private ModbusReadLongChannel activePowerL1; + private ModbusReadLongChannel activePowerL2; + private ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel reactivePowerL1; + private ModbusReadLongChannel reactivePowerL2; + private ModbusReadLongChannel reactivePowerL3; + private ModbusWriteLongChannel setWorkState; + private ModbusWriteLongChannel setActivePowerL1; + private ModbusWriteLongChannel setActivePowerL2; + private ModbusWriteLongChannel setActivePowerL3; + private ModbusWriteLongChannel setReactivePowerL1; + private ModbusWriteLongChannel setReactivePowerL2; + private ModbusWriteLongChannel setReactivePowerL3; + private ReadChannel allowedApparent; + // RealTimeClock + private ModbusWriteLongChannel rtcYear; + private ModbusWriteLongChannel rtcMonth; + private ModbusWriteLongChannel rtcDay; + private ModbusWriteLongChannel rtcHour; + private ModbusWriteLongChannel rtcMinute; + private ModbusWriteLongChannel rtcSecond; + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 12000L).unit("Wh"); + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 9000L) + .unit("VA"); + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public WriteChannel setActivePowerL1() { + return setActivePowerL1; + } + + @Override + public WriteChannel setActivePowerL2() { + return setActivePowerL2; + } + + @Override + public WriteChannel setActivePowerL3() { + return setActivePowerL3; + } + + @Override + public WriteChannel setReactivePowerL1() { + return setReactivePowerL1; + } + + @Override + public WriteChannel setReactivePowerL2() { + return setReactivePowerL2; + } + + @Override + public WriteChannel setReactivePowerL3() { + return setReactivePowerL3; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public WriteChannel rtcYear() { + return rtcYear; + } + + @Override + public WriteChannel rtcMonth() { + return rtcMonth; + } + + @Override + public WriteChannel rtcDay() { + return rtcDay; + } + + @Override + public WriteChannel rtcHour() { + return rtcHour; + } + + @Override + public WriteChannel rtcMinute() { + return rtcMinute; + } + + @Override + public WriteChannel rtcSecond() { + return rtcSecond; + } + + /* + * This Channels + */ + public ModbusReadLongChannel phaseAllowedApparent; + public ModbusReadLongChannel frequencyL3; + public ModbusReadLongChannel frequencyL2; + public ModbusReadLongChannel frequencyL1; + public ModbusReadLongChannel currentL1; + public ModbusReadLongChannel currentL2; + public ModbusReadLongChannel currentL3; + public ModbusReadLongChannel voltageL1; + public ModbusReadLongChannel voltageL2; + public ModbusReadLongChannel voltageL3; + public ModbusReadLongChannel pcsOperationState; + public ModbusReadLongChannel batteryPower; + public ModbusReadLongChannel batteryGroupAlarm; + public ModbusReadLongChannel batteryCurrent; + public ModbusReadLongChannel batteryVoltage; + public ModbusReadLongChannel batteryVoltageSection1; + public ModbusReadLongChannel batteryVoltageSection2; + public ModbusReadLongChannel batteryVoltageSection3; + public ModbusReadLongChannel batteryVoltageSection4; + public ModbusReadLongChannel batteryVoltageSection5; + public ModbusReadLongChannel batteryVoltageSection6; + public ModbusReadLongChannel batteryVoltageSection7; + public ModbusReadLongChannel batteryVoltageSection8; + public ModbusReadLongChannel batteryVoltageSection9; + public ModbusReadLongChannel batteryVoltageSection10; + public ModbusReadLongChannel batteryVoltageSection11; + public ModbusReadLongChannel batteryVoltageSection12; + public ModbusReadLongChannel batteryVoltageSection13; + public ModbusReadLongChannel batteryVoltageSection14; + public ModbusReadLongChannel batteryVoltageSection15; + public ModbusReadLongChannel batteryVoltageSection16; + public ModbusReadLongChannel batteryTemperatureSection1; + public ModbusReadLongChannel batteryTemperatureSection2; + public ModbusReadLongChannel batteryTemperatureSection3; + public ModbusReadLongChannel batteryTemperatureSection4; + public ModbusReadLongChannel batteryTemperatureSection5; + public ModbusReadLongChannel batteryTemperatureSection6; + public ModbusReadLongChannel batteryTemperatureSection7; + public ModbusReadLongChannel batteryTemperatureSection8; + public ModbusReadLongChannel batteryTemperatureSection9; + public ModbusReadLongChannel batteryTemperatureSection10; + public ModbusReadLongChannel batteryTemperatureSection11; + public ModbusReadLongChannel batteryTemperatureSection12; + public ModbusReadLongChannel batteryTemperatureSection13; + public ModbusReadLongChannel batteryTemperatureSection14; + public ModbusReadLongChannel batteryTemperatureSection15; + public ModbusReadLongChannel batteryTemperatureSection16; + public ModbusReadLongChannel batteryGroupState; + public ModbusReadLongChannel totalBatteryDischargeEnergy; + public ModbusReadLongChannel totalBatteryChargeEnergy; + public ModbusReadLongChannel workMode; + public ModbusReadLongChannel controlMode; + public ModbusWriteLongChannel setPcsMode; + public ModbusWriteLongChannel setSetupMode; + public ModbusReadLongChannel setupMode; + public ModbusReadLongChannel pcsMode; + public StatusBitChannel pcsAlarm1L1; + public StatusBitChannel pcsAlarm2L1; + public StatusBitChannel pcsFault1L1; + public StatusBitChannel pcsFault2L1; + public StatusBitChannel pcsFault3L1; + public StatusBitChannel pcsAlarm1L2; + public StatusBitChannel pcsAlarm2L2; + public StatusBitChannel pcsFault1L2; + public StatusBitChannel pcsFault2L2; + public StatusBitChannel pcsFault3L2; + public StatusBitChannel pcsAlarm1L3; + public StatusBitChannel pcsAlarm2L3; + public StatusBitChannel pcsFault1L3; + public StatusBitChannel pcsFault2L3; + public StatusBitChannel pcsFault3L3; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + warning = new StatusBitChannels("Warning", this); + + ModbusProtocol protokol = new ModbusProtocol(new ModbusRegisterRange(100, // + new UnsignedWordElement(100, // + systemState = new ModbusReadLongChannel("SystemState", this) // + .label(0, STANDBY) // + .label(1, "Start Off-Grid") // + .label(2, START) // + .label(3, FAULT) // + .label(4, "Off-grid PV")), + new UnsignedWordElement(101, // + controlMode = new ModbusReadLongChannel("ControlMode", this) // + .label(1, "Remote") // + .label(2, "Local")), // + new UnsignedWordElement(102, // + workMode = new ModbusReadLongChannel("WorkMode", this) // + .label(2, "Economy") // + .label(6, "Remote") // + .label(8, "Timing")), // + new DummyElement(103), // + new UnsignedDoublewordElement(104, // + totalBatteryChargeEnergy = new ModbusReadLongChannel("TotalBatteryChargeEnergy", this) + .unit("Wh")), // + new UnsignedDoublewordElement(106, // + totalBatteryDischargeEnergy = new ModbusReadLongChannel("TotalBatteryDischargeEnergy", this) + .unit("Wh")), // + new UnsignedWordElement(108, // + batteryGroupState = new ModbusReadLongChannel("BatteryGroupState", this) // + .label(0, "Initial") // + .label(1, "Stop") // + .label(2, "Starting") // + .label(3, "Running") // + .label(4, "Stopping") // + .label(5, "Fail")), + new UnsignedWordElement(109, // + soc = new ModbusReadLongChannel("Soc", this).unit("%").interval(0, 100)), + new UnsignedWordElement(110, // + batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV").multiplier(2)), + new SignedWordElement(111, // + batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA").multiplier(2)), + new SignedWordElement(112, // + batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W")), + new UnsignedWordElement(113, // + batteryGroupAlarm = new ModbusReadLongChannel("BatteryGroupAlarm", this) + .label(1, "Fail, The system should be stopped") // + .label(2, "Common low voltage alarm") // + .label(4, "Common high voltage alarm") // + .label(8, "Charging over current alarm") // + .label(16, "Discharging over current alarm") // + .label(32, "Over temperature alarm")// + .label(64, "Interal communication abnormal")), + new UnsignedWordElement(114, // + pcsOperationState = new ModbusReadLongChannel("PcsOperationState", this) + .label(0, "Self-checking") // + .label(1, "Standby") // + .label(2, "Off grid PV") // + .label(3, "Off grid") // + .label(4, ON_GRID) // + .label(5, "Fail") // + .label(6, "bypass 1") // + .label(7, "bypass 2")), + new DummyElement(115, 117), // + new SignedWordElement(118, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), + new SignedWordElement(119, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA").multiplier(2)), + new SignedWordElement(120, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA").multiplier(2)), + new UnsignedWordElement(121, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), + new UnsignedWordElement(122, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), + new UnsignedWordElement(123, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2)), + new SignedWordElement(124, // + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W")), + new SignedWordElement(125, // + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W")), + new SignedWordElement(126, // + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W")), + new SignedWordElement(127, // + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var")), + new SignedWordElement(128, // + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("var")), + new SignedWordElement(129, // + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("var")), + new DummyElement(130), new UnsignedWordElement(131, // + frequencyL1 = new ModbusReadLongChannel("FrequencyL1", this).unit("mHz").multiplier(1)), + new UnsignedWordElement(132, // + frequencyL2 = new ModbusReadLongChannel("FrequencyL2", this).unit("mHz").multiplier(1)), + new UnsignedWordElement(133, // + frequencyL3 = new ModbusReadLongChannel("FrequencyL3", this).unit("mHz").multiplier(1)), + new UnsignedWordElement(134, // + phaseAllowedApparent = new ModbusReadLongChannel("PhaseAllowedApparentPower", this).unit("VA")), + new DummyElement(135, 140), new UnsignedWordElement(141, // + allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).negate().unit("W")), + new UnsignedWordElement(142, // + allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W"))), + new ModbusRegisterRange(150, + new UnsignedWordElement(150, + pcsAlarm1L1 = warning.channel(new StatusBitChannel("PcsAlarm1L1", this)// + .label(1, "Grid undervoltage") // + .label(2, "Grid overvoltage") // + .label(4, "Grid under frequency") // + .label(8, "Grid over frequency") // + .label(16, "Grid power supply off") // + .label(32, "Grid condition unmeet")// + .label(64, "DC under voltage")// + .label(128, "Input over resistance")// + .label(256, "Combination error")// + .label(512, "Comm with inverter error")// + .label(1024, "Tme error")// + )), new UnsignedWordElement(151, + pcsAlarm2L1 = warning.channel(new StatusBitChannel("PcsAlarm2L1", this)// + )), + new UnsignedWordElement(152, + warning.channel(pcsFault1L1 = new StatusBitChannel("PcsFault1L1", this)// + .label(1, "Control current overload 100%")// + .label(2, "Control current overload 110%")// + .label(4, "Control current overload 150%")// + .label(8, "Control current overload 200%")// + .label(16, "Control current overload 120%")// + .label(32, "Control current overload 300%")// + .label(64, "Control transient load 300%")// + .label(128, "Grid over current")// + .label(256, "Locking waveform too many times")// + .label(512, "Inverter voltage zero drift error")// + .label(1024, "Grid voltage zero drift error")// + .label(2048, "Control current zero drift error")// + .label(4096, "Inverter current zero drift error")// + .label(8192, "Grid current zero drift error")// + .label(16384, "PDP protection")// + .label(32768, "Hardware control current protection")// + )), + new UnsignedWordElement(153, + warning.channel(pcsFault2L1 = new StatusBitChannel("PcsFault2L1", this)// + .label(1, "Hardware AC volt. protection")// + .label(2, "Hardware DC curr. protection")// + .label(4, "Hardware temperature protection")// + .label(8, "No capturing signal")// + .label(16, "DC overvoltage")// + .label(32, "DC disconnected")// + .label(64, "Inverter undervoltage")// + .label(128, "Inverter overvoltage")// + .label(256, "Current sensor fail")// + .label(512, "Voltage sensor fail")// + .label(1024, "Power uncontrollable")// + .label(2048, "Current uncontrollable")// + .label(4096, "Fan error")// + .label(8192, "Phase lack")// + .label(16384, "Inverter relay fault")// + .label(32768, "Grid relay fault")// + )), + new UnsignedWordElement(154, + warning.channel(pcsFault3L1 = new StatusBitChannel("PcsFault3L1", this)// + .label(1, "Control panel overtemp")// + .label(2, "Power panel overtemp")// + .label(4, "DC input overcurrent")// + .label(8, "Capacitor overtemp")// + .label(16, "Radiator overtemp")// + .label(32, "Transformer overtemp")// + .label(64, "Combination comm error")// + .label(128, "EEPROM error")// + .label(256, "Load current zero drift error")// + .label(512, "Current limit-R error")// + .label(1024, "Phase sync error")// + .label(2048, "External PV current zero drift error")// + .label(4096, "External grid current zero drift error")// + )), + new UnsignedWordElement(155, + warning.channel(pcsAlarm1L2 = new StatusBitChannel("PcsAlarm1L2", this)// + .label(1, "Grid undervoltage") // + .label(2, "Grid overvoltage") // + .label(4, "Grid under frequency") // + .label(8, "Grid over frequency") // + .label(16, "Grid power supply off") // + .label(32, "Grid condition unmeet")// + .label(64, "DC under voltage")// + .label(128, "Input over resistance")// + .label(256, "Combination error")// + .label(512, "Comm with inverter error")// + .label(1024, "Tme error")// + )), new UnsignedWordElement(156, + warning.channel(pcsAlarm2L2 = new StatusBitChannel("PcsAlarm2L2", this)// + )), + new UnsignedWordElement(157, + warning.channel(pcsFault1L2 = new StatusBitChannel("PcsFault1L2", this)// + .label(1, "Control current overload 100%")// + .label(2, "Control current overload 110%")// + .label(4, "Control current overload 150%")// + .label(8, "Control current overload 200%")// + .label(16, "Control current overload 120%")// + .label(32, "Control current overload 300%")// + .label(64, "Control transient load 300%")// + .label(128, "Grid over current")// + .label(256, "Locking waveform too many times")// + .label(512, "Inverter voltage zero drift error")// + .label(1024, "Grid voltage zero drift error")// + .label(2048, "Control current zero drift error")// + .label(4096, "Inverter current zero drift error")// + .label(8192, "Grid current zero drift error")// + .label(16384, "PDP protection")// + .label(32768, "Hardware control current protection")// + )), + new UnsignedWordElement(158, + warning.channel(pcsFault2L2 = new StatusBitChannel("PcsFault2L2", this)// + .label(1, "Hardware AC volt. protection")// + .label(2, "Hardware DC curr. protection")// + .label(4, "Hardware temperature protection")// + .label(8, "No capturing signal")// + .label(16, "DC overvoltage")// + .label(32, "DC disconnected")// + .label(64, "Inverter undervoltage")// + .label(128, "Inverter overvoltage")// + .label(256, "Current sensor fail")// + .label(512, "Voltage sensor fail")// + .label(1024, "Power uncontrollable")// + .label(2048, "Current uncontrollable")// + .label(4096, "Fan error")// + .label(8192, "Phase lack")// + .label(16384, "Inverter relay fault")// + .label(32768, "Grid relay fault")// + )), + new UnsignedWordElement(159, + warning.channel(pcsFault3L2 = new StatusBitChannel("PcsFault3L2", this)// + .label(1, "Control panel overtemp")// + .label(2, "Power panel overtemp")// + .label(4, "DC input overcurrent")// + .label(8, "Capacitor overtemp")// + .label(16, "Radiator overtemp")// + .label(32, "Transformer overtemp")// + .label(64, "Combination comm error")// + .label(128, "EEPROM error")// + .label(256, "Load current zero drift error")// + .label(512, "Current limit-R error")// + .label(1024, "Phase sync error")// + .label(2048, "External PV current zero drift error")// + .label(4096, "External grid current zero drift error")// + )), + new UnsignedWordElement(160, + warning.channel(pcsAlarm1L3 = new StatusBitChannel("PcsAlarm1L3", this)// + .label(1, "Grid undervoltage") // + .label(2, "Grid overvoltage") // + .label(4, "Grid under frequency") // + .label(8, "Grid over frequency") // + .label(16, "Grid power supply off") // + .label(32, "Grid condition unmeet")// + .label(64, "DC under voltage")// + .label(128, "Input over resistance")// + .label(256, "Combination error")// + .label(512, "Comm with inverter error")// + .label(1024, "Tme error")// + )), new UnsignedWordElement(161, + warning.channel(pcsAlarm2L3 = new StatusBitChannel("PcsAlarm2L3", this)// + )), + new UnsignedWordElement(162, + warning.channel(pcsFault1L3 = new StatusBitChannel("PcsFault1L3", this)// + .label(1, "Control current overload 100%")// + .label(2, "Control current overload 110%")// + .label(4, "Control current overload 150%")// + .label(8, "Control current overload 200%")// + .label(16, "Control current overload 120%")// + .label(32, "Control current overload 300%")// + .label(64, "Control transient load 300%")// + .label(128, "Grid over current")// + .label(256, "Locking waveform too many times")// + .label(512, "Inverter voltage zero drift error")// + .label(1024, "Grid voltage zero drift error")// + .label(2048, "Control current zero drift error")// + .label(4096, "Inverter current zero drift error")// + .label(8192, "Grid current zero drift error")// + .label(16384, "PDP protection")// + .label(32768, "Hardware control current protection")// + )), + new UnsignedWordElement(163, + warning.channel(pcsFault2L3 = new StatusBitChannel("PcsFault2L3", this)// + .label(1, "Hardware AC volt. protection")// + .label(2, "Hardware DC curr. protection")// + .label(4, "Hardware temperature protection")// + .label(8, "No capturing signal")// + .label(16, "DC overvoltage")// + .label(32, "DC disconnected")// + .label(64, "Inverter undervoltage")// + .label(128, "Inverter overvoltage")// + .label(256, "Current sensor fail")// + .label(512, "Voltage sensor fail")// + .label(1024, "Power uncontrollable")// + .label(2048, "Current uncontrollable")// + .label(4096, "Fan error")// + .label(8192, "Phase lack")// + .label(16384, "Inverter relay fault")// + .label(32768, "Grid relay fault")// + )), + new UnsignedWordElement(164, + warning.channel(pcsFault3L3 = new StatusBitChannel("PcsFault3L3", this)// + .label(1, "Control panel overtemp")// + .label(2, "Power panel overtemp")// + .label(4, "DC input overcurrent")// + .label(8, "Capacitor overtemp")// + .label(16, "Radiator overtemp")// + .label(32, "Transformer overtemp")// + .label(64, "Combination comm error")// + .label(128, "EEPROM error")// + .label(256, "Load current zero drift error")// + .label(512, "Current limit-R error")// + .label(1024, "Phase sync error")// + .label(2048, "External PV current zero drift error")// + .label(4096, "External grid current zero drift error")// + ))), // + new WriteableModbusRegisterRange(200, // + new UnsignedWordElement(200, setWorkState = new ModbusWriteLongChannel("SetWorkState", this)// + .label(0, "Local control") // + .label(1, START) // "Remote control on grid starting" + .label(2, "Remote control off grid starting") // + .label(3, STOP)// + .label(4, "Emergency Stop"))), + new WriteableModbusRegisterRange(206, // + new SignedWordElement(206, + setActivePowerL1 = new ModbusWriteLongChannel("SetActivePowerL1", this).unit("W")), // + new SignedWordElement(207, + setReactivePowerL1 = new ModbusWriteLongChannel("SetReactivePowerL1", this) + .unit("Var")), // + new SignedWordElement(208, + setActivePowerL2 = new ModbusWriteLongChannel("SetActivePowerL2", this).unit("W")), // + new SignedWordElement(209, + setReactivePowerL2 = new ModbusWriteLongChannel("SetReactivePowerL2", this) + .unit("Var")), // + new SignedWordElement(210, + setActivePowerL3 = new ModbusWriteLongChannel("SetActivePowerL3", this).unit("W")), // + new SignedWordElement(211, + setReactivePowerL3 = new ModbusWriteLongChannel("SetReactivePowerL3", this).unit("Var")// + )), // + new ModbusRegisterRange(3020, new UnsignedWordElement(3020, + batteryVoltageSection1 = new ModbusReadLongChannel("BatteryVoltageSection1", this).unit("mV")), + new UnsignedWordElement(3021, + batteryVoltageSection2 = new ModbusReadLongChannel("BatteryVoltageSection2", this) + .unit("mV")), + new UnsignedWordElement(3022, + batteryVoltageSection3 = new ModbusReadLongChannel("BatteryVoltageSection3", this) + .unit("mV")), + new UnsignedWordElement(3023, + batteryVoltageSection4 = new ModbusReadLongChannel("BatteryVoltageSection4", this) + .unit("mV")), + new UnsignedWordElement(3024, + batteryVoltageSection5 = new ModbusReadLongChannel("BatteryVoltageSection5", this) + .unit("mV")), + new UnsignedWordElement(3025, + batteryVoltageSection6 = new ModbusReadLongChannel("BatteryVoltageSection6", this) + .unit("mV")), + new UnsignedWordElement(3026, + batteryVoltageSection7 = new ModbusReadLongChannel("BatteryVoltageSection7", this) + .unit("mV")), + new UnsignedWordElement(3027, + batteryVoltageSection8 = new ModbusReadLongChannel("BatteryVoltageSection8", this) + .unit("mV")), + new UnsignedWordElement(3028, + batteryVoltageSection9 = new ModbusReadLongChannel("BatteryVoltageSection9", this) + .unit("mV")), + new UnsignedWordElement(3029, + batteryVoltageSection10 = new ModbusReadLongChannel("BatteryVoltageSection10", this) + .unit("mV")), + new UnsignedWordElement(3030, + batteryVoltageSection11 = new ModbusReadLongChannel("BatteryVoltageSection11", this) + .unit("mV")), + new UnsignedWordElement(3031, + batteryVoltageSection12 = new ModbusReadLongChannel("BatteryVoltageSection12", this) + .unit("mV")), + new UnsignedWordElement(3032, + batteryVoltageSection13 = new ModbusReadLongChannel("BatteryVoltageSection13", this) + .unit("mV")), + new UnsignedWordElement(3033, + batteryVoltageSection14 = new ModbusReadLongChannel("BatteryVoltageSection14", this) + .unit("mV")), + new UnsignedWordElement(3034, + batteryVoltageSection15 = new ModbusReadLongChannel("BatteryVoltageSection15", this) + .unit("mV")), + new UnsignedWordElement(3035, + batteryVoltageSection16 = new ModbusReadLongChannel("BatteryVoltageSection16", this) + .unit("mV")), + new UnsignedWordElement(3036, + batteryTemperatureSection1 = new ModbusReadLongChannel("BatteryTemperatureSection1", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3037, + batteryTemperatureSection2 = new ModbusReadLongChannel("BatteryTemperatureSection2", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3038, + batteryTemperatureSection3 = new ModbusReadLongChannel("BatteryTemperatureSection3", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3039, + batteryTemperatureSection4 = new ModbusReadLongChannel("BatteryTemperatureSection4", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3040, + batteryTemperatureSection5 = new ModbusReadLongChannel("BatteryTemperatureSection5", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3041, + batteryTemperatureSection6 = new ModbusReadLongChannel("BatteryTemperatureSection6", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3042, + batteryTemperatureSection7 = new ModbusReadLongChannel("BatteryTemperatureSection7", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3043, + batteryTemperatureSection8 = new ModbusReadLongChannel("BatteryTemperatureSection8", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3044, + batteryTemperatureSection9 = new ModbusReadLongChannel("BatteryTemperatureSection9", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3045, + batteryTemperatureSection10 = new ModbusReadLongChannel("BatteryTemperatureSection10", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3046, + batteryTemperatureSection11 = new ModbusReadLongChannel("BatteryTemperatureSection11", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3047, + batteryTemperatureSection12 = new ModbusReadLongChannel("BatteryTemperatureSection12", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3048, + batteryTemperatureSection13 = new ModbusReadLongChannel("BatteryTemperatureSection13", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3049, + batteryTemperatureSection14 = new ModbusReadLongChannel("BatteryTemperatureSection14", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3050, + batteryTemperatureSection15 = new ModbusReadLongChannel("BatteryTemperatureSection15", + this).unit("°C").delta(-40L)), + new UnsignedWordElement(3051, + batteryTemperatureSection16 = new ModbusReadLongChannel("BatteryTemperatureSection16", + this).unit("°C").delta(-40L))), + new WriteableModbusRegisterRange(9014, // + new UnsignedWordElement(9014, rtcYear = new ModbusWriteLongChannel("Year", this)), + new UnsignedWordElement(9015, rtcMonth = new ModbusWriteLongChannel("Month", this)), + new UnsignedWordElement(9016, rtcDay = new ModbusWriteLongChannel("Day", this)), + new UnsignedWordElement(9017, rtcHour = new ModbusWriteLongChannel("Hour", this)), + new UnsignedWordElement(9018, rtcMinute = new ModbusWriteLongChannel("Minute", this)), + new UnsignedWordElement(9019, rtcSecond = new ModbusWriteLongChannel("Second", this))), + new WriteableModbusRegisterRange(30558, + new UnsignedWordElement(30558, + setSetupMode = new ModbusWriteLongChannel("SetSetupMode", this).label(0, EssNature.OFF) + .label(1, EssNature.ON))), + new WriteableModbusRegisterRange(30559, + new UnsignedWordElement(30559, setPcsMode = new ModbusWriteLongChannel("SetPcsMode", this)// + .label(0, "Emergency")// + .label(1, "ConsumersPeakPattern")// + .label(2, "Economic")// + .label(3, "Eco")// + .label(4, "Debug")// + .label(5, "SmoothPv")// + .label(6, "Remote"))), + new ModbusRegisterRange(30157, + new UnsignedWordElement(30157, setupMode = new ModbusReadLongChannel("SetupMode", this)// + .label(0, EssNature.OFF)// + .label(1, EssNature.ON)), + new UnsignedWordElement(30158, pcsMode = new ModbusReadLongChannel("PcsMode", this)// + .label(0, "Emergency")// + .label(1, "ConsumersPeakPattern")// + .label(2, "Economic")// + .label(3, "Eco")// + .label(4, "Debug")// + .label(5, "SmoothPv")// + .label(6, "Remote")))); + gridMode = new FunctionalReadChannel("GridMode", this, (channels) -> { + ReadChannel state = channels[0]; + try { + if (state.value() == 1L) { + return 0L; + } else { + return 1L; + } + } catch (InvalidValueException e) { + return null; + } + }, systemState).label(0L, OFF_GRID).label(1L, ON_GRID); + allowedApparent = new FunctionalReadChannel("AllowedApparent", this, (channels) -> { + ReadChannel apparent = channels[0]; + try { + return apparent.value() * 3; + } catch (InvalidValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return 0l; + }, phaseAllowedApparent); + + return protokol; + } + + @Override + public StaticValueChannel capacity() { + return capacity; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + +} diff --git a/edge/src/io/openems/impl/device/pro/FeneconProPvMeter.java b/edge/src/io/openems/impl/device/pro/FeneconProPvMeter.java index 90f32b76aaf..8a23afe0c03 100644 --- a/edge/src/io/openems/impl/device/pro/FeneconProPvMeter.java +++ b/edge/src/io/openems/impl/device/pro/FeneconProPvMeter.java @@ -1,277 +1,278 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.pro; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadChannel; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "FENECON Pro Meter") -public class FeneconProPvMeter extends ModbusDeviceNature implements AsymmetricMeterNature, SymmetricMeterNature { - - /* - * Constructors - */ - public FeneconProPvMeter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - private ReadChannel activePowerL1; - private ReadChannel activePowerL2; - private ReadChannel activePowerL3; - private ReadChannel reactivePowerL1 = new ReadChannel("ReactivePowerL1", this); - private ReadChannel reactivePowerL2 = new ReadChannel("ReactivePowerL2", this); - private ReadChannel reactivePowerL3 = new ReadChannel("ReactivePowerL3", this); - private ModbusReadLongChannel voltageL1; - private ModbusReadLongChannel voltageL2; - private ModbusReadLongChannel voltageL3; - private ReadChannel currentL1 = new ReadChannel<>("currentL1", this); - private ReadChannel currentL2 = new ReadChannel<>("currentL2", this); - private ReadChannel currentL3 = new ReadChannel<>("currentL3", this); - private ReadChannel activePower; - private ReadChannel reactivePower; - private ReadChannel apparentPower = new ReadChannel<>("apparentPower", this); - private ReadChannel frequency = new ReadChannel<>("frequency", this); - private ModbusReadChannel orginalActivePowerL1; - private ModbusReadChannel orginalActivePowerL2; - private ModbusReadChannel orginalActivePowerL3; - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public ReadChannel currentL1() { - return currentL1; - } - - @Override - public ReadChannel currentL2() { - return currentL2; - } - - @Override - public ReadChannel currentL3() { - return currentL3; - } - - @Override - public ReadChannel voltageL1() { - return voltageL1; - } - - @Override - public ReadChannel voltageL2() { - return voltageL2; - } - - @Override - public ReadChannel voltageL3() { - return voltageL3; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - /* - * This Channels - */ - public ModbusReadLongChannel activeEnergyL1; - public ModbusReadLongChannel activeEnergyL2; - public ModbusReadLongChannel activeEnergyL3; - - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - ModbusProtocol protocol = new ModbusProtocol( // - new ModbusRegisterRange(121, new UnsignedWordElement(121, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), - new UnsignedWordElement(122, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), - new UnsignedWordElement(123, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2))), - new ModbusRegisterRange(2035, // - new UnsignedDoublewordElement(2035, // - activeEnergyL1 = new ModbusReadLongChannel("ActiveEnergyL1", this) - .unit("Wh").multiplier(2)), - new DummyElement(2037, 2065), new UnsignedWordElement(2066, // - orginalActivePowerL1 = new ModbusReadLongChannel("OriginalActivePowerL1", this) - .unit("W").delta(10000L))), - new ModbusRegisterRange(2135, // - new UnsignedDoublewordElement(2135, // - activeEnergyL2 = new ModbusReadLongChannel("ActiveEnergyL2", this) - .unit("Wh").multiplier(2)), - new DummyElement(2137, 2165), new UnsignedWordElement(2166, // - orginalActivePowerL2 = new ModbusReadLongChannel("OriginalActivePowerL2", this) - .unit("W").delta(10000L))), - new ModbusRegisterRange(2235, // - new UnsignedDoublewordElement(2235, // - activeEnergyL3 = new ModbusReadLongChannel("ActiveEnergyL3", this) - .unit("Wh").multiplier(2)), - new DummyElement(2237, 2265), new UnsignedWordElement(2266, // - orginalActivePowerL3 = new ModbusReadLongChannel("OriginalActivePowerL3", this) - .unit("W").delta(10000L)))); - activePowerL1 = new FunctionalReadChannel("ActivePowerL1", this, (channels) -> { - ReadChannel power = channels[0]; - try { - if (power.value() >= 0) { - return power.value(); - } else { - return 0L; - } - } catch (InvalidValueException e) { - return null; - } - }, orginalActivePowerL1); - activePowerL2 = new FunctionalReadChannel("ActivePowerL2", this, (channels) -> { - ReadChannel power = channels[0]; - try { - if (power.value() >= 0) { - return power.value(); - } else { - return 0L; - } - } catch (InvalidValueException e) { - return null; - } - }, orginalActivePowerL2); - activePowerL3 = new FunctionalReadChannel("ActivePowerL3", this, (channels) -> { - ReadChannel power = channels[0]; - try { - if (power.value() >= 0) { - return power.value(); - } else { - return 0L; - } - } catch (InvalidValueException e) { - return null; - } - }, orginalActivePowerL3); - activePower = new FunctionalReadChannel("ActivePower", this, (channels) -> { - ReadChannel L1 = channels[0]; - ReadChannel L2 = channels[1]; - ReadChannel L3 = channels[2]; - try { - return L1.value() + L2.value() + L3.value(); - } catch (InvalidValueException e) { - return null; - } - }, activePowerL1, activePowerL2, activePowerL3); - reactivePower = new FunctionalReadChannel("ReactivePower", this, (channels) -> { - ReadChannel L1 = channels[0]; - ReadChannel L2 = channels[1]; - ReadChannel L3 = channels[2]; - try { - return L1.value() + L2.value() + L3.value(); - } catch (InvalidValueException e) { - return null; - } - }, reactivePowerL1, reactivePowerL2, reactivePowerL3); - return protocol; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.pro; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadChannel; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "FENECON Pro Meter") +public class FeneconProPvMeter extends ModbusDeviceNature implements AsymmetricMeterNature, SymmetricMeterNature { + + /* + * Constructors + */ + public FeneconProPvMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + private ReadChannel activePowerL1; + private ReadChannel activePowerL2; + private ReadChannel activePowerL3; + private ReadChannel reactivePowerL1 = new ReadChannel("ReactivePowerL1", this); + private ReadChannel reactivePowerL2 = new ReadChannel("ReactivePowerL2", this); + private ReadChannel reactivePowerL3 = new ReadChannel("ReactivePowerL3", this); + private ModbusReadLongChannel voltageL1; + private ModbusReadLongChannel voltageL2; + private ModbusReadLongChannel voltageL3; + private ReadChannel currentL1 = new ReadChannel<>("currentL1", this); + private ReadChannel currentL2 = new ReadChannel<>("currentL2", this); + private ReadChannel currentL3 = new ReadChannel<>("currentL3", this); + private ReadChannel activePower; + private ReadChannel reactivePower; + private ReadChannel apparentPower = new ReadChannel<>("apparentPower", this); + private ReadChannel frequency = new ReadChannel<>("frequency", this); + private ModbusReadChannel orginalActivePowerL1; + private ModbusReadChannel orginalActivePowerL2; + private ModbusReadChannel orginalActivePowerL3; + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + /* + * This Channels + */ + public ModbusReadLongChannel activeEnergyL1; + public ModbusReadLongChannel activeEnergyL2; + public ModbusReadLongChannel activeEnergyL3; + + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + ModbusProtocol protocol = new ModbusProtocol( // + new ModbusRegisterRange(121, new UnsignedWordElement(121, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(2)), + new UnsignedWordElement(122, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(2)), + new UnsignedWordElement(123, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(2))), + new ModbusRegisterRange(2035, // + new UnsignedDoublewordElement(2035, // + activeEnergyL1 = new ModbusReadLongChannel("ActiveEnergyL1", this) + .unit("Wh").multiplier(2)), + new DummyElement(2037, 2065), new UnsignedWordElement(2066, // + orginalActivePowerL1 = new ModbusReadLongChannel("OriginalActivePowerL1", this) + .unit("W").delta(10000L))), + new ModbusRegisterRange(2135, // + new UnsignedDoublewordElement(2135, // + activeEnergyL2 = new ModbusReadLongChannel("ActiveEnergyL2", this) + .unit("Wh").multiplier(2)), + new DummyElement(2137, 2165), new UnsignedWordElement(2166, // + orginalActivePowerL2 = new ModbusReadLongChannel("OriginalActivePowerL2", this) + .unit("W").delta(10000L))), + new ModbusRegisterRange(2235, // + new UnsignedDoublewordElement(2235, // + activeEnergyL3 = new ModbusReadLongChannel("ActiveEnergyL3", this) + .unit("Wh").multiplier(2)), + new DummyElement(2237, 2265), new UnsignedWordElement(2266, // + orginalActivePowerL3 = new ModbusReadLongChannel("OriginalActivePowerL3", this) + .unit("W").delta(10000L)))); + activePowerL1 = new FunctionalReadChannel("ActivePowerL1", this, (channels) -> { + ReadChannel power = channels[0]; + try { + if (power.value() >= 0) { + return power.value(); + } else { + return 0L; + } + } catch (InvalidValueException e) { + return null; + } + }, orginalActivePowerL1); + activePowerL2 = new FunctionalReadChannel("ActivePowerL2", this, (channels) -> { + ReadChannel power = channels[0]; + try { + if (power.value() >= 0) { + return power.value(); + } else { + return 0L; + } + } catch (InvalidValueException e) { + return null; + } + }, orginalActivePowerL2); + activePowerL3 = new FunctionalReadChannel("ActivePowerL3", this, (channels) -> { + ReadChannel power = channels[0]; + try { + if (power.value() >= 0) { + return power.value(); + } else { + return 0L; + } + } catch (InvalidValueException e) { + return null; + } + }, orginalActivePowerL3); + activePower = new FunctionalReadChannel("ActivePower", this, (channels) -> { + ReadChannel L1 = channels[0]; + ReadChannel L2 = channels[1]; + ReadChannel L3 = channels[2]; + try { + return L1.value() + L2.value() + L3.value(); + } catch (InvalidValueException e) { + return null; + } + }, activePowerL1, activePowerL2, activePowerL3); + reactivePower = new FunctionalReadChannel("ReactivePower", this, (channels) -> { + ReadChannel L1 = channels[0]; + ReadChannel L2 = channels[1]; + ReadChannel L3 = channels[2]; + try { + return L1.value() + L2.value() + L3.value(); + } catch (InvalidValueException e) { + return null; + } + }, reactivePowerL1, reactivePowerL2, reactivePowerL3); + return protocol; + } +} diff --git a/edge/src/io/openems/impl/device/refu/Refu.java b/edge/src/io/openems/impl/device/refu/Refu.java index 3d31581e54d..c0dc67d728d 100644 --- a/edge/src/io/openems/impl/device/refu/Refu.java +++ b/edge/src/io/openems/impl/device/refu/Refu.java @@ -1,66 +1,67 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.refu; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "REFU battery inverter") -public class Refu extends ModbusDevice { - - /* - * Constructors - */ - public Refu() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = RefuEss.class) - public final ConfigChannel ess = new ConfigChannel<>("ess", this); - - /* - * Methods - */ - @Override - public String toString() { - return "FeneconCommercialAC [ess=" + ess + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (ess.valueOptional().isPresent()) { - natures.add(ess.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.refu; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "REFU battery inverter") +public class Refu extends ModbusDevice { + + /* + * Constructors + */ + public Refu(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Ess", description = "Sets the Ess nature.", type = RefuEss.class) + public final ConfigChannel ess = new ConfigChannel<>("ess", this); + + /* + * Methods + */ + @Override + public String toString() { + return "FeneconCommercialAC [ess=" + ess + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (ess.valueOptional().isPresent()) { + natures.add(ess.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/refu/RefuEss.java b/edge/src/io/openems/impl/device/refu/RefuEss.java index 39561bd8b3c..aa5295ef712 100644 --- a/edge/src/io/openems/impl/device/refu/RefuEss.java +++ b/edge/src/io/openems/impl/device/refu/RefuEss.java @@ -1,708 +1,741 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.refu; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.ess.AsymmetricEssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.SignedWordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; -import io.openems.impl.protocol.modbus.internal.WordOrder; -import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; - -@ThingInfo(title = "REFU battery inverter ESS") -public class RefuEss extends ModbusDeviceNature implements SymmetricEssNature, AsymmetricEssNature { - - /* - * Constructors - */ - public RefuEss(String thingId) throws ConfigException { - super(thingId); - minSoc.addUpdateListener((channel, newValue) -> { - // If chargeSoc was not set -> set it to minSoc minus 2 - if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { - chargeSoc.updateValue((Integer) newValue.get() - 2, false); - } - }); - } - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel<>("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel soc; - private ModbusReadLongChannel allowedCharge; - private ModbusReadLongChannel allowedDischarge; - private StaticValueChannel allowedApparent = new StaticValueChannel<>("allowedApparent", this, 96600L) - .unit("VA").unit("VA"); - private ModbusReadLongChannel apparentPower; - private StaticValueChannel gridMode = new StaticValueChannel("GridMode", this, 1L).label(1L, ON_GRID); - private ModbusReadLongChannel activePower; - private ModbusReadLongChannel reactivePower; - private ModbusReadLongChannel systemState; - private ModbusWriteLongChannel setActivePower; - private ModbusWriteLongChannel setActivePowerL1; - private ModbusWriteLongChannel setActivePowerL2; - private ModbusWriteLongChannel setActivePowerL3; - private ModbusWriteLongChannel setReactivePower; - private ModbusWriteLongChannel setReactivePowerL1; - private ModbusWriteLongChannel setReactivePowerL2; - private ModbusWriteLongChannel setReactivePowerL3; - private ModbusWriteLongChannel setWorkState; - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 100000L) - .unit("VA").unit("VA"); - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 130000L).unit("Wh"); - public StatusBitChannels warning; - - /* - * This Channels - */ - public StatusBitChannel communicationInformations; - public StatusBitChannel systemError1; - public StatusBitChannel inverterStatus; - public ModbusReadLongChannel errorCode; - public StatusBitChannel dcDcStatus; - public ModbusReadLongChannel dcDcError; - public ModbusReadLongChannel batteryCurrent; - public ModbusReadLongChannel batteryCurrentPcs; - public ModbusReadLongChannel batteryVoltage; - public ModbusReadLongChannel batteryVoltagePcs; - public ModbusReadLongChannel batteryPower; - public ModbusWriteLongChannel setSystemErrorReset; - public ModbusWriteLongChannel setOperationMode; - public ModbusReadLongChannel batteryState; - public ModbusReadLongChannel batteryMode; - public ModbusReadLongChannel allowedChargeCurrent; - public ModbusReadLongChannel allowedDischargeCurrent; - public ModbusReadLongChannel batteryChargeEnergy; - public ModbusReadLongChannel batteryDischargeEnergy; - public ModbusReadLongChannel pcsAllowedCharge; - public ModbusReadLongChannel pcsAllowedDischarge; - public StatusBitChannel batteryOperationStatus; - public ModbusReadLongChannel batteryHighestVoltage; - public ModbusReadLongChannel batteryLowestVoltage; - public ModbusReadLongChannel batteryHighestTemperature; - public ModbusReadLongChannel batteryLowestTemperature; - public ModbusReadLongChannel batteryStopRequest; - public ModbusReadLongChannel cosPhi3p; - public ModbusReadLongChannel cosPhiL1; - public ModbusReadLongChannel cosPhiL2; - public ModbusReadLongChannel cosPhiL3; - public ModbusReadLongChannel current; - public ModbusReadLongChannel currentL1; - public ModbusReadLongChannel currentL2; - public ModbusReadLongChannel currentL3; - private ModbusReadLongChannel activePowerL1; - private ModbusReadLongChannel activePowerL2; - private ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel reactivePowerL1; - private ModbusReadLongChannel reactivePowerL2; - private ModbusReadLongChannel reactivePowerL3; - public StatusBitChannel batteryFault1; - public StatusBitChannel batteryFault2; - public StatusBitChannel batteryFault3; - public StatusBitChannel batteryFault4; - public StatusBitChannel batteryFault5; - public StatusBitChannel batteryFault6; - public StatusBitChannel batteryFault7; - public StatusBitChannel batteryFault8; - public StatusBitChannel batteryFault9; - public StatusBitChannel batteryFault10; - public StatusBitChannel batteryFault11; - public StatusBitChannel batteryFault12; - public StatusBitChannel batteryFault13; - public StatusBitChannel batteryFault14; - public StatusBitChannel batteryAlarm1; - public StatusBitChannel batteryAlarm2; - public StatusBitChannel batteryAlarm3; - public StatusBitChannel batteryAlarm4; - // public StatusBitChannel batteryAlarm5; - public StatusBitChannel batteryAlarm6; - public StatusBitChannel batteryAlarm7; - public StatusBitChannel batteryGroupControlStatus; - public StatusBitChannel errorLog1; - public StatusBitChannel errorLog2; - public StatusBitChannel errorLog3; - public StatusBitChannel errorLog4; - public StatusBitChannel errorLog5; - public StatusBitChannel errorLog6; - public StatusBitChannel errorLog7; - public StatusBitChannel errorLog8; - public StatusBitChannel errorLog9; - public StatusBitChannel errorLog10; - public StatusBitChannel errorLog11; - public StatusBitChannel errorLog12; - public StatusBitChannel errorLog13; - public StatusBitChannel errorLog14; - public StatusBitChannel errorLog15; - public StatusBitChannel errorLog16; - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - warning = new StatusBitChannels("Warning", this); - return new ModbusProtocol( // - new ModbusInputRegisterRange(0x100, // - new UnsignedWordElement(0x100, // - systemState = new ModbusReadLongChannel("SystemState", this) // - .label(0, STOP) // - .label(1, "Init") // - .label(2, "Pre-operation") // - .label(3, STANDBY) // - .label(4, START) // - .label(5, FAULT)), - new UnsignedWordElement(0x101, - systemError1 = warning.channel(new StatusBitChannel("SystemError1", this)// - .label(1, "BMS In Error")// - .label(2, "BMS Overvoltage")// - .label(4, "BMS Undervoltage")// - .label(8, "BMS Overcurrent")// - .label(16, "Error BMS Limits not initialized")// - .label(32, "Connect Error")// - .label(64, "Overvoltage warning")// - .label(128, "Undervoltage warning")// - .label(256, "Overcurrent warning")// - .label(512, "BMS Ready")// - .label(1024, "TREX Ready")// - )), new UnsignedWordElement(0x102, communicationInformations = new StatusBitChannel("CommunicationInformations", this)// - .label(1, "Gateway Initialized")// - .label(2, "Modbus Slave Status")// - .label(4, "Modbus Master Status")// - .label(8, "CAN Timeout")// - .label(16, "First Communication Ok")// - ), new UnsignedWordElement(0x103, inverterStatus = new StatusBitChannel("InverterStatus", this)// - .label(1, "Ready to Power on")// - .label(2, "Ready for Operating")// - .label(4, "Enabled")// - .label(8, "Fault")// - .label(256, "Warning")// - .label(512, "Voltage/Current mode")// - .label(1024, "Power mode")// - .label(2048, "AC relays close")// - .label(4096, "DC relays 1 close")// - .label(8192, "DC relays 2 close")// - .label(16384, "Mains OK")// - ), new UnsignedWordElement(0x104, errorCode = new ModbusReadLongChannel("ErrorCode", this)), new UnsignedWordElement(0x105, dcDcStatus = new StatusBitChannel("DCDCStatus", this)// - .label(1, "Ready to Power on")// - .label(2, "Ready for Operating")// - .label(4, "Enabled")// - .label(8, "DCDC Fault")// - .label(128, "DCDC Warning")// - .label(256, "Voltage/Current mode")// - .label(512, "Power mode")// - ), new UnsignedWordElement(0x106, dcDcError = new ModbusReadLongChannel("DCDCError", this)), new SignedWordElement(0x107, batteryCurrentPcs = new ModbusReadLongChannel("BatteryCurrentPcs", this).unit("mA").multiplier(2)), // - new SignedWordElement(0x108, - batteryVoltagePcs = new ModbusReadLongChannel("BatteryVoltagePcs", this).unit("mV") - .multiplier(2)), // - new SignedWordElement(0x109, - current = new ModbusReadLongChannel("Current", this).unit("mA").multiplier(2)), // - new SignedWordElement(0x10A, - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), // - new SignedWordElement(0x10B, - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA").multiplier(2)), // - new SignedWordElement(0x10C, - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA").multiplier(2)), // - new SignedWordElement(0x10D, - activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(2)), // - new SignedWordElement(0x10E, - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W") - .multiplier(2)), // - new SignedWordElement(0x10F, - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W") - .multiplier(2)), // - new SignedWordElement(0x110, - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W") - .multiplier(2)), // - new SignedWordElement(0x111, - reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("Var") - .multiplier(2)), // - new SignedWordElement(0x112, - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("Var") - .multiplier(2)), // - new SignedWordElement(0x113, - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("Var") - .multiplier(2)), // - new SignedWordElement(0x114, - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("Var") - .multiplier(2)), // - new SignedWordElement(0x115, cosPhi3p = new ModbusReadLongChannel("CosPhi3p", this).unit("")), // - new SignedWordElement(0x116, cosPhiL1 = new ModbusReadLongChannel("CosPhiL1", this).unit("")), // - new SignedWordElement(0x117, cosPhiL2 = new ModbusReadLongChannel("CosPhiL2", this).unit("")), // - new SignedWordElement(0x118, cosPhiL3 = new ModbusReadLongChannel("CosPhiL3", this).unit(""))), // - new ModbusInputRegisterRange(0x11A, // - new SignedWordElement(0x11A, - pcsAllowedCharge = new ModbusReadLongChannel("PcsAllowedCharge", this).unit("kW") - .multiplier(2)), - new SignedWordElement(0x11B, - pcsAllowedDischarge = new ModbusReadLongChannel("PcsAllowedDischarge", this).unit("kW") - .multiplier(2)), - new UnsignedWordElement(0x11C, // - batteryState = new ModbusReadLongChannel("BatteryState", this)// - .label(0, "Initial")// - .label(1, STOP)// - .label(2, "Starting")// - .label(3, START)// - .label(4, "Stopping")// - .label(5, "Fault")), // - new UnsignedWordElement(0x11D, // - batteryMode = new ModbusReadLongChannel("BatteryMode", this).label(0, "Normal Mode")), - new SignedWordElement(0x11E, - batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV") - .multiplier(2)), - new SignedWordElement(0x11F, - batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA") - .multiplier(2)), - new SignedWordElement(0x120, // - batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W")// - .multiplier(2)), - new UnsignedWordElement(0x121, // - soc = new ModbusReadLongChannel("Soc", this).unit("%")), - new UnsignedWordElement(0x122, // - allowedChargeCurrent = new ModbusReadLongChannel("AllowedChargeCurrent", this) - .unit("mA")// - .multiplier(2)// - .negate()), - new UnsignedWordElement(0x123, // - allowedDischargeCurrent = new ModbusReadLongChannel("AllowedDischargeCurrent", this) - .unit("mA").multiplier(2)), - new UnsignedWordElement(0x124, // - allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).unit("W").multiplier(2) - .negate()), - new UnsignedWordElement(0x125, // - allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W") - .multiplier(2)), - new SignedDoublewordElement(0x126, // - batteryChargeEnergy = new ModbusReadLongChannel("BatteryChargeEnergy", this) - .unit("kWh")).wordorder(WordOrder.LSWMSW), - new SignedDoublewordElement(0x128, // - batteryDischargeEnergy = new ModbusReadLongChannel("BatteryDischargeEnergy", this) - .unit("kWh")).wordorder(WordOrder.LSWMSW), - new UnsignedWordElement(0x12A, // - batteryOperationStatus = new StatusBitChannel("BatteryOperationStatus", this) - .label(1, "Battery group 1 operating")// - .label(2, "Battery group 2 operating")// - .label(4, "Battery group 3 operating")// - .label(8, "Battery group 4 operating")), - new UnsignedWordElement(0x12B, // - batteryHighestVoltage = new ModbusReadLongChannel("BatteryHighestVoltage", this) - .unit("mV")), - new UnsignedWordElement(0x12C, // - batteryLowestVoltage = new ModbusReadLongChannel("BatteryLowestVoltage", this) - .unit("mV")), - new SignedWordElement(0x12D, // - batteryHighestTemperature = new ModbusReadLongChannel("BatteryHighestTemperature", this) - .unit("�C")), - new SignedWordElement(0x12E, // - batteryLowestTemperature = new ModbusReadLongChannel("BatteryLowestTemperature", this) - .unit("�C")), - new UnsignedWordElement(0x12F, // - batteryStopRequest = new ModbusReadLongChannel("BatteryStopRequest", this)), - new UnsignedWordElement(0x130, - batteryAlarm1 = warning.channel(new StatusBitChannel("BatteryAlarm1", this)// - .label(1, "Normal charging over-current ")// - .label(2, "Charginig current over limit")// - .label(4, "Discharging current over limit")// - .label(8, "Normal high voltage")// - .label(16, "Normal low voltage")// - .label(32, "Abnormal voltage variation")// - .label(64, "Normal high temperature")// - .label(128, "Normal low temperature")// - .label(256, "Abnormal temperature variation")// - .label(512, "Serious high voltage")// - .label(1024, "Serious low voltage")// - .label(2048, "Serious low temperature")// - .label(4096, "Charging serious over current")// - .label(8192, "Discharging serious over current")// - .label(16384, "Abnormal capacity alarm"))), - new UnsignedWordElement(0x131, - batteryAlarm2 = warning.channel(new StatusBitChannel("BatteryAlarm2", this)// - .label(1, "EEPROM parameter failure")// - .label(2, "Switch off inside combined cabinet")// - .label(32, "Should not be connected to grid due to the DC side condition")// - .label(128, "Emergency stop require from system controller"))), - new UnsignedWordElement(0x132, - batteryAlarm3 = warning.channel(new StatusBitChannel("BatteryAlarm3", this)// - .label(1, "Battery group 1 enable and not connected to grid")// - .label(2, "Battery group 2 enable and not connected to grid")// - .label(4, "Battery group 3 enable and not connected to grid")// - .label(8, "Battery group 4 enable and not connected to grid"))), - new UnsignedWordElement(0x133, - batteryAlarm4 = warning.channel(new StatusBitChannel("BatteryAlarm4", this)// - .label(1, "The isolation switch of battery group 1 open")// - .label(2, "The isolation switch of battery group 2 open")// - .label(4, "The isolation switch of battery group 3 open")// - .label(8, "The isolation switch of battery group 4 open"))), - new DummyElement(0x134), - new UnsignedWordElement(0x135, - batteryAlarm6 = warning.channel(new StatusBitChannel("BatteryAlarm6", this)// - .label(1, "Balancing sampling failure of battery group 1")// - .label(2, "Balancing sampling failure of battery group 2")// - .label(4, "Balancing sampling failure of battery group 3")// - .label(8, "Balancing sampling failure of battery group 4"))), - new UnsignedWordElement(0x136, - batteryAlarm7 = warning.channel(new StatusBitChannel("BatteryAlarm7", this)// - .label(1, "Balancing control failure of battery group 1")// - .label(2, "Balancing control failure of battery group 2")// - .label(4, "Balancing control failure of battery group 3")// - .label(8, "Balancing control failure of battery group 4"))), - new UnsignedWordElement(0x137, - batteryFault1 = warning.channel(new StatusBitChannel("BatteryFault1", this)// - .label(1, "No enable batery group or usable battery group")// - .label(2, "Normal leakage of battery group")// - .label(4, "Serious leakage of battery group")// - .label(8, "Battery start failure")// - .label(16, "Battery stop failure")// - .label(32, - "Interruption of CAN Communication between battery group and controller")// - .label(1024, "Emergency stop abnormal of auxiliary collector")// - .label(2048, "Leakage self detection on negative")// - .label(4096, "Leakage self detection on positive")// - .label(8192, "Self detection failure on battery"))), - new UnsignedWordElement(0x138, - batteryFault2 = warning.channel(new StatusBitChannel("BatteryFault2", this)// - .label(1, "CAN Communication interruption between battery group and group 1")// - .label(2, "CAN Communication interruption between battery group and group 2")// - .label(4, "CAN Communication interruption between battery group and group 3")// - .label(8, "CAN Communication interruption between battery group and group 4"))), - new UnsignedWordElement(0x139, - batteryFault3 = warning.channel(new StatusBitChannel("BatteryFault3", this)// - .label(1, "Main contractor abnormal in battery self detect group 1")// - .label(2, "Main contractor abnormal in battery self detect group 2")// - .label(4, "Main contractor abnormal in battery self detect group 3")// - .label(8, "Main contractor abnormal in battery self detect group 4"))), - new UnsignedWordElement(0x13A, - batteryFault4 = warning.channel(new StatusBitChannel("BatteryFault4", this)// - .label(1, "Pre-charge contractor abnormal on battery self detect group 1")// - .label(2, "Pre-charge contractor abnormal on battery self detect group 2")// - .label(4, "Pre-charge contractor abnormal on battery self detect group 3")// - .label(8, "Pre-charge contractor abnormal on battery self detect group 4"))), - new UnsignedWordElement(0x13B, - batteryFault5 = warning.channel(new StatusBitChannel("BatteryFault5", this)// - .label(1, "Main contact failure on battery control group 1")// - .label(2, "Main contact failure on battery control group 2")// - .label(4, "Main contact failure on battery control group 3")// - .label(8, "Main contact failure on battery control group 4"))), - new UnsignedWordElement(0x13C, - batteryFault6 = warning.channel(new StatusBitChannel("BatteryFault6", this)// - .label(1, "Pre-charge failure on battery control group 1")// - .label(2, "Pre-charge failure on battery control group 2")// - .label(4, "Pre-charge failure on battery control group 3")// - .label(8, "Pre-charge failure on battery control group 4"))), - new UnsignedWordElement(0x13D, - batteryFault7 = warning.channel(new StatusBitChannel("BatteryFault7", this)// - )), new UnsignedWordElement(0x13E, batteryFault8 = warning.channel(new StatusBitChannel("BatteryFault8", this)// - )), new UnsignedWordElement(0x13F, batteryFault9 = warning.channel(new StatusBitChannel("BatteryFault9", this)// - .label(4, "Sampling circuit abnormal for BMU")// - .label(8, "Power cable disconnect failure")// - .label(16, "Sampling circuit disconnect failure")// - .label(64, "CAN disconnect for master and slave")// - .label(512, "Sammpling circuit failure")// - .label(1024, "Single battery failure")// - .label(2048, "Circuit detection abnormal for main contactor")// - .label(4096, "Circuit detection abnormal for main contactor")// - .label(8192, "Circuit detection abnormal for Fancontactor")// - .label(16384, "BMUPower contactor circuit detection abnormal")// - .label(32768, "Central contactor circuit detection abnormal"))), - new UnsignedWordElement(0x140, - batteryFault10 = warning.channel(new StatusBitChannel("BatteryFault10", this)// - .label(4, "Serious temperature fault")// - .label(8, "Communication fault for system controller")// - .label(128, "Frog alarm")// - .label(256, "Fuse fault")// - .label(1024, "Normal leakage")// - .label(2048, "Serious leakage")// - .label(4096, "CAN disconnection between battery group and battery stack")// - .label(8192, "Central contactor circuit open")// - .label(16384, "BMU power contactor open"))), - new UnsignedWordElement(0x141, - batteryFault11 = warning.channel(new StatusBitChannel("BatteryFault11", this)// - )), new UnsignedWordElement(0x142, batteryFault12 = warning.channel(new StatusBitChannel("BatteryFault12", this)// - )), new UnsignedWordElement(0x143, batteryFault13 = warning.channel(new StatusBitChannel("BatteryFault13", this)// - )), new UnsignedWordElement(0x144, batteryFault14 = warning.channel(new StatusBitChannel("BatteryFault14", this)// - )), new UnsignedWordElement(0x145, batteryGroupControlStatus = warning.channel(new StatusBitChannel("BatteryGroupControlStatus", this)// - )), new UnsignedWordElement(0x146, errorLog1 = warning.channel(new StatusBitChannel("ErrorLog1", this)// - )), new UnsignedWordElement(0x147, errorLog2 = warning.channel(new StatusBitChannel("ErrorLog2", this)// - )), new UnsignedWordElement(0x148, errorLog3 = warning.channel(new StatusBitChannel("ErrorLog3", this)// - )), new UnsignedWordElement(0x149, errorLog4 = warning.channel(new StatusBitChannel("ErrorLog4", this)// - )), new UnsignedWordElement(0x14a, errorLog5 = warning.channel(new StatusBitChannel("ErrorLog5", this)// - )), new UnsignedWordElement(0x14b, errorLog6 = warning.channel(new StatusBitChannel("ErrorLog6", this)// - )), new UnsignedWordElement(0x14c, errorLog7 = warning.channel(new StatusBitChannel("ErrorLog7", this)// - )), new UnsignedWordElement(0x14d, errorLog8 = warning.channel(new StatusBitChannel("ErrorLog8", this)// - )), new UnsignedWordElement(0x14e, errorLog9 = warning.channel(new StatusBitChannel("ErrorLog9", this)// - )), new UnsignedWordElement(0x14f, errorLog10 = warning.channel(new StatusBitChannel("ErrorLog10", this)// - )), new UnsignedWordElement(0x150, errorLog11 = warning.channel(new StatusBitChannel("ErrorLog11", this)// - )), new UnsignedWordElement(0x151, errorLog12 = warning.channel(new StatusBitChannel("ErrorLog12", this)// - )), new UnsignedWordElement(0x152, errorLog13 = warning.channel(new StatusBitChannel("ErrorLog13", this)// - )), new UnsignedWordElement(0x153, errorLog14 = warning.channel(new StatusBitChannel("ErrorLog14", this)// - )), new UnsignedWordElement(0x154, errorLog15 = warning.channel(new StatusBitChannel("ErrorLog15", this)// - )), new UnsignedWordElement(0x155, errorLog16 = warning.channel(new StatusBitChannel("ErrorLog16", this)// - ))), new WriteableModbusRegisterRange(0x200, // - new UnsignedWordElement(0x200, // - setWorkState = new ModbusWriteLongChannel("SetWorkState", this) // - .label(0, STOP) // - .label(1, START)), - new UnsignedWordElement(0x201, // - setSystemErrorReset = new ModbusWriteLongChannel("SetSystemErrorReset", this)// - .label(0, OFF)// - .label(1, ON)), - new UnsignedWordElement(0x202, // - setOperationMode = new ModbusWriteLongChannel("SetOperationMode", this)// - .label(0, "P/Q Set point")// - .label(1, "IAC / cosphi set point"))), - new WriteableModbusRegisterRange(0x203, - new SignedWordElement(0x203, // - setActivePower = new ModbusWriteLongChannel("SetActivePower", this)// - .unit("W").multiplier(2))), - new WriteableModbusRegisterRange(0x204, - new SignedWordElement(0x204, // - setActivePowerL1 = new ModbusWriteLongChannel("SetActivePowerL1", this)// - .unit("W").multiplier(2)), - new SignedWordElement(0x205, // - setActivePowerL2 = new ModbusWriteLongChannel("SetActivePowerL2", this)// - .unit("W").multiplier(2)), - new SignedWordElement(0x206, // - setActivePowerL3 = new ModbusWriteLongChannel("SetActivePowerL3", this)// - .unit("W").multiplier(2))), - new WriteableModbusRegisterRange(0x207, - new SignedWordElement(0x207, // - setReactivePower = new ModbusWriteLongChannel("SetReactivePower", this)// - .unit("W").multiplier(2))), - new WriteableModbusRegisterRange(0x208, - new SignedWordElement(0x208, // - setReactivePowerL1 = new ModbusWriteLongChannel("SetReactivePowerL1", this)// - .unit("W").multiplier(2)), - new SignedWordElement(0x209, // - setReactivePowerL2 = new ModbusWriteLongChannel("SetReactivePowerL2", this)// - .unit("W").multiplier(2)), - new SignedWordElement(0x20A, // - setReactivePowerL3 = new ModbusWriteLongChannel("SetReactivePowerL3", this)// - .unit("W").multiplier(2)))); - // new ModbusInputRegisterRange(0x6040, - // new UnsignedWordElement(0x6040, // - // batteryInformation1 = new ModbusReadLongChannel("BatteryInformation1", this)), - // new UnsignedWordElement(0x6041, // - // batteryInformation2 = new ModbusReadLongChannel("BatteryInformation2", this)), - // new UnsignedWordElement(0x6042, // - // batteryInformation3 = new ModbusReadLongChannel("BatteryInformation3", this)), - // new UnsignedWordElement(0x6043, // - // batteryInformation4 = new ModbusReadLongChannel("BatteryInformation4", this))), - // new ModbusInputRegisterRange(0x6840, - // new UnsignedWordElement(0x6840, // - // batteryInformation5 = new ModbusReadLongChannel("BatteryInformation5", this)), - // new UnsignedWordElement(0x6841, // - // batteryInformation6 = new ModbusReadLongChannel("BatteryInformation6", this)), - // new UnsignedWordElement(0x6842, // - // batteryInformation7 = new ModbusReadLongChannel("BatteryInformation7", this)), - // new UnsignedWordElement(0x6843, // - // batteryInformation8 = new ModbusReadLongChannel("BatteryInformation8", this))), - // new ModbusInputRegisterRange(0x7640, - // new UnsignedWordElement(0x7640, // - // batteryInformation9 = new ModbusReadLongChannel("BatteryInformation9", this)), - // new UnsignedWordElement(0x7641, // - // batteryInformation10 = new ModbusReadLongChannel("BatteryInformation10", this)), - // new UnsignedWordElement(0x7642, // - // batteryInformation11 = new ModbusReadLongChannel("BatteryInformation11", this)), - // new UnsignedWordElement(0x7643, // - // batteryInformation12 = new ModbusReadLongChannel("BatteryInformation12", this))), - // new ModbusInputRegisterRange(0x8440, - // new UnsignedWordElement(0x8440, // - // batteryInformation13 = new ModbusReadLongChannel("BatteryInformation13", this)), - // new UnsignedWordElement(0x8441, // - // batteryInformation14 = new ModbusReadLongChannel("BatteryInformation14", this)), - // new UnsignedWordElement(0x8442, // - // batteryInformation15 = new ModbusReadLongChannel("BatteryInformation15", this)), - // new UnsignedWordElement(0x8443, // - // batteryInformation16 = new ModbusReadLongChannel("BatteryInformation16", this))), - // new ModbusInputRegisterRange(0x9240, - // new UnsignedWordElement(0x9240, // - // batteryInformation17 = new ModbusReadLongChannel("BatteryInformation17", this)), - // new UnsignedWordElement(0x9241, // - // batteryInformation18 = new ModbusReadLongChannel("BatteryInformation18", this)), - // new UnsignedWordElement(0x9242, // - // batteryInformation19 = new ModbusReadLongChannel("BatteryInformation19", this)), - // new UnsignedWordElement(0x9243, // - // batteryInformation20 = new ModbusReadLongChannel("BatteryInformation20", this)))); - } - - @Override - public StaticValueChannel capacity() { - return capacity; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public WriteChannel setActivePowerL1() { - return setActivePowerL1; - } - - @Override - public WriteChannel setActivePowerL2() { - return setActivePowerL2; - } - - @Override - public WriteChannel setActivePowerL3() { - return setActivePowerL3; - } - - @Override - public WriteChannel setReactivePowerL1() { - return setReactivePowerL1; - } - - @Override - public WriteChannel setReactivePowerL2() { - return setReactivePowerL2; - } - - @Override - public WriteChannel setReactivePowerL3() { - return setReactivePowerL3; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.refu; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.SignedWordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedWordElement; +import io.openems.impl.protocol.modbus.internal.WordOrder; +import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRegisterRange; + +@ThingInfo(title = "REFU battery inverter ESS") +public class RefuEss extends ModbusDeviceNature implements SymmetricEssNature, AsymmetricEssNature { + + /* + * Constructors + */ + public RefuEss(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + minSoc.addUpdateListener((channel, newValue) -> { + // If chargeSoc was not set -> set it to minSoc minus 2 + if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { + chargeSoc.updateValue((Integer) newValue.get() - 2, false); + } + }); + } + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel<>("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel soc; + private ModbusReadLongChannel allowedCharge; + private ModbusReadLongChannel allowedDischarge; + private StaticValueChannel allowedApparent = new StaticValueChannel<>("allowedApparent", this, 96600L) + .unit("VA").unit("VA"); + private ModbusReadLongChannel apparentPower; + private StaticValueChannel gridMode = new StaticValueChannel("GridMode", this, 1L).label(1L, ON_GRID); + private ModbusReadLongChannel activePower; + private ModbusReadLongChannel reactivePower; + private ModbusReadLongChannel systemState; + private ModbusWriteLongChannel setActivePower; + private ModbusWriteLongChannel setActivePowerL1; + private ModbusWriteLongChannel setActivePowerL2; + private ModbusWriteLongChannel setActivePowerL3; + private ModbusWriteLongChannel setReactivePower; + private ModbusWriteLongChannel setReactivePowerL1; + private ModbusWriteLongChannel setReactivePowerL2; + private ModbusWriteLongChannel setReactivePowerL3; + private ModbusWriteLongChannel setWorkState; + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 100000L) + .unit("VA").unit("VA"); + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 130000L).unit("Wh"); + public StatusBitChannels warning; + + /* + * This Channels + */ + public StatusBitChannel communicationInformations; + public StatusBitChannel systemError1; + public StatusBitChannel inverterStatus; + public ModbusReadLongChannel errorCode; + public StatusBitChannel dcDcStatus; + public ModbusReadLongChannel dcDcError; + public ModbusReadLongChannel batteryCurrent; + public ModbusReadLongChannel batteryCurrentPcs; + public ModbusReadLongChannel batteryVoltage; + public ModbusReadLongChannel batteryVoltagePcs; + public ModbusReadLongChannel batteryPower; + public ModbusWriteLongChannel setSystemErrorReset; + public ModbusWriteLongChannel setOperationMode; + public ModbusReadLongChannel batteryState; + public ModbusReadLongChannel batteryMode; + public ModbusReadLongChannel allowedChargeCurrent; + public ModbusReadLongChannel allowedDischargeCurrent; + public ModbusReadLongChannel batteryChargeEnergy; + public ModbusReadLongChannel batteryDischargeEnergy; + public ModbusReadLongChannel pcsAllowedCharge; + public ModbusReadLongChannel pcsAllowedDischarge; + public StatusBitChannel batteryOperationStatus; + public ModbusReadLongChannel batteryHighestVoltage; + public ModbusReadLongChannel batteryLowestVoltage; + public ModbusReadLongChannel batteryHighestTemperature; + public ModbusReadLongChannel batteryLowestTemperature; + public ModbusReadLongChannel batteryStopRequest; + public ModbusReadLongChannel cosPhi3p; + public ModbusReadLongChannel cosPhiL1; + public ModbusReadLongChannel cosPhiL2; + public ModbusReadLongChannel cosPhiL3; + public ModbusReadLongChannel current; + public ModbusReadLongChannel currentL1; + public ModbusReadLongChannel currentL2; + public ModbusReadLongChannel currentL3; + private ModbusReadLongChannel activePowerL1; + private ModbusReadLongChannel activePowerL2; + private ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel reactivePowerL1; + private ModbusReadLongChannel reactivePowerL2; + private ModbusReadLongChannel reactivePowerL3; + public StatusBitChannel batteryFault1; + public StatusBitChannel batteryFault2; + public StatusBitChannel batteryFault3; + public StatusBitChannel batteryFault4; + public StatusBitChannel batteryFault5; + public StatusBitChannel batteryFault6; + public StatusBitChannel batteryFault7; + public StatusBitChannel batteryFault8; + public StatusBitChannel batteryFault9; + public StatusBitChannel batteryFault10; + public StatusBitChannel batteryFault11; + public StatusBitChannel batteryFault12; + public StatusBitChannel batteryFault13; + public StatusBitChannel batteryFault14; + public StatusBitChannel batteryAlarm1; + public StatusBitChannel batteryAlarm2; + public StatusBitChannel batteryAlarm3; + public StatusBitChannel batteryAlarm4; + // public StatusBitChannel batteryAlarm5; + public StatusBitChannel batteryAlarm6; + public StatusBitChannel batteryAlarm7; + public StatusBitChannel batteryGroupControlStatus; + public StatusBitChannel errorLog1; + public StatusBitChannel errorLog2; + public StatusBitChannel errorLog3; + public StatusBitChannel errorLog4; + public StatusBitChannel errorLog5; + public StatusBitChannel errorLog6; + public StatusBitChannel errorLog7; + public StatusBitChannel errorLog8; + public StatusBitChannel errorLog9; + public StatusBitChannel errorLog10; + public StatusBitChannel errorLog11; + public StatusBitChannel errorLog12; + public StatusBitChannel errorLog13; + public StatusBitChannel errorLog14; + public StatusBitChannel errorLog15; + public StatusBitChannel errorLog16; + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + warning = new StatusBitChannels("Warning", this); + return new ModbusProtocol( // + new ModbusInputRegisterRange(0x100, // + new UnsignedWordElement(0x100, // + systemState = new ModbusReadLongChannel("SystemState", this) // + .label(0, STOP) // + .label(1, "Init") // + .label(2, "Pre-operation") // + .label(3, STANDBY) // + .label(4, START) // + .label(5, FAULT)), + new UnsignedWordElement(0x101, + systemError1 = warning.channel(new StatusBitChannel("SystemError1", this)// + .label(1, "BMS In Error")// + .label(2, "BMS Overvoltage")// + .label(4, "BMS Undervoltage")// + .label(8, "BMS Overcurrent")// + .label(16, "Error BMS Limits not initialized")// + .label(32, "Connect Error")// + .label(64, "Overvoltage warning")// + .label(128, "Undervoltage warning")// + .label(256, "Overcurrent warning")// + .label(512, "BMS Ready")// + .label(1024, "TREX Ready")// + )), + new UnsignedWordElement(0x102, + communicationInformations = new StatusBitChannel("CommunicationInformations", this)// + .label(1, "Gateway Initialized")// + .label(2, "Modbus Slave Status")// + .label(4, "Modbus Master Status")// + .label(8, "CAN Timeout")// + .label(16, "First Communication Ok")// + ), new UnsignedWordElement(0x103, inverterStatus = new StatusBitChannel("InverterStatus", this)// + .label(1, "Ready to Power on")// + .label(2, "Ready for Operating")// + .label(4, "Enabled")// + .label(8, "Fault")// + .label(256, "Warning")// + .label(512, "Voltage/Current mode")// + .label(1024, "Power mode")// + .label(2048, "AC relays close")// + .label(4096, "DC relays 1 close")// + .label(8192, "DC relays 2 close")// + .label(16384, "Mains OK")// + ), new UnsignedWordElement(0x104, errorCode = new ModbusReadLongChannel("ErrorCode", this)), + new UnsignedWordElement(0x105, dcDcStatus = new StatusBitChannel("DCDCStatus", this)// + .label(1, "Ready to Power on")// + .label(2, "Ready for Operating")// + .label(4, "Enabled")// + .label(8, "DCDC Fault")// + .label(128, "DCDC Warning")// + .label(256, "Voltage/Current mode")// + .label(512, "Power mode")// + ), new UnsignedWordElement(0x106, dcDcError = new ModbusReadLongChannel("DCDCError", this)), + new SignedWordElement(0x107, + batteryCurrentPcs = new ModbusReadLongChannel("BatteryCurrentPcs", this).unit("mA") + .multiplier(2)), // + new SignedWordElement(0x108, + batteryVoltagePcs = new ModbusReadLongChannel("BatteryVoltagePcs", this).unit("mV") + .multiplier(2)), // + new SignedWordElement(0x109, + current = new ModbusReadLongChannel("Current", this).unit("mA").multiplier(2)), // + new SignedWordElement(0x10A, + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA").multiplier(2)), // + new SignedWordElement(0x10B, + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA").multiplier(2)), // + new SignedWordElement(0x10C, + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA").multiplier(2)), // + new SignedWordElement(0x10D, + activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(2)), // + new SignedWordElement(0x10E, + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W") + .multiplier(2)), // + new SignedWordElement(0x10F, + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W") + .multiplier(2)), // + new SignedWordElement(0x110, + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W") + .multiplier(2)), // + new SignedWordElement(0x111, + reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("Var") + .multiplier(2)), // + new SignedWordElement(0x112, + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("Var") + .multiplier(2)), // + new SignedWordElement(0x113, + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("Var") + .multiplier(2)), // + new SignedWordElement(0x114, + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("Var") + .multiplier(2)), // + new SignedWordElement(0x115, cosPhi3p = new ModbusReadLongChannel("CosPhi3p", this).unit("")), // + new SignedWordElement(0x116, cosPhiL1 = new ModbusReadLongChannel("CosPhiL1", this).unit("")), // + new SignedWordElement(0x117, cosPhiL2 = new ModbusReadLongChannel("CosPhiL2", this).unit("")), // + new SignedWordElement(0x118, cosPhiL3 = new ModbusReadLongChannel("CosPhiL3", this).unit(""))), // + new ModbusInputRegisterRange(0x11A, // + new SignedWordElement(0x11A, + pcsAllowedCharge = new ModbusReadLongChannel("PcsAllowedCharge", this).unit("kW") + .multiplier(2)), + new SignedWordElement(0x11B, + pcsAllowedDischarge = new ModbusReadLongChannel("PcsAllowedDischarge", this).unit("kW") + .multiplier(2)), + new UnsignedWordElement(0x11C, // + batteryState = new ModbusReadLongChannel("BatteryState", this)// + .label(0, "Initial")// + .label(1, STOP)// + .label(2, "Starting")// + .label(3, START)// + .label(4, "Stopping")// + .label(5, "Fault")), // + new UnsignedWordElement(0x11D, // + batteryMode = new ModbusReadLongChannel("BatteryMode", this).label(0, "Normal Mode")), + new SignedWordElement(0x11E, + batteryVoltage = new ModbusReadLongChannel("BatteryVoltage", this).unit("mV") + .multiplier(2)), + new SignedWordElement(0x11F, + batteryCurrent = new ModbusReadLongChannel("BatteryCurrent", this).unit("mA") + .multiplier(2)), + new SignedWordElement(0x120, // + batteryPower = new ModbusReadLongChannel("BatteryPower", this).unit("W")// + .multiplier(2)), + new UnsignedWordElement(0x121, // + soc = new ModbusReadLongChannel("Soc", this).unit("%")), + new UnsignedWordElement(0x122, // + allowedChargeCurrent = new ModbusReadLongChannel("AllowedChargeCurrent", this) + .unit("mA")// + .multiplier(2)// + .negate()), + new UnsignedWordElement(0x123, // + allowedDischargeCurrent = new ModbusReadLongChannel("AllowedDischargeCurrent", this) + .unit("mA").multiplier(2)), + new UnsignedWordElement(0x124, // + allowedCharge = new ModbusReadLongChannel("AllowedCharge", this).unit("W").multiplier(2) + .negate()), + new UnsignedWordElement(0x125, // + allowedDischarge = new ModbusReadLongChannel("AllowedDischarge", this).unit("W") + .multiplier(2)), + new SignedDoublewordElement(0x126, // + batteryChargeEnergy = new ModbusReadLongChannel("BatteryChargeEnergy", + this).unit("kWh")).wordorder(WordOrder.LSWMSW), + new SignedDoublewordElement(0x128, // + batteryDischargeEnergy = new ModbusReadLongChannel( + "BatteryDischargeEnergy", this).unit("kWh")).wordorder(WordOrder.LSWMSW), + new UnsignedWordElement(0x12A, // + batteryOperationStatus = new StatusBitChannel("BatteryOperationStatus", this) + .label(1, "Battery group 1 operating")// + .label(2, "Battery group 2 operating")// + .label(4, "Battery group 3 operating")// + .label(8, "Battery group 4 operating")), + new UnsignedWordElement(0x12B, // + batteryHighestVoltage = new ModbusReadLongChannel("BatteryHighestVoltage", this) + .unit("mV")), + new UnsignedWordElement(0x12C, // + batteryLowestVoltage = new ModbusReadLongChannel("BatteryLowestVoltage", this) + .unit("mV")), + new SignedWordElement(0x12D, // + batteryHighestTemperature = new ModbusReadLongChannel("BatteryHighestTemperature", this) + .unit("�C")), + new SignedWordElement(0x12E, // + batteryLowestTemperature = new ModbusReadLongChannel("BatteryLowestTemperature", this) + .unit("�C")), + new UnsignedWordElement(0x12F, // + batteryStopRequest = new ModbusReadLongChannel("BatteryStopRequest", this)), + new UnsignedWordElement(0x130, + batteryAlarm1 = warning.channel(new StatusBitChannel("BatteryAlarm1", this)// + .label(1, "Normal charging over-current ")// + .label(2, "Charginig current over limit")// + .label(4, "Discharging current over limit")// + .label(8, "Normal high voltage")// + .label(16, "Normal low voltage")// + .label(32, "Abnormal voltage variation")// + .label(64, "Normal high temperature")// + .label(128, "Normal low temperature")// + .label(256, "Abnormal temperature variation")// + .label(512, "Serious high voltage")// + .label(1024, "Serious low voltage")// + .label(2048, "Serious low temperature")// + .label(4096, "Charging serious over current")// + .label(8192, "Discharging serious over current")// + .label(16384, "Abnormal capacity alarm"))), + new UnsignedWordElement(0x131, + batteryAlarm2 = warning.channel(new StatusBitChannel("BatteryAlarm2", this)// + .label(1, "EEPROM parameter failure")// + .label(2, "Switch off inside combined cabinet")// + .label(32, "Should not be connected to grid due to the DC side condition")// + .label(128, "Emergency stop require from system controller"))), + new UnsignedWordElement(0x132, + batteryAlarm3 = warning.channel(new StatusBitChannel("BatteryAlarm3", this)// + .label(1, "Battery group 1 enable and not connected to grid")// + .label(2, "Battery group 2 enable and not connected to grid")// + .label(4, "Battery group 3 enable and not connected to grid")// + .label(8, "Battery group 4 enable and not connected to grid"))), + new UnsignedWordElement(0x133, + batteryAlarm4 = warning.channel(new StatusBitChannel("BatteryAlarm4", this)// + .label(1, "The isolation switch of battery group 1 open")// + .label(2, "The isolation switch of battery group 2 open")// + .label(4, "The isolation switch of battery group 3 open")// + .label(8, "The isolation switch of battery group 4 open"))), + new DummyElement(0x134), + new UnsignedWordElement(0x135, + batteryAlarm6 = warning.channel(new StatusBitChannel("BatteryAlarm6", this)// + .label(1, "Balancing sampling failure of battery group 1")// + .label(2, "Balancing sampling failure of battery group 2")// + .label(4, "Balancing sampling failure of battery group 3")// + .label(8, "Balancing sampling failure of battery group 4"))), + new UnsignedWordElement(0x136, + batteryAlarm7 = warning.channel(new StatusBitChannel("BatteryAlarm7", this)// + .label(1, "Balancing control failure of battery group 1")// + .label(2, "Balancing control failure of battery group 2")// + .label(4, "Balancing control failure of battery group 3")// + .label(8, "Balancing control failure of battery group 4"))), + new UnsignedWordElement(0x137, + batteryFault1 = warning.channel(new StatusBitChannel("BatteryFault1", this)// + .label(1, "No enable batery group or usable battery group")// + .label(2, "Normal leakage of battery group")// + .label(4, "Serious leakage of battery group")// + .label(8, "Battery start failure")// + .label(16, "Battery stop failure")// + .label(32, + "Interruption of CAN Communication between battery group and controller")// + .label(1024, "Emergency stop abnormal of auxiliary collector")// + .label(2048, "Leakage self detection on negative")// + .label(4096, "Leakage self detection on positive")// + .label(8192, "Self detection failure on battery"))), + new UnsignedWordElement(0x138, + batteryFault2 = warning.channel(new StatusBitChannel("BatteryFault2", this)// + .label(1, "CAN Communication interruption between battery group and group 1")// + .label(2, "CAN Communication interruption between battery group and group 2")// + .label(4, "CAN Communication interruption between battery group and group 3")// + .label(8, "CAN Communication interruption between battery group and group 4"))), + new UnsignedWordElement(0x139, + batteryFault3 = warning.channel(new StatusBitChannel("BatteryFault3", this)// + .label(1, "Main contractor abnormal in battery self detect group 1")// + .label(2, "Main contractor abnormal in battery self detect group 2")// + .label(4, "Main contractor abnormal in battery self detect group 3")// + .label(8, "Main contractor abnormal in battery self detect group 4"))), + new UnsignedWordElement(0x13A, + batteryFault4 = warning.channel(new StatusBitChannel("BatteryFault4", this)// + .label(1, "Pre-charge contractor abnormal on battery self detect group 1")// + .label(2, "Pre-charge contractor abnormal on battery self detect group 2")// + .label(4, "Pre-charge contractor abnormal on battery self detect group 3")// + .label(8, "Pre-charge contractor abnormal on battery self detect group 4"))), + new UnsignedWordElement(0x13B, + batteryFault5 = warning.channel(new StatusBitChannel("BatteryFault5", this)// + .label(1, "Main contact failure on battery control group 1")// + .label(2, "Main contact failure on battery control group 2")// + .label(4, "Main contact failure on battery control group 3")// + .label(8, "Main contact failure on battery control group 4"))), + new UnsignedWordElement(0x13C, + batteryFault6 = warning.channel(new StatusBitChannel("BatteryFault6", this)// + .label(1, "Pre-charge failure on battery control group 1")// + .label(2, "Pre-charge failure on battery control group 2")// + .label(4, "Pre-charge failure on battery control group 3")// + .label(8, "Pre-charge failure on battery control group 4"))), + new UnsignedWordElement(0x13D, + batteryFault7 = warning.channel(new StatusBitChannel("BatteryFault7", this)// + )), new UnsignedWordElement(0x13E, + batteryFault8 = warning.channel(new StatusBitChannel("BatteryFault8", this)// + )), + new UnsignedWordElement(0x13F, + batteryFault9 = warning.channel(new StatusBitChannel("BatteryFault9", this)// + .label(4, "Sampling circuit abnormal for BMU")// + .label(8, "Power cable disconnect failure")// + .label(16, "Sampling circuit disconnect failure")// + .label(64, "CAN disconnect for master and slave")// + .label(512, "Sammpling circuit failure")// + .label(1024, "Single battery failure")// + .label(2048, "Circuit detection abnormal for main contactor")// + .label(4096, "Circuit detection abnormal for main contactor")// + .label(8192, "Circuit detection abnormal for Fancontactor")// + .label(16384, "BMUPower contactor circuit detection abnormal")// + .label(32768, "Central contactor circuit detection abnormal"))), + new UnsignedWordElement(0x140, + batteryFault10 = warning.channel(new StatusBitChannel("BatteryFault10", this)// + .label(4, "Serious temperature fault")// + .label(8, "Communication fault for system controller")// + .label(128, "Frog alarm")// + .label(256, "Fuse fault")// + .label(1024, "Normal leakage")// + .label(2048, "Serious leakage")// + .label(4096, "CAN disconnection between battery group and battery stack")// + .label(8192, "Central contactor circuit open")// + .label(16384, "BMU power contactor open"))), + new UnsignedWordElement(0x141, + batteryFault11 = warning.channel(new StatusBitChannel("BatteryFault11", this)// + )), new UnsignedWordElement(0x142, + batteryFault12 = warning.channel(new StatusBitChannel("BatteryFault12", this)// + )), + new UnsignedWordElement(0x143, + batteryFault13 = warning.channel(new StatusBitChannel("BatteryFault13", this)// + )), new UnsignedWordElement(0x144, + batteryFault14 = warning.channel(new StatusBitChannel("BatteryFault14", this)// + )), new UnsignedWordElement(0x145, batteryGroupControlStatus = warning + .channel(new StatusBitChannel("BatteryGroupControlStatus", this)// + )), new UnsignedWordElement(0x146, + errorLog1 = warning.channel(new StatusBitChannel("ErrorLog1", this)// + )), new UnsignedWordElement(0x147, + errorLog2 = warning.channel(new StatusBitChannel("ErrorLog2", this)// + )), new UnsignedWordElement(0x148, + errorLog3 = warning.channel(new StatusBitChannel("ErrorLog3", this)// + )), + new UnsignedWordElement(0x149, + errorLog4 = warning.channel(new StatusBitChannel("ErrorLog4", this)// + )), new UnsignedWordElement(0x14a, + errorLog5 = warning.channel(new StatusBitChannel("ErrorLog5", this)// + )), new UnsignedWordElement(0x14b, + errorLog6 = warning.channel(new StatusBitChannel("ErrorLog6", this)// + )), + new UnsignedWordElement(0x14c, + errorLog7 = warning.channel(new StatusBitChannel("ErrorLog7", this)// + )), new UnsignedWordElement(0x14d, + errorLog8 = warning.channel(new StatusBitChannel("ErrorLog8", this)// + )), new UnsignedWordElement(0x14e, + errorLog9 = warning.channel(new StatusBitChannel("ErrorLog9", this)// + )), + new UnsignedWordElement(0x14f, + errorLog10 = warning.channel(new StatusBitChannel("ErrorLog10", this)// + )), new UnsignedWordElement(0x150, + errorLog11 = warning.channel(new StatusBitChannel("ErrorLog11", this)// + )), new UnsignedWordElement(0x151, + errorLog12 = warning.channel(new StatusBitChannel("ErrorLog12", this)// + )), + new UnsignedWordElement(0x152, + errorLog13 = warning.channel(new StatusBitChannel("ErrorLog13", this)// + )), new UnsignedWordElement(0x153, + errorLog14 = warning.channel(new StatusBitChannel("ErrorLog14", this)// + )), new UnsignedWordElement(0x154, + errorLog15 = warning.channel(new StatusBitChannel("ErrorLog15", this)// + )), + new UnsignedWordElement(0x155, + errorLog16 = warning.channel(new StatusBitChannel("ErrorLog16", this)// + ))), new WriteableModbusRegisterRange(0x200, // + new UnsignedWordElement(0x200, // + setWorkState = new ModbusWriteLongChannel("SetWorkState", this) // + .label(0, STOP) // + .label(1, START)), + new UnsignedWordElement(0x201, // + setSystemErrorReset = new ModbusWriteLongChannel("SetSystemErrorReset", + this)// + .label(0, OFF)// + .label(1, ON)), + new UnsignedWordElement(0x202, // + setOperationMode = new ModbusWriteLongChannel("SetOperationMode", this)// + .label(0, "P/Q Set point")// + .label(1, "IAC / cosphi set point"))), + new WriteableModbusRegisterRange(0x203, new SignedWordElement(0x203, // + setActivePower = new ModbusWriteLongChannel("SetActivePower", this)// + .unit("W").multiplier(2))), + new WriteableModbusRegisterRange(0x204, new SignedWordElement(0x204, // + setActivePowerL1 = new ModbusWriteLongChannel("SetActivePowerL1", this)// + .unit("W").multiplier(2)), + new SignedWordElement(0x205, // + setActivePowerL2 = new ModbusWriteLongChannel("SetActivePowerL2", this)// + .unit("W").multiplier(2)), + new SignedWordElement(0x206, // + setActivePowerL3 = new ModbusWriteLongChannel("SetActivePowerL3", this)// + .unit("W").multiplier(2))), + new WriteableModbusRegisterRange(0x207, new SignedWordElement(0x207, // + setReactivePower = new ModbusWriteLongChannel("SetReactivePower", this)// + .unit("W").multiplier(2))), + new WriteableModbusRegisterRange(0x208, new SignedWordElement(0x208, // + setReactivePowerL1 = new ModbusWriteLongChannel("SetReactivePowerL1", this)// + .unit("W").multiplier(2)), + new SignedWordElement(0x209, // + setReactivePowerL2 = new ModbusWriteLongChannel("SetReactivePowerL2", this)// + .unit("W").multiplier(2)), + new SignedWordElement(0x20A, // + setReactivePowerL3 = new ModbusWriteLongChannel("SetReactivePowerL3", this)// + .unit("W").multiplier(2)))); + // new ModbusInputRegisterRange(0x6040, + // new UnsignedWordElement(0x6040, // + // batteryInformation1 = new ModbusReadLongChannel("BatteryInformation1", this)), + // new UnsignedWordElement(0x6041, // + // batteryInformation2 = new ModbusReadLongChannel("BatteryInformation2", this)), + // new UnsignedWordElement(0x6042, // + // batteryInformation3 = new ModbusReadLongChannel("BatteryInformation3", this)), + // new UnsignedWordElement(0x6043, // + // batteryInformation4 = new ModbusReadLongChannel("BatteryInformation4", this))), + // new ModbusInputRegisterRange(0x6840, + // new UnsignedWordElement(0x6840, // + // batteryInformation5 = new ModbusReadLongChannel("BatteryInformation5", this)), + // new UnsignedWordElement(0x6841, // + // batteryInformation6 = new ModbusReadLongChannel("BatteryInformation6", this)), + // new UnsignedWordElement(0x6842, // + // batteryInformation7 = new ModbusReadLongChannel("BatteryInformation7", this)), + // new UnsignedWordElement(0x6843, // + // batteryInformation8 = new ModbusReadLongChannel("BatteryInformation8", this))), + // new ModbusInputRegisterRange(0x7640, + // new UnsignedWordElement(0x7640, // + // batteryInformation9 = new ModbusReadLongChannel("BatteryInformation9", this)), + // new UnsignedWordElement(0x7641, // + // batteryInformation10 = new ModbusReadLongChannel("BatteryInformation10", this)), + // new UnsignedWordElement(0x7642, // + // batteryInformation11 = new ModbusReadLongChannel("BatteryInformation11", this)), + // new UnsignedWordElement(0x7643, // + // batteryInformation12 = new ModbusReadLongChannel("BatteryInformation12", this))), + // new ModbusInputRegisterRange(0x8440, + // new UnsignedWordElement(0x8440, // + // batteryInformation13 = new ModbusReadLongChannel("BatteryInformation13", this)), + // new UnsignedWordElement(0x8441, // + // batteryInformation14 = new ModbusReadLongChannel("BatteryInformation14", this)), + // new UnsignedWordElement(0x8442, // + // batteryInformation15 = new ModbusReadLongChannel("BatteryInformation15", this)), + // new UnsignedWordElement(0x8443, // + // batteryInformation16 = new ModbusReadLongChannel("BatteryInformation16", this))), + // new ModbusInputRegisterRange(0x9240, + // new UnsignedWordElement(0x9240, // + // batteryInformation17 = new ModbusReadLongChannel("BatteryInformation17", this)), + // new UnsignedWordElement(0x9241, // + // batteryInformation18 = new ModbusReadLongChannel("BatteryInformation18", this)), + // new UnsignedWordElement(0x9242, // + // batteryInformation19 = new ModbusReadLongChannel("BatteryInformation19", this)), + // new UnsignedWordElement(0x9243, // + // batteryInformation20 = new ModbusReadLongChannel("BatteryInformation20", this)))); + } + + @Override + public StaticValueChannel capacity() { + return capacity; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public WriteChannel setActivePowerL1() { + return setActivePowerL1; + } + + @Override + public WriteChannel setActivePowerL2() { + return setActivePowerL2; + } + + @Override + public WriteChannel setActivePowerL3() { + return setActivePowerL3; + } + + @Override + public WriteChannel setReactivePowerL1() { + return setReactivePowerL1; + } + + @Override + public WriteChannel setReactivePowerL2() { + return setReactivePowerL2; + } + + @Override + public WriteChannel setReactivePowerL3() { + return setReactivePowerL3; + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/BalancedRandomLoadGenerator.java b/edge/src/io/openems/impl/device/simulator/BalancedRandomLoadGenerator.java new file mode 100644 index 00000000000..4b479138bcb --- /dev/null +++ b/edge/src/io/openems/impl/device/simulator/BalancedRandomLoadGenerator.java @@ -0,0 +1,133 @@ +package io.openems.impl.device.simulator; + +/** + * Created by maxo2 on 30.08.2017. + */ +import com.google.gson.JsonObject; + +public class BalancedRandomLoadGenerator implements LoadGenerator { + + private long min; + private long max; + private double last; + private int longTermMode; + private int midTermMode; + private int shortTermMode; + private double dayCircleSpeedFactor; + + public long getMin() { + return min; + } + + public void setMin(long min) { + this.min = min; + } + + public long getMax() { + return max; + } + + public void setMax(long max) { + this.max = max; + } + + public BalancedRandomLoadGenerator(JsonObject config) { + super(); + /** + * Try to get config values or use default ones instead. + */ + this.min = config.get("min").getAsLong(); + this.max = config.get("max").getAsLong(); + try { + this.last = config.get("start").getAsLong(); + if (this.last < this.min || this.last > this.max){ + throw new IllegalArgumentException(); + } + }catch (Exception e){ + this.last = (max + min) / 2; + } + try { + this.dayCircleSpeedFactor = config.get("dayCircleSpeedFactor").getAsDouble(); + if (this.dayCircleSpeedFactor <= 0) { + throw new IllegalArgumentException(); + } + }catch (Exception e) { + this.dayCircleSpeedFactor = 1; + } + longTermMode = 0; + midTermMode = 0; + shortTermMode = 0; + } + + public BalancedRandomLoadGenerator() { + /** + * Set default config values. + */ + this.min = -1000; + this.max = 1000; + last = (max + min) / 2; + longTermMode = 0; + midTermMode = 0; + shortTermMode = 0; + dayCircleSpeedFactor = 1; + } + + + public long getLoad() { + double nev = 0; + + /** + * Change modes by chance. The modes specify whether the according changeValue is positive or negative. + */ + + if(longTermMode == 0 || Math.random() < (dayCircleSpeedFactor * 0.00004) || ((last == max || last == min) && Math.random() < 0.0002)){ + if(Math.random() < 0.5){ + longTermMode = 1; + }else { + longTermMode = -1; + } + } + + if(midTermMode == 0 || Math.random() < (dayCircleSpeedFactor * 0.0002)){ + if(Math.random() < 0.5){ + midTermMode = 1; + }else { + midTermMode = -1; + } + } + + if(shortTermMode == 0 || Math.random() < (Math.sqrt(dayCircleSpeedFactor) * 0.33)){ + if(Math.random() < 0.5){ + shortTermMode = 1; + }else { + shortTermMode = -1; + } + } + + /** + * Calculate the change applied to the last value. This values are influenced by the dayCircleSpeedFactor and the difference + * between max and min value. While the longTermChange has a rather small gradient (it's simulating the sun's day circle), + * the midTermMode has a higher one, because it stands for the weather situation. + */ + + double longTermChange = dayCircleSpeedFactor * longTermMode * ((double) (max - min) / (0.6 * Math.pow(10,(double) (long) Math.log10(max - min) + 2))); + double midTermChange = dayCircleSpeedFactor * midTermMode * ((double) (max - min) / (1.5 * Math.pow(10,(double) (long) Math.log10(max - min) + 2))); + double shortTermChange = Math.sqrt(dayCircleSpeedFactor) * shortTermMode * ((double) (max - min) / (1.0 * Math.pow(10,(double) (long) Math.log10(max - min) + 2))); + + nev = last + (longTermChange + midTermChange + shortTermChange); + + /** + * Make sure the new value does not exceed the min and max values and return. + */ + if (nev > max){ + nev = max; + } + if (nev < min){ + nev = min; + } + last = nev; + return (long) nev; + } + +} + diff --git a/edge/src/io/openems/impl/device/simulator/CSVLoadGenerator.java b/edge/src/io/openems/impl/device/simulator/CSVLoadGenerator.java new file mode 100644 index 00000000000..d0fa580b966 --- /dev/null +++ b/edge/src/io/openems/impl/device/simulator/CSVLoadGenerator.java @@ -0,0 +1,86 @@ +package io.openems.impl.device.simulator; + +/** + * Created by maxo2 on 30.08.2017. + */ +import com.google.gson.JsonObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +public class CSVLoadGenerator implements LoadGenerator { + + private String filepath = ""; + private String columnKey = ""; + protected final Logger log = LoggerFactory.getLogger(this.getClass()); + private List values = new ArrayList<>(0); + private int count = 0; + private int columnPart = 0; + private String separator = ""; + + public CSVLoadGenerator(JsonObject config) { + super(); + /** + * Get config details. + */ + this.filepath = config.get("filepath").getAsString(); + this.columnKey = config.get("columnKey").getAsString(); + + /** + * Try to read the specified file and extract important information according the file's structure. + */ + try { + BufferedReader br = new BufferedReader(new FileReader(this.filepath)); + values = br.lines().collect(Collectors.toList()); + String[] str = values.get(0).split("="); + separator = str[str.length - 1]; + str = values.get(1).split(separator); + for (int i = 0; i < str.length; i++){ + if (str[i].equals(columnKey)){ + columnPart = i; + } + } + values.remove(1); + values.remove(0); + count = -1; + }catch (Exception e){ + log.error(e.getMessage(),e); + } + } + + public CSVLoadGenerator() {} + + + public long getLoad() { + long value = 0; + /** + * Get the next line, pick the defined column and parse to long. + */ + try { + count++; + String[] str = new String[0]; + try{ + str = values.get(count).split(separator); + }catch (IndexOutOfBoundsException e){ + /** + * Restart file after its end was reached. + */ + log.error(e.getMessage() + " --> index reset "); + count = 0; + str = values.get(count).split(separator); + } + value = (long) Double.parseDouble(str[columnPart]); + }catch (Exception e){ + log.error(e.getMessage(),e); + } + return value; + } + +} + diff --git a/edge/src/io/openems/impl/device/simulator/RandomLoadGenerator.java b/edge/src/io/openems/impl/device/simulator/RandomLoadGenerator.java index 8898880fdeb..a1492e48d2d 100644 --- a/edge/src/io/openems/impl/device/simulator/RandomLoadGenerator.java +++ b/edge/src/io/openems/impl/device/simulator/RandomLoadGenerator.java @@ -1,42 +1,54 @@ -package io.openems.impl.device.simulator; - -import com.google.gson.JsonObject; - -public class RandomLoadGenerator implements LoadGenerator { - - private long min; - private long max; - - public long getMin() { - return min; - } - - public void setMin(long min) { - this.min = min; - } - - public long getMax() { - return max; - } - - public void setMax(long max) { - this.max = max; - } - - public RandomLoadGenerator(JsonObject config) { - super(); - this.min = config.get("min").getAsLong(); - this.max = config.get("max").getAsLong(); - } - - public RandomLoadGenerator() { - this.min = -1000; - this.max = 1000; - } - - @Override - public long getLoad() { - return min + (int) (Math.random() * ((max - min) + 1)); - } - -} +package io.openems.impl.device.simulator; + +import com.google.gson.JsonObject; + +public class RandomLoadGenerator implements LoadGenerator { + + private long min; + private long max; + private int delta; + private long lastValue; + + public long getMin() { + return min; + } + + public void setMin(long min) { + this.min = min; + } + + public long getMax() { + return max; + } + + public void setMax(long max) { + this.max = max; + } + + public int getDelta() { + return delta; + } + + public void setDelta(int delta) { + this.delta = delta; + } + + public RandomLoadGenerator(JsonObject config) { + super(); + this.min = config.get("min").getAsLong(); + this.max = config.get("max").getAsLong(); + this.delta = config.get("delta").getAsInt(); + } + + public RandomLoadGenerator() { + this.min = -1000; + this.max = 1000; + } + + @Override + public long getLoad() { + this.lastValue = SimulatorTools.addRandomLong(lastValue, min, max, delta); + return lastValue; + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/Simulator.java b/edge/src/io/openems/impl/device/simulator/Simulator.java index 5b31a455b2c..516f73323fd 100644 --- a/edge/src/io/openems/impl/device/simulator/Simulator.java +++ b/edge/src/io/openems/impl/device/simulator/Simulator.java @@ -1,97 +1,97 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.simulator; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.simulator.SimulatorDevice; - -@ThingInfo(title = "Simulator") -public class Simulator extends SimulatorDevice { - - /* - * Constructors - */ - public Simulator() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "symmetric Ess", description = "Sets the symmetric Ess nature.", type = SimulatorSymmetricEss.class, isOptional = true) - public final ConfigChannel symmetricEss = new ConfigChannel<>("symmetricEss", this); - @ConfigInfo(title = "asymmetric Ess", description = "Sets the asymmetric Ess nature.", type = SimulatorAsymmetricEss.class, isOptional = true) - public final ConfigChannel asymmetricEss = new ConfigChannel<>("asymmetricEss", this); - - @ConfigInfo(title = "Charger", description = "Sets the Charger nature.", type = SimulatorCharger.class, isOptional = true) - public final ConfigChannel charger = new ConfigChannel<>("charger", this); - - @ConfigInfo(title = "Grid-Meter", description = "Sets the grid meter nature.", type = SimulatorGridMeter.class, isOptional = true) - public final ConfigChannel gridMeter = new ConfigChannel<>("gridMeter", this); - - @ConfigInfo(title = "Production-Meter", description = "Sets the production meter nature.", type = SimulatorProductionMeter.class, isOptional = true) - public final ConfigChannel productionMeter = new ConfigChannel<>("productionMeter", this); - - @ConfigInfo(title = "Sps", description = "Sets the Riedmann sps nature.", type = SimulatorRiedmannNature.class, isOptional = true) - public final ConfigChannel sps = new ConfigChannel<>("sps", this); - - @ConfigInfo(title = "Output", description = "Sets the output nature.", type = SimulatorOutput.class, isOptional = true) - public final ConfigChannel output = new ConfigChannel<>("output", this); - - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (symmetricEss.valueOptional().isPresent()) { - natures.add(symmetricEss.valueOptional().get()); - } - if (asymmetricEss.valueOptional().isPresent()) { - natures.add(asymmetricEss.valueOptional().get()); - } - if (gridMeter.valueOptional().isPresent()) { - natures.add(gridMeter.valueOptional().get()); - } - if (productionMeter.valueOptional().isPresent()) { - natures.add(productionMeter.valueOptional().get()); - } - if (charger.valueOptional().isPresent()) { - natures.add(charger.valueOptional().get()); - } - if (sps.valueOptional().isPresent()) { - natures.add(sps.valueOptional().get()); - } - if (output.valueOptional().isPresent()) { - natures.add(output.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.simulator; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.simulator.SimulatorDevice; + +@ThingInfo(title = "Simulator") +public class Simulator extends SimulatorDevice { + + /* + * Constructors + */ + public Simulator(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "symmetric Ess", description = "Sets the symmetric Ess nature.", type = SimulatorSymmetricEss.class, isOptional = true) + public final ConfigChannel symmetricEss = new ConfigChannel<>("symmetricEss", this); + @ConfigInfo(title = "asymmetric Ess", description = "Sets the asymmetric Ess nature.", type = SimulatorAsymmetricEss.class, isOptional = true) + public final ConfigChannel asymmetricEss = new ConfigChannel<>("asymmetricEss", this); + + @ConfigInfo(title = "Charger", description = "Sets the Charger nature.", type = SimulatorCharger.class, isOptional = true) + public final ConfigChannel charger = new ConfigChannel<>("charger", this); + + @ConfigInfo(title = "Grid-Meter", description = "Sets the grid meter nature.", type = SimulatorGridMeter.class, isOptional = true) + public final ConfigChannel gridMeter = new ConfigChannel<>("gridMeter", this); + + @ConfigInfo(title = "Production-Meter", description = "Sets the production meter nature.", type = SimulatorProductionMeter.class, isOptional = true) + public final ConfigChannel productionMeter = new ConfigChannel<>("productionMeter", this); + + @ConfigInfo(title = "Sps", description = "Sets the Riedmann sps nature.", type = SimulatorRiedmannNature.class, isOptional = true) + public final ConfigChannel sps = new ConfigChannel<>("sps", this); + + @ConfigInfo(title = "Output", description = "Sets the output nature.", type = SimulatorOutput.class, isOptional = true) + public final ConfigChannel output = new ConfigChannel<>("output", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (symmetricEss.valueOptional().isPresent()) { + natures.add(symmetricEss.valueOptional().get()); + } + if (asymmetricEss.valueOptional().isPresent()) { + natures.add(asymmetricEss.valueOptional().get()); + } + if (gridMeter.valueOptional().isPresent()) { + natures.add(gridMeter.valueOptional().get()); + } + if (productionMeter.valueOptional().isPresent()) { + natures.add(productionMeter.valueOptional().get()); + } + if (charger.valueOptional().isPresent()) { + natures.add(charger.valueOptional().get()); + } + if (sps.valueOptional().isPresent()) { + natures.add(sps.valueOptional().get()); + } + if (output.valueOptional().isPresent()) { + natures.add(output.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorAsymmetricEss.java b/edge/src/io/openems/impl/device/simulator/SimulatorAsymmetricEss.java index 2247c7c3d88..2c66798b3e9 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorAsymmetricEss.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorAsymmetricEss.java @@ -1,391 +1,419 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.simulator; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ThreadLocalRandom; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.charger.ChargerNature; -import io.openems.api.device.nature.ess.AsymmetricEssNature; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.thing.Thing; -import io.openems.core.ThingRepository; -import io.openems.core.utilities.AvgFiFoQueue; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.simulator.SimulatorDeviceNature; -import io.openems.impl.protocol.simulator.SimulatorReadChannel; -import io.openems.test.utils.channel.UnitTestWriteChannel; - -@ThingInfo(title = "Simulator ESS") -public class SimulatorAsymmetricEss extends SimulatorDeviceNature - implements AsymmetricEssNature, ChannelChangeListener { - - private List chargerList; - private ThingRepository repo = ThingRepository.getInstance(); - private LoadGenerator offGridActivePowerGenerator = new RandomLoadGenerator(); - private LoadGenerator offGridReactivePowerGenerator = new RandomLoadGenerator(); - - /* - * Constructors - */ - public SimulatorAsymmetricEss(String thingId) throws ConfigException { - super(thingId); - minSoc.addUpdateListener((channel, newValue) -> { - // If chargeSoc was not set -> set it to minSoc minus 2 - if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { - chargeSoc.updateValue((Integer) newValue.get() - 2, false); - } - }); - long initialSoc = SimulatorTools.addRandomLong(50, 0, 100, 20); - this.energy = capacity.valueOptional().get() / 100 * initialSoc; - this.soc = new FunctionalReadChannel("Soc", this, new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - try { - energy -= (channels[0].value() + channels[1].value() + channels[2].value()) / 3600.0; - } catch (InvalidValueException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - if (chargerList != null) { - for (ChargerNature charger : chargerList) { - try { - energy += charger.getActualPower().value() / 3600.0; - } catch (InvalidValueException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - try { - if (energy > capacity.value()) { - energy = capacity.value(); - } else if (energy < 0) { - energy = 0; - } - return (long) (energy / capacity.value() * 100.0); - } catch (InvalidValueException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return 0L; - } - - }, this.activePowerL1, this.activePowerL2, this.activePowerL3); - } - - private double energy; - private AvgFiFoQueue activePowerQueueL1 = new AvgFiFoQueue(5, 1); - private AvgFiFoQueue activePowerQueueL2 = new AvgFiFoQueue(5, 1); - private AvgFiFoQueue activePowerQueueL3 = new AvgFiFoQueue(5, 1); - private AvgFiFoQueue reactivePowerQueueL1 = new AvgFiFoQueue(5, 1); - private AvgFiFoQueue reactivePowerQueueL2 = new AvgFiFoQueue(5, 1); - private AvgFiFoQueue reactivePowerQueueL3 = new AvgFiFoQueue(5, 1); - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - private StatusBitChannels warning = new StatusBitChannels("Warning", this);; - private FunctionalReadChannel soc; - private SimulatorReadChannel activePowerL1 = new SimulatorReadChannel("ActivePoweL1", this); - private SimulatorReadChannel activePowerL2 = new SimulatorReadChannel("ActivePoweL2", this); - private SimulatorReadChannel activePowerL3 = new SimulatorReadChannel("ActivePoweL3", this); - private SimulatorReadChannel allowedApparent = new SimulatorReadChannel("AllowedApparent", this); - private SimulatorReadChannel allowedCharge = new SimulatorReadChannel("AllowedCharge", this); - private SimulatorReadChannel allowedDischarge = new SimulatorReadChannel("AllowedDischarge", this); - private SimulatorReadChannel gridMode = new SimulatorReadChannel("GridMode", this).label(0, ON_GRID).label(1, - OFF_GRID); - private SimulatorReadChannel reactivePowerL1 = new SimulatorReadChannel("ReactivePowerL1", this); - private SimulatorReadChannel reactivePowerL2 = new SimulatorReadChannel("ReactivePowerL2", this); - private SimulatorReadChannel reactivePowerL3 = new SimulatorReadChannel("ReactivePowerL3", this); - private SimulatorReadChannel systemState = new SimulatorReadChannel("SystemState", this) // - .label(1, START).label(2, STOP); - private UnitTestWriteChannel setActivePowerL1 = new UnitTestWriteChannel("SetActivePowerL1", this); - private UnitTestWriteChannel setActivePowerL2 = new UnitTestWriteChannel("SetActivePowerL2", this); - private UnitTestWriteChannel setActivePowerL3 = new UnitTestWriteChannel("SetActivePowerL3", this); - private UnitTestWriteChannel setReactivePowerL1 = new UnitTestWriteChannel("SetReactivePowerL1", this); - private UnitTestWriteChannel setReactivePowerL2 = new UnitTestWriteChannel("SetReactivePowerL2", this); - private UnitTestWriteChannel setReactivePowerL3 = new UnitTestWriteChannel("SetReactivePowerL3", this); - private ModbusWriteLongChannel setWorkState = new ModbusWriteLongChannel("SetWorkState", this); - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) - .unit("VA"); - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 5000L).unit("Wh"); - @ConfigInfo(title = "charger", type = JsonArray.class, isOptional = true) - public ConfigChannel charger = new ConfigChannel("charger", this).addChangeListener(this); - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - private long getRandom(int min, int max) { - return ThreadLocalRandom.current().nextLong(min, max + 1); - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - /* - * Fields - */ - private long lastApparentPower = 0; - private double lastCosPhi = 0; - - /* - * Methods - */ - @Override - protected void update() { - if (chargerList == null) { - chargerList = new ArrayList<>(); - getCharger(); - } - Optional writtenActivePowerL1 = setActivePowerL1.getWrittenValue(); - if (writtenActivePowerL1.isPresent()) { - activePowerQueueL1.add(writtenActivePowerL1.get()); - } - Optional writtenActivePowerL2 = setActivePowerL2.getWrittenValue(); - if (writtenActivePowerL2.isPresent()) { - activePowerQueueL2.add(writtenActivePowerL2.get()); - } - Optional writtenActivePowerL3 = setActivePowerL3.getWrittenValue(); - if (writtenActivePowerL3.isPresent()) { - activePowerQueueL3.add(writtenActivePowerL3.get()); - } - Optional writtenReactivePowerL1 = setReactivePowerL1.getWrittenValue(); - if (writtenReactivePowerL1.isPresent()) { - reactivePowerQueueL1.add(writtenReactivePowerL1.get()); - } - Optional writtenReactivePowerL2 = setReactivePowerL2.getWrittenValue(); - if (writtenReactivePowerL2.isPresent()) { - reactivePowerQueueL2.add(writtenReactivePowerL2.get()); - } - Optional writtenReactivePowerL3 = setReactivePowerL3.getWrittenValue(); - if (writtenReactivePowerL3.isPresent()) { - reactivePowerQueueL3.add(writtenReactivePowerL3.get()); - } - // lastApparentPower = SimulatorTools.addRandomLong(lastApparentPower, -10000, 10000, 500); - // lastCosPhi = SimulatorTools.addRandomDouble(lastCosPhi, -1.5, 1.5, 0.5); - // - // long activePower = ControllerUtils.calculateActivePowerFromApparentPower(lastApparentPower, lastCosPhi); - // long reactivePower = ControllerUtils.calculateReactivePower(activePower, lastCosPhi); - long activePowerL1 = 0; - long activePowerL2 = 0; - long activePowerL3 = 0; - long reactivePowerL1 = 0; - long reactivePowerL2 = 0; - long reactivePowerL3 = 0; - if (this.gridMode.labelOptional().equals(Optional.of(EssNature.OFF_GRID))) { - activePowerL1 = offGridActivePowerGenerator.getLoad() / 3; - activePowerL2 = offGridActivePowerGenerator.getLoad() / 3; - activePowerL3 = offGridActivePowerGenerator.getLoad() / 3; - reactivePowerL1 = offGridReactivePowerGenerator.getLoad() / 3; - reactivePowerL2 = offGridReactivePowerGenerator.getLoad() / 3; - reactivePowerL3 = offGridReactivePowerGenerator.getLoad() / 3; - } else { - activePowerL1 = activePowerQueueL1.avg(); - activePowerL2 = activePowerQueueL2.avg(); - activePowerL3 = activePowerQueueL3.avg(); - reactivePowerL1 = reactivePowerQueueL1.avg(); - reactivePowerL2 = reactivePowerQueueL2.avg(); - reactivePowerL3 = reactivePowerQueueL3.avg(); - } - this.activePowerL1.updateValue(activePowerL1); - this.activePowerL2.updateValue(activePowerL2); - this.activePowerL3.updateValue(activePowerL3); - this.reactivePowerL1.updateValue(reactivePowerL1); - this.reactivePowerL2.updateValue(reactivePowerL2); - this.reactivePowerL3.updateValue(reactivePowerL3); - this.allowedCharge.updateValue(-9000L); - this.allowedDischarge.updateValue(3000L); - this.systemState.updateValue(1L); - this.gridMode.updateValue(0L); - } - - @Override - public StaticValueChannel capacity() { - return capacity; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public WriteChannel setActivePowerL1() { - return setActivePowerL1; - } - - @Override - public WriteChannel setActivePowerL2() { - return setActivePowerL2; - } - - @Override - public WriteChannel setActivePowerL3() { - return setActivePowerL3; - } - - @Override - public WriteChannel setReactivePowerL1() { - return setReactivePowerL1; - } - - @Override - public WriteChannel setReactivePowerL2() { - return setReactivePowerL2; - } - - @Override - public WriteChannel setReactivePowerL3() { - return setReactivePowerL3; - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel.equals(charger)) { - if (chargerList != null) { - getCharger(); - } - } - } - - private void getCharger() { - if (chargerList != null) { - for (ChargerNature charger : chargerList) { - soc.removeChannel(charger.getActualPower()); - } - chargerList.clear(); - if (charger.valueOptional().isPresent()) { - JsonArray ids = charger.valueOptional().get(); - for (JsonElement e : ids) { - Optional t = repo.getThingById(e.getAsString()); - if (t.isPresent()) { - if (t.get() instanceof ChargerNature) { - ChargerNature charger = (ChargerNature) t.get(); - chargerList.add(charger); - soc.addChannel(charger.getActualPower()); - } - } - } - } - } - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.simulator; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.charger.ChargerNature; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.thing.Thing; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.AvgFiFoQueue; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.simulator.SimulatorDeviceNature; +import io.openems.impl.protocol.simulator.SimulatorReadChannel; +import io.openems.test.utils.channel.UnitTestWriteChannel; + +@ThingInfo(title = "Simulator ESS") +public class SimulatorAsymmetricEss extends SimulatorDeviceNature + implements AsymmetricEssNature, ChannelChangeListener { + + private List chargerList; + private ThingRepository repo = ThingRepository.getInstance(); + private LoadGenerator offGridActivePowerGenerator = new RandomLoadGenerator(); + private LoadGenerator offGridReactivePowerGenerator = new RandomLoadGenerator(); + + /* + * Constructors + */ + public SimulatorAsymmetricEss(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + minSoc.addUpdateListener((channel, newValue) -> { + // If chargeSoc was not set -> set it to minSoc minus 2 + if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { + chargeSoc.updateValue((Integer) newValue.get() - 2, false); + } + }); + long initialSoc = SimulatorTools.addRandomLong(90, 90, 100, 5); + this.energy = capacity.valueOptional().get() / 100 * initialSoc; + this.soc = new FunctionalReadChannel("Soc", this, new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + try { + energy -= (channels[0].value() + channels[1].value() + channels[2].value()) / 3600.0; + } catch (InvalidValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (chargerList != null) { + for (ChargerNature charger : chargerList) { + try { + energy += charger.getActualPower().value() / 3600.0; + } catch (InvalidValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + try { + if (energy > capacity.value()) { + energy = capacity.value(); + } else if (energy < 0) { + energy = 0; + } + return (long) (energy / capacity.value() * 100.0); + } catch (InvalidValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return 0L; + } + + }, this.activePowerL1, this.activePowerL2, this.activePowerL3); + } + + private double energy; + private AvgFiFoQueue activePowerQueueL1 = new AvgFiFoQueue(5, 1); + private AvgFiFoQueue activePowerQueueL2 = new AvgFiFoQueue(5, 1); + private AvgFiFoQueue activePowerQueueL3 = new AvgFiFoQueue(5, 1); + private AvgFiFoQueue reactivePowerQueueL1 = new AvgFiFoQueue(5, 1); + private AvgFiFoQueue reactivePowerQueueL2 = new AvgFiFoQueue(5, 1); + private AvgFiFoQueue reactivePowerQueueL3 = new AvgFiFoQueue(5, 1); + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + @ConfigInfo(title = "Power", type = Long.class, isOptional = true) + public ConfigChannel power = new ConfigChannel("Power", this); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + private StatusBitChannels warning = new StatusBitChannels("Warning", this);; + private FunctionalReadChannel soc; + private SimulatorReadChannel activePowerL1 = new SimulatorReadChannel<>("ActivePoweL1", this); + private SimulatorReadChannel activePowerL2 = new SimulatorReadChannel<>("ActivePoweL2", this); + private SimulatorReadChannel activePowerL3 = new SimulatorReadChannel<>("ActivePoweL3", this); + private StaticValueChannel allowedApparent = new StaticValueChannel("AllowedApparent", this, (40000L / 3)); + private SimulatorReadChannel allowedCharge = new SimulatorReadChannel<>("AllowedCharge", this); + private SimulatorReadChannel allowedDischarge = new SimulatorReadChannel<>("AllowedDischarge", this); + private SimulatorReadChannel gridMode = new SimulatorReadChannel("GridMode", this).label(0L, ON_GRID) + .label(1L, OFF_GRID); + private SimulatorReadChannel reactivePowerL1 = new SimulatorReadChannel<>("ReactivePowerL1", this); + private SimulatorReadChannel reactivePowerL2 = new SimulatorReadChannel<>("ReactivePowerL2", this); + private SimulatorReadChannel reactivePowerL3 = new SimulatorReadChannel<>("ReactivePowerL3", this); + private SimulatorReadChannel systemState = new SimulatorReadChannel("SystemState", this) // + .label(1L, START).label(2L, STOP); + private UnitTestWriteChannel setActivePowerL1 = new UnitTestWriteChannel("SetActivePowerL1", this); + private UnitTestWriteChannel setActivePowerL2 = new UnitTestWriteChannel("SetActivePowerL2", this); + private UnitTestWriteChannel setActivePowerL3 = new UnitTestWriteChannel("SetActivePowerL3", this); + private UnitTestWriteChannel setReactivePowerL1 = new UnitTestWriteChannel("SetReactivePowerL1", this); + private UnitTestWriteChannel setReactivePowerL2 = new UnitTestWriteChannel("SetReactivePowerL2", this); + private UnitTestWriteChannel setReactivePowerL3 = new UnitTestWriteChannel("SetReactivePowerL3", this); + private ModbusWriteLongChannel setWorkState = new ModbusWriteLongChannel("SetWorkState", this).label(1L, START) + .label(2L, STOP); + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) + .unit("VA"); + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 5000L).unit("Wh"); + @ConfigInfo(title = "charger", type = JsonArray.class, isOptional = true) + public ConfigChannel charger = new ConfigChannel("charger", this).addChangeListener(this); + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + private long getRandom(int min, int max) { + return ThreadLocalRandom.current().nextLong(min, max + 1); + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + /* + * Fields + */ + private long lastApparentPower = 0; + private double lastCosPhi = 0; + + /* + * Methods + */ + @Override + protected void update() { + if (chargerList == null) { + chargerList = new ArrayList<>(); + getCharger(); + } + if (power.valueOptional().isPresent() && power.valueOptional().get() != 0) { + activePowerQueueL1.add(power.valueOptional().get() / 3); + activePowerQueueL2.add(power.valueOptional().get() / 3); + activePowerQueueL3.add(power.valueOptional().get() / 3); + } else { + Optional writtenActivePowerL1 = setActivePowerL1.getWrittenValue(); + if (writtenActivePowerL1.isPresent()) { + activePowerQueueL1.add(writtenActivePowerL1.get()); + } + Optional writtenActivePowerL2 = setActivePowerL2.getWrittenValue(); + if (writtenActivePowerL2.isPresent()) { + activePowerQueueL2.add(writtenActivePowerL2.get()); + } + Optional writtenActivePowerL3 = setActivePowerL3.getWrittenValue(); + if (writtenActivePowerL3.isPresent()) { + activePowerQueueL3.add(writtenActivePowerL3.get()); + } + Optional writtenReactivePowerL1 = setReactivePowerL1.getWrittenValue(); + if (writtenReactivePowerL1.isPresent()) { + reactivePowerQueueL1.add(writtenReactivePowerL1.get()); + } + Optional writtenReactivePowerL2 = setReactivePowerL2.getWrittenValue(); + if (writtenReactivePowerL2.isPresent()) { + reactivePowerQueueL2.add(writtenReactivePowerL2.get()); + } + Optional writtenReactivePowerL3 = setReactivePowerL3.getWrittenValue(); + if (writtenReactivePowerL3.isPresent()) { + reactivePowerQueueL3.add(writtenReactivePowerL3.get()); + } + } + // lastApparentPower = SimulatorTools.addRandomLong(lastApparentPower, -10000, 10000, 500); + // lastCosPhi = SimulatorTools.addRandomDouble(lastCosPhi, -1.5, 1.5, 0.5); + // + // long activePower = ControllerUtils.calculateActivePowerFromApparentPower(lastApparentPower, lastCosPhi); + // long reactivePower = ControllerUtils.calculateReactivePower(activePower, lastCosPhi); + long activePowerL1 = 0; + long activePowerL2 = 0; + long activePowerL3 = 0; + long reactivePowerL1 = 0; + long reactivePowerL2 = 0; + long reactivePowerL3 = 0; + if (this.gridMode.labelOptional().equals(Optional.of(EssNature.OFF_GRID))) { + activePowerL1 = offGridActivePowerGenerator.getLoad() / 3; + activePowerL2 = offGridActivePowerGenerator.getLoad() / 3; + activePowerL3 = offGridActivePowerGenerator.getLoad() / 3; + reactivePowerL1 = offGridReactivePowerGenerator.getLoad() / 3; + reactivePowerL2 = offGridReactivePowerGenerator.getLoad() / 3; + reactivePowerL3 = offGridReactivePowerGenerator.getLoad() / 3; + } else { + activePowerL1 = activePowerQueueL1.avg(); + activePowerL2 = activePowerQueueL2.avg(); + activePowerL3 = activePowerQueueL3.avg(); + reactivePowerL1 = reactivePowerQueueL1.avg(); + reactivePowerL2 = reactivePowerQueueL2.avg(); + reactivePowerL3 = reactivePowerQueueL3.avg(); + } + this.activePowerL1.updateValue(activePowerL1); + this.activePowerL2.updateValue(activePowerL2); + this.activePowerL3.updateValue(activePowerL3); + this.reactivePowerL1.updateValue(reactivePowerL1); + this.reactivePowerL2.updateValue(reactivePowerL2); + this.reactivePowerL3.updateValue(reactivePowerL3); + this.allowedCharge.updateValue(-9000L); + this.allowedDischarge.updateValue(3000L); + this.systemState.updateValue(1L); + this.gridMode.updateValue(0L); + try { + long multiplier = 100 - this.soc.value(); + if (multiplier > 10) { + multiplier = 10; + } + this.allowedCharge.updateValue((maxNominalPower.value() / 10 * multiplier) * -1); + } catch (InvalidValueException e) { + e.printStackTrace(); + } + try { + long multiplier = this.soc.value(); + if (multiplier > 10) { + multiplier = 10; + } + this.allowedDischarge.updateValue(maxNominalPower.value() / 10 * multiplier); + } catch (InvalidValueException e) { + e.printStackTrace(); + } + } + + @Override + public StaticValueChannel capacity() { + return capacity; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public WriteChannel setActivePowerL1() { + return setActivePowerL1; + } + + @Override + public WriteChannel setActivePowerL2() { + return setActivePowerL2; + } + + @Override + public WriteChannel setActivePowerL3() { + return setActivePowerL3; + } + + @Override + public WriteChannel setReactivePowerL1() { + return setReactivePowerL1; + } + + @Override + public WriteChannel setReactivePowerL2() { + return setReactivePowerL2; + } + + @Override + public WriteChannel setReactivePowerL3() { + return setReactivePowerL3; + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(charger)) { + if (chargerList != null) { + getCharger(); + } + } + } + + private void getCharger() { + if (chargerList != null) { + for (ChargerNature charger : chargerList) { + soc.removeChannel(charger.getActualPower()); + } + chargerList.clear(); + if (charger.valueOptional().isPresent()) { + JsonArray ids = charger.valueOptional().get(); + for (JsonElement e : ids) { + Optional t = repo.getThingById(e.getAsString()); + if (t.isPresent()) { + if (t.get() instanceof ChargerNature) { + ChargerNature charger = (ChargerNature) t.get(); + chargerList.add(charger); + soc.addChannel(charger.getActualPower()); + } + } + } + } + } + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorCharger.java b/edge/src/io/openems/impl/device/simulator/SimulatorCharger.java index 704d5571891..c487028844c 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorCharger.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorCharger.java @@ -1,103 +1,104 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.simulator; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.charger.ChargerNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.simulator.SimulatorDeviceNature; -import io.openems.impl.protocol.simulator.SimulatorReadChannel; - -@ThingInfo(title = "Simulator Charger") -public class SimulatorCharger extends SimulatorDeviceNature implements ChargerNature { - - /* - * Constructors - */ - public SimulatorCharger(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config-Channels - */ - @ConfigInfo(title = "PowerConfig", type = Long.class) - public ConfigChannel powerConfig = new ConfigChannel<>("powerConfig", this); - - /* - * Inherited Channels - */ - private SimulatorReadChannel voltage = new SimulatorReadChannel("InputVoltage", this).unit("mV"); - private SimulatorReadChannel power = new SimulatorReadChannel("ActualPower", this).unit("W"); - private StaticValueChannel nominalPower = new StaticValueChannel("NominalPower", this, 60000l); - private ModbusWriteLongChannel setMaxPower = new ModbusWriteLongChannel("SetMaxPower", this); - - /* - * Fields - */ - private long lastVoltage = 0; - - private final ConfigChannel maxActualPower = new ConfigChannel("maxActualPower", this); - - @Override - public ConfigChannel maxActualPower() { - return maxActualPower; - } - - /* - * Methods - */ - @Override - protected void update() { - long power = SimulatorTools.addRandomLong(powerConfig.valueOptional().orElse(0L), 0, - (long) (powerConfig.valueOptional().orElse(0L) * 1.10), 100); - lastVoltage = SimulatorTools.addRandomLong(lastVoltage, 300000, 600000, 100); - this.voltage.updateValue(lastVoltage); - this.power.updateValue(power); - } - - @Override - public WriteChannel setMaxPower() { - return setMaxPower; - } - - @Override - public ReadChannel getActualPower() { - return power; - } - - @Override - public ReadChannel getNominalPower() { - return nominalPower; - } - - @Override - public ReadChannel getInputVoltage() { - return voltage; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.simulator; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.charger.ChargerNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.simulator.SimulatorDeviceNature; +import io.openems.impl.protocol.simulator.SimulatorReadChannel; + +@ThingInfo(title = "Simulator Charger") +public class SimulatorCharger extends SimulatorDeviceNature implements ChargerNature { + + /* + * Constructors + */ + public SimulatorCharger(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config-Channels + */ + @ConfigInfo(title = "PowerConfig", type = Long.class) + public ConfigChannel powerConfig = new ConfigChannel<>("powerConfig", this); + + /* + * Inherited Channels + */ + private SimulatorReadChannel voltage = new SimulatorReadChannel("InputVoltage", this).unit("mV"); + private SimulatorReadChannel power = new SimulatorReadChannel("ActualPower", this).unit("W"); + private StaticValueChannel nominalPower = new StaticValueChannel("NominalPower", this, 60000l); + private ModbusWriteLongChannel setMaxPower = new ModbusWriteLongChannel("SetMaxPower", this); + + /* + * Fields + */ + private long lastVoltage = 0; + + private final ConfigChannel maxActualPower = new ConfigChannel("maxActualPower", this); + + @Override + public ConfigChannel maxActualPower() { + return maxActualPower; + } + + /* + * Methods + */ + @Override + protected void update() { + long power = SimulatorTools.addRandomLong(powerConfig.valueOptional().orElse(0L), 0, + (long) (powerConfig.valueOptional().orElse(0L) * 1.10), 100); + lastVoltage = SimulatorTools.addRandomLong(lastVoltage, 300000, 600000, 100); + this.voltage.updateValue(lastVoltage); + this.power.updateValue(power); + } + + @Override + public WriteChannel setMaxPower() { + return setMaxPower; + } + + @Override + public ReadChannel getActualPower() { + return power; + } + + @Override + public ReadChannel getNominalPower() { + return nominalPower; + } + + @Override + public ReadChannel getInputVoltage() { + return voltage; + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorGridMeter.java b/edge/src/io/openems/impl/device/simulator/SimulatorGridMeter.java index 05b0ec7f8c2..6176de42f70 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorGridMeter.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorGridMeter.java @@ -1,254 +1,374 @@ -package io.openems.impl.device.simulator; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.ess.AsymmetricEssNature; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.api.device.nature.meter.MeterNature; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.thing.Thing; -import io.openems.core.ThingRepository; -import io.openems.core.ThingsChangedListener; -import io.openems.core.utilities.ControllerUtils; -import io.openems.impl.protocol.simulator.SimulatorReadChannel; - -public class SimulatorGridMeter extends SimulatorMeter implements ChannelChangeListener { - - private SimulatorReadChannel activePower = new SimulatorReadChannel("ActivePower", this); - private FunctionalReadChannel apparentPower; - private SimulatorReadChannel reactivePower = new SimulatorReadChannel("ReactivePower", this); - @ConfigInfo(title = "esss", type = JsonArray.class) - public ConfigChannel esss = new ConfigChannel("esss", this).addChangeListener(this); - @ConfigInfo(title = "producer", type = JsonArray.class) - public ConfigChannel producer = new ConfigChannel("producer", this).addChangeListener(this); - @ConfigInfo(title = "ActivePowerGeneratorConfig", type = JsonObject.class) - public ConfigChannel activePowerGeneratorConfig = new ConfigChannel( - "activePowerGeneratorConfig", this).addChangeListener(this); - @ConfigInfo(title = "ReactivePowerGeneratorConfig", type = JsonObject.class) - public ConfigChannel reactivePowerGeneratorConfig = new ConfigChannel( - "reactivePowerGeneratorConfig", this).addChangeListener(this); - - private ThingRepository repo = ThingRepository.getInstance(); - private List essNatures = new ArrayList<>(); - private List meterNatures = new ArrayList<>(); - private LoadGenerator activePowerLoad; - private LoadGenerator reactivePowerLoad; - - public SimulatorGridMeter(String thingId) throws ConfigException { - super(thingId); - repo.addThingChangedListener(new ThingsChangedListener() { - - @Override - public void thingChanged(Thing thing, Action action) { - if (esss.valueOptional().isPresent()) { - JsonArray ids = esss.valueOptional().get(); - for (JsonElement id : ids) { - if (id.getAsString().equals(thing.id())) { - getEssNatures(); - } - } - } - if (producer.valueOptional().isPresent()) { - JsonArray ids = producer.valueOptional().get(); - for (JsonElement id : ids) { - if (id.getAsString().equals(thing.id())) { - getMeterNatures(); - } - } - } - } - }); - this.apparentPower = new FunctionalReadChannel("ApparentPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - return ControllerUtils.calculateApparentPower(channels[0].valueOptional().orElse(0L), - channels[1].valueOptional().orElse(0L)); - } - - }, activePower, reactivePower); - - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel.equals(esss)) { - if (essNatures != null) { - getEssNatures(); - } - } else if (channel.equals(producer)) { - if (meterNatures != null) { - getMeterNatures(); - } - } else if (channel.equals(activePowerGeneratorConfig)) { - if (activePowerGeneratorConfig.valueOptional().isPresent()) { - activePowerLoad = getGenerator(activePowerGeneratorConfig.valueOptional().get()); - } - } else if (channel.equals(reactivePowerGeneratorConfig)) { - if (reactivePowerGeneratorConfig.valueOptional().isPresent()) { - reactivePowerLoad = getGenerator(reactivePowerGeneratorConfig.valueOptional().get()); - } - } - } - - private LoadGenerator getGenerator(JsonObject config) { - try { - Class clazz = Class.forName(config.get("className").getAsString()); - if (config.get("config") != null) { - try { - Constructor constructor = clazz.getConstructor(JsonObject.class); - return (LoadGenerator) constructor.newInstance(config.get("config").getAsJsonObject()); - } catch (NoSuchMethodException e) { - - } - } - return (LoadGenerator) clazz.newInstance(); - - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ClassNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; - } - - private void getEssNatures() { - essNatures.clear(); - if (esss.valueOptional().isPresent()) { - JsonArray ids = esss.valueOptional().get(); - for (JsonElement e : ids) { - Optional t = repo.getThingById(e.getAsString()); - if (t.isPresent()) { - if (t.get() instanceof EssNature) { - essNatures.add((EssNature) t.get()); - } - } - } - } - } - - private void getMeterNatures() { - meterNatures.clear(); - if (producer.valueOptional().isPresent()) { - JsonArray ids = producer.valueOptional().get(); - for (JsonElement e : ids) { - Optional t = repo.getThingById(e.getAsString()); - if (t.isPresent()) { - if (t.get() instanceof MeterNature) { - meterNatures.add((MeterNature) t.get()); - } - } - } - } - } - - @Override - protected void update() { - super.update(); - long activePower = 0; - if (activePowerLoad != null) { - activePower = activePowerLoad.getLoad(); - } - activePower = activePower + SimulatorTools.getRandomLong(-1000, +1000); - long reactivePower = 0; - if (reactivePowerLoad != null) { - reactivePower = reactivePowerLoad.getLoad(); - } - for (EssNature entry : essNatures) { - if (entry instanceof SymmetricEssNature) { - SymmetricEssNature ess = (SymmetricEssNature) entry; - activePower -= ess.activePower().valueOptional().orElse(0L); - reactivePower -= ess.reactivePower().valueOptional().orElse(0L); - } else if (entry instanceof AsymmetricEssNature) { - AsymmetricEssNature ess = (AsymmetricEssNature) entry; - activePower -= ess.activePowerL1().valueOptional().orElse(0L) - + ess.activePowerL2().valueOptional().orElse(0L) - + ess.activePowerL3().valueOptional().orElse(0L); - reactivePower -= ess.reactivePowerL1().valueOptional().orElse(0L) - + ess.reactivePowerL2().valueOptional().orElse(0L) - + ess.reactivePowerL3().valueOptional().orElse(0L); - } - } - for (MeterNature entry : meterNatures) { - System.out.println(entry.id()); - if (entry instanceof SymmetricMeterNature) { - SymmetricMeterNature meter = (SymmetricMeterNature) entry; - activePower -= meter.activePower().valueOptional().orElse(0L); - reactivePower -= meter.reactivePower().valueOptional().orElse(0L); - } else if (entry instanceof AsymmetricMeterNature) { - AsymmetricMeterNature meter = (AsymmetricMeterNature) entry; - activePower += meter.activePowerL1().valueOptional().orElse(0L) - + meter.activePowerL2().valueOptional().orElse(0L) - + meter.activePowerL3().valueOptional().orElse(0L); - reactivePower += meter.reactivePowerL1().valueOptional().orElse(0L) - + meter.reactivePowerL2().valueOptional().orElse(0L) - + meter.reactivePowerL3().valueOptional().orElse(0L); - } - } - if (isOffGrid(essNatures)) { - this.activePower.updateValue(null); - this.reactivePower.updateValue(null); - } else { - this.activePower.updateValue(activePower); - this.reactivePower.updateValue(reactivePower); - } - } - - private boolean isOffGrid(List esss) { - for (EssNature ess : esss) { - if (ess.gridMode().labelOptional().equals(Optional.of(EssNature.OFF_GRID))) { - return true; - } - } - return false; - } - -} +package io.openems.impl.device.simulator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.MeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.thing.Thing; +import io.openems.core.ThingRepository; +import io.openems.core.ThingsChangedListener; +import io.openems.core.utilities.ControllerUtils; +import io.openems.impl.protocol.simulator.SimulatorReadChannel; + +public class SimulatorGridMeter extends SimulatorMeter implements ChannelChangeListener, AsymmetricMeterNature { + + private SimulatorReadChannel activePower = new SimulatorReadChannel<>("ActivePower", this); + private SimulatorReadChannel activePowerL1 = new SimulatorReadChannel<>("ActivePowerL1", this); + private SimulatorReadChannel activePowerL2 = new SimulatorReadChannel<>("ActivePowerL2", this); + private SimulatorReadChannel activePowerL3 = new SimulatorReadChannel<>("ActivePowerL3", this); + private FunctionalReadChannel apparentPower; + private SimulatorReadChannel reactivePower = new SimulatorReadChannel<>("ReactivePower", this); + private SimulatorReadChannel reactivePowerL1 = new SimulatorReadChannel<>("ReactivePowerL1", this); + private SimulatorReadChannel reactivePowerL2 = new SimulatorReadChannel<>("ReactivePowerL2", this); + private SimulatorReadChannel reactivePowerL3 = new SimulatorReadChannel<>("ReactivePowerL3", this); + @ConfigInfo(title = "esss", type = JsonArray.class) + public ConfigChannel esss = new ConfigChannel("esss", this).addChangeListener(this); + @ConfigInfo(title = "producer", type = JsonArray.class) + public ConfigChannel producer = new ConfigChannel("producer", this).addChangeListener(this); + @ConfigInfo(title = "ActivePowerGeneratorConfig", type = JsonObject.class) + public ConfigChannel activePowerGeneratorConfig = new ConfigChannel( + "activePowerGeneratorConfig", this).addChangeListener(this); + @ConfigInfo(title = "ReactivePowerGeneratorConfig", type = JsonObject.class) + public ConfigChannel reactivePowerGeneratorConfig = new ConfigChannel( + "reactivePowerGeneratorConfig", this).addChangeListener(this); + + private ThingRepository repo = ThingRepository.getInstance(); + private List essNatures = new ArrayList<>(); + private List meterNatures = new ArrayList<>(); + private LoadGenerator activePowerLoad; + private LoadGenerator reactivePowerLoad; + + public SimulatorGridMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + repo.addThingChangedListener(new ThingsChangedListener() { + + @Override + public void thingChanged(Thing thing, Action action) { + if (esss.valueOptional().isPresent()) { + JsonArray ids = esss.valueOptional().get(); + for (JsonElement id : ids) { + if (id.getAsString().equals(thing.id())) { + getEssNatures(); + } + } + } + if (producer.valueOptional().isPresent()) { + JsonArray ids = producer.valueOptional().get(); + for (JsonElement id : ids) { + if (id.getAsString().equals(thing.id())) { + getMeterNatures(); + } + } + } + } + }); + this.apparentPower = new FunctionalReadChannel("ApparentPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + return ControllerUtils.calculateApparentPower(channels[0].valueOptional().orElse(0L), + channels[1].valueOptional().orElse(0L)); + } + + }, activePower, reactivePower); + + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(esss)) { + if (essNatures != null) { + getEssNatures(); + } + } else if (channel.equals(producer)) { + if (meterNatures != null) { + getMeterNatures(); + } + } else if (channel.equals(activePowerGeneratorConfig)) { + if (activePowerGeneratorConfig.valueOptional().isPresent()) { + activePowerLoad = getGenerator(activePowerGeneratorConfig.valueOptional().get()); + } + } else if (channel.equals(reactivePowerGeneratorConfig)) { + if (reactivePowerGeneratorConfig.valueOptional().isPresent()) { + reactivePowerLoad = getGenerator(reactivePowerGeneratorConfig.valueOptional().get()); + } + } + } + + private LoadGenerator getGenerator(JsonObject config) { + try { + Class clazz = Class.forName(config.get("className").getAsString()); + if (config.get("config") != null) { + try { + Constructor constructor = clazz.getConstructor(JsonObject.class); + return (LoadGenerator) constructor.newInstance(config.get("config").getAsJsonObject()); + } catch (NoSuchMethodException e) { + + } + } + return (LoadGenerator) clazz.newInstance(); + + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + private void getEssNatures() { + essNatures.clear(); + if (esss.valueOptional().isPresent()) { + JsonArray ids = esss.valueOptional().get(); + for (JsonElement e : ids) { + Optional t = repo.getThingById(e.getAsString()); + if (t.isPresent()) { + if (t.get() instanceof EssNature) { + essNatures.add((EssNature) t.get()); + } + } + } + } + } + + private void getMeterNatures() { + meterNatures.clear(); + if (producer.valueOptional().isPresent()) { + JsonArray ids = producer.valueOptional().get(); + for (JsonElement e : ids) { + Optional t = repo.getThingById(e.getAsString()); + if (t.isPresent()) { + if (t.get() instanceof MeterNature) { + meterNatures.add((MeterNature) t.get()); + } + } + } + } + } + + @Override + protected void update() { + super.update(); + long activePower = 0; + long activePowerL1 = 0; + long activePowerL2 = 0; + long activePowerL3 = 0; + if (activePowerLoad != null) { + long load = activePowerLoad.getLoad(); + activePower = load; + activePowerL1 = load / 3; + activePowerL2 = load / 3; + activePowerL3 = load / 3; + } + long reactivePower = 0; + long reactivePowerL1 = 0; + long reactivePowerL2 = 0; + long reactivePowerL3 = 0; + if (reactivePowerLoad != null) { + reactivePower = reactivePowerLoad.getLoad(); + reactivePowerL1 = reactivePower / 3; + reactivePowerL2 = reactivePower / 3; + reactivePowerL3 = reactivePower / 3; + } + for (EssNature entry : essNatures) { + if (entry instanceof SymmetricEssNature) { + SymmetricEssNature ess = (SymmetricEssNature) entry; + activePower -= ess.activePower().valueOptional().orElse(0L); + activePowerL1 -= ess.activePower().valueOptional().orElse(0L) / 3; + activePowerL2 -= ess.activePower().valueOptional().orElse(0L) / 3; + activePowerL3 -= ess.activePower().valueOptional().orElse(0L) / 3; + reactivePower -= ess.reactivePower().valueOptional().orElse(0L); + reactivePowerL1 -= ess.reactivePower().valueOptional().orElse(0L) / 3; + reactivePowerL2 -= ess.reactivePower().valueOptional().orElse(0L) / 3; + reactivePowerL3 -= ess.reactivePower().valueOptional().orElse(0L) / 3; + } else if (entry instanceof AsymmetricEssNature) { + AsymmetricEssNature ess = (AsymmetricEssNature) entry; + activePower -= ess.activePowerL1().valueOptional().orElse(0L) + + ess.activePowerL2().valueOptional().orElse(0L) + + ess.activePowerL3().valueOptional().orElse(0L); + activePowerL1 -= ess.activePowerL1().valueOptional().orElse(0L); + activePowerL2 -= ess.activePowerL2().valueOptional().orElse(0L); + activePowerL3 -= ess.activePowerL3().valueOptional().orElse(0L); + reactivePower -= ess.reactivePowerL1().valueOptional().orElse(0L) + + ess.reactivePowerL2().valueOptional().orElse(0L) + + ess.reactivePowerL3().valueOptional().orElse(0L); + reactivePowerL1 -= ess.reactivePowerL1().valueOptional().orElse(0L); + reactivePowerL2 -= ess.reactivePowerL2().valueOptional().orElse(0L); + reactivePowerL3 -= ess.reactivePowerL3().valueOptional().orElse(0L); + } + } + for (MeterNature entry : meterNatures) { + if (entry instanceof SymmetricMeterNature) { + SymmetricMeterNature meter = (SymmetricMeterNature) entry; + activePower -= meter.activePower().valueOptional().orElse(0L); + activePowerL1 -= meter.activePower().valueOptional().orElse(0L) / 3; + activePowerL2 -= meter.activePower().valueOptional().orElse(0L) / 3; + activePowerL3 -= meter.activePower().valueOptional().orElse(0L) / 3; + reactivePower -= meter.reactivePower().valueOptional().orElse(0L); + reactivePowerL1 -= meter.reactivePower().valueOptional().orElse(0L) / 3; + reactivePowerL2 -= meter.reactivePower().valueOptional().orElse(0L) / 3; + reactivePowerL3 -= meter.reactivePower().valueOptional().orElse(0L) / 3; + } else if (entry instanceof AsymmetricMeterNature) { + AsymmetricMeterNature meter = (AsymmetricMeterNature) entry; + activePower -= meter.activePowerL1().valueOptional().orElse(0L) + + meter.activePowerL2().valueOptional().orElse(0L) + + meter.activePowerL3().valueOptional().orElse(0L); + activePowerL1 -= meter.activePowerL1().valueOptional().orElse(0L); + activePowerL2 -= meter.activePowerL2().valueOptional().orElse(0L); + activePowerL3 -= meter.activePowerL3().valueOptional().orElse(0L); + reactivePower -= meter.reactivePowerL1().valueOptional().orElse(0L) + + meter.reactivePowerL2().valueOptional().orElse(0L) + + meter.reactivePowerL3().valueOptional().orElse(0L); + reactivePowerL1 -= meter.reactivePowerL1().valueOptional().orElse(0L); + reactivePowerL2 -= meter.reactivePowerL2().valueOptional().orElse(0L); + reactivePowerL3 -= meter.reactivePowerL3().valueOptional().orElse(0L); + } + } + if (isOffGrid(essNatures)) { + this.activePower.updateValue(null); + this.activePowerL1.updateValue(null); + this.activePowerL2.updateValue(null); + this.activePowerL3.updateValue(null); + this.reactivePower.updateValue(null); + this.reactivePowerL1.updateValue(null); + this.reactivePowerL2.updateValue(null); + this.reactivePowerL3.updateValue(null); + } else { + this.activePower.updateValue(activePower); + this.activePowerL1.updateValue(activePowerL1); + this.activePowerL2.updateValue(activePowerL2); + this.activePowerL3.updateValue(activePowerL3); + this.reactivePower.updateValue(reactivePower); + this.reactivePowerL1.updateValue(reactivePowerL1); + this.reactivePowerL2.updateValue(reactivePowerL2); + this.reactivePowerL3.updateValue(reactivePowerL3); + } + } + + private boolean isOffGrid(List esss) { + for (EssNature ess : esss) { + if (ess.gridMode().labelOptional().equals(Optional.of(EssNature.OFF_GRID))) { + return true; + } + } + return false; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReadChannel currentL2() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReadChannel currentL3() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReadChannel voltageL1() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReadChannel voltageL2() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReadChannel voltageL3() { + // TODO Auto-generated method stub + return null; + } + +} \ No newline at end of file diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorMeter.java b/edge/src/io/openems/impl/device/simulator/SimulatorMeter.java index ca3e74b9f2f..911fcf495d5 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorMeter.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorMeter.java @@ -1,109 +1,110 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.simulator; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.simulator.SimulatorDeviceNature; - -@ThingInfo(title = "Simulator Meter") -public abstract class SimulatorMeter extends SimulatorDeviceNature implements SymmetricMeterNature { - - /* - * Constructors - */ - public SimulatorMeter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - @ConfigInfo(type = Long.class, title = "Frequency") - public ConfigChannel frequency = new ConfigChannel("frequency", this); - @ConfigInfo(type = Long.class, title = "Voltage") - public ConfigChannel voltage = new ConfigChannel("voltage", this); - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltage; - } - - /* - * Fields - */ - // private long lastApparentPower = 0; - // private double lastCosPhi = 0.9; - // private long lastVoltage = 230000; - // private long lastFrequency = 50000; - - /* - * Methods - */ - @Override - protected void update() { - // lastApparentPower = SimulatorTools.addRandomLong(lastApparentPower, getMinApparentPower(), - // getMaxApparentPower(), 3000); - // lastCosPhi = SimulatorTools.addRandomDouble(lastCosPhi, getMinCosPhi(), getMaxCosPhi(), 0.1); - // long activePower = ControllerUtils.calculateActivePowerFromApparentPower(lastApparentPower, lastCosPhi); - // long reactivePower = ControllerUtils.calculateReactivePower(activePower, lastCosPhi); - // this.activePower.updateValue(activePower); - // this.reactivePower.updateValue(reactivePower); - // this.apparentPower.updateValue(ControllerUtils.calculateApparentPower(activePower, reactivePower)); - // lastVoltage = SimulatorTools.addRandomLong(lastVoltage, 220000, 240000, 1000); - // this.voltage.updateValue(lastVoltage); - // lastFrequency = SimulatorTools.addRandomLong(lastFrequency, 48000, 52000, 1000); - // this.frequency.updateValue(lastFrequency); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.simulator; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.simulator.SimulatorDeviceNature; + +@ThingInfo(title = "Simulator Meter") +public abstract class SimulatorMeter extends SimulatorDeviceNature implements SymmetricMeterNature { + + /* + * Constructors + */ + public SimulatorMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + @ConfigInfo(type = Long.class, title = "Frequency") + public ConfigChannel frequency = new ConfigChannel("frequency", this); + @ConfigInfo(type = Long.class, title = "Voltage") + public ConfigChannel voltage = new ConfigChannel("voltage", this); + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltage; + } + + /* + * Fields + */ + // private long lastApparentPower = 0; + // private double lastCosPhi = 0.9; + // private long lastVoltage = 230000; + // private long lastFrequency = 50000; + + /* + * Methods + */ + @Override + protected void update() { + // lastApparentPower = SimulatorTools.addRandomLong(lastApparentPower, getMinApparentPower(), + // getMaxApparentPower(), 3000); + // lastCosPhi = SimulatorTools.addRandomDouble(lastCosPhi, getMinCosPhi(), getMaxCosPhi(), 0.1); + // long activePower = ControllerUtils.calculateActivePowerFromApparentPower(lastApparentPower, lastCosPhi); + // long reactivePower = ControllerUtils.calculateReactivePower(activePower, lastCosPhi); + // this.activePower.updateValue(activePower); + // this.reactivePower.updateValue(reactivePower); + // this.apparentPower.updateValue(ControllerUtils.calculateApparentPower(activePower, reactivePower)); + // lastVoltage = SimulatorTools.addRandomLong(lastVoltage, 220000, 240000, 1000); + // this.voltage.updateValue(lastVoltage); + // lastFrequency = SimulatorTools.addRandomLong(lastFrequency, 48000, 52000, 1000); + // this.frequency.updateValue(lastFrequency); + } +} diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorOutput.java b/edge/src/io/openems/impl/device/simulator/SimulatorOutput.java index 41b54608f48..5b00cd09cd8 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorOutput.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorOutput.java @@ -1,38 +1,39 @@ -package io.openems.impl.device.simulator; - -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.io.OutputNature; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.simulator.SimulatorDeviceNature; -import io.openems.impl.protocol.simulator.SimulatorWriteChannel; - -public class SimulatorOutput extends SimulatorDeviceNature implements OutputNature { - - private SimulatorWriteChannel do1 = new SimulatorWriteChannel<>("DO1", this, false); - private SimulatorWriteChannel do2 = new SimulatorWriteChannel<>("DO2", this, false); - private SimulatorWriteChannel do3 = new SimulatorWriteChannel<>("DO3", this, false); - private SimulatorWriteChannel do4 = new SimulatorWriteChannel<>("DO4", this, false); - private SimulatorWriteChannel do5 = new SimulatorWriteChannel<>("DO5", this, false); - private SimulatorWriteChannel do6 = new SimulatorWriteChannel<>("DO6", this, false); - private SimulatorWriteChannel do7 = new SimulatorWriteChannel<>("DO7", this, false); - private SimulatorWriteChannel do8 = new SimulatorWriteChannel<>("DO8", this, false); - private SimulatorWriteChannel do9 = new SimulatorWriteChannel<>("DO9", this, false); - private SimulatorWriteChannel do10 = new SimulatorWriteChannel<>("DO10", this, false); - private SimulatorWriteChannel[] array; - - public SimulatorOutput(String thingId) throws ConfigException { - super(thingId); - this.array = new SimulatorWriteChannel[] { do1, do2, do3, do4, do5, do6, do7, do8, do9, do10 }; - } - - @Override - public WriteChannel[] setOutput() { - return array; - } - - @Override - protected void update() { - - } - -} +package io.openems.impl.device.simulator; + +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.io.OutputNature; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.simulator.SimulatorDeviceNature; +import io.openems.impl.protocol.simulator.SimulatorWriteChannel; + +public class SimulatorOutput extends SimulatorDeviceNature implements OutputNature { + + private SimulatorWriteChannel do1 = new SimulatorWriteChannel<>("DO1", this, false); + private SimulatorWriteChannel do2 = new SimulatorWriteChannel<>("DO2", this, false); + private SimulatorWriteChannel do3 = new SimulatorWriteChannel<>("DO3", this, false); + private SimulatorWriteChannel do4 = new SimulatorWriteChannel<>("DO4", this, false); + private SimulatorWriteChannel do5 = new SimulatorWriteChannel<>("DO5", this, false); + private SimulatorWriteChannel do6 = new SimulatorWriteChannel<>("DO6", this, false); + private SimulatorWriteChannel do7 = new SimulatorWriteChannel<>("DO7", this, false); + private SimulatorWriteChannel do8 = new SimulatorWriteChannel<>("DO8", this, false); + private SimulatorWriteChannel do9 = new SimulatorWriteChannel<>("DO9", this, false); + private SimulatorWriteChannel do10 = new SimulatorWriteChannel<>("DO10", this, false); + private SimulatorWriteChannel[] array; + + public SimulatorOutput(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + this.array = new SimulatorWriteChannel[] { do1, do2, do3, do4, do5, do6, do7, do8, do9, do10 }; + } + + @Override + public WriteChannel[] setOutput() { + return array; + } + + @Override + protected void update() { + + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorProductionMeter.java b/edge/src/io/openems/impl/device/simulator/SimulatorProductionMeter.java index 6f54ded6027..60477198b5f 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorProductionMeter.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorProductionMeter.java @@ -1,132 +1,133 @@ -package io.openems.impl.device.simulator; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.Optional; - -import com.google.gson.JsonObject; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.ConfigException; -import io.openems.core.utilities.ControllerUtils; -import io.openems.impl.protocol.simulator.SimulatorReadChannel; - -public class SimulatorProductionMeter extends SimulatorMeter implements ChannelChangeListener { - - // @ConfigInfo(title = "ActivePower", type = Long.class) - // public ConfigChannel activePower = new ConfigChannel("ActivePower", this); - // @ConfigInfo(title = "ReactivePower", type = Long.class) - // public ConfigChannel reactivePower = new ConfigChannel("ReactivePower", this); - @ConfigInfo(title = "ActivePowerGeneratorConfig", type = JsonObject.class) - public ConfigChannel activePowerGeneratorConfig = new ConfigChannel( - "activePowerGeneratorConfig", this).addChangeListener(this); - @ConfigInfo(title = "ReactivePowerGeneratorConfig", type = JsonObject.class) - public ConfigChannel reactivePowerGeneratorConfig = new ConfigChannel( - "reactivePowerGeneratorConfig", this).addChangeListener(this); - private SimulatorReadChannel activePower = new SimulatorReadChannel<>("ActivePower", this); - private SimulatorReadChannel reactivePower = new SimulatorReadChannel<>("ReactivePower", this); - private FunctionalReadChannel apparentPower; - private LoadGenerator activePowerGenerator; - private LoadGenerator reactivePowerGenerator; - - public SimulatorProductionMeter(String thingId) throws ConfigException { - super(thingId); - this.apparentPower = new FunctionalReadChannel("ApparentPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - return ControllerUtils.calculateApparentPower(channels[0].valueOptional().orElse(0L), - channels[1].valueOptional().orElse(0L)); - } - - }, activePower, reactivePower); - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - protected void update() { - super.update(); - if (activePowerGenerator != null) { - activePower.updateValue(activePowerGenerator.getLoad()); - } else { - activePower.updateValue(0L); - log.error("activePowerGenerator is null"); - } - if (reactivePowerGenerator != null) { - reactivePower.updateValue(reactivePowerGenerator.getLoad()); - } else { - reactivePower.updateValue(0L); - log.error("reactivePowerGenerator is null"); - } - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel.equals(activePowerGeneratorConfig)) { - if (activePowerGeneratorConfig.valueOptional().isPresent()) { - activePowerGenerator = getGenerator(activePowerGeneratorConfig.valueOptional().get()); - } - } else if (channel.equals(reactivePowerGeneratorConfig)) { - if (reactivePowerGeneratorConfig.valueOptional().isPresent()) { - reactivePowerGenerator = getGenerator(reactivePowerGeneratorConfig.valueOptional().get()); - } - } - } - - private LoadGenerator getGenerator(JsonObject config) { - try { - Class clazz = Class.forName(config.get("className").getAsString()); - if (config.get("config") != null) { - try { - Constructor constructor = clazz.getConstructor(JsonObject.class); - return (LoadGenerator) constructor.newInstance(config.get("config").getAsJsonObject()); - } catch (NoSuchMethodException e) { - - } - } - return (LoadGenerator) clazz.newInstance(); - - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ClassNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; - } - -} +package io.openems.impl.device.simulator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Optional; + +import com.google.gson.JsonObject; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.core.utilities.ControllerUtils; +import io.openems.impl.protocol.simulator.SimulatorReadChannel; + +public class SimulatorProductionMeter extends SimulatorMeter implements ChannelChangeListener { + + // @ConfigInfo(title = "ActivePower", type = Long.class) + // public ConfigChannel activePower = new ConfigChannel("ActivePower", this); + // @ConfigInfo(title = "ReactivePower", type = Long.class) + // public ConfigChannel reactivePower = new ConfigChannel("ReactivePower", this); + @ConfigInfo(title = "ActivePowerGeneratorConfig", type = JsonObject.class) + public ConfigChannel activePowerGeneratorConfig = new ConfigChannel( + "activePowerGeneratorConfig", this).addChangeListener(this); + @ConfigInfo(title = "ReactivePowerGeneratorConfig", type = JsonObject.class) + public ConfigChannel reactivePowerGeneratorConfig = new ConfigChannel( + "reactivePowerGeneratorConfig", this).addChangeListener(this); + private SimulatorReadChannel activePower = new SimulatorReadChannel<>("ActivePower", this); + private SimulatorReadChannel reactivePower = new SimulatorReadChannel<>("ReactivePower", this); + private FunctionalReadChannel apparentPower; + private LoadGenerator activePowerGenerator; + private LoadGenerator reactivePowerGenerator; + + public SimulatorProductionMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + this.apparentPower = new FunctionalReadChannel("ApparentPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + return ControllerUtils.calculateApparentPower(channels[0].valueOptional().orElse(0L), + channels[1].valueOptional().orElse(0L)); + } + + }, activePower, reactivePower); + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + protected void update() { + super.update(); + if (activePowerGenerator != null) { + activePower.updateValue(activePowerGenerator.getLoad()); + } else { + activePower.updateValue(0L); + log.error("activePowerGenerator is null"); + } + if (reactivePowerGenerator != null) { + reactivePower.updateValue(reactivePowerGenerator.getLoad()); + } else { + reactivePower.updateValue(0L); + log.error("reactivePowerGenerator is null"); + } + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(activePowerGeneratorConfig)) { + if (activePowerGeneratorConfig.valueOptional().isPresent()) { + activePowerGenerator = getGenerator(activePowerGeneratorConfig.valueOptional().get()); + } + } else if (channel.equals(reactivePowerGeneratorConfig)) { + if (reactivePowerGeneratorConfig.valueOptional().isPresent()) { + reactivePowerGenerator = getGenerator(reactivePowerGeneratorConfig.valueOptional().get()); + } + } + } + + private LoadGenerator getGenerator(JsonObject config) { + try { + Class clazz = Class.forName(config.get("className").getAsString()); + if (config.get("config") != null) { + try { + Constructor constructor = clazz.getConstructor(JsonObject.class); + return (LoadGenerator) constructor.newInstance(config.get("config").getAsJsonObject()); + } catch (NoSuchMethodException e) { + + } + } + return (LoadGenerator) clazz.newInstance(); + + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + +} diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorRiedmannNature.java b/edge/src/io/openems/impl/device/simulator/SimulatorRiedmannNature.java index cdeaea95b67..20d0d75ae84 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorRiedmannNature.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorRiedmannNature.java @@ -27,6 +27,7 @@ import io.openems.api.channel.ConfigChannel; import io.openems.api.channel.ReadChannel; import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; import io.openems.api.doc.ConfigInfo; import io.openems.api.doc.ThingInfo; import io.openems.api.exception.ConfigException; @@ -45,8 +46,8 @@ public class SimulatorRiedmannNature extends SimulatorDeviceNature implements Ri /* * Constructors */ - public SimulatorRiedmannNature(String thingId) throws ConfigException { - super(thingId); + public SimulatorRiedmannNature(String thingId, Device parent) throws ConfigException { + super(thingId, parent); } /* diff --git a/edge/src/io/openems/impl/device/simulator/SimulatorSymmetricEss.java b/edge/src/io/openems/impl/device/simulator/SimulatorSymmetricEss.java index 47fb109e0ea..e841fe0287c 100644 --- a/edge/src/io/openems/impl/device/simulator/SimulatorSymmetricEss.java +++ b/edge/src/io/openems/impl/device/simulator/SimulatorSymmetricEss.java @@ -1,365 +1,366 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.simulator; - -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.ThreadLocalRandom; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.charger.ChargerNature; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.thing.Thing; -import io.openems.core.ThingRepository; -import io.openems.core.utilities.AvgFiFoQueue; -import io.openems.core.utilities.ControllerUtils; -import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; -import io.openems.impl.protocol.simulator.SimulatorDeviceNature; -import io.openems.impl.protocol.simulator.SimulatorReadChannel; -import io.openems.test.utils.channel.UnitTestWriteChannel; - -@ThingInfo(title = "Simulator ESS") -public class SimulatorSymmetricEss extends SimulatorDeviceNature implements SymmetricEssNature, ChannelChangeListener { - - private List chargerList; - private ThingRepository repo = ThingRepository.getInstance(); - private double energy; - private AvgFiFoQueue activePowerQueue = new AvgFiFoQueue(5, 1); - private AvgFiFoQueue reactivePowerQueue = new AvgFiFoQueue(5, 1); - @ConfigInfo(title = "ActivePowerGeneratorConfig", type = JsonObject.class) - public ConfigChannel activePowerGeneratorConfig = new ConfigChannel( - "activePowerGeneratorConfig", this).addChangeListener(this).addChangeListener(this); - @ConfigInfo(title = "ReactivePowerGeneratorConfig", type = JsonObject.class) - public ConfigChannel reactivePowerGeneratorConfig = new ConfigChannel( - "reactivePowerGeneratorConfig", this).addChangeListener(this).addChangeListener(this); - private LoadGenerator offGridActivePowerGenerator; - private LoadGenerator offGridReactivePowerGenerator; - - /* - * Constructors - */ - public SimulatorSymmetricEss(String thingId) throws ConfigException { - super(thingId); - minSoc.addUpdateListener((channel, newValue) -> { - // If chargeSoc was not set -> set it to minSoc minus 2 - if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { - chargeSoc.updateValue((Integer) newValue.get() - 2, false); - } - }); - long initialSoc = SimulatorTools.addRandomLong(50, 0, 100, 20); - this.energy = capacity.valueOptional().get() / 100 * initialSoc; - this.soc = new FunctionalReadChannel("Soc", this, new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - try { - energy -= channels[0].value() / 3600.0; - } catch (InvalidValueException e) { - - } - if (chargerList != null) { - for (ChargerNature charger : chargerList) { - try { - energy += charger.getActualPower().value() / 3600.0; - } catch (InvalidValueException e) {} - } - } - try { - if (energy > capacity.value()) { - energy = capacity.value(); - } else if (energy < 0) { - energy = 0; - } - return (long) (energy / capacity.value() * 100.0); - } catch (InvalidValueException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return 0L; - } - - }, this.activePower); - } - - /* - * Config - */ - private ConfigChannel minSoc = new ConfigChannel("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - @ConfigInfo(title = "GridMode", type = Long.class) - public ConfigChannel gridMode = new ConfigChannel("GridMode", this).label(0L, ON_GRID) - .label(1L, OFF_GRID).defaultValue(0L); - @ConfigInfo(title = "SystemState", type = Long.class) - public ConfigChannel systemState = new ConfigChannel("SystemState", this) // - .label(1L, START).label(2L, STOP).label(5L, FAULT).defaultValue(1L); - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - /* - * Inherited Channels - */ - private StatusBitChannels warning = new StatusBitChannels("Warning", this);; - private FunctionalReadChannel soc; - private SimulatorReadChannel activePower = new SimulatorReadChannel("ActivePower", this); - private StaticValueChannel allowedApparent = new StaticValueChannel("AllowedApparent", this, 40000L); - private SimulatorReadChannel allowedCharge = new SimulatorReadChannel("AllowedCharge", this); - private SimulatorReadChannel allowedDischarge = new SimulatorReadChannel("AllowedDischarge", this); - private SimulatorReadChannel apparentPower = new SimulatorReadChannel("ApparentPower", this); - private SimulatorReadChannel reactivePower = new SimulatorReadChannel("ReactivePower", this); - private UnitTestWriteChannel setActivePower = new UnitTestWriteChannel("SetActivePower", this) - .maxWriteChannel(allowedDischarge).minWriteChannel(allowedCharge); - private UnitTestWriteChannel setReactivePower = new UnitTestWriteChannel("SetReactivePower", this); - private ModbusWriteLongChannel setWorkState = new ModbusWriteLongChannel("SetWorkState", this).label(1, START) - .label(2, STOP); - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) - .unit("VA"); - private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 5000L).unit("Wh"); - @ConfigInfo(title = "charger", type = JsonArray.class, isOptional = true) - public ConfigChannel charger = new ConfigChannel("charger", this).addChangeListener(this); - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - private long getRandom(int min, int max) { - return ThreadLocalRandom.current().nextLong(min, max + 1); - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - /* - * Fields - */ - private long lastApparentPower = 0; - private double lastCosPhi = 0; - - /* - * Methods - */ - @Override - protected void update() { - if (chargerList == null) { - chargerList = new ArrayList<>(); - getCharger(); - } - Optional writtenActivePower = setActivePower.getWrittenValue(); - if (writtenActivePower.isPresent()) { - activePowerQueue.add(writtenActivePower.get()); - } - Optional writtenReactivePower = setReactivePower.getWrittenValue(); - if (writtenReactivePower.isPresent()) { - reactivePowerQueue.add(writtenReactivePower.get()); - } - // lastApparentPower = SimulatorTools.addRandomLong(lastApparentPower, -10000, 10000, 500); - // lastCosPhi = SimulatorTools.addRandomDouble(lastCosPhi, -1.5, 1.5, 0.5); - // - // long activePower = ControllerUtils.calculateActivePowerFromApparentPower(lastApparentPower, lastCosPhi); - // long reactivePower = ControllerUtils.calculateReactivePower(activePower, lastCosPhi); - long activePower = 0; - long reactivePower = 0; - if (this.systemState.labelOptional().equals(Optional.of(EssNature.START))) { - if (this.gridMode.labelOptional().equals(Optional.of(EssNature.OFF_GRID))) { - activePower = offGridActivePowerGenerator.getLoad(); - reactivePower = offGridReactivePowerGenerator.getLoad(); - } else { - activePower = activePowerQueue.avg(); - reactivePower = reactivePowerQueue.avg(); - } - } - this.activePower.updateValue(activePower); - this.reactivePower.updateValue(reactivePower); - this.apparentPower.updateValue(ControllerUtils.calculateApparentPower(activePower, reactivePower)); - this.allowedCharge.updateValue(-9000L); - this.allowedDischarge.updateValue(3000L); - } - - @Override - public StaticValueChannel capacity() { - return capacity; - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel.equals(charger)) { - if (chargerList != null) { - getCharger(); - } - } else if (channel.equals(activePowerGeneratorConfig)) { - if (activePowerGeneratorConfig.valueOptional().isPresent()) { - offGridActivePowerGenerator = getGenerator(activePowerGeneratorConfig.valueOptional().get()); - } - } else if (channel.equals(reactivePowerGeneratorConfig)) { - if (reactivePowerGeneratorConfig.valueOptional().isPresent()) { - offGridReactivePowerGenerator = getGenerator(reactivePowerGeneratorConfig.valueOptional().get()); - } - } - } - - private LoadGenerator getGenerator(JsonObject config) { - try { - Class clazz = Class.forName(config.get("className").getAsString()); - if (config.get("config") != null) { - try { - Constructor constructor = clazz.getConstructor(JsonObject.class); - return (LoadGenerator) constructor.newInstance(config.get("config").getAsJsonObject()); - } catch (NoSuchMethodException e) { - - } - } - return (LoadGenerator) clazz.newInstance(); - - } catch (SecurityException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (ClassNotFoundException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InstantiationException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalAccessException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (IllegalArgumentException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (InvocationTargetException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return null; - } - - private void getCharger() { - if (chargerList != null) { - for (ChargerNature charger : chargerList) { - soc.removeChannel(charger.getActualPower()); - } - chargerList.clear(); - if (charger.valueOptional().isPresent()) { - JsonArray ids = charger.valueOptional().get(); - for (JsonElement e : ids) { - Optional t = repo.getThingById(e.getAsString()); - if (t.isPresent()) { - if (t.get() instanceof ChargerNature) { - ChargerNature charger = (ChargerNature) t.get(); - chargerList.add(charger); - soc.addChannel(charger.getActualPower()); - } - } - } - } - } - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.simulator; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ThreadLocalRandom; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.charger.ChargerNature; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.thing.Thing; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.AvgFiFoQueue; +import io.openems.core.utilities.ControllerUtils; +import io.openems.impl.protocol.modbus.ModbusWriteLongChannel; +import io.openems.impl.protocol.simulator.SimulatorDeviceNature; +import io.openems.impl.protocol.simulator.SimulatorReadChannel; +import io.openems.test.utils.channel.UnitTestWriteChannel; + +@ThingInfo(title = "Simulator ESS") +public class SimulatorSymmetricEss extends SimulatorDeviceNature implements SymmetricEssNature, ChannelChangeListener { + + private List chargerList; + private ThingRepository repo = ThingRepository.getInstance(); + private double energy; + private AvgFiFoQueue activePowerQueue = new AvgFiFoQueue(3, 1); + private AvgFiFoQueue reactivePowerQueue = new AvgFiFoQueue(3, 1); + @ConfigInfo(title = "ActivePowerGeneratorConfig", type = JsonObject.class) + public ConfigChannel activePowerGeneratorConfig = new ConfigChannel( + "activePowerGeneratorConfig", this).addChangeListener(this).addChangeListener(this); + @ConfigInfo(title = "ReactivePowerGeneratorConfig", type = JsonObject.class) + public ConfigChannel reactivePowerGeneratorConfig = new ConfigChannel( + "reactivePowerGeneratorConfig", this).addChangeListener(this).addChangeListener(this); + private LoadGenerator offGridActivePowerGenerator; + private LoadGenerator offGridReactivePowerGenerator; + + /* + * Constructors + */ + public SimulatorSymmetricEss(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + minSoc.addUpdateListener((channel, newValue) -> { + // If chargeSoc was not set -> set it to minSoc minus 2 + if (channel == minSoc && !chargeSoc.valueOptional().isPresent()) { + chargeSoc.updateValue((Integer) newValue.get() - 2, false); + } + }); + long initialSoc = SimulatorTools.addRandomLong(50, 0, 100, 20); + this.energy = capacity.valueOptional().get() / 100 * initialSoc; + this.soc = new FunctionalReadChannel("Soc", this, new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + try { + energy -= channels[0].value() / 3600.0; + } catch (InvalidValueException e) { + + } + if (chargerList != null) { + for (ChargerNature charger : chargerList) { + try { + energy += charger.getActualPower().value() / 3600.0; + } catch (InvalidValueException e) {} + } + } + try { + if (energy > capacity.value()) { + energy = capacity.value(); + } else if (energy < 0) { + energy = 0; + } + return (long) (energy / capacity.value() * 100.0); + } catch (InvalidValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return 0L; + } + + }, this.activePower); + } + + /* + * Config + */ + private ConfigChannel minSoc = new ConfigChannel("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + @ConfigInfo(title = "GridMode", type = Long.class) + public ConfigChannel gridMode = new ConfigChannel("GridMode", this).label(0L, ON_GRID) + .label(1L, OFF_GRID).defaultValue(0L); + @ConfigInfo(title = "SystemState", type = Long.class) + public ConfigChannel systemState = new ConfigChannel("SystemState", this) // + .label(1L, START).label(2L, STOP).label(5L, FAULT).defaultValue(1L); + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + /* + * Inherited Channels + */ + private StatusBitChannels warning = new StatusBitChannels("Warning", this);; + private FunctionalReadChannel soc; + private SimulatorReadChannel activePower = new SimulatorReadChannel("ActivePower", this); + private StaticValueChannel allowedApparent = new StaticValueChannel("AllowedApparent", this, 40000L); + private SimulatorReadChannel allowedCharge = new SimulatorReadChannel("AllowedCharge", this); + private SimulatorReadChannel allowedDischarge = new SimulatorReadChannel("AllowedDischarge", this); + private SimulatorReadChannel apparentPower = new SimulatorReadChannel("ApparentPower", this); + private SimulatorReadChannel reactivePower = new SimulatorReadChannel("ReactivePower", this); + private UnitTestWriteChannel setActivePower = new UnitTestWriteChannel("SetActivePower", this) + .maxWriteChannel(allowedDischarge).minWriteChannel(allowedCharge); + private UnitTestWriteChannel setReactivePower = new UnitTestWriteChannel("SetReactivePower", this); + private ModbusWriteLongChannel setWorkState = new ModbusWriteLongChannel("SetWorkState", this).label(1, START) + .label(2, STOP); + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) + .unit("VA"); + private StaticValueChannel capacity = new StaticValueChannel<>("capacity", this, 5000L).unit("Wh"); + @ConfigInfo(title = "charger", type = JsonArray.class, isOptional = true) + public ConfigChannel charger = new ConfigChannel("charger", this).addChangeListener(this); + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + private long getRandom(int min, int max) { + return ThreadLocalRandom.current().nextLong(min, max + 1); + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + /* + * Fields + */ + private long lastApparentPower = 0; + private double lastCosPhi = 0; + + /* + * Methods + */ + @Override + protected void update() { + if (chargerList == null) { + chargerList = new ArrayList<>(); + getCharger(); + } + Optional writtenActivePower = setActivePower.getWrittenValue(); + if (writtenActivePower.isPresent()) { + activePowerQueue.add(writtenActivePower.get()); + } + Optional writtenReactivePower = setReactivePower.getWrittenValue(); + if (writtenReactivePower.isPresent()) { + reactivePowerQueue.add(writtenReactivePower.get()); + } + // lastApparentPower = SimulatorTools.addRandomLong(lastApparentPower, -10000, 10000, 500); + // lastCosPhi = SimulatorTools.addRandomDouble(lastCosPhi, -1.5, 1.5, 0.5); + // + // long activePower = ControllerUtils.calculateActivePowerFromApparentPower(lastApparentPower, lastCosPhi); + // long reactivePower = ControllerUtils.calculateReactivePower(activePower, lastCosPhi); + long activePower = 0; + long reactivePower = 0; + if (this.systemState.labelOptional().equals(Optional.of(EssNature.START))) { + if (this.gridMode.labelOptional().equals(Optional.of(EssNature.OFF_GRID))) { + activePower = offGridActivePowerGenerator.getLoad(); + reactivePower = offGridReactivePowerGenerator.getLoad(); + } else { + activePower = activePowerQueue.avg(); + reactivePower = reactivePowerQueue.avg(); + } + } + this.activePower.updateValue(activePower); + this.reactivePower.updateValue(reactivePower); + this.apparentPower.updateValue(ControllerUtils.calculateApparentPower(activePower, reactivePower)); + this.allowedCharge.updateValue(-9000L); + this.allowedDischarge.updateValue(3000L); + } + + @Override + public StaticValueChannel capacity() { + return capacity; + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(charger)) { + if (chargerList != null) { + getCharger(); + } + } else if (channel.equals(activePowerGeneratorConfig)) { + if (activePowerGeneratorConfig.valueOptional().isPresent()) { + offGridActivePowerGenerator = getGenerator(activePowerGeneratorConfig.valueOptional().get()); + } + } else if (channel.equals(reactivePowerGeneratorConfig)) { + if (reactivePowerGeneratorConfig.valueOptional().isPresent()) { + offGridReactivePowerGenerator = getGenerator(reactivePowerGeneratorConfig.valueOptional().get()); + } + } + } + + private LoadGenerator getGenerator(JsonObject config) { + try { + Class clazz = Class.forName(config.get("className").getAsString()); + if (config.get("config") != null) { + try { + Constructor constructor = clazz.getConstructor(JsonObject.class); + return (LoadGenerator) constructor.newInstance(config.get("config").getAsJsonObject()); + } catch (NoSuchMethodException e) { + + } + } + return (LoadGenerator) clazz.newInstance(); + + } catch (SecurityException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return null; + } + + private void getCharger() { + if (chargerList != null) { + for (ChargerNature charger : chargerList) { + soc.removeChannel(charger.getActualPower()); + } + chargerList.clear(); + if (charger.valueOptional().isPresent()) { + JsonArray ids = charger.valueOptional().get(); + for (JsonElement e : ids) { + Optional t = repo.getThingById(e.getAsString()); + if (t.isPresent()) { + if (t.get() instanceof ChargerNature) { + ChargerNature charger = (ChargerNature) t.get(); + chargerList.add(charger); + soc.addChannel(charger.getActualPower()); + } + } + } + } + } + } + +} diff --git a/edge/src/io/openems/impl/device/socomec/Socomec.java b/edge/src/io/openems/impl/device/socomec/Socomec.java index d9696770733..bd650c53b58 100644 --- a/edge/src/io/openems/impl/device/socomec/Socomec.java +++ b/edge/src/io/openems/impl/device/socomec/Socomec.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.socomec; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "Socomec") -public class Socomec extends ModbusDevice { - - /* - * Constructors - */ - public Socomec() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = SocomecMeter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.socomec; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "Socomec") +public class Socomec extends ModbusDevice { + + /* + * Constructors + */ + public Socomec(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = SocomecMeter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/socomec/SocomecB30.java b/edge/src/io/openems/impl/device/socomec/SocomecB30.java index f04abe35300..2f8336163ee 100644 --- a/edge/src/io/openems/impl/device/socomec/SocomecB30.java +++ b/edge/src/io/openems/impl/device/socomec/SocomecB30.java @@ -1,61 +1,62 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.socomec; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -@ThingInfo(title = "Socomec B30") -public class SocomecB30 extends ModbusDevice { - - /* - * Constructors - */ - public SocomecB30() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = SocomecB30Meter.class) - public final ConfigChannel meter = new ConfigChannel<>("meter", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (meter.valueOptional().isPresent()) { - natures.add(meter.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.socomec; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +@ThingInfo(title = "Socomec B30") +public class SocomecB30 extends ModbusDevice { + + /* + * Constructors + */ + public SocomecB30(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the meter nature.", type = SocomecB30Meter.class) + public final ConfigChannel meter = new ConfigChannel<>("meter", this); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (meter.valueOptional().isPresent()) { + natures.add(meter.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/socomec/SocomecB30Meter.java b/edge/src/io/openems/impl/device/socomec/SocomecB30Meter.java index e2e1db58fb5..20d045c15ce 100644 --- a/edge/src/io/openems/impl/device/socomec/SocomecB30Meter.java +++ b/edge/src/io/openems/impl/device/socomec/SocomecB30Meter.java @@ -1,249 +1,244 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.socomec; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "Socomec B30 Meter") -public class SocomecB30Meter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { - - /* - * Constructors - */ - public SocomecB30Meter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel activePower; - private ModbusReadLongChannel apparentPower; - private ModbusReadLongChannel reactivePower; - private ModbusReadLongChannel activePowerL1; - private ModbusReadLongChannel activePowerL2; - private ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel reactivePowerL1; - private ModbusReadLongChannel reactivePowerL2; - private ModbusReadLongChannel reactivePowerL3; - private ModbusReadLongChannel voltageL1; - private ModbusReadLongChannel voltageL2; - private ModbusReadLongChannel voltageL3; - private ModbusReadLongChannel currentL1; - private ModbusReadLongChannel currentL2; - private ModbusReadLongChannel currentL3; - private ModbusReadLongChannel frequency; - - @Override - public ModbusReadLongChannel activePower() { - return activePower; - } - - @Override - public ModbusReadLongChannel apparentPower() { - return apparentPower; - } - - @Override - public ModbusReadLongChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public ReadChannel currentL1() { - return currentL1; - } - - @Override - public ReadChannel currentL2() { - return currentL2; - } - - @Override - public ReadChannel currentL3() { - return currentL3; - } - - @Override - public ReadChannel voltageL1() { - return voltageL1; - } - - @Override - public ReadChannel voltageL2() { - return voltageL2; - } - - @Override - public ReadChannel voltageL3() { - return voltageL3; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - /* - * This Channels - */ - public ModbusReadLongChannel activeNegativeEnergy; - public ModbusReadLongChannel activePositiveEnergy; - public ModbusReadLongChannel reactiveNegativeEnergy; - public ModbusReadLongChannel reactivePositiveEnergy; - public ModbusReadLongChannel apparentEnergy; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( // - new ModbusRegisterRange(0x480A, // - new UnsignedDoublewordElement(0x480A, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ")), - new UnsignedDoublewordElement(0x480C, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(1)), - new UnsignedDoublewordElement(0x480E, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(1)), - new UnsignedDoublewordElement(0x4810, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(1)), - new DummyElement(0x4812, 0x4819), - new UnsignedDoublewordElement(0x481A, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), - new UnsignedDoublewordElement(0x481C, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), - new UnsignedDoublewordElement(0x481E, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA"))), - new ModbusRegisterRange(0x482C, // - new SignedDoublewordElement(0x482C, // - activePower = new ModbusReadLongChannel("ActivePower", this).unit("W")), - new SignedDoublewordElement(0x482E, // - reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("Var")), - new DummyElement(0x4830, 0x4833), - new SignedDoublewordElement(0x4834, // - apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA")), - new DummyElement(0x4836, 0x4837), - new SignedDoublewordElement(0x4838, // - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W")), - new SignedDoublewordElement(0x483A, // - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W")), - new SignedDoublewordElement(0x483C, // - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W")), - new SignedDoublewordElement(0x483E, // - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("Var")), - new SignedDoublewordElement(0x4840, // - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("Var")), - new SignedDoublewordElement(0x4842, // - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("Var"))), - new ModbusRegisterRange(0x4D83, // - new UnsignedDoublewordElement(0x4D83, // - activePositiveEnergy = new ModbusReadLongChannel("ActivePositiveEnergy", this) - .unit("kWh")), - new DummyElement(0x4D85), - new UnsignedDoublewordElement(0x4D86, // - activeNegativeEnergy = new ModbusReadLongChannel("ActiveNegativeEnergy", this) - .unit("kWh")), - new DummyElement(0x4D88), - new UnsignedDoublewordElement(0x4D89, // - reactivePositiveEnergy = new ModbusReadLongChannel("ReactivePositiveEnergy", this) - .unit("kvarh")), - new DummyElement(0x4D8B), - new UnsignedDoublewordElement(0x4D8C, // - reactiveNegativeEnergy = new ModbusReadLongChannel("ReactiveNegativeEnergy", this) - .unit("kvarh")), - new DummyElement(0x4D8E), new UnsignedDoublewordElement(0x4D8F, // - apparentEnergy = new ModbusReadLongChannel("ApparentEnergy", this).unit("kVAh"))) - - ); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.socomec; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "Socomec B30 Meter") +public class SocomecB30Meter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { + + /* + * Constructors + */ + public SocomecB30Meter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel activePower; + private ModbusReadLongChannel apparentPower; + private ModbusReadLongChannel reactivePower; + private ModbusReadLongChannel activePowerL1; + private ModbusReadLongChannel activePowerL2; + private ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel reactivePowerL1; + private ModbusReadLongChannel reactivePowerL2; + private ModbusReadLongChannel reactivePowerL3; + private ModbusReadLongChannel voltageL1; + private ModbusReadLongChannel voltageL2; + private ModbusReadLongChannel voltageL3; + private ModbusReadLongChannel currentL1; + private ModbusReadLongChannel currentL2; + private ModbusReadLongChannel currentL3; + private ModbusReadLongChannel frequency; + + @Override + public ModbusReadLongChannel activePower() { + return activePower; + } + + @Override + public ModbusReadLongChannel apparentPower() { + return apparentPower; + } + + @Override + public ModbusReadLongChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + /* + * This Channels + */ + public ModbusReadLongChannel activeNegativeEnergy; + public ModbusReadLongChannel activePositiveEnergy; + public ModbusReadLongChannel reactiveNegativeEnergy; + public ModbusReadLongChannel reactivePositiveEnergy; + public ModbusReadLongChannel apparentEnergy; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( // + new ModbusRegisterRange(0x480A, // + new UnsignedDoublewordElement(0x480A, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ")), + new UnsignedDoublewordElement(0x480C, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(1)), + new UnsignedDoublewordElement(0x480E, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(1)), + new UnsignedDoublewordElement(0x4810, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(1)), + new DummyElement(0x4812, 0x4819), new UnsignedDoublewordElement(0x481A, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), + new UnsignedDoublewordElement(0x481C, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), + new UnsignedDoublewordElement(0x481E, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA"))), + new ModbusRegisterRange(0x482C, // + new SignedDoublewordElement(0x482C, // + activePower = new ModbusReadLongChannel("ActivePower", this).unit("W")), + new SignedDoublewordElement(0x482E, // + reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("Var")), + new DummyElement(0x4830, 0x4833), new SignedDoublewordElement(0x4834, // + apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA")), + new DummyElement(0x4836, 0x4837), new SignedDoublewordElement(0x4838, // + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W")), + new SignedDoublewordElement(0x483A, // + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W")), + new SignedDoublewordElement(0x483C, // + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W")), + new SignedDoublewordElement(0x483E, // + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("Var")), + new SignedDoublewordElement(0x4840, // + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("Var")), + new SignedDoublewordElement(0x4842, // + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("Var"))), + new ModbusRegisterRange(0x4D83, // + new UnsignedDoublewordElement(0x4D83, // + activePositiveEnergy = new ModbusReadLongChannel("ActivePositiveEnergy", + this).unit("kWh")), + new DummyElement(0x4D85), new UnsignedDoublewordElement(0x4D86, // + activeNegativeEnergy = new ModbusReadLongChannel("ActiveNegativeEnergy", + this).unit("kWh")), + new DummyElement(0x4D88), new UnsignedDoublewordElement(0x4D89, // + reactivePositiveEnergy = new ModbusReadLongChannel("ReactivePositiveEnergy", + this).unit("kvarh")), + new DummyElement(0x4D8B), new UnsignedDoublewordElement(0x4D8C, // + reactiveNegativeEnergy = new ModbusReadLongChannel("ReactiveNegativeEnergy", this) + .unit("kvarh")), + new DummyElement(0x4D8E), new UnsignedDoublewordElement(0x4D8F, // + apparentEnergy = new ModbusReadLongChannel("ApparentEnergy", this).unit("kVAh"))) + + ); + } +} diff --git a/edge/src/io/openems/impl/device/socomec/SocomecMeter.java b/edge/src/io/openems/impl/device/socomec/SocomecMeter.java index dfcb2003c13..8bb51421f6b 100644 --- a/edge/src/io/openems/impl/device/socomec/SocomecMeter.java +++ b/edge/src/io/openems/impl/device/socomec/SocomecMeter.java @@ -1,253 +1,253 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.socomec; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.ModbusReadLongChannel; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; - -@ThingInfo(title = "Socomec Meter") -public class SocomecMeter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { - - /* - * Constructors - */ - public SocomecMeter(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - private final ConfigChannel type = new ConfigChannel("type", this); - - @Override - public ConfigChannel type() { - return type; - } - - private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); - - @Override - public ConfigChannel maxActivePower() { - return maxActivePower; - } - - private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); - - @Override - public ConfigChannel minActivePower() { - return minActivePower; - } - - /* - * Inherited Channels - */ - private ModbusReadLongChannel activePower; - private ModbusReadLongChannel apparentPower; - private ModbusReadLongChannel reactivePower; - private ModbusReadLongChannel activePowerL1; - private ModbusReadLongChannel activePowerL2; - private ModbusReadLongChannel activePowerL3; - private ModbusReadLongChannel reactivePowerL1; - private ModbusReadLongChannel reactivePowerL2; - private ModbusReadLongChannel reactivePowerL3; - private ModbusReadLongChannel voltageL1; - private ModbusReadLongChannel voltageL2; - private ModbusReadLongChannel voltageL3; - private ModbusReadLongChannel currentL1; - private ModbusReadLongChannel currentL2; - private ModbusReadLongChannel currentL3; - private ModbusReadLongChannel frequency; - - @Override - public ModbusReadLongChannel activePower() { - return activePower; - } - - @Override - public ModbusReadLongChannel apparentPower() { - return apparentPower; - } - - @Override - public ModbusReadLongChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public ReadChannel currentL1() { - return currentL1; - } - - @Override - public ReadChannel currentL2() { - return currentL2; - } - - @Override - public ReadChannel currentL3() { - return currentL3; - } - - @Override - public ReadChannel voltageL1() { - return voltageL1; - } - - @Override - public ReadChannel voltageL2() { - return voltageL2; - } - - @Override - public ReadChannel voltageL3() { - return voltageL3; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltageL1; - } - - /* - * This Channels - */ - public ModbusReadLongChannel activeNegativeEnergy; - public ModbusReadLongChannel activePositiveEnergy; - public ModbusReadLongChannel reactiveNegativeEnergy; - public ModbusReadLongChannel reactivePositiveEnergy; - public ModbusReadLongChannel apparentEnergy; - public ModbusReadLongChannel current; - - /* - * Methods - */ - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - return new ModbusProtocol( // - new ModbusRegisterRange(0xc558, // - - new UnsignedDoublewordElement(0xc558, // - voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(1)), - new UnsignedDoublewordElement(0xc55A, // - voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(1)), - new UnsignedDoublewordElement(0xc55C, // - voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(1)), - new UnsignedDoublewordElement(0xc55E, // - frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ").multiplier(1)), - new UnsignedDoublewordElement(0xc560, // - currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), - new UnsignedDoublewordElement(0xc562, // - currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), - new UnsignedDoublewordElement(0xc564, // - currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")), - new UnsignedDoublewordElement(0xc566, // - current = new ModbusReadLongChannel("Current", this).unit("mA")), - new SignedDoublewordElement(0xc568, // - activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(1)), - new SignedDoublewordElement(0xc56A, // - reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("var") - .multiplier(1)), - new SignedDoublewordElement(0xc56C, // - apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA") - .multiplier(1)), - new DummyElement(0xc56E, 0xc56F), - new SignedDoublewordElement(0xc570, // - activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W") - .multiplier(1)), - new SignedDoublewordElement(0xc572, // - activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W") - .multiplier(1)), - new SignedDoublewordElement(0xc574, // - activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W") - .multiplier(1)), - new SignedDoublewordElement(0xc576, // - reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var") - .multiplier(1)), - new SignedDoublewordElement(0xc578, // - reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("var") - .multiplier(1)), - new SignedDoublewordElement(0xc57A, // - reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("var") - .multiplier(1))), - new ModbusRegisterRange(0xc652, // - new UnsignedDoublewordElement(0xc652, // - activePositiveEnergy = new ModbusReadLongChannel("ActivePositiveEnergy", this) - .unit("kWh")), - new UnsignedDoublewordElement(0xc654, // - reactivePositiveEnergy = new ModbusReadLongChannel("ReactivePositiveEnergy", this) - .unit("kvarh")), - new UnsignedDoublewordElement(0xc656, // - apparentEnergy = new ModbusReadLongChannel("ApparentEnergy", this).unit("kVAh")), - new UnsignedDoublewordElement(0xc658, // - activeNegativeEnergy = new ModbusReadLongChannel("ActiveNegativeEnergy", this) - .unit("kWh")), - new UnsignedDoublewordElement(0xc65a, // - reactiveNegativeEnergy = new ModbusReadLongChannel("ReactiveNegativeEnergy", this) - .unit("kvarh")))); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.socomec; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.ModbusReadLongChannel; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.SignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.UnsignedDoublewordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRegisterRange; + +@ThingInfo(title = "Socomec Meter") +public class SocomecMeter extends ModbusDeviceNature implements SymmetricMeterNature, AsymmetricMeterNature { + + /* + * Constructors + */ + public SocomecMeter(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + /* + * Inherited Channels + */ + private ModbusReadLongChannel activePower; + private ModbusReadLongChannel apparentPower; + private ModbusReadLongChannel reactivePower; + private ModbusReadLongChannel activePowerL1; + private ModbusReadLongChannel activePowerL2; + private ModbusReadLongChannel activePowerL3; + private ModbusReadLongChannel reactivePowerL1; + private ModbusReadLongChannel reactivePowerL2; + private ModbusReadLongChannel reactivePowerL3; + private ModbusReadLongChannel voltageL1; + private ModbusReadLongChannel voltageL2; + private ModbusReadLongChannel voltageL3; + private ModbusReadLongChannel currentL1; + private ModbusReadLongChannel currentL2; + private ModbusReadLongChannel currentL3; + private ModbusReadLongChannel frequency; + + @Override + public ModbusReadLongChannel activePower() { + return activePower; + } + + @Override + public ModbusReadLongChannel apparentPower() { + return apparentPower; + } + + @Override + public ModbusReadLongChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltageL1; + } + + /* + * This Channels + */ + public ModbusReadLongChannel activeNegativeEnergy; + public ModbusReadLongChannel activePositiveEnergy; + public ModbusReadLongChannel reactiveNegativeEnergy; + public ModbusReadLongChannel reactivePositiveEnergy; + public ModbusReadLongChannel apparentEnergy; + public ModbusReadLongChannel current; + + /* + * Methods + */ + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + return new ModbusProtocol( // + new ModbusRegisterRange(0xc558, // + + new UnsignedDoublewordElement(0xc558, // + voltageL1 = new ModbusReadLongChannel("VoltageL1", this).unit("mV").multiplier(1)), + new UnsignedDoublewordElement(0xc55A, // + voltageL2 = new ModbusReadLongChannel("VoltageL2", this).unit("mV").multiplier(1)), + new UnsignedDoublewordElement(0xc55C, // + voltageL3 = new ModbusReadLongChannel("VoltageL3", this).unit("mV").multiplier(1)), + new UnsignedDoublewordElement(0xc55E, // + frequency = new ModbusReadLongChannel("Frequency", this).unit("mHZ").multiplier(1)), + new UnsignedDoublewordElement(0xc560, // + currentL1 = new ModbusReadLongChannel("CurrentL1", this).unit("mA")), + new UnsignedDoublewordElement(0xc562, // + currentL2 = new ModbusReadLongChannel("CurrentL2", this).unit("mA")), + new UnsignedDoublewordElement(0xc564, // + currentL3 = new ModbusReadLongChannel("CurrentL3", this).unit("mA")), + new UnsignedDoublewordElement(0xc566, // + current = new ModbusReadLongChannel("Current", this).unit("mA")), + new SignedDoublewordElement(0xc568, // + activePower = new ModbusReadLongChannel("ActivePower", this).unit("W").multiplier(1)), + new SignedDoublewordElement(0xc56A, // + reactivePower = new ModbusReadLongChannel("ReactivePower", this).unit("var") + .multiplier(1)), + new SignedDoublewordElement(0xc56C, // + apparentPower = new ModbusReadLongChannel("ApparentPower", this).unit("VA") + .multiplier(1)), + new DummyElement(0xc56E, 0xc56F), new SignedDoublewordElement(0xc570, // + activePowerL1 = new ModbusReadLongChannel("ActivePowerL1", this).unit("W") + .multiplier(1)), + new SignedDoublewordElement(0xc572, // + activePowerL2 = new ModbusReadLongChannel("ActivePowerL2", this).unit("W") + .multiplier(1)), + new SignedDoublewordElement(0xc574, // + activePowerL3 = new ModbusReadLongChannel("ActivePowerL3", this).unit("W") + .multiplier(1)), + new SignedDoublewordElement(0xc576, // + reactivePowerL1 = new ModbusReadLongChannel("ReactivePowerL1", this).unit("var") + .multiplier(1)), + new SignedDoublewordElement(0xc578, // + reactivePowerL2 = new ModbusReadLongChannel("ReactivePowerL2", this).unit("var") + .multiplier(1)), + new SignedDoublewordElement(0xc57A, // + reactivePowerL3 = new ModbusReadLongChannel("ReactivePowerL3", this).unit("var") + .multiplier(1))), + new ModbusRegisterRange(0xc652, // + new UnsignedDoublewordElement(0xc652, // + activePositiveEnergy = new ModbusReadLongChannel( + "ActivePositiveEnergy", this).unit("kWh")), + new UnsignedDoublewordElement(0xc654, // + reactivePositiveEnergy = new ModbusReadLongChannel( + "ReactivePositiveEnergy", this).unit("kvarh")), + new UnsignedDoublewordElement(0xc656, // + apparentEnergy = new ModbusReadLongChannel("ApparentEnergy", this).unit("kVAh")), + new UnsignedDoublewordElement(0xc658, // + activeNegativeEnergy = new ModbusReadLongChannel( + "ActiveNegativeEnergy", this).unit("kWh")), + new UnsignedDoublewordElement(0xc65a, // + reactiveNegativeEnergy = new ModbusReadLongChannel("ReactiveNegativeEnergy", this) + .unit("kvarh")))); + } +} diff --git a/edge/src/io/openems/impl/device/studer/StuderVs70.java b/edge/src/io/openems/impl/device/studer/StuderVs70.java index 98796107a66..c19bf800808 100644 --- a/edge/src/io/openems/impl/device/studer/StuderVs70.java +++ b/edge/src/io/openems/impl/device/studer/StuderVs70.java @@ -1,45 +1,46 @@ -package io.openems.impl.device.studer; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.studer.StuderDevice; - -@ThingInfo(title = "Studer VS-70") -public class StuderVs70 extends StuderDevice { - - /* - * Constructors - */ - public StuderVs70() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Charger", description = "Sets the charger nature.", type = StuderVs70Charger.class) - public final ConfigChannel charger = new ConfigChannel<>("charger", this); - - /* - * Methods - */ - @Override - public String toString() { - return "StuderVs70 [charger=" + charger + ", getThingId()=" + id() + "]"; - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (charger.valueOptional().isPresent()) { - natures.add(charger.valueOptional().get()); - } - return natures; - } -} +package io.openems.impl.device.studer; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.studer.StuderDevice; + +@ThingInfo(title = "Studer VS-70") +public class StuderVs70 extends StuderDevice { + + /* + * Constructors + */ + public StuderVs70(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Charger", description = "Sets the charger nature.", type = StuderVs70Charger.class) + public final ConfigChannel charger = new ConfigChannel<>("charger", this); + + /* + * Methods + */ + @Override + public String toString() { + return "StuderVs70 [charger=" + charger + ", getThingId()=" + id() + "]"; + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (charger.valueOptional().isPresent()) { + natures.add(charger.valueOptional().get()); + } + return natures; + } +} diff --git a/edge/src/io/openems/impl/device/studer/StuderVs70Charger.java b/edge/src/io/openems/impl/device/studer/StuderVs70Charger.java index 3dd9345d59b..2b444065314 100644 --- a/edge/src/io/openems/impl/device/studer/StuderVs70Charger.java +++ b/edge/src/io/openems/impl/device/studer/StuderVs70Charger.java @@ -1,77 +1,78 @@ -package io.openems.impl.device.studer; - -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.studer.StuderDeviceNature; -import io.openems.impl.protocol.studer.internal.StuderProtocol; -import io.openems.impl.protocol.studer.internal.object.FloatParameterObject; -import io.openems.impl.protocol.studer.internal.object.FloatUserinfoObject; -import io.openems.impl.protocol.studer.internal.object.IntParameterObject; - -@ThingInfo(title = "Studer VS-70 Charger") -public class StuderVs70Charger extends StuderDeviceNature { - - /* - * Constructors - */ - public StuderVs70Charger(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Inherited Channels - */ - public ReadChannel batteryVoltage; - public ReadChannel nominalCurrent = new StaticValueChannel("nominalCurrent", this, 70f).unit("A"); - public WriteChannel batteryChargeCurrentUnsavedValue; - - // @Override - // public WriteChannel setMaxCurrent() { - // return batteryChargeCurrentUnsavedValue; - // } - // - // @Override - // public ReadChannel getBatteryVoltage() { - // return batteryVoltage; - // } - // - // @Override - // public ReadChannel getNominalCurrent() { - // return nominalCurrent; - // } - - /* - * This Channels - */ - public WriteChannel batteryChargeCurrentValue; - public WriteChannel setStart; - public WriteChannel setStop; - - /* - * Methods - */ - @Override - protected StuderProtocol defineStuderProtocol() throws ConfigException { - StuderProtocol p = new StuderProtocol(); - - FloatParameterObject batteryChargeCurrent = new FloatParameterObject(14217, "batteryChargeCurrent", "Adc", - this); - p.addObject(batteryChargeCurrent); - batteryChargeCurrentValue = batteryChargeCurrent.value().channel(); - batteryChargeCurrentUnsavedValue = batteryChargeCurrent.unsavedValue().channel(); - IntParameterObject start = new IntParameterObject(14038, "start", "", this); - p.addObject(start); - setStart = start.value().channel(); - IntParameterObject stop = new IntParameterObject(14039, "stop", "", this); - p.addObject(stop); - setStop = stop.value().channel(); - FloatUserinfoObject vBatt = new FloatUserinfoObject(15000, "BatteryVoltage", "V", this); - p.addObject(vBatt); - batteryVoltage = (ReadChannel) vBatt.value().channel(); - return p; - } - -} +package io.openems.impl.device.studer; + +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.studer.StuderDeviceNature; +import io.openems.impl.protocol.studer.internal.StuderProtocol; +import io.openems.impl.protocol.studer.internal.object.FloatParameterObject; +import io.openems.impl.protocol.studer.internal.object.FloatUserinfoObject; +import io.openems.impl.protocol.studer.internal.object.IntParameterObject; + +@ThingInfo(title = "Studer VS-70 Charger") +public class StuderVs70Charger extends StuderDeviceNature { + + /* + * Constructors + */ + public StuderVs70Charger(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Inherited Channels + */ + public ReadChannel batteryVoltage; + public ReadChannel nominalCurrent = new StaticValueChannel("nominalCurrent", this, 70f).unit("A"); + public WriteChannel batteryChargeCurrentUnsavedValue; + + // @Override + // public WriteChannel setMaxCurrent() { + // return batteryChargeCurrentUnsavedValue; + // } + // + // @Override + // public ReadChannel getBatteryVoltage() { + // return batteryVoltage; + // } + // + // @Override + // public ReadChannel getNominalCurrent() { + // return nominalCurrent; + // } + + /* + * This Channels + */ + public WriteChannel batteryChargeCurrentValue; + public WriteChannel setStart; + public WriteChannel setStop; + + /* + * Methods + */ + @Override + protected StuderProtocol defineStuderProtocol() throws ConfigException { + StuderProtocol p = new StuderProtocol(); + + FloatParameterObject batteryChargeCurrent = new FloatParameterObject(14217, "batteryChargeCurrent", "Adc", + this); + p.addObject(batteryChargeCurrent); + batteryChargeCurrentValue = batteryChargeCurrent.value().channel(); + batteryChargeCurrentUnsavedValue = batteryChargeCurrent.unsavedValue().channel(); + IntParameterObject start = new IntParameterObject(14038, "start", "", this); + p.addObject(start); + setStart = start.value().channel(); + IntParameterObject stop = new IntParameterObject(14039, "stop", "", this); + p.addObject(stop); + setStop = stop.value().channel(); + FloatUserinfoObject vBatt = new FloatUserinfoObject(15000, "BatteryVoltage", "V", this); + p.addObject(vBatt); + batteryVoltage = (ReadChannel) vBatt.value().channel(); + return p; + } + +} diff --git a/edge/src/io/openems/impl/device/system/System.java b/edge/src/io/openems/impl/device/system/System.java index 72a4ee83aad..d4ed7405c2d 100644 --- a/edge/src/io/openems/impl/device/system/System.java +++ b/edge/src/io/openems/impl/device/system/System.java @@ -1,61 +1,79 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.system; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.system.SystemDevice; - -@ThingInfo(title = "Operating system") -public class System extends SystemDevice { - - /* - * Constructors - */ - public System() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "System", description = "Sets the system nature.", type = SystemNature.class) - public final ConfigChannel system = new ConfigChannel<>("system", this); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (system.valueOptional().isPresent()) { - natures.add(system.valueOptional().get()); - } - return natures; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.system; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.DebugChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.system.SystemDevice; + +@ThingInfo(title = "Operating system") +public class System extends SystemDevice { + + /* + * Constructors + */ + public System(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "System", description = "Sets the system nature.", type = SystemNature.class) + public final ConfigChannel system = new ConfigChannel<>("system", this); + @ConfigInfo(title = "Debug", description = "Enables DebugChannels to write into database", type = Boolean.class, isOptional = true, defaultValue = "false") + public final ConfigChannel debug = new ConfigChannel("debug", this) + .addChangeListener(new ChannelChangeListener() { + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (newValue.isPresent() && (boolean) newValue.get()) { + DebugChannel.enableDebug(); + } else { + DebugChannel.disableDebug(); + } + } + }); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (system.valueOptional().isPresent()) { + natures.add(system.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/system/SystemNature.java b/edge/src/io/openems/impl/device/system/SystemNature.java index 0fa013045c1..39a23c7acb1 100644 --- a/edge/src/io/openems/impl/device/system/SystemNature.java +++ b/edge/src/io/openems/impl/device/system/SystemNature.java @@ -1,122 +1,123 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.system; - -import java.net.Inet4Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.net.UnknownHostException; -import java.util.Arrays; -import java.util.Enumeration; - -import io.openems.api.channel.ReadChannel; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.impl.protocol.system.SystemDeviceNature; -import io.openems.impl.protocol.system.SystemReadChannel; - -@ThingInfo(title = "Operating system") -public class SystemNature extends SystemDeviceNature implements io.openems.api.device.nature.system.SystemNature { - - /* - * Constructors - */ - public SystemNature(String thingId) throws ConfigException { - super(thingId); - try { - OPENEMS_STATIC_IPS = new Inet4Address[] { // - // 192.168.100.100 - (Inet4Address) InetAddress - .getByAddress(new byte[] { (byte) 192, (byte) 168, (byte) 100, (byte) 100 }), - // 10.4.0.1 - (Inet4Address) InetAddress.getByAddress(new byte[] { (byte) 10, (byte) 4, (byte) 0, (byte) 1 }) }; - } catch (UnknownHostException e) { - throw new ConfigException("Error initializing OpenEMS Static IP: " + e.getMessage()); - } - } - - /* - * Inherited Channels - */ - private SystemReadChannel primaryIpAddress = new SystemReadChannel("PrimaryIpAddress", - this); - - @Override - public ReadChannel primaryIpAddress() { - return primaryIpAddress; - } - - /* - * Fields - */ - private final Inet4Address[] OPENEMS_STATIC_IPS; - - /* - * Methods - */ - @Override - protected void update() { - // Get IP address - Inet4Address primaryIpAddress; - try { - primaryIpAddress = getPrimaryIpAddress(); - if (primaryIpAddress != null) { - this.primaryIpAddress.updateValue(primaryIpAddress); - } - } catch (SocketException e) { - log.error("Error getting primary IPv4 address: " + e.getMessage()); - } - - } - - private Inet4Address getPrimaryIpAddress() throws SocketException { - Inet4Address primaryIpAddress = null; - boolean foundOpenEmsStaticIp = false; - Enumeration e = NetworkInterface.getNetworkInterfaces(); - while (e.hasMoreElements()) { - NetworkInterface n = e.nextElement(); - Enumeration ee = n.getInetAddresses(); - while (ee.hasMoreElements()) { - InetAddress i = ee.nextElement(); - if (i instanceof Inet4Address) { - Inet4Address inetAddress = (Inet4Address) i; - if (inetAddress.isLinkLocalAddress() || inetAddress.isLoopbackAddress()) { - // ignore local and loopback address - continue; - } else if (Arrays.asList(OPENEMS_STATIC_IPS).contains(inetAddress)) { - // ignore static ip - foundOpenEmsStaticIp = true; - continue; - } else { - // take this ip and stop - primaryIpAddress = inetAddress; - } - } - } - } - if (primaryIpAddress == null && foundOpenEmsStaticIp) { - // set static ip if no other ip was found and it is existing - primaryIpAddress = OPENEMS_STATIC_IPS[0]; - } - return primaryIpAddress; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.system; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.util.Arrays; +import java.util.Enumeration; + +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.impl.protocol.system.SystemDeviceNature; +import io.openems.impl.protocol.system.SystemReadChannel; + +@ThingInfo(title = "Operating system") +public class SystemNature extends SystemDeviceNature implements io.openems.api.device.nature.system.SystemNature { + + /* + * Constructors + */ + public SystemNature(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + try { + OPENEMS_STATIC_IPS = new Inet4Address[] { // + // 192.168.100.100 + (Inet4Address) InetAddress + .getByAddress(new byte[] { (byte) 192, (byte) 168, (byte) 100, (byte) 100 }), + // 10.4.0.1 + (Inet4Address) InetAddress.getByAddress(new byte[] { (byte) 10, (byte) 4, (byte) 0, (byte) 1 }) }; + } catch (UnknownHostException e) { + throw new ConfigException("Error initializing OpenEMS Static IP: " + e.getMessage()); + } + } + + /* + * Inherited Channels + */ + private SystemReadChannel primaryIpAddress = new SystemReadChannel("PrimaryIpAddress", + this); + + @Override + public ReadChannel primaryIpAddress() { + return primaryIpAddress; + } + + /* + * Fields + */ + private final Inet4Address[] OPENEMS_STATIC_IPS; + + /* + * Methods + */ + @Override + protected void update() { + // Get IP address + Inet4Address primaryIpAddress; + try { + primaryIpAddress = getPrimaryIpAddress(); + if (primaryIpAddress != null) { + this.primaryIpAddress.updateValue(primaryIpAddress); + } + } catch (SocketException e) { + log.error("Error getting primary IPv4 address: " + e.getMessage()); + } + + } + + private Inet4Address getPrimaryIpAddress() throws SocketException { + Inet4Address primaryIpAddress = null; + boolean foundOpenEmsStaticIp = false; + Enumeration e = NetworkInterface.getNetworkInterfaces(); + while (e.hasMoreElements()) { + NetworkInterface n = e.nextElement(); + Enumeration ee = n.getInetAddresses(); + while (ee.hasMoreElements()) { + InetAddress i = ee.nextElement(); + if (i instanceof Inet4Address) { + Inet4Address inetAddress = (Inet4Address) i; + if (inetAddress.isLinkLocalAddress() || inetAddress.isLoopbackAddress()) { + // ignore local and loopback address + continue; + } else if (Arrays.asList(OPENEMS_STATIC_IPS).contains(inetAddress)) { + // ignore static ip + foundOpenEmsStaticIp = true; + continue; + } else { + // take this ip and stop + primaryIpAddress = inetAddress; + } + } + } + } + if (primaryIpAddress == null && foundOpenEmsStaticIp) { + // set static ip if no other ip was found and it is existing + primaryIpAddress = OPENEMS_STATIC_IPS[0]; + } + return primaryIpAddress; + } +} diff --git a/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEss.java b/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEss.java index 6cef9f0abe7..f96a3df9a38 100644 --- a/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEss.java +++ b/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEss.java @@ -1,31 +1,32 @@ -package io.openems.impl.device.system.asymmetrictosymmetricess; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.system.SystemDevice; - -public class AsymmetricToSymmetricEss extends SystemDevice { - - @ConfigInfo(title = "AsymmetricToSymmetricEss", description = "Sets the wrapper nature to use asymmetric ess as symmetric ess.", type = AsymmetricToSymmetricEssNature.class) - public final ConfigChannel wrapper = new ConfigChannel<>("wrapper", this); - - public AsymmetricToSymmetricEss() throws OpenemsException { - super(); - // TODO Auto-generated constructor stub - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (wrapper.valueOptional().isPresent()) { - natures.add(wrapper.valueOptional().get()); - } - return natures; - } - -} +package io.openems.impl.device.system.asymmetrictosymmetricess; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.system.SystemDevice; + +public class AsymmetricToSymmetricEss extends SystemDevice { + + @ConfigInfo(title = "AsymmetricToSymmetricEss", description = "Sets the wrapper nature to use asymmetric ess as symmetric ess.", type = AsymmetricToSymmetricEssNature.class) + public final ConfigChannel wrapper = new ConfigChannel<>("wrapper", this); + + public AsymmetricToSymmetricEss(Bridge parent) throws OpenemsException { + super(parent); + // TODO Auto-generated constructor stub + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (wrapper.valueOptional().isPresent()) { + natures.add(wrapper.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEssNature.java b/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEssNature.java index 37618108e4c..4a5776fedec 100644 --- a/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEssNature.java +++ b/edge/src/io/openems/impl/device/system/asymmetrictosymmetricess/AsymmetricToSymmetricEssNature.java @@ -1,689 +1,690 @@ -package io.openems.impl.device.system.asymmetrictosymmetricess; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.FunctionalWriteChannel; -import io.openems.api.channel.FunctionalWriteChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.device.nature.ess.AsymmetricEssNature; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.WriteChannelException; -import io.openems.api.thing.ThingChannelsUpdatedListener; -import io.openems.core.ThingRepository; -import io.openems.core.utilities.ControllerUtils; -import io.openems.impl.protocol.system.SystemDeviceNature; - -public class AsymmetricToSymmetricEssNature extends SystemDeviceNature - implements SymmetricEssNature, ChannelChangeListener { - - private final Logger log; - private List listeners; - - private static ThingRepository repo = ThingRepository.getInstance(); - - @ConfigInfo(title = "Ess", description = "Sets the Ess devices for the proxy.", type = String.class) - public ConfigChannel essId = new ConfigChannel("essId", this).addChangeListener(this); - private ConfigChannel minSoc = new ConfigChannel<>("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - private AsymmetricEssNature ess; - - private FunctionalReadChannel soc = new FunctionalReadChannel("Soc", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - if (channels.length > 0) { - return channels[0].value(); - } - return null; - } - - }).unit("%"); - - private FunctionalReadChannel allowedCharge = new FunctionalReadChannel("AllowedCharge", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - if (channels.length > 0) { - return channels[0].value(); - } - return null; - } - - }).unit("W"); - private FunctionalReadChannel allowedDischarge = new FunctionalReadChannel("AllowedDischarge", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - if (channels.length > 0) { - return channels[0].value(); - } - return null; - } - - }).unit("W"); - private FunctionalReadChannel allowedApparent = new FunctionalReadChannel("AllowedApparent", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - if (channels.length > 0) { - return channels[0].value(); - } - return null; - } - - }).unit("VA"); - - private FunctionalReadChannel activePower = new FunctionalReadChannel("ActivePower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - try { - sum += ess.activePowerL1().value(); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - try { - sum += ess.activePowerL2().value(); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - try { - sum += ess.activePowerL3().value(); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - return sum; - } - - }).unit("W"); - private FunctionalReadChannel reactivePower = new FunctionalReadChannel("ReactivePower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - try { - sum += ess.reactivePowerL1().value(); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - try { - sum += ess.reactivePowerL2().value(); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - try { - sum += ess.reactivePowerL3().value(); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - return sum; - } - - }).unit("Var"); - private FunctionalReadChannel apparentPower = new FunctionalReadChannel("ApparentPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - try { - sum = ControllerUtils.calculateApparentPower(channels[0].value(), channels[1].value()); - } catch (InvalidValueException e) { - log.error("Can't read values of " + ess.id(), e); - } - return sum; - } - - }, activePower, reactivePower).unit("VA"); - - private FunctionalWriteChannel setActivePower = new FunctionalWriteChannel("SetActivePower", this, - new FunctionalWriteChannelFunction() { - - @Override - public void setValue(Long newValue, String newLabel, WriteChannel... channels) { - long power = newValue / 3; - for (WriteChannel channel : channels) { - try { - channel.pushWrite(power); - } catch (WriteChannelException e) { - log.error("Failed to write " + power + " to " + channel.address(), e); - } - } - } - - @Override - public Long getValue(ReadChannel... channels) { - long sum = 0L; - for (ReadChannel channel : channels) { - try { - sum += channel.value(); - } catch (InvalidValueException e) { - log.error("Can't read ActivePower from " + channel.address()); - } - } - return sum; - } - - @Override - public Long getMinValue(WriteChannel... channels) { - long min = Long.MIN_VALUE; - boolean isPresent = false; - for (WriteChannel channelMin : channels) { - if (channelMin.writeMin().isPresent() && channelMin.writeMin().get() > min) { - min = channelMin.writeMin().get(); - isPresent = true; - } - } - if (isPresent) { - return min * 3; - } - return null; - } - - @Override - public Long getMaxValue(WriteChannel... channels) { - long max = Long.MAX_VALUE; - boolean isPresent = false; - for (WriteChannel channelMax : channels) { - if (channelMax.writeMax().isPresent() && channelMax.writeMax().get() < max) { - max = channelMax.writeMax().get(); - isPresent = true; - } - } - if (isPresent) { - return max * 3; - } - return null; - } - - @Override - public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - long min = 0L; - min = newValue / 3; - if (min < getMinValue(channels)) { - throw new WriteChannelException("Value [" + newValue - + "] for [ SetActivePower ] is out of boundaries. Different min value [" - + getMinValue(channels) + "] had already been set"); - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMin(min); - } catch (WriteChannelException e) { - log.error("Failed to write " + min + " to " + channel.address(), e); - } - } - } - - @Override - public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - long max = 0L; - max = newValue / 3; - if (max > getMaxValue(channels)) { - throw new WriteChannelException("Value [" + newValue - + "] for [ SetActivePower ] is out of boundaries. Different max value [" - + getMaxValue(channels) + "] had already been set"); - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMax(max); - } catch (WriteChannelException e) { - log.error("Failed to write " + max + " to " + channel.address(), e); - } - } - } - - }); - private FunctionalWriteChannel setReactivePower = new FunctionalWriteChannel("SetReactivePower", this, - new FunctionalWriteChannelFunction() { - - @Override - public void setValue(Long newValue, String newLabel, WriteChannel... channels) { - long power = 0L; - if (channels.length > 0) { - power = newValue / channels.length; - } - for (WriteChannel channel : channels) { - try { - channel.pushWrite(power); - } catch (WriteChannelException e) { - log.error("Failed to write " + power + " to " + channel.address(), e); - } - } - } - - @Override - public Long getValue(ReadChannel... channels) { - long sum = 0L; - for (ReadChannel channel : channels) { - try { - sum += channel.value(); - } catch (InvalidValueException e) { - log.error("Can't read ReactivePower from " + channel.address()); - } - } - return sum; - } - - @Override - public Long getMinValue(WriteChannel... channels) { - long min = Long.MIN_VALUE; - boolean isPresent = false; - for (WriteChannel channelMin : channels) { - if (channelMin.writeMin().isPresent() && channelMin.writeMin().get() > min) { - min = channelMin.writeMin().get(); - isPresent = true; - } - } - if (isPresent) { - return min * 3; - } - return null; - } - - @Override - public Long getMaxValue(WriteChannel... channels) { - long max = Long.MAX_VALUE; - boolean isPresent = false; - for (WriteChannel channelMax : channels) { - if (channelMax.writeMax().isPresent() && channelMax.writeMax().get() < max) { - max = channelMax.writeMax().get(); - isPresent = true; - } - } - if (isPresent) { - return max * 3; - } - return null; - } - - @Override - public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - long min = 0L; - min = newValue / 3; - if (getMinValue(channels) != null && min < getMinValue(channels)) { - throw new WriteChannelException("Value [" + newValue - + "] for [ SetReactivePower ] is out of boundaries. Different min value [" - + getMinValue(channels) + "] had already been set"); - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMin(min); - } catch (WriteChannelException e) { - log.error("Failed to write " + min + " to " + channel.address(), e); - } - } - } - - @Override - public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - long max = 0L; - max = newValue / 3; - if (getMaxValue(channels) != null && max > getMaxValue(channels)) { - throw new WriteChannelException("Value [" + newValue - + "] for [ SetReactivePower ] is out of boundaries. Different max value [" - + getMaxValue(channels) + "] had already been set"); - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMax(max); - } catch (WriteChannelException e) { - log.error("Failed to write " + max + " to " + channel.address(), e); - } - } - } - - }); - - private FunctionalReadChannel maxNominalPower = new FunctionalReadChannel("MaxNominalPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - if (channels.length > 0) { - return channels[0].value(); - } - return null; - } - - }).unit("VA"); - private FunctionalReadChannel capacity = new FunctionalReadChannel("Capacity", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) throws InvalidValueException { - if (channels.length > 0) { - return channels[0].value(); - } - return null; - } - - }).unit("Wh"); - - private FunctionalReadChannel gridMode = new FunctionalReadChannel("GridMode", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - if (channels[0].labelOptional().equals(Optional.of(EssNature.ON_GRID))) { - return 1L; - } - return 0L; - } - - }).label(0L, EssNature.OFF_GRID).label(1L, EssNature.ON_GRID); - private FunctionalReadChannel systemState = new FunctionalReadChannel("SystemState", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - if (channels[0].labelOptional().equals(Optional.of(EssNature.ON))) { - return 1L; - } else if (channels[0].labelOptional().equals(Optional.of(EssNature.OFF))) { - return 0L; - } else if (channels[0].labelOptional().equals(Optional.of(EssNature.FAULT))) { - return 2L; - } else { - return 3L; - } - } - - }).label(0L, EssNature.OFF).label(1L, EssNature.ON).label(2L, EssNature.FAULT).label(3L, "UNDEFINED"); - private StatusBitChannels warning = new StatusBitChannels("Warning", this); - - private FunctionalWriteChannel setWorkState = new FunctionalWriteChannel("SetWorkState", this, - new FunctionalWriteChannelFunction() { - - @Override - public void setValue(Long newValue, String newLabel, WriteChannel... channels) { - for (WriteChannel channel : channels) { - try { - channel.pushWriteFromLabel(newLabel); - } catch (WriteChannelException e) { - log.error("Can't set value for channel " + channel.address(), e); - } - } - } - - @Override - public Long getValue(ReadChannel... channels) { - if (channels.length > 0) { - if (channels[0].labelOptional().equals(Optional.of(EssNature.ON))) { - return 1L; - } - } - return 0L; - } - - @Override - public Long getMinValue(WriteChannel... channels) { - if (channels.length > 0 && channels[0].writeMin().isPresent()) { - return channels[0].writeMin().get(); - } - return null; - } - - @Override - public Long getMaxValue(WriteChannel... channels) { - if (channels.length > 0 && channels[0].writeMax().isPresent()) { - return channels[0].writeMax().get(); - } - return null; - } - - @Override - public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - if (getMinValue(channels) != null && newValue < getMinValue(channels)) { - throw new WriteChannelException( - "Value [" + newValue + "] for [ GridMode ] is out of boundaries. Different min value [" - + getMinValue(channels) + "] had already been set"); - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMax(newValue); - } catch (WriteChannelException e) { - log.error("Failed to write " + newValue + " to " + channel.address(), e); - } - } - } - - @Override - public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - if (getMaxValue(channels) != null && newValue < getMaxValue(channels)) { - throw new WriteChannelException( - "Value [" + newValue + "] for [ GridMode ] is out of boundaries. Different max value [" - + getMaxValue(channels) + "] had already been set"); - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMax(newValue); - } catch (WriteChannelException e) { - log.error("Failed to write " + newValue + " to " + channel.address(), e); - } - } - } - - }).label(0L, EssNature.OFF).label(1L, EssNature.ON); - - public AsymmetricToSymmetricEssNature(String id) throws ConfigException { - super(id); - log = LoggerFactory.getLogger(this.getClass()); - this.listeners = new ArrayList<>(); - } - - @Override - public void setAsRequired(Channel channel) { - // unused - } - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - @Override - public ReadChannel capacity() { - return capacity; - } - - @Override - public void addListener(ThingChannelsUpdatedListener listener) { - this.listeners.add(listener); - } - - @Override - public void removeListener(ThingChannelsUpdatedListener listener) { - this.listeners.remove(listener); - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel.equals(essId)) { - loadEss(); - } - } - - private void loadEss() { - Set natures = repo.getDeviceNatures(); - String essId; - try { - essId = this.essId.value(); - if (ess != null) { - // remove old ess - activePower.removeChannel(ess.activePowerL1()); - activePower.removeChannel(ess.activePowerL2()); - activePower.removeChannel(ess.activePowerL3()); - reactivePower.removeChannel(ess.reactivePowerL1()); - reactivePower.removeChannel(ess.reactivePowerL2()); - reactivePower.removeChannel(ess.reactivePowerL3()); - setActivePower.removeChannel(ess.setActivePowerL1()); - setActivePower.removeChannel(ess.setActivePowerL2()); - setActivePower.removeChannel(ess.setActivePowerL3()); - setReactivePower.removeChannel(ess.setReactivePowerL1()); - setReactivePower.removeChannel(ess.setReactivePowerL2()); - setReactivePower.removeChannel(ess.setReactivePowerL3()); - allowedCharge.removeChannel(ess.allowedCharge()); - allowedDischarge.removeChannel(ess.allowedDischarge()); - allowedApparent.removeChannel(ess.allowedApparent()); - systemState.removeChannel(ess.systemState()); - setWorkState.removeChannel(ess.setWorkState()); - capacity.removeChannel(ess.capacity()); - maxNominalPower.removeChannel(ess.maxNominalPower()); - gridMode.removeChannel(ess.gridMode()); - soc.removeChannel(ess.soc()); - ess = null; - } - for (DeviceNature nature : natures) { - if (nature instanceof AsymmetricEssNature) { - if (essId.contains(nature.id())) { - AsymmetricEssNature ess = (AsymmetricEssNature) nature; - this.ess = ess; - activePower.addChannel(ess.activePowerL1()); - activePower.addChannel(ess.activePowerL2()); - activePower.addChannel(ess.activePowerL3()); - reactivePower.addChannel(ess.reactivePowerL1()); - reactivePower.addChannel(ess.reactivePowerL2()); - reactivePower.addChannel(ess.reactivePowerL3()); - setActivePower.addChannel(ess.setActivePowerL1()); - setActivePower.addChannel(ess.setActivePowerL2()); - setActivePower.addChannel(ess.setActivePowerL3()); - setReactivePower.addChannel(ess.setReactivePowerL1()); - setReactivePower.addChannel(ess.setReactivePowerL2()); - setReactivePower.addChannel(ess.setReactivePowerL3()); - allowedCharge.addChannel(ess.allowedCharge()); - allowedDischarge.addChannel(ess.allowedDischarge()); - allowedApparent.addChannel(ess.allowedApparent()); - systemState.addChannel(ess.systemState()); - setWorkState.addChannel(ess.setWorkState()); - capacity.addChannel(ess.capacity()); - maxNominalPower.addChannel(ess.maxNominalPower()); - gridMode.addChannel(ess.gridMode()); - soc.addChannel(ess.soc()); - } - } - } - } catch (InvalidValueException e) { - log.error("esss value is invalid!", e); - } - } - - @Override - public void init() { - for (ThingChannelsUpdatedListener listener : this.listeners) { - listener.thingChannelsUpdated(this); - } - } - - @Override - protected void update() { - if (ess == null) { - loadEss(); - } - } - -} +package io.openems.impl.device.system.asymmetrictosymmetricess; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.FunctionalWriteChannel; +import io.openems.api.channel.FunctionalWriteChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.WriteChannelException; +import io.openems.api.thing.ThingChannelsUpdatedListener; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.ControllerUtils; +import io.openems.impl.protocol.system.SystemDeviceNature; + +public class AsymmetricToSymmetricEssNature extends SystemDeviceNature + implements SymmetricEssNature, ChannelChangeListener { + + private final Logger log; + private List listeners; + + private static ThingRepository repo = ThingRepository.getInstance(); + + @ConfigInfo(title = "Ess", description = "Sets the Ess devices for the proxy.", type = String.class) + public ConfigChannel essId = new ConfigChannel("essId", this).addChangeListener(this); + private ConfigChannel minSoc = new ConfigChannel<>("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + private AsymmetricEssNature ess; + + private FunctionalReadChannel soc = new FunctionalReadChannel("Soc", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + if (channels.length > 0) { + return channels[0].value(); + } + return null; + } + + }).unit("%"); + + private FunctionalReadChannel allowedCharge = new FunctionalReadChannel("AllowedCharge", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + if (channels.length > 0) { + return channels[0].value(); + } + return null; + } + + }).unit("W"); + private FunctionalReadChannel allowedDischarge = new FunctionalReadChannel("AllowedDischarge", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + if (channels.length > 0) { + return channels[0].value(); + } + return null; + } + + }).unit("W"); + private FunctionalReadChannel allowedApparent = new FunctionalReadChannel("AllowedApparent", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + if (channels.length > 0) { + return channels[0].value(); + } + return null; + } + + }).unit("VA"); + + private FunctionalReadChannel activePower = new FunctionalReadChannel("ActivePower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + try { + sum += ess.activePowerL1().value(); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + try { + sum += ess.activePowerL2().value(); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + try { + sum += ess.activePowerL3().value(); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + return sum; + } + + }).unit("W"); + private FunctionalReadChannel reactivePower = new FunctionalReadChannel("ReactivePower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + try { + sum += ess.reactivePowerL1().value(); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + try { + sum += ess.reactivePowerL2().value(); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + try { + sum += ess.reactivePowerL3().value(); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + return sum; + } + + }).unit("Var"); + private FunctionalReadChannel apparentPower = new FunctionalReadChannel("ApparentPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + try { + sum = ControllerUtils.calculateApparentPower(channels[0].value(), channels[1].value()); + } catch (InvalidValueException e) { + log.error("Can't read values of " + ess.id(), e); + } + return sum; + } + + }, activePower, reactivePower).unit("VA"); + + private FunctionalWriteChannel setActivePower = new FunctionalWriteChannel("SetActivePower", this, + new FunctionalWriteChannelFunction() { + + @Override + public void setValue(Long newValue, String newLabel, WriteChannel... channels) { + long power = newValue / 3; + for (WriteChannel channel : channels) { + try { + channel.pushWrite(power); + } catch (WriteChannelException e) { + log.error("Failed to write " + power + " to " + channel.address(), e); + } + } + } + + @Override + public Long getValue(ReadChannel... channels) { + long sum = 0L; + for (ReadChannel channel : channels) { + try { + sum += channel.value(); + } catch (InvalidValueException e) { + log.error("Can't read ActivePower from " + channel.address()); + } + } + return sum; + } + + @Override + public Long getMinValue(WriteChannel... channels) { + long min = Long.MIN_VALUE; + boolean isPresent = false; + for (WriteChannel channelMin : channels) { + if (channelMin.writeMin().isPresent() && channelMin.writeMin().get() > min) { + min = channelMin.writeMin().get(); + isPresent = true; + } + } + if (isPresent) { + return min * 3; + } + return null; + } + + @Override + public Long getMaxValue(WriteChannel... channels) { + long max = Long.MAX_VALUE; + boolean isPresent = false; + for (WriteChannel channelMax : channels) { + if (channelMax.writeMax().isPresent() && channelMax.writeMax().get() < max) { + max = channelMax.writeMax().get(); + isPresent = true; + } + } + if (isPresent) { + return max * 3; + } + return null; + } + + @Override + public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + long min = 0L; + min = newValue / 3; + if (min < getMinValue(channels)) { + throw new WriteChannelException("Value [" + newValue + + "] for [ SetActivePower ] is out of boundaries. Different min value [" + + getMinValue(channels) + "] had already been set"); + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMin(min); + } catch (WriteChannelException e) { + log.error("Failed to write " + min + " to " + channel.address(), e); + } + } + } + + @Override + public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + long max = 0L; + max = newValue / 3; + if (max > getMaxValue(channels)) { + throw new WriteChannelException("Value [" + newValue + + "] for [ SetActivePower ] is out of boundaries. Different max value [" + + getMaxValue(channels) + "] had already been set"); + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMax(max); + } catch (WriteChannelException e) { + log.error("Failed to write " + max + " to " + channel.address(), e); + } + } + } + + }); + private FunctionalWriteChannel setReactivePower = new FunctionalWriteChannel("SetReactivePower", this, + new FunctionalWriteChannelFunction() { + + @Override + public void setValue(Long newValue, String newLabel, WriteChannel... channels) { + long power = 0L; + if (channels.length > 0) { + power = newValue / channels.length; + } + for (WriteChannel channel : channels) { + try { + channel.pushWrite(power); + } catch (WriteChannelException e) { + log.error("Failed to write " + power + " to " + channel.address(), e); + } + } + } + + @Override + public Long getValue(ReadChannel... channels) { + long sum = 0L; + for (ReadChannel channel : channels) { + try { + sum += channel.value(); + } catch (InvalidValueException e) { + log.error("Can't read ReactivePower from " + channel.address()); + } + } + return sum; + } + + @Override + public Long getMinValue(WriteChannel... channels) { + long min = Long.MIN_VALUE; + boolean isPresent = false; + for (WriteChannel channelMin : channels) { + if (channelMin.writeMin().isPresent() && channelMin.writeMin().get() > min) { + min = channelMin.writeMin().get(); + isPresent = true; + } + } + if (isPresent) { + return min * 3; + } + return null; + } + + @Override + public Long getMaxValue(WriteChannel... channels) { + long max = Long.MAX_VALUE; + boolean isPresent = false; + for (WriteChannel channelMax : channels) { + if (channelMax.writeMax().isPresent() && channelMax.writeMax().get() < max) { + max = channelMax.writeMax().get(); + isPresent = true; + } + } + if (isPresent) { + return max * 3; + } + return null; + } + + @Override + public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + long min = 0L; + min = newValue / 3; + if (getMinValue(channels) != null && min < getMinValue(channels)) { + throw new WriteChannelException("Value [" + newValue + + "] for [ SetReactivePower ] is out of boundaries. Different min value [" + + getMinValue(channels) + "] had already been set"); + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMin(min); + } catch (WriteChannelException e) { + log.error("Failed to write " + min + " to " + channel.address(), e); + } + } + } + + @Override + public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + long max = 0L; + max = newValue / 3; + if (getMaxValue(channels) != null && max > getMaxValue(channels)) { + throw new WriteChannelException("Value [" + newValue + + "] for [ SetReactivePower ] is out of boundaries. Different max value [" + + getMaxValue(channels) + "] had already been set"); + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMax(max); + } catch (WriteChannelException e) { + log.error("Failed to write " + max + " to " + channel.address(), e); + } + } + } + + }); + + private FunctionalReadChannel maxNominalPower = new FunctionalReadChannel("MaxNominalPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + if (channels.length > 0) { + return channels[0].value(); + } + return null; + } + + }).unit("VA"); + private FunctionalReadChannel capacity = new FunctionalReadChannel("Capacity", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) throws InvalidValueException { + if (channels.length > 0) { + return channels[0].value(); + } + return null; + } + + }).unit("Wh"); + + private FunctionalReadChannel gridMode = new FunctionalReadChannel("GridMode", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + if (channels[0].labelOptional().equals(Optional.of(EssNature.ON_GRID))) { + return 1L; + } + return 0L; + } + + }).label(0L, EssNature.OFF_GRID).label(1L, EssNature.ON_GRID); + private FunctionalReadChannel systemState = new FunctionalReadChannel("SystemState", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + if (channels[0].labelOptional().equals(Optional.of(EssNature.ON))) { + return 1L; + } else if (channels[0].labelOptional().equals(Optional.of(EssNature.OFF))) { + return 0L; + } else if (channels[0].labelOptional().equals(Optional.of(EssNature.FAULT))) { + return 2L; + } else { + return 3L; + } + } + + }).label(0L, EssNature.OFF).label(1L, EssNature.ON).label(2L, EssNature.FAULT).label(3L, "UNDEFINED"); + private StatusBitChannels warning = new StatusBitChannels("Warning", this); + + private FunctionalWriteChannel setWorkState = new FunctionalWriteChannel("SetWorkState", this, + new FunctionalWriteChannelFunction() { + + @Override + public void setValue(Long newValue, String newLabel, WriteChannel... channels) { + for (WriteChannel channel : channels) { + try { + channel.pushWriteFromLabel(newLabel); + } catch (WriteChannelException e) { + log.error("Can't set value for channel " + channel.address(), e); + } + } + } + + @Override + public Long getValue(ReadChannel... channels) { + if (channels.length > 0) { + if (channels[0].labelOptional().equals(Optional.of(EssNature.ON))) { + return 1L; + } + } + return 0L; + } + + @Override + public Long getMinValue(WriteChannel... channels) { + if (channels.length > 0 && channels[0].writeMin().isPresent()) { + return channels[0].writeMin().get(); + } + return null; + } + + @Override + public Long getMaxValue(WriteChannel... channels) { + if (channels.length > 0 && channels[0].writeMax().isPresent()) { + return channels[0].writeMax().get(); + } + return null; + } + + @Override + public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + if (getMinValue(channels) != null && newValue < getMinValue(channels)) { + throw new WriteChannelException( + "Value [" + newValue + "] for [ GridMode ] is out of boundaries. Different min value [" + + getMinValue(channels) + "] had already been set"); + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMax(newValue); + } catch (WriteChannelException e) { + log.error("Failed to write " + newValue + " to " + channel.address(), e); + } + } + } + + @Override + public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + if (getMaxValue(channels) != null && newValue < getMaxValue(channels)) { + throw new WriteChannelException( + "Value [" + newValue + "] for [ GridMode ] is out of boundaries. Different max value [" + + getMaxValue(channels) + "] had already been set"); + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMax(newValue); + } catch (WriteChannelException e) { + log.error("Failed to write " + newValue + " to " + channel.address(), e); + } + } + } + + }).label(0L, EssNature.OFF).label(1L, EssNature.ON); + + public AsymmetricToSymmetricEssNature(String id, Device parent) throws ConfigException { + super(id, parent); + log = LoggerFactory.getLogger(this.getClass()); + this.listeners = new ArrayList<>(); + } + + @Override + public void setAsRequired(Channel channel) { + // unused + } + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + @Override + public ReadChannel capacity() { + return capacity; + } + + @Override + public void addListener(ThingChannelsUpdatedListener listener) { + this.listeners.add(listener); + } + + @Override + public void removeListener(ThingChannelsUpdatedListener listener) { + this.listeners.remove(listener); + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(essId)) { + loadEss(); + } + } + + private void loadEss() { + Set natures = repo.getDeviceNatures(); + String essId; + try { + essId = this.essId.value(); + if (ess != null) { + // remove old ess + activePower.removeChannel(ess.activePowerL1()); + activePower.removeChannel(ess.activePowerL2()); + activePower.removeChannel(ess.activePowerL3()); + reactivePower.removeChannel(ess.reactivePowerL1()); + reactivePower.removeChannel(ess.reactivePowerL2()); + reactivePower.removeChannel(ess.reactivePowerL3()); + setActivePower.removeChannel(ess.setActivePowerL1()); + setActivePower.removeChannel(ess.setActivePowerL2()); + setActivePower.removeChannel(ess.setActivePowerL3()); + setReactivePower.removeChannel(ess.setReactivePowerL1()); + setReactivePower.removeChannel(ess.setReactivePowerL2()); + setReactivePower.removeChannel(ess.setReactivePowerL3()); + allowedCharge.removeChannel(ess.allowedCharge()); + allowedDischarge.removeChannel(ess.allowedDischarge()); + allowedApparent.removeChannel(ess.allowedApparent()); + systemState.removeChannel(ess.systemState()); + setWorkState.removeChannel(ess.setWorkState()); + capacity.removeChannel(ess.capacity()); + maxNominalPower.removeChannel(ess.maxNominalPower()); + gridMode.removeChannel(ess.gridMode()); + soc.removeChannel(ess.soc()); + ess = null; + } + for (DeviceNature nature : natures) { + if (nature instanceof AsymmetricEssNature) { + if (essId.contains(nature.id())) { + AsymmetricEssNature ess = (AsymmetricEssNature) nature; + this.ess = ess; + activePower.addChannel(ess.activePowerL1()); + activePower.addChannel(ess.activePowerL2()); + activePower.addChannel(ess.activePowerL3()); + reactivePower.addChannel(ess.reactivePowerL1()); + reactivePower.addChannel(ess.reactivePowerL2()); + reactivePower.addChannel(ess.reactivePowerL3()); + setActivePower.addChannel(ess.setActivePowerL1()); + setActivePower.addChannel(ess.setActivePowerL2()); + setActivePower.addChannel(ess.setActivePowerL3()); + setReactivePower.addChannel(ess.setReactivePowerL1()); + setReactivePower.addChannel(ess.setReactivePowerL2()); + setReactivePower.addChannel(ess.setReactivePowerL3()); + allowedCharge.addChannel(ess.allowedCharge()); + allowedDischarge.addChannel(ess.allowedDischarge()); + allowedApparent.addChannel(ess.allowedApparent()); + systemState.addChannel(ess.systemState()); + setWorkState.addChannel(ess.setWorkState()); + capacity.addChannel(ess.capacity()); + maxNominalPower.addChannel(ess.maxNominalPower()); + gridMode.addChannel(ess.gridMode()); + soc.addChannel(ess.soc()); + } + } + } + } catch (InvalidValueException e) { + log.error("esss value is invalid!", e); + } + } + + @Override + public void init() { + for (ThingChannelsUpdatedListener listener : this.listeners) { + listener.thingChannelsUpdated(this); + } + } + + @Override + protected void update() { + if (ess == null) { + loadEss(); + } + } + +} diff --git a/edge/src/io/openems/impl/device/system/esscluster/EssCluster.java b/edge/src/io/openems/impl/device/system/esscluster/EssCluster.java index 08db4ec9062..9e3919c835f 100644 --- a/edge/src/io/openems/impl/device/system/esscluster/EssCluster.java +++ b/edge/src/io/openems/impl/device/system/esscluster/EssCluster.java @@ -1,31 +1,32 @@ -package io.openems.impl.device.system.esscluster; - -import java.util.HashSet; -import java.util.Set; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.system.SystemDevice; - -public class EssCluster extends SystemDevice { - - @ConfigInfo(title = "EssCluster", description = "Sets the cluster nature.", type = EssClusterNature.class) - public final ConfigChannel cluster = new ConfigChannel<>("cluster", this); - - public EssCluster() throws OpenemsException { - super(); - // TODO Auto-generated constructor stub - } - - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (cluster.valueOptional().isPresent()) { - natures.add(cluster.valueOptional().get()); - } - return natures; - } - -} +package io.openems.impl.device.system.esscluster; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.system.SystemDevice; + +public class EssCluster extends SystemDevice { + + @ConfigInfo(title = "EssCluster", description = "Sets the cluster nature.", type = EssClusterNature.class) + public final ConfigChannel cluster = new ConfigChannel<>("cluster", this); + + public EssCluster(Bridge parent) throws OpenemsException { + super(parent); + // TODO Auto-generated constructor stub + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (cluster.valueOptional().isPresent()) { + natures.add(cluster.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/system/esscluster/EssClusterNature.java b/edge/src/io/openems/impl/device/system/esscluster/EssClusterNature.java index f1e50ac2956..e52eb89162c 100644 --- a/edge/src/io/openems/impl/device/system/esscluster/EssClusterNature.java +++ b/edge/src/io/openems/impl/device/system/esscluster/EssClusterNature.java @@ -1,709 +1,710 @@ -package io.openems.impl.device.system.esscluster; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.gson.JsonArray; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.FunctionalReadChannel; -import io.openems.api.channel.FunctionalReadChannelFunction; -import io.openems.api.channel.FunctionalWriteChannel; -import io.openems.api.channel.FunctionalWriteChannelFunction; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.WriteChannelException; -import io.openems.api.thing.ThingChannelsUpdatedListener; -import io.openems.core.ThingRepository; -import io.openems.impl.protocol.system.SystemDeviceNature; - -public class EssClusterNature extends SystemDeviceNature implements SymmetricEssNature, ChannelChangeListener { - - private final Logger log; - private List listeners; - - private static ThingRepository repo = ThingRepository.getInstance(); - - @ConfigInfo(title = "Ess", description = "Sets the Ess devices for the cluster.", type = JsonArray.class) - public ConfigChannel esss = new ConfigChannel("esss", this).addChangeListener(this); - private ConfigChannel minSoc = new ConfigChannel<>("minSoc", this); - private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); - private List essList = new ArrayList<>(); - private FunctionalReadChannel soc = new FunctionalReadChannel("Soc", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - double nominalKWhSum = 0; - double actualCapacity = 0; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - long capacity; - try { - capacity = ess.capacity().value(); - nominalKWhSum += capacity; - actualCapacity += (capacity / 100.0) * ess.soc().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return (long) (actualCapacity / nominalKWhSum * 100.0); - } - - }).unit("%"); - private FunctionalReadChannel allowedCharge = new FunctionalReadChannel("AllowedCharge", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.allowedCharge().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("W"); - private FunctionalReadChannel allowedDischarge = new FunctionalReadChannel("AllowedDischarge", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.allowedDischarge().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("W"); - private FunctionalReadChannel allowedApparent = new FunctionalReadChannel("AllowedApparent", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.allowedApparent().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("VA"); - private FunctionalReadChannel activePower = new FunctionalReadChannel("ActivePower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.activePower().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("W"); - private FunctionalReadChannel reactivePower = new FunctionalReadChannel("ReactivePower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.reactivePower().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("Var"); - private FunctionalReadChannel apparentPower = new FunctionalReadChannel("ApparentPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.apparentPower().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("VA"); - private FunctionalReadChannel maxNominalPower = new FunctionalReadChannel("MaxNominalPower", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.maxNominalPower().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("VA"); - private FunctionalReadChannel capacity = new FunctionalReadChannel("Capacity", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - long sum = 0L; - for (SymmetricEssNature ess : EssClusterNature.this.essList) { - try { - sum += ess.capacity().value(); - } catch (InvalidValueException e) { - log.debug("Can't read values of " + ess.id(), e); - } - } - return sum; - } - - }).unit("Wh"); - - private FunctionalReadChannel gridMode = new FunctionalReadChannel("GridMode", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - for (SymmetricEssNature ess : essList) { - if (ess.gridMode().labelOptional().equals(Optional.of(EssNature.ON_GRID))) { - return 1L; - } - } - return 0L; - } - - }).label(0L, EssNature.OFF_GRID).label(1L, EssNature.ON_GRID); - private FunctionalReadChannel systemState = new FunctionalReadChannel("SystemState", this, - new FunctionalReadChannelFunction() { - - @Override - public Long handle(ReadChannel... channels) { - for (SymmetricEssNature ess : essList) { - if (!ess.systemState().labelOptional().equals(Optional.of(EssNature.ON))) { - if (ess.systemState().labelOptional().equals(Optional.of(EssNature.OFF))) { - return 0L; - } else if (ess.systemState().labelOptional().equals(Optional.of(EssNature.FAULT))) { - return 2L; - } else { - return 3L; - } - - } - } - return 1L; - } - - }).label(0L, EssNature.STOP).label(1L, EssNature.START).label(2L, EssNature.FAULT).label(3L, "UNDEFINED"); - private StatusBitChannels warning = new StatusBitChannels("Warning", this); - - private FunctionalWriteChannel setWorkState = new FunctionalWriteChannel("SetWorkState", this, - new FunctionalWriteChannelFunction() { - - @Override - public void setValue(Long newValue, String newLabel, WriteChannel... channels) { - for (WriteChannel channel : channels) { - try { - channel.pushWriteFromLabel(newLabel); - } catch (WriteChannelException e) { - log.error("Can't set value for channel " + channel.address(), e); - } - } - } - - @Override - public Long getValue(ReadChannel... channels) { - for (ReadChannel state : channels) { - if (state.labelOptional().equals(Optional.of(EssNature.START))) { - return 1L; - } - } - return 0L; - } - - @Override - public Long getMinValue(WriteChannel... channels) { - long min = Long.MIN_VALUE; - for (WriteChannel channelMin : channels) { - if (channelMin.writeMin().isPresent() && channelMin.writeMin().get() > min) { - min = channelMin.writeMin().get(); - } - } - if (min == Long.MIN_VALUE) { - return null; - } else { - return min; - } - } - - @Override - public Long getMaxValue(WriteChannel... channels) { - long max = Long.MAX_VALUE; - for (WriteChannel channelMax : channels) { - if (channelMax.writeMax().isPresent() && channelMax.writeMax().get() < max) { - max = channelMax.writeMax().get(); - } - } - if (max == Long.MAX_VALUE) { - return null; - } else { - return max; - } - } - - @Override - public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) { - for (WriteChannel channel : channels) { - try { - channel.pushWriteMin(newValue); - } catch (WriteChannelException e) { - log.error("Can't set value for channel " + channel.address(), e); - } - } - } - - @Override - public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) { - for (WriteChannel channel : channels) { - try { - channel.pushWriteMax(newValue); - } catch (WriteChannelException e) { - log.error("Can't set value for channel " + channel.address(), e); - } - } - } - - }).label(0L, EssNature.STOP).label(1L, EssNature.START); - - private FunctionalWriteChannel setActivePower = new FunctionalWriteChannel("SetActivePower", this, - new FunctionalWriteChannelFunction() { - - @Override - public void setValue(Long newValue, String newLabel, WriteChannel... channels) - throws WriteChannelException { - long minValue = 0L; - boolean minValueValid = false; - long maxValue = 0L; - boolean maxValueValid = false; - Map soc = new HashMap<>(); - for (SymmetricEssNature ess : essList) { - if (ess.soc().valueOptional().isPresent()) { - soc.put(ess.id(), ess.soc().valueOptional().get()); - } else { - soc.put(ess.id(), 0L); - } - if (ess.setActivePower().writeMin().isPresent()) { - minValue += ess.setActivePower().writeMin().get(); - minValueValid = true; - } - if (ess.setActivePower().writeMax().isPresent()) { - maxValue += ess.setActivePower().writeMax().get(); - maxValueValid = true; - } - } - if (maxValueValid && maxValue < newValue) { - throw new WriteChannelException("Value [" + newValue + "] for [" + setActivePower.address() - + "] is out of boundaries. Max value [" + maxValue + "] had already been set"); - } - if (minValueValid && minValue > newValue) { - throw new WriteChannelException("Value [" + newValue + "] for [" + setActivePower.address() - + "] is out of boundaries. Min value [" + minValue + "] had already been set"); - } - for (WriteChannel channel : channels) { - long power = 0L; - if (channels.length > 0) { - if (newValue >= 0) { - power = newValue / (channels.length * 100) * soc.get(channel.parent().id()); - } else { - power = newValue / (channels.length * 100) * (100 - soc.get(channel.parent().id())); - } - } - try { - channel.pushWrite(power); - } catch (WriteChannelException e) { - log.error("Failed to write " + power + " to " + channel.address(), e); - } - } - } - - @Override - public Long getValue(ReadChannel... channels) { - long sum = 0L; - for (ReadChannel channel : channels) { - try { - sum += channel.value(); - } catch (InvalidValueException e) { - log.error("Can't read ActivePower from " + channel.address()); - } - } - return sum; - } - - @Override - public Long getMinValue(WriteChannel... channels) { - long min = 0L; - boolean isPresent = false; - for (WriteChannel channelMin : channels) { - if (channelMin.writeMin().isPresent()) { - min += channelMin.writeMin().get(); - isPresent = true; - } - } - if (isPresent) { - return min; - } - return null; - } - - @Override - public Long getMaxValue(WriteChannel... channels) { - long max = 0L; - boolean isPresent = false; - for (WriteChannel channelMax : channels) { - if (channelMax.writeMax().isPresent()) { - max += channelMax.writeMax().get(); - isPresent = true; - } - } - if (isPresent) { - return max; - } - return null; - } - - @Override - public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) { - // don't forward the maxValue otherwise the pushWrite with power weight by soc will break - } - - @Override - public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) { - // don't forward the maxValue otherwise the pushWrite with power weight by soc will break - } - - }); - private FunctionalWriteChannel setReactivePower = new FunctionalWriteChannel("SetReactivePower", this, - new FunctionalWriteChannelFunction() { - - @Override - public void setValue(Long newValue, String newLabel, WriteChannel... channels) { - long power = 0L; - if (channels.length > 0) { - power = newValue / channels.length; - } - for (WriteChannel channel : channels) { - try { - channel.pushWrite(power); - } catch (WriteChannelException e) { - log.error("Failed to write " + power + " to " + channel.address(), e); - } - } - } - - @Override - public Long getValue(ReadChannel... channels) { - long sum = 0L; - for (ReadChannel channel : channels) { - try { - sum += channel.value(); - } catch (InvalidValueException e) { - log.error("Can't read ReactivePower from " + channel.address()); - } - } - return sum; - } - - @Override - public Long getMinValue(WriteChannel... channels) { - long min = 0L; - boolean isPresent = false; - for (WriteChannel channelMin : channels) { - if (channelMin.writeMin().isPresent()) { - min += channelMin.writeMin().get(); - isPresent = true; - } - } - if (isPresent) { - return min; - } - return null; - } - - @Override - public Long getMaxValue(WriteChannel... channels) { - long max = 0L; - boolean isPresent = false; - for (WriteChannel channelMax : channels) { - if (channelMax.writeMax().isPresent()) { - max += channelMax.writeMax().get(); - isPresent = true; - } - } - if (isPresent) { - return max; - } - return null; - - } - - @Override - public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) { - long power = 0L; - if (channels.length > 0) { - power = newValue / channels.length; - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMin(power); - } catch (WriteChannelException e) { - log.error("Failed to write " + power + " to " + channel.address(), e); - } - } - } - - @Override - public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) { - long power = 0L; - if (channels.length > 0) { - power = newValue / channels.length; - } - for (WriteChannel channel : channels) { - try { - channel.pushWriteMax(power); - } catch (WriteChannelException e) { - log.error("Failed to write " + power + " to " + channel.address(), e); - } - } - } - - }); - - public EssClusterNature(String id) throws ConfigException { - super(id); - log = LoggerFactory.getLogger(this.getClass()); - this.listeners = new ArrayList<>(); - } - - @Override - public void setAsRequired(Channel channel) { - // unused - } - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return warning; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - @Override - public ReadChannel capacity() { - return capacity; - } - - @Override - public void addListener(ThingChannelsUpdatedListener listener) { - this.listeners.add(listener); - } - - @Override - public void removeListener(ThingChannelsUpdatedListener listener) { - this.listeners.remove(listener); - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel.equals(esss)) { - loadEss(); - } - } - - private void loadEss() { - Set natures = repo.getDeviceNatures(); - JsonArray essIds; - try { - essIds = esss.value(); - // remove old ess - for (SymmetricEssNature ess : this.essList) { - soc.removeChannel(ess.soc()); - gridMode.removeChannel(ess.gridMode()); - systemState.removeChannel(ess.systemState()); - allowedCharge.removeChannel(ess.allowedCharge()); - allowedDischarge.removeChannel(ess.allowedDischarge()); - allowedApparent.removeChannel(ess.allowedApparent()); - activePower.removeChannel(ess.activePower()); - reactivePower.removeChannel(ess.reactivePower()); - apparentPower.removeChannel(ess.apparentPower()); - maxNominalPower.removeChannel(ess.maxNominalPower()); - capacity.removeChannel(ess.capacity()); - setWorkState.removeChannel(ess.setWorkState()); - setActivePower.removeChannel(ess.setActivePower()); - setReactivePower.removeChannel(ess.setReactivePower()); - } - essList.clear(); - if (essIds != null) { - for (DeviceNature nature : natures) { - if (nature instanceof SymmetricEssNature) { - if (essIds.toString().contains(nature.id())) { - SymmetricEssNature ess = (SymmetricEssNature) nature; - essList.add(ess); - soc.addChannel(ess.soc()); - gridMode.addChannel(ess.gridMode()); - systemState.addChannel(ess.systemState()); - allowedCharge.addChannel(ess.allowedCharge()); - allowedDischarge.addChannel(ess.allowedDischarge()); - allowedApparent.addChannel(ess.allowedApparent()); - activePower.addChannel(ess.activePower()); - reactivePower.addChannel(ess.reactivePower()); - apparentPower.addChannel(ess.apparentPower()); - maxNominalPower.addChannel(ess.maxNominalPower()); - capacity.addChannel(ess.capacity()); - setWorkState.addChannel(ess.setWorkState()); - setActivePower.addChannel(ess.setActivePower()); - setReactivePower.addChannel(ess.setReactivePower()); - } - } - } - // capacity.channelUpdated(null, null); - } - } catch (InvalidValueException e) { - log.error("esss value is invalid!", e); - } - } - - @Override - protected void update() { - try { - if (esss.value().size() != essList.size()) { - loadEss(); - } - } catch (InvalidValueException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - @Override - public void init() { - for (ThingChannelsUpdatedListener listener : this.listeners) { - listener.thingChannelsUpdated(this); - } - } - -} +package io.openems.impl.device.system.esscluster; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.FunctionalReadChannel; +import io.openems.api.channel.FunctionalReadChannelFunction; +import io.openems.api.channel.FunctionalWriteChannel; +import io.openems.api.channel.FunctionalWriteChannelFunction; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.WriteChannelException; +import io.openems.api.thing.ThingChannelsUpdatedListener; +import io.openems.core.ThingRepository; +import io.openems.impl.protocol.system.SystemDeviceNature; + +public class EssClusterNature extends SystemDeviceNature implements SymmetricEssNature, ChannelChangeListener { + + private final Logger log; + private List listeners; + + private static ThingRepository repo = ThingRepository.getInstance(); + + @ConfigInfo(title = "Ess", description = "Sets the Ess devices for the cluster.", type = JsonArray.class) + public ConfigChannel esss = new ConfigChannel("esss", this).addChangeListener(this); + private ConfigChannel minSoc = new ConfigChannel<>("minSoc", this); + private ConfigChannel chargeSoc = new ConfigChannel("chargeSoc", this); + private List essList = new ArrayList<>(); + private FunctionalReadChannel soc = new FunctionalReadChannel("Soc", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + double nominalKWhSum = 0; + double actualCapacity = 0; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + long capacity; + try { + capacity = ess.capacity().value(); + nominalKWhSum += capacity; + actualCapacity += (capacity / 100.0) * ess.soc().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return (long) (actualCapacity / nominalKWhSum * 100.0); + } + + }).unit("%"); + private FunctionalReadChannel allowedCharge = new FunctionalReadChannel("AllowedCharge", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.allowedCharge().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("W"); + private FunctionalReadChannel allowedDischarge = new FunctionalReadChannel("AllowedDischarge", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.allowedDischarge().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("W"); + private FunctionalReadChannel allowedApparent = new FunctionalReadChannel("AllowedApparent", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.allowedApparent().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("VA"); + private FunctionalReadChannel activePower = new FunctionalReadChannel("ActivePower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.activePower().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("W"); + private FunctionalReadChannel reactivePower = new FunctionalReadChannel("ReactivePower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.reactivePower().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("Var"); + private FunctionalReadChannel apparentPower = new FunctionalReadChannel("ApparentPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.apparentPower().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("VA"); + private FunctionalReadChannel maxNominalPower = new FunctionalReadChannel("MaxNominalPower", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.maxNominalPower().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("VA"); + private FunctionalReadChannel capacity = new FunctionalReadChannel("Capacity", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + long sum = 0L; + for (SymmetricEssNature ess : EssClusterNature.this.essList) { + try { + sum += ess.capacity().value(); + } catch (InvalidValueException e) { + log.debug("Can't read values of " + ess.id(), e); + } + } + return sum; + } + + }).unit("Wh"); + + private FunctionalReadChannel gridMode = new FunctionalReadChannel("GridMode", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + for (SymmetricEssNature ess : essList) { + if (ess.gridMode().labelOptional().equals(Optional.of(EssNature.ON_GRID))) { + return 1L; + } + } + return 0L; + } + + }).label(0L, EssNature.OFF_GRID).label(1L, EssNature.ON_GRID); + private FunctionalReadChannel systemState = new FunctionalReadChannel("SystemState", this, + new FunctionalReadChannelFunction() { + + @Override + public Long handle(ReadChannel... channels) { + for (SymmetricEssNature ess : essList) { + if (!ess.systemState().labelOptional().equals(Optional.of(EssNature.ON))) { + if (ess.systemState().labelOptional().equals(Optional.of(EssNature.OFF))) { + return 0L; + } else if (ess.systemState().labelOptional().equals(Optional.of(EssNature.FAULT))) { + return 2L; + } else { + return 3L; + } + + } + } + return 1L; + } + + }).label(0L, EssNature.STOP).label(1L, EssNature.START).label(2L, EssNature.FAULT).label(3L, "UNDEFINED"); + private StatusBitChannels warning = new StatusBitChannels("Warning", this); + + private FunctionalWriteChannel setWorkState = new FunctionalWriteChannel("SetWorkState", this, + new FunctionalWriteChannelFunction() { + + @Override + public void setValue(Long newValue, String newLabel, WriteChannel... channels) { + for (WriteChannel channel : channels) { + try { + channel.pushWriteFromLabel(newLabel); + } catch (WriteChannelException e) { + log.error("Can't set value for channel " + channel.address(), e); + } + } + } + + @Override + public Long getValue(ReadChannel... channels) { + for (ReadChannel state : channels) { + if (state.labelOptional().equals(Optional.of(EssNature.START))) { + return 1L; + } + } + return 0L; + } + + @Override + public Long getMinValue(WriteChannel... channels) { + long min = Long.MIN_VALUE; + for (WriteChannel channelMin : channels) { + if (channelMin.writeMin().isPresent() && channelMin.writeMin().get() > min) { + min = channelMin.writeMin().get(); + } + } + if (min == Long.MIN_VALUE) { + return null; + } else { + return min; + } + } + + @Override + public Long getMaxValue(WriteChannel... channels) { + long max = Long.MAX_VALUE; + for (WriteChannel channelMax : channels) { + if (channelMax.writeMax().isPresent() && channelMax.writeMax().get() < max) { + max = channelMax.writeMax().get(); + } + } + if (max == Long.MAX_VALUE) { + return null; + } else { + return max; + } + } + + @Override + public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) { + for (WriteChannel channel : channels) { + try { + channel.pushWriteMin(newValue); + } catch (WriteChannelException e) { + log.error("Can't set value for channel " + channel.address(), e); + } + } + } + + @Override + public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) { + for (WriteChannel channel : channels) { + try { + channel.pushWriteMax(newValue); + } catch (WriteChannelException e) { + log.error("Can't set value for channel " + channel.address(), e); + } + } + } + + }).label(0L, EssNature.STOP).label(1L, EssNature.START); + + private FunctionalWriteChannel setActivePower = new FunctionalWriteChannel("SetActivePower", this, + new FunctionalWriteChannelFunction() { + + @Override + public void setValue(Long newValue, String newLabel, WriteChannel... channels) + throws WriteChannelException { + long minValue = 0L; + boolean minValueValid = false; + long maxValue = 0L; + boolean maxValueValid = false; + Map soc = new HashMap<>(); + for (SymmetricEssNature ess : essList) { + if (ess.soc().valueOptional().isPresent()) { + soc.put(ess.id(), ess.soc().valueOptional().get()); + } else { + soc.put(ess.id(), 0L); + } + if (ess.setActivePower().writeMin().isPresent()) { + minValue += ess.setActivePower().writeMin().get(); + minValueValid = true; + } + if (ess.setActivePower().writeMax().isPresent()) { + maxValue += ess.setActivePower().writeMax().get(); + maxValueValid = true; + } + } + if (maxValueValid && maxValue < newValue) { + throw new WriteChannelException("Value [" + newValue + "] for [" + setActivePower.address() + + "] is out of boundaries. Max value [" + maxValue + "] had already been set"); + } + if (minValueValid && minValue > newValue) { + throw new WriteChannelException("Value [" + newValue + "] for [" + setActivePower.address() + + "] is out of boundaries. Min value [" + minValue + "] had already been set"); + } + for (WriteChannel channel : channels) { + long power = 0L; + if (channels.length > 0) { + if (newValue >= 0) { + power = newValue / (channels.length * 100) * soc.get(channel.parent().id()); + } else { + power = newValue / (channels.length * 100) * (100 - soc.get(channel.parent().id())); + } + } + try { + channel.pushWrite(power); + } catch (WriteChannelException e) { + log.error("Failed to write " + power + " to " + channel.address(), e); + } + } + } + + @Override + public Long getValue(ReadChannel... channels) { + long sum = 0L; + for (ReadChannel channel : channels) { + try { + sum += channel.value(); + } catch (InvalidValueException e) { + log.error("Can't read ActivePower from " + channel.address()); + } + } + return sum; + } + + @Override + public Long getMinValue(WriteChannel... channels) { + long min = 0L; + boolean isPresent = false; + for (WriteChannel channelMin : channels) { + if (channelMin.writeMin().isPresent()) { + min += channelMin.writeMin().get(); + isPresent = true; + } + } + if (isPresent) { + return min; + } + return null; + } + + @Override + public Long getMaxValue(WriteChannel... channels) { + long max = 0L; + boolean isPresent = false; + for (WriteChannel channelMax : channels) { + if (channelMax.writeMax().isPresent()) { + max += channelMax.writeMax().get(); + isPresent = true; + } + } + if (isPresent) { + return max; + } + return null; + } + + @Override + public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) { + // don't forward the maxValue otherwise the pushWrite with power weight by soc will break + } + + @Override + public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) { + // don't forward the maxValue otherwise the pushWrite with power weight by soc will break + } + + }); + private FunctionalWriteChannel setReactivePower = new FunctionalWriteChannel("SetReactivePower", this, + new FunctionalWriteChannelFunction() { + + @Override + public void setValue(Long newValue, String newLabel, WriteChannel... channels) { + long power = 0L; + if (channels.length > 0) { + power = newValue / channels.length; + } + for (WriteChannel channel : channels) { + try { + channel.pushWrite(power); + } catch (WriteChannelException e) { + log.error("Failed to write " + power + " to " + channel.address(), e); + } + } + } + + @Override + public Long getValue(ReadChannel... channels) { + long sum = 0L; + for (ReadChannel channel : channels) { + try { + sum += channel.value(); + } catch (InvalidValueException e) { + log.error("Can't read ReactivePower from " + channel.address()); + } + } + return sum; + } + + @Override + public Long getMinValue(WriteChannel... channels) { + long min = 0L; + boolean isPresent = false; + for (WriteChannel channelMin : channels) { + if (channelMin.writeMin().isPresent()) { + min += channelMin.writeMin().get(); + isPresent = true; + } + } + if (isPresent) { + return min; + } + return null; + } + + @Override + public Long getMaxValue(WriteChannel... channels) { + long max = 0L; + boolean isPresent = false; + for (WriteChannel channelMax : channels) { + if (channelMax.writeMax().isPresent()) { + max += channelMax.writeMax().get(); + isPresent = true; + } + } + if (isPresent) { + return max; + } + return null; + + } + + @Override + public void setMinValue(Long newValue, String newLabel, WriteChannel... channels) { + long power = 0L; + if (channels.length > 0) { + power = newValue / channels.length; + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMin(power); + } catch (WriteChannelException e) { + log.error("Failed to write " + power + " to " + channel.address(), e); + } + } + } + + @Override + public void setMaxValue(Long newValue, String newLabel, WriteChannel... channels) { + long power = 0L; + if (channels.length > 0) { + power = newValue / channels.length; + } + for (WriteChannel channel : channels) { + try { + channel.pushWriteMax(power); + } catch (WriteChannelException e) { + log.error("Failed to write " + power + " to " + channel.address(), e); + } + } + } + + }); + + public EssClusterNature(String id, Device parent) throws ConfigException { + super(id, parent); + log = LoggerFactory.getLogger(this.getClass()); + this.listeners = new ArrayList<>(); + } + + @Override + public void setAsRequired(Channel channel) { + // unused + } + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return warning; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + @Override + public ReadChannel capacity() { + return capacity; + } + + @Override + public void addListener(ThingChannelsUpdatedListener listener) { + this.listeners.add(listener); + } + + @Override + public void removeListener(ThingChannelsUpdatedListener listener) { + this.listeners.remove(listener); + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(esss)) { + loadEss(); + } + } + + private void loadEss() { + Set natures = repo.getDeviceNatures(); + JsonArray essIds; + try { + essIds = esss.value(); + // remove old ess + for (SymmetricEssNature ess : this.essList) { + soc.removeChannel(ess.soc()); + gridMode.removeChannel(ess.gridMode()); + systemState.removeChannel(ess.systemState()); + allowedCharge.removeChannel(ess.allowedCharge()); + allowedDischarge.removeChannel(ess.allowedDischarge()); + allowedApparent.removeChannel(ess.allowedApparent()); + activePower.removeChannel(ess.activePower()); + reactivePower.removeChannel(ess.reactivePower()); + apparentPower.removeChannel(ess.apparentPower()); + maxNominalPower.removeChannel(ess.maxNominalPower()); + capacity.removeChannel(ess.capacity()); + setWorkState.removeChannel(ess.setWorkState()); + setActivePower.removeChannel(ess.setActivePower()); + setReactivePower.removeChannel(ess.setReactivePower()); + } + essList.clear(); + if (essIds != null) { + for (DeviceNature nature : natures) { + if (nature instanceof SymmetricEssNature) { + if (essIds.toString().contains(nature.id())) { + SymmetricEssNature ess = (SymmetricEssNature) nature; + essList.add(ess); + soc.addChannel(ess.soc()); + gridMode.addChannel(ess.gridMode()); + systemState.addChannel(ess.systemState()); + allowedCharge.addChannel(ess.allowedCharge()); + allowedDischarge.addChannel(ess.allowedDischarge()); + allowedApparent.addChannel(ess.allowedApparent()); + activePower.addChannel(ess.activePower()); + reactivePower.addChannel(ess.reactivePower()); + apparentPower.addChannel(ess.apparentPower()); + maxNominalPower.addChannel(ess.maxNominalPower()); + capacity.addChannel(ess.capacity()); + setWorkState.addChannel(ess.setWorkState()); + setActivePower.addChannel(ess.setActivePower()); + setReactivePower.addChannel(ess.setReactivePower()); + } + } + } + // capacity.channelUpdated(null, null); + } + } catch (InvalidValueException e) { + log.error("esss value is invalid!", e); + } + } + + @Override + protected void update() { + try { + if (esss.value().size() != essList.size()) { + loadEss(); + } + } catch (InvalidValueException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + @Override + public void init() { + for (ThingChannelsUpdatedListener listener : this.listeners) { + listener.thingChannelsUpdated(this); + } + } + +} diff --git a/edge/src/io/openems/impl/device/system/metercluster/MeterCluster.java b/edge/src/io/openems/impl/device/system/metercluster/MeterCluster.java new file mode 100644 index 00000000000..9c0cc86a570 --- /dev/null +++ b/edge/src/io/openems/impl/device/system/metercluster/MeterCluster.java @@ -0,0 +1,32 @@ +package io.openems.impl.device.system.metercluster; + +import java.util.HashSet; +import java.util.Set; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.system.SystemDevice; + +public class MeterCluster extends SystemDevice { + + @ConfigInfo(title = "EssCluster", description = "Sets the cluster nature.", type = MeterClusterNature.class) + public final ConfigChannel cluster = new ConfigChannel<>("cluster", this); + + public MeterCluster(Bridge parent) throws OpenemsException { + super(parent); + // TODO Auto-generated constructor stub + } + + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (cluster.valueOptional().isPresent()) { + natures.add(cluster.valueOptional().get()); + } + return natures; + } + +} diff --git a/edge/src/io/openems/impl/device/system/metercluster/MeterClusterNature.java b/edge/src/io/openems/impl/device/system/metercluster/MeterClusterNature.java new file mode 100644 index 00000000000..3e56ff1141d --- /dev/null +++ b/edge/src/io/openems/impl/device/system/metercluster/MeterClusterNature.java @@ -0,0 +1,429 @@ +package io.openems.impl.device.system.metercluster; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.thing.Thing; +import io.openems.api.thing.ThingChannelsUpdatedListener; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.ControllerUtils; +import io.openems.impl.protocol.simulator.SimulatorDeviceNature; +import io.openems.impl.protocol.simulator.SimulatorReadChannel; + +public class MeterClusterNature extends SimulatorDeviceNature + implements SymmetricMeterNature, AsymmetricMeterNature, ChannelChangeListener { + + private final Logger log; + private List listeners; + private ThingRepository repo; + private List symmetricMeterList = new ArrayList<>(); + private List asymmetricMeterList = new ArrayList<>(); + + /* + * Channels + */ + + private SimulatorReadChannel activePower = new SimulatorReadChannel<>("ActivePower", this); + private SimulatorReadChannel apparentPower = new SimulatorReadChannel<>("ApparentPower", this); + private SimulatorReadChannel activePowerL1 = new SimulatorReadChannel<>("ActivePowerL1", this); + private SimulatorReadChannel activePowerL2 = new SimulatorReadChannel<>("ActivePowerL2", this); + private SimulatorReadChannel activePowerL3 = new SimulatorReadChannel<>("ActivePowerL3", this); + private SimulatorReadChannel reactivePower = new SimulatorReadChannel<>("ReactivePower", this); + private SimulatorReadChannel reactivePowerL1 = new SimulatorReadChannel<>("ReactivePowerL1", this); + private SimulatorReadChannel reactivePowerL2 = new SimulatorReadChannel<>("ReactivePowerL2", this); + private SimulatorReadChannel reactivePowerL3 = new SimulatorReadChannel<>("ReactivePowerL3", this); + private SimulatorReadChannel currentL1 = new SimulatorReadChannel<>("CurrentL1", this); + private SimulatorReadChannel currentL2 = new SimulatorReadChannel<>("CurrentL2", this); + private SimulatorReadChannel currentL3 = new SimulatorReadChannel<>("CurrentL3", this); + private SimulatorReadChannel voltage = new SimulatorReadChannel<>("Voltage", this); + private SimulatorReadChannel voltageL1 = new SimulatorReadChannel<>("VoltageL1", this); + private SimulatorReadChannel voltageL2 = new SimulatorReadChannel<>("VoltageL2", this); + private SimulatorReadChannel voltageL3 = new SimulatorReadChannel<>("VoltageL3", this); + private SimulatorReadChannel frequency = new SimulatorReadChannel<>("Frequency", this); + + public MeterClusterNature(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + log = LoggerFactory.getLogger(this.getClass()); + this.listeners = new ArrayList<>(); + this.repo = ThingRepository.getInstance(); + } + + /* + * Config + */ + @ConfigInfo(title = "Meter", description = "Sets the Meter devices for the cluster.", type = JsonArray.class) + public ConfigChannel meter = new ConfigChannel("meter", this).addChangeListener(this); + + private final ConfigChannel type = new ConfigChannel("type", this); + + @Override + public ConfigChannel type() { + return type; + } + + private final ConfigChannel maxActivePower = new ConfigChannel("maxActivePower", this); + + @Override + public ConfigChannel maxActivePower() { + return maxActivePower; + } + + private final ConfigChannel minActivePower = new ConfigChannel("minActivePower", this); + + @Override + public ConfigChannel minActivePower() { + return minActivePower; + } + + @Override + protected void update() { + Long activePower = null; + Long activePowerL1 = null; + Long activePowerL2 = null; + Long activePowerL3 = null; + Long reactivePower = null; + Long reactivePowerL1 = null; + Long reactivePowerL2 = null; + Long reactivePowerL3 = null; + Long voltage = null; + Long voltageL1 = null; + Long voltageL2 = null; + Long voltageL3 = null; + synchronized (asymmetricMeterList) { + for (AsymmetricMeterNature meter : asymmetricMeterList) { + if (meter.activePowerL1().valueOptional().isPresent()) { + if (activePower == null) { + activePower = 0L; + } + if (activePowerL1 == null) { + activePowerL1 = 0L; + } + activePower += meter.activePowerL1().valueOptional().get(); + activePowerL1 += meter.activePowerL1().valueOptional().get(); + } else { + log.warn(meter.id() + ": activePowerL1 is invalid!"); + } + if (meter.activePowerL2().valueOptional().isPresent()) { + if (activePower == null) { + activePower = 0L; + } + if (activePowerL2 == null) { + activePowerL2 = 0L; + } + activePower += meter.activePowerL2().valueOptional().get(); + activePowerL2 += meter.activePowerL2().valueOptional().get(); + } else { + log.warn(meter.id() + ": activePowerL2 is invalid!"); + } + if (meter.activePowerL3().valueOptional().isPresent()) { + if (activePower == null) { + activePower = 0L; + } + if (activePowerL3 == null) { + activePowerL3 = 0L; + } + activePower += meter.activePowerL3().valueOptional().get(); + activePowerL3 += meter.activePowerL3().valueOptional().get(); + } else { + log.warn(meter.id() + ": activePowerL3 is invalid!"); + } + if (meter.reactivePowerL1().valueOptional().isPresent()) { + if (reactivePower == null) { + reactivePower = 0L; + } + if (reactivePowerL1 == null) { + reactivePowerL1 = 0L; + } + reactivePower += meter.reactivePowerL1().valueOptional().get(); + reactivePowerL1 += meter.reactivePowerL1().valueOptional().get(); + } else { + log.warn(meter.id() + ": reactivePowerL1 is invalid!"); + } + if (meter.reactivePowerL2().valueOptional().isPresent()) { + if (reactivePower == null) { + reactivePower = 0L; + } + if (reactivePowerL2 == null) { + reactivePowerL2 = 0L; + } + reactivePower += meter.reactivePowerL2().valueOptional().get(); + reactivePowerL2 += meter.reactivePowerL2().valueOptional().get(); + } else { + log.warn(meter.id() + ": reactivePowerL2 is invalid!"); + } + if (meter.reactivePowerL3().valueOptional().isPresent()) { + if (reactivePower == null) { + reactivePower = 0L; + } + if (reactivePowerL3 == null) { + reactivePowerL3 = 0L; + } + reactivePower += meter.reactivePowerL3().valueOptional().get(); + reactivePowerL3 += meter.reactivePowerL3().valueOptional().get(); + } else { + log.warn(meter.id() + ": reactivePowerL3 is invalid!"); + } + try { + if (voltage == null) { + voltage = meter.voltageL1().value(); + } + if (voltageL1 == null) { + voltageL1 = meter.voltageL1().value(); + } + } catch (InvalidValueException e) { + log.warn(meter.id() + ": voltageL1 is invalid!"); + } + try { + if (voltage == null) { + voltage = meter.voltageL2().value(); + } + if (voltageL2 == null) { + voltageL2 = meter.voltageL2().value(); + } + } catch (InvalidValueException e) { + log.warn(meter.id() + ": voltageL2 is invalid!"); + } + try { + if (voltage == null) { + voltage = meter.voltageL3().value(); + } + if (voltageL3 == null) { + voltageL3 = meter.voltageL3().value(); + } + } catch (InvalidValueException e) { + log.warn(meter.id() + ": voltageL3 is invalid!"); + } + } + } + synchronized (symmetricMeterList) { + for (SymmetricMeterNature meter : symmetricMeterList) { + if (meter.activePower().valueOptional().isPresent()) { + if (activePower == null) { + activePower = 0L; + } + if (activePowerL1 == null) { + activePowerL1 = 0L; + } + if (activePowerL2 == null) { + activePowerL2 = 0L; + } + if (activePowerL3 == null) { + activePowerL3 = 0L; + } + activePower += meter.activePower().valueOptional().get(); + activePowerL1 += meter.activePower().valueOptional().get() / 3; + activePowerL2 += meter.activePower().valueOptional().get() / 3; + activePowerL3 += meter.activePower().valueOptional().get() / 3; + } else { + log.warn(meter.id() + ": activePower is invalid!"); + } + if (meter.activePower().valueOptional().isPresent()) { + if (reactivePower == null) { + reactivePower = 0L; + } + if (reactivePowerL1 == null) { + reactivePowerL1 = 0L; + } + if (reactivePowerL2 == null) { + reactivePowerL2 = 0L; + } + if (reactivePowerL3 == null) { + reactivePowerL3 = 0L; + } + reactivePower += meter.reactivePower().valueOptional().get(); + reactivePowerL1 += meter.reactivePower().valueOptional().get() / 3; + reactivePowerL2 += meter.reactivePower().valueOptional().get() / 3; + reactivePowerL3 += meter.reactivePower().valueOptional().get() / 3; + } else { + log.warn(meter.id() + ": reactivePower is invalid!"); + } + try { + if (voltage == null) { + voltage = meter.voltage().value(); + } + } catch (InvalidValueException e) { + log.warn(meter.id() + ": voltage is invalid!"); + } + } + } + this.activePower.updateValue(activePower); + this.activePowerL1.updateValue(activePowerL1); + this.activePowerL2.updateValue(activePowerL2); + this.activePowerL3.updateValue(activePowerL3); + this.reactivePower.updateValue(reactivePower); + this.reactivePowerL1.updateValue(reactivePowerL1); + this.reactivePowerL2.updateValue(reactivePowerL2); + this.reactivePowerL3.updateValue(reactivePowerL3); + if (activePower != null && reactivePower != null) { + this.apparentPower.updateValue(ControllerUtils.calculateApparentPower(activePower, reactivePower)); + } else { + this.apparentPower.updateValue(null); + } + this.voltage.updateValue(voltage); + this.voltageL1.updateValue(voltageL1); + this.voltageL2.updateValue(voltageL2); + this.voltageL3.updateValue(voltageL3); + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltage; + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + if (channel.equals(meter)) { + loadMeter(); + } + } + + private void loadMeter() { + JsonArray meterIds; + try { + meterIds = meter.value(); + // remove old ess + synchronized (symmetricMeterList) { + synchronized (asymmetricMeterList) { + symmetricMeterList.clear(); + asymmetricMeterList.clear(); + if (meterIds != null) { + for (JsonElement id : meterIds) { + Optional nature = repo.getThingById(id.getAsString()); + if (nature.isPresent()) { + if (nature.get() instanceof AsymmetricMeterNature) { + AsymmetricMeterNature meter = (AsymmetricMeterNature) nature.get(); + asymmetricMeterList.add(meter); + } else if (nature.get() instanceof SymmetricMeterNature) { + SymmetricMeterNature meter = (SymmetricMeterNature) nature.get(); + symmetricMeterList.add(meter); + } else { + log.error("ThingID: " + id.getAsString() + " is no Meter!"); + } + } else { + log.warn("meter: " + id.getAsString() + " not found!"); + } + } + } + } + } + } catch (InvalidValueException e) { + log.error("esss value is invalid!", e); + } + } + + @Override + public void init() { + loadMeter(); + for (ThingChannelsUpdatedListener listener : this.listeners) { + listener.thingChannelsUpdated(this); + } + } + + @Override + public void addListener(ThingChannelsUpdatedListener listener) { + this.listeners.add(listener); + } + + @Override + public void removeListener(ThingChannelsUpdatedListener listener) { + this.listeners.remove(listener); + } + +} diff --git a/edge/src/io/openems/impl/device/wago/WagoFB.java b/edge/src/io/openems/impl/device/wago/WagoFB.java index 25b88d85a1f..925e1763fd2 100644 --- a/edge/src/io/openems/impl/device/wago/WagoFB.java +++ b/edge/src/io/openems/impl/device/wago/WagoFB.java @@ -1,188 +1,189 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.wago; - -import java.io.IOException; -import java.io.InputStream; -import java.net.Inet4Address; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; - -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.NamedNodeMap; -import org.w3c.dom.Node; -import org.xml.sax.SAXException; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.modbus.ModbusDevice; - -/* - * Example config: - * - *
- * {
- *   "class": "io.openems.impl.protocol.modbus.ModbusTcp",
- *   "ip": "172.16.86.1",
- *   "devices": [
- *     {
- *       "class": "io.openems.impl.device.wago.WagoFB",
- *       "modbusUnitId": 1,
- *       "output": {
- *         "id": "output0",
- *         "ip": "172.16.86.1"
- *       },
- *       "input": {
- *         "id": "input0",
- *         "ip": "172.16.86.1"
- *       }
- *     }
- *   ]
- * }
- * 
- */ - -@ThingInfo(title = "WAGO I/O") -public class WagoFB extends ModbusDevice { - - /* - * Constructors - */ - public WagoFB() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Output", description = "Sets the output nature.", type = WagoFBOutput.class) - public final ConfigChannel output = new ConfigChannel<>("output", this); - - @ConfigInfo(title = "Input", description = "Sets the input nature.", type = WagoFBInput.class) - public final ConfigChannel input = new ConfigChannel<>("input", this); - - /* - * Fields - */ - private static HashMap>> configCache = new HashMap<>(); - - /* - * Methods - */ - @Override - protected Set getDeviceNatures() { - Set natures = new HashSet<>(); - if (output.valueOptional().isPresent()) { - natures.add(output.valueOptional().get()); - } - if (input.valueOptional().isPresent()) { - natures.add(input.valueOptional().get()); - } - return natures; - } - - public static HashMap> getConfig(Inet4Address ip) throws ConfigException { - if (configCache.containsKey(ip)) { - return configCache.get(ip); - } else { - HashMap> channels = new HashMap<>(); - String username = "admin"; - String password = "wago"; - int ftpPort = 21; - URL url; - Document doc; - try { - url = new URL("ftp://" + username + ":" + password + "@" + ip.getHostAddress() + ":" + ftpPort - + "/etc/EA-config.xml;type=i"); - URLConnection urlc = url.openConnection(); - InputStream is = urlc.getInputStream(); - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - doc = dBuilder.parse(is); - doc.getDocumentElement().normalize(); - } catch (IOException | SAXException | ParserConfigurationException e) { - throw new ConfigException(e.getMessage()); - } - - Node wagoNode = doc.getElementsByTagName("WAGO").item(0); - if (wagoNode != null) { - HashMap moduleCounter = new HashMap(); - Node moduleNode = wagoNode.getFirstChild(); - while (moduleNode != null) { - if (moduleNode.getNodeType() == Node.ELEMENT_NODE) { - NamedNodeMap moduleAttrs = moduleNode.getAttributes(); - String article = moduleAttrs.getNamedItem("ARTIKELNR").getNodeValue(); - String moduletype = moduleAttrs.getNamedItem("MODULETYPE").getNodeValue(); - if (!moduleCounter.containsKey(moduletype)) { - moduleCounter.put(moduletype, 0); - } - moduleCounter.replace(moduletype, moduleCounter.get(moduletype) + 1); - int index = 1; - Node channelNode = moduleNode.getFirstChild(); - while (channelNode != null) { - if (channelNode.getNodeType() == Node.ELEMENT_NODE) { - NamedNodeMap channelAttrs = channelNode.getAttributes(); - String channelType = channelAttrs.getNamedItem("CHANNELTYPE").getNodeValue(); - if (!channels.containsKey(channelType)) { - channels.put(channelType, new ArrayList()); - } - String channelName = ""; - switch (channelType) { - case "DO": - channelName = "DigitalOutput_" + moduleCounter.get(channelType) + "_" + index; - break; - case "DI": - channelName = "DigitalInput_" + moduleCounter.get(channelType) + "_" + index; - break; - default: - LoggerFactory.getLogger(WagoFB.class) - .debug("ChannelType: " + channelName + " nicht erkannt"); - break; - } - channels.get(channelType).add(channelName); - index++; - } - channelNode = channelNode.getNextSibling(); - } - } - moduleNode = moduleNode.getNextSibling(); - } - } - configCache.put(ip, channels); - return channels; - } - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.wago; + +import java.io.IOException; +import java.io.InputStream; +import java.net.Inet4Address; +import java.net.URL; +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.modbus.ModbusDevice; + +/* + * Example config: + * + *
+ * {
+ *   "class": "io.openems.impl.protocol.modbus.ModbusTcp",
+ *   "ip": "172.16.86.1",
+ *   "devices": [
+ *     {
+ *       "class": "io.openems.impl.device.wago.WagoFB",
+ *       "modbusUnitId": 1,
+ *       "output": {
+ *         "id": "output0",
+ *         "ip": "172.16.86.1"
+ *       },
+ *       "input": {
+ *         "id": "input0",
+ *         "ip": "172.16.86.1"
+ *       }
+ *     }
+ *   ]
+ * }
+ * 
+ */ + +@ThingInfo(title = "WAGO I/O") +public class WagoFB extends ModbusDevice { + + /* + * Constructors + */ + public WagoFB(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Output", description = "Sets the output nature.", type = WagoFBOutput.class) + public final ConfigChannel output = new ConfigChannel<>("output", this); + + @ConfigInfo(title = "Input", description = "Sets the input nature.", type = WagoFBInput.class) + public final ConfigChannel input = new ConfigChannel<>("input", this); + + /* + * Fields + */ + private static HashMap>> configCache = new HashMap<>(); + + /* + * Methods + */ + @Override + protected Set getDeviceNatures() { + Set natures = new HashSet<>(); + if (output.valueOptional().isPresent()) { + natures.add(output.valueOptional().get()); + } + if (input.valueOptional().isPresent()) { + natures.add(input.valueOptional().get()); + } + return natures; + } + + public static HashMap> getConfig(Inet4Address ip) throws ConfigException { + if (configCache.containsKey(ip)) { + return configCache.get(ip); + } else { + HashMap> channels = new HashMap<>(); + String username = "admin"; + String password = "wago"; + int ftpPort = 21; + URL url; + Document doc; + try { + url = new URL("ftp://" + username + ":" + password + "@" + ip.getHostAddress() + ":" + ftpPort + + "/etc/EA-config.xml;type=i"); + URLConnection urlc = url.openConnection(); + InputStream is = urlc.getInputStream(); + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + doc = dBuilder.parse(is); + doc.getDocumentElement().normalize(); + } catch (IOException | SAXException | ParserConfigurationException e) { + throw new ConfigException(e.getMessage()); + } + + Node wagoNode = doc.getElementsByTagName("WAGO").item(0); + if (wagoNode != null) { + HashMap moduleCounter = new HashMap(); + Node moduleNode = wagoNode.getFirstChild(); + while (moduleNode != null) { + if (moduleNode.getNodeType() == Node.ELEMENT_NODE) { + NamedNodeMap moduleAttrs = moduleNode.getAttributes(); + String article = moduleAttrs.getNamedItem("ARTIKELNR").getNodeValue(); + String moduletype = moduleAttrs.getNamedItem("MODULETYPE").getNodeValue(); + if (!moduleCounter.containsKey(moduletype)) { + moduleCounter.put(moduletype, 0); + } + moduleCounter.replace(moduletype, moduleCounter.get(moduletype) + 1); + int index = 1; + Node channelNode = moduleNode.getFirstChild(); + while (channelNode != null) { + if (channelNode.getNodeType() == Node.ELEMENT_NODE) { + NamedNodeMap channelAttrs = channelNode.getAttributes(); + String channelType = channelAttrs.getNamedItem("CHANNELTYPE").getNodeValue(); + if (!channels.containsKey(channelType)) { + channels.put(channelType, new ArrayList()); + } + String channelName = ""; + switch (channelType) { + case "DO": + channelName = "DigitalOutput_" + moduleCounter.get(channelType) + "_" + index; + break; + case "DI": + channelName = "DigitalInput_" + moduleCounter.get(channelType) + "_" + index; + break; + default: + LoggerFactory.getLogger(WagoFB.class) + .debug("ChannelType: " + channelName + " nicht erkannt"); + break; + } + channels.get(channelType).add(channelName); + index++; + } + channelNode = channelNode.getNextSibling(); + } + } + moduleNode = moduleNode.getNextSibling(); + } + } + configCache.put(ip, channels); + return channels; + } + } + +} diff --git a/edge/src/io/openems/impl/device/wago/WagoFBInput.java b/edge/src/io/openems/impl/device/wago/WagoFBInput.java index 4bea9d66a5f..a253f5fe416 100644 --- a/edge/src/io/openems/impl/device/wago/WagoFBInput.java +++ b/edge/src/io/openems/impl/device/wago/WagoFBInput.java @@ -1,107 +1,108 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.wago; - -import java.net.Inet4Address; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.io.InputNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.impl.protocol.modbus.ModbusCoilReadChannel; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.internal.CoilElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.range.ModbusCoilRange; -import io.openems.impl.protocol.modbus.internal.range.ModbusRange; - -@ThingInfo(title = "WAGO I/O Input") -public class WagoFBInput extends ModbusDeviceNature implements InputNature { - - /* - * Constructors - */ - public WagoFBInput(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - @ConfigInfo(title = "IP address", description = "IP address of the WAGO device.", type = Inet4Address.class) - public ConfigChannel ip = new ConfigChannel("ip", this); - - /* - * This Channels - */ - private List channel = new ArrayList<>(); - - /* - * Methods - */ - @Override - public ModbusCoilReadChannel[] getInput() { - return channel.toArray(new ModbusCoilReadChannel[channel.size()]); - } - - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - List ranges = new ArrayList<>(); - HashMap> channels; - try { - channels = WagoFB.getConfig(ip.value()); - for (String key : channels.keySet()) { - switch (key) { - case "DI": { - List elements = new ArrayList<>(); - int count = 0; - for (String channel : channels.get(key)) { - ModbusCoilReadChannel ch = new ModbusCoilReadChannel(Integer.toString(count), this); - this.channel.add(ch); - elements.add(new CoilElement(count, ch)); - count++; - if (count % 63 == 0) { - ranges.add(new ModbusCoilRange(elements.get(0).getAddress(), - elements.toArray(new CoilElement[elements.size()]))); - elements.clear(); - } - } - if (this.channel.size() > 0) { - ranges.add(new ModbusCoilRange(elements.get(0).getAddress(), - elements.toArray(new CoilElement[elements.size()]))); - } - } - break; - } - } - } catch (InvalidValueException e) { - log.error("Ip-Address is Invalid", e); - } - ModbusProtocol protocol = new ModbusProtocol(ranges.toArray(new ModbusRange[ranges.size()])); - return protocol; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.wago; + +import java.net.Inet4Address; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.io.InputNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.impl.protocol.modbus.ModbusCoilReadChannel; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.internal.CoilElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.ModbusCoilRange; +import io.openems.impl.protocol.modbus.internal.range.ModbusRange; + +@ThingInfo(title = "WAGO I/O Input") +public class WagoFBInput extends ModbusDeviceNature implements InputNature { + + /* + * Constructors + */ + public WagoFBInput(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + @ConfigInfo(title = "IP address", description = "IP address of the WAGO device.", type = Inet4Address.class) + public ConfigChannel ip = new ConfigChannel("ip", this); + + /* + * This Channels + */ + private List channel = new ArrayList<>(); + + /* + * Methods + */ + @Override + public ModbusCoilReadChannel[] getInput() { + return channel.toArray(new ModbusCoilReadChannel[channel.size()]); + } + + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + List ranges = new ArrayList<>(); + HashMap> channels; + try { + channels = WagoFB.getConfig(ip.value()); + for (String key : channels.keySet()) { + switch (key) { + case "DI": { + List elements = new ArrayList<>(); + int count = 0; + for (String channel : channels.get(key)) { + ModbusCoilReadChannel ch = new ModbusCoilReadChannel(Integer.toString(count), this); + this.channel.add(ch); + elements.add(new CoilElement(count, ch)); + count++; + if (count % 63 == 0) { + ranges.add(new ModbusCoilRange(elements.get(0).getAddress(), + elements.toArray(new CoilElement[elements.size()]))); + elements.clear(); + } + } + if (this.channel.size() > 0) { + ranges.add(new ModbusCoilRange(elements.get(0).getAddress(), + elements.toArray(new CoilElement[elements.size()]))); + } + } + break; + } + } + } catch (InvalidValueException e) { + log.error("Ip-Address is Invalid", e); + } + ModbusProtocol protocol = new ModbusProtocol(ranges.toArray(new ModbusRange[ranges.size()])); + return protocol; + } + +} diff --git a/edge/src/io/openems/impl/device/wago/WagoFBOutput.java b/edge/src/io/openems/impl/device/wago/WagoFBOutput.java index 68a487d526c..ed0dd974583 100644 --- a/edge/src/io/openems/impl/device/wago/WagoFBOutput.java +++ b/edge/src/io/openems/impl/device/wago/WagoFBOutput.java @@ -1,108 +1,109 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.device.wago; - -import java.net.Inet4Address; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.nature.io.OutputNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.impl.protocol.modbus.ModbusCoilWriteChannel; -import io.openems.impl.protocol.modbus.ModbusDeviceNature; -import io.openems.impl.protocol.modbus.internal.CoilElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.range.ModbusCoilRange; -import io.openems.impl.protocol.modbus.internal.range.ModbusRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; - -@ThingInfo(title = "WAGO I/O Output") -public class WagoFBOutput extends ModbusDeviceNature implements OutputNature { - - /* - * Constructors - */ - public WagoFBOutput(String thingId) throws ConfigException { - super(thingId); - } - - /* - * Config - */ - @ConfigInfo(title = "IP", description = "IP address of the WAGO device.", type = Inet4Address.class) - public ConfigChannel ip = new ConfigChannel("ip", this); - - /* - * This Channels - */ - private List channel = new ArrayList<>(); - - /* - * Methods - */ - @Override - public ModbusCoilWriteChannel[] setOutput() { - return channel.toArray(new ModbusCoilWriteChannel[channel.size()]); - } - - @Override - protected ModbusProtocol defineModbusProtocol() throws ConfigException { - List ranges = new ArrayList<>(); - HashMap> channels; - try { - channels = WagoFB.getConfig(ip.value()); - for (String key : channels.keySet()) { - switch (key) { - case "DO": { - List elements = new ArrayList<>(); - int count = 0; - for (String channel : channels.get(key)) { - ModbusCoilWriteChannel ch = new ModbusCoilWriteChannel(Integer.toString(count), this); - this.channel.add(ch); - elements.add(new CoilElement(512 + count, ch)); - count++; - if (count % 63 == 0) { - ranges.add(new ModbusCoilRange(elements.get(0).getAddress(), - elements.toArray(new CoilElement[elements.size()]))); - elements.clear(); - } - } - if (this.channel.size() > 0) { - ranges.add(new WriteableModbusCoilRange(elements.get(0).getAddress(), - elements.toArray(new CoilElement[elements.size()]))); - } - } - break; - } - } - } catch (InvalidValueException e) { - log.error("Ip-Address is Invalid", e); - } - ModbusProtocol protocol = new ModbusProtocol(ranges.toArray(new ModbusRange[ranges.size()])); - return protocol; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.device.wago; + +import java.net.Inet4Address; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.io.OutputNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.impl.protocol.modbus.ModbusCoilWriteChannel; +import io.openems.impl.protocol.modbus.ModbusDeviceNature; +import io.openems.impl.protocol.modbus.internal.CoilElement; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.ModbusCoilRange; +import io.openems.impl.protocol.modbus.internal.range.ModbusRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; + +@ThingInfo(title = "WAGO I/O Output") +public class WagoFBOutput extends ModbusDeviceNature implements OutputNature { + + /* + * Constructors + */ + public WagoFBOutput(String thingId, Device parent) throws ConfigException { + super(thingId, parent); + } + + /* + * Config + */ + @ConfigInfo(title = "IP", description = "IP address of the WAGO device.", type = Inet4Address.class) + public ConfigChannel ip = new ConfigChannel("ip", this); + + /* + * This Channels + */ + private List channel = new ArrayList<>(); + + /* + * Methods + */ + @Override + public ModbusCoilWriteChannel[] setOutput() { + return channel.toArray(new ModbusCoilWriteChannel[channel.size()]); + } + + @Override + protected ModbusProtocol defineModbusProtocol() throws ConfigException { + List ranges = new ArrayList<>(); + HashMap> channels; + try { + channels = WagoFB.getConfig(ip.value()); + for (String key : channels.keySet()) { + switch (key) { + case "DO": { + List elements = new ArrayList<>(); + int count = 0; + for (String channel : channels.get(key)) { + ModbusCoilWriteChannel ch = new ModbusCoilWriteChannel(Integer.toString(count), this); + this.channel.add(ch); + elements.add(new CoilElement(512 + count, ch)); + count++; + if (count % 63 == 0) { + ranges.add(new ModbusCoilRange(elements.get(0).getAddress(), + elements.toArray(new CoilElement[elements.size()]))); + elements.clear(); + } + } + if (this.channel.size() > 0) { + ranges.add(new WriteableModbusCoilRange(elements.get(0).getAddress(), + elements.toArray(new CoilElement[elements.size()]))); + } + } + break; + } + } + } catch (InvalidValueException e) { + log.error("Ip-Address is Invalid", e); + } + ModbusProtocol protocol = new ModbusProtocol(ranges.toArray(new ModbusRange[ranges.size()])); + return protocol; + } + +} diff --git a/edge/src/io/openems/impl/persistence/fenecon/FeneconPersistence.java b/edge/src/io/openems/impl/persistence/fenecon/FeneconPersistence.java index 1f846535e31..05666e7743c 100644 --- a/edge/src/io/openems/impl/persistence/fenecon/FeneconPersistence.java +++ b/edge/src/io/openems/impl/persistence/fenecon/FeneconPersistence.java @@ -1,294 +1,344 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.persistence.fenecon; - -import java.net.Inet4Address; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Iterator; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.google.common.collect.EvictingQueue; -import com.google.common.collect.HashMultimap; -import com.google.gson.JsonObject; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.persistence.Persistence; -import io.openems.core.Databus; -import io.openems.core.utilities.websocket.WebsocketHandler; - -@ThingInfo(title = "FENECON Persistence", description = "Establishes the connection to FENECON Cloud.") -public class FeneconPersistence extends Persistence implements ChannelChangeListener { - - /* - * Config - */ - @ConfigInfo(title = "Apikey", description = "Sets the apikey for FENECON Cloud.", type = String.class) - public final ConfigChannel apikey = new ConfigChannel("apikey", this); - - @ConfigInfo(title = "Uri", description = "Sets the connection Uri to FENECON Cloud.", type = String.class, defaultValue = "\"wss://fenecon.de:443/femsserver\"") - public final ConfigChannel uri = new ConfigChannel("uri", this); - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this) - .defaultValue(DEFAULT_CYCLETIME); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - private static final int DEFAULT_CYCLETIME = 2000; - private HashMultimap> queue = HashMultimap.create(); - private EvictingQueue unsentCache = EvictingQueue.create(1000); - private volatile WebsocketClient websocketClient; - private volatile Integer configuredCycleTime = DEFAULT_CYCLETIME; - - /* - * Methods - */ - /** - * Receives update events for all {@link ReadChannel}s, excluding {@link ConfigChannel}s via the {@link Databus}. - */ - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - if (channel == cycleTime) { - // Cycle Time - this.configuredCycleTime = cycleTime.valueOptional().orElse(DEFAULT_CYCLETIME); - } - - if (!(channel instanceof ReadChannel)) { - return; - } - ReadChannel readChannel = (ReadChannel) channel; - - String field = readChannel.address(); - FieldValue fieldValue; - if (!newValue.isPresent()) { - fieldValue = new NullFieldValue(field); - } else { - Object value = newValue.get(); - if (value instanceof Number) { - fieldValue = new NumberFieldValue(field, (Number) value); - } else if (value instanceof String) { - fieldValue = new StringFieldValue(field, (String) value); - } else if (value instanceof Inet4Address) { - fieldValue = new StringFieldValue(field, ((Inet4Address) value).getHostAddress()); - } else if (value instanceof Boolean) { - fieldValue = new NumberFieldValue(field, ((Boolean) value) ? 1 : 0); - } else { - log.warn("FENECON Persistence for value type [" + value.getClass().getName() + "] is not implemented."); - return; - } - } - // Round time to Seconds - Long timestamp = System.currentTimeMillis() / 1000 * 1000; - synchronized (queue) { - queue.put(timestamp, fieldValue); - } - } - - @Override - protected void dispose() { - if (this.websocketClient != null) { - this.websocketClient.close(); - } - } - - @Override - protected void forever() { - JsonObject jTimedata = new JsonObject(); - /* - * Convert FieldVales in queue to JsonObject - */ - synchronized (queue) { - queue.asMap().forEach((timestamp, fieldValues) -> { - JsonObject jTimestamp = new JsonObject(); - fieldValues.forEach(fieldValue -> { - if (fieldValue instanceof NumberFieldValue) { - jTimestamp.addProperty(fieldValue.field, ((NumberFieldValue) fieldValue).value); - } else if (fieldValue instanceof StringFieldValue) { - jTimestamp.addProperty(fieldValue.field, ((StringFieldValue) fieldValue).value); - } - }); - jTimedata.add(String.valueOf(timestamp), jTimestamp); - }); - queue.clear(); - } - // build Json - JsonObject j = new JsonObject(); - j.add("timedata", jTimedata); - /* - * Send to Server - */ - if (this.send(j)) { - /* - * Sent successfully - */ - // reset cycleTime - resetCycleTime(); - - // resend from cache - for (Iterator iterator = unsentCache.iterator(); iterator.hasNext();) { - JsonObject jCachedTimedata = iterator.next(); - JsonObject jCached = new JsonObject(); - jCached.add("timedata", jCachedTimedata); - boolean cacheWasSent = this.send(jCached); - if (cacheWasSent) { - iterator.remove(); - } - } - } else { - /* - * Unable to send - */ - // increase cycleTime - increaseCycleTime(); - - // cache data for later - unsentCache.add(jTimedata); - } - } - - /** - * Send message to websocket - * - * @param j - * @return - */ - private boolean send(JsonObject j) { - Optional websocketHandler = getWebsocketHandler(); - return websocketHandler.isPresent() && websocketHandler.get().send(j); - } - - /** - * Gets the websocket handler - * - * @return - */ - public Optional getWebsocketHandler() { - Optional websocketClient = getWebsocketClient(); - if (websocketClient.isPresent()) { - return Optional.of(websocketClient.get().getWebsocketHandler()); - } - return Optional.empty(); - } - - /** - * Gets the websocket client - * - * @return - */ - private AtomicBoolean isAlreadyConnecting = new AtomicBoolean(false); - - public Optional getWebsocketClient() { - // return existing and opened websocket - if (this.websocketClient != null && this.websocketClient.getConnection().isOpen()) { - return Optional.of(this.websocketClient); - } - this.websocketClient = null; - // check config - if (!this.apikey.valueOptional().isPresent() || !this.uri.valueOptional().isPresent()) { - return Optional.empty(); - } - // a connection is already requested -> return null - // from here only one thread is allowed to enter - if (isAlreadyConnecting.getAndSet(true)) { - return Optional.empty(); - } - String uri = this.uri.valueOptional().get(); - String apikey = this.apikey.valueOptional().get(); - // Try to connect in asynchronous thread - Runnable task = () -> { - WebsocketClient newWebsocketClient = null; - try { - // create new websocket - // TODO: check server certificate - newWebsocketClient = new WebsocketClient(new URI(uri), apikey); - log.info("FENECON persistence is connecting... [" + uri + "]"); - if (newWebsocketClient.connectBlocking(10)) { - // successful -> return connected websocket - log.info("FENECON persistence connected [" + uri + "]"); - } else { - // not connected -> return empty - log.warn("FENECON persistence failed connection to uri [" + uri + "]"); - newWebsocketClient = null; - } - } catch (URISyntaxException e) { - log.error("Invalid uri: " + e.getMessage()); - // newWebsocketClient = null; - } catch (InterruptedException e) { - log.warn("Websocket connection interrupted: " + e.getMessage()); - newWebsocketClient = null; - } catch (Exception e) { - log.warn("Websocket exception: " + e.getMessage()); - newWebsocketClient = null; - } - this.websocketClient = newWebsocketClient; - isAlreadyConnecting.set(false); - }; - Thread thread = new Thread(task); - thread.start(); - // while connecting -> still returning null - return Optional.empty(); - } - - private void increaseCycleTime() { - int currentCycleTime = this.cycleTime().valueOptional().orElse(DEFAULT_CYCLETIME); - int newCycleTime; - if (currentCycleTime < 30000 /* 30 seconds */) { - newCycleTime = currentCycleTime * 2; - } else { - newCycleTime = currentCycleTime; - } - if (currentCycleTime != newCycleTime) { - this.cycleTime().updateValue(newCycleTime, false); - log.info("New cycle time: " + newCycleTime); - } - } - - private void resetCycleTime() { - int currentCycleTime = this.cycleTime().valueOptional().orElse(DEFAULT_CYCLETIME); - int newCycleTime = this.configuredCycleTime; - this.cycleTime().updateValue(newCycleTime, false); - if (currentCycleTime != newCycleTime) { - this.cycleTime().updateValue(newCycleTime, false); - log.info("Reset cycle time: " + newCycleTime); - } - } - - @Override - protected boolean initialize() { - boolean successful = getWebsocketClient().isPresent(); - if (!successful) { - increaseCycleTime(); - } - return getWebsocketClient().isPresent(); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.persistence.fenecon; + +import java.net.Inet4Address; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Iterator; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; + +import com.google.common.collect.EvictingQueue; +import com.google.common.collect.HashMultimap; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.persistence.Persistence; +import io.openems.api.thing.Thing; +import io.openems.common.types.FieldValue; +import io.openems.common.types.NullFieldValue; +import io.openems.common.types.NumberFieldValue; +import io.openems.common.types.StringFieldValue; +import io.openems.common.websocket.DefaultMessages; +import io.openems.common.websocket.WebSocketUtils; +import io.openems.core.Databus; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.websocket.EdgeWebsocketHandler; + +// TODO make sure this is registered as ChannelChangeListener also to ConfigChannels +@ThingInfo(title = "FENECON Persistence", description = "Establishes the connection to FENECON Cloud.") +public class FeneconPersistence extends Persistence implements ChannelChangeListener { + + /* + * Config + */ + @ConfigInfo(title = "Apikey", description = "Sets the apikey for FENECON Cloud.", type = String.class) + public final ConfigChannel apikey = new ConfigChannel("apikey", this).doNotPersist(); + + @ConfigInfo(title = "Uri", description = "Sets the connection Uri to FENECON Cloud.", type = String.class, defaultValue = "\"wss://fenecon.de:443/openems-backend\"") + public final ConfigChannel uri = new ConfigChannel("uri", this).doNotPersist(); + + @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class) + public ConfigChannel cycleTime = new ConfigChannel("cycleTime", this) + .defaultValue(DEFAULT_CYCLETIME); + + /* + * Fields + */ + private static final int DEFAULT_CYCLETIME = 2000; + // Queue of data for the next cycle + private HashMultimap> queue = HashMultimap.create(); + // Unsent queue (FIFO) + private EvictingQueue unsentCache = EvictingQueue.create(1000); + private volatile WebsocketClient websocketClient; + private volatile Integer configuredCycleTime = DEFAULT_CYCLETIME; + + /* + * Methods + */ + /** + * Receives update events for all {@link ReadChannel}s, excluding {@link ConfigChannel}s via the {@link Databus}. + */ + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + // Update cycleTime of FENECON Persistence + if (channel == cycleTime) { + this.configuredCycleTime = cycleTime.valueOptional().orElse(DEFAULT_CYCLETIME); + } + this.addChannelValueToQueue(channel, newValue); + } + + @Override + protected void forever() { + // Convert FieldVales in queue to JsonObject + JsonObject j; + synchronized (queue) { + j = DefaultMessages.timestampedData(queue); + queue.clear(); + } + + // Send data to Server + if (this.send(j)) { + // Successful + + // reset cycleTime + resetCycleTime(); + + // resend from cache + for (Iterator iterator = unsentCache.iterator(); iterator.hasNext();) { + JsonObject jCached = iterator.next(); + boolean cacheWasSent = this.send(jCached); + if (cacheWasSent) { + iterator.remove(); + } + } + } else { + // Failed to send + + // increase cycleTime + increaseCycleTime(); + + // cache data for later + unsentCache.add(j); + } + } + + @Override + protected void dispose() { + if (this.websocketClient != null) { + this.websocketClient.close(); + } + } + + /** + * Send message to websocket + * + * @param j + * @return + */ + private boolean send(JsonObject j) { + Optional websocketHandler = getWebsocketHandler(); + return websocketHandler.isPresent() && WebSocketUtils.send(websocketHandler.get().getWebsocket(), j); + } + + /** + * Gets the websocket handler + * + * @return + */ + public Optional getWebsocketHandler() { + Optional websocketClient = getWebsocketClient(); + if (websocketClient.isPresent()) { + return Optional.of(websocketClient.get().getWebsocketHandler()); + } + return Optional.empty(); + } + + /** + * Gets the websocket client + * + * @return + */ + private AtomicBoolean isAlreadyConnecting = new AtomicBoolean(false); + + public Optional getWebsocketClient() { + // return existing and opened websocket + if (this.websocketClient != null && this.websocketClient.getConnection().isOpen()) { + return Optional.of(this.websocketClient); + } + this.websocketClient = null; + // check config + if (!this.apikey.valueOptional().isPresent() || !this.uri.valueOptional().isPresent()) { + return Optional.empty(); + } + // a connection is already requested -> return null + // from here only one thread is allowed to enter + if (isAlreadyConnecting.getAndSet(true)) { + return Optional.empty(); + } + String uri = this.uri.valueOptional().get(); + String apikey = this.apikey.valueOptional().get(); + // Try to connect in asynchronous thread + Runnable task = () -> { + WebsocketClient newWebsocketClient = null; + try { + // create new websocket + // TODO: check server certificate + newWebsocketClient = new WebsocketClient(new URI(uri), apikey); + log.info("FENECON persistence is connecting... [" + uri + "]"); + if (newWebsocketClient.connectBlocking(10)) { + // successful -> return connected websocket + log.info("FENECON persistence connected [" + uri + "]"); + // Add current status of all channels to queue + this.addCurrentValueOfAllChannelsToQueue(); + } else { + // not connected -> return empty + log.warn("FENECON persistence failed connection to uri [" + uri + "]"); + newWebsocketClient = null; + } + } catch (URISyntaxException e) { + log.error("Invalid uri: " + e.getMessage()); + // newWebsocketClient = null; + } catch (InterruptedException e) { + log.warn("Websocket connection interrupted: " + e.getMessage()); + newWebsocketClient = null; + } catch (Exception e) { + log.warn("Websocket exception: " + e.getMessage()); + newWebsocketClient = null; + } + this.websocketClient = newWebsocketClient; + isAlreadyConnecting.set(false); + }; + Thread thread = new Thread(task); + thread.start(); + // while connecting -> still returning null + return Optional.empty(); + } + + private void increaseCycleTime() { + int currentCycleTime = this.getCycleTime(); + int newCycleTime; + if (currentCycleTime < 30000 /* 30 seconds */) { + newCycleTime = currentCycleTime * 2; + } else { + newCycleTime = currentCycleTime; + } + if (currentCycleTime != newCycleTime) { + this.cycleTime.updateValue(newCycleTime, false); + } + } + + /** + * Cycletime is adjusted if connection to Backend fails. This method resets it to configured or default value. + */ + private void resetCycleTime() { + int currentCycleTime = this.getCycleTime(); + int newCycleTime = this.configuredCycleTime; + this.cycleTime.updateValue(newCycleTime, false); + if (currentCycleTime != newCycleTime) { + this.cycleTime.updateValue(newCycleTime, false); + } + } + + @Override + protected boolean initialize() { + boolean successful = getWebsocketClient().isPresent(); + if (!successful) { + increaseCycleTime(); + } + return getWebsocketClient().isPresent(); + } + + /** + * Add a channel value to the send queue + * + * @param channel + * @param valueOpt + */ + private void addChannelValueToQueue(Channel channel) { + if (!(channel instanceof ReadChannel)) { + // TODO check for more types - see other addChannelValueToQueue method + return; + } + ReadChannel readChannel = (ReadChannel) channel; + this.addChannelValueToQueue(channel, readChannel.valueOptional()); + } + + /** + * Add a channel value to the send queue + * + * @param channel + * @param valueOpt + */ + private void addChannelValueToQueue(Channel channel, Optional valueOpt) { + // Ignore anything that is not a ReadChannel + if (!(channel instanceof ReadChannel)) { + return; + } + ReadChannel readChannel = (ReadChannel) channel; + // Ignore channels that shall not be persisted + if (readChannel.isDoNotPersist()) { + return; + } + + // Get timestamp and round to seconds + Long timestamp = System.currentTimeMillis() / 1000 * 1000; + + // Read and format value from channel + String field = readChannel.address(); + FieldValue fieldValue; + if (!valueOpt.isPresent()) { + fieldValue = new NullFieldValue(field); + } else { + Object value = valueOpt.get(); + if (value instanceof Number) { + fieldValue = new NumberFieldValue(field, (Number) value); + } else if (value instanceof String) { + fieldValue = new StringFieldValue(field, (String) value); + } else if (value instanceof Inet4Address) { + fieldValue = new StringFieldValue(field, ((Inet4Address) value).getHostAddress()); + } else if (value instanceof Boolean) { + fieldValue = new NumberFieldValue(field, ((Boolean) value) ? 1 : 0); + } else if (value instanceof DeviceNature || value instanceof JsonElement || value instanceof Map + || value instanceof Set) { + // ignore + return; + } else { + log.warn("FENECON Persistence for value type [" + value.getClass().getName() + "] of channel [" + + channel.address() + "] is not implemented."); + return; + } + } + + // Add timestamp + value to queue + synchronized (queue) { + queue.put(timestamp, fieldValue); + } + } + + /** + * On websocket open, add current values of all channels to queue. This is to prepare upcoming "channelChanged" + * events, where only changes are sent + */ + private void addCurrentValueOfAllChannelsToQueue() { + ThingRepository thingRepository = ThingRepository.getInstance(); + for (Thing thing : thingRepository.getThings()) { + for (Channel channel : thingRepository.getChannels(thing)) { + this.addChannelValueToQueue(channel); + } + } + } + + @Override + protected int getCycleTime() { + return cycleTime.valueOptional().orElse(DEFAULT_CYCLETIME); + } +} \ No newline at end of file diff --git a/edge/src/io/openems/impl/persistence/fenecon/WebsocketClient.java b/edge/src/io/openems/impl/persistence/fenecon/WebsocketClient.java index 1f3e663aeb5..286ea83b321 100644 --- a/edge/src/io/openems/impl/persistence/fenecon/WebsocketClient.java +++ b/edge/src/io/openems/impl/persistence/fenecon/WebsocketClient.java @@ -37,13 +37,13 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import io.openems.core.utilities.websocket.WebsocketHandler; +import io.openems.core.utilities.websocket.EdgeWebsocketHandler; public class WebsocketClient extends org.java_websocket.client.WebSocketClient { private static Logger log = LoggerFactory.getLogger(WebsocketClient.class); - private final WebsocketHandler websocketHandler; + private final EdgeWebsocketHandler websocketHandler; // TODO remove public WebsocketClient(URI uri, String apikey) throws Exception { super( // @@ -64,8 +64,7 @@ public WebsocketClient(URI uri, String apikey) throws Exception { // } this.setSocket(SSLSocketFactory.getDefault().createSocket()); } - this.websocketHandler = new WebsocketHandler(this.getConnection(), - null /* second parameter is only for local websocket access */); + this.websocketHandler = new EdgeWebsocketHandler(this.getConnection()); } @Override @@ -97,18 +96,15 @@ public void onMessage(String message) { private CountDownLatch connectLatch = new CountDownLatch(1); /** - * Same as connect but blocks until the websocket connected or failed to do so.
+ * Same as connect but blocks until the websocket connected or failed to do so. * Returns whether it succeeded or not. * - * Overrides original method to use timeout of 10 seconds + * Overrides original method to be able to use custom timeout */ public boolean connectBlocking(long timeoutSeconds) throws InterruptedException { connect(); connectLatch.await(timeoutSeconds, TimeUnit.SECONDS); boolean connected = getConnection().isOpen(); - if (connected) { - this.websocketHandler.sendConnectionSuccessfulReply(); - } return connected; } @@ -117,7 +113,7 @@ public boolean connectBlocking(long timeoutSeconds) throws InterruptedException * * @return */ - public WebsocketHandler getWebsocketHandler() { + public EdgeWebsocketHandler getWebsocketHandler() { return this.websocketHandler; } } diff --git a/edge/src/io/openems/impl/persistence/influxdb/FieldValue.java b/edge/src/io/openems/impl/persistence/influxdb/FieldValue.java index 64403ee3928..cc1fd6bb987 100644 --- a/edge/src/io/openems/impl/persistence/influxdb/FieldValue.java +++ b/edge/src/io/openems/impl/persistence/influxdb/FieldValue.java @@ -20,6 +20,7 @@ *******************************************************************************/ package io.openems.impl.persistence.influxdb; +//TODO move to common package public abstract class FieldValue { public final String field; public final T value; diff --git a/edge/src/io/openems/impl/persistence/influxdb/InfluxdbPersistence.java b/edge/src/io/openems/impl/persistence/influxdb/InfluxdbPersistence.java index 4d7ab37e35a..8c0857ab208 100644 --- a/edge/src/io/openems/impl/persistence/influxdb/InfluxdbPersistence.java +++ b/edge/src/io/openems/impl/persistence/influxdb/InfluxdbPersistence.java @@ -1,203 +1,214 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.persistence.influxdb; - -import java.net.Inet4Address; -import java.time.ZonedDateTime; -import java.util.Optional; -import java.util.concurrent.TimeUnit; - -import org.influxdb.InfluxDB; -import org.influxdb.InfluxDBFactory; -import org.influxdb.dto.BatchPoints; -import org.influxdb.dto.Point; -import org.influxdb.dto.Point.Builder; - -import com.google.common.collect.HashMultimap; -import com.google.gson.JsonObject; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelUpdateListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.api.persistence.QueryablePersistence; -import io.openems.core.Databus; - -@ThingInfo(title = "InfluxDB Persistence", description = "Persists data in an InfluxDB time-series database.") -public class InfluxdbPersistence extends QueryablePersistence implements ChannelUpdateListener { - - /* - * Config - */ - @ConfigInfo(title = "FEMS", description = "Sets FEMS-number.", type = Integer.class) - public final ConfigChannel fems = new ConfigChannel<>("fems", this); - - @ConfigInfo(title = "IP address", description = "IP address of InfluxDB.", type = Inet4Address.class) - public final ConfigChannel ip = new ConfigChannel<>("ip", this); - - @ConfigInfo(title = "Username", description = "Username for InfluxDB.", type = String.class, defaultValue = "root") - public final ConfigChannel username = new ConfigChannel<>("username", this); - - @ConfigInfo(title = "Password", description = "Password for InfluxDB.", type = String.class, defaultValue = "root") - public final ConfigChannel password = new ConfigChannel<>("password", this); - - @ConfigInfo(title = "Database", description = "Database name for InfluxDB.", type = String.class, defaultValue = "db") - public final ConfigChannel database = new ConfigChannel<>("database", this); - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(10000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - private Optional _influxdb = Optional.empty(); - private HashMultimap> queue = HashMultimap.create(); - - /* - * Methods - */ - /** - * Receives events for all {@link ReadChannel}s, excluding {@link ConfigChannel}s via the {@link Databus}. - */ - @Override - public void channelUpdated(Channel channel, Optional newValue) { - if (!(channel instanceof ReadChannel)) { - return; - } - ReadChannel readChannel = (ReadChannel) channel; - if (!newValue.isPresent()) { - return; - } - Object value = newValue.get(); - String field = readChannel.address(); - FieldValue fieldValue; - if (value instanceof Number) { - fieldValue = new NumberFieldValue(field, (Number) value); - } else if (value instanceof String) { - fieldValue = new StringFieldValue(field, (String) value); - } else { - return; - } - // Round time to Cycle-Time - int cycleTime = this.cycleTime().valueOptional().get(); - Long timestamp = System.currentTimeMillis() / cycleTime * cycleTime; - synchronized (queue) { - queue.put(timestamp, fieldValue); - } - } - - @Override - protected void dispose() { - - } - - @Override - protected void forever() { - // Prepare DB connection - Optional _influxdb = getInfluxDB(); - if (!_influxdb.isPresent()) { - synchronized (queue) { - // Clear queue if we don't have a valid influxdb connection. This is necessary to avoid filling the - // memory in case of no available DB connection - queue.clear(); - } - } - InfluxDB influxDB = _influxdb.get(); - /* - * Convert FieldVales in queue to Points - */ - BatchPoints batchPoints = BatchPoints.database(database.valueOptional().orElse("db")) // - .tag("fems", String.valueOf(fems.valueOptional().get())) // - /* .retentionPolicy("autogen") */.build(); - synchronized (queue) { - queue.asMap().forEach((timestamp, fieldValues) -> { - Builder builder = Point.measurement("data") // - .time(timestamp, TimeUnit.MILLISECONDS); - fieldValues.forEach(fieldValue -> { - if (fieldValue instanceof NumberFieldValue) { - builder.addField(fieldValue.field, ((NumberFieldValue) fieldValue).value); - } else if (fieldValue instanceof StringFieldValue) { - builder.addField(fieldValue.field, ((StringFieldValue) fieldValue).value); - } - }); - batchPoints.point(builder.build()); - }); - queue.clear(); - } - // write to DB - try { - influxDB.write(batchPoints); - log.debug("Wrote [" + batchPoints.getPoints().size() + "] points to InfluxDB"); - } catch (RuntimeException e) { - log.error("Error writing to InfluxDB: " + e); - } - } - - @Override - protected boolean initialize() { - if (getInfluxDB().isPresent()) { - return true; - } else { - return false; - } - } - - private Optional getInfluxDB() { - if (!this.ip.valueOptional().isPresent() || !this.fems.valueOptional().isPresent() - || !this.username.valueOptional().isPresent() || !this.password.valueOptional().isPresent()) { - return Optional.empty(); - } - - if (_influxdb.isPresent()) { - return this._influxdb; - } - - String ip = this.ip.valueOptional().get().getHostAddress(); - String username = this.username.valueOptional().get(); - String password = this.password.valueOptional().get(); - - InfluxDB influxdb = InfluxDBFactory.connect("http://" + ip + ":8086", username, password); - try { - influxdb.createDatabase(database.valueOptional().orElse("db")); - } catch (RuntimeException e) { - log.error("Unable to connect to InfluxDB: " + e.getCause()); - return Optional.empty(); - } - - this._influxdb = Optional.of(influxdb); - return this._influxdb; - } - - @Override - public JsonObject query(ZonedDateTime fromDate, ZonedDateTime toDate, JsonObject channels, int resolution) - throws OpenemsException { - Optional _influxdb = getInfluxDB(); - return InfluxdbQueryWrapper.query(_influxdb, fems.valueOptional(), fromDate, toDate, channels, resolution, - database.valueOptional().orElse("db")); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.persistence.influxdb; + +import java.net.Inet4Address; +import java.time.ZonedDateTime; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import org.influxdb.InfluxDB; +import org.influxdb.InfluxDBFactory; +import org.influxdb.dto.BatchPoints; +import org.influxdb.dto.Point; +import org.influxdb.dto.Point.Builder; + +import com.google.common.collect.HashMultimap; +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelUpdateListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.api.persistence.QueryablePersistence; +import io.openems.common.utils.InfluxdbUtils; +import io.openems.core.Databus; + +@ThingInfo(title = "InfluxDB Persistence", description = "Persists data in an InfluxDB time-series database.") +public class InfluxdbPersistence extends QueryablePersistence implements ChannelUpdateListener { + + /* + * Config + */ + @ConfigInfo(title = "FEMS", description = "Sets FEMS-number.", type = Integer.class) + public final ConfigChannel fems = new ConfigChannel<>("fems", this); + + @ConfigInfo(title = "IP address", description = "IP address of InfluxDB.", type = Inet4Address.class) + public final ConfigChannel ip = new ConfigChannel<>("ip", this); + + @ConfigInfo(title = "Username", description = "Username for InfluxDB.", type = String.class, defaultValue = "root") + public final ConfigChannel username = new ConfigChannel<>("username", this); + + @ConfigInfo(title = "Password", description = "Password for InfluxDB.", type = String.class, defaultValue = "root") + public final ConfigChannel password = new ConfigChannel<>("password", this); + + @ConfigInfo(title = "Database", description = "Database name for InfluxDB.", type = String.class, defaultValue = "db") + public final ConfigChannel database = new ConfigChannel<>("database", this); + + @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class) + public ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(10000); + + /* + * Fields + */ + private static final int DEFAULT_CYCLETIME = 10000; + private Optional _influxdb = Optional.empty(); + private HashMultimap> queue = HashMultimap.create(); + + /* + * Methods + */ + /** + * Receives events for all {@link ReadChannel}s, excluding {@link ConfigChannel}s via the {@link Databus}. + */ + @Override + public void channelUpdated(Channel channel, Optional newValue) { + if (!(channel instanceof ReadChannel)) { + return; + } + ReadChannel readChannel = (ReadChannel) channel; + if (!newValue.isPresent()) { + return; + } + Object value = newValue.get(); + String field = readChannel.address(); + FieldValue fieldValue; + if (value instanceof Number) { + fieldValue = new NumberFieldValue(field, (Number) value); + } else if (value instanceof String) { + fieldValue = new StringFieldValue(field, (String) value); + } else { + return; + } + // Round time to Cycle-Time + int cycleTime = this.getCycleTime(); + Long timestamp = System.currentTimeMillis() / cycleTime * cycleTime; + synchronized (queue) { + queue.put(timestamp, fieldValue); + } + } + + @Override + protected void dispose() { + + } + + @Override + protected void forever() { + // Prepare DB connection + Optional _influxdb = getInfluxDB(); + if (!_influxdb.isPresent()) { + synchronized (queue) { + // Clear queue if we don't have a valid influxdb connection. This is necessary to avoid filling the + // memory in case of no available DB connection + queue.clear(); + } + } + InfluxDB influxDB = _influxdb.get(); + /* + * Convert FieldVales in queue to Points + */ + BatchPoints batchPoints = BatchPoints.database(database.valueOptional().orElse("db")) // + .tag("fems", String.valueOf(fems.valueOptional().get())) // + /* .retentionPolicy("autogen") */.build(); + synchronized (queue) { + queue.asMap().forEach((timestamp, fieldValues) -> { + Builder builder = Point.measurement("data") // + .time(timestamp, TimeUnit.MILLISECONDS); + fieldValues.forEach(fieldValue -> { + if (fieldValue instanceof NumberFieldValue) { + builder.addField(fieldValue.field, ((NumberFieldValue) fieldValue).value); + } else if (fieldValue instanceof StringFieldValue) { + builder.addField(fieldValue.field, ((StringFieldValue) fieldValue).value); + } + }); + batchPoints.point(builder.build()); + }); + queue.clear(); + } + // write to DB + try { + influxDB.write(batchPoints); + log.debug("Wrote [" + batchPoints.getPoints().size() + "] points to InfluxDB"); + } catch (RuntimeException e) { + log.error("Error writing to InfluxDB: " + e); + } + } + + @Override + protected boolean initialize() { + if (getInfluxDB().isPresent()) { + return true; + } else { + return false; + } + } + + private Optional getInfluxDB() { + if (!this.ip.valueOptional().isPresent() || !this.fems.valueOptional().isPresent() + || !this.username.valueOptional().isPresent() || !this.password.valueOptional().isPresent()) { + return Optional.empty(); + } + + if (_influxdb.isPresent()) { + return this._influxdb; + } + + String ip = this.ip.valueOptional().get().getHostAddress(); + String username = this.username.valueOptional().get(); + String password = this.password.valueOptional().get(); + + InfluxDB influxdb = InfluxDBFactory.connect("http://" + ip + ":8086", username, password); + try { + influxdb.createDatabase(database.valueOptional().orElse("db")); + } catch (RuntimeException e) { + log.error("Unable to connect to InfluxDB: " + e.getCause()); + return Optional.empty(); + } + + this._influxdb = Optional.of(influxdb); + return this._influxdb; + } + + @Override + public JsonArray queryHistoricData(Optional deviceIdOpt, ZonedDateTime fromDate, ZonedDateTime toDate, + JsonObject channels, int resolution) throws io.openems.common.exceptions.OpenemsException { + Optional influxdbOpt = getInfluxDB(); + if (!influxdbOpt.isPresent()) { + throw new OpenemsException("InfluxDB is not available"); + } + Optional databaseOpt = this.database.valueOptional(); + if (!databaseOpt.isPresent()) { + throw new OpenemsException("InfluxDB database is not available"); + } + return InfluxdbUtils.queryHistoricData(influxdbOpt.get(), databaseOpt.get(), deviceIdOpt, fromDate, toDate, + channels, resolution); + } + + @Override + protected int getCycleTime() { + return cycleTime.valueOptional().orElse(DEFAULT_CYCLETIME); + } +} \ No newline at end of file diff --git a/edge/src/io/openems/impl/persistence/influxdb/NumberFieldValue.java b/edge/src/io/openems/impl/persistence/influxdb/NumberFieldValue.java index af1eef8162a..59bb8b46d9c 100644 --- a/edge/src/io/openems/impl/persistence/influxdb/NumberFieldValue.java +++ b/edge/src/io/openems/impl/persistence/influxdb/NumberFieldValue.java @@ -20,6 +20,7 @@ *******************************************************************************/ package io.openems.impl.persistence.influxdb; +//TODO move to common package public class NumberFieldValue extends FieldValue { public NumberFieldValue(String field, Number value) { diff --git a/edge/src/io/openems/impl/persistence/influxdb/StringFieldValue.java b/edge/src/io/openems/impl/persistence/influxdb/StringFieldValue.java index fe236f15a87..72ce5e8b202 100644 --- a/edge/src/io/openems/impl/persistence/influxdb/StringFieldValue.java +++ b/edge/src/io/openems/impl/persistence/influxdb/StringFieldValue.java @@ -20,6 +20,7 @@ *******************************************************************************/ package io.openems.impl.persistence.influxdb; +// TODO move to common package public class StringFieldValue extends FieldValue { public StringFieldValue(String field, String value) { diff --git a/edge/src/io/openems/impl/protocol/keba/KebaBridge.java b/edge/src/io/openems/impl/protocol/keba/KebaBridge.java index ed289b0914a..e18ff395e92 100644 --- a/edge/src/io/openems/impl/protocol/keba/KebaBridge.java +++ b/edge/src/io/openems/impl/protocol/keba/KebaBridge.java @@ -20,8 +20,6 @@ *******************************************************************************/ package io.openems.impl.protocol.keba; -import java.util.ArrayList; -import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -32,7 +30,6 @@ import io.openems.api.bridge.Bridge; import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; import io.openems.api.doc.ThingInfo; @ThingInfo(title = "KEBA KeContact Bridge") @@ -45,19 +42,12 @@ public KebaBridge() { /* * Config */ - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(10000); private ConfigChannel port = new ConfigChannel("port", this).defaultValue(9070); - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - /* * Fields */ private Logger log = LoggerFactory.getLogger(KebaBridge.class); - protected volatile KebaDevice[] kebadevices = new KebaDevice[0]; private AtomicBoolean isWriteTriggered = new AtomicBoolean(false); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private ScheduledFuture receivingJob = null; @@ -70,44 +60,6 @@ public String toString() { return "KebaBridge []"; } - @Override - public void triggerWrite() { - // set the Write-flag - isWriteTriggered.set(true); - // start "run()" again as fast as possible - triggerForceRun(); - } - - @Override - protected void forever() { - log.info("KebaBridge... forever"); - for (KebaDevice kebadevice : kebadevices) { - log.info("KebaBridge... forever: " + kebadevice.id()); - // if Write-flag was set -> start writing for all Devices immediately - if (isWriteTriggered.get()) { - isWriteTriggered.set(false); - writeAllDevices(); - } - // Poll update for all reports - try { - log.info("KebaBridge... update: " + kebadevice.id()); - kebadevice.update(); - } catch (InterruptedException e) { - log.warn("Updating KebaDevice [{}] was interrupted: {}", kebadevice.id(), e.getMessage()); - } - } - } - - private void writeAllDevices() { - for (KebaDevice kebadevice : kebadevices) { - try { - kebadevice.write(); - } catch (InterruptedException e) { - log.error("Error while writing to KebaDevice [" + kebadevice.id() + "]: " + e.getMessage()); - } - } - } - @Override protected void dispose() { if (this.receivingJob != null) { @@ -118,23 +70,6 @@ protected void dispose() { @Override protected boolean initialize() { - /* - * Copy and cast devices to local kebadevices array - */ - if (devices.isEmpty()) { - return false; - } - List kebadevices = new ArrayList<>(); - for (Device device : devices) { - if (device instanceof KebaDevice) { - kebadevices.add((KebaDevice) device); - } - } - KebaDevice[] newKebadevices = kebadevices.stream().toArray(KebaDevice[]::new); - if (newKebadevices == null) { - newKebadevices = new KebaDevice[0]; - } - this.kebadevices = newKebadevices; /* * Restart ReceivingRunnable */ diff --git a/edge/src/io/openems/impl/protocol/keba/KebaDevice.java b/edge/src/io/openems/impl/protocol/keba/KebaDevice.java index 5caa599409d..7c3be29e73f 100644 --- a/edge/src/io/openems/impl/protocol/keba/KebaDevice.java +++ b/edge/src/io/openems/impl/protocol/keba/KebaDevice.java @@ -24,9 +24,13 @@ import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.Inet4Address; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import io.openems.api.bridge.Bridge; +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; import io.openems.api.channel.ConfigChannel; import io.openems.api.device.Device; import io.openems.api.device.nature.DeviceNature; @@ -39,8 +43,8 @@ public abstract class KebaDevice extends Device { /* * Constructors */ - public KebaDevice() throws OpenemsException { - super(); + public KebaDevice(Bridge parent) throws OpenemsException { + super(parent); log.info("Constructor KebaDevice"); } @@ -58,16 +62,9 @@ public KebaDevice() throws OpenemsException { @ConfigInfo(title = "Port", description = "Sets the port (e.g. 7090).", type = Integer.class, defaultValue = "7090") public final ConfigChannel port = new ConfigChannel("port", this); - protected void update() throws InterruptedException { - log.info("Update"); - try { - this.send("report 1"); - this.send("report 2"); - this.send("report 3"); - } catch (ConfigException | IOException e) { - log.error("Error while updating KebaDevice [" + this.id() + "]: " + e.getMessage()); - } - } + // protected void update() throws InterruptedException { + // + // } /** * Send UDP message to Keba EVCS @@ -95,19 +92,69 @@ protected void send(String s) throws IOException, ConfigException, InterruptedEx } } - protected void write() throws InterruptedException { + // protected void write() throws InterruptedException { + // for (DeviceNature nature : getDeviceNatures()) { + // if (nature instanceof KebaDeviceNature) { + // List messages = ((KebaDeviceNature) nature).getWriteMessages(); + // try { + // for (String message : messages) { + // this.send(message); + // } + // } catch (ConfigException | IOException e) { + // log.error("Error while writing [{}] to KebaDevice [{}]: {}", String.join(",", messages), this.id(), + // e.getMessage()); + // } + // } + // } + // } + + @Override + public List getReadTasks() { + List readTasks = new ArrayList<>(); + for (DeviceNature nature : getDeviceNatures()) { + if (nature instanceof KebaDeviceNature) { + readTasks.add(new BridgeReadTask() { + + @Override + protected void run() throws InterruptedException { + log.info("Update"); + try { + KebaDevice.this.send("report 1"); + KebaDevice.this.send("report 2"); + KebaDevice.this.send("report 3"); + } catch (ConfigException | IOException e) { + log.error("Error while updating KebaDevice [" + KebaDevice.this.id() + "]: " + + e.getMessage()); + } + } + }); + } + } + return readTasks; + } + + @Override + public List getWriteTasks() { + List writeTasks = new ArrayList<>(); for (DeviceNature nature : getDeviceNatures()) { if (nature instanceof KebaDeviceNature) { - List messages = ((KebaDeviceNature) nature).getWriteMessages(); - try { - for (String message : messages) { - this.send(message); + writeTasks.add(new BridgeWriteTask() { + + @Override + protected void run() throws InterruptedException { + List messages = ((KebaDeviceNature) nature).getWriteMessages(); + try { + for (String message : messages) { + KebaDevice.this.send(message); + } + } catch (ConfigException | IOException e) { + log.error("Error while writing [{}] to KebaDevice [{}]: {}", String.join(",", messages), + KebaDevice.this.id(), e.getMessage()); + } } - } catch (ConfigException | IOException e) { - log.error("Error while writing [{}] to KebaDevice [{}]: {}", String.join(",", messages), this.id(), - e.getMessage()); - } + }); } } + return super.getWriteTasks(); } } diff --git a/edge/src/io/openems/impl/protocol/keba/KebaDeviceNature.java b/edge/src/io/openems/impl/protocol/keba/KebaDeviceNature.java index be1ad491a76..92f1bc0007f 100644 --- a/edge/src/io/openems/impl/protocol/keba/KebaDeviceNature.java +++ b/edge/src/io/openems/impl/protocol/keba/KebaDeviceNature.java @@ -26,20 +26,25 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; import io.openems.api.channel.Channel; +import io.openems.api.device.Device; import io.openems.api.device.nature.DeviceNature; import io.openems.api.exception.ConfigException; import io.openems.api.thing.ThingChannelsUpdatedListener; public abstract class KebaDeviceNature implements DeviceNature { + private Device parent; protected final Logger log; private final String thingId; private List listeners; - public KebaDeviceNature(String thingId) throws ConfigException { + public KebaDeviceNature(String thingId, Device parent) throws ConfigException { this.thingId = thingId; + this.parent = parent; log = LoggerFactory.getLogger(this.getClass()); log.info("Constructor KebaDeviceNature"); this.listeners = new ArrayList<>(); @@ -60,6 +65,29 @@ public String id() { return thingId; } + @Override + public List getReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getWriteTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequiredReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Device getParent() { + return parent; + } + @Override public void setAsRequired(Channel channel) { // ignore. All channels/reports are polled by default diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusBridge.java b/edge/src/io/openems/impl/protocol/modbus/ModbusBridge.java index 324a4a220e6..2709c6b9e7d 100644 --- a/edge/src/io/openems/impl/protocol/modbus/ModbusBridge.java +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusBridge.java @@ -1,447 +1,386 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.modbus; - -import java.util.ArrayList; -import java.util.BitSet; -import java.util.List; -import java.util.StringJoiner; -import java.util.concurrent.atomic.AtomicBoolean; - -import com.ghgande.j2mod.modbus.ModbusException; -import com.ghgande.j2mod.modbus.io.ModbusTransaction; -import com.ghgande.j2mod.modbus.msg.ExceptionResponse; -import com.ghgande.j2mod.modbus.msg.ModbusResponse; -import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest; -import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse; -import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest; -import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse; -import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersRequest; -import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse; -import com.ghgande.j2mod.modbus.msg.WriteCoilRequest; -import com.ghgande.j2mod.modbus.msg.WriteMultipleCoilsRequest; -import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest; -import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest; -import com.ghgande.j2mod.modbus.procimg.InputRegister; -import com.ghgande.j2mod.modbus.procimg.Register; -import com.ghgande.j2mod.modbus.util.BitVector; - -import io.openems.api.bridge.Bridge; -import io.openems.api.device.Device; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.api.exception.OpenemsModbusException; -import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; -import io.openems.impl.protocol.modbus.internal.range.ModbusRange; - -@ThingInfo(title = "Modbus") -public abstract class ModbusBridge extends Bridge { - - /* - * Fields - */ - protected volatile ModbusDevice[] modbusdevices = new ModbusDevice[0]; - private AtomicBoolean isWriteTriggered = new AtomicBoolean(false); - - /* - * Abstract Methods - */ - public abstract ModbusTransaction getTransaction() throws OpenemsModbusException; - - protected abstract void closeModbusConnection(); - - /* - * Static Methods - */ - static boolean[] toBooleanArray(byte[] bytes) { - BitSet bits = BitSet.valueOf(bytes); - boolean[] bools = new boolean[bytes.length * 8]; - for (int i = bits.nextSetBit(0); i != -1; i = bits.nextSetBit(i + 1)) { - bools[i] = true; - } - return bools; - } - - /* - * Methods - */ - @Override - public void triggerWrite() { - // set the Write-flag - isWriteTriggered.set(true); - // start "run()" again as fast as possible - triggerForceRun(); - } - - @Override - protected void forever() { - for (ModbusDevice modbusdevice : modbusdevices) { - // if Write-flag was set -> start writing for all Devices immediately - if (isWriteTriggered.get()) { - isWriteTriggered.set(false); - writeAllDevices(); - } - // Update this Device - try { - modbusdevice.update(this); - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - } - - private void writeAllDevices() { - for (ModbusDevice modbusdevice : modbusdevices) { - try { - modbusdevice.write(this); - } catch (OpenemsException e) { - log.error("Error while writing to ModbusDevice [" + modbusdevice.id() + "]: " + e.getMessage()); - } - } - } - - @Override - protected boolean initialize() { - /* - * Copy and cast devices to local modbusdevices array - */ - if (devices.isEmpty()) { - return false; - } - List modbusdevices = new ArrayList<>(); - for (Device device : devices) { - if (device instanceof ModbusDevice) { - modbusdevices.add((ModbusDevice) device); - } - } - ModbusDevice[] newModbusdevices = modbusdevices.stream().toArray(ModbusDevice[]::new); - if (newModbusdevices == null) { - newModbusdevices = new ModbusDevice[0]; - } - this.modbusdevices = newModbusdevices; - /* - * Create a new SerialConnection - */ - closeModbusConnection(); - return true; - } - - protected InputRegister[] query(int modbusUnitId, ModbusRange range) throws OpenemsModbusException { - if (range instanceof ModbusInputRegisterRange) { - return queryInputRegisters(modbusUnitId, range.getStartAddress(), range.getLength()); - } else { - return queryMultipleRegisters(modbusUnitId, range.getStartAddress(), range.getLength()); - } - } - - protected boolean[] queryCoil(int modbusUnitId, ModbusRange range) throws OpenemsModbusException { - return queryCoils(modbusUnitId, range.getStartAddress(), range.getLength()); - } - - private boolean[] queryCoils(int modbusUnitId, int startAddress, int length) throws OpenemsModbusException { - ModbusTransaction trans = getTransaction(); - ReadCoilsRequest req = new ReadCoilsRequest(startAddress, length); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new ReadCoilsRequest(startAddress, length); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e1) { - throw new OpenemsModbusException("Error on modbus query. " // - + "UnitId [" + modbusUnitId + "], Address [" + startAddress + "/0x" - + Integer.toHexString(startAddress) + "], Count [" + length + "]: " + e1.getMessage()); - } - } - ModbusResponse res = trans.getResponse(); - if (res instanceof ReadCoilsResponse) { - ReadCoilsResponse mres = (ReadCoilsResponse) res; - return toBooleanArray(mres.getCoils().getBytes()); - } else { - throw new OpenemsModbusException("Unable to read modbus response. " // - + "UnitId [" + modbusUnitId + "], Address [" + startAddress + "], Count [" + length + "]: " - + res.toString()); - } - } - - protected void write(int modbusUnitId, int address, List registers) throws OpenemsModbusException { - write(modbusUnitId, address, registers.toArray(new Register[registers.size()])); - } - - protected void write(int modbusUnitId, int address, Register... register) throws OpenemsModbusException { - ModbusResponse res; - try { - if (register.length == 0) { - return; - } else if (register.length == 1) { - res = writeSingleRegister(modbusUnitId, address, register[0]); - } else { - res = writeMultipleRegisters(modbusUnitId, address, register); - } - } catch (ModbusException | OpenemsModbusException e) { - throw new OpenemsModbusException("Error on modbus write. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Register [" + registersAsString(register) + "]: " + e.getMessage()); - } - if (res instanceof ExceptionResponse) { - throw new OpenemsModbusException("Error on modbus write response. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Register [" + registersAsString(register) + "]: " + res.toString()); - } - log.debug("Successful write. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Register [" + registersAsString(register) + "]"); - } - - protected void writeCoil(int modbusUnitId, int address, List coils) throws OpenemsModbusException { - writeCoil(modbusUnitId, address, coils.toArray(new Boolean[coils.size()])); - } - - protected void writeCoil(int modbusUnitId, int address, Boolean... values) throws OpenemsModbusException { - boolean[] coils = new boolean[values.length]; - for (int i = 0; i < values.length; i++) { - coils[i] = values[i]; - } - ModbusResponse res; - try { - if (coils.length == 0) { - return; - } else if (coils.length == 1) { - res = writeSingleCoil(modbusUnitId, address, coils[0]); - } else { - res = writeMultipleCoils(modbusUnitId, address, coils); - } - } catch (ModbusException | OpenemsModbusException e) { - throw new OpenemsModbusException("Error on modbus write. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Coil [" + coilsAsString(coils) + "]: " + e.getMessage()); - } - if (res instanceof ExceptionResponse) { - throw new OpenemsModbusException("Error on modbus write response. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Coil [" + coilsAsString(coils) + "]: " + res.toString()); - } - log.debug("Successful write. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Coil [" + coilsAsString(coils) + "]"); - } - - private String coilsAsString(boolean[] coils) { - StringJoiner joiner = new StringJoiner(","); - for (boolean coil : coils) { - joiner.add(String.valueOf(coil)); - } - return joiner.toString(); - } - - private ModbusResponse writeMultipleCoils(int modbusUnitId, int address, boolean[] coils) - throws OpenemsModbusException, ModbusException { - ModbusTransaction trans = getTransaction(); - BitVector vec = new BitVector(coils.length); - for (int i = 0; i < coils.length; i++) { - vec.setBit(i, coils[i]); - } - WriteMultipleCoilsRequest req = new WriteMultipleCoilsRequest(address, vec); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new WriteMultipleCoilsRequest(address, vec); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - trans.execute(); - } - return trans.getResponse(); - } - - private ModbusResponse writeSingleCoil(int modbusUnitId, int address, boolean b) - throws OpenemsModbusException, ModbusException { - ModbusTransaction trans = getTransaction(); - WriteCoilRequest req = new WriteCoilRequest(address, b); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new WriteCoilRequest(address, b); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - trans.execute(); - } - return trans.getResponse(); - } - - private ModbusResponse writeSingleRegister(int modbusUnitId, int address, Register register) - throws ModbusException, OpenemsModbusException { - ModbusTransaction trans = getTransaction(); - WriteSingleRegisterRequest req = new WriteSingleRegisterRequest(address, register); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new WriteSingleRegisterRequest(address, register); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - trans.execute(); - } - return trans.getResponse(); - } - - /** - * Write Multiple Registers (function code 16) - * - * @param modbusUnitId - * @param address - * @param register - * @throws OpenemsModbusException - * @throws ModbusException - */ - private ModbusResponse writeMultipleRegisters(int modbusUnitId, int address, Register... register) - throws OpenemsModbusException, ModbusException { - ModbusTransaction trans = getTransaction(); - WriteMultipleRegistersRequest req = new WriteMultipleRegistersRequest(address, register); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new WriteMultipleRegistersRequest(address, register); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - trans.execute(); - } - return trans.getResponse(); - } - - private String registersAsString(Register... registers) { - StringJoiner joiner = new StringJoiner(","); - for (Register register : registers) { - joiner.add(String.valueOf(register.getValue())); - } - return joiner.toString(); - } - - /** - * Executes a query on the Modbus client - * - * @param modbusUnitId - * @param address - * @param count - * @return - * @throws OpenemsModbusException - */ - private Register[] queryMultipleRegisters(int modbusUnitId, int address, int count) throws OpenemsModbusException { - ModbusTransaction trans = getTransaction(); - ReadMultipleRegistersRequest req = new ReadMultipleRegistersRequest(address, count); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new ReadMultipleRegistersRequest(address, count); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e1) { - throw new OpenemsModbusException("Error on modbus query. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Count [" + count + "]: " + e1.getMessage()); - } - } - ModbusResponse res = trans.getResponse(); - if (res instanceof ReadMultipleRegistersResponse) { - ReadMultipleRegistersResponse mres = (ReadMultipleRegistersResponse) res; - return mres.getRegisters(); - } else { - throw new OpenemsModbusException("Unable to read modbus response. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "], Count [" + count + "]: " - + res.toString()); - } - } - - /** - * Executes a query on the Modbus client - * - * @param modbusUnitId - * @param address - * @param count - * @return - * @throws OpenemsModbusException - */ - private InputRegister[] queryInputRegisters(int modbusUnitId, int address, int count) - throws OpenemsModbusException { - ModbusTransaction trans = getTransaction(); - ReadInputRegistersRequest req = new ReadInputRegistersRequest(address, count); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e) { - // try again with new connection - closeModbusConnection(); - trans = getTransaction(); - req = new ReadInputRegistersRequest(address, count); - req.setUnitID(modbusUnitId); - trans.setRequest(req); - try { - trans.execute(); - } catch (ModbusException e1) { - throw new OpenemsModbusException("Error on modbus query. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) - + "], Count [" + count + "]: " + e1.getMessage()); - } - } - ModbusResponse res = trans.getResponse(); - if (res instanceof ReadInputRegistersResponse) { - ReadInputRegistersResponse mres = (ReadInputRegistersResponse) res; - return mres.getRegisters(); - } else { - throw new OpenemsModbusException("Unable to read modbus response. " // - + "UnitId [" + modbusUnitId + "], Address [" + address + "], Count [" + count + "]: " - + res.toString()); - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.modbus; + +import java.util.BitSet; +import java.util.List; +import java.util.StringJoiner; + +import com.ghgande.j2mod.modbus.ModbusException; +import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.msg.ExceptionResponse; +import com.ghgande.j2mod.modbus.msg.ModbusResponse; +import com.ghgande.j2mod.modbus.msg.ReadCoilsRequest; +import com.ghgande.j2mod.modbus.msg.ReadCoilsResponse; +import com.ghgande.j2mod.modbus.msg.ReadInputRegistersRequest; +import com.ghgande.j2mod.modbus.msg.ReadInputRegistersResponse; +import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersRequest; +import com.ghgande.j2mod.modbus.msg.ReadMultipleRegistersResponse; +import com.ghgande.j2mod.modbus.msg.WriteCoilRequest; +import com.ghgande.j2mod.modbus.msg.WriteMultipleCoilsRequest; +import com.ghgande.j2mod.modbus.msg.WriteMultipleRegistersRequest; +import com.ghgande.j2mod.modbus.msg.WriteSingleRegisterRequest; +import com.ghgande.j2mod.modbus.procimg.InputRegister; +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.util.BitVector; + +import io.openems.api.bridge.Bridge; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsModbusException; +import io.openems.impl.protocol.modbus.internal.range.ModbusInputRegisterRange; +import io.openems.impl.protocol.modbus.internal.range.ModbusRange; + +@ThingInfo(title = "Modbus") +public abstract class ModbusBridge extends Bridge { + + /* + * Abstract Methods + */ + public abstract ModbusTransaction getTransaction() throws OpenemsModbusException; + + protected abstract void closeModbusConnection(); + + /* + * Static Methods + */ + static boolean[] toBooleanArray(byte[] bytes) { + BitSet bits = BitSet.valueOf(bytes); + boolean[] bools = new boolean[bytes.length * 8]; + for (int i = bits.nextSetBit(0); i != -1; i = bits.nextSetBit(i + 1)) { + bools[i] = true; + } + return bools; + } + + /* + * Methods + */ + + @Override + protected boolean initialize() { + /* + * Create a new SerialConnection + */ + closeModbusConnection(); + return true; + } + + protected InputRegister[] query(int modbusUnitId, ModbusRange range) throws OpenemsModbusException { + if (range instanceof ModbusInputRegisterRange) { + return queryInputRegisters(modbusUnitId, range.getStartAddress(), range.getLength()); + } else { + return queryMultipleRegisters(modbusUnitId, range.getStartAddress(), range.getLength()); + } + } + + protected boolean[] queryCoil(int modbusUnitId, ModbusRange range) throws OpenemsModbusException { + return queryCoils(modbusUnitId, range.getStartAddress(), range.getLength()); + } + + private boolean[] queryCoils(int modbusUnitId, int startAddress, int length) throws OpenemsModbusException { + ModbusTransaction trans = getTransaction(); + ReadCoilsRequest req = new ReadCoilsRequest(startAddress, length); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new ReadCoilsRequest(startAddress, length); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e1) { + throw new OpenemsModbusException("Error on modbus query. " // + + "UnitId [" + modbusUnitId + "], Address [" + startAddress + "/0x" + + Integer.toHexString(startAddress) + "], Count [" + length + "]: " + e1.getMessage()); + } + } + ModbusResponse res = trans.getResponse(); + if (res instanceof ReadCoilsResponse) { + ReadCoilsResponse mres = (ReadCoilsResponse) res; + return toBooleanArray(mres.getCoils().getBytes()); + } else { + throw new OpenemsModbusException("Unable to read modbus response. " // + + "UnitId [" + modbusUnitId + "], Address [" + startAddress + "], Count [" + length + "]: " + + res.toString()); + } + } + + protected void write(int modbusUnitId, int address, List registers) throws OpenemsModbusException { + write(modbusUnitId, address, registers.toArray(new Register[registers.size()])); + } + + protected void write(int modbusUnitId, int address, Register... register) throws OpenemsModbusException { + ModbusResponse res; + try { + if (register.length == 0) { + return; + } else if (register.length == 1) { + res = writeSingleRegister(modbusUnitId, address, register[0]); + } else { + res = writeMultipleRegisters(modbusUnitId, address, register); + } + } catch (ModbusException | OpenemsModbusException e) { + throw new OpenemsModbusException("Error on modbus write. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Register [" + registersAsString(register) + "]: " + e.getMessage()); + } + if (res instanceof ExceptionResponse) { + throw new OpenemsModbusException("Error on modbus write response. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Register [" + registersAsString(register) + "]: " + res.toString()); + } + log.debug("Successful write. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Register [" + registersAsString(register) + "]"); + } + + protected void writeCoil(int modbusUnitId, int address, List coils) throws OpenemsModbusException { + writeCoil(modbusUnitId, address, coils.toArray(new Boolean[coils.size()])); + } + + protected void writeCoil(int modbusUnitId, int address, Boolean... values) throws OpenemsModbusException { + boolean[] coils = new boolean[values.length]; + for (int i = 0; i < values.length; i++) { + coils[i] = values[i]; + } + ModbusResponse res; + try { + if (coils.length == 0) { + return; + } else if (coils.length == 1) { + res = writeSingleCoil(modbusUnitId, address, coils[0]); + } else { + res = writeMultipleCoils(modbusUnitId, address, coils); + } + } catch (ModbusException | OpenemsModbusException e) { + throw new OpenemsModbusException("Error on modbus write. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Coil [" + coilsAsString(coils) + "]: " + e.getMessage()); + } + if (res instanceof ExceptionResponse) { + throw new OpenemsModbusException("Error on modbus write response. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Coil [" + coilsAsString(coils) + "]: " + res.toString()); + } + log.debug("Successful write. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Coil [" + coilsAsString(coils) + "]"); + } + + private String coilsAsString(boolean[] coils) { + StringJoiner joiner = new StringJoiner(","); + for (boolean coil : coils) { + joiner.add(String.valueOf(coil)); + } + return joiner.toString(); + } + + private ModbusResponse writeMultipleCoils(int modbusUnitId, int address, boolean[] coils) + throws OpenemsModbusException, ModbusException { + ModbusTransaction trans = getTransaction(); + BitVector vec = new BitVector(coils.length); + for (int i = 0; i < coils.length; i++) { + vec.setBit(i, coils[i]); + } + WriteMultipleCoilsRequest req = new WriteMultipleCoilsRequest(address, vec); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new WriteMultipleCoilsRequest(address, vec); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + trans.execute(); + } + return trans.getResponse(); + } + + private ModbusResponse writeSingleCoil(int modbusUnitId, int address, boolean b) + throws OpenemsModbusException, ModbusException { + ModbusTransaction trans = getTransaction(); + WriteCoilRequest req = new WriteCoilRequest(address, b); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new WriteCoilRequest(address, b); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + trans.execute(); + } + return trans.getResponse(); + } + + private ModbusResponse writeSingleRegister(int modbusUnitId, int address, Register register) + throws ModbusException, OpenemsModbusException { + ModbusTransaction trans = getTransaction(); + WriteSingleRegisterRequest req = new WriteSingleRegisterRequest(address, register); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new WriteSingleRegisterRequest(address, register); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + trans.execute(); + } + return trans.getResponse(); + } + + /** + * Write Multiple Registers (function code 16) + * + * @param modbusUnitId + * @param address + * @param register + * @throws OpenemsModbusException + * @throws ModbusException + */ + private ModbusResponse writeMultipleRegisters(int modbusUnitId, int address, Register... register) + throws OpenemsModbusException, ModbusException { + ModbusTransaction trans = getTransaction(); + WriteMultipleRegistersRequest req = new WriteMultipleRegistersRequest(address, register); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new WriteMultipleRegistersRequest(address, register); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + trans.execute(); + } + return trans.getResponse(); + } + + private String registersAsString(Register... registers) { + StringJoiner joiner = new StringJoiner(","); + for (Register register : registers) { + joiner.add(String.valueOf(register.getValue())); + } + return joiner.toString(); + } + + /** + * Executes a query on the Modbus client + * + * @param modbusUnitId + * @param address + * @param count + * @return + * @throws OpenemsModbusException + */ + private Register[] queryMultipleRegisters(int modbusUnitId, int address, int count) throws OpenemsModbusException { + ModbusTransaction trans = getTransaction(); + ReadMultipleRegistersRequest req = new ReadMultipleRegistersRequest(address, count); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new ReadMultipleRegistersRequest(address, count); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e1) { + throw new OpenemsModbusException("Error on modbus query. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Count [" + count + "]: " + e1.getMessage()); + } + } + ModbusResponse res = trans.getResponse(); + if (res instanceof ReadMultipleRegistersResponse) { + ReadMultipleRegistersResponse mres = (ReadMultipleRegistersResponse) res; + return mres.getRegisters(); + } else { + throw new OpenemsModbusException("Unable to read modbus response. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "], Count [" + count + "]: " + + res.toString()); + } + } + + /** + * Executes a query on the Modbus client + * + * @param modbusUnitId + * @param address + * @param count + * @return + * @throws OpenemsModbusException + */ + private InputRegister[] queryInputRegisters(int modbusUnitId, int address, int count) + throws OpenemsModbusException { + ModbusTransaction trans = getTransaction(); + ReadInputRegistersRequest req = new ReadInputRegistersRequest(address, count); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e) { + // try again with new connection + closeModbusConnection(); + trans = getTransaction(); + req = new ReadInputRegistersRequest(address, count); + req.setUnitID(modbusUnitId); + trans.setRequest(req); + try { + trans.execute(); + } catch (ModbusException e1) { + throw new OpenemsModbusException("Error on modbus query. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "/0x" + Integer.toHexString(address) + + "], Count [" + count + "]: " + e1.getMessage()); + } + } + ModbusResponse res = trans.getResponse(); + if (res instanceof ReadInputRegistersResponse) { + ReadInputRegistersResponse mres = (ReadInputRegistersResponse) res; + return mres.getRegisters(); + } else { + throw new OpenemsModbusException("Unable to read modbus response. " // + + "UnitId [" + modbusUnitId + "], Address [" + address + "], Count [" + count + "]: " + + res.toString()); + } + } +} diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusBridgeReadTask.java b/edge/src/io/openems/impl/protocol/modbus/ModbusBridgeReadTask.java new file mode 100644 index 00000000000..b4824fdab97 --- /dev/null +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusBridgeReadTask.java @@ -0,0 +1,99 @@ +package io.openems.impl.protocol.modbus; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ghgande.j2mod.modbus.procimg.InputRegister; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.exception.OpenemsModbusException; +import io.openems.impl.protocol.modbus.internal.CoilElement; +import io.openems.impl.protocol.modbus.internal.DoublewordElement; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.WordElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusCoilRange; +import io.openems.impl.protocol.modbus.internal.range.ModbusRange; + +public class ModbusBridgeReadTask extends BridgeReadTask { + + private int modbusUnitId; + private ModbusBridge modbusBridge; + private ModbusRange range; + protected final Logger log; + + public ModbusBridgeReadTask(int modbusUnitId, ModbusBridge bridge, ModbusRange range) { + log = LoggerFactory.getLogger(this.getClass()); + this.modbusUnitId = modbusUnitId; + this.modbusBridge = bridge; + this.range = range; + } + + public ModbusRange getRange() { + return range; + } + + @Override + protected void run() { + if (range instanceof ModbusCoilRange) { + try { + // Query using this Range + boolean[] coils = modbusBridge.queryCoil(modbusUnitId, range); + + // Fill channels + int position = 0; + for (ModbusElement element : range.getElements()) { + if (element instanceof CoilElement) { + // Use _one_ Register for the element + ((CoilElement) element).setValue(coils[position]); + } else { + log.warn("Element type not defined: Element [" + element.getAddress() + "], Bridge [" + + modbusBridge + "], Coil [" + range.getStartAddress() + "]: "); + } + position += element.getLength(); + } + } catch (OpenemsModbusException e) { + log.error("Modbus query failed. " // + + "Bridge [" + modbusBridge.id() + "], Coil [" + range.getStartAddress() + "]: {}", + e.getMessage()); + modbusBridge.triggerInitialize(); + // set all elements to invalid + for (ModbusElement element : range.getElements()) { + element.setValue(null); + } + } + } else { + try { + // Query using this Range + InputRegister[] registers = modbusBridge.query(modbusUnitId, range); + + // Fill channels + int position = 0; + for (ModbusElement element : range.getElements()) { + if (element instanceof DummyElement) { + // ignore dummy + } else if (element instanceof WordElement) { + // Use _one_ Register for the element + ((WordElement) element).setValue(registers[position]); + } else if (element instanceof DoublewordElement) { + // Use _two_ registers for the element + ((DoublewordElement) element).setValue(registers[position], registers[position + 1]); + } else { + log.warn("Element type not defined: Element [" + element.getAddress() + "], Bridge [" + + modbusBridge + "], Range [" + range.getStartAddress() + "]: "); + } + position += element.getLength(); + } + } catch (OpenemsModbusException e) { + log.error("Modbus query failed. " // + + "Bridge [" + modbusBridge.id() + "], Range [" + range.getStartAddress() + "]: {}", + e.getMessage()); + modbusBridge.triggerInitialize(); + // set all elements to invalid + for (ModbusElement element : range.getElements()) { + element.setValue(null); + } + } + } + } + +} diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusBridgeWriteTask.java b/edge/src/io/openems/impl/protocol/modbus/ModbusBridgeWriteTask.java new file mode 100644 index 00000000000..a2a2ad6bc95 --- /dev/null +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusBridgeWriteTask.java @@ -0,0 +1,179 @@ +package io.openems.impl.protocol.modbus; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map.Entry; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ghgande.j2mod.modbus.procimg.Register; +import com.ghgande.j2mod.modbus.procimg.SimpleRegister; + +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.exception.OpenemsModbusException; +import io.openems.impl.protocol.modbus.internal.CoilElement; +import io.openems.impl.protocol.modbus.internal.DoublewordElement; +import io.openems.impl.protocol.modbus.internal.DummyElement; +import io.openems.impl.protocol.modbus.internal.WordElement; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRange; + +public class ModbusBridgeWriteTask extends BridgeWriteTask { + + private int modbusUnitId; + private ModbusBridge modbusBridge; + private WriteableModbusRange range; + protected final Logger log; + + public ModbusBridgeWriteTask(int modbusUnitId, ModbusBridge bridge, WriteableModbusRange range) { + log = LoggerFactory.getLogger(this.getClass()); + this.modbusUnitId = modbusUnitId; + this.modbusBridge = bridge; + this.range = range; + } + + @Override + protected void run() { + if (range instanceof WriteableModbusCoilRange) { + /* + * Build a list of start-addresses of elements without holes. The start-addresses map to a list + * of elements and their values (sorted by order of insertion using LinkedHashMap) + */ + LinkedHashMap> elements = new LinkedHashMap<>(); + Integer nextStartAddress = null; + LinkedHashMap values = new LinkedHashMap(); + for (ModbusElement element : range.getElements()) { + // Test if Channel is a dummy or writable and receive write value + boolean value = false; + if (element instanceof DummyElement) { + value = false; + } else if (element.getChannel() != null && element.getChannel() instanceof ModbusCoilWriteChannel) { + Optional valueOptional = ((ModbusCoilWriteChannel) element.getChannel()).writeShadowCopy(); + if (valueOptional.isPresent()) { + value = valueOptional.get(); + } else { + continue; + } + } else { + // no dummy, no WriteChannel, no value -> try next element + continue; + } + // Test if there is a "hole", if yes -> create new values map + if (nextStartAddress == null || nextStartAddress != element.getAddress()) { + values = new LinkedHashMap(); + elements.put(element.getAddress(), values); + } + // store this element and the value + values.put(element, value); + // store for next run + nextStartAddress = element.getAddress() + element.getLength(); + } + + /* + * Write all elements + */ + for (Entry> entry : elements.entrySet()) { + /* + * Get start address and all registers + */ + int address = entry.getKey(); + List coils = new ArrayList<>(); + for (Entry value : entry.getValue().entrySet()) { + ModbusElement element = value.getKey(); + if (element instanceof CoilElement) { + coils.add(value.getValue()); + } else { // DummyElement -> write false; + for (int i = 0; i < element.getLength(); i++) { + coils.add(false); + } + } + } + /* + * Write + */ + try { + modbusBridge.writeCoil(modbusUnitId, address, coils); + } catch (OpenemsModbusException e) { + log.error("Bridge [" + modbusBridge.id() + "], Range [" + range.getStartAddress() + "/0x" + + Integer.toHexString(range.getStartAddress()) + "]: " + e.getMessage()); + modbusBridge.triggerInitialize(); + } + } + } else { + /* + * Build a list of start-addresses of elements without holes. The start-addresses map to a list + * of elements and their values (sorted by order of insertion using LinkedHashMap) + */ + LinkedHashMap> elements = new LinkedHashMap<>(); + Integer nextStartAddress = null; + LinkedHashMap values = new LinkedHashMap(); + ; + for (ModbusElement element : range.getElements()) { + // Test if Channel is a dummy or writable and receive write value + long value = 0L; + if (element instanceof DummyElement) { + value = 0L; + } else if (element.getChannel() != null && element.getChannel() instanceof ModbusWriteLongChannel) { + Optional valueOptional = ((ModbusWriteLongChannel) element.getChannel()).writeShadowCopy(); + if (valueOptional.isPresent()) { + value = valueOptional.get(); + } else { + continue; + } + } else { + // no dummy, no WriteChannel, no value -> try next element + continue; + } + // Test if there is a "hole", if yes -> create new values map + if (nextStartAddress == null || nextStartAddress != element.getAddress()) { + values = new LinkedHashMap(); + elements.put(element.getAddress(), values); + } + // store this element and the value + values.put(element, value); + // store for next run + nextStartAddress = element.getAddress() + element.getLength(); + } + + /* + * Write all elements + */ + for (Entry> entry : elements.entrySet()) { + /* + * Get start address and all registers + */ + int address = entry.getKey(); + List registers = new ArrayList<>(); + for (Entry value : entry.getValue().entrySet()) { + ModbusElement element = value.getKey(); + if (element instanceof WordElement) { + registers.add( // + ((WordElement) element).toRegister(value.getValue())); + } else if (element instanceof DoublewordElement) { + registers.addAll(Arrays.asList( // + ((DoublewordElement) element).toRegisters(value.getValue()))); + } else { // DummyElement -> write 0; + for (int i = 0; i < element.getLength(); i++) { + registers.add(new SimpleRegister(value.getValue().intValue())); + } + } + } + /* + * Write + */ + try { + modbusBridge.write(modbusUnitId, address, registers); + } catch (OpenemsModbusException e) { + log.error("Bridge [" + modbusBridge.id() + "], Range [" + range.getStartAddress() + "/0x" + + Integer.toHexString(range.getStartAddress()) + "]: " + e.getMessage()); + modbusBridge.triggerInitialize(); + } + } + } + } + +} diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusDevice.java b/edge/src/io/openems/impl/protocol/modbus/ModbusDevice.java index faaa2b2548e..c861e03fc0e 100644 --- a/edge/src/io/openems/impl/protocol/modbus/ModbusDevice.java +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusDevice.java @@ -1,77 +1,59 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.modbus; - -import java.util.Optional; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.OpenemsException; -import io.openems.api.exception.ReflectionException; - -public abstract class ModbusDevice extends Device { - - /* - * Constructors - */ - public ModbusDevice() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Unit-ID", description = "Sets the Modbus unit-id.", type = Integer.class) - public final ConfigChannel modbusUnitId = new ConfigChannel("modbusUnitId", this); - - /* - * Methods - */ - protected final void update(ModbusBridge modbusBridge) throws ConfigException, ReflectionException { - int modbusUnitId = getModbusUnitId(); - for (DeviceNature nature : getDeviceNatures()) { - if (nature instanceof ModbusDeviceNature) { - ((ModbusDeviceNature) nature).update(modbusUnitId, modbusBridge); - } - } - } - - protected final void write(ModbusBridge modbusBridge) throws ConfigException, ReflectionException { - int modbusUnitId = getModbusUnitId(); - for (DeviceNature nature : getDeviceNatures()) { - if (nature instanceof ModbusDeviceNature) { - ((ModbusDeviceNature) nature).write(modbusUnitId, modbusBridge); - } - } - } - - private int getModbusUnitId() throws ConfigException { - Optional modbusUnitId = this.modbusUnitId.valueOptional(); - if (modbusUnitId.isPresent()) { - return modbusUnitId.get(); - } else { - throw new ConfigException("No ModbusUnitId configured for Device[" + this.id() + "]"); - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.modbus; + +import java.util.Optional; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.OpenemsException; + +public abstract class ModbusDevice extends Device { + + /* + * Constructors + */ + public ModbusDevice(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Unit-ID", description = "Sets the Modbus unit-id.", type = Integer.class) + public final ConfigChannel modbusUnitId = new ConfigChannel("modbusUnitId", this); + + /* + * Methods + */ + + protected int getModbusUnitId() throws ConfigException { + Optional modbusUnitId = this.modbusUnitId.valueOptional(); + if (modbusUnitId.isPresent()) { + return modbusUnitId.get(); + } else { + throw new ConfigException("No ModbusUnitId configured for Device[" + this.id() + "]"); + } + } +} diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusDeviceNature.java b/edge/src/io/openems/impl/protocol/modbus/ModbusDeviceNature.java index 81c226bfed0..c644515d859 100644 --- a/edge/src/io/openems/impl/protocol/modbus/ModbusDeviceNature.java +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusDeviceNature.java @@ -1,345 +1,166 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.modbus; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map.Entry; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.ghgande.j2mod.modbus.procimg.InputRegister; -import com.ghgande.j2mod.modbus.procimg.Register; -import com.ghgande.j2mod.modbus.procimg.SimpleRegister; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.OpenemsModbusException; -import io.openems.api.thing.ThingChannelsUpdatedListener; -import io.openems.impl.protocol.modbus.internal.CoilElement; -import io.openems.impl.protocol.modbus.internal.DoublewordElement; -import io.openems.impl.protocol.modbus.internal.DummyElement; -import io.openems.impl.protocol.modbus.internal.ModbusProtocol; -import io.openems.impl.protocol.modbus.internal.WordElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusCoilRange; -import io.openems.impl.protocol.modbus.internal.range.ModbusRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusCoilRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRange; - -public abstract class ModbusDeviceNature implements DeviceNature, ChannelChangeListener { - protected final Logger log; - private ModbusProtocol protocol = null; - private final String thingId; - private List listeners; - - public ModbusDeviceNature(String thingId) throws ConfigException { - this.thingId = thingId; - log = LoggerFactory.getLogger(this.getClass()); - // this.protocol = defineModbusProtocol(); - this.listeners = new ArrayList<>(); - } - - private ModbusProtocol getProtocol() { - if (protocol == null) { - createModbusProtocol(); - } - return this.protocol; - } - - @Override - public void addListener(ThingChannelsUpdatedListener listener) { - this.listeners.add(listener); - } - - @Override - public void removeListener(ThingChannelsUpdatedListener listener) { - this.listeners.remove(listener); - } - - @Override - public void init() { - DeviceNature.super.init(); - createModbusProtocol(); - } - - @Override - public String id() { - return thingId; - } - - @Override - /** - * Sets a Channel as required. The Range with this Channel will be added to ModbusProtocol.RequiredRanges. - */ - public void setAsRequired(Channel channel) { - getProtocol().setAsRequired(channel); - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - createModbusProtocol(); - } - - private void createModbusProtocol() { - try { - this.protocol = defineModbusProtocol(); - for (ThingChannelsUpdatedListener listener : this.listeners) { - listener.thingChannelsUpdated(this); - } - } catch (ConfigException e) { - log.error("Failed to define modbus protocol!", e); - } catch (Throwable t) { - log.error("Some error occured while create ModbusProtocol", t); - } - } - - protected abstract ModbusProtocol defineModbusProtocol() throws ConfigException; - - protected void update(int unitId, ModbusBridge bridge) throws ConfigException { - /** - * Update required ranges - */ - for (ModbusRange range : getProtocol().getRequiredRanges()) { - update(unitId, bridge, range); - } - /** - * Update other ranges - */ - Optional range = getProtocol().getNextOtherRange(); - if (range.isPresent()) { - update(unitId, bridge, range.get()); - } - } - - protected void write(int modbusUnitId, ModbusBridge modbusBridge) throws ConfigException { - for (WriteableModbusRange range : getProtocol().getWritableRanges()) { - if (range instanceof WriteableModbusCoilRange) { - /* - * Build a list of start-addresses of elements without holes. The start-addresses map to a list - * of elements and their values (sorted by order of insertion using LinkedHashMap) - */ - LinkedHashMap> elements = new LinkedHashMap<>(); - Integer nextStartAddress = null; - LinkedHashMap values = new LinkedHashMap(); - for (ModbusElement element : range.getElements()) { - // Test if Channel is a dummy or writable and receive write value - boolean value = false; - if (element instanceof DummyElement) { - value = false; - } else if (element.getChannel() != null && element.getChannel() instanceof ModbusCoilWriteChannel) { - Optional valueOptional = ((ModbusCoilWriteChannel) element.getChannel()) - .writeShadowCopy(); - if (valueOptional.isPresent()) { - value = valueOptional.get(); - } else { - continue; - } - } else { - // no dummy, no WriteChannel, no value -> try next element - continue; - } - // Test if there is a "hole", if yes -> create new values map - if (nextStartAddress == null || nextStartAddress != element.getAddress()) { - values = new LinkedHashMap(); - elements.put(element.getAddress(), values); - } - // store this element and the value - values.put(element, value); - // store for next run - nextStartAddress = element.getAddress() + element.getLength(); - } - - /* - * Write all elements - */ - for (Entry> entry : elements.entrySet()) { - /* - * Get start address and all registers - */ - int address = entry.getKey(); - List coils = new ArrayList<>(); - for (Entry value : entry.getValue().entrySet()) { - ModbusElement element = value.getKey(); - if (element instanceof CoilElement) { - coils.add(value.getValue()); - } else { // DummyElement -> write false; - for (int i = 0; i < element.getLength(); i++) { - coils.add(false); - } - } - } - /* - * Write - */ - try { - modbusBridge.writeCoil(modbusUnitId, address, coils); - } catch (OpenemsModbusException e) { - log.error("Bridge [" + modbusBridge.id() + "], Range [" + range.getStartAddress() + "/0x" - + Integer.toHexString(range.getStartAddress()) + "]: " + e.getMessage()); - modbusBridge.triggerInitialize(); - } - } - } else { - /* - * Build a list of start-addresses of elements without holes. The start-addresses map to a list - * of elements and their values (sorted by order of insertion using LinkedHashMap) - */ - LinkedHashMap> elements = new LinkedHashMap<>(); - Integer nextStartAddress = null; - LinkedHashMap values = new LinkedHashMap(); - ; - for (ModbusElement element : range.getElements()) { - // Test if Channel is a dummy or writable and receive write value - long value = 0L; - if (element instanceof DummyElement) { - value = 0L; - } else if (element.getChannel() != null && element.getChannel() instanceof ModbusWriteLongChannel) { - Optional valueOptional = ((ModbusWriteLongChannel) element.getChannel()) - .writeShadowCopy(); - if (valueOptional.isPresent()) { - value = valueOptional.get(); - } else { - continue; - } - } else { - // no dummy, no WriteChannel, no value -> try next element - continue; - } - // Test if there is a "hole", if yes -> create new values map - if (nextStartAddress == null || nextStartAddress != element.getAddress()) { - values = new LinkedHashMap(); - elements.put(element.getAddress(), values); - } - // store this element and the value - values.put(element, value); - // store for next run - nextStartAddress = element.getAddress() + element.getLength(); - } - - /* - * Write all elements - */ - for (Entry> entry : elements.entrySet()) { - /* - * Get start address and all registers - */ - int address = entry.getKey(); - List registers = new ArrayList<>(); - for (Entry value : entry.getValue().entrySet()) { - ModbusElement element = value.getKey(); - if (element instanceof WordElement) { - registers.add( // - ((WordElement) element).toRegister(value.getValue())); - } else if (element instanceof DoublewordElement) { - registers.addAll(Arrays.asList( // - ((DoublewordElement) element).toRegisters(value.getValue()))); - } else { // DummyElement -> write 0; - for (int i = 0; i < element.getLength(); i++) { - registers.add(new SimpleRegister(value.getValue().intValue())); - } - } - } - /* - * Write - */ - try { - modbusBridge.write(modbusUnitId, address, registers); - } catch (OpenemsModbusException e) { - log.error("Bridge [" + modbusBridge.id() + "], Range [" + range.getStartAddress() + "/0x" - + Integer.toHexString(range.getStartAddress()) + "]: " + e.getMessage()); - modbusBridge.triggerInitialize(); - } - } - } - } - } - - private void update(int modbusUnitId, ModbusBridge modbusBridge, ModbusRange range) { - if (range instanceof ModbusCoilRange) { - try { - // Query using this Range - boolean[] coils = modbusBridge.queryCoil(modbusUnitId, range); - - // Fill channels - int position = 0; - for (ModbusElement element : range.getElements()) { - if (element instanceof CoilElement) { - // Use _one_ Register for the element - ((CoilElement) element).setValue(coils[position]); - } else { - log.warn("Element type not defined: Element [" + element.getAddress() + "], Bridge [" - + modbusBridge + "], Coil [" + range.getStartAddress() + "]: "); - } - position += element.getLength(); - } - } catch (OpenemsModbusException e) { - log.error( - "Modbus query failed. " // - + "Bridge [" + modbusBridge.id() + "], Coil [" + range.getStartAddress() + "]: {}", - e.getMessage()); - modbusBridge.triggerInitialize(); - // set all elements to invalid - for (ModbusElement element : range.getElements()) { - element.setValue(null); - } - } - } else { - try { - // Query using this Range - InputRegister[] registers = modbusBridge.query(modbusUnitId, range); - - // Fill channels - int position = 0; - for (ModbusElement element : range.getElements()) { - if (element instanceof DummyElement) { - // ignore dummy - } else if (element instanceof WordElement) { - // Use _one_ Register for the element - ((WordElement) element).setValue(registers[position]); - } else if (element instanceof DoublewordElement) { - // Use _two_ registers for the element - ((DoublewordElement) element).setValue(registers[position], registers[position + 1]); - } else { - log.warn("Element type not defined: Element [" + element.getAddress() + "], Bridge [" - + modbusBridge + "], Range [" + range.getStartAddress() + "]: "); - } - position += element.getLength(); - } - } catch (OpenemsModbusException e) { - log.error( - "Modbus query failed. " // - + "Bridge [" + modbusBridge.id() + "], Range [" + range.getStartAddress() + "]: {}", - e.getMessage()); - modbusBridge.triggerInitialize(); - // set all elements to invalid - for (ModbusElement element : range.getElements()) { - element.setValue(null); - } - } - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.modbus; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.exception.ConfigException; +import io.openems.api.thing.ThingChannelsUpdatedListener; +import io.openems.impl.protocol.modbus.internal.ModbusProtocol; +import io.openems.impl.protocol.modbus.internal.range.ModbusRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRange; + +public abstract class ModbusDeviceNature implements DeviceNature, ChannelChangeListener { + protected final Logger log; + private ModbusProtocol protocol = null; + private final String thingId; + private List listeners; + private List otherReadTasks; + private List readTasks; + private List writeTasks; + private Device parent; + + public ModbusDeviceNature(String thingId, Device parent) throws ConfigException { + this.parent = parent; + this.thingId = thingId; + log = LoggerFactory.getLogger(this.getClass()); + // this.protocol = defineModbusProtocol(); + this.listeners = new ArrayList<>(); + } + + private ModbusProtocol getProtocol() { + if (protocol == null) { + createModbusProtocol(); + } + return this.protocol; + } + + @Override + public Device getParent() { + return parent; + } + + @Override + public void addListener(ThingChannelsUpdatedListener listener) { + this.listeners.add(listener); + } + + @Override + public void removeListener(ThingChannelsUpdatedListener listener) { + this.listeners.remove(listener); + } + + @Override + public List getReadTasks() { + return otherReadTasks; + } + + @Override + public List getRequiredReadTasks() { + return readTasks; + } + + @Override + public List getWriteTasks() { + return writeTasks; + } + + @Override + public void init() { + createModbusProtocol(); + } + + @Override + public String id() { + return thingId; + } + + @Override + /** + * Sets a Channel as required. The Range with this Channel will be added to ModbusProtocol.RequiredRanges. + */ + public void setAsRequired(Channel channel) { + ModbusRange range = getProtocol().getRangeByChannel(channel); + Iterator i = otherReadTasks.iterator(); + while (i.hasNext()) { + BridgeReadTask task = i.next(); + if (((ModbusBridgeReadTask) task).getRange().equals(range)) { + this.readTasks.add(task); + i.remove(); + } + } + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + createModbusProtocol(); + } + + private void createModbusProtocol() { + try { + this.protocol = defineModbusProtocol(); + for (ThingChannelsUpdatedListener listener : this.listeners) { + listener.thingChannelsUpdated(this); + } + if (this.parent instanceof ModbusDevice) { + ModbusDevice parent = (ModbusDevice) this.parent; + if (parent.getBridge() instanceof ModbusBridge) { + ModbusBridge bridge = (ModbusBridge) parent.getBridge(); + // create WriteTasks + writeTasks = Collections.synchronizedList(new ArrayList<>()); + for (WriteableModbusRange range : protocol.getWritableRanges()) { + writeTasks.add(new ModbusBridgeWriteTask(parent.getModbusUnitId(), bridge, range)); + } + // create ReadTasks + readTasks = Collections.synchronizedList(new ArrayList<>()); + otherReadTasks = Collections.synchronizedList(new ArrayList<>()); + for (ModbusRange range : protocol.getReadRanges()) { + otherReadTasks.add(new ModbusBridgeReadTask(parent.getModbusUnitId(), bridge, range)); + } + } else { + log.error("Invalid Bridge Type. The bridge needs to inherit from ModbusBridge."); + } + } else { + log.error("Invalid Device Type. The Device needs to inherit from ModbusDevice"); + } + } catch (ConfigException e) { + log.error("Failed to define modbus protocol!", e); + } catch (Throwable t) { + log.error("Some error occured while create ModbusProtocol", t); + } + } + + protected abstract ModbusProtocol defineModbusProtocol() throws ConfigException; + +} diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusRtu.java b/edge/src/io/openems/impl/protocol/modbus/ModbusRtu.java index a3d3a3fe5fd..db28e65799c 100644 --- a/edge/src/io/openems/impl/protocol/modbus/ModbusRtu.java +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusRtu.java @@ -1,166 +1,157 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.modbus; - -import java.util.Optional; - -import com.ghgande.j2mod.modbus.Modbus; -import com.ghgande.j2mod.modbus.io.ModbusSerialTransaction; -import com.ghgande.j2mod.modbus.io.ModbusTransaction; -import com.ghgande.j2mod.modbus.net.SerialConnection; -import com.ghgande.j2mod.modbus.util.SerialParameters; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelUpdateListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsModbusException; - -@ThingInfo(title = "Modbus/RTU") -public class ModbusRtu extends ModbusBridge { - - private final ChannelUpdateListener channelUpdateListener = new ChannelUpdateListener() { - @Override - public void channelUpdated(Channel channel, Optional newValue) { - triggerInitialize(); - } - }; - - /* - * Config - */ - @ConfigInfo(title = "Baudrate", description = "Sets the baudrate (e.g. 9600).", type = Integer.class) - public final ConfigChannel baudrate = new ConfigChannel("baudrate", this) - .addUpdateListener(channelUpdateListener); - - @ConfigInfo(title = "Databits", description = "Sets the databits (e.g. 8).", type = Integer.class) - public final ConfigChannel databits = new ConfigChannel("databits", this) - .addUpdateListener(channelUpdateListener); - - @ConfigInfo(title = "Parity", description = "Sets the parity (e.g. 'even').", type = String.class) - public final ConfigChannel parity = new ConfigChannel("parity", this) - .addUpdateListener(channelUpdateListener); - - @ConfigInfo(title = "Serial interface", description = "Sets the serial interface (e.g. /dev/ttyUSB0).", type = String.class) - public final ConfigChannel serialinterface = new ConfigChannel("serialinterface", this) - .addUpdateListener(channelUpdateListener); - - @ConfigInfo(title = "Stopbits", description = "Sets the stopbits (e.g. 1).", type = Integer.class) - public final ConfigChannel stopbits = new ConfigChannel("stopbits", this) - .addUpdateListener(channelUpdateListener); - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - private Optional connection = Optional.empty(); - - /* - * Methods - */ - @Override - public void dispose() { - - } - - @Override - public ModbusTransaction getTransaction() throws OpenemsModbusException { - SerialConnection connection = getModbusConnection(); - ModbusSerialTransaction trans = new ModbusSerialTransaction(connection); - trans.setRetries(0); - return trans; - } - - @Override - public void addDevice(Device device) { - super.addDevice(device); - triggerInitialize(); - } - - @Override - public String toString() { - return "ModbusRtu [baudrate=" + baudrate + ", serialinterface=" + serialinterface + "]"; - } - - @Override - protected boolean initialize() { - if (!super.initialize()) { - return false; - } - try { - getModbusConnection(); - } catch (OpenemsModbusException e) { - log.error(e.getMessage()); - return false; - } - return true; - } - - @Override - protected void closeModbusConnection() { - if (connection.isPresent() && connection.get().isOpen()) { - try { - connection.get().close(); - } catch (NullPointerException e) { /* ignore */} - } - connection = Optional.empty(); - } - - private SerialConnection getModbusConnection() throws OpenemsModbusException { - if (!connection.isPresent()) { - if (!baudrate.valueOptional().isPresent() || !databits.valueOptional().isPresent() - || !parity.valueOptional().isPresent() || !serialinterface.valueOptional().isPresent() - || !stopbits.valueOptional().isPresent()) { - throw new OpenemsModbusException("Modbus-RTU is not configured completely"); - } - SerialParameters params = new SerialParameters(); - params.setPortName(serialinterface.valueOptional().get()); - params.setBaudRate(baudrate.valueOptional().get()); - params.setDatabits(databits.valueOptional().get()); - params.setParity(parity.valueOptional().get()); - params.setStopbits(stopbits.valueOptional().get()); - params.setEncoding(Modbus.SERIAL_ENCODING_RTU); - params.setEcho(false); - connection = Optional.of(new SerialConnection(params)); - } - if (!connection.get().isOpen()) { - try { - SerialConnection serialCon = connection.get(); - serialCon.open(); - if (cycleTime.valueOptional().isPresent()) { - serialCon.getModbusTransport().setTimeout(cycleTime.valueOptional().get()); - } - } catch (Exception e) { - throw new OpenemsModbusException("Unable to open Modbus-RTU connection: " + connection); - } - } - return connection.get(); - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.modbus; + +import java.util.Optional; + +import com.ghgande.j2mod.modbus.Modbus; +import com.ghgande.j2mod.modbus.io.ModbusSerialTransaction; +import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.net.SerialConnection; +import com.ghgande.j2mod.modbus.util.SerialParameters; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelUpdateListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsModbusException; + +@ThingInfo(title = "Modbus/RTU") +public class ModbusRtu extends ModbusBridge { + + private final ChannelUpdateListener channelUpdateListener = new ChannelUpdateListener() { + @Override + public void channelUpdated(Channel channel, Optional newValue) { + triggerInitialize(); + } + }; + + /* + * Config + */ + @ConfigInfo(title = "Baudrate", description = "Sets the baudrate (e.g. 9600).", type = Integer.class) + public final ConfigChannel baudrate = new ConfigChannel("baudrate", this) + .addUpdateListener(channelUpdateListener); + + @ConfigInfo(title = "Databits", description = "Sets the databits (e.g. 8).", type = Integer.class) + public final ConfigChannel databits = new ConfigChannel("databits", this) + .addUpdateListener(channelUpdateListener); + + @ConfigInfo(title = "Parity", description = "Sets the parity (e.g. 'even').", type = String.class) + public final ConfigChannel parity = new ConfigChannel("parity", this) + .addUpdateListener(channelUpdateListener); + + @ConfigInfo(title = "Serial interface", description = "Sets the serial interface (e.g. /dev/ttyUSB0).", type = String.class) + public final ConfigChannel serialinterface = new ConfigChannel("serialinterface", this) + .addUpdateListener(channelUpdateListener); + + @ConfigInfo(title = "Stopbits", description = "Sets the stopbits (e.g. 1).", type = Integer.class) + public final ConfigChannel stopbits = new ConfigChannel("stopbits", this) + .addUpdateListener(channelUpdateListener); + + /* + * Fields + */ + private Optional connection = Optional.empty(); + + /* + * Methods + */ + @Override + public void dispose() { + + } + + @Override + public ModbusTransaction getTransaction() throws OpenemsModbusException { + SerialConnection connection = getModbusConnection(); + ModbusSerialTransaction trans = new ModbusSerialTransaction(connection); + trans.setRetries(0); + return trans; + } + + @Override + public void addDevice(Device device) { + super.addDevice(device); + triggerInitialize(); + } + + @Override + public String toString() { + return "ModbusRtu [baudrate=" + baudrate + ", serialinterface=" + serialinterface + "]"; + } + + @Override + protected boolean initialize() { + if (!super.initialize()) { + return false; + } + try { + getModbusConnection(); + } catch (OpenemsModbusException e) { + log.error(e.getMessage()); + return false; + } + return true; + } + + @Override + protected void closeModbusConnection() { + if (connection.isPresent() && connection.get().isOpen()) { + try { + connection.get().close(); + } catch (NullPointerException e) { /* ignore */} + } + connection = Optional.empty(); + } + + private SerialConnection getModbusConnection() throws OpenemsModbusException { + if (!connection.isPresent()) { + if (!baudrate.valueOptional().isPresent() || !databits.valueOptional().isPresent() + || !parity.valueOptional().isPresent() || !serialinterface.valueOptional().isPresent() + || !stopbits.valueOptional().isPresent()) { + throw new OpenemsModbusException("Modbus-RTU is not configured completely"); + } + SerialParameters params = new SerialParameters(); + params.setPortName(serialinterface.valueOptional().get()); + params.setBaudRate(baudrate.valueOptional().get()); + params.setDatabits(databits.valueOptional().get()); + params.setParity(parity.valueOptional().get()); + params.setStopbits(stopbits.valueOptional().get()); + params.setEncoding(Modbus.SERIAL_ENCODING_RTU); + params.setEcho(false); + connection = Optional.of(new SerialConnection(params)); + } + if (!connection.get().isOpen()) { + try { + SerialConnection serialCon = connection.get(); + serialCon.open(); + serialCon.getModbusTransport().setTimeout(1000); + } catch (Exception e) { + throw new OpenemsModbusException("Unable to open Modbus-RTU connection: " + connection); + } + } + return connection.get(); + } + +} diff --git a/edge/src/io/openems/impl/protocol/modbus/ModbusTcp.java b/edge/src/io/openems/impl/protocol/modbus/ModbusTcp.java index 6cd6285a628..62578ff823c 100644 --- a/edge/src/io/openems/impl/protocol/modbus/ModbusTcp.java +++ b/edge/src/io/openems/impl/protocol/modbus/ModbusTcp.java @@ -1,151 +1,142 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.modbus; - -import java.net.Inet4Address; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; -import com.ghgande.j2mod.modbus.io.ModbusTransaction; -import com.ghgande.j2mod.modbus.net.TCPMasterConnection; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelUpdateListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.OpenemsModbusException; - -@ThingInfo(title = "Modbus/TCP") -public class ModbusTcp extends ModbusBridge { - - private final ChannelUpdateListener channelUpdateListener = new ChannelUpdateListener() { - @Override - public void channelUpdated(Channel channel, Optional newValue) { - triggerInitialize(); - } - }; - - /* - * Config - */ - @ConfigInfo(title = "IP address", description = "Sets the IP address (e.g. 10.0.0.15).", type = Inet4Address.class) - public final ConfigChannel ip = new ConfigChannel("ip", this) - .addUpdateListener(channelUpdateListener); - - @ConfigInfo(title = "Port", description = "Sets the port (e.g. 502).", type = Integer.class, defaultValue = "502") - public final ConfigChannel port = new ConfigChannel("port", this) - .addUpdateListener(channelUpdateListener); - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - - private static Logger log = LoggerFactory.getLogger(ModbusTcp.class); - private Optional connection = Optional.empty(); - - /* - * Methods - */ - - @Override - public void dispose() { - - } - - @Override - public ModbusTransaction getTransaction() throws OpenemsModbusException { - TCPMasterConnection connection = getModbusConnection(); - ModbusTCPTransaction trans = new ModbusTCPTransaction(connection); - return trans; - } - - @Override - public void addDevice(Device device) { - super.addDevice(device); - triggerInitialize(); - } - - @Override - public String toString() { - return "ModbusTcp [ip=" + ip + "]"; - } - - @Override - protected boolean initialize() { - if (!super.initialize()) { - return false; - } - try { - getModbusConnection(); - } catch (OpenemsModbusException e) { - log.error(e.getMessage()); - return false; - } - return true; - } - - @Override - protected void closeModbusConnection() { - if (connection.isPresent() && connection.get().isConnected()) { - try { - connection.get().close(); - } catch (NullPointerException e) { /* ignore */} - } - connection = Optional.empty(); - } - - private TCPMasterConnection getModbusConnection() throws OpenemsModbusException { - if (!connection.isPresent()) { - try { - TCPMasterConnection tcpCon = new TCPMasterConnection(ip.value()); - tcpCon.setPort(port.valueOptional().orElse(502)); - connection = Optional.of(tcpCon); - } catch (InvalidValueException e) { - throw new OpenemsModbusException("Modbus-TCP is not configured completely"); - } - } - if (!connection.get().isConnected()) { - try { - TCPMasterConnection tcpCon = connection.get(); - tcpCon.connect(); - if (cycleTime.valueOptional().isPresent()) { - tcpCon.getModbusTransport().setTimeout(cycleTime.valueOptional().get()); - } - } catch (Exception e) { - throw new OpenemsModbusException("Unable to open Modbus-TCP connection: " + ip.valueOptional().get()); - } - } - return connection.get(); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.modbus; + +import java.net.Inet4Address; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.ghgande.j2mod.modbus.io.ModbusTCPTransaction; +import com.ghgande.j2mod.modbus.io.ModbusTransaction; +import com.ghgande.j2mod.modbus.net.TCPMasterConnection; + +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelUpdateListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.OpenemsModbusException; + +@ThingInfo(title = "Modbus/TCP") +public class ModbusTcp extends ModbusBridge { + + private final ChannelUpdateListener channelUpdateListener = new ChannelUpdateListener() { + @Override + public void channelUpdated(Channel channel, Optional newValue) { + triggerInitialize(); + } + }; + + /* + * Config + */ + @ConfigInfo(title = "IP address", description = "Sets the IP address (e.g. 10.0.0.15).", type = Inet4Address.class) + public final ConfigChannel ip = new ConfigChannel("ip", this) + .addUpdateListener(channelUpdateListener); + + @ConfigInfo(title = "Port", description = "Sets the port (e.g. 502).", type = Integer.class, defaultValue = "502") + public final ConfigChannel port = new ConfigChannel("port", this) + .addUpdateListener(channelUpdateListener); + + /* + * Fields + */ + + private static Logger log = LoggerFactory.getLogger(ModbusTcp.class); + private Optional connection = Optional.empty(); + + /* + * Methods + */ + + @Override + public void dispose() { + + } + + @Override + public ModbusTransaction getTransaction() throws OpenemsModbusException { + TCPMasterConnection connection = getModbusConnection(); + ModbusTCPTransaction trans = new ModbusTCPTransaction(connection); + return trans; + } + + @Override + public void addDevice(Device device) { + super.addDevice(device); + triggerInitialize(); + } + + @Override + public String toString() { + return "ModbusTcp [ip=" + ip + "]"; + } + + @Override + protected boolean initialize() { + if (!super.initialize()) { + return false; + } + try { + getModbusConnection(); + } catch (OpenemsModbusException e) { + log.error(e.getMessage()); + return false; + } + return true; + } + + @Override + protected void closeModbusConnection() { + if (connection.isPresent() && connection.get().isConnected()) { + try { + connection.get().close(); + } catch (NullPointerException e) { /* ignore */} + } + connection = Optional.empty(); + } + + private TCPMasterConnection getModbusConnection() throws OpenemsModbusException { + if (!connection.isPresent()) { + try { + TCPMasterConnection tcpCon = new TCPMasterConnection(ip.value()); + tcpCon.setPort(port.valueOptional().orElse(502)); + connection = Optional.of(tcpCon); + } catch (InvalidValueException e) { + throw new OpenemsModbusException("Modbus-TCP is not configured completely"); + } + } + if (!connection.get().isConnected()) { + try { + TCPMasterConnection tcpCon = connection.get(); + tcpCon.connect(); + tcpCon.getModbusTransport().setTimeout(1000); + } catch (Exception e) { + throw new OpenemsModbusException("Unable to open Modbus-TCP connection: " + ip.valueOptional().get()); + } + } + return connection.get(); + } +} diff --git a/edge/src/io/openems/impl/protocol/modbus/internal/ModbusProtocol.java b/edge/src/io/openems/impl/protocol/modbus/internal/ModbusProtocol.java index 90921b9ff24..33bc5f373b0 100644 --- a/edge/src/io/openems/impl/protocol/modbus/internal/ModbusProtocol.java +++ b/edge/src/io/openems/impl/protocol/modbus/internal/ModbusProtocol.java @@ -1,132 +1,104 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.modbus.internal; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.Channel; -import io.openems.impl.protocol.modbus.ModbusChannel; -import io.openems.impl.protocol.modbus.ModbusElement; -import io.openems.impl.protocol.modbus.internal.range.ModbusRange; -import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRange; - -public class ModbusProtocol { - private static Logger log = LoggerFactory.getLogger(ModbusProtocol.class); - private final Map channelElementMap = new ConcurrentHashMap<>(); - private final Map otherRanges = new ConcurrentHashMap<>(); // key = startAddress - private final LinkedList otherRangesQueue = new LinkedList<>(); - // requiredRanges stays empty till someone calls "setAsRequired()" - private final Map requiredRanges = new ConcurrentHashMap<>(); // key = startAddress - private final Map writableRanges = new ConcurrentHashMap<>(); // key = - // startAddress - - public ModbusProtocol(ModbusRange... ranges) { - for (ModbusRange range : ranges) { - addRange(range); - } - } - - public void addRange(ModbusRange range) { - // check each range for plausibility - checkRange(range); - // fill writableRanges - if (range instanceof WriteableModbusRange) { - WriteableModbusRange writableRange = (WriteableModbusRange) range; - writableRanges.put(writableRange.getStartAddress(), writableRange); - } - // fill otherRanges Map - otherRanges.put(range.getStartAddress(), range); - // fill channelElementMap - for (ModbusElement element : range.getElements()) { - if (element.getChannel() != null) { - // ignore Elements without Channel (DummyChannels) - channelElementMap.put(element.getChannel(), element); - } - } - } - - public Optional getNextOtherRange() { - if (otherRangesQueue.isEmpty()) { - otherRangesQueue.addAll(otherRanges.keySet()); - } - Integer address = otherRangesQueue.poll(); - if (address == null) { - return Optional.empty(); - } - return Optional.ofNullable(otherRanges.get(address)); - } - - public Collection getOtherRanges() { - if (otherRanges.isEmpty()) { - return Collections.unmodifiableCollection(new ArrayList()); - } - return Collections.unmodifiableCollection(otherRanges.values()); - } - - public Collection getRequiredRanges() { - if (requiredRanges.isEmpty()) { - return Collections.unmodifiableCollection(new ArrayList()); - } - return Collections.unmodifiableCollection(requiredRanges.values()); - } - - public Collection getWritableRanges() { - if (writableRanges.isEmpty()) { - return Collections.unmodifiableCollection(new ArrayList()); - } - return Collections.unmodifiableCollection(writableRanges.values()); - } - - public void setAsRequired(Channel channel) { - if (channel instanceof ModbusChannel) { - ModbusRange range = channelElementMap.get(channel).getModbusRange(); - otherRanges.remove(range.getStartAddress()); - requiredRanges.put(range.getStartAddress(), range); - } - } - - /** - * Checks a {@link ModbusRange} for plausibility - * - * @param range - */ - private void checkRange(ModbusRange range) { - int address = range.getStartAddress(); - for (ModbusElement element : range.getElements()) { - if (element.getAddress() != address) { - log.error("Start address of Element is wrong. It is " + element.getAddress() + "/0x" - + Integer.toHexString(element.getAddress()) + ", should be " + address + "/0x" - + Integer.toHexString(address) + "."); - } - address += element.getLength(); - // TODO: check BitElements - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.modbus.internal; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.channel.Channel; +import io.openems.impl.protocol.modbus.ModbusElement; +import io.openems.impl.protocol.modbus.internal.range.ModbusRange; +import io.openems.impl.protocol.modbus.internal.range.WriteableModbusRange; + +public class ModbusProtocol { + private static Logger log = LoggerFactory.getLogger(ModbusProtocol.class); + private final Map channelElementMap = new ConcurrentHashMap<>(); + private final Map readRanges = new ConcurrentHashMap<>(); // key = startAddress + private final Map writableRanges = new ConcurrentHashMap<>(); // key = + // startAddress + + public ModbusProtocol(ModbusRange... ranges) { + for (ModbusRange range : ranges) { + addRange(range); + } + } + + public void addRange(ModbusRange range) { + // check each range for plausibility + checkRange(range); + // fill writableRanges + if (range instanceof WriteableModbusRange) { + WriteableModbusRange writableRange = (WriteableModbusRange) range; + writableRanges.put(writableRange.getStartAddress(), writableRange); + } + // fill readRanges Map + readRanges.put(range.getStartAddress(), range); + // fill channelElementMap + for (ModbusElement element : range.getElements()) { + if (element.getChannel() != null) { + // ignore Elements without Channel (DummyChannels) + channelElementMap.put(element.getChannel(), element); + } + } + } + + public Collection getReadRanges() { + if (readRanges.isEmpty()) { + return Collections.unmodifiableCollection(new ArrayList()); + } + return Collections.unmodifiableCollection(readRanges.values()); + } + + public Collection getWritableRanges() { + if (writableRanges.isEmpty()) { + return Collections.unmodifiableCollection(new ArrayList()); + } + return Collections.unmodifiableCollection(writableRanges.values()); + } + + public ModbusRange getRangeByChannel(Channel channel) { + return channelElementMap.get(channel).getModbusRange(); + } + + /** + * Checks a {@link ModbusRange} for plausibility + * + * @param range + */ + private void checkRange(ModbusRange range) { + int address = range.getStartAddress(); + for (ModbusElement element : range.getElements()) { + if (element.getAddress() != address) { + log.error("Start address of Element is wrong. It is " + element.getAddress() + "/0x" + + Integer.toHexString(element.getAddress()) + ", should be " + address + "/0x" + + Integer.toHexString(address) + "."); + } + address += element.getLength(); + // TODO: check BitElements + } + } +} diff --git a/edge/src/io/openems/impl/protocol/simulator/SimulatorBridge.java b/edge/src/io/openems/impl/protocol/simulator/SimulatorBridge.java index f684dccd73b..a4e6b19b571 100644 --- a/edge/src/io/openems/impl/protocol/simulator/SimulatorBridge.java +++ b/edge/src/io/openems/impl/protocol/simulator/SimulatorBridge.java @@ -1,96 +1,52 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.simulator; - -import java.util.ArrayList; -import java.util.List; - -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.doc.ThingInfo; - -@ThingInfo(title = "Simulator") -public class SimulatorBridge extends Bridge { - protected volatile SimulatorDevice[] simulatordevices = new SimulatorDevice[0]; - - /* - * Config - */ - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Methods - */ - @Override - public void triggerWrite() { - // not implemented - } - - @Override - protected void dispose() { - // nothing to dispose - } - - @Override - protected void forever() { - for (SimulatorDevice device : simulatordevices) { - device.update(); - } - } - - @Override - protected boolean initialize() { - /* - * Wait a little bit, because the simulator is much faster than real hardware. - * Otherwise the system waits 10 seconds to call initialize() again. - */ - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - /* - * Copy and cast devices to local simulatordevices array - */ - if (devices.isEmpty()) { - return false; - } - List simulatordevices = new ArrayList<>(); - for (Device device : devices) { - if (device instanceof SimulatorDevice) { - simulatordevices.add((SimulatorDevice) device); - } - } - SimulatorDevice[] newSimulatordevices = simulatordevices.stream().toArray(SimulatorDevice[]::new); - if (newSimulatordevices == null) { - newSimulatordevices = new SimulatorDevice[0]; - } - this.simulatordevices = newSimulatordevices; - return true; - } - -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.simulator; + +import io.openems.api.bridge.Bridge; +import io.openems.api.doc.ThingInfo; + +@ThingInfo(title = "Simulator") +public class SimulatorBridge extends Bridge { + + /* + * Methods + */ + + @Override + protected void dispose() { + // nothing to dispose + } + + @Override + protected boolean initialize() { + /* + * Wait a little bit, because the simulator is much faster than real hardware. + * Otherwise the system waits 10 seconds to call initialize() again. + */ + // try { + // Thread.sleep(1000); + // } catch (InterruptedException e) { + // e.printStackTrace(); + // } + return true; + } + +} diff --git a/edge/src/io/openems/impl/protocol/simulator/SimulatorDevice.java b/edge/src/io/openems/impl/protocol/simulator/SimulatorDevice.java index 05ba8309962..145a350f665 100644 --- a/edge/src/io/openems/impl/protocol/simulator/SimulatorDevice.java +++ b/edge/src/io/openems/impl/protocol/simulator/SimulatorDevice.java @@ -1,48 +1,38 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.simulator; - -import io.openems.api.device.Device; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; - -@ThingInfo(title = "Simulator") -public abstract class SimulatorDevice extends Device { - - /* - * Constructors - */ - public SimulatorDevice() throws OpenemsException { - super(); - } - - /* - * Methods - */ - protected final void update() { - for (DeviceNature nature : getDeviceNatures()) { - if (nature instanceof SimulatorDeviceNature) { - ((SimulatorDeviceNature) nature).update(); - } - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.simulator; + +import io.openems.api.bridge.Bridge; +import io.openems.api.device.Device; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; + +@ThingInfo(title = "Simulator") +public abstract class SimulatorDevice extends Device { + + /* + * Constructors + */ + public SimulatorDevice(Bridge parent) throws OpenemsException { + super(parent); + } + +} diff --git a/edge/src/io/openems/impl/protocol/simulator/SimulatorDeviceNature.java b/edge/src/io/openems/impl/protocol/simulator/SimulatorDeviceNature.java index 603302bcc07..ebcffbbcdae 100644 --- a/edge/src/io/openems/impl/protocol/simulator/SimulatorDeviceNature.java +++ b/edge/src/io/openems/impl/protocol/simulator/SimulatorDeviceNature.java @@ -1,88 +1,124 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.simulator; - -import java.util.ArrayList; -import java.util.List; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.Channel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.thing.ThingChannelsUpdatedListener; - -@ThingInfo(title = "Simulator") -public abstract class SimulatorDeviceNature implements DeviceNature { - - /* - * Constructors - */ - public SimulatorDeviceNature(String thingId) throws ConfigException { - this.thingId = thingId; - log = LoggerFactory.getLogger(this.getClass()); - this.listeners = new ArrayList<>(); - } - - /* - * Fields - */ - protected final Logger log; - private final String thingId; - private List listeners; - - /* - * Methods - */ - @Override - public String id() { - return thingId; - } - - @Override - public void addListener(ThingChannelsUpdatedListener listener) { - this.listeners.add(listener); - } - - @Override - public void removeListener(ThingChannelsUpdatedListener listener) { - this.listeners.remove(listener); - } - - @Override - /** - * Sets a Channel as required. The Range with this Channel will be added to ModbusProtocol.RequiredRanges. - */ - public void setAsRequired(Channel channel) { - // ignore - } - - protected abstract void update(); - - @Override - public void init() { - for (ThingChannelsUpdatedListener listener : this.listeners) { - listener.thingChannelsUpdated(this); - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.simulator; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.thing.ThingChannelsUpdatedListener; + +@ThingInfo(title = "Simulator") +public abstract class SimulatorDeviceNature implements DeviceNature { + + private Device parent; + private List readTasks = new ArrayList<>(); + + /* + * Constructors + */ + public SimulatorDeviceNature(String thingId, Device parent) throws ConfigException { + this.thingId = thingId; + this.parent = parent; + log = LoggerFactory.getLogger(this.getClass()); + this.listeners = new ArrayList<>(); + readTasks.add(new BridgeReadTask() { + + @Override + protected void run() throws Exception { + SimulatorDeviceNature.this.update(); + } + }); + } + + /* + * Fields + */ + protected final Logger log; + private final String thingId; + private List listeners; + + /* + * Methods + */ + @Override + public String id() { + return thingId; + } + + @Override + public void addListener(ThingChannelsUpdatedListener listener) { + this.listeners.add(listener); + } + + @Override + public void removeListener(ThingChannelsUpdatedListener listener) { + this.listeners.remove(listener); + } + + @Override + public List getReadTasks() { + // not required + return null; + } + + @Override + public List getRequiredReadTasks() { + return readTasks; + } + + @Override + public List getWriteTasks() { + // not required + return null; + } + + @Override + public Device getParent() { + return this.parent; + } + + @Override + /** + * Sets a Channel as required. The Range with this Channel will be added to ModbusProtocol.RequiredRanges. + */ + public void setAsRequired(Channel channel) { + // ignore + } + + protected abstract void update(); + + @Override + public void init() { + for (ThingChannelsUpdatedListener listener : this.listeners) { + listener.thingChannelsUpdated(this); + } + } +} diff --git a/edge/src/io/openems/impl/protocol/studer/StuderBridge.java b/edge/src/io/openems/impl/protocol/studer/StuderBridge.java index 29b391a78fe..339267ae626 100644 --- a/edge/src/io/openems/impl/protocol/studer/StuderBridge.java +++ b/edge/src/io/openems/impl/protocol/studer/StuderBridge.java @@ -1,198 +1,143 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.studer; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelUpdateListener; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; -import io.openems.impl.protocol.studer.internal.Request; -import io.openems.impl.protocol.studer.internal.StuderConnection; -import io.openems.impl.protocol.studer.internal.request.ReadRequest; -import io.openems.impl.protocol.studer.internal.request.ReadResponse; - -@ThingInfo(title = "Studer") -public class StuderBridge extends Bridge implements ChannelUpdateListener { - - private final ChannelUpdateListener channelUpdateListener = new ChannelUpdateListener() { - @Override - public void channelUpdated(Channel channel, Optional newValue) { - triggerInitialize(); - } - }; - - /* - * Config - */ - @ConfigInfo(title = "Serial interface", description = "Sets the serial interface (e.g. /dev/ttyUSB0).", type = String.class) - public final ConfigChannel serialinterface = new ConfigChannel("serialinterface", this) - .addUpdateListener(channelUpdateListener); - - @ConfigInfo(title = "Source address", description = "Sets the source address (e.g. 1).", type = Integer.class, defaultValue = "1") - public final ConfigChannel address = new ConfigChannel("address", this); - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - private Optional connection = Optional.empty(); - protected volatile StuderDevice[] studerdevices = new StuderDevice[0]; - private AtomicBoolean isWriteTriggered = new AtomicBoolean(false); - - /* - * Methods - */ - @Override - public void dispose() { - this.closeConnection(); - } - - @Override - public void channelUpdated(Channel channel, Optional newValue) { - triggerInitialize(); - } - - @Override - public void triggerWrite() { - // set the Write-flag - isWriteTriggered.set(true); - // start "run()" again as fast as possible - triggerForceRun(); - } - - @Override - protected void forever() { - for (StuderDevice studerdevice : studerdevices) { - // if Write-flag was set -> start writing for all Devices immediately - if (isWriteTriggered.get()) { - isWriteTriggered.set(false); - writeAllDevices(); - } - // Update this Device - try { - studerdevice.update(this); - } catch (OpenemsException e) { - log.error(e.getMessage()); - } - } - } - - /** - * Starts writing at all StuderDevices - */ - private void writeAllDevices() { - for (StuderDevice studerdevice : studerdevices) { - try { - studerdevice.write(this); - } catch (OpenemsException e) { - log.error("Error while writing to StuderDevice [" + studerdevice.id() + "]: " + e.getMessage()); - } - } - } - - /** - * Gets a serial connection. Tries to establish one if there is none yet. - * - * @return - * @throws OpenemsException - */ - public StuderConnection getConnection() throws OpenemsException { - if (!connection.isPresent()) { - if (!serialinterface.valueOptional().isPresent()) { - throw new OpenemsException("StuderBridge is not configured completely"); - } - connection = Optional.of(new StuderConnection(serialinterface.valueOptional().get())); - } - StuderConnection conn = connection.get(); - if (!conn.isConnected()) { - conn.connect(); - } - return conn; - } - - /** - * Closes a serial connection if existing. - */ - protected void closeConnection() { - if (connection.isPresent() && connection.get().isConnected()) { - try { - connection.get().disconnect(); - } catch (NullPointerException e) { /* ignore */} - } - connection = Optional.empty(); - } - - @Override - protected boolean initialize() { - /* - * Copy and cast devices to local studerdevices array - */ - if (devices.isEmpty()) { - return false; - } - List studerdevices = new ArrayList<>(); - for (Device device : devices) { - if (device instanceof StuderDevice) { - studerdevices.add((StuderDevice) device); - } - } - StuderDevice[] newStuderdevices = studerdevices.stream().toArray(StuderDevice[]::new); - if (newStuderdevices == null) { - newStuderdevices = new StuderDevice[0]; - } - this.studerdevices = newStuderdevices; - /* - * Create a new SerialConnection - */ - closeConnection(); - return true; - } - - /** - * Executes a Request. For a {@link ReadRequest} use getResponse() afterwards to get hold of its - * {@link ReadResponse}. - * - * @param request - * @throws IOException - * @throws OpenemsException - */ - public void execute(Request request) throws IOException, OpenemsException { - StuderConnection connection = getConnection(); - connection.setRequest(request); - connection.execute(); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.studer; + +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelUpdateListener; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; +import io.openems.impl.protocol.studer.internal.Request; +import io.openems.impl.protocol.studer.internal.StuderConnection; +import io.openems.impl.protocol.studer.internal.request.ReadRequest; +import io.openems.impl.protocol.studer.internal.request.ReadResponse; + +@ThingInfo(title = "Studer") +public class StuderBridge extends Bridge implements ChannelUpdateListener { + + private final ChannelUpdateListener channelUpdateListener = new ChannelUpdateListener() { + @Override + public void channelUpdated(Channel channel, Optional newValue) { + triggerInitialize(); + } + }; + + /* + * Config + */ + @ConfigInfo(title = "Serial interface", description = "Sets the serial interface (e.g. /dev/ttyUSB0).", type = String.class) + public final ConfigChannel serialinterface = new ConfigChannel("serialinterface", this) + .addUpdateListener(channelUpdateListener); + + @ConfigInfo(title = "Source address", description = "Sets the source address (e.g. 1).", type = Integer.class, defaultValue = "1") + public final ConfigChannel address = new ConfigChannel("address", this); + + /* + * Fields + */ + private Optional connection = Optional.empty(); + private AtomicBoolean isWriteTriggered = new AtomicBoolean(false); + + /* + * Methods + */ + @Override + public void dispose() { + this.closeConnection(); + } + + @Override + public void channelUpdated(Channel channel, Optional newValue) { + triggerInitialize(); + } + + /** + * Gets a serial connection. Tries to establish one if there is none yet. + * + * @return + * @throws OpenemsException + */ + public StuderConnection getConnection() throws OpenemsException { + if (!connection.isPresent()) { + if (!serialinterface.valueOptional().isPresent()) { + throw new OpenemsException("StuderBridge is not configured completely"); + } + connection = Optional.of(new StuderConnection(serialinterface.valueOptional().get())); + } + StuderConnection conn = connection.get(); + if (!conn.isConnected()) { + conn.connect(); + } + return conn; + } + + /** + * Closes a serial connection if existing. + */ + protected void closeConnection() { + if (connection.isPresent() && connection.get().isConnected()) { + try { + connection.get().disconnect(); + } catch (NullPointerException e) { /* ignore */} + } + connection = Optional.empty(); + } + + @Override + protected boolean initialize() { + /* + * Create a new SerialConnection + */ + closeConnection(); + return true; + } + + /** + * Executes a Request. For a {@link ReadRequest} use getResponse() afterwards to get hold of its + * {@link ReadResponse}. + * + * @param request + * @throws IOException + * @throws OpenemsException + */ + public void execute(Request request) throws IOException, OpenemsException { + StuderConnection connection = getConnection(); + connection.setRequest(request); + connection.execute(); + } + + protected int getSrcAddress() throws OpenemsException { + int srcAddress; + try { + srcAddress = this.address.value(); + } catch (Throwable e) { + e.printStackTrace(); + throw new OpenemsException("Unable to find srcAddress: " + e.getMessage()); + } + return srcAddress; + } +} diff --git a/edge/src/io/openems/impl/protocol/studer/StuderBridgeReadTask.java b/edge/src/io/openems/impl/protocol/studer/StuderBridgeReadTask.java new file mode 100644 index 00000000000..55fbf8a448d --- /dev/null +++ b/edge/src/io/openems/impl/protocol/studer/StuderBridgeReadTask.java @@ -0,0 +1,30 @@ +package io.openems.impl.protocol.studer; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.impl.protocol.studer.internal.property.ReadProperty; + +public class StuderBridgeReadTask extends BridgeReadTask { + + private final ReadProperty property; + private final int srcAddress; + private final int dstAddress; + private final StuderBridge bridge; + + public StuderBridgeReadTask(ReadProperty property, int srcAddress, int dstAddress, StuderBridge bridge) { + super(); + this.property = property; + this.srcAddress = srcAddress; + this.dstAddress = dstAddress; + this.bridge = bridge; + } + + public ReadProperty getProperty() { + return property; + } + + @Override + protected void run() throws Exception { + property.updateValue(srcAddress, dstAddress, bridge); + } + +} diff --git a/edge/src/io/openems/impl/protocol/studer/StuderBridgeWriteTask.java b/edge/src/io/openems/impl/protocol/studer/StuderBridgeWriteTask.java new file mode 100644 index 00000000000..07be9fe47a4 --- /dev/null +++ b/edge/src/io/openems/impl/protocol/studer/StuderBridgeWriteTask.java @@ -0,0 +1,26 @@ +package io.openems.impl.protocol.studer; + +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.impl.protocol.studer.internal.property.WriteProperty; + +public class StuderBridgeWriteTask extends BridgeWriteTask { + + private final WriteProperty property; + private final int srcAddress; + private final int dstAddress; + private final StuderBridge bridge; + + public StuderBridgeWriteTask(WriteProperty property, int srcAddress, int dstAddress, StuderBridge bridge) { + super(); + this.property = property; + this.srcAddress = srcAddress; + this.dstAddress = dstAddress; + this.bridge = bridge; + } + + @Override + protected void run() throws Exception { + property.writeValue(srcAddress, dstAddress, bridge); + } + +} diff --git a/edge/src/io/openems/impl/protocol/studer/StuderDevice.java b/edge/src/io/openems/impl/protocol/studer/StuderDevice.java index 6795189d12e..8e6755e710d 100644 --- a/edge/src/io/openems/impl/protocol/studer/StuderDevice.java +++ b/edge/src/io/openems/impl/protocol/studer/StuderDevice.java @@ -1,89 +1,59 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.studer; - -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; - -@ThingInfo(title = "Studer") -public abstract class StuderDevice extends Device { - - /* - * Constructors - */ - public StuderDevice() throws OpenemsException { - super(); - } - - /* - * Config - */ - @ConfigInfo(title = "Address", description = "Sets the device address (e.g. 701).", type = Integer.class) - public final ConfigChannel address = new ConfigChannel("address", this); - - /* - * Methods - */ - protected final void update(StuderBridge studerBridge) throws OpenemsException { - int srcAddress = getSrcAddress(studerBridge); - int dstAddress = getDstAddress(); - for (DeviceNature nature : getDeviceNatures()) { - if (nature instanceof StuderDeviceNature) { - ((StuderDeviceNature) nature).update(srcAddress, dstAddress, studerBridge); - } - } - } - - protected final void write(StuderBridge studerBridge) throws OpenemsException { - int srcAddress = getSrcAddress(studerBridge); - int dstAddress = getDstAddress(); - for (DeviceNature nature : getDeviceNatures()) { - if (nature instanceof StuderDeviceNature) { - ((StuderDeviceNature) nature).write(srcAddress, dstAddress, studerBridge); - } - } - } - - private int getSrcAddress(StuderBridge studerBridge) throws OpenemsException { - int srcAddress; - try { - srcAddress = studerBridge.address.value(); - } catch (Throwable e) { - e.printStackTrace(); - throw new OpenemsException("Unable to find srcAddress: " + e.getMessage()); - } - return srcAddress; - } - - private int getDstAddress() throws OpenemsException { - int dstAddress; - try { - dstAddress = this.address.value(); - } catch (Throwable e) { - throw new OpenemsException("Unable to find dstAddress: " + e.getMessage()); - } - return dstAddress; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.studer; + +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.device.Device; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; + +@ThingInfo(title = "Studer") +public abstract class StuderDevice extends Device { + + /* + * Constructors + */ + public StuderDevice(Bridge parent) throws OpenemsException { + super(parent); + } + + /* + * Config + */ + @ConfigInfo(title = "Address", description = "Sets the device address (e.g. 701).", type = Integer.class) + public final ConfigChannel address = new ConfigChannel("address", this); + + /* + * Methods + */ + + protected int getDstAddress() throws OpenemsException { + int dstAddress; + try { + dstAddress = this.address.value(); + } catch (Throwable e) { + throw new OpenemsException("Unable to find dstAddress: " + e.getMessage()); + } + return dstAddress; + } +} diff --git a/edge/src/io/openems/impl/protocol/studer/StuderDeviceNature.java b/edge/src/io/openems/impl/protocol/studer/StuderDeviceNature.java index b07235f5e34..d69e930e577 100644 --- a/edge/src/io/openems/impl/protocol/studer/StuderDeviceNature.java +++ b/edge/src/io/openems/impl/protocol/studer/StuderDeviceNature.java @@ -1,142 +1,183 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.studer; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ChannelChangeListener; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.OpenemsException; -import io.openems.api.thing.ThingChannelsUpdatedListener; -import io.openems.impl.protocol.studer.internal.StuderProtocol; -import io.openems.impl.protocol.studer.internal.property.ReadProperty; -import io.openems.impl.protocol.studer.internal.property.WriteProperty; - -@ThingInfo(title = "Studer") -public abstract class StuderDeviceNature implements DeviceNature, ChannelChangeListener { - - /* - * Constructors - */ - public StuderDeviceNature(String thingId) throws ConfigException { - this.thingId = thingId; - log = LoggerFactory.getLogger(this.getClass()); - this.listeners = new ArrayList<>(); - } - - /* - * Fields - */ - protected final Logger log; - private StuderProtocol protocol = null; - private final String thingId; - private List listeners; - - /* - * Abstract Methods - */ - protected abstract StuderProtocol defineStuderProtocol() throws ConfigException; - - /* - * Methods - */ - private StuderProtocol getProtocol() { - if (protocol == null) { - createStuderProtocol(); - } - return this.protocol; - } - - @Override - public void addListener(ThingChannelsUpdatedListener listener) { - this.listeners.add(listener); - } - - @Override - public void removeListener(ThingChannelsUpdatedListener listener) { - this.listeners.remove(listener); - } - - @Override - public void init() { - DeviceNature.super.init(); - createStuderProtocol(); - } - - @Override - public String id() { - return thingId; - } - - @Override - /** - * Sets a Channel as required. The Range with this Channel will be added to StuderProtocol.RequiredRanges. - */ - public void setAsRequired(Channel channel) { - getProtocol().setAsRequired(channel); - } - - @Override - public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { - createStuderProtocol(); - } - - private void createStuderProtocol() { - try { - this.protocol = defineStuderProtocol(); - for (ThingChannelsUpdatedListener listener : this.listeners) { - listener.thingChannelsUpdated(this); - } - } catch (ConfigException e) { - log.error("Failed to define modbus protocol!", e); - } - } - - protected void update(int srcAddress, int dstAddress, StuderBridge bridge) throws OpenemsException { - /** - * Update required properties - */ - for (ReadProperty property : getProtocol().getRequiredProperties()) { - property.updateValue(srcAddress, dstAddress, bridge); - } - /** - * Update other properties - */ - Optional> propertyOptional = getProtocol().getNextOtherProperty(); - if (propertyOptional.isPresent()) { - propertyOptional.get().updateValue(srcAddress, dstAddress, bridge); - } - } - - protected void write(int srcAddress, int dstAddress, StuderBridge bridge) throws OpenemsException { - for (WriteProperty property : getProtocol().getWritableProperties()) { - property.writeValue(srcAddress, dstAddress, bridge); - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.studer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ChannelChangeListener; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.OpenemsException; +import io.openems.api.thing.ThingChannelsUpdatedListener; +import io.openems.impl.protocol.studer.internal.StuderProtocol; +import io.openems.impl.protocol.studer.internal.property.ReadProperty; +import io.openems.impl.protocol.studer.internal.property.StuderProperty; +import io.openems.impl.protocol.studer.internal.property.WriteProperty; + +@ThingInfo(title = "Studer") +public abstract class StuderDeviceNature implements DeviceNature, ChannelChangeListener { + + private final Device parent; + + /* + * Constructors + */ + public StuderDeviceNature(String thingId, Device parent) throws ConfigException { + this.parent = parent; + this.thingId = thingId; + log = LoggerFactory.getLogger(this.getClass()); + this.listeners = new ArrayList<>(); + } + + /* + * Fields + */ + protected final Logger log; + private StuderProtocol protocol = null; + private final String thingId; + private List listeners; + private List readTasks; + private List requiredReadTasks; + private List writeTasks; + + /* + * Abstract Methods + */ + protected abstract StuderProtocol defineStuderProtocol() throws ConfigException; + + /* + * Methods + */ + private StuderProtocol getProtocol() { + if (protocol == null) { + createStuderProtocol(); + } + return this.protocol; + } + + @Override + public void addListener(ThingChannelsUpdatedListener listener) { + this.listeners.add(listener); + } + + @Override + public void removeListener(ThingChannelsUpdatedListener listener) { + this.listeners.remove(listener); + } + + @Override + public void init() { + DeviceNature.super.init(); + createStuderProtocol(); + } + + @Override + public String id() { + return thingId; + } + + @Override + public Device getParent() { + return parent; + } + + @Override + public List getReadTasks() { + return readTasks; + } + + @Override + public List getRequiredReadTasks() { + return requiredReadTasks; + } + + @Override + public List getWriteTasks() { + return writeTasks; + } + + @Override + /** + * Sets a Channel as required. The Range with this Channel will be added to StuderProtocol.RequiredRanges. + */ + public void setAsRequired(Channel channel) { + StuderProperty property = getProtocol().getPropertyByChannel(channel); + Iterator i = readTasks.iterator(); + while (i.hasNext()) { + BridgeReadTask task = i.next(); + if (((StuderBridgeReadTask) task).getProperty().equals(property)) { + this.requiredReadTasks.add(task); + i.remove(); + } + } + } + + @Override + public void channelChanged(Channel channel, Optional newValue, Optional oldValue) { + createStuderProtocol(); + } + + private void createStuderProtocol() { + try { + this.protocol = defineStuderProtocol(); + if (this.parent instanceof StuderDevice) { + StuderDevice parent = (StuderDevice) this.parent; + if (parent.getBridge() instanceof StuderBridge) { + StuderBridge bridge = (StuderBridge) parent.getBridge(); + // create WriteTasks + writeTasks = Collections.synchronizedList(new ArrayList<>()); + for (WriteProperty property : protocol.getWritableProperties()) { + writeTasks.add(new StuderBridgeWriteTask(property, bridge.getSrcAddress(), + parent.getDstAddress(), bridge)); + } + // create ReadTasks + readTasks = Collections.synchronizedList(new ArrayList<>()); + requiredReadTasks = Collections.synchronizedList(new ArrayList<>()); + for (ReadProperty property : protocol.getReadProperties()) { + readTasks.add(new StuderBridgeReadTask(property, bridge.getSrcAddress(), parent.getDstAddress(), + bridge)); + } + } else { + log.error("Invalid Bridge Type. The bridge needs to inherit from ModbusBridge."); + } + } else { + log.error("Invalid Device Type. The Device needs to inherit from ModbusDevice"); + } + for (ThingChannelsUpdatedListener listener : this.listeners) { + listener.thingChannelsUpdated(this); + } + } catch (OpenemsException e) { + log.error("Failed to define modbus protocol!", e); + } + } +} diff --git a/edge/src/io/openems/impl/protocol/studer/internal/StuderProtocol.java b/edge/src/io/openems/impl/protocol/studer/internal/StuderProtocol.java index 8e4f6195116..acd62d95b45 100644 --- a/edge/src/io/openems/impl/protocol/studer/internal/StuderProtocol.java +++ b/edge/src/io/openems/impl/protocol/studer/internal/StuderProtocol.java @@ -1,103 +1,105 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.studer.internal; - -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedList; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.Channel; -import io.openems.impl.protocol.studer.internal.object.StuderObject; -import io.openems.impl.protocol.studer.internal.property.ReadProperty; -import io.openems.impl.protocol.studer.internal.property.StuderProperty; -import io.openems.impl.protocol.studer.internal.property.WriteProperty; - -/** - * Holds the protocol definition of a Studer device - * - * @author stefan.feilmeier - */ -public class StuderProtocol { - - private static Logger log = LoggerFactory.getLogger(StuderProtocol.class); - - private final Map> channelPropertyMap = new ConcurrentHashMap<>(); - private final Set> requiredProperties = ConcurrentHashMap.newKeySet(); - // requiredProperties stays empty till someone calls "setAsRequired()" - private final Set> writableProperties = ConcurrentHashMap.newKeySet(); - private final Set> otherProperties = ConcurrentHashMap.newKeySet(); - private final LinkedList> otherPropertiesQueue = new LinkedList<>(); - - public StuderProtocol(StuderObject... objects) { - for (StuderObject object : objects) { - addObject(object); - } - } - - public void addObject(StuderObject object) { - for (StuderProperty property : object.getProperties()) { - // fillchannelPropertyMap - Channel channel = property.channel(); - if (channel != null) { - channelPropertyMap.put(channel, property); - } - if (property instanceof WriteProperty) { - // fill writableProperties - writableProperties.add((WriteProperty) property); - } - if (property instanceof ReadProperty) { - // fill otherProperties - otherProperties.add((ReadProperty) property); - } - } - } - - public Optional> getNextOtherProperty() { - if (otherPropertiesQueue.isEmpty()) { - otherPropertiesQueue.addAll(otherProperties); - } - ReadProperty property = otherPropertiesQueue.poll(); - return Optional.ofNullable(property); - } - - public Collection> getRequiredProperties() { - return Collections.unmodifiableSet(requiredProperties); - } - - public Collection> getWritableProperties() { - return Collections.unmodifiableSet(writableProperties); - } - - public void setAsRequired(Channel channel) { - StuderProperty property = channelPropertyMap.get(channel); - if (property != null && property instanceof ReadProperty) { - otherProperties.remove(property); - requiredProperties.add((ReadProperty) property); - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.studer.internal; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.channel.Channel; +import io.openems.impl.protocol.studer.internal.object.StuderObject; +import io.openems.impl.protocol.studer.internal.property.ReadProperty; +import io.openems.impl.protocol.studer.internal.property.StuderProperty; +import io.openems.impl.protocol.studer.internal.property.WriteProperty; + +/** + * Holds the protocol definition of a Studer device + * + * @author stefan.feilmeier + */ +public class StuderProtocol { + + private static Logger log = LoggerFactory.getLogger(StuderProtocol.class); + + private final Map> channelPropertyMap = new ConcurrentHashMap<>(); + private final Set> readProperties = ConcurrentHashMap.newKeySet(); + // requiredProperties stays empty till someone calls "setAsRequired()" + private final Set> writableProperties = ConcurrentHashMap.newKeySet(); + // private final Set> otherProperties = ConcurrentHashMap.newKeySet(); + // private final LinkedList> otherPropertiesQueue = new LinkedList<>(); + + public StuderProtocol(StuderObject... objects) { + for (StuderObject object : objects) { + addObject(object); + } + } + + public void addObject(StuderObject object) { + for (StuderProperty property : object.getProperties()) { + // fillchannelPropertyMap + Channel channel = property.channel(); + if (channel != null) { + channelPropertyMap.put(channel, property); + } + if (property instanceof WriteProperty) { + // fill writableProperties + writableProperties.add((WriteProperty) property); + } + if (property instanceof ReadProperty) { + // fill otherProperties + readProperties.add((ReadProperty) property); + } + } + } + + // public Optional> getNextOtherProperty() { + // if (otherPropertiesQueue.isEmpty()) { + // otherPropertiesQueue.addAll(otherProperties); + // } + // ReadProperty property = otherPropertiesQueue.poll(); + // return Optional.ofNullable(property); + // } + + public Collection> getReadProperties() { + return Collections.unmodifiableSet(readProperties); + } + + public Collection> getWritableProperties() { + return Collections.unmodifiableSet(writableProperties); + } + + public StuderProperty getPropertyByChannel(Channel channel) { + return channelPropertyMap.get(channel); + } + + // public void setAsRequired(Channel channel) { + // StuderProperty property = channelPropertyMap.get(channel); + // if (property != null && property instanceof ReadProperty) { + // otherProperties.remove(property); + // readProperties.add((ReadProperty) property); + // } + // } +} diff --git a/edge/src/io/openems/impl/protocol/system/SystemBridge.java b/edge/src/io/openems/impl/protocol/system/SystemBridge.java index 798e6cb6a80..34f55f82bf7 100644 --- a/edge/src/io/openems/impl/protocol/system/SystemBridge.java +++ b/edge/src/io/openems/impl/protocol/system/SystemBridge.java @@ -1,91 +1,48 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.system; - -import java.util.ArrayList; -import java.util.List; - -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.device.Device; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; - -@ThingInfo(title = "Operating system") -public class SystemBridge extends Bridge { - protected volatile SystemDevice[] systemdevices = new SystemDevice[0]; - - @Override - public void triggerWrite() { - // not implemented - } - - @Override - protected void dispose() { - // nothing to dispose - } - - @Override - protected void forever() { - for (SystemDevice device : systemdevices) { - device.update(); - } - } - - @Override - protected boolean initialize() { - /* - * Wait a little bit, because the simulator is much faster than real hardware. - * Otherwise the system waits 10 seconds to call initialize() again. - */ - try { - Thread.sleep(1000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - /* - * Copy and cast devices to local simulatordevices array - */ - if (devices.isEmpty()) { - return false; - } - List systemdevices = new ArrayList<>(); - for (Device device : devices) { - if (device instanceof SystemDevice) { - systemdevices.add((SystemDevice) device); - } - } - SystemDevice[] newSystemdevices = systemdevices.stream().toArray(SystemDevice[]::new); - if (newSystemdevices == null) { - newSystemdevices = new SystemDevice[0]; - } - this.systemdevices = newSystemdevices; - return true; - } - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(10000); - - @Override - @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class) - public ConfigChannel cycleTime() { - return cycleTime; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.system; + +import io.openems.api.bridge.Bridge; +import io.openems.api.doc.ThingInfo; + +@ThingInfo(title = "Operating system") +public class SystemBridge extends Bridge { + + @Override + protected void dispose() { + // nothing to dispose + } + + @Override + protected boolean initialize() { + /* + * Wait a little bit, because the simulator is much faster than real hardware. + * Otherwise the system waits 10 seconds to call initialize() again. + */ + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + return true; + } + +} diff --git a/edge/src/io/openems/impl/protocol/system/SystemDevice.java b/edge/src/io/openems/impl/protocol/system/SystemDevice.java index 973078fb976..b9a17a9e557 100644 --- a/edge/src/io/openems/impl/protocol/system/SystemDevice.java +++ b/edge/src/io/openems/impl/protocol/system/SystemDevice.java @@ -1,42 +1,35 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.system; - -import io.openems.api.device.Device; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.OpenemsException; - -@ThingInfo(title = "Operating system") -public abstract class SystemDevice extends Device { - - public SystemDevice() throws OpenemsException { - super(); - } - - protected final void update() { - for (DeviceNature nature : getDeviceNatures()) { - if (nature instanceof SystemDeviceNature) { - ((SystemDeviceNature) nature).update(); - } - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.system; + +import io.openems.api.bridge.Bridge; +import io.openems.api.device.Device; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.OpenemsException; + +@ThingInfo(title = "Operating system") +public abstract class SystemDevice extends Device { + + public SystemDevice(Bridge parent) throws OpenemsException { + super(parent); + } + +} diff --git a/edge/src/io/openems/impl/protocol/system/SystemDeviceNature.java b/edge/src/io/openems/impl/protocol/system/SystemDeviceNature.java index a361f140cdb..6b4ed895553 100644 --- a/edge/src/io/openems/impl/protocol/system/SystemDeviceNature.java +++ b/edge/src/io/openems/impl/protocol/system/SystemDeviceNature.java @@ -1,55 +1,94 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.protocol.system; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import io.openems.api.channel.Channel; -import io.openems.api.device.nature.DeviceNature; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; - -@ThingInfo(title = "Operating system") -public abstract class SystemDeviceNature implements DeviceNature { - protected final Logger log; - private final String thingId; - - public SystemDeviceNature(String thingId) throws ConfigException { - this.thingId = thingId; - log = LoggerFactory.getLogger(this.getClass()); - } - - @Override - public String id() { - return thingId; - } - - @Override - /** - * Sets a Channel as required. The Range with this Channel will be added to ModbusProtocol.RequiredRanges. - */ - public void setAsRequired(Channel channel) { - // ignore - } - - protected abstract void update(); -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.protocol.system; + +import java.util.ArrayList; +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.DeviceNature; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; + +@ThingInfo(title = "Operating system") +public abstract class SystemDeviceNature implements DeviceNature { + protected final Logger log; + private final String thingId; + private Device parent; + private List readRequiredTasks; + + public SystemDeviceNature(String thingId, Device parent) throws ConfigException { + this.thingId = thingId; + this.parent = parent; + log = LoggerFactory.getLogger(this.getClass()); + this.readRequiredTasks = new ArrayList<>(); + readRequiredTasks.add(new BridgeReadTask() { + + @Override + protected void run() { + SystemDeviceNature.this.update(); + } + }); + } + + @Override + public String id() { + return thingId; + } + + @Override + /** + * Sets a Channel as required. The Range with this Channel will be added to ModbusProtocol.RequiredRanges. + */ + public void setAsRequired(Channel channel) { + // ignore + } + + protected abstract void update(); + + @Override + public Device getParent() { + return parent; + } + + @Override + public List getReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequiredReadTasks() { + return readRequiredTasks; + } + + @Override + public List getWriteTasks() { + // TODO Auto-generated method stub + return null; + } +} diff --git a/edge/src/io/openems/impl/scheduler/SimpleScheduler.java b/edge/src/io/openems/impl/scheduler/SimpleScheduler.java index b24252ef59b..827766ec251 100644 --- a/edge/src/io/openems/impl/scheduler/SimpleScheduler.java +++ b/edge/src/io/openems/impl/scheduler/SimpleScheduler.java @@ -1,92 +1,74 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.scheduler; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import info.faljse.SDNotify.SDNotify; -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.controller.Controller; -import io.openems.api.doc.ThingInfo; -import io.openems.api.scheduler.Scheduler; -import io.openems.core.ThingRepository; - -@ThingInfo(title = "App-Planner") -public class SimpleScheduler extends Scheduler { - - /* - * Constructors - */ - public SimpleScheduler() { - thingRepository = ThingRepository.getInstance(); - } - - /* - * Config - */ - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - - private ThingRepository thingRepository; - - /* - * Methods - */ - @Override - protected void dispose() {} - - @Override - protected void forever() { - // kick the watchdog - SDNotify.sendWatchdog(); - - List controllers = new ArrayList<>(this.controllers.values()); - Collections.sort(controllers, (c1, c2) -> c2.priority.valueOptional().orElse(Integer.MIN_VALUE) - - c1.priority.valueOptional().orElse(Integer.MIN_VALUE)); - for (Controller controller : controllers) { - // TODO: check if WritableChannels can still be changed, before executing - controller.run(); - } - for (WriteChannel channel : thingRepository.getWriteChannels()) { - channel.shadowCopyAndReset(); - } - for (Bridge bridge : thingRepository.getBridges()) { - bridge.triggerWrite(); - } - } - - @Override - protected boolean initialize() { - return true; - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.scheduler; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import info.faljse.SDNotify.SDNotify; +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.WriteChannel; +import io.openems.api.controller.Controller; +import io.openems.api.doc.ThingInfo; +import io.openems.api.scheduler.Scheduler; + +@ThingInfo(title = "App-Planner") +public class SimpleScheduler extends Scheduler { + + /* + * Constructors + */ + public SimpleScheduler() { + + } + + /* + * Methods + */ + @Override + protected void dispose() {} + + @Override + protected void execute() { + // kick the watchdog + SDNotify.sendWatchdog(); + List controllers = new ArrayList<>(this.controllers.values()); + Collections.sort(controllers, (c1, c2) -> c2.priority.valueOptional().orElse(Integer.MIN_VALUE) + - c1.priority.valueOptional().orElse(Integer.MIN_VALUE)); + for (Controller controller : controllers) { + // TODO: check if WritableChannels can still be changed, before executing + controller.run(); + } + for (WriteChannel channel : thingRepository.getWriteChannels()) { + channel.shadowCopyAndReset(); + } + for (Bridge bridge : thingRepository.getBridges()) { + bridge.triggerWrite(); + } + } + + @Override + protected boolean initialize() { + return true; + } + +} diff --git a/edge/src/io/openems/impl/scheduler/channelthreshold/ChannelThresholdScheduler.java b/edge/src/io/openems/impl/scheduler/channelthreshold/ChannelThresholdScheduler.java index 15829a39ce8..7fff086cbc1 100644 --- a/edge/src/io/openems/impl/scheduler/channelthreshold/ChannelThresholdScheduler.java +++ b/edge/src/io/openems/impl/scheduler/channelthreshold/ChannelThresholdScheduler.java @@ -1,308 +1,299 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.scheduler.channelthreshold; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import info.faljse.SDNotify.SDNotify; -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.controller.Controller; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.ReflectionException; -import io.openems.api.scheduler.Scheduler; -import io.openems.core.ThingRepository; - -@ThingInfo(title = "Channel threshold app-planer", description = "app-planer with thresholds on configured channel to run different controllers by threshold on channel.") -public class ChannelThresholdScheduler extends Scheduler { - - public ChannelThresholdScheduler() { - thingRepository = ThingRepository.getInstance(); - } - - /* - * Fields - */ - private ThingRepository thingRepository; - private ReadChannel thresholdChannel; - private ControllerHysteresis activeHysteresis; - - /* - * Config - */ - - @ConfigInfo(title = "Always", description = "Sets the controllers that are always activated.", type = JsonArray.class) - public ConfigChannel always = new ConfigChannel<>("always", this); - - @ConfigInfo(title = "the ammount of time to wait till next run.", type = Integer.class) - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - /* - * "thresholds":[ - * { - * "threshold":20, - * "hysteresis": 5, - * "controller":["ctr1","ctr3"] - * }, - * { - * "threshold":40, - * "hysteresis": 7, - * "controller":["ctr2","ctr4"] - * }, - * { - * "threshold":60, - * "hysteresis": 3, - * "controller":["ctr5","ctr6"] - * }, - * ] - */ - - @ConfigInfo(title = "Configures the Controllers ", type = JsonArray.class) - public ConfigChannel thresholds = new ConfigChannel("thresholds", this) - .addChangeListener((channel, newValue, oldValue) -> { - try { - if (isInitialized()) { - loadThresholds(); - } - } catch (InvalidValueException e) { - log.error("Failed to load thresholds", e); - } - }); - - @SuppressWarnings("unchecked") - @ConfigInfo(title = "the address of the channel to switch the controllers by thresholds.", type = String.class) - public ConfigChannel thresholdChannelAddress = new ConfigChannel("thresholdChannelAddress", this) - .addChangeListener((channel, newValue, oldValue) -> { - Optional channelAddress = (Optional) newValue; - if (channelAddress.isPresent()) { - Optional ch = thingRepository.getChannelByAddress(channelAddress.get()); - if (ch.isPresent()) { - thresholdChannel = (ReadChannel) ch.get(); - } else { - log.error("Channel " + channelAddress.get() + " not found"); - } - } else { - log.error("'thresholdChannelAddress' is not configured!"); - } - }); - - @Override - @ConfigInfo(title = "Sets the duration of each cycle in milliseconds", type = Integer.class) - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Methods - */ - - @Override - protected void dispose() {} - - @Override - protected void forever() { - // kick the watchdog - SDNotify.sendWatchdog(); - - List controllers = getActiveControllers(); - controllers.addAll(getAlwaysController()); - Collections.sort(controllers, (c1, c2) -> c2.priority.valueOptional().orElse(Integer.MIN_VALUE) - - c1.priority.valueOptional().orElse(Integer.MIN_VALUE)); - for (Controller controller : controllers) { - controller.run(); - } - for (WriteChannel channel : thingRepository.getWriteChannels()) { - channel.shadowCopyAndReset(); - } - for (Bridge bridge : thingRepository.getBridges()) { - bridge.triggerWrite(); - } - } - - private List getActiveControllers() { - List controllers = new ArrayList<>(); - try { - if (activeHysteresis != null) { - if (!activeHysteresis.isBetween(thresholdChannel.value())) { - if (activeHysteresis.min > thresholdChannel.value()) { - // below - if (activeHysteresis.below != null) { - activeHysteresis = activeHysteresis.below; - } - } else { - // above - if (activeHysteresis.above != null) { - activeHysteresis = activeHysteresis.above; - } - } - } - controllers.addAll(activeHysteresis.controllers); - } - } catch (InvalidValueException e) { - log.error("Can't read thresholdChannel.", e); - } - return controllers; - } - - @Override - protected boolean initialize() { - try { - loadThresholds(); - } catch (InvalidValueException e) { - log.error("Failed to load thresholds", e); - } - return true; - } - - private Controller getController(String id) { - for (Controller c : getControllers()) { - if (c.id().equals(id)) { - return c; - } - } - return null; - } - - @Override - public synchronized void addController(Controller controller) throws ReflectionException, ConfigException { - super.addController(controller); - if (isInitialized()) { - try { - loadThresholds(); - } catch (InvalidValueException e) { - log.error("Failed to load thresholds", e); - } - } - } - - @Override - public synchronized void removeController(Controller controller) { - super.removeController(controller); - if (isInitialized()) { - try { - loadThresholds(); - } catch (InvalidValueException e) { - log.error("Failed to load thresholds", e); - } - } - } - - private void loadThresholds() throws InvalidValueException { - List thresholdCollection = new ArrayList<>(); - JsonArray thresholds = this.thresholds.value(); - for (JsonElement e : thresholds) { - if (e.isJsonObject()) { - JsonObject thresholdJson = e.getAsJsonObject(); - Threshold t = new Threshold(); - t.threshold = thresholdJson.get("threshold").getAsLong(); - t.hysteresis = thresholdJson.get("hysteresis").getAsLong(); - JsonArray controllers = thresholdJson.get("controller").getAsJsonArray(); - for (JsonElement ctr : controllers) { - Controller c = getController(ctr.getAsString()); - if (c != null) { - t.controllers.add(c); - } else { - log.error("can't find Controller '" + ctr.getAsString() + "'!"); - } - } - if (t.threshold != null) { - if (t.hysteresis != null) { - thresholdCollection.add(t); - } else { - log.error("no hysteresis defined for threshold [" + t.threshold + "]!"); - } - } else { - log.error("threshold of element [" + e + "] is not defined."); - } - } else { - log.error(e + " is no jsonobject!"); - } - } - Collections.sort(thresholdCollection, (c1, c2) -> c1.threshold.compareTo(c2.threshold)); - ControllerHysteresis lastHysteresis = null; - for (Threshold t : thresholdCollection) { - ControllerHysteresis ch = new ControllerHysteresis(); - ch.min = t.threshold; - if (lastHysteresis != null) { - lastHysteresis.max = t.threshold + t.hysteresis; - } - ch.below = lastHysteresis; - ch.controllers.addAll(t.controllers); - if (lastHysteresis != null) { - lastHysteresis.above = ch; - } - lastHysteresis = ch; - } - if (lastHysteresis != null) { - lastHysteresis.max = Long.MAX_VALUE; - } - if (thresholdChannel.valueOptional().isPresent() && lastHysteresis != null) { - while (lastHysteresis.below != null) { - if (lastHysteresis.isBetween(thresholdChannel.value())) { - break; - } - lastHysteresis = lastHysteresis.below; - } - } - activeHysteresis = lastHysteresis; - } - - private List getAlwaysController() { - List controller = new ArrayList<>(); - if (always.valueOptional().isPresent()) { - for (JsonElement element : always.valueOptional().get()) { - controller.add(controllers.get(element.getAsString())); - } - } - return controller; - } - - private class Threshold { - public Long threshold; - public Long hysteresis; - public List controllers = new ArrayList<>(); - } - - private class ControllerHysteresis { - public Long min; - public Long max; - public ControllerHysteresis above; - public ControllerHysteresis below; - public final List controllers = new ArrayList<>(); - - public boolean isBetween(long value) { - return min <= value && value <= max; - } - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.scheduler.channelthreshold; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import info.faljse.SDNotify.SDNotify; +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.controller.Controller; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.ReflectionException; +import io.openems.api.scheduler.Scheduler; +import io.openems.core.ThingRepository; + +@ThingInfo(title = "Channel threshold app-planer", description = "app-planer with thresholds on configured channel to run different controllers by threshold on channel.") +public class ChannelThresholdScheduler extends Scheduler { + + public ChannelThresholdScheduler() { + thingRepository = ThingRepository.getInstance(); + } + + /* + * Fields + */ + private ThingRepository thingRepository; + private ReadChannel thresholdChannel; + private ControllerHysteresis activeHysteresis; + + /* + * Config + */ + + @ConfigInfo(title = "Always", description = "Sets the controllers that are always activated.", type = JsonArray.class) + public ConfigChannel always = new ConfigChannel<>("always", this); + + /* + * "thresholds":[ + * { + * "threshold":20, + * "hysteresis": 5, + * "controller":["ctr1","ctr3"] + * }, + * { + * "threshold":40, + * "hysteresis": 7, + * "controller":["ctr2","ctr4"] + * }, + * { + * "threshold":60, + * "hysteresis": 3, + * "controller":["ctr5","ctr6"] + * }, + * ] + */ + + @ConfigInfo(title = "Configures the Controllers ", type = JsonArray.class) + public ConfigChannel thresholds = new ConfigChannel("thresholds", this) + .addChangeListener((channel, newValue, oldValue) -> { + try { + if (isInitialized()) { + loadThresholds(); + } + } catch (InvalidValueException e) { + log.error("Failed to load thresholds", e); + } + }); + + @SuppressWarnings("unchecked") + @ConfigInfo(title = "the address of the channel to switch the controllers by thresholds.", type = String.class) + public ConfigChannel thresholdChannelAddress = new ConfigChannel("thresholdChannelAddress", this) + .addChangeListener((channel, newValue, oldValue) -> { + Optional channelAddress = (Optional) newValue; + if (channelAddress.isPresent()) { + Optional ch = thingRepository.getChannelByAddress(channelAddress.get()); + if (ch.isPresent()) { + thresholdChannel = (ReadChannel) ch.get(); + } else { + log.error("Channel " + channelAddress.get() + " not found"); + } + } else { + log.error("'thresholdChannelAddress' is not configured!"); + } + }); + + /* + * Methods + */ + + @Override + protected void dispose() {} + + @Override + protected void execute() { + // kick the watchdog + SDNotify.sendWatchdog(); + + List controllers = getActiveControllers(); + controllers.addAll(getAlwaysController()); + Collections.sort(controllers, (c1, c2) -> c2.priority.valueOptional().orElse(Integer.MIN_VALUE) + - c1.priority.valueOptional().orElse(Integer.MIN_VALUE)); + for (Controller controller : controllers) { + controller.run(); + } + for (WriteChannel channel : thingRepository.getWriteChannels()) { + channel.shadowCopyAndReset(); + } + for (Bridge bridge : thingRepository.getBridges()) { + bridge.triggerWrite(); + } + } + + private List getActiveControllers() { + List controllers = new ArrayList<>(); + try { + if (activeHysteresis != null) { + if (!activeHysteresis.isBetween(thresholdChannel.value())) { + if (activeHysteresis.min > thresholdChannel.value()) { + // below + if (activeHysteresis.below != null) { + activeHysteresis = activeHysteresis.below; + } + } else { + // above + if (activeHysteresis.above != null) { + activeHysteresis = activeHysteresis.above; + } + } + } + controllers.addAll(activeHysteresis.controllers); + } + } catch (InvalidValueException e) { + log.error("Can't read thresholdChannel.", e); + } + return controllers; + } + + @Override + protected boolean initialize() { + try { + loadThresholds(); + } catch (InvalidValueException e) { + log.error("Failed to load thresholds", e); + } + return true; + } + + private Controller getController(String id) { + for (Controller c : getControllers()) { + if (c.id().equals(id)) { + return c; + } + } + return null; + } + + @Override + public synchronized void addController(Controller controller) throws ReflectionException, ConfigException { + super.addController(controller); + if (isInitialized()) { + try { + loadThresholds(); + } catch (InvalidValueException e) { + log.error("Failed to load thresholds", e); + } + } + } + + @Override + public synchronized void removeController(Controller controller) { + super.removeController(controller); + if (isInitialized()) { + try { + loadThresholds(); + } catch (InvalidValueException e) { + log.error("Failed to load thresholds", e); + } + } + } + + private void loadThresholds() throws InvalidValueException { + List thresholdCollection = new ArrayList<>(); + JsonArray thresholds = this.thresholds.value(); + for (JsonElement e : thresholds) { + if (e.isJsonObject()) { + JsonObject thresholdJson = e.getAsJsonObject(); + Threshold t = new Threshold(); + t.threshold = thresholdJson.get("threshold").getAsLong(); + t.hysteresis = thresholdJson.get("hysteresis").getAsLong(); + JsonArray controllers = thresholdJson.get("controller").getAsJsonArray(); + for (JsonElement ctr : controllers) { + Controller c = getController(ctr.getAsString()); + if (c != null) { + t.controllers.add(c); + } else { + log.error("can't find Controller '" + ctr.getAsString() + "'!"); + } + } + if (t.threshold != null) { + if (t.hysteresis != null) { + thresholdCollection.add(t); + } else { + log.error("no hysteresis defined for threshold [" + t.threshold + "]!"); + } + } else { + log.error("threshold of element [" + e + "] is not defined."); + } + } else { + log.error(e + " is no jsonobject!"); + } + } + Collections.sort(thresholdCollection, (c1, c2) -> c1.threshold.compareTo(c2.threshold)); + ControllerHysteresis lastHysteresis = null; + for (Threshold t : thresholdCollection) { + ControllerHysteresis ch = new ControllerHysteresis(); + ch.min = t.threshold; + if (lastHysteresis != null) { + lastHysteresis.max = t.threshold + t.hysteresis; + } + ch.below = lastHysteresis; + ch.controllers.addAll(t.controllers); + if (lastHysteresis != null) { + lastHysteresis.above = ch; + } + lastHysteresis = ch; + } + if (lastHysteresis != null) { + lastHysteresis.max = Long.MAX_VALUE; + } + if (thresholdChannel.valueOptional().isPresent() && lastHysteresis != null) { + while (lastHysteresis.below != null) { + if (lastHysteresis.isBetween(thresholdChannel.value())) { + break; + } + lastHysteresis = lastHysteresis.below; + } + } + activeHysteresis = lastHysteresis; + } + + private List getAlwaysController() { + List controller = new ArrayList<>(); + if (always.valueOptional().isPresent()) { + for (JsonElement element : always.valueOptional().get()) { + controller.add(controllers.get(element.getAsString())); + } + } + return controller; + } + + private class Threshold { + public Long threshold; + public Long hysteresis; + public List controllers = new ArrayList<>(); + } + + private class ControllerHysteresis { + public Long min; + public Long max; + public ControllerHysteresis above; + public ControllerHysteresis below; + public final List controllers = new ArrayList<>(); + + public boolean isBetween(long value) { + return min <= value && value <= max; + } + } +} diff --git a/edge/src/io/openems/impl/scheduler/time/WeekTimeScheduler.java b/edge/src/io/openems/impl/scheduler/time/WeekTimeScheduler.java index a9e3dbab55f..4949f783147 100644 --- a/edge/src/io/openems/impl/scheduler/time/WeekTimeScheduler.java +++ b/edge/src/io/openems/impl/scheduler/time/WeekTimeScheduler.java @@ -1,229 +1,222 @@ -/******************************************************************************* - * OpenEMS - Open Source Energy Management System - * Copyright (c) 2016, 2017 FENECON GmbH and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * Contributors: - * FENECON GmbH - initial API and implementation and initial documentation - *******************************************************************************/ -package io.openems.impl.scheduler.time; - -import java.time.DayOfWeek; -import java.time.LocalDate; -import java.time.LocalTime; -import java.time.format.DateTimeParseException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.TreeMap; - -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; - -import info.faljse.SDNotify.SDNotify; -import io.openems.api.bridge.Bridge; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.WriteChannel; -import io.openems.api.controller.Controller; -import io.openems.api.doc.ConfigInfo; -import io.openems.api.doc.ThingInfo; -import io.openems.api.exception.ConfigException; -import io.openems.api.exception.InvalidValueException; -import io.openems.api.exception.ReflectionException; -import io.openems.api.scheduler.Scheduler; -import io.openems.core.ThingRepository; -import io.openems.core.utilities.JsonUtils; - -@ThingInfo(title = "Weekly App-Planner", description = "Define recurring weekly plans.") -public class WeekTimeScheduler extends Scheduler { - - /* - * Constructors - */ - public WeekTimeScheduler() { - thingRepository = ThingRepository.getInstance(); - } - - /* - * Config - */ - /* - * JsonArray format: - * [{ - * time: "08:00", - * controllers: [ "controller0", "controller1"] - * }] - */ - @ConfigInfo(title = "Monday", description = "Sets the controllers for monday.", type = JsonArray.class) - public ConfigChannel monday = new ConfigChannel<>("monday", this); - - @ConfigInfo(title = "Tuesday", description = "Sets the controllers for tuesday.", type = JsonArray.class) - public ConfigChannel tuesday = new ConfigChannel<>("tuesday", this); - - @ConfigInfo(title = "Wednesday", description = "Sets the controllers for wednesday.", type = JsonArray.class) - public ConfigChannel wednesday = new ConfigChannel<>("wednesday", this); - - @ConfigInfo(title = "Thursday", description = "Sets the controllers for thursday.", type = JsonArray.class) - public ConfigChannel thursday = new ConfigChannel<>("thursday", this); - - @ConfigInfo(title = "Friday", description = "Sets the controllers for friday.", type = JsonArray.class) - public ConfigChannel friday = new ConfigChannel<>("friday", this); - - @ConfigInfo(title = "Saturday", description = "Sets the controllers for saturday.", type = JsonArray.class) - public ConfigChannel saturday = new ConfigChannel<>("saturday", this); - - @ConfigInfo(title = "Sunday", description = "Sets the controllers for sunday.", type = JsonArray.class) - public ConfigChannel sunday = new ConfigChannel<>("sunday", this); - - @ConfigInfo(title = "Always", description = "Sets the controllers that are always activated.", type = JsonArray.class) - public ConfigChannel always = new ConfigChannel<>("always", this); - - private ConfigChannel cycleTime = new ConfigChannel("cycleTime", this).defaultValue(1000); - - @Override - public ConfigChannel cycleTime() { - return cycleTime; - } - - /* - * Fields - */ - private ThingRepository thingRepository; - - /* - * Methods - */ - @Override - protected void dispose() {} - - @Override - protected void forever() { - // kick the watchdog - SDNotify.sendWatchdog(); - - try { - List controllers = getActiveControllers(); - controllers.addAll(getAlwaysController()); - Collections.sort(controllers, (c1, c2) -> { - if (c1 == null || c2 == null) { - return 0; - } - return c2.priority.valueOptional().orElse(Integer.MIN_VALUE) - - c1.priority.valueOptional().orElse(Integer.MIN_VALUE); - }); - for (Controller controller : controllers) { - // TODO: check if WritableChannels can still be changed, before executing - if (controller != null) { - controller.run(); - } - } - for (WriteChannel channel : thingRepository.getWriteChannels()) { - channel.shadowCopyAndReset(); - } - for (Bridge bridge : thingRepository.getBridges()) { - bridge.triggerWrite(); - } - } catch (InvalidValueException | DateTimeParseException | ConfigException | ReflectionException e) { - log.error(e.getMessage()); - } - } - - private List getAlwaysController() { - List controller = new ArrayList<>(); - if (always.valueOptional().isPresent()) { - for (JsonElement element : always.valueOptional().get()) { - controller.add(controllers.get(element.getAsString())); - } - } - return controller; - } - - private List getActiveControllers() throws InvalidValueException, ConfigException, ReflectionException { - JsonArray jHours = getJsonOfDay(LocalDate.now().getDayOfWeek()); - LocalTime time = LocalTime.now(); - List controllers = new ArrayList<>(); - int count = 1; - while (controllers.size() == 0 && count < 8) { - try { - controllers.addAll(floorController(jHours, time)); - } catch (IndexOutOfBoundsException e) { - time = LocalTime.MAX; - jHours = getJsonOfDay(LocalDate.now().getDayOfWeek().minus(count)); - } - count++; - } - return controllers; - } - - private JsonArray getJsonOfDay(DayOfWeek day) throws InvalidValueException { - switch (day) { - case FRIDAY: - return friday.value(); - case SATURDAY: - return saturday.value(); - case SUNDAY: - return sunday.value(); - case THURSDAY: - return thursday.value(); - case TUESDAY: - return tuesday.value(); - case WEDNESDAY: - return wednesday.value(); - default: - case MONDAY: - return monday.value(); - } - } - - private List floorController(JsonArray jHours, LocalTime time) - throws ConfigException, ReflectionException { - // fill times map; sorted by hour - TreeMap times = new TreeMap<>(); - for (JsonElement jHourElement : jHours) { - JsonObject jHour = JsonUtils.getAsJsonObject(jHourElement); - String hourTime = JsonUtils.getAsString(jHour, "time"); - JsonArray jControllers = JsonUtils.getAsJsonArray(jHourElement, "controllers"); - times.put(LocalTime.parse(hourTime), jControllers); - } - // return matching controllers - if (times.floorEntry(time) != null) { - List controllers = new ArrayList<>(); - for (JsonElement jControllerElement : times.floorEntry(time).getValue()) { - String controllerId = JsonUtils.getAsString(jControllerElement); - Controller controller = this.controllers.get(controllerId); - if (controller != null) { - controllers.add(controller); - } else { - throw new ConfigException("Controller [" + controllerId + "] not found."); - } - } - return controllers; - } else { - throw new IndexOutOfBoundsException("No smaller time found"); - } - } - - @Override - protected boolean initialize() { - return true; - } - - @Override - public synchronized void removeController(Controller controller) { - // remove controller from all times - super.removeController(controller); - } -} +/******************************************************************************* + * OpenEMS - Open Source Energy Management System + * Copyright (c) 2016, 2017 FENECON GmbH and contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Contributors: + * FENECON GmbH - initial API and implementation and initial documentation + *******************************************************************************/ +package io.openems.impl.scheduler.time; + +import java.time.DayOfWeek; +import java.time.LocalDate; +import java.time.LocalTime; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.TreeMap; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import info.faljse.SDNotify.SDNotify; +import io.openems.api.bridge.Bridge; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.WriteChannel; +import io.openems.api.controller.Controller; +import io.openems.api.doc.ConfigInfo; +import io.openems.api.doc.ThingInfo; +import io.openems.api.exception.ConfigException; +import io.openems.api.exception.InvalidValueException; +import io.openems.api.exception.ReflectionException; +import io.openems.api.scheduler.Scheduler; +import io.openems.core.ThingRepository; +import io.openems.core.utilities.JsonUtils; + +@ThingInfo(title = "Weekly App-Planner", description = "Define recurring weekly plans.") +public class WeekTimeScheduler extends Scheduler { + + /* + * Constructors + */ + public WeekTimeScheduler() { + thingRepository = ThingRepository.getInstance(); + } + + /* + * Config + */ + /* + * JsonArray format: + * [{ + * time: "08:00", + * controllers: [ "controller0", "controller1"] + * }] + */ + @ConfigInfo(title = "Monday", description = "Sets the controllers for monday.", type = JsonArray.class) + public ConfigChannel monday = new ConfigChannel<>("monday", this); + + @ConfigInfo(title = "Tuesday", description = "Sets the controllers for tuesday.", type = JsonArray.class) + public ConfigChannel tuesday = new ConfigChannel<>("tuesday", this); + + @ConfigInfo(title = "Wednesday", description = "Sets the controllers for wednesday.", type = JsonArray.class) + public ConfigChannel wednesday = new ConfigChannel<>("wednesday", this); + + @ConfigInfo(title = "Thursday", description = "Sets the controllers for thursday.", type = JsonArray.class) + public ConfigChannel thursday = new ConfigChannel<>("thursday", this); + + @ConfigInfo(title = "Friday", description = "Sets the controllers for friday.", type = JsonArray.class) + public ConfigChannel friday = new ConfigChannel<>("friday", this); + + @ConfigInfo(title = "Saturday", description = "Sets the controllers for saturday.", type = JsonArray.class) + public ConfigChannel saturday = new ConfigChannel<>("saturday", this); + + @ConfigInfo(title = "Sunday", description = "Sets the controllers for sunday.", type = JsonArray.class) + public ConfigChannel sunday = new ConfigChannel<>("sunday", this); + + @ConfigInfo(title = "Always", description = "Sets the controllers that are always activated.", type = JsonArray.class) + public ConfigChannel always = new ConfigChannel<>("always", this); + + /* + * Fields + */ + private ThingRepository thingRepository; + + /* + * Methods + */ + @Override + protected void dispose() {} + + @Override + protected void execute() { + // kick the watchdog + SDNotify.sendWatchdog(); + + try { + List controllers = getActiveControllers(); + controllers.addAll(getAlwaysController()); + Collections.sort(controllers, (c1, c2) -> { + if (c1 == null || c2 == null) { + return 0; + } + return c2.priority.valueOptional().orElse(Integer.MIN_VALUE) + - c1.priority.valueOptional().orElse(Integer.MIN_VALUE); + }); + for (Controller controller : controllers) { + // TODO: check if WritableChannels can still be changed, before executing + if (controller != null) { + controller.run(); + } + } + for (WriteChannel channel : thingRepository.getWriteChannels()) { + channel.shadowCopyAndReset(); + } + for (Bridge bridge : thingRepository.getBridges()) { + bridge.triggerWrite(); + } + } catch (InvalidValueException | DateTimeParseException | ConfigException | ReflectionException e) { + log.error(e.getMessage()); + } + } + + private List getAlwaysController() { + List controller = new ArrayList<>(); + if (always.valueOptional().isPresent()) { + for (JsonElement element : always.valueOptional().get()) { + controller.add(controllers.get(element.getAsString())); + } + } + return controller; + } + + private List getActiveControllers() throws InvalidValueException, ConfigException, ReflectionException { + JsonArray jHours = getJsonOfDay(LocalDate.now().getDayOfWeek()); + LocalTime time = LocalTime.now(); + List controllers = new ArrayList<>(); + int count = 1; + while (controllers.size() == 0 && count < 8) { + try { + controllers.addAll(floorController(jHours, time)); + } catch (IndexOutOfBoundsException e) { + time = LocalTime.MAX; + jHours = getJsonOfDay(LocalDate.now().getDayOfWeek().minus(count)); + } + count++; + } + return controllers; + } + + private JsonArray getJsonOfDay(DayOfWeek day) throws InvalidValueException { + switch (day) { + case FRIDAY: + return friday.value(); + case SATURDAY: + return saturday.value(); + case SUNDAY: + return sunday.value(); + case THURSDAY: + return thursday.value(); + case TUESDAY: + return tuesday.value(); + case WEDNESDAY: + return wednesday.value(); + default: + case MONDAY: + return monday.value(); + } + } + + private List floorController(JsonArray jHours, LocalTime time) + throws ConfigException, ReflectionException { + // fill times map; sorted by hour + TreeMap times = new TreeMap<>(); + for (JsonElement jHourElement : jHours) { + JsonObject jHour = JsonUtils.getAsJsonObject(jHourElement); + String hourTime = JsonUtils.getAsString(jHour, "time"); + JsonArray jControllers = JsonUtils.getAsJsonArray(jHourElement, "controllers"); + times.put(LocalTime.parse(hourTime), jControllers); + } + // return matching controllers + if (times.floorEntry(time) != null) { + List controllers = new ArrayList<>(); + for (JsonElement jControllerElement : times.floorEntry(time).getValue()) { + String controllerId = JsonUtils.getAsString(jControllerElement); + Controller controller = this.controllers.get(controllerId); + if (controller != null) { + controllers.add(controller); + } else { + throw new ConfigException("Controller [" + controllerId + "] not found."); + } + } + return controllers; + } else { + throw new IndexOutOfBoundsException("No smaller time found"); + } + } + + @Override + protected boolean initialize() { + return true; + } + + @Override + public synchronized void removeController(Controller controller) { + // remove controller from all times + super.removeController(controller); + } +} diff --git a/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricEssNature.java b/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricEssNature.java index d45da213c6e..c41804a3e2b 100644 --- a/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricEssNature.java +++ b/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricEssNature.java @@ -1,180 +1,209 @@ -package io.openems.test.utils.devicenatures; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.ess.AsymmetricEssNature; -import io.openems.impl.device.simulator.SimulatorTools; -import io.openems.test.utils.channel.UnitTestConfigChannel; -import io.openems.test.utils.channel.UnitTestReadChannel; -import io.openems.test.utils.channel.UnitTestWriteChannel; - -public class UnitTestAsymmetricEssNature implements AsymmetricEssNature { - - public UnitTestConfigChannel minSoc = new UnitTestConfigChannel<>("MinSoc", this); - public UnitTestConfigChannel chargeSoc = new UnitTestConfigChannel<>("ChargeSoc", this); - public UnitTestReadChannel gridMode = new UnitTestReadChannel<>("GridMode", this); - public UnitTestReadChannel soc = new UnitTestReadChannel<>("Soc", this); - public UnitTestReadChannel systemState = new UnitTestReadChannel<>("SystemState", this); - public UnitTestReadChannel allowedCharge = new UnitTestReadChannel<>("AllowedCharge", this); - public UnitTestReadChannel allowedDischarge = new UnitTestReadChannel<>("AllowedDischarge", this); - public UnitTestReadChannel allowedApparent = new UnitTestReadChannel<>("AllowedApparent", this); - public UnitTestReadChannel activePowerL1 = new UnitTestReadChannel<>("ActivePowerL1", this); - public UnitTestReadChannel activePowerL2 = new UnitTestReadChannel<>("ActivePowerL2", this); - public UnitTestReadChannel activePowerL3 = new UnitTestReadChannel<>("ActivePowerL3", this); - public UnitTestReadChannel reactivePowerL1 = new UnitTestReadChannel<>("ReactivePowerL1", this); - public UnitTestReadChannel reactivePowerL2 = new UnitTestReadChannel<>("ReactivePowerL2", this); - public UnitTestReadChannel reactivePowerL3 = new UnitTestReadChannel<>("ReactivePowerL3", this); - public UnitTestWriteChannel setActivePowerL1 = new UnitTestWriteChannel<>("SetActivePowerL1", this); - public UnitTestWriteChannel setActivePowerL2 = new UnitTestWriteChannel<>("SetActivePowerL2", this); - public UnitTestWriteChannel setActivePowerL3 = new UnitTestWriteChannel<>("SetActivePowerL3", this); - public UnitTestWriteChannel setReactivePowerL1 = new UnitTestWriteChannel<>("SetReactivePowerL1", this); - public UnitTestWriteChannel setReactivePowerL2 = new UnitTestWriteChannel<>("SetReactivePowerL2", this); - public UnitTestWriteChannel setReactivePowerL3 = new UnitTestWriteChannel<>("SetReactivePowerL3", this); - public UnitTestWriteChannel setWorkState = new UnitTestWriteChannel<>("SetWorkState", this); - public StaticValueChannel capacity = new StaticValueChannel("Capacity", this, - SimulatorTools.getRandomLong(3000, 50000)); - private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) - .unit("VA"); - - private final String id; - - public UnitTestAsymmetricEssNature(String id) { - super(); - this.id = id; - } - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return null; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public void setAsRequired(Channel channel) { - // Not required in Test mode - } - - @Override - public String id() { - return id; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public WriteChannel setActivePowerL1() { - return setActivePowerL1; - } - - @Override - public WriteChannel setActivePowerL2() { - return setActivePowerL2; - } - - @Override - public WriteChannel setActivePowerL3() { - return setActivePowerL3; - } - - @Override - public WriteChannel setReactivePowerL1() { - return setReactivePowerL1; - } - - @Override - public WriteChannel setReactivePowerL2() { - return setReactivePowerL2; - } - - @Override - public WriteChannel setReactivePowerL3() { - return setReactivePowerL3; - } - - @Override - public ReadChannel capacity() { - return capacity; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower; - } - -} +package io.openems.test.utils.devicenatures; + +import java.util.List; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.AsymmetricEssNature; +import io.openems.impl.device.simulator.SimulatorTools; +import io.openems.test.utils.channel.UnitTestConfigChannel; +import io.openems.test.utils.channel.UnitTestReadChannel; +import io.openems.test.utils.channel.UnitTestWriteChannel; + +public class UnitTestAsymmetricEssNature implements AsymmetricEssNature { + + public UnitTestConfigChannel minSoc = new UnitTestConfigChannel<>("MinSoc", this); + public UnitTestConfigChannel chargeSoc = new UnitTestConfigChannel<>("ChargeSoc", this); + public UnitTestReadChannel gridMode = new UnitTestReadChannel<>("GridMode", this); + public UnitTestReadChannel soc = new UnitTestReadChannel<>("Soc", this); + public UnitTestReadChannel systemState = new UnitTestReadChannel<>("SystemState", this); + public UnitTestReadChannel allowedCharge = new UnitTestReadChannel<>("AllowedCharge", this); + public UnitTestReadChannel allowedDischarge = new UnitTestReadChannel<>("AllowedDischarge", this); + public UnitTestReadChannel allowedApparent = new UnitTestReadChannel<>("AllowedApparent", this); + public UnitTestReadChannel activePowerL1 = new UnitTestReadChannel<>("ActivePowerL1", this); + public UnitTestReadChannel activePowerL2 = new UnitTestReadChannel<>("ActivePowerL2", this); + public UnitTestReadChannel activePowerL3 = new UnitTestReadChannel<>("ActivePowerL3", this); + public UnitTestReadChannel reactivePowerL1 = new UnitTestReadChannel<>("ReactivePowerL1", this); + public UnitTestReadChannel reactivePowerL2 = new UnitTestReadChannel<>("ReactivePowerL2", this); + public UnitTestReadChannel reactivePowerL3 = new UnitTestReadChannel<>("ReactivePowerL3", this); + public UnitTestWriteChannel setActivePowerL1 = new UnitTestWriteChannel<>("SetActivePowerL1", this); + public UnitTestWriteChannel setActivePowerL2 = new UnitTestWriteChannel<>("SetActivePowerL2", this); + public UnitTestWriteChannel setActivePowerL3 = new UnitTestWriteChannel<>("SetActivePowerL3", this); + public UnitTestWriteChannel setReactivePowerL1 = new UnitTestWriteChannel<>("SetReactivePowerL1", this); + public UnitTestWriteChannel setReactivePowerL2 = new UnitTestWriteChannel<>("SetReactivePowerL2", this); + public UnitTestWriteChannel setReactivePowerL3 = new UnitTestWriteChannel<>("SetReactivePowerL3", this); + public UnitTestWriteChannel setWorkState = new UnitTestWriteChannel<>("SetWorkState", this); + public StaticValueChannel capacity = new StaticValueChannel("Capacity", this, + SimulatorTools.getRandomLong(3000, 50000)); + private StaticValueChannel maxNominalPower = new StaticValueChannel<>("maxNominalPower", this, 40000L) + .unit("VA"); + + private final String id; + + public UnitTestAsymmetricEssNature(String id) { + super(); + this.id = id; + } + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return null; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public void setAsRequired(Channel channel) { + // Not required in Test mode + } + + @Override + public String id() { + return id; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public WriteChannel setActivePowerL1() { + return setActivePowerL1; + } + + @Override + public WriteChannel setActivePowerL2() { + return setActivePowerL2; + } + + @Override + public WriteChannel setActivePowerL3() { + return setActivePowerL3; + } + + @Override + public WriteChannel setReactivePowerL1() { + return setReactivePowerL1; + } + + @Override + public WriteChannel setReactivePowerL2() { + return setReactivePowerL2; + } + + @Override + public WriteChannel setReactivePowerL3() { + return setReactivePowerL3; + } + + @Override + public ReadChannel capacity() { + return capacity; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower; + } + + @Override + public Device getParent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequiredReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getWriteTasks() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricMeterNature.java b/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricMeterNature.java index b8f1cb5e591..5a9c3ac05c0 100644 --- a/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricMeterNature.java +++ b/edge/src/io/openems/test/utils/devicenatures/UnitTestAsymmetricMeterNature.java @@ -1,119 +1,148 @@ -package io.openems.test.utils.devicenatures; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.AsymmetricMeterNature; -import io.openems.test.utils.channel.UnitTestReadChannel; - -public class UnitTestAsymmetricMeterNature implements AsymmetricMeterNature { - - public UnitTestReadChannel activePowerL1 = new UnitTestReadChannel<>("ActivePowerL1", this); - public UnitTestReadChannel activePowerL2 = new UnitTestReadChannel<>("ActivePowerL2", this); - public UnitTestReadChannel activePowerL3 = new UnitTestReadChannel<>("ActivePowerL3", this); - public UnitTestReadChannel reactivePowerL1 = new UnitTestReadChannel<>("ReactivePowerL1", this); - public UnitTestReadChannel reactivePowerL2 = new UnitTestReadChannel<>("ReactivePowerL2", this); - public UnitTestReadChannel reactivePowerL3 = new UnitTestReadChannel<>("ReactivePowerL3", this); - public UnitTestReadChannel voltageL1 = new UnitTestReadChannel<>("VoltageL1", this); - public UnitTestReadChannel voltageL2 = new UnitTestReadChannel<>("VoltageL2", this); - public UnitTestReadChannel voltageL3 = new UnitTestReadChannel<>("VoltageL3", this); - public UnitTestReadChannel currentL1 = new UnitTestReadChannel<>("CurrentL1", this); - public UnitTestReadChannel currentL2 = new UnitTestReadChannel<>("CurrentL2", this); - public UnitTestReadChannel currentL3 = new UnitTestReadChannel<>("CurrentL3", this); - - private final String id; - - public UnitTestAsymmetricMeterNature(String id) { - super(); - this.id = id; - } - - @Override - public ConfigChannel type() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setAsRequired(Channel channel) { - // Not required in Tests - } - - @Override - public String id() { - return id; - } - - @Override - public ReadChannel activePowerL1() { - return activePowerL1; - } - - @Override - public ReadChannel activePowerL2() { - return activePowerL2; - } - - @Override - public ReadChannel activePowerL3() { - return activePowerL3; - } - - @Override - public ReadChannel reactivePowerL1() { - return reactivePowerL1; - } - - @Override - public ReadChannel reactivePowerL2() { - return reactivePowerL2; - } - - @Override - public ReadChannel reactivePowerL3() { - return reactivePowerL3; - } - - @Override - public ReadChannel currentL1() { - return currentL1; - } - - @Override - public ReadChannel currentL2() { - return currentL2; - } - - @Override - public ReadChannel currentL3() { - return currentL3; - } - - @Override - public ReadChannel voltageL1() { - return voltageL1; - } - - @Override - public ReadChannel voltageL2() { - return voltageL2; - } - - @Override - public ReadChannel voltageL3() { - return voltageL3; - } - - @Override - public ConfigChannel maxActivePower() { - // TODO Auto-generated method stub - return null; - } - - @Override - public ConfigChannel minActivePower() { - // TODO Auto-generated method stub - return null; - } - -} +package io.openems.test.utils.devicenatures; + +import java.util.List; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.AsymmetricMeterNature; +import io.openems.test.utils.channel.UnitTestReadChannel; + +public class UnitTestAsymmetricMeterNature implements AsymmetricMeterNature { + + public UnitTestReadChannel activePowerL1 = new UnitTestReadChannel<>("ActivePowerL1", this); + public UnitTestReadChannel activePowerL2 = new UnitTestReadChannel<>("ActivePowerL2", this); + public UnitTestReadChannel activePowerL3 = new UnitTestReadChannel<>("ActivePowerL3", this); + public UnitTestReadChannel reactivePowerL1 = new UnitTestReadChannel<>("ReactivePowerL1", this); + public UnitTestReadChannel reactivePowerL2 = new UnitTestReadChannel<>("ReactivePowerL2", this); + public UnitTestReadChannel reactivePowerL3 = new UnitTestReadChannel<>("ReactivePowerL3", this); + public UnitTestReadChannel voltageL1 = new UnitTestReadChannel<>("VoltageL1", this); + public UnitTestReadChannel voltageL2 = new UnitTestReadChannel<>("VoltageL2", this); + public UnitTestReadChannel voltageL3 = new UnitTestReadChannel<>("VoltageL3", this); + public UnitTestReadChannel currentL1 = new UnitTestReadChannel<>("CurrentL1", this); + public UnitTestReadChannel currentL2 = new UnitTestReadChannel<>("CurrentL2", this); + public UnitTestReadChannel currentL3 = new UnitTestReadChannel<>("CurrentL3", this); + + private final String id; + + public UnitTestAsymmetricMeterNature(String id) { + super(); + this.id = id; + } + + @Override + public ConfigChannel type() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAsRequired(Channel channel) { + // Not required in Tests + } + + @Override + public String id() { + return id; + } + + @Override + public ReadChannel activePowerL1() { + return activePowerL1; + } + + @Override + public ReadChannel activePowerL2() { + return activePowerL2; + } + + @Override + public ReadChannel activePowerL3() { + return activePowerL3; + } + + @Override + public ReadChannel reactivePowerL1() { + return reactivePowerL1; + } + + @Override + public ReadChannel reactivePowerL2() { + return reactivePowerL2; + } + + @Override + public ReadChannel reactivePowerL3() { + return reactivePowerL3; + } + + @Override + public ReadChannel currentL1() { + return currentL1; + } + + @Override + public ReadChannel currentL2() { + return currentL2; + } + + @Override + public ReadChannel currentL3() { + return currentL3; + } + + @Override + public ReadChannel voltageL1() { + return voltageL1; + } + + @Override + public ReadChannel voltageL2() { + return voltageL2; + } + + @Override + public ReadChannel voltageL3() { + return voltageL3; + } + + @Override + public ConfigChannel maxActivePower() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ConfigChannel minActivePower() { + // TODO Auto-generated method stub + return null; + } + + @Override + public Device getParent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequiredReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getWriteTasks() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricEssNature.java b/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricEssNature.java index fedb0bd03b4..ec62384b11f 100644 --- a/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricEssNature.java +++ b/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricEssNature.java @@ -1,137 +1,166 @@ -package io.openems.test.utils.devicenatures; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.channel.StaticValueChannel; -import io.openems.api.channel.StatusBitChannels; -import io.openems.api.channel.WriteChannel; -import io.openems.api.device.nature.ess.EssNature; -import io.openems.api.device.nature.ess.SymmetricEssNature; -import io.openems.impl.device.simulator.SimulatorTools; -import io.openems.test.utils.channel.UnitTestConfigChannel; -import io.openems.test.utils.channel.UnitTestReadChannel; -import io.openems.test.utils.channel.UnitTestWriteChannel; - -public class UnitTestSymmetricEssNature implements SymmetricEssNature { - - public UnitTestConfigChannel minSoc = new UnitTestConfigChannel<>("minSoc", this); - public UnitTestConfigChannel chargeSoc = new UnitTestConfigChannel<>("chargeSoc", this); - public UnitTestReadChannel gridMode = new UnitTestReadChannel("gridMode", this) - .label(0L, EssNature.OFF_GRID).label(1L, EssNature.ON_GRID); - public UnitTestReadChannel soc = new UnitTestReadChannel<>("Soc", this); - public UnitTestReadChannel systemState = new UnitTestReadChannel<>("SystemState", this); - public UnitTestReadChannel allowedCharge = new UnitTestReadChannel<>("AllowedCharge", this); - public UnitTestReadChannel allowedDischarge = new UnitTestReadChannel<>("AllowedDischarge", this); - public UnitTestReadChannel allowedApparent = new UnitTestReadChannel<>("AllowedApparent", this); - public UnitTestReadChannel activePower = new UnitTestReadChannel<>("ActivePower", this); - public UnitTestReadChannel reactivePower = new UnitTestReadChannel<>("ReactivePower", this); - public UnitTestReadChannel apparentPower = new UnitTestReadChannel<>("ApparentPower", this); - public UnitTestReadChannel maxNominalPower = new UnitTestReadChannel<>("MaxNominalPower", this); - public UnitTestWriteChannel setWorkState = new UnitTestWriteChannel<>("SetWorkState", this); - public UnitTestWriteChannel setActivePower = new UnitTestWriteChannel<>("SetActivePower", this); - public UnitTestWriteChannel setReactivePower = new UnitTestWriteChannel<>("SetReactivePower", this); - public StaticValueChannel capacity = new StaticValueChannel("Capacity", this, - SimulatorTools.getRandomLong(3000, 50000)); - private final String id; - - public UnitTestSymmetricEssNature(String id) { - this.id = id; - } - - @Override - public ConfigChannel minSoc() { - return minSoc; - } - - @Override - public ConfigChannel chargeSoc() { - return chargeSoc; - } - - @Override - public ReadChannel gridMode() { - return gridMode; - } - - @Override - public ReadChannel soc() { - return soc; - } - - @Override - public ReadChannel systemState() { - return systemState; - } - - @Override - public ReadChannel allowedCharge() { - return allowedCharge; - } - - @Override - public ReadChannel allowedDischarge() { - return allowedDischarge; - } - - @Override - public ReadChannel allowedApparent() { - return allowedApparent; - } - - @Override - public StatusBitChannels warning() { - return null; - } - - @Override - public WriteChannel setWorkState() { - return setWorkState; - } - - @Override - public void setAsRequired(Channel channel) { - - } - - @Override - public String id() { - return id; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel maxNominalPower() { - return maxNominalPower(); - } - - @Override - public WriteChannel setActivePower() { - return setActivePower; - } - - @Override - public WriteChannel setReactivePower() { - return setReactivePower; - } - - @Override - public ReadChannel capacity() { - return capacity; - } - -} +package io.openems.test.utils.devicenatures; + +import java.util.List; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.channel.StaticValueChannel; +import io.openems.api.channel.StatusBitChannels; +import io.openems.api.channel.WriteChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.ess.EssNature; +import io.openems.api.device.nature.ess.SymmetricEssNature; +import io.openems.impl.device.simulator.SimulatorTools; +import io.openems.test.utils.channel.UnitTestConfigChannel; +import io.openems.test.utils.channel.UnitTestReadChannel; +import io.openems.test.utils.channel.UnitTestWriteChannel; + +public class UnitTestSymmetricEssNature implements SymmetricEssNature { + + public UnitTestConfigChannel minSoc = new UnitTestConfigChannel<>("minSoc", this); + public UnitTestConfigChannel chargeSoc = new UnitTestConfigChannel<>("chargeSoc", this); + public UnitTestReadChannel gridMode = new UnitTestReadChannel("gridMode", this) + .label(0L, EssNature.OFF_GRID).label(1L, EssNature.ON_GRID); + public UnitTestReadChannel soc = new UnitTestReadChannel<>("Soc", this); + public UnitTestReadChannel systemState = new UnitTestReadChannel<>("SystemState", this); + public UnitTestReadChannel allowedCharge = new UnitTestReadChannel<>("AllowedCharge", this); + public UnitTestReadChannel allowedDischarge = new UnitTestReadChannel<>("AllowedDischarge", this); + public UnitTestReadChannel allowedApparent = new UnitTestReadChannel<>("AllowedApparent", this); + public UnitTestReadChannel activePower = new UnitTestReadChannel<>("ActivePower", this); + public UnitTestReadChannel reactivePower = new UnitTestReadChannel<>("ReactivePower", this); + public UnitTestReadChannel apparentPower = new UnitTestReadChannel<>("ApparentPower", this); + public UnitTestReadChannel maxNominalPower = new UnitTestReadChannel<>("MaxNominalPower", this); + public UnitTestWriteChannel setWorkState = new UnitTestWriteChannel<>("SetWorkState", this); + public UnitTestWriteChannel setActivePower = new UnitTestWriteChannel<>("SetActivePower", this); + public UnitTestWriteChannel setReactivePower = new UnitTestWriteChannel<>("SetReactivePower", this); + public StaticValueChannel capacity = new StaticValueChannel("Capacity", this, + SimulatorTools.getRandomLong(3000, 50000)); + private final String id; + + public UnitTestSymmetricEssNature(String id) { + this.id = id; + } + + @Override + public ConfigChannel minSoc() { + return minSoc; + } + + @Override + public ConfigChannel chargeSoc() { + return chargeSoc; + } + + @Override + public ReadChannel gridMode() { + return gridMode; + } + + @Override + public ReadChannel soc() { + return soc; + } + + @Override + public ReadChannel systemState() { + return systemState; + } + + @Override + public ReadChannel allowedCharge() { + return allowedCharge; + } + + @Override + public ReadChannel allowedDischarge() { + return allowedDischarge; + } + + @Override + public ReadChannel allowedApparent() { + return allowedApparent; + } + + @Override + public StatusBitChannels warning() { + return null; + } + + @Override + public WriteChannel setWorkState() { + return setWorkState; + } + + @Override + public void setAsRequired(Channel channel) { + + } + + @Override + public String id() { + return id; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel maxNominalPower() { + return maxNominalPower(); + } + + @Override + public WriteChannel setActivePower() { + return setActivePower; + } + + @Override + public WriteChannel setReactivePower() { + return setReactivePower; + } + + @Override + public ReadChannel capacity() { + return capacity; + } + + @Override + public Device getParent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequiredReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getWriteTasks() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricMeterNature.java b/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricMeterNature.java index 256442c84ce..17c4a535f01 100644 --- a/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricMeterNature.java +++ b/edge/src/io/openems/test/utils/devicenatures/UnitTestSymmetricMeterNature.java @@ -1,75 +1,104 @@ -package io.openems.test.utils.devicenatures; - -import io.openems.api.channel.Channel; -import io.openems.api.channel.ConfigChannel; -import io.openems.api.channel.ReadChannel; -import io.openems.api.device.nature.meter.SymmetricMeterNature; -import io.openems.test.utils.channel.UnitTestReadChannel; - -public class UnitTestSymmetricMeterNature implements SymmetricMeterNature { - - private final String id; - public UnitTestReadChannel activePower = new UnitTestReadChannel<>("ActivePower", this); - public UnitTestReadChannel reactivePower = new UnitTestReadChannel<>("ReactivePower", this); - public UnitTestReadChannel apparentPower = new UnitTestReadChannel<>("ApparentPower", this); - public UnitTestReadChannel frequency = new UnitTestReadChannel<>("frequency", this); - public UnitTestReadChannel voltage = new UnitTestReadChannel<>("voltage", this); - - public UnitTestSymmetricMeterNature(String id) { - this.id = id; - } - - @Override - public ConfigChannel type() { - // TODO Auto-generated method stub - return null; - } - - @Override - public void setAsRequired(Channel channel) { - // No implementation required - } - - @Override - public String id() { - return id; - } - - @Override - public ReadChannel activePower() { - return activePower; - } - - @Override - public ConfigChannel maxActivePower() { - // TODO Auto-generated method stub - return null; - } - - @Override - public ConfigChannel minActivePower() { - // TODO Auto-generated method stub - return null; - } - - @Override - public ReadChannel apparentPower() { - return apparentPower; - } - - @Override - public ReadChannel reactivePower() { - return reactivePower; - } - - @Override - public ReadChannel frequency() { - return frequency; - } - - @Override - public ReadChannel voltage() { - return voltage; - } - -} +package io.openems.test.utils.devicenatures; + +import java.util.List; + +import io.openems.api.bridge.BridgeReadTask; +import io.openems.api.bridge.BridgeWriteTask; +import io.openems.api.channel.Channel; +import io.openems.api.channel.ConfigChannel; +import io.openems.api.channel.ReadChannel; +import io.openems.api.device.Device; +import io.openems.api.device.nature.meter.SymmetricMeterNature; +import io.openems.test.utils.channel.UnitTestReadChannel; + +public class UnitTestSymmetricMeterNature implements SymmetricMeterNature { + + private final String id; + public UnitTestReadChannel activePower = new UnitTestReadChannel<>("ActivePower", this); + public UnitTestReadChannel reactivePower = new UnitTestReadChannel<>("ReactivePower", this); + public UnitTestReadChannel apparentPower = new UnitTestReadChannel<>("ApparentPower", this); + public UnitTestReadChannel frequency = new UnitTestReadChannel<>("frequency", this); + public UnitTestReadChannel voltage = new UnitTestReadChannel<>("voltage", this); + + public UnitTestSymmetricMeterNature(String id) { + this.id = id; + } + + @Override + public ConfigChannel type() { + // TODO Auto-generated method stub + return null; + } + + @Override + public void setAsRequired(Channel channel) { + // No implementation required + } + + @Override + public String id() { + return id; + } + + @Override + public ReadChannel activePower() { + return activePower; + } + + @Override + public ConfigChannel maxActivePower() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ConfigChannel minActivePower() { + // TODO Auto-generated method stub + return null; + } + + @Override + public ReadChannel apparentPower() { + return apparentPower; + } + + @Override + public ReadChannel reactivePower() { + return reactivePower; + } + + @Override + public ReadChannel frequency() { + return frequency; + } + + @Override + public ReadChannel voltage() { + return voltage; + } + + @Override + public Device getParent() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getRequiredReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getReadTasks() { + // TODO Auto-generated method stub + return null; + } + + @Override + public List getWriteTasks() { + // TODO Auto-generated method stub + return null; + } + +} diff --git a/edge/template/Simulator.json b/edge/template/Simulator.json index e856806d196..bd161547431 100644 --- a/edge/template/Simulator.json +++ b/edge/template/Simulator.json @@ -1,134 +1,155 @@ -{ - "things": [ - { - "class": "io.openems.impl.protocol.system.SystemBridge", - "devices": [ - { - "class": "io.openems.impl.device.system.System", - "system": { - "id": "system0" - } - } - ] - }, - { - "class": "io.openems.impl.protocol.simulator.SimulatorBridge", - "devices": [ - { - "class": "io.openems.impl.device.simulator.Simulator", - "symmetricEss": { - "id": "ess0", - "activePowerGeneratorConfig": { - "className": "io.openems.impl.device.simulator.RandomLoadGenerator", - "config": { - "min": -500, - "max": 0 - } - }, - "reactivePowerGeneratorConfig": { - "className": "io.openems.impl.device.simulator.RandomLoadGenerator", - "config": { - "min": 0, - "max": 1000 - } - }, - "minSoc": 15, - "chargeSoc": 13 - }, - "productionMeter": { - "id": "meter1", - "reactivePowerGeneratorConfig": { - "className": "io.openems.impl.device.simulator.RandomLoadGenerator", - "config": { - "min": 0, - "max": 1000 - } - }, - "voltage": 230000, - "minActivePower": -259, - "maxActivePower": 11000, - "frequency": 50000, - "activePowerGeneratorConfig": { - "className": "io.openems.impl.device.simulator.RandomLoadGenerator", - "config": { - "min": -500, - "max": 0 - } - } - }, - "gridMeter": { - "id": "meter0", - "minActivePower": -15000, - "activePowerGeneratorConfig": { - "className": "io.openems.impl.device.simulator.RandomLoadGenerator", - "config": { - "min": -500, - "max": 0 - } - }, - "voltage": 230000, - "producer": [ - "meter1" - ], - "type": "grid", - "esss": [ - "ess0" - ], - "maxActivePower": 15000, - "frequency": 50000, - "reactivePowerGeneratorConfig": { - "className": "io.openems.impl.device.simulator.RandomLoadGenerator", - "config": { - "min": 0, - "max": 1000 - } - } - } - } - ] - } - ], - "scheduler": { - "class": "io.openems.impl.scheduler.SimpleScheduler", - "controllers": [ - { - "class": "io.openems.impl.controller.api.rest.RestApiController", - "priority": 150 - }, - { - "class": "io.openems.impl.controller.debuglog.DebugLogController", - "meters": [ - "meter0", - "meter1" - ], - "priority": 10, - "esss": [ - "ess0" - ] - }, - { - "class": "io.openems.impl.controller.api.websocket.WebsocketApiController", - "priority": 0 - } - ] - }, - "persistence": [], - "users": { - "admin": { - "password": "txASlUVQkEI9Bxa/IZOJe8l3+R4lMzFTShz27vK44go\u003d", - "salt": "YWRtaW4\u003d" - }, - "installer": { - "password": "2O1dMlsFdwafy58ehrT+X+0CEWaAmBRad5JFbTLx/Wo\u003d", - "salt": "aW5zdGFsbGVy" - }, - "owner": { - "password": "eJgLBfHTmehv4S1whsfjeE3q3AJmJCBabV59Y65eoYI\u003d", - "salt": "b3duZXI\u003d" - }, - "guest": { - "password": "IcIzJSOvNM1PvQ8v5ypFvPoTZyHw3Knob+zi7d+WspU\u003d", - "salt": "dXNlcg\u003d\u003d" - } - } +{ + "things": [ + { + "class": "io.openems.impl.protocol.system.SystemBridge", + "devices": [ + { + "class": "io.openems.impl.device.system.System", + "system": { + "id": "system0" + } + } + ] + }, + { + "class": "io.openems.impl.protocol.simulator.SimulatorBridge", + "devices": [ + { + "class": "io.openems.impl.device.simulator.Simulator", + "symmetricEss": { + "id": "ess0", + "activePowerGeneratorConfig": { + "className": "io.openems.impl.device.simulator.RandomLoadGenerator", + "config": { + "min": -500, + "max": 1000, + "delta": 100 + } + }, + "reactivePowerGeneratorConfig": { + "className": "io.openems.impl.device.simulator.RandomLoadGenerator", + "config": { + "min": 0, + "max": 1000, + "delta": 50 + } + }, + "minSoc": 15, + "chargeSoc": 13 + }, + "productionMeter": { + "id": "meter1", + "reactivePowerGeneratorConfig": { + "className": "io.openems.impl.device.simulator.RandomLoadGenerator", + "config": { + "min": 0, + "max": 1000, + "delta": 50 + } + }, + "voltage": 230000, + "minActivePower": -259, + "maxActivePower": 11000, + "frequency": 50000, + "activePowerGeneratorConfig": { + "className": "io.openems.impl.device.simulator.RandomLoadGenerator", + "config": { + "min": 0, + "max": 10000, + "delta": 120 + } + } + }, + "gridMeter": { + "id": "meter0", + "minActivePower": -15000, + "activePowerGeneratorConfig": { + "className": "io.openems.impl.device.simulator.RandomLoadGenerator", + "config": { + "min": 0, + "max": 5000, + "delta": 250 + } + }, + "voltage": 230000, + "producer": [ + "meter1" + ], + "type": "grid", + "esss": [ + "ess0" + ], + "maxActivePower": 15000, + "frequency": 50000, + "reactivePowerGeneratorConfig": { + "className": "io.openems.impl.device.simulator.RandomLoadGenerator", + "config": { + "min": 0, + "max": 1000, + "delta": 100 + } + } + } + } + ] + } + ], + "scheduler": { + "class": "io.openems.impl.scheduler.SimpleScheduler", + "controllers": [ + { + "class": "io.openems.impl.controller.api.rest.RestApiController", + "priority": 150 + }, + { + "class": "io.openems.impl.controller.debuglog.DebugLogController", + "meters": [ + "meter0", + "meter1" + ], + "priority": 10, + "esss": [ + "ess0" + ] + }, + { + "class": "io.openems.impl.controller.symmetric.avoidtotaldischarge.AvoidTotalDischargeController", + "priority": 100, + "esss": [ + "ess0" + ] + }, + { + "class": "io.openems.impl.controller.symmetric.balancing.BalancingController", + "esss": [ + "ess0" + ], + "meter": "meter0", + "priority": 50 + }, + { + "class": "io.openems.impl.controller.api.websocket.WebsocketApiController", + "priority": 0 + } + ] + }, + "persistence": [], + "users": { + "admin": { + "password": "txASlUVQkEI9Bxa/IZOJe8l3+R4lMzFTShz27vK44go\u003d", + "salt": "YWRtaW4\u003d" + }, + "installer": { + "password": "2O1dMlsFdwafy58ehrT+X+0CEWaAmBRad5JFbTLx/Wo\u003d", + "salt": "aW5zdGFsbGVy" + }, + "owner": { + "password": "eJgLBfHTmehv4S1whsfjeE3q3AJmJCBabV59Y65eoYI\u003d", + "salt": "b3duZXI\u003d" + }, + "guest": { + "password": "IcIzJSOvNM1PvQ8v5ypFvPoTZyHw3Knob+zi7d+WspU\u003d", + "salt": "dXNlcg\u003d\u003d" + } + } } \ No newline at end of file diff --git a/edge/template/app/FEMS App Heizstab.json b/edge/template/app/FEMS App Heizstab.json index d64d8c5ee7b..b2dbc59e34f 100644 --- a/edge/template/app/FEMS App Heizstab.json +++ b/edge/template/app/FEMS App Heizstab.json @@ -1,3 +1,15 @@ +{ + "class": "io.openems.impl.device.kmtronic.KMTronicRelayRev1", + "output": { + "id": "output0" + }, + "modbusUnitId": 1 +} + + + + + { "class": "io.openems.impl.controller.channelthreshold.ChannelThresholdController", "priority": 60, diff --git a/edge/template/controller/BalancingBandgapController.json b/edge/template/controller/BalancingBandgapController.json index b95096194f7..e59eb89f1a4 100644 --- a/edge/template/controller/BalancingBandgapController.json +++ b/edge/template/controller/BalancingBandgapController.json @@ -1,13 +1,13 @@ -{ - "id": "peakshaving0", - "class": "io.openems.impl.controller.symmetric.balancingbandgap", - "ess": "ess0", - "priority": 150, - "meter": "meter0", - "minActivePower": 0, - "maxActivePower": 33000, - "minReactivePower": 0, - "maxReactivePower": 0, - "activePowerActivated": true, - "reactivePowerActivated": false +{ + "id": "peakshaving0", + "class": "io.openems.impl.controller.symmetric.balancingbandgap.BalancingBandgapController", + "ess": "ess0", + "priority": 150, + "meter": "meter0", + "minActivePower": 0, + "maxActivePower": 33000, + "minReactivePower": 0, + "maxReactivePower": 0, + "activePowerActivated": true, + "reactivePowerActivated": false } \ No newline at end of file diff --git a/ui/.angular-cli.json b/ui/.angular-cli.json index 607d2363326..c0eec68cfca 100644 --- a/ui/.angular-cli.json +++ b/ui/.angular-cli.json @@ -28,13 +28,10 @@ ], "environmentSource": "environments/environment.ts", "environments": { - "femsserver": "environments/femsserver.ts", - "femsserver-dev": "environments/femsserver-dev.ts", - "femsserver-local": "environments/femsserver-local.ts", - "openems": "environments/openems.ts", - "dev": "environments/environment.ts", - "project-VA6403": "environments/project-VA6403.ts", - "project-VA7282": "environments/project-VA7282.ts" + "backend": "environments/openems-backend.ts", + "backend-dev": "environments/openems-backend-dev.ts", + "edge": "environments/openems-edge.ts", + "dev": "environments/environment.ts" } } ], diff --git a/ui/README.md b/ui/README.md index 329a2eead0f..3e9e6da3004 100644 --- a/ui/README.md +++ b/ui/README.md @@ -5,9 +5,9 @@ Dependencies are managed by yarn: `ng set --global packageManager=yarn` and `yar ## Development server -Run `ng serve --env=femsserver-dev` to serve for FemsServer-Backend. +Run `ng serve --env=backend-dev` to serve for OpenEMS Backend. -Run `ng serve` to serve for OpenEMS-Backend. +Run `ng serve` to serve for OpenEMS Edge. ## Build using maven @@ -35,17 +35,13 @@ If you want to build despite "Cannot create the build number because you have lo Run `ng build` to build the project. The build artifacts will be stored in the `target` directory. Use the `-prod` flag for a production build. -Build for femsserver: - -`ng build -prod --env=femsserver --base-href /m/` - -Build for openems: +Build for OpenEMS Backend: -`ng build -prod --env=openems --base-href /` +`ng build -prod --env=backend --base-href /m/` -Bulid for projects: +Build for OpenEMS Edge: -`ng build -prod --env=project-VA6403 --base-href /` +`ng build -prod --env=edge --base-href /` ## Further help @@ -75,14 +71,14 @@ this.translate.instant('General.StorageSystem') For "subscribe" please follow this: https://stackoverflow.com/questions/38008334/angular-rxjs-when-should-i-unsubscribe-from-subscription ``` import { Subject } from 'rxjs/Subject'; -private ngUnsubscribe: Subject = new Subject(); +private stopOnDestroy: Subject = new Subject(); ngOnInit() { - /*subject*/.takeUntil(this.ngUnsubscribe).subscribe(/*variable*/ => { + /*subject*/.takeUntil(this.stopOnDestroy).subscribe(/*variable*/ => { ... }); } ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); + this.stopOnDestroy.next(); + this.stopOnDestroy.complete(); } ``` \ No newline at end of file diff --git a/ui/karma.conf.js b/ui/karma.conf.js index 4d9ab9d9482..af139fada36 100644 --- a/ui/karma.conf.js +++ b/ui/karma.conf.js @@ -1,5 +1,5 @@ // Karma configuration file, see link for more information -// https://karma-runner.github.io/0.13/config/configuration-file.html +// https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function (config) { config.set({ diff --git a/ui/package.json b/ui/package.json index 0188c370c9e..1b41fbf78e5 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,22 +14,21 @@ }, "private": true, "dependencies": { - "@angular/animations": "^4.0.0", - "@angular/cdk": "^2.0.0-beta.8", - "@angular/common": "^4.0.0", - "@angular/compiler": "^4.0.0", - "@angular/core": "^4.0.0", - "@angular/flex-layout": "2.0.0-beta.8", - "@angular/forms": "^4.0.0", - "@angular/http": "^4.0.0", - "@angular/material": "2.0.0-beta.8", - "@angular/platform-browser": "^4.0.0", - "@angular/platform-browser-dynamic": "^4.0.0", - "@angular/platform-server": "^4.0.0", - "@angular/router": "^4.0.0", + "@angular/animations": "^4.2.4", + "@angular/cdk": "2.0.0-beta.10", + "@angular/common": "^4.2.4", + "@angular/compiler": "^4.2.4", + "@angular/core": "^4.2.4", + "@angular/flex-layout": "2.0.0-beta.9", + "@angular/forms": "^4.2.4", + "@angular/http": "^4.2.4", + "@angular/material": "2.0.0-beta.10", + "@angular/platform-browser": "^4.2.4", + "@angular/platform-browser-dynamic": "^4.2.4", + "@angular/platform-server": "^4.2.4", + "@angular/router": "^4.2.4", "@ngx-translate/core": "^7.1.0", "@types/d3": "4.10.0", - "angular-spinners": "^5.0.4", "angular2-uuid": "^1.1.1", "chart.js": "^2.6.0", "classlist.js": "^1.1.20150312", @@ -49,21 +48,23 @@ "material-design-icons-iconfont": "3.0.3", "moment": "2.18.1", "ng2-charts": "^1.6.0", + "ng2-cookies": "^1.0.12", + "ngx-loading": "^1.0.5", "queueing-subject": "^0.1.1", "roboto-fontface": "0.8.0", - "rxjs": "^5.4.1", + "rxjs": "^5.4.2", "rxjs-websockets": "^1.1.0", "web-animations-js": "^2.3.1", "zone.js": "^0.8.14" }, "devDependencies": { - "@angular/cli": "1.2.7", - "@angular/compiler-cli": "^4.0.0", - "@angular/language-service": "^4.0.0", + "@angular/cli": "1.3.2", + "@angular/compiler-cli": "^4.2.4", + "@angular/language-service": "^4.2.4", "@types/jasmine": "~2.5.53", "@types/jasminewd2": "~2.0.2", "@types/node": "~6.0.60", - "codelyzer": "~3.0.1", + "codelyzer": "~3.1.1", "jasmine-core": "~2.6.2", "jasmine-spec-reporter": "~4.1.0", "karma": "~1.7.0", @@ -73,8 +74,8 @@ "karma-jasmine": "~1.1.0", "karma-jasmine-html-reporter": "^0.2.2", "protractor": "~5.1.2", - "ts-node": "~3.0.4", + "ts-node": "~3.2.0", "tslint": "~5.3.2", "typescript": "~2.3.3" } -} \ No newline at end of file +} diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index f1542e126df..a28b4f934bb 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -4,7 +4,8 @@ - + + General.History @@ -17,25 +18,37 @@
- +
- + +
- +
- + + +
+ Trying to reconnect... + +
+ +
+ Device not connected. + +
+
diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 7b04b79f52a..f420edf111e 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -6,35 +6,28 @@ import { TranslateService } from '@ngx-translate/core'; import * as moment from 'moment'; import { environment } from '../environments'; -import { WebappService, WebsocketService, Device, Notification } from './shared/shared'; +import { Service, Websocket } from './shared/shared'; @Component({ selector: 'root', templateUrl: './app.component.html' }) export class AppComponent implements OnInit, OnDestroy { - public environment = environment; - public currentDevice: Device; + public env = environment; private navCollapsed: boolean = true; private ngUnsubscribe: Subject = new Subject(); constructor( - public websocketService: WebsocketService, - private webappService: WebappService, + public websocket: Websocket, + private service: Service, private snackBar: MdSnackBar ) { - webappService.setLang('de'); + service.setLang('de'); } ngOnInit() { - this.websocketService.currentDevice.takeUntil(this.ngUnsubscribe).subscribe(currentDevice => { - setTimeout(() => { - this.currentDevice = currentDevice; - }) - }); - - this.webappService.notificationEvent.takeUntil(this.ngUnsubscribe).subscribe(notification => { + this.service.notificationEvent.takeUntil(this.ngUnsubscribe).subscribe(notification => { this.snackBar.open(notification.message, null, { duration: 3000 }); }); } diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 6311c9ea309..1cb948b3ac2 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -15,7 +15,7 @@ import { DeviceModule } from './device/device.module'; import { AppComponent } from './app.component'; // services -import { WebappService, WebsocketService } from './shared/shared'; +import { Websocket, Service } from './shared/shared'; import { MyTranslateLoader } from './shared/translate/translate'; @NgModule({ @@ -39,7 +39,7 @@ import { MyTranslateLoader } from './shared/translate/translate'; MdSnackBar, { provide: ErrorHandler, - useExisting: WebappService + useExisting: Service } ] }) diff --git a/ui/src/app/app.routing.ts b/ui/src/app/app.routing.ts index d46322862e9..15644488ac1 100644 --- a/ui/src/app/app.routing.ts +++ b/ui/src/app/app.routing.ts @@ -11,6 +11,7 @@ import { BridgeComponent as DeviceConfigBridgeComponent } from './device/config/ import { SchedulerComponent as DeviceConfigSchedulerComponent } from './device/config/scheduler/scheduler.component'; import { LogComponent as DeviceConfigLogComponent } from './device/config/log/log.component'; import { MoreComponent as DeviceConfigMoreComponent } from './device/config/more/more.component'; +import { RawConfigComponent as DeviceConfigRawConfigComponent } from './device/config/more/rawconfig/rawconfig.component'; import { OverviewComponent as DeviceControllerOverviewComponent } from './device/config/controller/overview/overview.component'; import { SimulatorComponent as DeviceConfigSimulatorComponent } from './device/config/simulator/simulator.component'; @@ -20,20 +21,22 @@ const appRoutes: Routes = [ { path: 'about', component: AboutComponent }, - { path: 'device/:websocket/:device', redirectTo: 'device/:websocket/:device/overview', pathMatch: 'full' }, - { path: 'device/:websocket/:device/overview', component: DeviceOverviewComponent }, - { path: 'device/:websocket/:device/history', component: DeviceHistoryComponent }, - { path: 'device/:websocket/:device/log', component: DeviceConfigLogComponent }, - - { path: 'device/:websocket/:device/config', redirectTo: 'device/:websocket/:device/config/overview', pathMatch: 'full' }, - { path: 'device/:websocket/:device/config/overview', component: DeviceConfigOverviewComponent }, - { path: 'device/:websocket/:device/config/bridge', component: DeviceConfigBridgeComponent }, - { path: 'device/:websocket/:device/config/scheduler', component: DeviceConfigSchedulerComponent }, - { path: 'device/:websocket/:device/config/more', component: DeviceConfigMoreComponent }, - - { path: 'device/:websocket/:device/config/controller', redirectTo: 'device/:websocket/:device/config/controller/overview', pathMatch: 'full' }, - { path: 'device/:websocket/:device/config/controller/overview', component: DeviceControllerOverviewComponent }, - { path: 'device/:websocket/:device/config/simulator', component: DeviceConfigSimulatorComponent }, + { path: 'device/:device', redirectTo: 'device/:device/overview', pathMatch: 'full' }, + { path: 'device/:device/overview', component: DeviceOverviewComponent }, + { path: 'device/:device/history', component: DeviceHistoryComponent }, + { path: 'device/:device/log', component: DeviceConfigLogComponent }, + + /* TODO: update Odoo direct monitoring links to reflect path changes */ + { path: 'device/:device/config', redirectTo: 'device/:device/config/overview', pathMatch: 'full' }, + { path: 'device/:device/config/overview', component: DeviceConfigOverviewComponent }, + { path: 'device/:device/config/bridge', component: DeviceConfigBridgeComponent }, + { path: 'device/:device/config/scheduler', component: DeviceConfigSchedulerComponent }, + { path: 'device/:device/config/more', component: DeviceConfigMoreComponent }, + { path: 'device/:device/config/more/rawconfig', component: DeviceConfigRawConfigComponent }, + + { path: 'device/:device/config/controller', redirectTo: 'device/:device/config/controller/overview', pathMatch: 'full' }, + { path: 'device/:device/config/controller/overview', component: DeviceControllerOverviewComponent }, + { path: 'device/:device/config/simulator', component: DeviceConfigSimulatorComponent }, ]; diff --git a/ui/src/app/device/config/abstractconfig.ts b/ui/src/app/device/config/abstractconfig.ts index eb4c6783651..e7b06a7cdb8 100644 --- a/ui/src/app/device/config/abstractconfig.ts +++ b/ui/src/app/device/config/abstractconfig.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormControl, FormGroup, FormArray, AbstractControl, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, Device } from '../../shared/shared'; +import { Device } from '../../shared/device/device'; +import { Websocket } from '../../shared/shared'; import { AbstractConfigForm } from './abstractconfigform'; export type ConfigureRequestModeType = "update" | "create" | "delete"; @@ -30,25 +31,26 @@ export abstract class AbstractConfig extends AbstractConfigForm implements OnIni constructor( private route: ActivatedRoute, - websocketService: WebsocketService, + websocket: Websocket, protected formBuilder: FormBuilder ) { - super(websocketService); + super(websocket); } protected abstract initForm(config); ngOnInit() { super.ngOnInit(); - this.websocketService.setCurrentDevice(this.route.snapshot.params); - this.device.takeUntil(this.ngUnsubscribe).subscribe(device => { - if (device != null) { - device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { - this.config = config; - this.initForm(config); - }); - } - }); + // TODO + // this.websocket.setCurrentDevice(this.route.snapshot.params); + // this.device.takeUntil(this.ngUnsubscribe).subscribe(device => { + // if (device != null) { + // device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { + // this.config = config; + // this.initForm(config); + // }); + // } + // }); } ngOnDestroy() { diff --git a/ui/src/app/device/config/abstractconfigform.ts b/ui/src/app/device/config/abstractconfigform.ts index d5304602dc9..51d27734156 100644 --- a/ui/src/app/device/config/abstractconfigform.ts +++ b/ui/src/app/device/config/abstractconfigform.ts @@ -5,7 +5,8 @@ import { Subscription } from 'rxjs/Subscription'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Subject } from 'rxjs/Subject'; -import { WebsocketService, Device } from '../../shared/shared'; +import { Websocket } from '../../shared/shared'; +import { Device } from '../../shared/device/device'; export type ConfigureRequestModeType = "update" | "create" | "delete"; export class ConfigureRequest { @@ -35,13 +36,14 @@ export abstract class AbstractConfigForm implements OnDestroy, OnInit { protected ngUnsubscribe: Subject = new Subject(); constructor( - protected websocketService: WebsocketService, + protected websocket: Websocket, ) { } ngOnInit() { - this.websocketService.currentDevice.takeUntil(this.ngUnsubscribe).subscribe(device => { - this.device.next(device); - }); + // TODO + // this.websocket.currentDevice.takeUntil(this.ngUnsubscribe).subscribe(device => { + // this.device.next(device); + // }); } ngOnDestroy() { diff --git a/ui/src/app/device/config/bridge/bridge.component.html b/ui/src/app/device/config/bridge/bridge.component.html index 9017b101c16..12d396629db 100644 --- a/ui/src/app/device/config/bridge/bridge.component.html +++ b/ui/src/app/device/config/bridge/bridge.component.html @@ -1,3 +1,7 @@ + + TODO: migrate BridgeComponent to new config layout + +
diff --git a/ui/src/app/device/config/bridge/bridge.component.ts b/ui/src/app/device/config/bridge/bridge.component.ts index a57b0d95526..0e12319be81 100644 --- a/ui/src/app/device/config/bridge/bridge.component.ts +++ b/ui/src/app/device/config/bridge/bridge.component.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormControl, FormGroup, FormArray, AbstractControl, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, WebappService, Device } from '../../../shared/shared'; +import { Device } from '../../../shared/device/device'; +import { Websocket, Service } from '../../../shared/shared'; import { AbstractConfig, ConfigureRequest, ConfigureUpdateRequest, ConfigureCreateRequest, ConfigureDeleteRequest } from '../abstractconfig'; @@ -24,10 +25,10 @@ export class BridgeComponent extends AbstractConfig { constructor( route: ActivatedRoute, - websocketService: WebsocketService, + websocket: Websocket, formBuilder: FormBuilder ) { - super(route, websocketService, formBuilder); + super(route, websocket, formBuilder); } initForm(config) { diff --git a/ui/src/app/device/config/controller/controller.ts b/ui/src/app/device/config/controller/controller.ts index 8302fe850f8..a07c6efc2cd 100644 --- a/ui/src/app/device/config/controller/controller.ts +++ b/ui/src/app/device/config/controller/controller.ts @@ -1,41 +1,44 @@ import { FormGroup, FormBuilder } from '@angular/forms'; import { Thing } from '../thing'; -import { WebsocketService, Device, Config, Meta, ThingMeta } from '../../../shared/shared'; +import { Websocket } from '../../../shared/shared'; +import { Device } from '../../../shared/device/device'; export class Controller extends Thing { - public readonly meta: ThingMeta; + //public readonly meta: ThingMeta; - public static getControllers(config: Config, formBuilder: FormBuilder): Controller[] { - let controllers: Controller[] = []; - for (let controllerConfig of config.scheduler.controllers) { - controllers.push(new Controller(controllerConfig, config._meta, formBuilder)); - } - return controllers; - } + // TODO + // public static getControllers(config: Config, formBuilder: FormBuilder): Controller[] { + // let controllers: Controller[] = []; + // for (let controllerConfig of config.scheduler.controllers) { + // controllers.push(new Controller(controllerConfig, config._meta, formBuilder)); + // } + // return controllers; + // } - constructor(config: any, meta: Meta, formBuilder: FormBuilder) { + constructor(config: any, formBuilder: FormBuilder) { super(formBuilder); + // TODO // get meta for controller - if ('class' in config && config.class in meta.availableControllers) { - this.meta = meta.availableControllers[config.class]; - } else { - this.meta = { - title: config.class, - class: config.class, - text: '', - channels: {} - // TODO add channels from config - } - } + // if ('class' in config && config.class in meta.availableControllers) { + // this.meta = meta.availableControllers[config.class]; + // } else { + // this.meta = { + // title: config.class, + // class: config.class, + // text: '', + // channels: {} + // // TODO add channels from config + // } + // } // add missing channels - for (let channelName in this.meta.channels) { - if (!(channelName in config)) { - //let channel = this.meta.channels[channelName]; - config[channelName] = null; - // TODO set default value - } - } + // for (let channelName in this.meta.channels) { + // if (!(channelName in config)) { + // //let channel = this.meta.channels[channelName]; + // config[channelName] = null; + // // TODO set default value + // } + // } // build form this.form = this.buildFormGroup(config); } diff --git a/ui/src/app/device/config/controller/details/details.component.html b/ui/src/app/device/config/controller/details/details.component.html index 982a1342c8c..bd1ac06cba0 100644 --- a/ui/src/app/device/config/controller/details/details.component.html +++ b/ui/src/app/device/config/controller/details/details.component.html @@ -5,7 +5,7 @@ {{ controller.form.value.id }}: {{ controller.form.value.class }} - + @@ -31,7 +31,7 @@ - + diff --git a/ui/src/app/device/config/controller/details/details.component.ts b/ui/src/app/device/config/controller/details/details.component.ts index 980e55d60af..d0955c4af35 100644 --- a/ui/src/app/device/config/controller/details/details.component.ts +++ b/ui/src/app/device/config/controller/details/details.component.ts @@ -1,7 +1,8 @@ import { Component, Input } from '@angular/core'; import { Controller } from '../controller'; -import { TemplateHelper, Device } from '../../../../shared/shared'; +import { Utils } from '../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; import { Role, ROLES } from '../../../../shared/type/role'; @Component({ @@ -25,5 +26,5 @@ export class DetailsComponent { public _device: Device public role: Role = ROLES.guest; - constructor(public tmpl: TemplateHelper) { } + constructor(public utils: Utils) { } } \ No newline at end of file diff --git a/ui/src/app/device/config/controller/forminput/forminput.component.html b/ui/src/app/device/config/controller/forminput/forminput.component.html index be23dd68347..2d25bda7925 100644 --- a/ui/src/app/device/config/controller/forminput/forminput.component.html +++ b/ui/src/app/device/config/controller/forminput/forminput.component.html @@ -4,7 +4,7 @@ - + diff --git a/ui/src/app/device/config/controller/forminput/forminput.component.ts b/ui/src/app/device/config/controller/forminput/forminput.component.ts index 12800bc1315..121129812ae 100644 --- a/ui/src/app/device/config/controller/forminput/forminput.component.ts +++ b/ui/src/app/device/config/controller/forminput/forminput.component.ts @@ -2,7 +2,8 @@ import { Component, Input, OnChanges } from '@angular/core'; import { FormGroup } from '@angular/forms'; import { Controller } from '../controller'; -import { TemplateHelper, Device, Meta } from '../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; +import { Utils } from '../../../../shared/shared'; import { Role, ROLES } from '../../../../shared/type/role'; interface ArrayPath { @@ -33,7 +34,7 @@ export class FormInputComponent { public meta: any @Input() - public allMeta: Meta; + public allMeta: any; @Input() public showTitle: boolean = true; @@ -41,7 +42,7 @@ export class FormInputComponent { public type: string; public specialType: string; - constructor(public tmpl: TemplateHelper) { } + constructor(public utils: Utils) { } ngOnChanges(changes: any) { switch (this.meta.type) { diff --git a/ui/src/app/device/config/controller/overview/overview.component.html b/ui/src/app/device/config/controller/overview/overview.component.html index 3b468b2e02a..8e874c3683f 100644 --- a/ui/src/app/device/config/controller/overview/overview.component.html +++ b/ui/src/app/device/config/controller/overview/overview.component.html @@ -1,3 +1,7 @@ + + TODO: migrate ControllerComponent to new config layout + +
@@ -29,7 +33,7 @@ Device.Config.Controller.App - {{ tmpl.classname(controller.form.value.class) }} + {{ utils.classname(controller.form.value.class) }} Device.Config.Controller.Priority diff --git a/ui/src/app/device/config/controller/overview/overview.component.ts b/ui/src/app/device/config/controller/overview/overview.component.ts index 6e1fb0da50a..81ea5ba2dde 100644 --- a/ui/src/app/device/config/controller/overview/overview.component.ts +++ b/ui/src/app/device/config/controller/overview/overview.component.ts @@ -3,8 +3,9 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormBuilder } from '@angular/forms'; import { Subject } from 'rxjs/Subject'; -import { TemplateHelper } from './../../../../shared/service/templatehelper'; -import { WebsocketService, Device } from '../../../../shared/shared'; +import { Utils } from './../../../../shared/service/utils'; +import { Websocket } from '../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; import { Controller } from '../controller'; @Component({ @@ -21,22 +22,25 @@ export class OverviewComponent implements OnInit, OnDestroy { constructor( private route: ActivatedRoute, - private websocketService: WebsocketService, + private websocket: Websocket, private formBuilder: FormBuilder, - private tmpl: TemplateHelper + private utils: Utils ) { } ngOnInit() { - this.websocketService.setCurrentDevice(this.route.snapshot.params).takeUntil(this.ngUnsubscribe).subscribe(device => { - this.device = device; - if (device != null) { - device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { - if (config != null) { - this.controllers = Controller.getControllers(config, this.formBuilder); - } - }); - } - }); + //this.websocket.setCurrentDevice(this.route.snapshot.params); + + // TODO + // this.websocket.setCurrentDevice(this.route.snapshot.params).takeUntil(this.ngUnsubscribe).subscribe(device => { + // this.device = device; + // if (device != null) { + // device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { + // if (config != null) { + // this.controllers = Controller.getControllers(config, this.formBuilder); + // } + // }); + // } + // }); } ngOnDestroy() { diff --git a/ui/src/app/device/config/log/log.component.html b/ui/src/app/device/config/log/log.component.html index 86023f40c80..cc3c5f4985c 100644 --- a/ui/src/app/device/config/log/log.component.html +++ b/ui/src/app/device/config/log/log.component.html @@ -3,7 +3,7 @@ code - {{ device.address }} + System-Log {{ device.name }} @@ -17,7 +17,7 @@ Device.Config.Log.Source Device.Config.Log.Message - + {{ log.time }} {{ log.level }} {{ log.source }} diff --git a/ui/src/app/device/config/log/log.component.ts b/ui/src/app/device/config/log/log.component.ts index be2ed57035e..ccd3582f52c 100644 --- a/ui/src/app/device/config/log/log.component.ts +++ b/ui/src/app/device/config/log/log.component.ts @@ -1,8 +1,11 @@ import { Component, OnInit, OnDestroy } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; +import { Subject } from 'rxjs/Subject'; -import { WebsocketService, Device, Log } from '../../../shared/shared'; +import { Device } from '../../../shared/device/device'; +import { Websocket } from '../../../shared/shared'; +import { DefaultTypes } from '../../../shared/service/defaulttypes'; import * as moment from 'moment'; @@ -12,47 +15,25 @@ import * as moment from 'moment'; }) export class LogComponent implements OnInit { - public device: Device; - public logs: Log[] = []; + public device: Device = null; + public logs: DefaultTypes.Log[] = []; + public isSubscribed: boolean = false; private MAX_LOG_ENTRIES = 200; - private deviceSubscription: Subscription; - - private isSubscribed: boolean = false; + private stopOnDestroy: Subject = new Subject(); constructor( private route: ActivatedRoute, - private websocketService: WebsocketService, + private websocket: Websocket, ) { } ngOnInit() { - this.deviceSubscription = this.websocketService.setCurrentDevice(this.route.snapshot.params).subscribe(device => { - this.device = device; - if (this.device != null) { + this.websocket.setCurrentDevice(this.route) + .takeUntil(this.stopOnDestroy) + .subscribe(device => { + this.device = device; this.subscribeLog(); - this.device.log.subscribe(log => { - log.time = moment(log.timestamp).format("DD.MM.YYYY HH:mm:ss"); - switch (log.level) { - case 'INFO': - log.color = 'green'; - break; - case 'WARN': - log.color = 'orange'; - break; - case 'DEBUG': - log.color = 'gray'; - break; - case 'ERROR': - log.color = 'red'; - break; - }; - this.logs.unshift(log); - if (this.logs.length > this.MAX_LOG_ENTRIES) { - this.logs.length = this.MAX_LOG_ENTRIES; - } - }) - } - }); + }); } public toggleSubscribe($event: any /*MdSlideToggleChange*/) { @@ -64,21 +45,53 @@ export class LogComponent implements OnInit { } public subscribeLog() { + // put placeholder + if (this.logs.length > 0) { + this.logs.unshift({ + time: "-------------------", + level: "----", + color: "black", + message: "", + source: "" + }); + } + if (this.device != null) { - this.device.subscribeLog("all"); + this.device.subscribeLog().takeUntil(this.stopOnDestroy).subscribe(log => { + log.time = moment(log.time).format("DD.MM.YYYY HH:mm:ss"); + switch (log.level) { + case 'INFO': + log.color = 'green'; + break; + case 'WARN': + log.color = 'orange'; + break; + case 'DEBUG': + log.color = 'gray'; + break; + case 'ERROR': + log.color = 'red'; + break; + }; + this.logs.unshift(log); + if (this.logs.length > this.MAX_LOG_ENTRIES) { + this.logs.length = this.MAX_LOG_ENTRIES; + } + }); this.isSubscribed = true; }; } public unsubscribeLog() { - this.device.unsubscribeLog(); + if (this.device != null) { + this.device.unsubscribeLog(); + } + this.stopOnDestroy.next(); + this.stopOnDestroy.complete(); this.isSubscribed = false; }; ngOnDestroy() { - this.deviceSubscription.unsubscribe(); - if (this.device) { - this.device.unsubscribeLog(); - } + this.unsubscribeLog(); } } \ No newline at end of file diff --git a/ui/src/app/device/config/more/manualpq/manualpq.component.html b/ui/src/app/device/config/more/manualpq/manualpq.component.html index b95d9c906b7..be67b298037 100644 --- a/ui/src/app/device/config/more/manualpq/manualpq.component.html +++ b/ui/src/app/device/config/more/manualpq/manualpq.component.html @@ -1,4 +1,7 @@ - + + TODO: adjust ManualpqComponent to new config layout + + \ No newline at end of file diff --git a/ui/src/app/device/config/more/manualpq/manualpq.component.ts b/ui/src/app/device/config/more/manualpq/manualpq.component.ts index f8b7c7c3ba5..554971aacba 100644 --- a/ui/src/app/device/config/more/manualpq/manualpq.component.ts +++ b/ui/src/app/device/config/more/manualpq/manualpq.component.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormGroup, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, WebappService, Device } from '../../../../shared/shared'; +import { Websocket, Service } from '../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; @Component({ selector: 'manualpq', diff --git a/ui/src/app/device/config/more/more.component.html b/ui/src/app/device/config/more/more.component.html index 996a513ef4b..7bcccad3bec 100644 --- a/ui/src/app/device/config/more/more.component.html +++ b/ui/src/app/device/config/more/more.component.html @@ -1,19 +1,39 @@ - +
+
+ +
- +
+ +
- - - stop - Device.Config.More.ManualCommand - - -
- -
-
-
- -
-
-
\ No newline at end of file +
+ + + stop + Device.Config.More.ManualCommand + + +
+ +
+
+
+ +
+
+
+
+ +
+ + + more_horiz + Display technical data + + + + + +
+
\ No newline at end of file diff --git a/ui/src/app/device/config/more/more.component.ts b/ui/src/app/device/config/more/more.component.ts index 6b43c06a664..afbf1e9acb8 100644 --- a/ui/src/app/device/config/more/more.component.ts +++ b/ui/src/app/device/config/more/more.component.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormGroup, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, WebappService, Device } from '../../../shared/shared'; +import { Websocket, Service } from '../../../shared/shared'; +import { Device } from '../../../shared/device/device'; @Component({ selector: 'more', @@ -14,34 +15,31 @@ export class MoreComponent implements OnInit { public device: Device; public manualMessageForm: FormGroup; - private deviceSubscription: Subscription; - constructor( private route: ActivatedRoute, - private websocketService: WebsocketService, - private webappService: WebappService, + private websocket: Websocket, + private service: Service, private formBuilder: FormBuilder ) { } ngOnInit() { - this.deviceSubscription = this.websocketService.setCurrentDevice(this.route.snapshot.params).subscribe(device => { - this.device = device; - }) + this.websocket.setCurrentDevice(this.route) + .filter(device => device != null) + .first() + .subscribe(device => { + this.device = device; + }); this.manualMessageForm = this.formBuilder.group({ "message": this.formBuilder.control('') }); } - ngOnDestroy() { - this.deviceSubscription.unsubscribe(); - } - public sendManualMessage(form: FormGroup) { try { let obj = JSON.parse(form["value"]["message"]); this.device.send(obj); } catch (e) { - this.webappService.notify({ + this.service.notify({ type: "error", message: (e).message }); diff --git a/ui/src/app/device/config/more/more.module.ts b/ui/src/app/device/config/more/more.module.ts index d21285b3e54..fa280935ee8 100644 --- a/ui/src/app/device/config/more/more.module.ts +++ b/ui/src/app/device/config/more/more.module.ts @@ -4,6 +4,7 @@ import { SharedModule } from './../../../shared/shared.module'; import { MoreComponent } from './more.component'; import { RefuComponent } from './refu/refu.component'; import { ManualpqComponent } from './manualpq/manualpq.component'; +import { RawConfigComponent } from './rawconfig/rawconfig.component'; @NgModule({ imports: [ @@ -12,7 +13,8 @@ import { ManualpqComponent } from './manualpq/manualpq.component'; declarations: [ MoreComponent, RefuComponent, - ManualpqComponent + ManualpqComponent, + RawConfigComponent ] }) export class MoreModule { } diff --git a/ui/src/app/device/config/more/rawconfig/rawconfig.component.html b/ui/src/app/device/config/more/rawconfig/rawconfig.component.html new file mode 100644 index 00000000000..9a3f161acc9 --- /dev/null +++ b/ui/src/app/device/config/more/rawconfig/rawconfig.component.html @@ -0,0 +1,27 @@ +
+ +

Things

+ +

{{ thing.key }}:

+
    +
  • {{ channel.key }}: {{ channel.value | json }}
  • +
+
+

Meta

+ +

{{ class.key }}:

+
    + +
  • {{ key.key }}: {{ key.value | json }}
  • +
    +
  • Channels
  • +
      +
    • {{ channel.key }}:
    • +
        +
      • {{ key.key }}: {{ key.value | json }}
      • +
      +
    +
+
+
+
\ No newline at end of file diff --git a/ui/src/app/device/config/more/rawconfig/rawconfig.component.ts b/ui/src/app/device/config/more/rawconfig/rawconfig.component.ts new file mode 100644 index 00000000000..2f853fa533d --- /dev/null +++ b/ui/src/app/device/config/more/rawconfig/rawconfig.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from '@angular/core'; +import { Router, ActivatedRoute, Params } from '@angular/router'; +import { FormGroup, FormBuilder } from '@angular/forms'; +import { Subscription } from 'rxjs/Subscription'; + +import { Websocket, Service } from '../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; +import { Utils } from '../../../../shared/shared'; + +@Component({ + selector: 'rawconfig', + templateUrl: './rawconfig.component.html' +}) +export class RawConfigComponent { + + constructor( + public websocket: Websocket, + private route: ActivatedRoute, + public utils: Utils + ) { } + + ngOnInit() { + this.websocket.setCurrentDevice(this.route); + } +} \ No newline at end of file diff --git a/ui/src/app/device/config/more/refu/refu.component.html b/ui/src/app/device/config/more/refu/refu.component.html index 4ee23ef7aed..432e24e36b6 100644 --- a/ui/src/app/device/config/more/refu/refu.component.html +++ b/ui/src/app/device/config/more/refu/refu.component.html @@ -1,4 +1,7 @@ - + + TODO: adjust RefuComponent to new config layout + + \ No newline at end of file diff --git a/ui/src/app/device/config/more/refu/refu.component.ts b/ui/src/app/device/config/more/refu/refu.component.ts index 6d27d35ff43..4703127dc9d 100644 --- a/ui/src/app/device/config/more/refu/refu.component.ts +++ b/ui/src/app/device/config/more/refu/refu.component.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormGroup, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, WebappService, Device } from '../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; +import { Websocket } from '../../../../shared/shared'; @Component({ selector: 'refu', diff --git a/ui/src/app/device/config/overview/overview.component.html b/ui/src/app/device/config/overview/overview.component.html index 864f36afa39..4c27f3a7b5c 100644 --- a/ui/src/app/device/config/overview/overview.component.html +++ b/ui/src/app/device/config/overview/overview.component.html @@ -1,103 +1,106 @@ -
- -
- - - device_hub - Device.Config.Overview.Bridge - - - - -

{{ bridge.class | classname }}

-

{{ bridge.id }}

-
-
-
-
-
- -
- - - schedule - Device.Config.Overview.Scheduler - - - - -

{{ (device.config | async)?.scheduler.class | classname }}

-

{{ (device.config | async)?.scheduler.id }}

-
-
-
-
-
- -
- - - apps - Device.Config.Overview.Controller - - - - -

{{ controller.class | classname }}

-

{{ controller.id }}

-
-
-
-
-
- - -
+
+ + +
+ + + device_hub + Device.Config.Overview.Bridge + + + + +

{{ config.things[bridgeId].class | classname }}

+

{{ bridgeId }}

+
+
+
+
+
+ +
+ + + schedule + Device.Config.Overview.Scheduler + + + + +

{{ config.things[schedulerId].class | classname }}

+

{{ schedulerId }}

+
+
+
+
+
+ +
+ + + apps + Device.Config.Overview.Controller + + + + +

{{ config.things[controllerId].class | classname }}

+

{{ controllerId }}

+
+
+
+
+
+ + + +
+ + + explore + Device.Config.Overview.Simulator + + + + +

Device.Config.Overview.ExecuteSimulator

+
+
+
+
+
+
+ +
+ + + code + Device.Config.Overview.Log + + + + +

Device.Config.Overview.LiveLog

+
+
+
+
+
+ +
- explore - Device.Config.Overview.Simulator + more_horiz + General.More -

Device.Config.Overview.ExecuteSimulator

+

Device.Config.Overview.ManualControl

- -
- - - code - Device.Config.Overview.Log - - - - -

Device.Config.Overview.LiveLog

-
-
-
-
-
- -
- - - more_horiz - General.More - - - - -

Device.Config.Overview.ManualControl

-
-
-
-
-
\ No newline at end of file diff --git a/ui/src/app/device/config/overview/overview.component.ts b/ui/src/app/device/config/overview/overview.component.ts index 259a24e53be..e18568e5bc2 100644 --- a/ui/src/app/device/config/overview/overview.component.ts +++ b/ui/src/app/device/config/overview/overview.component.ts @@ -3,13 +3,14 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormGroup, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, Device } from '../../../shared/shared'; +import { Device } from '../../../shared/device/device'; +import { Websocket } from '../../../shared/shared'; @Component({ selector: 'overview', templateUrl: './overview.component.html' }) -export class OverviewComponent implements OnInit, OnDestroy { +export class OverviewComponent implements OnInit { public device: Device; @@ -17,17 +18,11 @@ export class OverviewComponent implements OnInit, OnDestroy { constructor( private route: ActivatedRoute, - private websocketService: WebsocketService, + public websocket: Websocket, private formBuilder: FormBuilder ) { } ngOnInit() { - this.deviceSubscription = this.websocketService.setCurrentDevice(this.route.snapshot.params).subscribe(device => { - this.device = device; - }) - } - - ngOnDestroy() { - this.deviceSubscription.unsubscribe(); + this.websocket.setCurrentDevice(this.route); } } \ No newline at end of file diff --git a/ui/src/app/device/config/scheduler/channelthreshold/channelthreshold.component.ts b/ui/src/app/device/config/scheduler/channelthreshold/channelthreshold.component.ts index 8a52bb0ff98..c8af0c0474a 100644 --- a/ui/src/app/device/config/scheduler/channelthreshold/channelthreshold.component.ts +++ b/ui/src/app/device/config/scheduler/channelthreshold/channelthreshold.component.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; import { AbstractControl, FormArray, FormGroup, FormBuilder } from '@angular/forms'; import { Router, ActivatedRoute, Params } from '@angular/router'; -import { WebsocketService } from '../../../../shared/shared'; +import { Websocket } from '../../../../shared/shared'; import { AbstractConfig, ConfigureRequest, ConfigureUpdateRequest } from '../../abstractconfig'; import { AbstractConfigForm } from '../../abstractconfigform'; @@ -15,10 +15,10 @@ export class ChannelthresholdComponent extends AbstractConfigForm { public schedulerForm: FormGroup; constructor( - public websocketService: WebsocketService, + public websocket: Websocket, private formBuilder: FormBuilder ) { - super(websocketService); + super(websocket); } @Input() diff --git a/ui/src/app/device/config/scheduler/scheduler.component.html b/ui/src/app/device/config/scheduler/scheduler.component.html index 0ab4b665755..158a468b0ac 100644 --- a/ui/src/app/device/config/scheduler/scheduler.component.html +++ b/ui/src/app/device/config/scheduler/scheduler.component.html @@ -1,3 +1,7 @@ + + TODO: migrate SchedulerComponent to new config layout + +
diff --git a/ui/src/app/device/config/scheduler/scheduler.component.ts b/ui/src/app/device/config/scheduler/scheduler.component.ts index d2936113621..5a62295d3ba 100644 --- a/ui/src/app/device/config/scheduler/scheduler.component.ts +++ b/ui/src/app/device/config/scheduler/scheduler.component.ts @@ -3,7 +3,8 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { FormControl, FormGroup, FormArray, AbstractControl, FormBuilder } from '@angular/forms'; import { Subscription } from 'rxjs/Subscription'; -import { WebsocketService, Device } from '../../../shared/shared'; +import { Websocket } from '../../../shared/shared'; +import { Device } from '../../../shared/device/device'; import { AbstractConfig, ConfigureRequest } from '../abstractconfig'; @Component({ @@ -17,10 +18,10 @@ export class SchedulerComponent extends AbstractConfig { constructor( route: ActivatedRoute, - websocketService: WebsocketService, + websocket: Websocket, formBuilder: FormBuilder ) { - super(route, websocketService, formBuilder); + super(route, websocket, formBuilder); } initForm(config) { diff --git a/ui/src/app/device/config/scheduler/simple/simple.component.ts b/ui/src/app/device/config/scheduler/simple/simple.component.ts index f639f567cb0..e8d5d5b59ed 100644 --- a/ui/src/app/device/config/scheduler/simple/simple.component.ts +++ b/ui/src/app/device/config/scheduler/simple/simple.component.ts @@ -2,7 +2,7 @@ import { Component, Input } from '@angular/core'; import { AbstractControl, FormArray, FormGroup, FormBuilder } from '@angular/forms'; import { Router, ActivatedRoute, Params } from '@angular/router'; -import { WebsocketService } from '../../../../shared/shared'; +import { Websocket } from '../../../../shared/shared'; import { AbstractConfig, ConfigureRequest, ConfigureUpdateRequest } from '../../abstractconfig'; import { AbstractConfigForm } from '../../abstractconfigform'; @@ -15,10 +15,10 @@ export class SimpleComponent extends AbstractConfigForm { schedulerForm: FormGroup; constructor( - websocketService: WebsocketService, + websocket: Websocket, private formBuilder: FormBuilder ) { - super(websocketService); + super(websocket); } @Input() diff --git a/ui/src/app/device/config/scheduler/weektime/weektime.component.ts b/ui/src/app/device/config/scheduler/weektime/weektime.component.ts index ee7de386ae5..7f9a5cb4403 100644 --- a/ui/src/app/device/config/scheduler/weektime/weektime.component.ts +++ b/ui/src/app/device/config/scheduler/weektime/weektime.component.ts @@ -3,7 +3,7 @@ import { AbstractControl, FormArray, FormGroup, FormBuilder } from '@angular/for import { Router, ActivatedRoute, Params } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; -import { WebsocketService } from '../../../../shared/shared'; +import { Websocket } from '../../../../shared/shared'; import { AbstractConfig, ConfigureRequest, ConfigureUpdateRequest, ConfigureDeleteRequest } from '../../abstractconfig'; import { AbstractConfigForm } from '../../abstractconfigform'; @@ -22,11 +22,11 @@ export class WeekTimeComponent extends AbstractConfigForm { public config: FormGroup; constructor( - public websocketService: WebsocketService, + public websocket: Websocket, private formBuilder: FormBuilder, private translate: TranslateService ) { - super(websocketService); + super(websocket); } public days: Day[] = [{ diff --git a/ui/src/app/device/config/simulator/simulator.component.html b/ui/src/app/device/config/simulator/simulator.component.html index d25c0d757bc..09fd245e6b2 100644 --- a/ui/src/app/device/config/simulator/simulator.component.html +++ b/ui/src/app/device/config/simulator/simulator.component.html @@ -1,3 +1,7 @@ + + TODO: migrate SimulatorComponent to new config layout + +
@@ -15,12 +19,6 @@

- - - - - - @@ -42,18 +40,6 @@

Id: {{ form.productionMeter.value.id }}

- - - - - - - @@ -95,6 +81,24 @@ save + +
+
+

Asymmetric ESS

+

Id: {{ form.asymmetricEss.value.id }}

+
+
+ + + +
+
+
+ + +
diff --git a/ui/src/app/device/config/simulator/simulator.component.ts b/ui/src/app/device/config/simulator/simulator.component.ts index bfc059d3e61..32fc7fbeec7 100644 --- a/ui/src/app/device/config/simulator/simulator.component.ts +++ b/ui/src/app/device/config/simulator/simulator.component.ts @@ -3,7 +3,7 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; import { FormControl, FormGroup, FormArray, AbstractControl, FormBuilder } from '@angular/forms'; -import { WebsocketService, Device, Log } from '../../../shared/shared'; +import { Websocket } from '../../../shared/shared'; import { AbstractConfig, ConfigureRequest } from '../abstractconfig'; import 'rxjs/add/operator/retryWhen'; import 'rxjs/add/operator/delay'; @@ -34,10 +34,10 @@ export class SimulatorComponent extends AbstractConfig implements OnInit, OnDest constructor( route: ActivatedRoute, - websocketService: WebsocketService, + websocket: Websocket, formBuilder: FormBuilder, ) { - super(route, websocketService, formBuilder); + super(route, websocket, formBuilder); } keys(object: {}) { @@ -84,25 +84,26 @@ export class SimulatorComponent extends AbstractConfig implements OnInit, OnDest }).takeUntil(this.ngUnsubscribe).subscribe(data => { let tmpData = {}; // subscribed to data - if (data.data != null) { - for (let thing in data.data) { - if (!tmpData[thing]) { - tmpData[thing] = {}; - } - for (let channel in data.data[thing]) { - let newData = { name: moment(), value: data.data[thing][channel] }; - // if (!this.data[thing][channel]) { - // // create new array - // this.data[thing][channel] = []; - // } - // if (this.data[thing][channel].length > 9) { - // // max 10 entries - // this.data[thing][channel].shift(); - // } - tmpData[thing][channel] = newData; - } - } - } + // TODO + // if (data.data != null) { + // for (let thing in data.data) { + // if (!tmpData[thing]) { + // tmpData[thing] = {}; + // } + // for (let channel in data.data[thing]) { + // let newData = { name: moment(), value: data.data[thing][channel] }; + // // if (!this.data[thing][channel]) { + // // // create new array + // // this.data[thing][channel] = []; + // // } + // // if (this.data[thing][channel].length > 9) { + // // // max 10 entries + // // this.data[thing][channel].shift(); + // // } + // tmpData[thing][channel] = newData; + // } + // } + // } this.data = tmpData; }, error => { console.error("error", error); @@ -143,6 +144,7 @@ export class SimulatorComponent extends AbstractConfig implements OnInit, OnDest } } } + console.log(this.forms); } protected getConfigureCreateRequests(form: FormGroup): ConfigureRequest[] { diff --git a/ui/src/app/device/config/thing.ts b/ui/src/app/device/config/thing.ts index ca2a43197f9..eb59e9086bf 100644 --- a/ui/src/app/device/config/thing.ts +++ b/ui/src/app/device/config/thing.ts @@ -1,5 +1,5 @@ import { FormGroup, FormBuilder, FormControl, FormArray, AbstractControl } from '@angular/forms'; -import { Device } from '../../shared/shared'; +import { Device } from '../../shared/device/device'; type ConfigureRequestModeType = "update" | "create" | "delete"; class ConfigureRequest { diff --git a/ui/src/app/device/history/chart/energychart/energychart.component.html b/ui/src/app/device/history/chart/energychart/energychart.component.html index 8a3c5e5c00f..54008c12a1a 100644 --- a/ui/src/app/device/history/chart/energychart/energychart.component.html +++ b/ui/src/app/device/history/chart/energychart/energychart.component.html @@ -1,7 +1,7 @@ - - -
+
+ + -
- \ No newline at end of file + +
\ No newline at end of file diff --git a/ui/src/app/device/history/chart/energychart/energychart.component.ts b/ui/src/app/device/history/chart/energychart/energychart.component.ts index c88c08595b5..6d9e8d0d04f 100644 --- a/ui/src/app/device/history/chart/energychart/energychart.component.ts +++ b/ui/src/app/device/history/chart/energychart/energychart.component.ts @@ -4,13 +4,15 @@ import { BaseChartDirective } from 'ng2-charts/ng2-charts'; import { TranslateService } from '@ngx-translate/core'; import * as moment from 'moment'; -import { Dataset, EMPTY_DATASET, Device, Config, QueryReply, Summary } from './../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; +import { ConfigImpl } from '../../../../shared/device/config'; +import { DefaultTypes } from '../../../../shared/service/defaulttypes'; +import { Dataset, EMPTY_DATASET } from './../../../../shared/shared'; import { DEFAULT_TIME_CHART_OPTIONS, ChartOptions, TooltipItem, Data } from './../shared'; -import { TemplateHelper } from './../../../../shared/service/templatehelper'; - -// spinner component -import { SpinnerComponent } from '../../../../shared/spinner.component'; +import { Utils } from './../../../../shared/service/utils'; +import { CurrentDataAndSummary } from '../../../../shared/device/currentdata'; +// TODO grid should be shown as "Netzeinspeisung"/"Netzbezug" instead of positive/negative value @Component({ selector: 'energychart', templateUrl: './energychart.component.html' @@ -18,13 +20,15 @@ import { SpinnerComponent } from '../../../../shared/spinner.component'; export class EnergyChartComponent implements OnChanges { @Input() private device: Device; + @Input() private config: ConfigImpl; + @Input() private channels: DefaultTypes.ChannelAddresses; @Input() private fromDate: moment.Moment; @Input() private toDate: moment.Moment; @ViewChild('energyChart') private chart: BaseChartDirective; constructor( - private tmpl: TemplateHelper, + private utils: Utils, private translate: TranslateService ) { this.grid = this.translate.instant('General.Grid'); @@ -37,7 +41,6 @@ export class EnergyChartComponent implements OnChanges { public loading: boolean = true; private ngUnsubscribe: Subject = new Subject(); - private queryreplySubject: Subject; private grid: String = ""; private gridBuy: String = ""; private gridSell: String = ""; @@ -55,7 +58,7 @@ export class EnergyChartComponent implements OnChanges { private options: ChartOptions; ngOnInit() { - let options = this.tmpl.deepCopy(DEFAULT_TIME_CHART_OPTIONS); + let options = this.utils.deepCopy(DEFAULT_TIME_CHART_OPTIONS); options.scales.yAxes[0].scaleLabel.labelString = "kW"; options.tooltips.callbacks.label = function (tooltipItem: TooltipItem, data: Data) { let label = data.datasets[tooltipItem.datasetIndex].label; @@ -73,18 +76,11 @@ export class EnergyChartComponent implements OnChanges { this.options = options; } - ngOnChanges(changes: any) { - // close old queryreplySubject - if (this.queryreplySubject != null) { - this.queryreplySubject.complete(); - } - // show loading... + ngOnChanges() { this.loading = true; - // create channels for query - let channels = this.device.config.getValue().getPowerChannels(); - // execute query - let queryreplySubject = this.device.query(this.fromDate, this.toDate, channels); - queryreplySubject.subscribe(queryreply => { + this.device.historicDataQuery(this.fromDate, this.toDate, this.channels).then(historicData => { + // prepare datas array and prefill with each device + // prepare datasets and labels let activePowers = { production: [], @@ -92,12 +88,12 @@ export class EnergyChartComponent implements OnChanges { consumption: [] } let labels: moment.Moment[] = []; - for (let reply of queryreply.data) { - labels.push(moment(reply.time)); - let data = new Summary(this.device.config.getValue(), reply.channels); - activePowers.grid.push(data.grid.activePower / -1000); // convert to kW and invert value - activePowers.production.push(data.production.activePower / 1000); // convert to kW - activePowers.consumption.push(data.consumption.activePower / 1000); // convert to kW + for (let record of historicData.data) { + labels.push(moment(record.time)); + let data = new CurrentDataAndSummary(record.channels, this.config); + activePowers.grid.push(Utils.divideSafely(data.summary.grid.activePower, -1000)); // convert to kW and invert value + activePowers.production.push(Utils.divideSafely(data.summary.production.activePower, 1000)); // convert to kW + activePowers.consumption.push(Utils.divideSafely(data.summary.consumption.activePower, 1000)); // convert to kW } this.datasets = [{ label: this.translate.instant('General.Production'), @@ -110,6 +106,7 @@ export class EnergyChartComponent implements OnChanges { data: activePowers.consumption }]; this.labels = labels; + // stop loading spinner this.loading = false; setTimeout(() => { // Workaround, because otherwise chart data and labels are not refreshed... @@ -117,17 +114,9 @@ export class EnergyChartComponent implements OnChanges { this.chart.ngOnChanges({} as SimpleChanges); } }); - - }, error => { + }).catch(error => { this.datasets = EMPTY_DATASET; this.labels = []; - // TODO should be error message - this.loading = true; }); } - - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } } \ No newline at end of file diff --git a/ui/src/app/device/history/chart/socchart/socchart.component.html b/ui/src/app/device/history/chart/socchart/socchart.component.html index 29fb36518d0..b9468cd2fd6 100644 --- a/ui/src/app/device/history/chart/socchart/socchart.component.html +++ b/ui/src/app/device/history/chart/socchart/socchart.component.html @@ -1,6 +1,7 @@ - - -
- -
-
\ No newline at end of file +
+ + + + +
\ No newline at end of file diff --git a/ui/src/app/device/history/chart/socchart/socchart.component.ts b/ui/src/app/device/history/chart/socchart/socchart.component.ts index 27a1ce7b72f..91a619c113d 100644 --- a/ui/src/app/device/history/chart/socchart/socchart.component.ts +++ b/ui/src/app/device/history/chart/socchart/socchart.component.ts @@ -4,28 +4,27 @@ import { BaseChartDirective } from 'ng2-charts/ng2-charts'; import { TranslateService } from '@ngx-translate/core'; import * as moment from 'moment'; -import { Dataset, EMPTY_DATASET, Device, Config, QueryReply, ChannelAddresses } from './../../../../shared/shared'; +import { Device } from '../../../../shared/device/device'; +import { DefaultTypes } from '../../../../shared/service/defaulttypes'; +import { Dataset, EMPTY_DATASET } from './../../../../shared/shared'; import { DEFAULT_TIME_CHART_OPTIONS, ChartOptions } from './../shared'; -import { TemplateHelper } from './../../../../shared/service/templatehelper'; - -// spinner component -import { SpinnerComponent } from '../../../../shared/spinner.component'; +import { Utils } from './../../../../shared/service/utils'; @Component({ selector: 'socchart', templateUrl: './socchart.component.html' }) -export class SocChartComponent implements OnInit, OnChanges, OnDestroy { +export class SocChartComponent implements OnInit, OnChanges { @Input() private device: Device; - @Input() private socChannels: ChannelAddresses; + @Input() private channels: DefaultTypes.ChannelAddresses; @Input() private fromDate: moment.Moment; @Input() private toDate: moment.Moment; @ViewChild('socChart') private chart: BaseChartDirective; constructor( - private tmpl: TemplateHelper, + private utils: Utils, private translate: TranslateService ) { } @@ -33,9 +32,6 @@ export class SocChartComponent implements OnInit, OnChanges, OnDestroy { public datasets: Dataset[] = EMPTY_DATASET; public loading: boolean = true; - private ngUnsubscribe: Subject = new Subject(); - private queryreplySubject: Subject; - private colors = [{ backgroundColor: 'rgba(0,152,70,0.2)', borderColor: 'rgba(0,152,70,1)', @@ -53,37 +49,34 @@ export class SocChartComponent implements OnInit, OnChanges, OnDestroy { private options: ChartOptions; ngOnInit() { - let options = this.tmpl.deepCopy(DEFAULT_TIME_CHART_OPTIONS); + let options = this.utils.deepCopy(DEFAULT_TIME_CHART_OPTIONS); options.scales.yAxes[0].scaleLabel.labelString = this.translate.instant('General.Percentage'); options.scales.yAxes[0].ticks.max = 100; this.options = options; } - ngOnChanges(changes: any) { - // close old queryreplySubject - if (this.queryreplySubject != null) { - this.queryreplySubject.complete(); - } - // show loading... + ngOnChanges() { this.loading = true; - // execute query - let queryreplySubject = this.device.query(this.fromDate, this.toDate, this.socChannels); - queryreplySubject.subscribe(queryreply => { + if (Object.keys(this.channels).length === 0) { + return; + } + // TODO stop previous subscribe; show only results for latest query. Otherwise the chart misbehaves on fast switch of period + this.device.historicDataQuery(this.fromDate, this.toDate, this.channels).then(historicData => { // prepare datas array and prefill with each device let tmpData: { [thing: string]: number[]; } = {}; let labels: moment.Moment[] = []; - for (let thing in this.socChannels) { + for (let thing in this.channels) { tmpData[thing] = []; } - for (let reply of queryreply.data) { - // read timestamp and soc of each device' reply - labels.push(moment(reply.time)); - for (let thing in this.socChannels) { - let soc = 0; - if (thing in reply.channels && "Soc" in reply.channels[thing] && reply.channels[thing]["Soc"]) { - soc = Math.round(reply.channels[thing].Soc); + for (let record of historicData.data) { + // read timestamp and soc of each device + labels.push(moment(record.time)); + for (let thing in this.channels) { + let soc = null; + if (thing in record.channels && "Soc" in record.channels[thing] && record.channels[thing]["Soc"] != null) { + soc = Math.round(record.channels[thing].Soc); } tmpData[thing].push(soc); } @@ -98,6 +91,7 @@ export class SocChartComponent implements OnInit, OnChanges, OnDestroy { } this.datasets = datasets; this.labels = labels; + // stop loading spinner this.loading = false; setTimeout(() => { // Workaround, because otherwise chart data and labels are not refreshed... @@ -105,17 +99,12 @@ export class SocChartComponent implements OnInit, OnChanges, OnDestroy { this.chart.ngOnChanges({} as SimpleChanges); } }); - - }, error => { + }).catch(error => { this.datasets = EMPTY_DATASET; this.labels = []; - // TODO should be error message - this.loading = true; + // stop loading spinner + this.loading = false; + // TODO error message }); - } - - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } + }; } \ No newline at end of file diff --git a/ui/src/app/device/history/history.component.html b/ui/src/app/device/history/history.component.html index d3fd77dc0ce..49078cee9a5 100644 --- a/ui/src/app/device/history/history.component.html +++ b/ui/src/app/device/history/history.component.html @@ -1,4 +1,4 @@ -
+
@@ -37,6 +37,7 @@
+
@@ -74,10 +75,10 @@
- +
- +
diff --git a/ui/src/app/device/history/history.component.ts b/ui/src/app/device/history/history.component.ts index 0af140ce70b..b8f25fc602b 100644 --- a/ui/src/app/device/history/history.component.ts +++ b/ui/src/app/device/history/history.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, ElementRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, ElementRef } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; import { Subject } from 'rxjs/Subject'; @@ -7,7 +7,10 @@ import * as d3shape from 'd3-shape'; import * as moment from 'moment'; import { TranslateService } from '@ngx-translate/core'; -import { WebsocketService, Device, ChannelAddresses } from '../../shared/shared'; +import { Device } from '../../shared/device/device'; +import { ConfigImpl } from '../../shared/device/config'; +import { DefaultTypes } from '../../shared/service/defaulttypes'; +import { Websocket } from '../../shared/service/websocket'; type PeriodString = "today" | "yesterday" | "lastWeek" | "lastMonth" | "lastYear" | "otherTimespan"; @@ -15,44 +18,51 @@ type PeriodString = "today" | "yesterday" | "lastWeek" | "lastMonth" | "lastYear selector: 'history', templateUrl: './history.component.html' }) -export class HistoryComponent implements OnInit { +export class HistoryComponent implements OnInit, OnDestroy { public device: Device = null; - public socChannels: ChannelAddresses = {}; + public config: ConfigImpl = null; + public socChannels: DefaultTypes.ChannelAddresses = {}; + public powerChannels: DefaultTypes.ChannelAddresses = {}; public fromDate = null; public toDate = null; public activePeriodText: string = ""; public showOtherTimespan = false; + private stopOnDestroy: Subject = new Subject(); private activePeriod: PeriodString = "today"; - private ngUnsubscribe: Subject = new Subject(); constructor( - public websocketService: WebsocketService, + public websocket: Websocket, private route: ActivatedRoute, private translate: TranslateService ) { } ngOnInit() { - this.websocketService.setCurrentDevice(this.route.snapshot.params).takeUntil(this.ngUnsubscribe).subscribe(device => { - this.device = device; - if (device != null) { - device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { - this.socChannels = config.getEssSocChannels(); - }); - } - }) + this.websocket.setCurrentDevice(this.route) + .takeUntil(this.stopOnDestroy) + .subscribe(device => { + this.device = device; + if (device == null) { + this.config = null; + } else { + device.config + .takeUntil(this.stopOnDestroy) + .subscribe(config => { + this.config = config; + if(config) { + this.socChannels = config.getEssSocChannels(); + this.powerChannels = config.getPowerChannels(); + } else { + this.socChannels = {}; + this.powerChannels = {}; + } + }); + } + }); this.setPeriod("today"); } - ngOnDestroy() { - if (this.device) { - this.device.unsubscribeCurrentData(); - } - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } - /** * This is called by the input button on the UI. * @param period @@ -67,22 +77,22 @@ export class HistoryComponent implements OnInit { switch (period) { case "yesterday": this.fromDate = this.toDate = moment().subtract(1, "days"); - this.activePeriodText = this.translate.instant('Device.History.Yesterday') + ", " + this.fromDate.format("DD.MM.YYYY"); + this.activePeriodText = this.translate.instant('Device.History.Yesterday') + ", " + this.fromDate.format(this.translate.instant('General.DateFormat')); break; case "lastWeek": this.fromDate = moment().subtract(1, "weeks"); this.toDate = moment(); - this.activePeriodText = this.translate.instant('Device.History.LastWeek') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format("DD.MM.YYYY"), value2: this.toDate.format("DD.MM.YYYY") }); + this.activePeriodText = this.translate.instant('Device.History.LastWeek') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format(this.translate.instant('General.DateFormat')), value2: this.toDate.format(this.translate.instant('General.DateFormat')) }); break; case "lastMonth": this.fromDate = moment().subtract(1, "months"); this.toDate = moment(); - this.activePeriodText = this.translate.instant('Device.History.LastMonth') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format("DD.MM.YYYY"), value2: this.toDate.format("DD.MM.YYYY") }); + this.activePeriodText = this.translate.instant('Device.History.LastMonth') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format(this.translate.instant('General.DateFormat')), value2: this.toDate.format(this.translate.instant('General.DateFormat')) }); break; case "lastYear": this.fromDate = moment().subtract(1, "years"); this.toDate = moment(); - this.activePeriodText = this.translate.instant('Device.History.LastYear') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format("DD.MM.YYYY"), value2: this.toDate.format("DD.MM.YYYY") }); + this.activePeriodText = this.translate.instant('Device.History.LastYear') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format(this.translate.instant('General.DateFormat')), value2: this.toDate.format(this.translate.instant('General.DateFormat')) }); break; case "otherTimespan": let fromDate = moment(from); @@ -92,12 +102,12 @@ export class HistoryComponent implements OnInit { } this.fromDate = fromDate; this.toDate = toDate; - this.activePeriodText = this.translate.instant('Device.History.Period') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format("DD.MM.YYYY"), value2: this.toDate.format("DD.MM.YYYY") }); + this.activePeriodText = this.translate.instant('Device.History.Period') + ", " + this.translate.instant('General.PeriodFromTo', { value1: this.fromDate.format(this.translate.instant('General.DateFormat')), value2: this.toDate.format(this.translate.instant('General.DateFormat')) }); break; case "today": default: this.fromDate = this.toDate = moment(); - this.activePeriodText = this.translate.instant('Device.History.Today') + ", " + this.fromDate.format("DD.MM.YYYY"); + this.activePeriodText = this.translate.instant('Device.History.Today') + ", " + this.fromDate.format(this.translate.instant('General.DateFormat')); break; } } @@ -260,5 +270,10 @@ export class HistoryComponent implements OnInit { // } // } + ngOnDestroy() { + this.stopOnDestroy.next(); + this.stopOnDestroy.complete(); + } + } \ No newline at end of file diff --git a/ui/src/app/device/overview/energymonitor/chart/chart.component.html b/ui/src/app/device/overview/energymonitor/chart/chart.component.html index 7dfda58a888..3b9142dac4e 100644 --- a/ui/src/app/device/overview/energymonitor/chart/chart.component.html +++ b/ui/src/app/device/overview/energymonitor/chart/chart.component.html @@ -1,3 +1,4 @@ +
diff --git a/ui/src/app/device/overview/energymonitor/chart/chart.component.ts b/ui/src/app/device/overview/energymonitor/chart/chart.component.ts index c5becf628bb..d35d73bbdee 100644 --- a/ui/src/app/device/overview/energymonitor/chart/chart.component.ts +++ b/ui/src/app/device/overview/energymonitor/chart/chart.component.ts @@ -8,8 +8,7 @@ import { ConsumptionSectionComponent } from './section/consumptionsection.compon import { GridSectionComponent } from './section/gridsection.component'; import { ProductionSectionComponent } from './section/productionsection.component'; import { StorageSectionComponent } from './section/storagesection.component'; - -import { Device, Data } from '../../../../shared/shared'; +import { CurrentDataAndSummary } from '../../../../shared/device/currentdata'; @Component({ selector: 'energymonitor-chart', @@ -32,13 +31,15 @@ export class EnergymonitorChartComponent implements OnInit, OnDestroy { @ViewChild('energymonitorChart') private chartDiv: ElementRef; @Input() - set currentData(currentData: Data) { + set currentData(currentData: CurrentDataAndSummary) { + this.loading = currentData == null; this.updateValue(currentData); } public translation: string; public width: number; public height: number; + public loading: boolean = true; private style: string; private ngUnsubscribe: Subject = new Subject(); @@ -61,7 +62,7 @@ export class EnergymonitorChartComponent implements OnInit, OnDestroy { /** * This method is called on every change of values. */ - updateValue(currentData: Data) { + updateValue(currentData: CurrentDataAndSummary) { if (currentData) { /* * Set values for energy monitor diff --git a/ui/src/app/device/overview/energymonitor/chart/section/abstractsection.component.ts b/ui/src/app/device/overview/energymonitor/chart/section/abstractsection.component.ts index 394645e67fb..00b08f9d15f 100644 --- a/ui/src/app/device/overview/energymonitor/chart/section/abstractsection.component.ts +++ b/ui/src/app/device/overview/energymonitor/chart/section/abstractsection.component.ts @@ -3,7 +3,7 @@ import { TranslateService } from '@ngx-translate/core'; import * as d3 from 'd3'; import { EnergytableComponent } from '../../../energytable/energytable.component'; -import { Device } from '../../../../../shared/shared'; +import { Device } from '../../../../../shared/device/device'; export class SectionValue { absolute: number; @@ -115,6 +115,7 @@ export abstract class AbstractSection { * This method is called on every change of values. */ public updateValue(absolute: number, ratio: number) { + // TODO smoothly resize the arc this.lastValue = { absolute: absolute, ratio: ratio }; this.valueRatio = this.getValueRatio(ratio); this.valueText = this.getValueText(absolute); diff --git a/ui/src/app/device/overview/energymonitor/energymonitor.component.html b/ui/src/app/device/overview/energymonitor/energymonitor.component.html index 4026fbe8d09..01508d0c367 100644 --- a/ui/src/app/device/overview/energymonitor/energymonitor.component.html +++ b/ui/src/app/device/overview/energymonitor/energymonitor.component.html @@ -1,4 +1,5 @@ - + + visibility Device.Overview.Energymonitor.Title diff --git a/ui/src/app/device/overview/energymonitor/energymonitor.component.ts b/ui/src/app/device/overview/energymonitor/energymonitor.component.ts index 8c3941cb0a1..6a8e3c78118 100644 --- a/ui/src/app/device/overview/energymonitor/energymonitor.component.ts +++ b/ui/src/app/device/overview/energymonitor/energymonitor.component.ts @@ -2,7 +2,7 @@ import { Component, Input, OnInit, OnChanges } from '@angular/core'; import { Subject } from 'rxjs/Subject'; import * as d3 from 'd3'; -import { Data } from '../../../shared/shared'; +import { CurrentDataAndSummary } from '../../../shared/device/currentdata'; @Component({ selector: 'energymonitor', @@ -11,5 +11,5 @@ import { Data } from '../../../shared/shared'; export class EnergymonitorComponent { @Input() - public currentData: Data; + public currentData: CurrentDataAndSummary; } diff --git a/ui/src/app/device/overview/energytable/energytable.component.html b/ui/src/app/device/overview/energytable/energytable.component.html index 970676037de..280ea1c7d72 100644 --- a/ui/src/app/device/overview/energytable/energytable.component.html +++ b/ui/src/app/device/overview/energytable/energytable.component.html @@ -1,226 +1,229 @@ -
+
+ format_list_bulleted - Device.Overview.Energymonitor.Title + Device.Overview.Energytable.Title - - - - - - General.StorageSystem - {{ thing }} - - - - - - - - - - + + + + + + + General.StorageSystem + {{ thing }} + + +
General.Soc{{ data.Soc }}%
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Device.Overview.Energymonitor.ChargePower{{ data.ActivePower | sign }}0W
Device.Overview.Energymonitor.DischargePower{{ data.ActivePower }}0W
Device.Overview.Energymonitor.ReactivePower{{ data.ReactivePower }}var
Device.Overview.Energymonitor.ActivePowerL1{{ data.ActivePowerL1 }}W
L2{{ data.ActivePowerL2 }}W
L3{{ data.ActivePowerL3 }}W
-
-
-
- - - - - - - Device.Overview.Energymonitor.GridMeter - {{ thing }} - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
General.GridBuyGeneral.Soc {{ data.ActivePower }}0W
General.GridSell{{ data.ActivePower | sign }}0W
Device.Overview.Energymonitor.ReactivePower{{ data.ReactivePower }}var
General.GridSellL1{{ data.ActivePowerL1 | sign }}W
General.GridBuyL1{{ data.ActivePowerL1 }}W
General.GridSellL2{{ data.ActivePowerL2 | sign }}W
General.GridBuyL2{{ data.ActivePowerL2 }}W
General.GridSellL3{{ data.ActivePowerL3 | sign }}W
General.GridBuyL3{{ data.ActivePowerL3 }}W
-
-
-
+ {{ data.Soc }} + % + + + + Device.Overview.Energymonitor.ChargePower + + {{ data.ActivePower | sign }} + 0 + W + + + Device.Overview.Energymonitor.DischargePower + + {{ data.ActivePower }} + 0 + W + + + Device.Overview.Energymonitor.ReactivePower + + {{ data.ReactivePower }} + var + + + + + Device.Overview.Energymonitor.ActivePower + L1 + {{ data.ActivePowerL1 }} + W + + + + L2 + {{ data.ActivePowerL2 }} + W + + + + L3 + {{ data.ActivePowerL3 }} + W + + + + + + - - - - - - Device.Overview.Energymonitor.ProductionMeter - {{ thing }} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
General.Production{{ data.ActualPower }}W
General.Production{{ data.ActivePower }}W
Device.Overview.Energymonitor.ReactivePower{{ data.ReactivePower }}var
General.ProductionL1{{ data.ActivePowerL1 }}W
L2{{ data.ActivePowerL2 }}W
L3{{ data.ActivePowerL3 }}W
-
-
-
+ + + + + + Device.Overview.Energymonitor.GridMeter + {{ thing }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
General.GridBuy{{ data.ActivePower }}0W
General.GridSell{{ data.ActivePower | sign }}0W
Device.Overview.Energymonitor.ReactivePower{{ data.ReactivePower }}var
General.GridSellL1{{ data.ActivePowerL1 | sign }}W
General.GridBuyL1{{ data.ActivePowerL1 }}W
General.GridSellL2{{ data.ActivePowerL2 | sign }}W
General.GridBuyL2{{ data.ActivePowerL2 }}W
General.GridSellL3{{ data.ActivePowerL3 | sign }}W
General.GridBuyL3{{ data.ActivePowerL3 }}W
+
+
+
- - - - - - General.Consumption - - - - - - - - - - - -
General.Power{{ data.activePower }}W
-
-
+ + + + + + Device.Overview.Energymonitor.ProductionMeter + {{ thing }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
General.Production{{ data.ActualPower }}W
General.Production{{ data.ActivePower }}W
Device.Overview.Energymonitor.ReactivePower{{ data.ReactivePower }}var
General.ProductionL1{{ data.ActivePowerL1 }}W
L2{{ data.ActivePowerL2 }}W
L3{{ data.ActivePowerL3 }}W
+
+
+
+ + + + + + + General.Consumption + + + + + + + + + + + +
General.Power{{ data.activePower }}W
+
+
+
\ No newline at end of file diff --git a/ui/src/app/device/overview/energytable/energytable.component.ts b/ui/src/app/device/overview/energytable/energytable.component.ts index f02779f948c..096ba44997e 100644 --- a/ui/src/app/device/overview/energytable/energytable.component.ts +++ b/ui/src/app/device/overview/energytable/energytable.component.ts @@ -1,8 +1,9 @@ import { Component, Input, OnDestroy } from '@angular/core'; import { Subject } from 'rxjs/Subject'; - -import { Device, Data, Config, TemplateHelper } from '../../../shared/shared'; +import { Utils } from '../../../shared/service/utils'; +import { DefaultTypes } from '../../../shared/service/defaulttypes'; +import { CurrentDataAndSummary } from '../../../shared/device/currentdata'; @Component({ selector: 'energytable', @@ -11,10 +12,10 @@ import { Device, Data, Config, TemplateHelper } from '../../../shared/shared'; export class EnergytableComponent { @Input() - public currentData: Data; + public currentData: CurrentDataAndSummary; @Input() - public config: Config; + public config: DefaultTypes.Config; - constructor(public tmpl: TemplateHelper) { } + constructor(public utils: Utils) { } } diff --git a/ui/src/app/device/overview/fieldstatus/fieldstatus.component.html b/ui/src/app/device/overview/fieldstatus/fieldstatus.component.html index d7ce4f0b48f..c3069803e07 100644 --- a/ui/src/app/device/overview/fieldstatus/fieldstatus.component.html +++ b/ui/src/app/device/overview/fieldstatus/fieldstatus.component.html @@ -12,19 +12,19 @@ - + - + {{ field.title }} - + - {{ field.map[data[thing][channel]] }} + {{ field.map[data[thing][channel]] }} {{ data[thing][channel] }} {{ field.unit }} diff --git a/ui/src/app/device/overview/fieldstatus/fieldstatus.component.ts b/ui/src/app/device/overview/fieldstatus/fieldstatus.component.ts index f39f36aed01..f24aed981ff 100644 --- a/ui/src/app/device/overview/fieldstatus/fieldstatus.component.ts +++ b/ui/src/app/device/overview/fieldstatus/fieldstatus.component.ts @@ -2,8 +2,9 @@ import { Component, Input } from '@angular/core'; import { Router, ActivatedRoute, Params } from '@angular/router'; import { Subject } from 'rxjs/Subject'; -import { WebsocketService, Device, Data, Config, TemplateHelper } from '../../../shared/shared'; +import { Websocket, Utils } from '../../../shared/shared'; import { CustomFieldDefinition } from '../../../shared/type/customfielddefinition'; +import { CurrentDataAndSummary } from '../../../shared/device/currentdata'; @Component({ selector: 'fieldstatus', @@ -12,10 +13,10 @@ import { CustomFieldDefinition } from '../../../shared/type/customfielddefinitio export class FieldstatusComponent { @Input() - public currentData: Data; + public currentData: CurrentDataAndSummary; @Input() public fielddefinition: CustomFieldDefinition; - constructor(public tmpl: TemplateHelper) { } + constructor(public utils: Utils) { } } diff --git a/ui/src/app/device/overview/history/history.component.html b/ui/src/app/device/overview/history/history.component.html index bc3726ca743..1edaa2cc454 100644 --- a/ui/src/app/device/overview/history/history.component.html +++ b/ui/src/app/device/overview/history/history.component.html @@ -4,17 +4,11 @@ General.History - -
- +
+
- -
- -
-
- + \ No newline at end of file diff --git a/ui/src/app/device/overview/history/history.component.ts b/ui/src/app/device/overview/history/history.component.ts index da6a6a58786..4da431927b9 100644 --- a/ui/src/app/device/overview/history/history.component.ts +++ b/ui/src/app/device/overview/history/history.component.ts @@ -1,40 +1,32 @@ -import { Component, Input, OnInit, OnDestroy } from '@angular/core'; +import { Component, Input, OnChanges } from '@angular/core'; import { Subject } from 'rxjs/Subject'; import * as moment from 'moment'; -import { Device, Dataset, ChannelAddresses } from '../../../shared/shared'; - -// spinner component -import { SpinnerComponent } from '../../../shared/spinner.component'; +import { ConfigImpl } from '../../../shared/device/config'; +import { Device } from '../../../shared/device/device'; +import { DefaultTypes } from '../../../shared/service/defaulttypes'; @Component({ selector: 'history', templateUrl: './history.component.html' }) -export class HistoryComponent implements OnInit, OnDestroy { - - @Input() - public device: Device; +export class HistoryComponent implements OnChanges { - public socChannels: ChannelAddresses = {}; - public fromDate = moment(); - public toDate = moment(); - public loading: boolean = true; + @Input() public config: ConfigImpl; - private ngUnsubscribe: Subject = new Subject(); + @Input() public device: Device; - ngOnInit() { - if (this.device != null) { - this.loading = true; - this.device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { - this.socChannels = config.getEssSocChannels(); - this.loading = false; - }); + ngOnChanges() { + if (this.device != null && this.config != null) { + this.socChannels = this.config.getEssSocChannels(); + } else { + this.socChannels = {}; } } - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } + public socChannels: DefaultTypes.ChannelAddresses = {}; + + // show the chart for today + public fromDate = moment(); + public toDate = moment(); } diff --git a/ui/src/app/device/overview/overview.component.html b/ui/src/app/device/overview/overview.component.html index a7b0b37b503..31af6c0ef6b 100644 --- a/ui/src/app/device/overview/overview.component.html +++ b/ui/src/app/device/overview/overview.component.html @@ -1,8 +1,9 @@ -
+
- - + + +
\ No newline at end of file diff --git a/ui/src/app/device/overview/overview.component.ts b/ui/src/app/device/overview/overview.component.ts index 25b23e8f9ce..875c5a596c1 100644 --- a/ui/src/app/device/overview/overview.component.ts +++ b/ui/src/app/device/overview/overview.component.ts @@ -3,7 +3,12 @@ import { Router, ActivatedRoute, Params } from '@angular/router'; import { Subscription } from 'rxjs/Subscription'; import { Subject } from 'rxjs/Subject'; -import { WebsocketService, Websocket, Notification, Device, Data, Config } from '../../shared/shared'; +import { Device } from '../../shared/device/device'; +import { DefaultTypes } from '../../shared/service/defaulttypes'; +import { Utils } from '../../shared/shared'; +import { ConfigImpl } from '../../shared/device/config'; +import { CurrentDataAndSummary } from '../../shared/device/currentdata'; +import { Websocket } from '../../shared/shared'; import { CustomFieldDefinition } from '../../shared/type/customfielddefinition'; import { environment } from '../../../environments'; @@ -13,50 +18,76 @@ import { environment } from '../../../environments'; }) export class OverviewComponent implements OnInit, OnDestroy { - public device: Device; - public currentData: Data; - public config: Config; - public customFields: CustomFieldDefinition = {}; + public device: Device = null + public config: ConfigImpl = null; + public currentData: CurrentDataAndSummary = null; + //public customFields: CustomFieldDefinition = {}; - private ngUnsubscribe: Subject = new Subject(); + private stopOnDestroy: Subject = new Subject(); + private currentDataTimeout: number; constructor( - public websocketService: WebsocketService, - private route: ActivatedRoute + public websocket: Websocket, + private route: ActivatedRoute, + public utils: Utils ) { } ngOnInit() { - this.websocketService.setCurrentDevice(this.route.snapshot.params).takeUntil(this.ngUnsubscribe).subscribe(device => { - this.device = device; - if (device != null) { - this.device.config.takeUntil(this.ngUnsubscribe).subscribe(config => { - this.config = config; - this.customFields = environment.getCustomFields(config); - let channels = config.getImportantChannels(); - /* - * Add custom fields for fieldstatus component - */ - for (let thing in this.customFields) { - let thingChannels = [] - for (let channel in this.customFields[thing]) { - thingChannels.push(channel); - } - channels[thing] = thingChannels; - } - - device.subscribeCurrentData(channels).takeUntil(this.ngUnsubscribe).subscribe(currentData => { - this.currentData = currentData; - }); - }) - } - }) + this.websocket.setCurrentDevice(this.route) + .takeUntil(this.stopOnDestroy) + .subscribe(device => { + this.device = device; + if (device == null) { + this.config = null; + } else { + + device.config + .takeUntil(this.stopOnDestroy) + .subscribe(config => { + this.config = config; + if (config != null) { + + let channels = config.getImportantChannels(); + device.subscribeCurrentData(channels) + .takeUntil(this.stopOnDestroy) + .subscribe(currentData => { + this.currentData = currentData; + + // resubscribe on timeout + clearInterval(this.currentDataTimeout); + this.currentDataTimeout = window.setInterval(() => { + this.currentData = null; + if (this.websocket.status == 'online') { + device.subscribeCurrentData(channels); + } + }, Websocket.TIMEOUT); + }); + } + // TODO fieldstatus + // /* + // * Add custom fields for fieldstatus component + // */ + // for (let thing in this.customFields) { + // let thingChannels = [] + // for (let channel in this.customFields[thing]) { + // thingChannels.push(channel); + // } + // channels[thing] = thingChannels; + // } + }); + } + }); } ngOnDestroy() { + clearInterval(this.currentDataTimeout); if (this.device) { this.device.unsubscribeCurrentData(); } - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); + this.device = null; + this.config = null; + this.currentData = null; + this.stopOnDestroy.next(); + this.stopOnDestroy.complete(); } } \ No newline at end of file diff --git a/ui/src/app/overview/overview.component.html b/ui/src/app/overview/overview.component.html index e0256143aee..7e8854bf79f 100644 --- a/ui/src/app/overview/overview.component.html +++ b/ui/src/app/overview/overview.component.html @@ -1,48 +1,63 @@
-
- -
- - +
+ + + + face + Overview.ConnectionSuccessful + {{ websocket.url }} + + + stop + Overview.ConnectionFailed + {{ websocket.url }} + + + + play + Connecting... + + {{ websocket.url }} + + + + stop - Overview.ConnectionFailed - {{ websocket.url }} - - - face - Overview.ConnectionSuccessful + Bitte geben Sie Ihre Zugangsdaten ein + - - - +

{{ (websocket.event | async)?.message }}

- - - + + + -
- - - - - - - - folder - error - Overview.ToEnergymonitor - {{ dev.value.comment }}
Overview.IsOffline
- {{ dev.key }}: {{ dev.value.producttype }} -
- - - -
-
+ + + + + + + + folder + error + Overview.ToEnergymonitor + + {{ device.comment }}
Overview.IsOffline
+
+
+ + {{ device.name }}: {{ device.producttype }} + +
+ + + +
\ No newline at end of file diff --git a/ui/src/app/overview/overview.component.ts b/ui/src/app/overview/overview.component.ts index 12919e688b0..78189fc546c 100644 --- a/ui/src/app/overview/overview.component.ts +++ b/ui/src/app/overview/overview.component.ts @@ -4,82 +4,53 @@ import { FormGroup, FormBuilder } from '@angular/forms'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; import { Subscription } from 'rxjs/Subscription'; import { Subject } from 'rxjs/Subject'; +import { Observable } from 'rxjs/Observable'; import { TranslateService } from '@ngx-translate/core'; import { environment } from '../../environments'; -import { WebappService, WebsocketService, Websocket, Notification, TemplateHelper } from '../shared/shared'; +import { Service, Websocket, Utils } from '../shared/shared'; +import { DefaultMessages } from '../shared/service/defaultmessages'; @Component({ selector: 'overview', templateUrl: './overview.component.html' }) -export class OverviewComponent implements OnInit, OnDestroy { +export class OverviewComponent { + public env = environment; + public form: FormGroup; - public forms: FormGroup[] = []; - - private ngUnsubscribe: Subject = new Subject(); + private stopOnDestroy: Subject = new Subject(); constructor( - private websocketService: WebsocketService, - private webappService: WebappService, - private tmpl: TemplateHelper, - private router: Router, + public websocket: Websocket, + public utils: Utils, + private translate: TranslateService, private formBuilder: FormBuilder, - private translate: TranslateService) { - } - - ngOnInit() { - this.websocketService.clearCurrentDevice(); - for (let websocketName in this.websocketService.websockets) { - let websocket = this.websocketService.websockets[websocketName]; - websocket.event.takeUntil(this.ngUnsubscribe).subscribe(notification => this.websocketEvent(notification)); - let form: FormGroup = this.formBuilder.group({ - "password": this.formBuilder.control('user') - }); - form['_websocket'] = websocket; - this.forms.push(form); - } - } - - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); + private router: Router) { + this.form = formBuilder.group({ + "password": formBuilder.control('user') + }); + websocket.devices.takeUntil(this.stopOnDestroy).subscribe(devices => { + if (Object.keys(devices).length == 1) { + // redirect if only one device + let device = devices[Object.keys(devices)[0]]; + this.router.navigate(['/device', device.name]); + } + }) } - doLogin(form: FormGroup) { - let websocket: Websocket = form['_websocket']; - let password: string = form.value['password']; - websocket.connectWithPassword(password); + doLogin() { + let password: string = this.form.value['password']; + this.websocket.send(DefaultMessages.authenticateLogin(password)); } doLogout(form: FormGroup) { - let websocket: Websocket = form['_websocket']; - websocket.isConnected = false; - websocket.close(); + this.websocket.close(); } - websocketEvent(notification: Notification) { - let allConnected = true; - let noOfConnectedDevices = 0; - let lastDevice = null; - for (let websocketName in this.websocketService.websockets) { - let websocket = this.websocketService.websockets[websocketName]; - if (websocket.isConnected) { - for (let deviceName in websocket.devices) { - noOfConnectedDevices++; - lastDevice = websocket.devices[deviceName]; - } - } else { - allConnected = false; - break; - } - } - if (allConnected) { - this.webappService.notify({ - type: "success", - message: this.translate.instant('Overview.AllConnected') - }); - } + onDestroy() { + this.stopOnDestroy.next(); + this.stopOnDestroy.complete(); } } diff --git a/ui/src/app/shared/device/config.ts b/ui/src/app/shared/device/config.ts index 5647fbc15e1..91e2b69128a 100644 --- a/ui/src/app/shared/device/config.ts +++ b/ui/src/app/shared/device/config.ts @@ -1,110 +1,99 @@ -import { Role, ROLES } from '../type/role'; +import { DefaultTypes } from '../service/defaulttypes' -export interface ChannelAddresses { - [thing: string]: string[]; -} +export class ConfigImpl implements DefaultTypes.Config { -export interface Meta { - natures: { - [thing: string]: { - channels: {}, - implements: string[] + // Attributes from Config interface + public readonly things: { + [id: string]: { + id: string, + class: string | string[], + [channel: string]: any } - }, - availableBridges: { - [thing: string]: ThingMeta - }, - availableControllers: { - [thing: string]: ThingMeta - }, - availableSchedulers: { - [thing: string]: ThingMeta - }, - availableDevices: { - [thing: string]: ThingMeta - } -} - -interface Channel { - name: string, - title: string, - type: "Integer" | "String" - optional: boolean, - array: boolean, - accessLevel: string -} - -export interface ThingMeta { - class: string, - text: string, - title: string, - channels: { - [thing: string]: Channel[] - } -} - -interface Thing { - id: string, - class: string -} - -interface Scheduler extends Thing { - controllers: [{ - id: string, - class: string - }] -} - -interface Bridge extends Thing { - devices: [{ - id: string, - class: string - }] -} - -export class Config { - public readonly _meta: Meta; - public readonly persistence: [{ class: string }]; - public readonly scheduler: Scheduler; - public readonly things: Bridge[] + }; + public readonly meta: { + [clazz: string]: { + implements: [string], + channels: { + [channel: string]: { + name: string, + title: string, + type: string | string[], + optional: boolean, + array: boolean, + accessLevel: string + } + } + } + }; - // A list of thing ids which are EssNatures. (e.g. ["ess0", "ess1"]) + // A list of thing ids which are matching Natures. (e.g. ["ess0", "ess1"]) public readonly storageThings: string[] = []; public readonly gridMeters: string[] = []; public readonly productionMeters: string[] = []; + public readonly bridges: string[] = []; + public readonly scheduler: string = null; + public readonly controllers: string[] = []; + public readonly simulatorDevices: string[] = []; - constructor(config: any) { + constructor(private readonly config: DefaultTypes.Config) { Object.assign(this, config); let storageThings: string[] = [] let gridMeters: string[] = []; let productionMeters: string[] = []; - - if (this._meta && "natures" in this._meta) { - let natures = this._meta.natures; - for (let thing in natures) { - if ("implements" in natures[thing]) { - let i = natures[thing].implements; - // Ess - if (i.includes("EssNature")) { - if (!i.includes("EssClusterNature")) { // ignore cluster - storageThings.push(thing); - } - } - // Meter - if (i.includes("MeterNature")) { - let type = natures[thing].channels["type"]["value"]; - if (type === "grid") { - gridMeters.push(thing); - } else if (type === "production") { - productionMeters.push(thing); + let bridges: string[] = []; + let scheduler: string = null; + let controllers: string[] = []; + let simulatorDevices: string[] = []; + + for (let thingId in config.things) { + let thing = config.things[thingId]; + let i = config.things[thingId].class; + if (i instanceof Array) { + /* + * Natures + */ + // Ess + if (i.includes("EssNature") && !i.includes("EssClusterNature") /* ignore cluster */) { + storageThings.push(thingId); + } + // Meter + if (i.includes("MeterNature")) { + if ("type" in thing) { + if (thing.type == 'grid') { + gridMeters.push(thingId); + } else if (thing.type === "production") { + productionMeters.push(thingId); } else { console.warn("Meter without type: " + thing); } } - // Charger - if (i.includes("ChargerNature")) { - productionMeters.push(thing); + } + // Charger + if (i.includes("ChargerNature")) { + productionMeters.push(thingId); + } + } else { + /* + * Other Things + */ + if (i in config.meta) { + i = config.meta[i].implements + // Bridge + if (i.includes("io.openems.api.bridge.Bridge")) { + bridges.push(thingId); + } + // Scheduler + if (i.includes("io.openems.api.scheduler.Scheduler")) { + scheduler = thingId; + } + // Controller + if (i.includes("io.openems.api.controller.Controller")) { + controllers.push(thingId); + } + // Simulator Devices + if (i.includes("io.openems.impl.device.simulator.Simulator")) { + simulatorDevices.push(thingId); } } } @@ -113,30 +102,23 @@ export class Config { this.storageThings = storageThings.sort(); this.gridMeters = gridMeters.sort(); this.productionMeters = productionMeters.sort(); + this.bridges = bridges.sort(); + this.scheduler = scheduler; + this.controllers = controllers; + this.simulatorDevices = simulatorDevices; } /** - * Returns ChannelAddresses of ESS Soc channels - */ - public getEssSocChannels(): ChannelAddresses { - let channels: ChannelAddresses = {}; - this.storageThings.forEach(device => channels[device] = ['Soc']); - return channels; - } - - /** - * Return ChannelAddresses of power channels - */ - public getPowerChannels(): ChannelAddresses { - let natures = this._meta.natures; + * Return ChannelAddresses of power channels + */ + public getPowerChannels(): DefaultTypes.ChannelAddresses { let ignoreNatures = { EssClusterNature: true }; - let result = {} - for (let thing in natures) { - let i = natures[thing].implements; + let result: DefaultTypes.ChannelAddresses = {} + for (let thingId in this.config.things) { + let i = this.config.things[thingId].class; let channels = []; - // ESS - if (!i.includes("EssClusterNature")) { // ignore cluster + if (i.includes("EssNature") && !i.includes("EssClusterNature") /* ignore cluster */) { if (i.includes("AsymmetricEssNature")) { channels.push("ActivePowerL1", "ActivePowerL2", "ActivePowerL3", "ReactivePowerL1", "ReactivePowerL2", "ReactivePowerL3"); } else if (i.includes("SymmetricEssNature")) { @@ -146,18 +128,39 @@ export class Config { ignoreNatures["AsymmetricMeterNature"] = true; } } - // Meter - if (i.includes("AsymmetricMeterNature") && !ignoreNatures["AsymmetricMeterNature"]) { - channels.push("ActivePowerL1", "ActivePowerL2", "ActivePowerL3", "ReactivePowerL1", "ReactivePowerL2", "ReactivePowerL3"); - } else if (i.includes("SymmetricMeterNature")) { - channels.push("ActivePower", "ReactivePower"); + if (i.includes("MeterNature")) { + if (i.includes("AsymmetricMeterNature") && !ignoreNatures["AsymmetricMeterNature"]) { + channels.push("ActivePowerL1", "ActivePowerL2", "ActivePowerL3", "ReactivePowerL1", "ReactivePowerL2", "ReactivePowerL3"); + } else if (i.includes("SymmetricMeterNature")) { + channels.push("ActivePower", "ReactivePower"); + } } // Charger if (i.includes("ChargerNature")) { channels.push("ActualPower"); } - result[thing] = channels; + // store result + if (channels.length > 0) { + result[thingId] = channels; + } + } + return result; + } + + /** + * Returns ChannelAddresses of ESS Soc channels + */ + public getEssSocChannels(): DefaultTypes.ChannelAddresses { + let result: DefaultTypes.ChannelAddresses = {} + for (let thingId of this.storageThings) { + let channels = []; + // ESS + channels.push("Soc"); + // store result + if (channels.length > 0) { + result[thingId] = channels; + } } return result; } @@ -165,30 +168,18 @@ export class Config { /** * Return ChannelAddresses of power and soc channels */ - public getImportantChannels(): ChannelAddresses { - let channels: ChannelAddresses = this.getPowerChannels(); + public getImportantChannels(): DefaultTypes.ChannelAddresses { + let channels: DefaultTypes.ChannelAddresses = this.getPowerChannels(); let essChannels = this.getEssSocChannels(); + // join/merge both results for (let thing in essChannels) { if (thing in channels) { - let arr = essChannels[thing]; - channels[thing] = channels[thing].concat(arr); + channels[thing] = channels[thing].concat(essChannels[thing]); } else { channels[thing] = essChannels[thing]; } } return channels; } -} - -// private refreshThingsFromConfig(): Things { -// let result = new Things(); -// let config = this.config.getValue(); -// if ("_meta" in config && "natures" in config._meta) { -// let natures = this.config.getValue()._meta.natures; -// for (let thing in natures) { -// let i = natures[thing]["implements"]; -// } -// } -// return result; -// } \ No newline at end of file +} \ No newline at end of file diff --git a/ui/src/app/shared/device/currentdata.ts b/ui/src/app/shared/device/currentdata.ts new file mode 100644 index 00000000000..c10884890a3 --- /dev/null +++ b/ui/src/app/shared/device/currentdata.ts @@ -0,0 +1,154 @@ +import { DefaultTypes } from '../service/defaulttypes'; +import { ConfigImpl } from './config'; +import { Utils } from '../service/utils'; + +export class CurrentDataAndSummary { + public readonly summary: DefaultTypes.Summary; + + constructor(public data: DefaultTypes.Data, config: ConfigImpl) { + this.summary = this.calculateSummary(data, config); + } + + private calculateSummary(currentData: DefaultTypes.Data, config: ConfigImpl): DefaultTypes.Summary { + let result: DefaultTypes.Summary = { + storage: { + soc: null, + activePower: null, + maxActivePower: null + }, production: { + powerRatio: null, + activePower: null, // sum of activePowerAC and activePowerDC + activePowerAC: null, + activePowerDC: null, + maxActivePower: null + }, grid: { + powerRatio: null, + activePower: null, + maxActivePower: null, + minActivePower: null + }, consumption: { + powerRatio: null, + activePower: null + } + }; + + { + /* + * Storage + */ + let soc = null; + let activePower = null; + let countSoc = 0; + for (let thing of config.storageThings) { + if (thing in currentData) { + let essData = currentData[thing]; + if ("Soc" in essData) { + soc = Utils.addSafely(soc, essData.Soc); + countSoc += 1; + } + activePower = Utils.addSafely(activePower, this.getActivePower(essData)); + } + } + result.storage.soc = Utils.divideSafely(soc, countSoc); + result.storage.activePower = activePower; + } + + { + /* + * Grid + */ + let powerRatio = 0; + let activePower = null; + let maxActivePower = 0; + let minActivePower = 0; + for (let thing of config.gridMeters) { + let meterData = currentData[thing]; + let meterConfig = config.things[thing]; + activePower = Utils.addSafely(activePower, this.getActivePower(meterData)); + if ("maxActivePower" in meterConfig) { + maxActivePower += meterConfig.maxActivePower; + } + if ("minActivePower" in meterConfig) { + minActivePower += meterConfig.minActivePower; + } + } + // calculate ratio + if (activePower == null) { + if (activePower > 0) { + powerRatio = 50 * activePower / maxActivePower + } else { + powerRatio = -50 * activePower / minActivePower + } + } + result.grid.powerRatio = powerRatio; + result.grid.activePower = activePower; + result.grid.maxActivePower = maxActivePower; + result.grid.minActivePower = minActivePower; + } + + { + /* + * Production + */ + let powerRatio = 0; + let activePowerAC = null; + let activePowerDC = null; + let maxActivePower = 0; + for (let thing of config.productionMeters) { + let meterData = currentData[thing]; + let meterConfig = config.things[thing]; + activePowerAC = Utils.addSafely(activePowerAC, this.getActivePower(meterData)); + if ("ActualPower" in meterData && meterData.ActualPower != null) { + activePowerDC = Utils.addSafely(activePowerDC, meterData.ActualPower); + } + if ("maxActivePower" in meterConfig) { + maxActivePower += meterConfig.maxActivePower; + } + if ("maxActualPower" in meterConfig) { + maxActivePower += meterConfig.maxActualPower; + } + } + + // correct negative production + if (activePowerAC < 0) { + console.warn("negative production? ", config.productionMeters, activePowerAC) + // TODO activePowerAC = 0; + } + if (maxActivePower < 0) { maxActivePower = 0; } + + if (maxActivePower == 0) { + powerRatio = 100; + } else { + let activePowerACDC = Utils.addSafely(activePowerAC, activePowerDC); + powerRatio = Utils.divideSafely(activePowerACDC, (maxActivePower / 100)); + } + + result.production.powerRatio = powerRatio; + result.production.activePowerAC = activePowerAC; + result.production.activePowerDC = activePowerDC; + result.production.activePower = Utils.addSafely(activePowerAC, activePowerDC); + result.production.maxActivePower = maxActivePower; + } + + { + /* + * Consumption + */ + let activePower = Utils.addSafely(Utils.addSafely(result.grid.activePower, result.production.activePowerAC), result.storage.activePower); + let maxActivePower = result.grid.maxActivePower + result.production.maxActivePower + result.storage.maxActivePower; + result.consumption.powerRatio = Utils.divideSafely(activePower, (maxActivePower / 100)); + result.consumption.activePower = activePower; + } + return result; + } + + private getActivePower(o: any): number { + if ("ActivePowerL1" in o && o.ActivePowerL1 != null && "ActivePowerL2" in o && o.ActivePowerL2 != null && "ActivePowerL3" in o && o.ActivePowerL3 != null) { + return o.ActivePowerL1 + o.ActivePowerL2 + o.ActivePowerL3; + } else if ("ActivePower" in o && o.ActivePower != null) { + return o.ActivePower; + } else { + return null; + } + } +} \ No newline at end of file diff --git a/ui/src/app/shared/device/data.ts b/ui/src/app/shared/device/data.ts deleted file mode 100644 index 20722ea4cca..00000000000 --- a/ui/src/app/shared/device/data.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { Config } from './config'; - -export class Summary { - public readonly storage = { - soc: null, - activePower: 0, - maxActivePower: 0 - }; - public readonly production = { - powerRatio: 0, - activePower: 0, // sum of activePowerAC and activePowerDC - activePowerAC: 0, - activePowerDC: 0, - maxActivePower: 0 - }; - public readonly grid = { - powerRatio: 0, - activePower: 0, - maxActivePower: 0 - }; - public readonly consumption = { - powerRatio: 0, - activePower: 0 - }; - - /** - * Calculate summary data from websocket reply - */ - constructor(config: Config, data: ChannelData) { - function getActivePower(o: any): number { - if ("ActivePowerL1" in o && o.ActivePowerL1 != null && "ActivePowerL2" in o && o.ActivePowerL2 != null && "ActivePowerL3" in o && o.ActivePowerL3 != null) { - return o.ActivePowerL1 + o.ActivePowerL2 + o.ActivePowerL3; - } else if ("ActivePower" in o && o.ActivePower != null) { - return o.ActivePower; - } else { - return 0; - } - } - - { - /* - * Storage - */ - let soc = 0; - let activePower = 0; - let essThings = config.storageThings; - let countSoc = 0; - for (let thing of essThings) { - if (thing in data) { - let ess = data[thing]; - if ("Soc" in ess && ess.Soc != null) { - soc += ess.Soc; - countSoc += 1; - } - activePower += getActivePower(ess); - } - } - this.storage.soc = soc / countSoc; - this.storage.activePower = activePower; - } - - { - /* - * Grid - */ - let powerRatio = 0; - let activePower = 0; - let maxActivePower = 0; - for (let thing of config.gridMeters) { - if (thing in data) { - let thingChannels = config._meta.natures[thing].channels; - let meter = data[thing]; - let power = getActivePower(meter); - if (thingChannels["maxActivePower"]) { - if (activePower > 0) { - powerRatio = (power * 50.) / thingChannels["maxActivePower"]["value"] - } else { - powerRatio = (power * -50.) / thingChannels["minActivePower"]["value"] - } - } else { - console.log("no maxActivePower Grid"); - } - activePower += power; - maxActivePower += thingChannels["maxActivePower"]["value"]; - } - } - this.grid.powerRatio = powerRatio; - this.grid.activePower = activePower; - this.grid.maxActivePower = maxActivePower; - } - - { - /* - * Production - */ - let powerRatio = 0; - let activePowerAC = 0; - let activePowerDC = 0; - let maxActivePower = 0; - for (let thing of config.productionMeters) { - if (thing in data) { - let thingChannels = config._meta.natures[thing].channels; - let meter = data[thing]; - activePowerAC += getActivePower(meter); - if ("ActualPower" in meter && meter.ActualPower != null) { - activePowerDC += meter.ActualPower; - } - if (thingChannels["maxActivePower"]) { - maxActivePower += thingChannels["maxActivePower"]["value"]; - } else { - // no maxActivePower - } - if (thingChannels["maxActualPower"]) { - maxActivePower += thingChannels["maxActualPower"]["value"]; - } else { - // no maxActualPower - } - } - } - - // correct negative production - if (activePowerAC < 0) { - // console.warn("negative production? ", this) - activePowerAC = 0; - } - if (maxActivePower < 0) { maxActivePower = 0; } - - if (maxActivePower == 0) { - powerRatio = 100; - } else { - powerRatio = ((activePowerAC + activePowerDC) * 100.) / maxActivePower; - } - - this.production.powerRatio = powerRatio; - this.production.activePowerAC = activePowerAC; - this.production.activePowerDC = activePowerDC; - this.production.activePower = activePowerAC + activePowerDC; - this.production.maxActivePower = maxActivePower; - } - - { - /* - * Consumption - */ - let activePower = this.grid.activePower + this.production.activePowerAC + this.storage.activePower; - let maxActivePower = this.grid.maxActivePower + this.production.maxActivePower + this.storage.maxActivePower; - this.consumption.powerRatio = (activePower * 100.) / maxActivePower; - this.consumption.activePower = activePower; - } - } -} - -export class ChannelData { - [thing: string]: { - [channel: string]: number; - } -} - -export class Data { - public readonly summary: Summary; - - constructor( - public readonly data: ChannelData, - private config: Config - ) { - this.summary = new Summary(config, data); - } -} - - -// private getkWhResult(channels: { [thing: string]: [string] }): { [thing: string]: [string] } { -// let kWh = {}; -// let thingChannel = []; - -// for (let type in this.things) { -// for (let thing in this.things[type]) { -// for (let channel in channels[thing]) { -// if (channels[thing][channel] == "ActivePower") { -// kWh[thing + "/ActivePower"] = type; -// } else if (channels[thing][channel] == "ActivePowerL1" || channels[thing][channel] == "ActivePowerL2" || channels[thing][channel] == "ActivePowerL3") { -// kWh[thing + "/ActivePowerL1"] = type; -// kWh[thing + "/ActivePowerL2"] = type; -// kWh[thing + "/ActivePowerL3"] = type; -// } -// } -// } -// } - -// return kWh; -// } \ No newline at end of file diff --git a/ui/src/app/shared/device/device.ts b/ui/src/app/shared/device/device.ts index 21531efd9b7..fac4d4c4eca 100644 --- a/ui/src/app/shared/device/device.ts +++ b/ui/src/app/shared/device/device.ts @@ -1,15 +1,19 @@ import { Subject } from 'rxjs/Subject'; import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { ReplaySubject } from 'rxjs/ReplaySubject'; +import { Observer } from 'rxjs/Observer'; +import { Observable } from 'rxjs/Observable'; import { UUID } from 'angular2-uuid'; import * as moment from 'moment'; -import { Notification, Websocket } from '../shared'; -import { Config, ChannelAddresses } from './config'; -import { Data, ChannelData, Summary } from './data'; +import { Websocket } from '../shared'; +import { ConfigImpl } from './config'; +import { CurrentDataAndSummary } from './currentdata'; +import { DefaultMessages } from '../service/defaultmessages'; +import { DefaultTypes } from '../service/defaulttypes'; +import { Utils } from '../service/utils'; import { Role, ROLES } from '../type/role'; -export { Data, ChannelData, Summary, Config, ChannelAddresses }; - export class Log { timestamp: number; time: string = ""; @@ -19,271 +23,171 @@ export class Log { message: string; } -export class QueryReply { - requestId: string; - data: [{ - time: string - channels: ChannelData - }] -} - export class Device { + constructor( + public readonly name: string, + public readonly comment: string, + public readonly producttype: string, + public readonly role: Role, + public online: boolean, + private replyStreams: { [id: string]: Subject }, + private websocket: Websocket + ) { + // prepare stream/obersable for currentData + let currentDataStream = replyStreams["currentData"] = new Subject(); + this.currentData = currentDataStream + .map(message => message.currentData) + .combineLatest(this.config, (currentData, config) => new CurrentDataAndSummary(currentData, config)); + // prepare stream/obersable for log + let logStream = replyStreams["log"] = new Subject(); + this.log = logStream + .map(message => message.log); + } + + // holds current data + public currentData: Observable; + + // holds log + public log: Observable; + + // holds config + public config: BehaviorSubject = new BehaviorSubject(null); + public event = new Subject(); public address: string; - public config = new BehaviorSubject(null); - public log = new Subject(); - public producttype: 'Pro 9-12' | 'MiniES 3-3' | 'PRO Hybrid 9-10' | 'PRO Compact 3-10' | 'COMMERCIAL 40-45' | 'INDUSTRIAL' | '' = ''; - public role: Role = ROLES.guest; //public historykWh = new BehaviorSubject(null); - private comment: string = ''; private state: 'active' | 'inactive' | 'test' | 'installed-on-stock' | '' = ''; - private queryreply = new Subject(); - private currentData = new BehaviorSubject(null); - private ngUnsubscribeCurrentData: Subject = new Subject(); - private online = false; + private subscribeCurrentDataChannels: DefaultTypes.ChannelAddresses = {}; - private influxdb: { - ip: string, - username: string, - password: string, - fems: string + /* + * Called by websocket, when this device is set as currentDevice + */ + public markAsCurrentDevice() { + if (this.config.getValue() == null) { + this.refreshConfig(); + } } - constructor( - public name: string, - public websocket: Websocket, - private roleName: string - ) { - this.setRole(roleName); - if (this.name == 'fems') { - this.address = this.websocket.name; - } else { - this.address = this.websocket.name + ": " + this.name; - } - this.comment = name; + /* + * Refresh the config + */ + public refreshConfig(): BehaviorSubject { + let message = DefaultMessages.configQuery(); + let messageId = message.id[0]; + this.replyStreams[messageId] = new Subject(); + this.send(message); + // wait for reply + this.replyStreams[messageId].first().subscribe(reply => { + let config = (reply).config; + let configImpl = new ConfigImpl(config) + this.config.next(configImpl); + this.replyStreams[messageId].unsubscribe(); + delete this.replyStreams[messageId]; + }); + // TODO add timeout + return this.config; } /** - * Sends a message to websocket, returns the unique request id + * Sends a message to websocket */ - public send(value: any): string { - let requestId = UUID.UUID(); - value["requestId"] = requestId; - this.websocket.send(this, value); - return requestId; + public send(value: any): void { + this.websocket.send(value, this); } /** - * Subscribe to specified channels + * Subscribe to current data of specified channels */ - public subscribeCurrentData(channels: ChannelAddresses): Subject { + public subscribeCurrentData(channels: DefaultTypes.ChannelAddresses): Observable { // send subscribe - this.send({ - subscribe: { - channels: channels - } - }); - // prepare result - let result = new Subject(); - let gotData: boolean = false; - // timeout after 10 seconds - setTimeout(() => { - if (!gotData) { - result.error("CurrentData timeout"); - result.complete(); - } - }, 10000); - // wait for current data - this.currentData.takeUntil(this.ngUnsubscribeCurrentData).subscribe(currentData => { - gotData = true; - result.next(currentData); - }); - return result; + let message = DefaultMessages.currentDataSubscribe(channels); + this.send(message); + this.subscribeCurrentDataChannels = channels; + // TODO timeout + return this.currentData; } /** - * Unsubscribe from channels + * Unsubscribe from current data */ public unsubscribeCurrentData() { - if (!this.ngUnsubscribeCurrentData.isStopped) { - this.ngUnsubscribeCurrentData.next(); - this.ngUnsubscribeCurrentData.complete(); - } - this.send({ - subscribe: { - channels: {} - } - }); + this.subscribeCurrentData({}); } /** - * Subscribe to log + * Query data */ - public subscribeLog(key: "all" | "info" | "warning" | "error") { - this.send({ - subscribe: { - log: key - } - }); + // TODO: kWh: this.getkWhResult(this.getImportantChannels()) + public historicDataQuery(fromDate: moment.Moment, toDate: moment.Moment, channels: DefaultTypes.ChannelAddresses): Promise { + // send query + let timezone = new Date().getTimezoneOffset() * 60; + let message = DefaultMessages.historicDataQuery(fromDate, toDate, timezone, channels); + let messageId = message.id[0]; + this.replyStreams[messageId] = new Subject(); + this.send(message); + // wait for reply + return new Promise((resolve, reject) => { + this.replyStreams[messageId].first().subscribe(reply => { + let historicData = (reply).historicData; + this.replyStreams[messageId].unsubscribe(); + delete this.replyStreams[messageId]; + resolve(historicData); + }); + }) } /** - * Unsubscribe from channels + * Mark this device as online or offline + * @param online */ - public unsubscribeLog() { - this.send({ - subscribe: { - log: "" - } - }); + public setOnline(online: boolean) { + this.online = online; } /** - * Send "query" message to websocket + * Subscribe to log */ - // TODO: kWh: this.getkWhResult(this.getImportantChannels()) - public query(fromDate: moment.Moment, toDate: moment.Moment, channels: ChannelAddresses): Subject { - // create query object - let obj = { - mode: "history", - fromDate: fromDate.format("YYYY-MM-DD"), - toDate: toDate.format("YYYY-MM-DD"), - timezone: new Date().getTimezoneOffset() * 60, - channels: channels - }; - // send query and receive requestId - let requestId = this.send({ query: obj }); - // prepare result - let ngUnsubscribe: Subject = new Subject(); - let result = new Subject(); - // timeout after 10 seconds - setTimeout(() => { - result.error("Query timeout"); - result.complete(); - }, 10000); - // wait for queryreply with this requestId - this.queryreply.takeUntil(ngUnsubscribe).subscribe(queryreply => { - if (queryreply.requestId == requestId) { - ngUnsubscribe.next(); - ngUnsubscribe.complete(); - result.next(queryreply); - result.complete(); - } - }); - return result; + public subscribeLog(): Observable { + let message = DefaultMessages.logSubscribe(); + this.send(message); + return this.log; } /** - * Receive new data from websocket + * Unsubscribe from log */ - public receive(message: any) { - if ("metadata" in message) { - let metadata = message.metadata; - /* - * config - */ - if ("config" in metadata) { - let config: Config = new Config(metadata.config); - - // parse influxdb connection - if ("persistence" in config) { - for (let persistence of config.persistence) { - if (persistence.class == "io.openems.impl.persistence.influxdb.InfluxdbPersistence" && - "ip" in persistence && "username" in persistence && "password" in persistence && "fems" in persistence) { - let ip = persistence["ip"]; - if (ip == "127.0.0.1" || ip == "localhost") { // rewrite localhost to remote ip - ip = location.hostname; - } - this.influxdb = { - ip: ip, - username: persistence["username"], - password: persistence["password"], - fems: persistence["fems"] - } - } - } - } - // store all config - this.config.next(config); - //TODO this.things = this.refreshThingsFromConfig(); - } - - if ("online" in metadata) { - this.online = metadata.online; - } else { - this.online = true; - } - - if ("comment" in metadata) { - this.comment = metadata.comment; - } - - if ("role" in metadata) { - this.setRole(metadata.role); - } - - if ("state" in metadata) { - this.state = metadata.state; - } - - if ("producttype" in metadata) { - this.producttype = metadata.producttype; - } - } - - /* - * currentdata - */ - if ("currentdata" in message) { - let data: Data = new Data(message.currentdata, this.config.getValue()); - this.currentData.next(data); - } - - /* - * log - */ - if ("log" in message) { - let log = message.log; - this.log.next(log); - } - - /* - * Reply to a query - */ - if ("queryreply" in message) { - if (!("requestId" in message)) { - throw ("No requestId in message: " + message); - } - message.queryreply["requestId"] = message.requestId; - this.queryreply.next(message.queryreply); - - //let kWh = null; - // history data - // if (message.queryreply != null) { - // if ("data" in message.queryreply && message.queryreply.data != null) { - // data = message.queryreply.data; - // for (let datum of data) { - // let sum = this.calculateSummary(datum.channels); - // datum["summary"] = sum; - // } - // } - // // kWh data - // if ("kWh" in message.queryreply) { - // kWh = message.queryreply.kWh; - // } - // } - //this.historykWh.next(kWh); - } + public unsubscribeLog() { + let message = DefaultMessages.logUnsubscribe(); + this.send(message); } - private setRole(role: string) { - if (role in ROLES) { - this.role = ROLES[role]; - } else { - console.warn("Role '" + role + "' not found.") - this.role = ROLES.guest; - } - } + + /* + * log + */ + // if ("log" in message) { + // let log = message.log; + // this.log.next(log); + // } + + + //let kWh = null; + // history data + // if (message.queryreply != null) { + // if ("data" in message.queryreply && message.queryreply.data != null) { + // data = message.queryreply.data; + // for (let datum of data) { + // let sum = this.calculateSummary(datum.channels); + // datum["summary"] = sum; + // } + // } + // // kWh data + // if ("kWh" in message.queryreply) { + // kWh = message.queryreply.kWh; + // } + // } + //this.historykWh.next(kWh); + // } } \ No newline at end of file diff --git a/ui/src/app/shared/service/defaultmessages.ts b/ui/src/app/shared/service/defaultmessages.ts new file mode 100644 index 00000000000..55bdf8df979 --- /dev/null +++ b/ui/src/app/shared/service/defaultmessages.ts @@ -0,0 +1,99 @@ +import { UUID } from 'angular2-uuid'; +import * as moment from 'moment'; + +import { DefaultTypes } from './defaulttypes'; + +export class DefaultMessages { + public static authenticateLogin(password: string, username?: string) { + let m = { + authenticate: { + mode: "login", + password: password + } + }; + if (username) m.authenticate["username"] = username; + return m; + }; + + public static configQuery() { + return { + device: String, + id: [UUID.UUID()], + config: { + mode: "query", + language: 'de' + } + } + }; + + public static currentDataSubscribe(channels: DefaultTypes.ChannelAddresses) { + return { + device: String, + id: ["currentData"], + currentData: { + mode: "subscribe", + channels: channels + } + } + }; + + public static historicDataQuery(fromDate: moment.Moment, toDate: moment.Moment, timezone: number /*offset in seconds*/, channels: DefaultTypes.ChannelAddresses) { + return { + device: String, + id: [UUID.UUID()], + historicData: { + mode: "query", + fromDate: fromDate.format('YYYY-MM-DD'), + toDate: toDate.format('YYYY-MM-DD'), + timezone: timezone, + channels: channels + // TODO + // kwhChannels: { + // address: 'grid' | 'production' | 'storage', + // } + } + } + }; + + public static logSubscribe() { + return { + device: String, + id: ["log"], + log: { + mode: "subscribe", + } + } + }; + + public static logUnsubscribe() { + return { + device: String, + id: ["log"], + log: { + mode: "unsubscribe", + } + } + }; +} + +export module DefaultMessages { + export interface Reply { + id: string[] + } + + export interface ConfigQueryReply extends Reply { + config: DefaultTypes.Config + } + + export interface CurrentDataReply extends Reply { + currentData: DefaultTypes.Data + } + + export interface HistoricDataReply extends Reply { + historicData: DefaultTypes.HistoricData + } + + export interface LogReply extends Reply { + log: DefaultTypes.Log + } +} \ No newline at end of file diff --git a/ui/src/app/shared/service/defaulttypes.ts b/ui/src/app/shared/service/defaulttypes.ts new file mode 100644 index 00000000000..e49631f0c74 --- /dev/null +++ b/ui/src/app/shared/service/defaulttypes.ts @@ -0,0 +1,99 @@ +import * as moment from 'moment'; + +export module DefaultTypes { + + export type Backend = "OpenEMS Backend" | "OpenEMS Edge"; + + export type ConnectionStatus = "online" | "connecting" | "waiting for authentication" | "failed"; + + export interface ChannelAddresses { + [thing: string]: string[]; + } + + export interface Config { + things: { + [id: string]: { + id: string, + class: string | string[], + [channel: string]: any + } + }, + meta: { + [clazz: string]: { + implements: [string], + channels: { + [channel: string]: { + name: string, + title: string, + type: string | string[], + optional: boolean, + array: boolean, + accessLevel: string + } + } + } + } + } + + export interface Data { + [thing: string]: { + [channel: string]: any + } + } + + export interface HistoricData { + data: [{ + time: string, + channels: Data + }] + } + + export interface Summary { + storage: { + soc: number, + activePower: number, + maxActivePower: number + }, production: { + powerRatio: number, + activePower: number, // sum of activePowerAC and activePowerDC + activePowerAC: number, + activePowerDC: number, + maxActivePower: number + }, grid: { + powerRatio: number, + activePower: number, + maxActivePower: number, + minActivePower: number + }, consumption: { + powerRatio: number, + activePower: number + } + } + + export interface MessageMetadataDevice { + name: string, + comment: string, + producttype: string, + role: string, + online: boolean + } + + export type NotificationType = "success" | "error" | "warning" | "info"; + + export interface Notification { + type: NotificationType; + message: string; + code?: number, + params?: string[] + } + + export interface Log { + time: number | string, + level: string, + source: string, + message: string, + color?: string /* is added later */ + } + + export type LanguageTag = "de" | "en" | "cz" | "nl"; +} \ No newline at end of file diff --git a/ui/src/app/shared/service/service.ts b/ui/src/app/shared/service/service.ts new file mode 100644 index 00000000000..8019105b6e6 --- /dev/null +++ b/ui/src/app/shared/service/service.ts @@ -0,0 +1,71 @@ +import { Injectable, ErrorHandler } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { Subject } from 'rxjs/Subject'; +import { Cookie } from 'ng2-cookies'; +import * as moment from 'moment'; + +import { Websocket } from './websocket'; +import { Device } from '../device/device'; +import { DefaultTypes } from './defaulttypes'; + +@Injectable() +export class Service implements ErrorHandler { + public notificationEvent: Subject = new Subject(); + + constructor( + public translate: TranslateService + ) { + // add language + translate.addLangs(["de", "en", "cz", "nl"]); + // this language will be used as a fallback when a translation isn't found in the current language + translate.setDefaultLang('de'); + } + + /** + * Sets the application language + */ + public setLang(id: DefaultTypes.LanguageTag) { + this.translate.use(id); + moment.locale(id); + } + + /** + * Gets the token from the cookie + */ + public getToken(): string { + return Cookie.get("token"); + } + + /** + * Sets the token in the cookie + */ + public setToken(token: string) { + Cookie.set("token", token); + } + + /** + * Removes the token from the cookie + */ + public removeToken() { + Cookie.delete("token"); + } + + /** + * Shows a nofication using toastr + */ + public notify(notification: DefaultTypes.Notification) { + this.notificationEvent.next(notification); + } + + /** + * Handles an application error + */ + public handleError(error: any) { + console.error(error); + // let notification: Notification = { + // type: "error", + // message: error + // }; + // this.notify(notification); + } +} \ No newline at end of file diff --git a/ui/src/app/shared/service/templatehelper.ts b/ui/src/app/shared/service/utils.ts similarity index 55% rename from ui/src/app/shared/service/templatehelper.ts rename to ui/src/app/shared/service/utils.ts index 94e96f0dfc0..e924b68a8d0 100644 --- a/ui/src/app/shared/service/templatehelper.ts +++ b/ui/src/app/shared/service/utils.ts @@ -1,25 +1,42 @@ import { Injectable } from '@angular/core'; -import { WebsocketService } from './websocket.service' @Injectable() -export class TemplateHelper { +export class Utils { - constructor( - private websocketService: WebsocketService - ) { } + /** + * Promise with timeout + * Source: https://italonascimento.github.io/applying-a-timeout-to-your-promises/ + */ + public static timeoutPromise = function (ms, promise) { + // Create a promise that rejects in milliseconds + let timeout = new Promise((resolve, reject) => { + let id = setTimeout(() => { + clearTimeout(id); + reject('Timed out in ' + ms + 'ms.') + }, ms) + }) + + // Returns a race between our timeout and the passed in promise + return Promise.race([ + promise, + timeout + ]) + } + + constructor() { } /** * Helps to use an object inside an *ngFor loop. Returns the object keys. * Source: https://stackoverflow.com/a/39896058 */ - keys(object: {}): string[] { + public keys(object: {}): string[] { return Object.keys(object); } /** * Helps to use an object inside an *ngFor loop. Returns the object key value pairs. */ - keyvalues(object: {}): any[] | {} { + public keyvalues(object: {}): any[] | {} { if (!object) { return object; } @@ -34,7 +51,7 @@ export class TemplateHelper { * Helps to use an object inside an *ngFor loop. Returns the object values. * Source: https://stackoverflow.com/a/39896058 */ - values(object: {}): any[] { + public values(object: {}): any[] { let values = []; for (let key in object) { values.push(object[key]); @@ -45,7 +62,7 @@ export class TemplateHelper { /** * Returns true if an object has a property */ - has(object: {}, property: string): boolean { + public has(object: {}, property: string): boolean { if (property in object) { return true; } else { @@ -56,7 +73,7 @@ export class TemplateHelper { /** * Returns a sorted array */ - sort(obj: any[], ascending: boolean = true, property?: string) { + public sort(obj: any[], ascending: boolean = true, property?: string) { if (obj == null) { return obj; } @@ -81,7 +98,7 @@ export class TemplateHelper { /** * Returns the short classname */ - classname(value): string { + public classname(value): string { let parts = value.split("."); return parts[parts.length - 1]; } @@ -89,7 +106,7 @@ export class TemplateHelper { /** * Creates a deep copy of the object */ - deepCopy(obj) { + public deepCopy(obj) { var copy; // Handle the 3 simple types, and null or undefined @@ -123,19 +140,46 @@ export class TemplateHelper { throw new Error("Unable to copy obj! Its type isn't supported."); } + public static addSafely(v1: number, v2: number): number { + if (v1 == null) { + return v2; + } else if (v2 == null) { + return v1; + } else { + return v1 + v2; + } + } + + public static divideSafely(v1: number, v2: number): number { + if (v1 == null || v2 == null) { + return null; + } else { + return v1 / v2; + } + } + + public static multiplySafely(v1: number, v2: number): number { + if (v1 == null || v2 == null) { + return null; + } else { + return v1 * v2; + } + } + /** * Receive meta information for thing/channel/... */ - meta(identifier: string, type: 'controller' | 'channel'): {} { - let property = type == 'controller' ? 'availableControllers' : type; - let device = this.websocketService.currentDevice.getValue(); - if (device) { - let config = device.config.getValue(); - let meta = config._meta[property]; - if (identifier in meta) { - return (meta[identifier]); - } - } - return null; - } + // TODO + // public meta(identifier: string, type: 'controller' | 'channel'): {} { + // let property = type == 'controller' ? 'availableControllers' : type; + // let device = this.websocket.currentDevice; + // if (device) { + // let config = device.config.getValue(); + // let meta = config._meta[property]; + // if (identifier in meta) { + // return (meta[identifier]); + // } + // } + // return null; + // } } \ No newline at end of file diff --git a/ui/src/app/shared/service/webapp.service.ts b/ui/src/app/shared/service/webapp.service.ts deleted file mode 100644 index 62278429d51..00000000000 --- a/ui/src/app/shared/service/webapp.service.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { Injectable, ErrorHandler } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; -import { Subject } from 'rxjs/Subject'; - -import * as moment from 'moment'; - -import { WebsocketService } from './websocket.service'; -import { Device } from '../shared'; - -type NotificationType = "success" | "error" | "warning" | "info"; - -export interface Notification { - type: NotificationType; - message: string; -} - -@Injectable() -export class WebappService implements ErrorHandler { - - public notificationEvent: Subject = new Subject(); - - constructor( - public translate: TranslateService - ) { - // add language - translate.addLangs(["de", "en", "cz"]); - // this language will be used as a fallback when a translation isn't found in the current language - translate.setDefaultLang('de'); - } - - /** - * Sets the application language - */ - public setLang(id: 'de' | 'en' | 'cz') { - this.translate.use(id); - moment.locale(id); - } - - /** - * Gets the token for this id from localstorage - */ - public getToken(id: string): string { - return localStorage.getItem(id + "_token"); - } - - /** - * Sets the token for this id in localstorage - */ - public setToken(id: string, token: string) { - localStorage.setItem(id + "_token", token); - } - - /** - * Removes the token for this id from localstorage - */ - public removeToken(id: string) { - localStorage.removeItem(id + "_token"); - } - - /** - * Shows a nofication using toastr - */ - public notify(notification: Notification) { - this.notificationEvent.next(notification); - } - - /** - * Handles an application error - */ - public handleError(error: any) { - console.error(error); - let notification: Notification = { - type: "error", - message: error - }; - this.notify(notification); - } -} \ No newline at end of file diff --git a/ui/src/app/shared/service/websocket.service.ts b/ui/src/app/shared/service/websocket.service.ts deleted file mode 100644 index d3008908528..00000000000 --- a/ui/src/app/shared/service/websocket.service.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { Injectable, EventEmitter } from '@angular/core'; -import { Subject } from 'rxjs/Subject'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -import { Router, ActivatedRoute, Params } from '@angular/router'; - -import { environment } from '../../../environments'; -import { Websocket } from '../websocket'; -import { WebappService, Notification } from './webapp.service'; -import { Device } from '../device/device'; - -export { Websocket }; - -@Injectable() -export class WebsocketService { - public websockets: { [name: string]: Websocket } = {}; - public event = new Subject(); - public currentDevice = new BehaviorSubject(null); - - constructor( - private router: Router, - private webappService: WebappService, - ) { - // initialize websockets - for (var defaultWebsocket of environment.websockets) { - // load default websockets - let websocket = new Websocket(defaultWebsocket.name, defaultWebsocket.url, defaultWebsocket.backend, webappService); - websocket.event.subscribe(notification => { - let n = { - type: notification.type, - message: websocket.name + ": " + notification.message - } - this.event.next(n); - if (!websocket.isConnected) { - let device = this.currentDevice.getValue(); - if (device) { - if (device.websocket === websocket) { - this.router.navigate(['/overview']); - } - } - } - }); - this.websockets[websocket.name] = websocket; - } - } - - /** - * Parses the route params, sets the current device and returns it - or redirects to overview and returns null - */ - public setCurrentDevice(params: Params): BehaviorSubject { - let timeout = null; - let retryCounter = 0; - let worker = (params: Params): boolean => { - retryCounter++; - if ('websocket' in params && 'device' in params) { - let websocketName = params['websocket']; - let deviceName = params['device']; - let websocket = this.getWebsocket(websocketName); - if (websocket) { - let device = websocket.getDevice(deviceName); - if (device) { - // found it -> we quit here - this.currentDevice.next(device); - return; - } - } - } - if (retryCounter < 10) { - // retry 10 times - timeout = setTimeout(() => { - worker(params); - }, 1000); - } else { - // failed -> redirect to /overview - this.currentDevice.next(null); - this.router.navigate(['/overview']); - } - } - worker(params); - return this.currentDevice; - } - - /** - * Clears the current device - */ - public clearCurrentDevice() { - this.currentDevice.next(null); - } - - /** - * Returns the websocket with the given name - */ - public getWebsocket(name: string) { - if (this.websockets[name]) { - return this.websockets[name]; - } else { - return null; - } - } - - /** - * Closes the given websocket - */ - public closeConnection(websocket: Websocket) { - console.info("Closing websocket[" + websocket.name + "; " + websocket.url + "]"); - websocket.close(); - //this.event.emit({ type: "info", message: "Verbindung beendet" }); - } -} \ No newline at end of file diff --git a/ui/src/app/shared/service/websocket.ts b/ui/src/app/shared/service/websocket.ts new file mode 100644 index 00000000000..4046f5307bb --- /dev/null +++ b/ui/src/app/shared/service/websocket.ts @@ -0,0 +1,306 @@ +import { Injectable, EventEmitter } from '@angular/core'; +import { Subject } from 'rxjs/Subject'; +import { BehaviorSubject } from 'rxjs/BehaviorSubject'; +import { Router, ActivatedRoute, Params } from '@angular/router'; +import { Observable } from 'rxjs/Observable'; +import { Subscription } from 'rxjs/Subscription'; +import websocketConnect from 'rxjs-websockets'; +import 'rxjs/add/operator/timeout'; + +import { environment as env } from '../../../environments'; +import { Service } from './service'; +import { Utils } from './utils'; +import { Device } from '../device/device'; +import { ROLES } from '../type/role'; +import { DefaultTypes } from '../service/defaulttypes'; +import { DefaultMessages } from '../service/defaultmessages'; + +@Injectable() +export class Websocket { + public static readonly TIMEOUT = 5000; + private static readonly DEFAULT_DEVICENAME = "fems"; + + // holds references of device names (=key) to Device objects (=value) + private _devices: BehaviorSubject<{ [name: string]: Device }> = new BehaviorSubject({}); + public get devices() { + return this._devices; + } + + // holds the currently selected device + private _currentDevice: BehaviorSubject = new BehaviorSubject(null); + public get currentDevice() { + return this._currentDevice; + } + + public status: DefaultTypes.ConnectionStatus = "connecting"; + public noOfConnectedWebsockets: number; + + private username: string = ""; + private messages: Observable; + private inputStream: Subject; + private websocketSubscription: Subscription = new Subscription(); + private queryreply = new Subject<{ id: string[] }>(); + + // holds stream per device (=key1) and message-id (=key2); triggered on message reply for the device + private replyStreams: { [deviceName: string]: { [messageId: string]: Subject } } = {}; + + // tracks which message id (=key) is connected with which deviceName (=value) + private pendingQueryReplies: { [id: string]: string } = {}; + + constructor( + private router: Router, + private service: Service, + ) { + // try to auto connect using token or session_id + setTimeout(() => { + this.connect(); + }) + } + + /** + * Parses the route params and sets the current device + */ + public setCurrentDevice(route: ActivatedRoute): Subject { + let onTimeout = () => { + // Timeout: redirect to overview + this.router.navigate(['/overview']); + subscription.unsubscribe(); + } + + let deviceName = route.snapshot.params["device"]; + let subscription = this.devices + .filter(devices => deviceName in devices) + .first() + .map(devices => devices[deviceName]) + .subscribe(device => { + if (device == null || !device.online) { + onTimeout(); + } else { + // set current device + this.currentDevice.next(device); + device.markAsCurrentDevice(); + } + }, error => { + console.error("Error while setting current device: ", error); + }) + setTimeout(() => { + let device = this.currentDevice.getValue(); + if (device == null || !device.online) { + onTimeout(); + } + }, Websocket.TIMEOUT); + return this.currentDevice; + } + + /** + * Clears the current device + */ + public clearCurrentDevice() { + this.currentDevice.next(null); + } + + /** + * Opens a connection using a stored token or a cookie with a session_id for this websocket. Called once by constructor + */ + private connect() { + if (this.messages) { + return; + } + + const { messages, connectionStatus } = websocketConnect( + env.url, + this.inputStream = new Subject() + ); + connectionStatus.subscribe(noOfConnectedWebsockets => { + this.noOfConnectedWebsockets = noOfConnectedWebsockets; + if (noOfConnectedWebsockets == 0 && this.status == 'online') { + this.service.notify({ + message: "Connection lost. Trying to reconnect.", // TODO translate + type: 'warning' + }); + // TODO show spinners everywhere + this.status = 'connecting'; + } + }); + this.messages = messages.share(); + this.websocketSubscription = this.messages.retryWhen(errors => { + return errors.delay(1000); + + }).subscribe(message => { + // called on every receive of message from server + // console.log(message); + /* + * Authenticate + */ + if ("authenticate" in message && "mode" in message.authenticate) { + let mode = message.authenticate.mode; + + if (mode === "allow") { + // authentication successful + this.status = "online"; + + if ("token" in message.authenticate) { + // received login token -> save in cookie + this.service.setToken(message.authenticate.token); + } + + if ("role" in message.authenticate && env.backend === "OpenEMS Edge") { + // for OpenEMS Edge we have only one device + let role = ROLES.getRole(message.authenticate.role); + let replyStream: { [messageId: string]: Subject } = {}; + this.replyStreams[Websocket.DEFAULT_DEVICENAME] = replyStream; + this.devices.next({ + fems: new Device(Websocket.DEFAULT_DEVICENAME, "FEMS", "", role, true, replyStream, this) + }); + } + + // TODO username is deprecated + // if ("username" in message.authenticate) { + // this.username = message.authenticate.username; + // this.event.next({ type: "success", message: this.service.translate.instant('Notifications.LoggedInAs', { value: this.username }) }); + // } else { + // this.event.next({ type: "success", message: this.service.translate.instant('Notifications.LoggedIn') }); + // } + + } else { + // authentication denied -> close websocket + this.status = "failed"; + this.service.removeToken(); + this.initialize(); + if (env.backend === "OpenEMS Backend") { + console.log("would redirect...") // TODO fix redirect + //window.location.href = "/web/login?redirect=/m/overview"; + } else if (env.backend === "OpenEMS Edge") { + this.router.navigate(['/overview']); + } + } + } + + /* + * Query reply + */ + if ("id" in message && message.id instanceof Array) { + let id = message.id[0]; + let deviceName = Websocket.DEFAULT_DEVICENAME; + if ("device" in message) { + // Receive a reply with a message id and a device -> forward to devices' replyStream + deviceName = message.device; + if (deviceName in this.replyStreams && id in this.replyStreams[deviceName]) { + this.replyStreams[deviceName][id].next(message); + } + } else { + // Receive a reply with a message id -> find device and forward to devices' replyStream + for (let deviceName in this.replyStreams) { + if (id in this.replyStreams[deviceName]) { + this.replyStreams[deviceName][id].next(message); + break; + } + } + } + } + + /* + * Metadata + */ + if ("metadata" in message) { + if ("devices" in message.metadata) { + let devices = message.metadata.devices; + let newDevices = {}; + for (let device of devices) { + let replyStream: { [messageId: string]: Subject } = {}; + this.replyStreams[device.name] = replyStream; + let newDevice = new Device( + device.name, + device.comment, + device.producttype, + ROLES.getRole(device.role), + device.online, + replyStream, + this + ); + newDevices[newDevice.name] = newDevice; + } + this.devices.next(newDevices); + } + } + + /* + * receive notification + */ + if ("notification" in message) { + let notification = message.notification; + let n: DefaultTypes.Notification; + let notify: boolean = true; + if ("code" in notification) { + // handle specific notification codes - see Java source for details + let code = notification.code; + let params = notification.params; + if (code == 100 /* device disconnected -> mark as offline */) { + let deviceId = params[0]; + if (deviceId in this.devices.getValue()) { + this.devices.getValue()[deviceId].setOnline(false); + } + } else if (code == 101 /* device reconnected -> mark as online */) { + let deviceId = params[0]; + if (deviceId in this.devices.getValue()) { + let device = this.devices.getValue()[deviceId]; + device.setOnline(true); + } + } else if (code == 103 /* authentication by token failed */) { + let token: string = params[0]; + if (token !== "") { + // remove old token + this.service.removeToken(); + } + // ask for authentication info + this.status = "waiting for authentication"; + notify = false; + setTimeout(() => this.router.navigate["/overview"]); + } + } + if (notify) { + this.service.notify(notification); + } + } + }); + } + + /** + * Reset everything to default + */ + private initialize() { + this.websocketSubscription.unsubscribe(); + this.messages = null; + this.devices.next({}); + } + + /** + * Closes the connection. + */ + public close() { + console.info("Closing websocket"); + if (this.status != "online") { // TODO why this if? + this.service.removeToken(); + this.initialize(); + // TODO + // var status: DefaultTypes.Notification = { type: "info", message: this.service.translate.instant('Notifications.Closed') }; + // this.event.next(status); + } + } + + /** + * Sends a message to the websocket + */ + public send(message: any, device?: Device): void { + // console.log("SEND: ", message); + if (device) { + if ("id" in message) { + this.pendingQueryReplies[message.id[0]] = device.name; + } + message["device"] = device.name; + this.inputStream.next(message); + } else { + this.inputStream.next(message); + } + } +} \ No newline at end of file diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 6ccee4bc036..d3659b0c17c 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -7,7 +7,7 @@ import { FlexLayoutModule } from '@angular/flex-layout'; import { RouterModule } from '@angular/router'; import { ChartsModule } from 'ng2-charts/ng2-charts'; import { MdSnackBar } from '@angular/material'; -import { SpinnerModule } from 'angular-spinners'; +import { LoadingModule } from 'ngx-loading'; import { TranslateModule } from '@ngx-translate/core'; import 'hammerjs'; @@ -19,9 +19,9 @@ import { routing, appRoutingProviders } from './../app.routing'; /* * Services */ -import { WebappService, Notification } from './service/webapp.service'; -import { WebsocketService, Websocket } from './service/websocket.service'; -import { TemplateHelper } from './service/templatehelper'; +import { Service } from './service/service'; +import { Websocket } from './service/websocket'; +import { Utils } from './service/utils'; /* * Pipes @@ -36,7 +36,6 @@ import { HasclassPipe } from './pipe/hasclass/hasclass.pipe'; * Components */ import { SocChartComponent } from './../device/history/chart/socchart/socchart.component'; -import { SpinnerComponent } from './spinner.component'; @NgModule({ imports: [ @@ -48,7 +47,7 @@ import { SpinnerComponent } from './spinner.component'; FlexLayoutModule, RouterModule, ChartsModule, - SpinnerModule, + LoadingModule, routing ], declarations: [ @@ -59,8 +58,7 @@ import { SpinnerComponent } from './spinner.component'; IsclassPipe, HasclassPipe, // components - SocChartComponent, - SpinnerComponent + SocChartComponent ], exports: [ // pipes @@ -81,15 +79,14 @@ import { SpinnerComponent } from './spinner.component'; TranslateModule, // components SocChartComponent, - SpinnerComponent + LoadingModule ], providers: [ - TemplateHelper, - WebappService, - WebsocketService, + Utils, + Service, + Websocket, appRoutingProviders, - MdSnackBar, - SpinnerComponent + MdSnackBar ] }) export class SharedModule { } diff --git a/ui/src/app/shared/shared.ts b/ui/src/app/shared/shared.ts index 02c595767c2..50d18f0c362 100644 --- a/ui/src/app/shared/shared.ts +++ b/ui/src/app/shared/shared.ts @@ -1,9 +1,7 @@ import { routing, appRoutingProviders } from './../app.routing'; -import { Device, Log, QueryReply, Data, ChannelAddresses, Summary } from './device/device'; -import { Config, Meta, ThingMeta } from './device/config'; import { Dataset, EMPTY_DATASET } from './chart'; -import { TemplateHelper } from './service/templatehelper'; -import { WebappService, Notification } from './service/webapp.service'; -import { WebsocketService, Websocket } from './service/websocket.service'; +import { Service } from './service/service'; +import { Utils } from './service/utils'; +import { Websocket } from './service/websocket'; -export { WebappService, TemplateHelper, Notification, WebsocketService, Websocket, Device, Log, Dataset, EMPTY_DATASET, Config, Meta, ThingMeta, QueryReply, ChannelAddresses, Data, Summary }; \ No newline at end of file +export { Service, Utils, Websocket, Dataset, EMPTY_DATASET }; \ No newline at end of file diff --git a/ui/src/app/shared/spinner.component.html b/ui/src/app/shared/spinner.component.html deleted file mode 100644 index 58da4eb2d03..00000000000 --- a/ui/src/app/shared/spinner.component.html +++ /dev/null @@ -1,5 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/ui/src/app/shared/spinner.component.ts b/ui/src/app/shared/spinner.component.ts deleted file mode 100644 index 95cfe7d3065..00000000000 --- a/ui/src/app/shared/spinner.component.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { Component, Input, trigger, state, style, transition, animate, OnInit, OnDestroy } from '@angular/core'; -import { Subject } from 'rxjs/Subject'; -import { Observable } from 'rxjs/Observable'; -import { Subscription } from 'rxjs/Subscription'; - -// spinner -import { SpinnerService } from 'angular-spinners'; - -let pulsetime = 1000; -let pulsetimedown = 2000; - -@Component({ - selector: 'spinner-animation', - templateUrl: './spinner.component.html', - animations: [ - trigger('spinner', [ - state('one', style({ - height: "30px", - width: "30px" - })), - state('two', style({ - height: "60px", - width: "60px" - })), - transition('one => two', animate(pulsetime + 'ms')), - transition('two => one', animate(pulsetime + 'ms')) - ]) - ] -}) -export class SpinnerComponent implements OnInit, OnDestroy { - public state: "one" | "two" = "two"; - public loading: boolean = true; - - private subscription: Subscription = new Subscription(); - - constructor( - protected spinnerService: SpinnerService - ) { } - - ngOnInit() { - this.spinnerService.show('loadingSpinner'); - this.switchState(); - this.subscription = Observable.interval(pulsetime).subscribe(() => { - this.switchState(); - }); - } - - ngOnDestroy() { - this.subscription.unsubscribe(); - } - - public switchState() { - if (this.state == 'one') { - this.state = 'two'; - } else if (this.state == 'two') { - this.state = 'one'; - } else { - this.state = 'one'; - } - } - -} \ No newline at end of file diff --git a/ui/src/app/shared/translate/cz.ts b/ui/src/app/shared/translate/cz.ts index c068332c024..900f21535d3 100644 --- a/ui/src/app/shared/translate/cz.ts +++ b/ui/src/app/shared/translate/cz.ts @@ -12,7 +12,8 @@ export const TRANSLATION = { Soc: "Stav nabití", Percentage: "Procentuální vyjádření", More: "Další", - PeriodFromTo: "{{value1}} do {{value2}}", // value1 = beginning date, value2 = end date + PeriodFromTo: "od {{value1}} do {{value2}}", // value1 = beginning date, value2 = end date + DateFormat: "DD.MM.YYYY", // e.g. German: DD.MM.YYYY, English: YYYY-MM-DD (DD = Day, MM = Month, YYYY = Year) Week: { Monday: "Pondělí", Tuesday: "Úterý", diff --git a/ui/src/app/shared/translate/de.ts b/ui/src/app/shared/translate/de.ts index 3823830771b..96232c1345a 100644 --- a/ui/src/app/shared/translate/de.ts +++ b/ui/src/app/shared/translate/de.ts @@ -13,6 +13,7 @@ export const TRANSLATION = { Percentage: "Prozent", More: "Mehr...", PeriodFromTo: "von {{value1}} bis {{value2}}", // value1 = start date, value2 = end date + DateFormat: "DD.MM.YYYY", // z.B. Englisch: YYYY-MM-DD (DD = Tag, MM = Monat, YYYY = Jahr) Week: { Monday: "Montag", Tuesday: "Dienstag", diff --git a/ui/src/app/shared/translate/en.ts b/ui/src/app/shared/translate/en.ts index b2882e4a616..39ea299ae4e 100644 --- a/ui/src/app/shared/translate/en.ts +++ b/ui/src/app/shared/translate/en.ts @@ -13,6 +13,7 @@ export const TRANSLATION = { Percentage: "Percentage", More: "More...", PeriodFromTo: "from {{value1}} to {{value2}}", // value1 = beginning date, value2 = end date + DateFormat: "YYYY-MM-DD", // e.g. German: DD.MM.YYYY (DD = Day, MM = Month, YYYY = Year) Week: { Monday: "Monday", Tuesday: "Tuesday", diff --git a/ui/src/app/shared/translate/nl.ts b/ui/src/app/shared/translate/nl.ts new file mode 100644 index 00000000000..274dcf225a9 --- /dev/null +++ b/ui/src/app/shared/translate/nl.ts @@ -0,0 +1,131 @@ +export const TRANSLATION = { + General: { + Grid: "Net", + GridBuy: "Netafname", + GridSell: "Netteruglevering", + Production: "Opwekking", + Consumption: "Verbruik", + Power: "Vermogen", + StorageSystem: "Batterij", + History: "Historie", + NoValue: "Geen waarde", + Soc: "Laadstatus", + Percentage: "Procent", + More: "Meer…", + PeriodFromTo: "van {{value1}} tot {{value2}}", // value1 = beginning date, value2 = end date + DateFormat: "DD-MM-YYYY", // e.g. German: DD.MM.YYYY, English: YYYY-MM-DD (DD = Day, MM = Month, YYYY = Year) + Week: { + Monday: "Maandag", + Tuesday: "Dinsdag", + Wednesday: "Woensdag", + Thursday: "Donderdag", + Friday: "Vrijdag", + Saturday: "Zaterdag", + Sunday: "Zondag" + } + }, + Menu: { + Overview: "Overzicht", + AboutUI: "Over FEMS- UI" + }, + Overview: { + AllConnected: "Alle verbindingen gemaakt.", + ConnectionSuccessful: "Succesvol verbonden met {{value }}.", // (value = Name vom Websocket) + ConnectionFailed: "Verbinding met {{ value } } mislukt.", // (value = Name vom Websocket) + ToEnergymonitor: "Naar Energiemonitor...", + IsOffline: "FEMS is offline!" + }, + Device: { + Overview: { + Energymonitor: { + Title: "Energiemonitor", + ConsumptionWarning: "Verbruik & onbekende producenten", + Storage: "Batterij", + ChargePower: "Laad vermogen", + DischargePower: "Ontlaad vermogen", + ReactivePower: "Blind vermogen", + ActivePower: "Actief vermogen", + GridMeter: "Energiemeter", + ProductionMeter: "Productiemeter" + }, + Energytable: { + Title: "Energie tabel" + } + }, + History: { + SelectedPeriod: "Geselecteerde periode: ", + OtherPeriod: "Andere periode: ", + Period: "Periode", + Today: "Vandaag", + Yesterday: "Gisteren", + LastWeek: "Vorige week", + LastMonth: "Vorige maand", + LastYear: "Vorig jaar", + Go: "Ga!" + }, + Config: { + Overview: { + Bridge: "Verbindingen en apparaten", + Scheduler: "Toepassingsschema", + Controller: "Toepassingen", + Simulator: "Simulator", + ExecuteSimulator: "Simulatie uitvoeren", + Log: "Log", + LiveLog: "Live System log", + ManualControl: "Handmatige bediening" + }, + More: { + ManualCommand: "Handmatig commando", + Send: "Verstuur", + RefuInverter: "REFU inverter", + RefuStartStop: "Inverter starten/ stoppen", + RefuStart: "Start", + RefuStop: "Stop", + ManualpqPowerSpecification: "Gespecificeerd vermogen", + ManualpqSubmit: "Toepassen", + ManualpqReset: "Reset" + }, + Scheduler: { + NewScheduler: "New Schema...", + Class: "Soort: ", + NotImplemented: "Gegevens niet geïmplementeerd: ", + Contact: "Dit zou niet mogen gebeuren.Neem contact op met {{value}}.", // (value = E - Mail vom FEMS- Team) + Always: "Altijd" + }, + Log: { + AutomaticUpdating: "Automatisch updaten", + Timestamp: "Tijdspit", + Level: "Niveau", + Source: "Bron", + Message: "Bericht" + }, + Controller: { + InternallyID: "Intern ID:", + App: "App:", + Priority: "Prioriteit: " + }, + Bridge: { + NewDevice: "Nieuw apparaat…", + NewConnection: "Nieuwe verbinding..." + } + } + }, + About: { + UI: "Gebruikersinterface voor FEMS en OpenEMS", + Developed: "Deze gebruikersinterface is ontwikkeld door FENECON als open- source - software.", + Fenecon: "Meer over FENECON", + Fems: "Meer over FEMS", + Sourcecode: "Broncode", + CurrentDevelopments: "Huidige ontwikkelingen", + Build: "Versie", + Contact: "Voor meer informatie of suggesties over het systeem, neem contact op met het FEMS team via {{value}}.", // (value = E - Mail vom FEMS- Team) + Language: "Selecteer taal: " + }, + Notifications: { + Failed: "Verbinding mislukt.", + LoggedInAs: "Aangemeld als gebruiker {{ value } }.", // (value = Benutzername) + LoggedIn: "Aangemeld.", + AuthenticationFailed: "Geen verbinding.Autorisatie mislukt.", + Closed: "Verbinding beëindigd." + } +} \ No newline at end of file diff --git a/ui/src/app/shared/translate/translate.ts b/ui/src/app/shared/translate/translate.ts index 10a9574c0d8..1c6a6d2ab67 100644 --- a/ui/src/app/shared/translate/translate.ts +++ b/ui/src/app/shared/translate/translate.ts @@ -4,6 +4,7 @@ import { Observable } from 'rxjs/Observable'; import { TRANSLATION as DE } from './de'; import { TRANSLATION as EN } from './en'; import { TRANSLATION as CZ } from './cz'; +import { TRANSLATION as NL } from './nl'; export class MyTranslateLoader implements TranslateLoader { @@ -14,6 +15,8 @@ export class MyTranslateLoader implements TranslateLoader { return Observable.of(DE); } else if (lang == 'cz') { return Observable.of(CZ); + } else if (lang == 'nl') { + return Observable.of(NL); } else { return Observable.of(EN); } diff --git a/ui/src/app/shared/type/backend.ts b/ui/src/app/shared/type/backend.ts deleted file mode 100644 index fa943a46d1b..00000000000 --- a/ui/src/app/shared/type/backend.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum Backend { - FemsServer = "femsserver", - OpenEMS = "openems" -} \ No newline at end of file diff --git a/ui/src/app/shared/type/environment.ts b/ui/src/app/shared/type/environment.ts index 398ec3a2a7e..76b359e48d9 100644 --- a/ui/src/app/shared/type/environment.ts +++ b/ui/src/app/shared/type/environment.ts @@ -1,17 +1,12 @@ import { CustomFieldDefinition } from './customfielddefinition'; -import { Backend } from './backend'; -import { Config } from "../device/config"; +import { DefaultTypes } from '../service/defaulttypes'; export abstract class Environment { public readonly abstract production: boolean; + public readonly abstract url: string; + public readonly abstract backend: DefaultTypes.Backend; - public abstract websockets: { - name: string, - url: string, - backend: Backend - }[]; - - public getCustomFields(config: Config): CustomFieldDefinition { + public getCustomFields(): CustomFieldDefinition { return {}; } } \ No newline at end of file diff --git a/ui/src/app/shared/type/role.ts b/ui/src/app/shared/type/role.ts index a0faa09822d..23ec52cb2f2 100644 --- a/ui/src/app/shared/type/role.ts +++ b/ui/src/app/shared/type/role.ts @@ -23,9 +23,16 @@ export const ROLES = { installer: new Role("installer", 2), admin: new Role("admin", 3), + /** + * Gets the role of a string + * @param name of the role + */ getRole(name: string): Role { - if (name in ROLES) { + if (name.toLowerCase() in ROLES) { return ROLES[name]; + } else { + console.warn("Role '" + name + "' not found.") + return ROLES.guest; } } }; \ No newline at end of file diff --git a/ui/src/app/shared/websocket.ts b/ui/src/app/shared/websocket.ts deleted file mode 100644 index 5d2c4104ee2..00000000000 --- a/ui/src/app/shared/websocket.ts +++ /dev/null @@ -1,238 +0,0 @@ -import { EventEmitter } from '@angular/core'; -import { Router } from '@angular/router'; -import { BehaviorSubject } from 'rxjs/BehaviorSubject'; -import { Subject } from 'rxjs/Subject'; -import { Observable } from 'rxjs/Observable'; -import { Observer } from 'rxjs/Observer'; -import { QueueingSubject } from 'queueing-subject'; -import { Subscription } from 'rxjs/Subscription'; - -import websocketConnect from 'rxjs-websockets'; - -import 'rxjs/add/operator/retryWhen'; -import 'rxjs/add/operator/delay'; - -import { Device } from './device/device'; -import { WebappService, Notification } from './service/webapp.service'; -import { Backend } from './type/backend'; - -export class Websocket { - public isConnected: boolean = false; - public event: Subject = new Subject(); - public devices: { [name: string]: Device } = {}; - - private username: string = ""; - private messages: Observable; - private inputStream: QueueingSubject; - private websocketSubscription: Subscription = new Subscription(); - - constructor( - public name: string, - public url: string, - public backend: Backend, - private webappService: WebappService - ) { - // try to auto connect using token or session_id - setTimeout(() => { - this.connectWithTokenOrSessionId(false); - }) - } - - /** - * Opens a connection using a stored token or a cookie with a session_id for this websocket - */ - public connectWithTokenOrSessionId(throwErrorOnDeny: boolean = true) { - var token = this.webappService.getToken(this.name); - if (token) { - this.connect(null, token, throwErrorOnDeny); - } else if (document.cookie.indexOf("session_id=") != -1) { - this.connect(null, null, throwErrorOnDeny); - } - } - - /** - * Opens a connection using a password - */ - public connectWithPassword(password: string) { - this.connect(password, null); - } - - /** - * Tries to connect using given password or token. - */ - private connect(password: string, token: string, throwErrorOnDeny: boolean = true) { - if (this.messages) { - return; - } - - this.messages = websocketConnect( - this.url, - this.inputStream = new QueueingSubject() - ).messages.share(); - - let authenticate = { - mode: "login" - }; - if (password) { - authenticate["password"] = password; - } else if (token) { - authenticate["token"] = token; - } - - this.send(null, { - authenticate: authenticate - }); - - /** - * called on every receive of message from server - */ - let retryCounter = 0; - this.websocketSubscription = this.messages.retryWhen(errors => errors.do(error => { - // Websocket tries to reconnect ==> send authentication only one time - if (retryCounter == 0) { - this.inputStream.next({ authenticate: authenticate }); - } else if (retryCounter == 10) { - // disconnect user and redirect to login - this.isConnected = false; - this.close(); - this.webappService.notify({ - type: "error", - message: this.webappService.translate.instant('Notifications.Failed') - }); - } - retryCounter++; - }).delay(1000)).subscribe(message => { - retryCounter = 0; - // Receive authentication token - if ("authenticate" in message && "mode" in message.authenticate) { - let mode = message.authenticate.mode; - - if (mode === "allow") { - // authentication successful - this.isConnected = true; - if ("token" in message.authenticate) { - this.webappService.setToken(this.name, message.authenticate.token); - } - if ("username" in message.authenticate) { - this.username = message.authenticate.username; - this.event.next({ type: "success", message: this.webappService.translate.instant('Notifications.LoggedInAs', { value: this.username }) }); - } else { - this.event.next({ type: "success", message: this.webappService.translate.instant('Notifications.LoggedIn') }); - } - - } else { - throwErrorOnDeny = true; - this.isConnected = false; - // authentication denied -> close websocket - if (this.backend == Backend.FemsServer) { - window.location.href = "/web/login?redirect=/m/overview"; - } else if (throwErrorOnDeny) { - let status: Notification = { type: "error", message: this.webappService.translate.instant('Notifications.AuthenticationFailed') }; - this.event.next(status); - } - this.webappService.removeToken(this.name); - this.initialize(); - } - } - - // receive metadata - if ("metadata" in message) { - if ("devices" in message.metadata) { - // receive device specific data - let newDevices = message.metadata.devices; - for (let newDevice of newDevices) { - let name = newDevice["name"]; - let device; - if (name in this.devices) { - device = this.devices[name]; - } else { - device = new Device(name, this, this.username); - this.devices[name] = device; - } - device.receive({ - metadata: newDevice - }); - } - } else { - // only one device - if (!("fems" in this.devices)) { - // device was not existing - this.devices = { - fems: new Device("fems", this, this.username) - } - } - } - if ("backend" in message.metadata) { - this.backend = message.metadata.backend; - } - - } - - // receive device specific data - if ("device" in message) { - // device was specified -> forward - if (this.devices[message.device]) { - let device = this.devices[message.device]; - device.receive(message); - } - } else if (Object.keys(this.devices).length == 1) { - // device was not specified, but we have only one - for (let key in this.devices) { - this.devices[key].receive(message); - } - } - - // receive notification - if ("notification" in message) { - this.webappService.notify(message.notification); - } - - }); - } - - /** - * Reset everything to default - */ - private initialize() { - if (!this.isConnected) { - this.websocketSubscription.unsubscribe(); - this.messages = null; - this.devices = {}; - } - } - - /** - * Closes the connection. - */ - public close() { - if (!this.isConnected) { - this.webappService.removeToken(this.name); - this.initialize(); - var status: Notification = { type: "error", message: this.webappService.translate.instant('Notifications.Closed') }; - this.event.next(status); - } - } - - /** - * Sends a message to the websocket - */ - public send(device: Device, message: any): void { - if (device == null) { - this.inputStream.next(message); - } else { - message["device"] = device.name; - this.inputStream.next(message); - } - } - - /** - * Returns the websocket with the given name - */ - public getDevice(name: string) { - if (name in this.devices) { - return this.devices[name]; - } else { - return null; - } - } -} \ No newline at end of file diff --git a/ui/src/environments/environment.ts b/ui/src/environments/environment.ts index 91a5ccf667d..348a01f453d 100644 --- a/ui/src/environments/environment.ts +++ b/ui/src/environments/environment.ts @@ -1,14 +1,10 @@ import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; +import { DefaultTypes } from "../app/shared/service/defaulttypes"; class DefaultEnvironment extends Environment { public readonly production = false; - - public readonly websockets = [{ - name: "FEMS", - url: "ws://" + location.hostname + ":8085", - backend: Backend.OpenEMS - }]; + public readonly url = "ws://" + location.hostname + ":8085"; + public readonly backend: DefaultTypes.Backend = "OpenEMS Backend"; } export const environment = new DefaultEnvironment(); \ No newline at end of file diff --git a/ui/src/environments/femsserver-dev.ts b/ui/src/environments/femsserver-dev.ts deleted file mode 100644 index 3374f3769ba..00000000000 --- a/ui/src/environments/femsserver-dev.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; - -class FemsserverDevEnvironment extends Environment { - public readonly production = false; - - public readonly websockets = [{ - name: location.hostname, - url: "ws://" + location.hostname + ":8078", - backend: Backend.FemsServer - }]; -} - -export const environment = new FemsserverDevEnvironment(); diff --git a/ui/src/environments/femsserver-local.ts b/ui/src/environments/femsserver-local.ts deleted file mode 100644 index dca9882db43..00000000000 --- a/ui/src/environments/femsserver-local.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; - -class FemsserverLocalEnvironment extends Environment { - public readonly production = false; - - public readonly websockets = [{ - name: location.hostname, - url: "ws://" + location.hostname + ":8089", - backend: Backend.FemsServer - }]; -} - -export const environment = new FemsserverLocalEnvironment(); diff --git a/ui/src/environments/femsserver.ts b/ui/src/environments/femsserver.ts deleted file mode 100644 index 98be6e89a51..00000000000 --- a/ui/src/environments/femsserver.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; - -class FemsserverEnvironment extends Environment { - public readonly production = true; - - public readonly websockets = [{ - name: location.hostname, - url: "wss://fenecon.de:443/femsmonitor", - backend: Backend.FemsServer - }]; -} - -export const environment = new FemsserverEnvironment(); diff --git a/ui/src/environments/openems-backend-dev.ts b/ui/src/environments/openems-backend-dev.ts new file mode 100644 index 00000000000..1793cb318bf --- /dev/null +++ b/ui/src/environments/openems-backend-dev.ts @@ -0,0 +1,10 @@ +import { Environment } from "../app/shared/type/environment"; +import { DefaultTypes } from '../app/shared/service/defaulttypes'; + +class OpenemsBackendDevEnvironment extends Environment { + public readonly production = false; + public readonly url = "ws://" + location.hostname + ":8076"; + public readonly backend: DefaultTypes.Backend = "OpenEMS Backend" +} + +export const environment = new OpenemsBackendDevEnvironment(); diff --git a/ui/src/environments/openems-backend.ts b/ui/src/environments/openems-backend.ts new file mode 100644 index 00000000000..4789fa588c5 --- /dev/null +++ b/ui/src/environments/openems-backend.ts @@ -0,0 +1,11 @@ +import { Environment } from "../app/shared/type/environment"; +import { DefaultTypes } from '../app/shared/service/defaulttypes'; + +class OpenemsBackendEnvironment extends Environment { + public readonly production = true; + public readonly url = (location.protocol == "https:" ? "wss" : "ws") + + "://" + location.hostname + ":" + location.port + "/openems-backend-ui"; + public readonly backend: DefaultTypes.Backend = "OpenEMS Backend"; +} + +export const environment = new OpenemsBackendEnvironment(); diff --git a/ui/src/environments/openems-edge.ts b/ui/src/environments/openems-edge.ts new file mode 100644 index 00000000000..e2694f9df03 --- /dev/null +++ b/ui/src/environments/openems-edge.ts @@ -0,0 +1,10 @@ +import { Environment } from "../app/shared/type/environment"; +import { DefaultTypes } from '../app/shared/service/defaulttypes'; + +class OpenemsEnvironment extends Environment { + public readonly production = true; + public readonly url = "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket"; + public readonly backend: DefaultTypes.Backend = "OpenEMS Edge"; +} + +export const environment = new OpenemsEnvironment(); diff --git a/ui/src/environments/openems.ts b/ui/src/environments/openems.ts deleted file mode 100644 index 878a16d38b0..00000000000 --- a/ui/src/environments/openems.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; - -class OpenemsEnvironment extends Environment { - public readonly production = true; - - public readonly websockets = [{ - name: location.hostname, - url: "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket", - backend: Backend.OpenEMS - }]; -} - -export const environment = new OpenemsEnvironment(); diff --git a/ui/src/environments/project-VA6403.ts b/ui/src/environments/project-VA6403.ts deleted file mode 100644 index afdd66557c7..00000000000 --- a/ui/src/environments/project-VA6403.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; - -class VA6403Environment extends Environment { - public readonly production = true; - - public readonly websockets = [{ - name: "Station D04", - url: "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket-d04", - backend: Backend.OpenEMS - }, { - name: "Station D02", - url: "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket-d02", - backend: Backend.OpenEMS - }, { - name: "Station J08", - url: "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket-j08", - backend: Backend.OpenEMS - }, { - name: "Station D12", - url: "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket-d12", - backend: Backend.OpenEMS - }, { - name: "Station D10", - url: "ws://" + location.hostname + (location.port ? ":" + location.port : "") + "/websocket-d10", - backend: Backend.OpenEMS - }]; -} - -export const environment = new VA6403Environment(); diff --git a/ui/src/environments/project-VA7282.ts b/ui/src/environments/project-VA7282.ts deleted file mode 100644 index b258f83a786..00000000000 --- a/ui/src/environments/project-VA7282.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { Environment } from "../app/shared/type/environment"; -import { Backend } from "../app/shared/type/backend"; -import { Config } from "../app/shared/device/config"; - -class VA7282Environment extends Environment { - public readonly production = true; - - public readonly websockets = [{ - name: "FEMS", - url: "ws://" + location.hostname + ":8085", - backend: Backend.OpenEMS - }]; - - public getCustomFields(config: Config) { - return { - sps0: { - WaterLevel: { - title: "Water level", - unit: "foot" - }, - GetPivotOn: { - title: "Pivot", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetBorehole1On: { - title: "Borehole 1", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetBorehole2On: { - title: "Borehole 2", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetBorehole3On: { - title: "Borehole 3", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetClima1On: { - title: "Aircondition 1", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetClima2On: { - title: "Aircondition 2", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetOfficeOn: { - title: "Office", - map: { - 0: 'Off', - 1: 'On' - } - }, - GetTraineeCentereOn: { - title: "Trainee Center", - map: { - 0: 'Off', - 1: 'On' - } - }, - AutomaticMode: { - title: "Automatic Mode", - map: { - 0: 'Off', - 1: 'On' - } - }, - ManualMode: { - title: "Manual Mode", - map: { - 0: 'Off', - 1: 'On' - } - }, - EmergencyStop: { - title: "Emergency Stop", - map: { - 0: 'Off', - 1: 'On' - } - }, - SwitchStatePivotPump: { - title: "Switch State Pivot Pump", - map: { - 0: 'Off', - 1: 'On' - } - }, - SwitchStatePivotDrive: { - title: "Switch State Pivot Drive", - map: { - 0: 'Off', - 1: 'On' - } - } - } - } - } -} - -export const environment = new VA7282Environment(); \ No newline at end of file diff --git a/ui/tsconfig.json b/ui/tsconfig.json index 1f9b55851be..a6c016bf38a 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -12,7 +12,7 @@ "node_modules/@types" ], "lib": [ - "es2016", + "es2017", "dom" ] } diff --git a/ui/yarn.lock b/ui/yarn.lock index 9aca48f3f45..8445d8aab84 100644 --- a/ui/yarn.lock +++ b/ui/yarn.lock @@ -2,24 +2,33 @@ # yarn lockfile v1 -"@angular/animations@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.1.tgz#1f7e0bb803efc21c608246e6765a1c647f3d1a5f" +"@angular-devkit/build-optimizer@0.0.13": + version "0.0.13" + resolved "https://registry.yarnpkg.com/@angular-devkit/build-optimizer/-/build-optimizer-0.0.13.tgz#cf397af76abe899aa909d4a735106694ca1f08cf" + dependencies: + loader-utils "^1.1.0" + source-map "^0.5.6" + typescript "^2.3.3" + +"@angular/animations@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/animations/-/animations-4.3.6.tgz#bf9283ec7c8c98b32f569d84dcda10890fdc0262" dependencies: tslib "^1.7.1" -"@angular/cdk@^2.0.0-beta.8": - version "2.0.0-beta.8" - resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-2.0.0-beta.8.tgz#71961c851dfbeb19e085e898bf5e4461408f8b57" +"@angular/cdk@2.0.0-beta.10": + version "2.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@angular/cdk/-/cdk-2.0.0-beta.10.tgz#7ffcc430d6f67dfde1df25e1fa693c188e4da08a" dependencies: tslib "^1.7.1" -"@angular/cli@1.2.7": - version "1.2.7" - resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.2.7.tgz#dd20e8b0da24af5359077c05a6944823cb764132" +"@angular/cli@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@angular/cli/-/cli-1.3.2.tgz#fb5e895621e32f053e757d97efa6d29954cef7fc" dependencies: + "@angular-devkit/build-optimizer" "0.0.13" "@ngtools/json-schema" "1.1.0" - "@ngtools/webpack" "1.5.5" + "@ngtools/webpack" "1.6.2" autoprefixer "^6.5.3" chalk "^2.0.1" circular-dependency-plugin "^3.0.0" @@ -32,22 +41,22 @@ ember-cli-normalize-entity-name "^1.0.0" ember-cli-string-utils "^1.0.0" exports-loader "^0.6.3" - extract-text-webpack-plugin "^2.1.0" + extract-text-webpack-plugin "3.0.0" file-loader "^0.10.0" fs-extra "^4.0.0" get-caller-file "^1.0.0" glob "^7.0.3" heimdalljs "^0.2.4" heimdalljs-logger "^0.1.9" - html-webpack-plugin "^2.19.0" + html-webpack-plugin "^2.29.0" inflection "^1.7.0" inquirer "^3.0.0" isbinaryfile "^3.0.0" istanbul-instrumenter-loader "^2.0.0" karma-source-map-support "^1.2.0" less "^2.7.2" - less-loader "^4.0.2" - license-webpack-plugin "^0.4.2" + less-loader "^4.0.5" + license-webpack-plugin "^0.5.1" lodash "^4.11.1" memory-fs "^0.4.1" minimatch "^3.0.3" @@ -60,7 +69,7 @@ raw-loader "^0.5.1" resolve "^1.1.7" rsvp "^3.0.17" - rxjs "^5.0.1" + rxjs "^5.4.2" sass-loader "^6.0.3" script-loader "^0.7.0" semver "^5.1.0" @@ -71,98 +80,100 @@ stylus "^0.54.5" stylus-loader "^3.0.1" temp "0.8.3" - typescript ">=2.0.0 <2.4.0" + typescript ">=2.0.0 <2.5.0" url-loader "^0.5.7" walk-sync "^0.3.1" - webpack "~2.4.0" - webpack-dev-middleware "^1.10.2" - webpack-dev-server "~2.4.5" - webpack-merge "^2.4.0" + webpack "~3.4.1" + webpack-dev-middleware "^1.11.0" + webpack-dev-server "~2.5.1" + webpack-merge "^4.1.0" zone.js "^0.8.14" optionalDependencies: node-sass "^4.3.0" -"@angular/common@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.3.1.tgz#260f487a7cdca326c436bd3ea9515c797de2ff72" +"@angular/common@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/common/-/common-4.3.6.tgz#ed37e9307c7506dd834797c1a6cf675e52b5b6ee" dependencies: tslib "^1.7.1" -"@angular/compiler-cli@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.3.1.tgz#00b41afb6faeb4aef561b8427804ac8880aff63c" +"@angular/compiler-cli@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/compiler-cli/-/compiler-cli-4.3.6.tgz#6afa6aef68dd681e61b398be4d6270e5c8680b12" dependencies: - "@angular/tsc-wrapped" "4.3.1" + "@angular/tsc-wrapped" "4.3.6" minimist "^1.2.0" reflect-metadata "^0.1.2" -"@angular/compiler@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.3.1.tgz#3a24d49ecf01ac2b6e07f63e378b8ff8e257fe09" +"@angular/compiler@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/compiler/-/compiler-4.3.6.tgz#be170df098b71e835ccedf168d5fb7b23e5045b8" dependencies: tslib "^1.7.1" -"@angular/core@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.3.1.tgz#a9d0a7d644b96260674269b689a04feea632a8d3" +"@angular/core@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/core/-/core-4.3.6.tgz#bbac63d68d0f7bcb389d12b34208652be3287e96" dependencies: tslib "^1.7.1" -"@angular/flex-layout@2.0.0-beta.8": - version "2.0.0-beta.8" - resolved "https://registry.yarnpkg.com/@angular/flex-layout/-/flex-layout-2.0.0-beta.8.tgz#b9cf57865a93ca158fe56d850952423f248d103b" +"@angular/flex-layout@2.0.0-beta.9": + version "2.0.0-beta.9" + resolved "https://registry.yarnpkg.com/@angular/flex-layout/-/flex-layout-2.0.0-beta.9.tgz#3e54f898e805d0b1426d15e6139db415d04ee75f" + dependencies: + tslib "^1.7.1" -"@angular/forms@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.3.1.tgz#33914da2cb146430ff901471e682c76654622dfe" +"@angular/forms@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/forms/-/forms-4.3.6.tgz#0f20c4597c16a152745d7cd95559855a0a5c6687" dependencies: tslib "^1.7.1" -"@angular/http@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.3.1.tgz#e4f661f746711e88ecbea76a3c905babf97d315a" +"@angular/http@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/http/-/http-4.3.6.tgz#563827d1a7d5e89e3b7d86b77fbbd367b2c08591" dependencies: tslib "^1.7.1" -"@angular/language-service@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-4.3.1.tgz#9910833fc89ddaac3e48d3ffaf8bb893618cda11" +"@angular/language-service@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/language-service/-/language-service-4.3.6.tgz#71cf2dbb4661568f3d12a9c0e4b9e043ef93bd3a" -"@angular/material@2.0.0-beta.8": - version "2.0.0-beta.8" - resolved "https://registry.yarnpkg.com/@angular/material/-/material-2.0.0-beta.8.tgz#a92852abc9261aea26c2401f576645470be2cf38" +"@angular/material@2.0.0-beta.10": + version "2.0.0-beta.10" + resolved "https://registry.yarnpkg.com/@angular/material/-/material-2.0.0-beta.10.tgz#23f5887d5e34da89dd6da1fe4da0b3f779da4807" dependencies: tslib "^1.7.1" -"@angular/platform-browser-dynamic@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.3.1.tgz#84034da60a82ef36e7effda7b3ade6e645b330b3" +"@angular/platform-browser-dynamic@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/platform-browser-dynamic/-/platform-browser-dynamic-4.3.6.tgz#9eabf826f119c98f85c2a96edcb18ab00b4efb1c" dependencies: tslib "^1.7.1" -"@angular/platform-browser@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.3.1.tgz#db727b06eed64bda5defec71815db26a4da2f690" +"@angular/platform-browser@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/platform-browser/-/platform-browser-4.3.6.tgz#6152b1f3b78d0246fc5e150e2f7b9ed4337e3ba6" dependencies: tslib "^1.7.1" -"@angular/platform-server@4.3.3": - version "4.3.3" - resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.3.3.tgz#404c00208d7258d4f9a54c9a5dda2147b21b6c9b" +"@angular/platform-server@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/platform-server/-/platform-server-4.3.6.tgz#369d49844f1c0a9a10c7cba9b0cb78c2520741a5" dependencies: parse5 "^3.0.1" tslib "^1.7.1" xhr2 "^0.1.4" -"@angular/router@^4.0.0": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.3.1.tgz#5219d44526156d816065841127610165a015b450" +"@angular/router@^4.2.4": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/router/-/router-4.3.6.tgz#64033edb4fcda08a323e7533b4a1820c0f28d130" dependencies: tslib "^1.7.1" -"@angular/tsc-wrapped@4.3.1": - version "4.3.1" - resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.3.1.tgz#f6616a4d2a3bbec1cded664fd1f526edce99ef41" +"@angular/tsc-wrapped@4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@angular/tsc-wrapped/-/tsc-wrapped-4.3.6.tgz#1aa66e0ab2c4799a4ad14b675e13953aa5fcd436" dependencies: tsickle "^0.21.0" @@ -170,25 +181,25 @@ version "1.1.0" resolved "https://registry.yarnpkg.com/@ngtools/json-schema/-/json-schema-1.1.0.tgz#c3a0c544d62392acc2813a42c8a0dc6f58f86922" -"@ngtools/webpack@1.5.5": - version "1.5.5" - resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.5.5.tgz#d6e2a933184015653de3be33d38437fdd81009e7" +"@ngtools/webpack@1.6.2": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@ngtools/webpack/-/webpack-1.6.2.tgz#70f2af1a59785d7abb9b4927a4aafdff2ef43a49" dependencies: loader-utils "^1.0.2" magic-string "^0.22.3" source-map "^0.5.6" "@ngx-translate/core@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-7.1.0.tgz#5087a65c8ff312e4244ca0646ed45cde83b170cd" + version "7.2.2" + resolved "https://registry.yarnpkg.com/@ngx-translate/core/-/core-7.2.2.tgz#e70069d5ef8e837ea336e6890773786f5efb5de5" "@types/d3-array@*": - version "1.1.1" - resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.1.1.tgz#11845239ae973849857a031530af77b970b9ff09" + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-1.2.1.tgz#e489605208d46a1c9d980d2e5772fa9c75d9ec65" "@types/d3-axis@*": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-1.0.7.tgz#d7aabb97e71247269648034ac19dab34e28c23ff" + version "1.0.9" + resolved "https://registry.yarnpkg.com/@types/d3-axis/-/d3-axis-1.0.9.tgz#62ce7bc8d04354298cda57f3f1d1f856ad69b89a" dependencies: "@types/d3-selection" "*" @@ -203,20 +214,20 @@ resolved "https://registry.yarnpkg.com/@types/d3-chord/-/d3-chord-1.0.6.tgz#0589eb97a3191f4edaf17b7bde498462890ce1ec" "@types/d3-collection@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.4.tgz#19b1eede200ba5bdafb4c8af19afe32c2de7a660" + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-collection/-/d3-collection-1.0.5.tgz#bb1f3aa97cdc8d881645541b9d6cf87edfee9bc3" "@types/d3-color@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.0.4.tgz#f0e1b64162fea2f932007fb966c26cc8aff3a47d" + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-color/-/d3-color-1.0.5.tgz#cad755f0fc6de7b70fa6e5e08afa81ef4c2248de" "@types/d3-dispatch@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-1.0.4.tgz#91b8de6198053efc46788ad5f27182bcc42335b5" + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-dispatch/-/d3-dispatch-1.0.5.tgz#f1f9187b538ecb05157569d8dc2f70dfb04f1b52" "@types/d3-drag@*": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-1.0.6.tgz#38b4130accd97b29269dc5899d59f87bc55eab7d" + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-drag/-/d3-drag-1.1.0.tgz#9105e35ca58aa0c4783f3ce83082bcb24ccb6960" dependencies: "@types/d3-selection" "*" @@ -225,20 +236,20 @@ resolved "https://registry.yarnpkg.com/@types/d3-dsv/-/d3-dsv-1.0.30.tgz#78e0dddde4283566f463e51551a97a63c170d5a8" "@types/d3-ease@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-1.0.5.tgz#cae0c4a6dd39be58cc1c94ad08bd991c2d932400" + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-ease/-/d3-ease-1.0.7.tgz#93a301868be9e15061f3d44343b1ab3f8acb6f09" "@types/d3-force@*": - version "1.0.6" - resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-1.0.6.tgz#cadf5c5591e5d554c93ddb1c359d2b0daa83abca" + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-force/-/d3-force-1.0.7.tgz#8e3c533697143ebb70275d56840206e8ba789185" "@types/d3-format@*": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.1.0.tgz#e04e8ba801560486304f7790765cf4d158f4458b" + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-format/-/d3-format-1.2.1.tgz#9435fb1771d2fbf6a858c93218f4097c9aa396c1" "@types/d3-geo@*": - version "1.6.1" - resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-1.6.1.tgz#c3a10f030ec5706ba633d6ed4f5f55bbc567b783" + version "1.6.3" + resolved "https://registry.yarnpkg.com/@types/d3-geo/-/d3-geo-1.6.3.tgz#21b501d1fe224d88877f39f84cb8c9dd8aa1bf28" dependencies: "@types/geojson" "*" @@ -247,78 +258,78 @@ resolved "https://registry.yarnpkg.com/@types/d3-hierarchy/-/d3-hierarchy-1.1.0.tgz#50f1ee052840638035cbdd4acab1fc3470905907" "@types/d3-interpolate@*": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-1.1.5.tgz#956485bb0f75c07b49b33f7b44a35cfa9f7f4f46" + version "1.1.6" + resolved "https://registry.yarnpkg.com/@types/d3-interpolate/-/d3-interpolate-1.1.6.tgz#64041b15c9c032c348da1b22baabc59fa4d16136" dependencies: "@types/d3-color" "*" "@types/d3-path@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.5.tgz#84e54043410048a188c03100592c2b9e417664f7" + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-path/-/d3-path-1.0.6.tgz#c1a7d2dc07b295fdd1c84dabe4404df991b48693" "@types/d3-polygon@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-1.0.4.tgz#44aa10bf081c8e87642827feb6285e8280c3eee4" + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-polygon/-/d3-polygon-1.0.5.tgz#35ad54ed84c39d7e9f1252b6535be600be6cace2" "@types/d3-quadtree@*": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-1.0.4.tgz#fb164e00a82f3105b810afad757804664742c049" + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-quadtree/-/d3-quadtree-1.0.5.tgz#1ce1e659eae4530df0cb127f297f1741a367a82e" "@types/d3-queue@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-queue/-/d3-queue-3.0.4.tgz#a86b6d55e307d363aedd5153d8f1117793df6710" + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-queue/-/d3-queue-3.0.5.tgz#3e4cbe2aff61db6a0b2b8c4800299e4ec6acc850" "@types/d3-random@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-1.0.5.tgz#e1d212f503376899f8a290ebae9cbf606eac856d" + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-random/-/d3-random-1.1.0.tgz#2dd08f1159c70719270e4a7c834af85c8b88d2c3" "@types/d3-request@*": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/d3-request/-/d3-request-1.0.1.tgz#b3e8b79c97618b396814ee8931a318efe9a51c64" + version "1.0.2" + resolved "https://registry.yarnpkg.com/@types/d3-request/-/d3-request-1.0.2.tgz#db9db8154f47816584706c6e6f702be66f22f4be" dependencies: "@types/d3-dsv" "*" "@types/d3-scale@*": - version "1.0.7" - resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-1.0.7.tgz#048df21e8b20f8e129cb84feccd2274c986f5f53" + version "1.0.10" + resolved "https://registry.yarnpkg.com/@types/d3-scale/-/d3-scale-1.0.10.tgz#8c5c1dca54a159eed042b46719dbb3bdb7e8c842" dependencies: "@types/d3-time" "*" "@types/d3-selection@*": - version "1.0.13" - resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.0.13.tgz#a62e30fe7c9663e490d8b001ac733da084c535c1" + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-selection/-/d3-selection-1.1.0.tgz#59b88f10d2cff7d9ffd7fe986b3aaef3de048224" "@types/d3-shape@*": - version "1.0.9" - resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.0.9.tgz#a5f4756b8affaf66ee350a2067282e406cb2b5d6" + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/d3-shape/-/d3-shape-1.2.1.tgz#cac2d9f0122f173220c32c8c152dc42ee9349df2" dependencies: "@types/d3-path" "*" "@types/d3-time-format@*": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-2.0.4.tgz#3f4749652f347bce4b1c405053b0cfc5154cf537" + version "2.0.5" + resolved "https://registry.yarnpkg.com/@types/d3-time-format/-/d3-time-format-2.0.5.tgz#1d4c5ba77ed5352b10c7fce062c883382f1e16e0" "@types/d3-time@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.5.tgz#c86f1f43dc2b22d3c6c75cd1734effeda74213e5" + version "1.0.7" + resolved "https://registry.yarnpkg.com/@types/d3-time/-/d3-time-1.0.7.tgz#4266d7c9be15fa81256a88d1d052d61cd8dc572c" "@types/d3-timer@*": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-1.0.5.tgz#aad31e6aa185a6440f544d9167ef3194667d7f1e" + version "1.0.6" + resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-1.0.6.tgz#786d4e20731adf03af2c5df6c86fe29667fe429b" "@types/d3-transition@*": - version "1.0.8" - resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-1.0.8.tgz#56de7b0aed94c3637a66fc352673bff53063974f" + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/d3-transition/-/d3-transition-1.1.0.tgz#74475d4a8f8a0944a517d5ef861970cc30287e40" dependencies: "@types/d3-selection" "*" "@types/d3-voronoi@*": - version "1.1.5" - resolved "https://registry.yarnpkg.com/@types/d3-voronoi/-/d3-voronoi-1.1.5.tgz#67d03acb2d56006bb202c20cee1b8d5ad9fe0461" + version "1.1.7" + resolved "https://registry.yarnpkg.com/@types/d3-voronoi/-/d3-voronoi-1.1.7.tgz#c0a145cf04395927e01706ff6c4ff835c97a8ece" "@types/d3-zoom@*": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-1.1.2.tgz#cad8b5db137cff55cbba285a87abc2262c3a0ce6" + version "1.5.0" + resolved "https://registry.yarnpkg.com/@types/d3-zoom/-/d3-zoom-1.5.0.tgz#21f690b25a8419fd1bcc95ac629cefdfb462c70f" dependencies: "@types/d3-interpolate" "*" "@types/d3-selection" "*" @@ -359,22 +370,22 @@ "@types/d3-zoom" "*" "@types/geojson@*": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.0.tgz#6af48a0d492c09068e97cb78e6bef940d57fb36d" + version "1.0.3" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-1.0.3.tgz#fbcf7fa5eb6dd108d51385cc6987ec1f24214523" "@types/jasmine@*", "@types/jasmine@~2.5.53": - version "2.5.53" - resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.5.53.tgz#4e0cefad09df5ec48c8dd40433512f84b1568d61" + version "2.5.54" + resolved "https://registry.yarnpkg.com/@types/jasmine/-/jasmine-2.5.54.tgz#a6b5f2ae2afb6e0307774e8c7c608e037d491c63" "@types/jasminewd2@~2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.2.tgz#5f68e1e697bf10bc6fd8cbf2e006a3d6712c5b64" + version "2.0.3" + resolved "https://registry.yarnpkg.com/@types/jasminewd2/-/jasminewd2-2.0.3.tgz#0d2886b0cbdae4c0eeba55e30792f584bf040a95" dependencies: "@types/jasmine" "*" "@types/node@^6.0.46", "@types/node@~6.0.60": - version "6.0.68" - resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.68.tgz#0c43b6b8b9445feb86a0fbd3457e3f4bc591e66d" + version "6.0.88" + resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.88.tgz#f618f11a944f6a18d92b5c472028728a3e3d4b66" "@types/q@^0.0.32": version "0.0.32" @@ -402,12 +413,12 @@ acorn-dynamic-import@^2.0.0: acorn "^4.0.3" acorn@^4.0.3: - version "4.0.11" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.11.tgz#edcda3bd937e7556410d42ed5860f67399c794c0" + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" acorn@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" + version "5.1.2" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.1.2.tgz#911cb53e036807cf0fa778dc5d370fbd864246d7" adm-zip@0.4.4: version "0.4.4" @@ -422,21 +433,30 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" agent-base@2: - version "2.0.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.0.1.tgz#bd8f9e86a8eb221fffa07bd14befd55df142815e" + version "2.1.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-2.1.1.tgz#d6de10d5af6132d5bd692427d46fc538539094c7" dependencies: extend "~3.0.0" semver "~5.0.1" -ajv-keywords@^1.1.1: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" +ajv-keywords@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-2.1.0.tgz#a296e17f7bfae7c1ce4f7e0de53d29cb32162df0" + +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" -ajv@^4.11.2, ajv@^4.7.0, ajv@^4.9.1: - version "4.11.5" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.5.tgz#b6ee74657b993a01dce44b7944d56f485828d5bd" +ajv@^5.0.0, ajv@^5.1.5: + version "5.2.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.2.2.tgz#47c68d69e86f5d953103b0074a9430dc63da5e39" dependencies: co "^4.6.0" + fast-deep-equal "^1.0.0" + json-schema-traverse "^0.3.0" json-stable-stringify "^1.0.1" align-text@^0.1.1, align-text@^0.1.3: @@ -455,17 +475,13 @@ amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" -angular-spinners@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/angular-spinners/-/angular-spinners-5.0.4.tgz#7ca35780dae3c9e4710011591127543785f80f1e" - angular2-uuid@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/angular2-uuid/-/angular2-uuid-1.1.1.tgz#72f03cd532b7f40032eb1ecfb9f8457384be956e" -ansi-escapes@^1.1.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" ansi-html@0.0.7: version "0.0.7" @@ -475,6 +491,10 @@ ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -486,11 +506,11 @@ ansi-styles@^3.1.0: color-convert "^1.9.0" anymatch@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" dependencies: - arrify "^1.0.0" micromatch "^2.1.5" + normalize-path "^2.0.0" app-root-path@^2.0.1: version "2.0.1" @@ -503,15 +523,15 @@ append-transform@^0.4.0: default-require-extensions "^1.0.0" aproba@^1.0.3: - version "1.1.1" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.1.tgz#95d3600f07710aa0e9298c726ad5ecf2eacbabab" + version "1.1.2" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" are-we-there-yet@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.2.tgz#80e470e95a084794fe1899262c5667c6e88de1b3" + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" dependencies: delegates "^1.0.0" - readable-stream "^2.0.0 || ^1.1.13" + readable-stream "^2.0.6" argparse@^1.0.7: version "1.0.9" @@ -526,8 +546,8 @@ arr-diff@^2.0.0: arr-flatten "^1.0.1" arr-flatten@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b" + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" array-find-index@^1.0.1: version "1.0.2" @@ -537,6 +557,10 @@ array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" +array-flatten@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-2.1.1.tgz#426bb9da84090c1838d812c8150af20a8331e296" + array-slice@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" @@ -564,8 +588,8 @@ arrify@^1.0.0: resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" asap@~2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" + version "2.0.6" + resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" asn1.js@^4.0.0: version "4.9.1" @@ -609,9 +633,9 @@ async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" -async@^2.1.2, async@^2.1.4, async@^2.1.5: - version "2.2.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.2.0.tgz#c324eba010a237e4fbd55a12dee86367d5c0ef32" +async@^2.1.2, async@^2.1.4, async@^2.1.5, async@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/async/-/async-2.5.0.tgz#843190fd6b7357a0b9e1c956edddd5ec8462b54d" dependencies: lodash "^4.14.0" @@ -642,25 +666,25 @@ aws4@^1.2.1: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -babel-code-frame@^6.11.0, babel-code-frame@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" +babel-code-frame@^6.11.0, babel-code-frame@^6.22.0, babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" dependencies: - chalk "^1.1.0" + chalk "^1.1.3" esutils "^2.0.2" - js-tokens "^3.0.0" + js-tokens "^3.0.2" babel-generator@^6.18.0: - version "6.24.0" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.24.0.tgz#eba270a8cc4ce6e09a61be43465d7c62c1f87c56" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.0.tgz#ac1ae20070b79f6e3ca1d3269613053774f20dc5" dependencies: babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-types "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" detect-indent "^4.0.0" jsesc "^1.3.0" - lodash "^4.2.0" - source-map "^0.5.0" + lodash "^4.17.4" + source-map "^0.5.6" trim-right "^1.0.1" babel-messages@^6.23.0: @@ -669,49 +693,49 @@ babel-messages@^6.23.0: dependencies: babel-runtime "^6.22.0" -babel-runtime@^6.18.0, babel-runtime@^6.22.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" dependencies: core-js "^2.4.0" - regenerator-runtime "^0.10.0" + regenerator-runtime "^0.11.0" babel-template@^6.16.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.23.0.tgz#04d4f270adbb3aa704a8143ae26faa529238e638" + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.23.0" - babel-types "^6.23.0" - babylon "^6.11.0" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" -babel-traverse@^6.18.0, babel-traverse@^6.23.0: - version "6.23.1" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.23.1.tgz#d3cb59010ecd06a97d81310065f966b699e14f48" +babel-traverse@^6.18.0, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" dependencies: - babel-code-frame "^6.22.0" + babel-code-frame "^6.26.0" babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-types "^6.23.0" - babylon "^6.15.0" - debug "^2.2.0" - globals "^9.0.0" - invariant "^2.2.0" - lodash "^4.2.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" -babel-types@^6.18.0, babel-types@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.23.0.tgz#bb17179d7538bad38cd0c9e115d340f77e7e9acf" +babel-types@^6.18.0, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" dependencies: - babel-runtime "^6.22.0" + babel-runtime "^6.26.0" esutils "^2.0.2" - lodash "^4.2.0" - to-fast-properties "^1.0.1" + lodash "^4.17.4" + to-fast-properties "^1.0.3" -babylon@^6.11.0, babylon@^6.13.0, babylon@^6.15.0: - version "6.16.1" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.16.1.tgz#30c5a22f481978a9e7f8cdfdf496b11d94b404d3" +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" backo2@1.0.2: version "1.0.2" @@ -730,16 +754,16 @@ base64-arraybuffer@0.1.5: resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" base64-js@^1.0.2: - version "1.2.0" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.0.tgz#a39992d723584811982be5e290bb6a53d86700f1" + version "1.2.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.2.1.tgz#a91947da1f4a516ea38e5b4ec0ec3773675e0886" base64id@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/base64id/-/base64id-1.0.0.tgz#47688cb99bb6804f0e06d3e763b1c32e57d8e6b6" -batch@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/batch/-/batch-0.5.3.tgz#3f3414f380321743bfc1042f9a83ff1d5824d464" +batch@0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/batch/-/batch-0.6.1.tgz#dc34314f4e679318093fc760272525f94bf25c16" bcrypt-pbkdf@^1.0.0: version "1.0.1" @@ -758,8 +782,8 @@ big.js@^3.1.3: resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.1.3.tgz#4cada2193652eb3ca9ec8e55c9015669c9806978" binary-extensions@^1.0.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + version "1.10.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.10.0.tgz#9aeb9a6c5e88638aad171e167f5900abe24835d0" blob@0.0.4: version "0.0.4" @@ -782,8 +806,8 @@ bluebird@^3.3.0, bluebird@^3.4.7: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: - version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" + version "4.11.8" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" body-parser@^1.16.1: version "1.17.2" @@ -800,6 +824,17 @@ body-parser@^1.16.1: raw-body "~2.2.0" type-is "~1.6.15" +bonjour@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/bonjour/-/bonjour-3.5.0.tgz#8e890a183d8ee9a2393b3844c691a42bcf7bc9f5" + dependencies: + array-flatten "^2.1.0" + deep-equal "^1.0.1" + dns-equal "^1.0.0" + dns-txt "^2.0.2" + multicast-dns "^6.0.1" + multicast-dns-service-types "^1.1.0" + boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -836,14 +871,15 @@ brorand@^1.0.1: resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.0.6" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.6.tgz#5e7725dbdef1fd5930d4ebab48567ce451c48a0a" + version "1.0.8" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.0.8.tgz#c8fa3b1b7585bb7ba77c5560b60996ddec6d5309" dependencies: - buffer-xor "^1.0.2" + buffer-xor "^1.0.3" cipher-base "^1.0.0" create-hash "^1.1.0" - evp_bytestokey "^1.0.0" + evp_bytestokey "^1.0.3" inherits "^2.0.1" + safe-buffer "^5.0.1" browserify-cipher@^1.0.0: version "1.0.0" @@ -886,18 +922,18 @@ browserify-zlib@^0.1.4: dependencies: pako "~0.2.0" -browserslist@^1.0.1, browserslist@^1.5.2, browserslist@^1.7.6: +browserslist@^1.3.6, browserslist@^1.5.2, browserslist@^1.7.6: version "1.7.7" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" dependencies: caniuse-db "^1.0.30000639" electron-to-chromium "^1.2.7" -buffer-shims@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51" +buffer-indexof@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c" -buffer-xor@^1.0.2: +buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" @@ -917,14 +953,14 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" -bytes@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.3.0.tgz#d5b680a165b6201739acb611542aabc2d8ceb070" - bytes@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.4.0.tgz#7d97196f9d5baf7f6935e25985549edd2a6c2339" +bytes@2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-2.5.0.tgz#4c9423ea2d252c270c41b2bdefeff9bb6b62c06a" + callsite@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/callsite/-/callsite-1.0.0.tgz#280398e5d664bd74038b6f0905153e6e8af1bc20" @@ -955,18 +991,22 @@ camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" +camelcase@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd" + caniuse-api@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.5.3.tgz#5018e674b51c393e4d50614275dc017e27c4a2a2" + version "1.6.1" + resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-1.6.1.tgz#b534e7c734c4f81ec5fbe8aca2ad24354b962c6c" dependencies: - browserslist "^1.0.1" - caniuse-db "^1.0.30000346" - lodash.memoize "^4.1.0" - lodash.uniq "^4.3.0" + browserslist "^1.3.6" + caniuse-db "^1.0.30000529" + lodash.memoize "^4.1.2" + lodash.uniq "^4.5.0" -caniuse-db@^1.0.30000346, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: - version "1.0.30000646" - resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000646.tgz#c724b90d61df24286e015fc528d062073c00def4" +caniuse-db@^1.0.30000529, caniuse-db@^1.0.30000634, caniuse-db@^1.0.30000639: + version "1.0.30000726" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000726.tgz#9bb742f8d026a62df873bc03c06843d2255b60d7" caseless@~0.12.0: version "0.12.0" @@ -979,7 +1019,7 @@ center-align@^0.1.1: align-text "^0.1.3" lazy-cache "^1.0.3" -chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: +chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" dependencies: @@ -989,9 +1029,9 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.0.1.tgz#dbec49436d2ae15f536114e76d14656cdbc0f44d" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.1.0.tgz#ac5becf14fa21b99c6c92ca7a7d7cfd5b17e743e" dependencies: ansi-styles "^3.1.0" escape-string-regexp "^1.0.5" @@ -1017,9 +1057,9 @@ chartjs-color@^2.1.0: chartjs-color-string "^0.4.0" color-convert "^0.5.3" -chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0: - version "1.6.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.6.1.tgz#2f4447ab5e96e50fb3d789fd90d4c72e0e4c70c2" +chokidar@^1.4.1, chokidar@^1.6.0, chokidar@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" dependencies: anymatch "^1.3.0" async-each "^1.0.0" @@ -1032,19 +1072,20 @@ chokidar@^1.4.1, chokidar@^1.4.3, chokidar@^1.6.0: optionalDependencies: fsevents "^1.0.0" -cipher-base@^1.0.0, cipher-base@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.3.tgz#eeabf194419ce900da3018c207d212f2a6df0a07" +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" dependencies: inherits "^2.0.1" + safe-buffer "^5.0.1" circular-dependency-plugin@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/circular-dependency-plugin/-/circular-dependency-plugin-3.0.0.tgz#9b68692e35b0e3510998d0164b6ae5011bea5760" clap@^1.0.9: - version "1.1.3" - resolved "https://registry.yarnpkg.com/clap/-/clap-1.1.3.tgz#b3bd36e93dd4cbfb395a3c26896352445265c05b" + version "1.2.0" + resolved "https://registry.yarnpkg.com/clap/-/clap-1.2.0.tgz#59c90fe3e137104746ff19469a27a634ff68c857" dependencies: chalk "^1.1.3" @@ -1052,9 +1093,9 @@ classlist.js@^1.1.20150312: version "1.1.20150312" resolved "https://registry.yarnpkg.com/classlist.js/-/classlist.js-1.1.20150312.tgz#1d70842f7022f08d9ac086ce69e5b250f2c57789" -clean-css@4.0.x: - version "4.0.10" - resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.0.10.tgz#6be448d6ba8c767654ebe11f158b97a887cb713f" +clean-css@4.1.x: + version "4.1.8" + resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.1.8.tgz#061455b2494a750ac98f46d8d5ebb17c679ea9d1" dependencies: source-map "0.5.x" @@ -1065,8 +1106,8 @@ cli-cursor@^2.1.0: restore-cursor "^2.0.0" cli-width@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + version "2.2.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" cliui@^2.1.0: version "2.1.0" @@ -1084,14 +1125,13 @@ cliui@^3.2.0: strip-ansi "^3.0.1" wrap-ansi "^2.0.0" -clone-deep@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" +clone-deep@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.3.0.tgz#348c61ae9cdbe0edfe053d91ff4cc521d790ede8" dependencies: - for-own "^0.1.3" + for-own "^1.0.0" is-plain-object "^2.0.1" - kind-of "^3.0.2" - lazy-cache "^1.0.3" + kind-of "^3.2.2" shallow-clone "^0.1.2" clone@^1.0.2: @@ -1107,8 +1147,8 @@ co@^4.6.0: resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" coa@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.1.tgz#7f959346cfc8719e3f7233cd6852854a7c67d8a3" + version "1.0.4" + resolved "https://registry.yarnpkg.com/coa/-/coa-1.0.4.tgz#a9ef153660d6a86a8bdec0289a5c684d217432fd" dependencies: q "^1.1.2" @@ -1116,9 +1156,9 @@ code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" -codelyzer@~3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.0.1.tgz#ba66b7b2aa564fe9f45d6004b4003ad2cf116828" +codelyzer@~3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/codelyzer/-/codelyzer-3.1.2.tgz#9ff1f041fb9b5ee5dbeb45ba866dfaf04983af04" dependencies: app-root-path "^2.0.1" css-selector-tokenizer "^0.7.0" @@ -1138,8 +1178,8 @@ color-convert@^1.3.0, color-convert@^1.9.0: color-name "^1.1.1" color-name@^1.0.0, color-name@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" color-string@^0.3.0: version "0.3.0" @@ -1179,11 +1219,9 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -commander@2, commander@2.9.x: - version "2.9.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" - dependencies: - graceful-readlink ">= 1.0.0" +commander@2, commander@2.11.x, commander@~2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" common-tags@^1.3.1: version "1.4.0" @@ -1207,22 +1245,23 @@ component-inherit@0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/component-inherit/-/component-inherit-0.0.3.tgz#645fc4adf58b72b649d5cae65135619db26ff143" -compressible@~2.0.8: - version "2.0.10" - resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.10.tgz#feda1c7f7617912732b29bf8cf26252a20b9eecd" +compressible@~2.0.10: + version "2.0.11" + resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.11.tgz#16718a75de283ed8e604041625a2064586797d8a" dependencies: - mime-db ">= 1.27.0 < 2" + mime-db ">= 1.29.0 < 2" compression@^1.5.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/compression/-/compression-1.6.2.tgz#cceb121ecc9d09c52d7ad0c3350ea93ddd402bc3" + version "1.7.0" + resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.0.tgz#030c9f198f1643a057d776a738e922da4373012d" dependencies: accepts "~1.3.3" - bytes "2.3.0" - compressible "~2.0.8" - debug "~2.2.0" + bytes "2.5.0" + compressible "~2.0.10" + debug "2.6.8" on-headers "~1.0.1" - vary "~1.1.0" + safe-buffer "5.1.1" + vary "~1.1.1" concat-map@0.0.1: version "0.0.1" @@ -1233,11 +1272,11 @@ connect-history-api-fallback@^1.3.0: resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.3.0.tgz#e51d17f8f0ef0db90a64fdb47de3051556e9f169" connect@^3.6.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.2.tgz#694e8d20681bfe490282c8ab886be98f09f42fe7" + version "3.6.3" + resolved "https://registry.yarnpkg.com/connect/-/connect-3.6.3.tgz#f7320d46a25b4be7b483a2236517f24b1e27e301" dependencies: - debug "2.6.7" - finalhandler "1.0.3" + debug "2.6.8" + finalhandler "1.0.4" parseurl "~1.3.1" utils-merge "1.0.0" @@ -1276,22 +1315,22 @@ cookie@0.3.1: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" core-js@^2.2.0, core-js@^2.4.0, core-js@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + version "2.5.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.1.tgz#ae6874dc66937789b80754ff5428df66819ca50b" core-object@^3.1.0: - version "3.1.3" - resolved "https://registry.yarnpkg.com/core-object/-/core-object-3.1.3.tgz#df399b3311bdb0c909e8aae8929fc3c1c4b25880" + version "3.1.5" + resolved "https://registry.yarnpkg.com/core-object/-/core-object-3.1.5.tgz#fa627b87502adc98045e44678e9a8ec3b9c0d2a9" dependencies: - chalk "^1.1.3" + chalk "^2.0.0" -core-util-is@~1.0.0: +core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" cosmiconfig@^2.1.0, cosmiconfig@^2.1.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.1.3.tgz#952771eb0dddc1cb3fa2f6fbe51a522e93b3ee0a" + version "2.2.2" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-2.2.2.tgz#6173cebd56fac042c1f4390edf7af6c07c7cb892" dependencies: is-directory "^0.3.1" js-yaml "^3.4.3" @@ -1308,21 +1347,25 @@ create-ecdh@^4.0.0: bn.js "^4.1.0" elliptic "^6.0.0" -create-hash@^1.1.0, create-hash@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.2.tgz#51210062d7bb7479f6c65bb41a92208b1d61abad" +create-hash@^1.1.0, create-hash@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.1.3.tgz#606042ac8b9262750f483caddab0f5819172d8fd" dependencies: cipher-base "^1.0.1" inherits "^2.0.1" - ripemd160 "^1.0.0" - sha.js "^2.3.6" + ripemd160 "^2.0.0" + sha.js "^2.4.0" -create-hmac@^1.1.0, create-hmac@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.4.tgz#d3fb4ba253eb8b3f56e39ea2fbcb8af747bd3170" +create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: + version "1.1.6" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.6.tgz#acb9e221a4e17bdb076e90657c42b93e3726cf06" dependencies: + cipher-base "^1.0.3" create-hash "^1.1.0" inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" cross-spawn@^3.0.0: version "3.0.1" @@ -1331,6 +1374,14 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" +cross-spawn@^5.0.1: + version "5.1.0" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449" + dependencies: + lru-cache "^4.0.1" + shebang-command "^1.2.0" + which "^1.2.9" + cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1338,8 +1389,8 @@ cryptiles@2.x.x: boom "2.x.x" crypto-browserify@^3.11.0: - version "3.11.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.0.tgz#3652a0906ab9b2a7e0c3ce66a408e957a2485522" + version "3.11.1" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.11.1.tgz#948945efc6757a400d6e5e5af47194d10064279f" dependencies: browserify-cipher "^1.0.0" browserify-sign "^4.0.0" @@ -1357,12 +1408,13 @@ css-color-names@0.0.4: resolved "https://registry.yarnpkg.com/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0" css-loader@^0.28.1: - version "0.28.2" - resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.2.tgz#0ff48e1d6013afcdb585d46c1e61a5fcd036404e" + version "0.28.7" + resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-0.28.7.tgz#5f2ee989dd32edd907717f953317656160999c1b" dependencies: babel-code-frame "^6.11.0" css-selector-tokenizer "^0.7.0" cssnano ">=2.6.1 <4" + icss-utils "^2.1.0" loader-utils "^1.0.2" lodash.camelcase "^4.3.0" object-assign "^4.0.1" @@ -1372,7 +1424,7 @@ css-loader@^0.28.1: postcss-modules-scope "^1.0.0" postcss-modules-values "^1.1.0" postcss-value-parser "^3.3.0" - source-list-map "^0.1.7" + source-list-map "^2.0.0" css-parse@1.7.x: version "1.7.0" @@ -1387,14 +1439,6 @@ css-select@^1.1.0: domutils "1.5.1" nth-check "~1.0.1" -css-selector-tokenizer@^0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.6.0.tgz#6445f582c7930d241dcc5007a43d6fcb8f073152" - dependencies: - cssesc "^0.1.0" - fastparse "^1.1.1" - regexpu-core "^1.0.0" - css-selector-tokenizer@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz#e6988474ae8c953477bf5e7efecfceccd9cf4c86" @@ -1681,6 +1725,12 @@ d3@4.10.0: d3-voronoi "1.1.2" d3-zoom "1.5.0" +d@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.0.tgz#754bb5bfe55451da69a58b94d45f4c5b0462d58f" + dependencies: + es5-ext "^0.10.9" + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -1691,13 +1741,13 @@ date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" -debug@*, debug@2, debug@2.6.7, debug@^2.2.0, debug@^2.6.3: - version "2.6.7" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" +debug@*, debug@2, debug@2.6.8, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" dependencies: ms "2.0.0" -debug@2.2.0, debug@~2.2.0: +debug@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" dependencies: @@ -1709,19 +1759,23 @@ debug@2.3.3: dependencies: ms "0.7.2" -debug@2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.1.tgz#79855090ba2c4e3115cc7d8769491d58f0491351" +debug@2.6.7: + version "2.6.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.7.tgz#92bad1f6d05bbb6bba22cca88bcd0ec894c2861e" dependencies: - ms "0.7.2" + ms "2.0.0" decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" +deep-equal@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.0.1.tgz#f5d260292b660e084eff4cdbc9f08ad3247448b5" + deep-extend@~0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.1.tgz#efe4113d08085f4e6f9687759810f807469e2253" + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" default-require-extensions@^1.0.0: version "1.0.0" @@ -1745,6 +1799,17 @@ del@^2.2.0: pinkie-promise "^2.0.0" rimraf "^2.2.8" +del@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/del/-/del-3.0.0.tgz#53ecf699ffcbcb39637691ab13baf160819766e5" + dependencies: + globby "^6.1.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + p-map "^1.1.1" + pify "^3.0.0" + rimraf "^2.2.8" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" @@ -1757,9 +1822,9 @@ denodeify@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/denodeify/-/denodeify-1.2.1.tgz#3a36287f5034e699e7577901052c2e6c94251631" -depd@1.1.0, depd@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.0.tgz#e1bd82c6aab6ced965b97b88b17ed3e528ca18c3" +depd@1.1.1, depd@~1.1.0, depd@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" des.js@^1.0.0: version "1.0.0" @@ -1778,13 +1843,17 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" +detect-node@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.3.tgz#a2033c09cc8e158d37748fbde7507832bd6ce127" + di@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" diff@^3.1.0, diff@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" + version "3.3.1" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.3.1.tgz#aa8567a6eed03c531fc89d3f711cd0e5259dec75" diffie-hellman@^5.0.0: version "5.0.2" @@ -1802,6 +1871,23 @@ directory-encoder@^0.7.2: handlebars "^1.3.0" img-stats "^0.5.2" +dns-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d" + +dns-packet@^1.0.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.2.2.tgz#a8a26bec7646438963fc86e06f8f8b16d6c8bf7a" + dependencies: + ip "^1.1.0" + safe-buffer "^5.0.1" + +dns-txt@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/dns-txt/-/dns-txt-2.0.2.tgz#b91d806f5d27188e4ab3e7d107d881a1cc4642b6" + dependencies: + buffer-indexof "^1.0.0" + dom-converter@~0.1: version "0.1.4" resolved "https://registry.yarnpkg.com/dom-converter/-/dom-converter-0.1.4.tgz#a45ef5727b890c9bffe6d7c876e7b19cb0e17f3b" @@ -1866,8 +1952,8 @@ ee-first@1.1.1: resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" electron-to-chromium@^1.2.7: - version "1.3.2" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.2.tgz#b8ce5c93b308db0e92f6d0435c46ddec8f6363ab" + version "1.3.21" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.21.tgz#a967ebdcfe8ed0083fc244d1894022a8e8113ea2" elliptic@^6.0.0: version "6.4.0" @@ -1938,14 +2024,14 @@ engine.io@1.8.3: engine.io-parser "1.3.2" ws "1.1.2" -enhanced-resolve@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.1.0.tgz#9f4b626f577245edcf4b2ad83d86e17f4f421dec" +enhanced-resolve@^3.4.0: + version "3.4.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz#0421e339fd71419b3da13d129b3979040230476e" dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" object-assign "^4.0.1" - tapable "^0.2.5" + tapable "^0.2.7" ensure-posix-path@^1.0.0: version "1.0.2" @@ -1971,6 +2057,58 @@ error-ex@^1.2.0: dependencies: is-arrayish "^0.2.1" +es5-ext@^0.10.14, es5-ext@^0.10.9, es5-ext@~0.10.14: + version "0.10.30" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.30.tgz#7141a16836697dbabfaaaeee41495ce29f52c939" + dependencies: + es6-iterator "2" + es6-symbol "~3.1" + +es6-iterator@2, es6-iterator@^2.0.1, es6-iterator@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.1.tgz#8e319c9f0453bf575d374940a655920e59ca5512" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-symbol "^3.1" + +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1, es6-symbol@^3.1, es6-symbol@^3.1.1, es6-symbol@~3.1, es6-symbol@~3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-weak-map@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.2.tgz#5e3ab32251ffd1538a1f8e5ffa1357772f92d96f" + dependencies: + d "1" + es5-ext "^0.10.14" + es6-iterator "^2.0.1" + es6-symbol "^3.1.1" + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -1979,13 +2117,33 @@ escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + esprima@^2.6.0: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" -esprima@^3.1.1: - version "3.1.3" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" +esprima@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" + +esrecurse@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" + dependencies: + estraverse "^4.1.0" + object-assign "^4.0.1" + +estraverse@^4.1.0, estraverse@^4.1.1: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" esutils@^2.0.2: version "2.0.2" @@ -1995,6 +2153,13 @@ etag@~1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.0.tgz#6f631aef336d6c46362b51764044ce216be3c051" +event-emitter@~0.3.5: + version "0.3.5" + resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" + dependencies: + d "1" + es5-ext "~0.10.14" + eventemitter3@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.2.0.tgz#1c86991d816ad1e504750e73874224ecf3bec508" @@ -2009,11 +2174,24 @@ eventsource@0.1.6: dependencies: original ">=0.0.5" -evp_bytestokey@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.0.tgz#497b66ad9fef65cd7c08a6180824ba1476b66e53" +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" dependencies: - create-hash "^1.1.1" + md5.js "^1.3.4" + safe-buffer "^5.1.1" + +execa@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777" + dependencies: + cross-spawn "^5.0.1" + get-stream "^3.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" exit@^0.1.2: version "0.1.2" @@ -2054,8 +2232,8 @@ exports-loader@^0.6.3: source-map "0.5.x" express@^4.13.3: - version "4.15.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.15.2.tgz#af107fc148504457f2dca9a6f2571d7129b97b35" + version "4.15.4" + resolved "https://registry.yarnpkg.com/express/-/express-4.15.4.tgz#032e2253489cf8fce02666beca3d11ed7a2daed1" dependencies: accepts "~1.3.3" array-flatten "1.1.1" @@ -2063,37 +2241,39 @@ express@^4.13.3: content-type "~1.0.2" cookie "0.3.1" cookie-signature "1.0.6" - debug "2.6.1" - depd "~1.1.0" + debug "2.6.8" + depd "~1.1.1" encodeurl "~1.0.1" escape-html "~1.0.3" etag "~1.8.0" - finalhandler "~1.0.0" + finalhandler "~1.0.4" fresh "0.5.0" merge-descriptors "1.0.1" methods "~1.1.2" on-finished "~2.3.0" parseurl "~1.3.1" path-to-regexp "0.1.7" - proxy-addr "~1.1.3" - qs "6.4.0" + proxy-addr "~1.1.5" + qs "6.5.0" range-parser "~1.2.0" - send "0.15.1" - serve-static "1.12.1" + send "0.15.4" + serve-static "1.12.4" setprototypeof "1.0.3" statuses "~1.3.1" - type-is "~1.6.14" + type-is "~1.6.15" utils-merge "1.0.0" - vary "~1.1.0" + vary "~1.1.1" extend@3, extend@^3.0.0, extend@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.0.tgz#5a474353b9f3353ddd8176dfd37b91c83a46f1d4" + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" -external-editor@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.1.tgz#4c597c6c88fa6410e41dbbaa7b1be2336aa31095" +external-editor@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" tmp "^0.0.31" extglob@^0.3.1: @@ -2102,18 +2282,22 @@ extglob@^0.3.1: dependencies: is-extglob "^1.0.0" -extract-text-webpack-plugin@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-2.1.0.tgz#69315b885f876dbf96d3819f6a9f1cca7aebf159" +extract-text-webpack-plugin@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/extract-text-webpack-plugin/-/extract-text-webpack-plugin-3.0.0.tgz#90caa7907bc449f335005e3ac7532b41b00de612" dependencies: - ajv "^4.11.2" - async "^2.1.2" - loader-utils "^1.0.2" - webpack-sources "^0.1.0" + async "^2.4.1" + loader-utils "^1.1.0" + schema-utils "^0.3.0" + webpack-sources "^1.0.1" -extsprintf@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" +extsprintf@1.3.0, extsprintf@^1.2.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + +fast-deep-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" fastparse@^1.1.1: version "1.1.1" @@ -2144,8 +2328,8 @@ file-loader@^0.10.0: loader-utils "^1.0.2" filename-regex@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.0.tgz#996e3e80479b98b9897f15a8a58b3d084e926775" + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" fileset@^2.0.2: version "2.0.3" @@ -2164,11 +2348,11 @@ fill-range@^2.1.0: repeat-element "^1.1.2" repeat-string "^1.5.2" -finalhandler@1.0.3, finalhandler@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.3.tgz#ef47e77950e999780e86022a560e3217e0d0cc89" +finalhandler@1.0.4, finalhandler@~1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.0.4.tgz#18574f2e7c4b98b8ae3b230c21f201f31bdb3fb7" dependencies: - debug "2.6.7" + debug "2.6.8" encodeurl "~1.0.1" escape-html "~1.0.3" on-finished "~2.3.0" @@ -2183,6 +2367,12 @@ find-up@^1.0.0: path-exists "^2.0.0" pinkie-promise "^2.0.0" +find-up@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + flatten@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.2.tgz#dae46a9d78fbe25292258cc1e780a41d95c03782" @@ -2195,19 +2385,25 @@ for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" -for-own@^0.1.3, for-own@^0.1.4: +for-own@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" dependencies: for-in "^1.0.1" +for-own@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-1.0.0.tgz#c63332f415cedc4b04dbfe70cf836494c53cb44b" + dependencies: + for-in "^1.0.1" + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" form-data@~2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.2.tgz#89c3534008b97eada4cbb157d58f6f5df025eae4" + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" dependencies: asynckit "^0.4.0" combined-stream "^1.0.5" @@ -2249,11 +2445,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" fsevents@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.1.tgz#f19fd28f43eeaf761680e519a203c4d0b3d31aff" + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" dependencies: nan "^2.3.0" - node-pre-gyp "^0.6.29" + node-pre-gyp "^0.6.36" fstream-ignore@^1.0.5: version "1.0.5" @@ -2273,12 +2469,12 @@ fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: rimraf "2" function-bind@^1.0.2: - version "1.1.0" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" -gauge@~2.7.1: - version "2.7.3" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.3.tgz#1c23855f962f17b3ad3d0dc7443f304542edfe09" +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" @@ -2303,9 +2499,13 @@ get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" +get-stream@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + getpass@^0.1.1: - version "0.1.6" - resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.6.tgz#283ffd9fc1256840875311c1b60e8c40187110e6" + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" dependencies: assert-plus "^1.0.0" @@ -2334,19 +2534,19 @@ glob@7.0.x: path-is-absolute "^1.0.0" glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@~7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.2" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" -globals@^9.0.0: - version "9.17.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.17.0.tgz#0c0ca696d9b9bb694d2e5470bd37777caad50286" +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" globby@^5.0.0: version "5.0.0" @@ -2359,27 +2559,33 @@ globby@^5.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +globby@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-6.1.0.tgz#f5a6d70e8395e21c858fb0489d64df02424d506c" + dependencies: + array-union "^1.0.1" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + globule@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.1.0.tgz#c49352e4dc183d85893ee825385eb994bb6df45f" + version "1.2.0" + resolved "https://registry.yarnpkg.com/globule/-/globule-1.2.0.tgz#1dc49c6822dd9e8a2fa00ba2a295006e8664bd09" dependencies: glob "~7.1.1" - lodash "~4.16.4" + lodash "~4.17.4" minimatch "~3.0.2" graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" - hammerjs@2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" -handle-thing@^1.2.4: +handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" @@ -2392,8 +2598,8 @@ handlebars@^1.3.0: uglify-js "~2.3" handlebars@^4.0.3: - version "4.0.6" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7" + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" dependencies: async "^1.4.0" optimist "^0.6.1" @@ -2446,11 +2652,25 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.0.3.tgz#1332ff00156c0a0ffdd8236013d07b77a0451573" +hash-base@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-2.0.2.tgz#66ea1d856db4e8a5470cadf6fce23ae5244ef2e1" + dependencies: + inherits "^2.0.1" + +hash-base@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" dependencies: inherits "^2.0.1" + safe-buffer "^5.0.1" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.0" hawk@~3.1.3: version "3.1.3" @@ -2479,8 +2699,8 @@ heimdalljs@^0.2.0, heimdalljs@^0.2.4: rsvp "~3.2.1" hmac-drbg@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.0.tgz#3db471f45aae4a994a0688322171f51b8b91bee5" + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" @@ -2491,8 +2711,8 @@ hoek@2.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" hosted-git-info@^2.1.4: - version "2.4.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.1.tgz#4b0445e41c004a8bd1337773a4ff790ca40318c8" + version "2.5.0" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" hpack.js@^2.1.6: version "2.1.6" @@ -2508,25 +2728,25 @@ html-comment-regex@^1.1.0: resolved "https://registry.yarnpkg.com/html-comment-regex/-/html-comment-regex-1.1.1.tgz#668b93776eaae55ebde8f3ad464b307a4963625e" html-entities@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.0.tgz#41948caf85ce82fed36e4e6a0ed371a6664379e2" + version "1.2.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" html-minifier@^3.2.3: - version "3.4.2" - resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.4.2.tgz#31896baaf735c1d95f7a0b7291f9dc36c0720752" + version "3.5.3" + resolved "https://registry.yarnpkg.com/html-minifier/-/html-minifier-3.5.3.tgz#4a275e3b1a16639abb79b4c11191ff0d0fcf1ab9" dependencies: camel-case "3.0.x" - clean-css "4.0.x" - commander "2.9.x" + clean-css "4.1.x" + commander "2.11.x" he "1.1.x" ncname "1.0.x" param-case "2.1.x" relateurl "0.2.x" - uglify-js "2.8.x" + uglify-js "3.0.x" -html-webpack-plugin@^2.19.0: - version "2.28.0" - resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.28.0.tgz#2e7863b57e5fd48fe263303e2ffc934c3064d009" +html-webpack-plugin@^2.29.0: + version "2.30.1" + resolved "https://registry.yarnpkg.com/html-webpack-plugin/-/html-webpack-plugin-2.30.1.tgz#7f9c421b7ea91ec460f56527d78df484ee7537d5" dependencies: bluebird "^3.4.7" html-minifier "^3.2.3" @@ -2544,23 +2764,15 @@ htmlparser2@~3.3.0: domutils "1.1" readable-stream "1.0" -http-deceiver@^1.2.4: +http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" -http-errors@~1.5.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.5.1.tgz#788c0d2c1de2c81b9e6e8c01843b6b97eb920750" - dependencies: - inherits "2.0.3" - setprototypeof "1.0.2" - statuses ">= 1.3.1 < 2" - -http-errors@~1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.1.tgz#5f8b8ed98aca545656bf572997387f904a722257" +http-errors@~1.6.1, http-errors@~1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" dependencies: - depd "1.1.0" + depd "1.1.1" inherits "2.0.3" setprototypeof "1.0.3" statuses ">= 1.3.1 < 2" @@ -2601,21 +2813,31 @@ https-proxy-agent@^1.0.0: debug "2" extend "3" -iconv-lite@0.4, iconv-lite@0.4.15: +iconv-lite@0.4, iconv-lite@^0.4.17: + version "0.4.18" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" + +iconv-lite@0.4.15: version "0.4.15" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.15.tgz#fe265a218ac6a57cfe854927e9d04c19825eddeb" -icss-replace-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5" +icss-replace-symbols@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz#06ea6f83679a7749e386cfe1fe812ae5db223ded" + +icss-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-2.1.0.tgz#83f0a0ec378bf3246178b6c2ad9136f135b1c962" + dependencies: + postcss "^6.0.1" ieee754@^1.1.4: version "1.1.8" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4" image-size@~0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.1.tgz#28eea8548a4b1443480ddddc1e083ae54652439f" + version "0.5.5" + resolved "https://registry.yarnpkg.com/image-size/-/image-size-0.5.5.tgz#09dfd4ab9d20e29eb1c3e80b8990378df9e3cb9c" img-stats@^0.5.2: version "0.5.2" @@ -2652,7 +2874,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@~2.0.0, inherits@~2.0.1: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -2665,28 +2887,35 @@ ini@^1.3.4, ini@~1.3.0: resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" inquirer@^3.0.0: - version "3.0.6" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.0.6.tgz#e04aaa9d05b7a3cb9b0f407d04375f0447190347" + version "3.2.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.2.3.tgz#1c7b1731cf77b934ec47d22c9ac5aa8fe7fbe095" dependencies: - ansi-escapes "^1.1.0" - chalk "^1.0.0" + ansi-escapes "^2.0.0" + chalk "^2.0.0" cli-cursor "^2.1.0" cli-width "^2.0.0" - external-editor "^2.0.1" + external-editor "^2.0.4" figures "^2.0.0" lodash "^4.3.0" mute-stream "0.0.7" run-async "^2.2.0" - rx "^4.1.0" - string-width "^2.0.0" - strip-ansi "^3.0.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.1.0" + strip-ansi "^4.0.0" through "^2.3.6" +internal-ip@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-1.2.0.tgz#ae9fbf93b984878785d50a8de1b356956058cf5c" + dependencies: + meow "^3.3.0" + interpret@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.2.tgz#f4f623f0bb7122f15f5717c8e254b8161b5c5b2d" + version "1.0.3" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" -invariant@^2.2.0: +invariant@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" dependencies: @@ -2696,9 +2925,13 @@ invert-kv@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" -ipaddr.js@1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.3.0.tgz#1e03a52fdad83a8bbb2b25cbf4998b4cffcd3dec" +ip@^1.1.0: + version "1.1.5" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" + +ipaddr.js@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.4.0.tgz#296aca878a821816e5b85d0a285a99bcff4582f0" is-absolute-url@^2.0.0: version "2.1.0" @@ -2714,7 +2947,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2: +is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" @@ -2729,8 +2962,8 @@ is-directory@^0.3.1: resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" is-dotfile@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.2.tgz#2c132383f39199f8edc268ca01b9b007d205cc4d" + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" is-equal-shallow@^0.1.3: version "0.1.3" @@ -2782,12 +3015,18 @@ is-number@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-number/-/is-number-0.1.1.tgz#69a7af116963d47206ec9bd9b48a14216f1e3806" -is-number@^2.0.2, is-number@^2.1.0: +is-number@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" dependencies: kind-of "^3.0.2" +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -2809,10 +3048,10 @@ is-plain-obj@^1.0.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" is-plain-object@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.1.tgz#4d7ca539bc9db9b737b8acb612f2318ef92f294f" + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" dependencies: - isobject "^1.0.0" + isobject "^3.0.1" is-posix-bracket@^0.1.0: version "0.1.1" @@ -2826,6 +3065,10 @@ is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" +is-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + is-svg@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-svg/-/is-svg-2.1.0.tgz#cf61090da0d9efbcab8722deba6f032208dbb0e9" @@ -2860,32 +3103,32 @@ isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" -isobject@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-1.0.2.tgz#f0f9b8ce92dd540fa0740882e3835a2e022ec78a" - isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" dependencies: isarray "1.0.0" +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" istanbul-api@^1.1.8: - version "1.1.9" - resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.9.tgz#2827920d380d4286d857d57a2968a841db8a7ec8" + version "1.1.14" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.14.tgz#25bc5701f7c680c0ffff913de46e3619a3a6e680" dependencies: async "^2.1.4" fileset "^2.0.2" istanbul-lib-coverage "^1.1.1" istanbul-lib-hook "^1.0.7" - istanbul-lib-instrument "^1.7.2" + istanbul-lib-instrument "^1.8.0" istanbul-lib-report "^1.1.1" istanbul-lib-source-maps "^1.2.1" - istanbul-reports "^1.1.1" + istanbul-reports "^1.1.2" js-yaml "^3.7.0" mkdirp "^0.5.1" once "^1.4.0" @@ -2909,15 +3152,15 @@ istanbul-lib-hook@^1.0.7: dependencies: append-transform "^0.4.0" -istanbul-lib-instrument@^1.1.3, istanbul-lib-instrument@^1.7.2: - version "1.7.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz#6014b03d3470fb77638d5802508c255c06312e56" +istanbul-lib-instrument@^1.1.3, istanbul-lib-instrument@^1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.8.0.tgz#66f6c9421cc9ec4704f76f2db084ba9078a2b532" dependencies: babel-generator "^6.18.0" babel-template "^6.16.0" babel-traverse "^6.18.0" babel-types "^6.18.0" - babylon "^6.13.0" + babylon "^6.18.0" istanbul-lib-coverage "^1.1.1" semver "^5.3.0" @@ -2940,58 +3183,52 @@ istanbul-lib-source-maps@^1.2.1: rimraf "^2.6.1" source-map "^0.5.3" -istanbul-reports@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e" +istanbul-reports@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.2.tgz#0fb2e3f6aa9922bd3ce45d05d8ab4d5e8e07bd4f" dependencies: handlebars "^4.0.3" -jasmine-core@~2.5.2: - version "2.5.2" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.5.2.tgz#6f61bd79061e27f43e6f9355e44b3c6cab6ff297" - jasmine-core@~2.6.2: - version "2.6.3" - resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815" + version "2.6.4" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.4.tgz#dec926cd0a9fa287fb6db5c755fa487e74cecac5" + +jasmine-core@~2.8.0: + version "2.8.0" + resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.8.0.tgz#bcc979ae1f9fd05701e45e52e65d3a5d63f1a24e" jasmine-spec-reporter@~4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-4.1.0.tgz#b8aa7584df58e4dab61acdbefb76d096aa38663f" + version "4.1.1" + resolved "https://registry.yarnpkg.com/jasmine-spec-reporter/-/jasmine-spec-reporter-4.1.1.tgz#5a6d58ab5d61bea7309fbc279239511756b1b588" dependencies: colors "1.1.2" jasmine@^2.5.3: - version "2.5.3" - resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.5.3.tgz#5441f254e1fc2269deb1dfd93e0e57d565ff4d22" + version "2.8.0" + resolved "https://registry.yarnpkg.com/jasmine/-/jasmine-2.8.0.tgz#6b089c0a11576b1f16df11b80146d91d4e8b8a3e" dependencies: exit "^0.1.2" glob "^7.0.6" - jasmine-core "~2.5.2" + jasmine-core "~2.8.0" jasminewd2@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/jasminewd2/-/jasminewd2-2.1.0.tgz#da595275d1ae631de736ac0a7c7d85c9f73ef652" -jodid25519@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" - dependencies: - jsbn "~0.1.0" - -js-base64@^2.1.5, js-base64@^2.1.9: +js-base64@^2.1.5, js-base64@^2.1.8, js-base64@^2.1.9: version "2.1.9" resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.1.9.tgz#f0e80ae039a4bd654b5f281fc93f04a914a7fcce" -js-tokens@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" +js-tokens@^3.0.0, js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" js-yaml@^3.4.3, js-yaml@^3.7.0: - version "3.8.2" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.2.tgz#02d3e2c0f6beab20248d412c352203827d786721" + version "3.9.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.9.1.tgz#08775cebdfdd359209f0d2acd383c8f86a6904a0" dependencies: argparse "^1.0.7" - esprima "^3.1.1" + esprima "^4.0.0" js-yaml@~3.7.0: version "3.7.0" @@ -3004,6 +3241,10 @@ jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" +jschardet@^1.4.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.5.1.tgz#c519f629f86b3a5bedba58a88d311309eec097f9" + jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -3013,8 +3254,12 @@ jsesc@~0.5.0: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" json-loader@^0.5.4: - version "0.5.4" - resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.4.tgz#8baa1365a632f58a3c46d20175fc6002c96e37de" + version "0.5.7" + resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" + +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" json-schema@0.2.3: version "0.2.3" @@ -3055,13 +3300,13 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" jsprim@^1.2.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + version "1.4.1" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" dependencies: assert-plus "1.0.0" - extsprintf "1.0.2" + extsprintf "1.3.0" json-schema "0.2.3" - verror "1.3.6" + verror "1.10.0" karma-chrome-launcher@~2.1.1: version "2.1.1" @@ -3100,8 +3345,8 @@ karma-source-map-support@^1.2.0: source-map-support "^0.4.1" karma@~1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.0.tgz#6f7a1a406446fa2e187ec95398698f4cee476269" + version "1.7.1" + resolved "https://registry.yarnpkg.com/karma/-/karma-1.7.1.tgz#85cc08e9e0a22d7ce9cca37c4a1be824f6a2b1ae" dependencies: bluebird "^3.3.0" body-parser "^1.16.1" @@ -3137,11 +3382,17 @@ kind-of@^2.0.1: dependencies: is-buffer "^1.0.2" -kind-of@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47" +kind-of@^3.0.2, kind-of@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" dependencies: - is-buffer "^1.0.2" + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" lazy-cache@^0.2.3: version "0.2.7" @@ -3157,9 +3408,9 @@ lcid@^1.0.0: dependencies: invert-kv "^1.0.0" -less-loader@^4.0.2: - version "4.0.3" - resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-4.0.3.tgz#d1e6462ca2f090c11248455e14b8dda4616d0521" +less-loader@^4.0.5: + version "4.0.5" + resolved "https://registry.yarnpkg.com/less-loader/-/less-loader-4.0.5.tgz#ae155a7406cac6acd293d785587fcff0f478c4dd" dependencies: clone "^2.1.1" loader-utils "^1.1.0" @@ -3178,9 +3429,9 @@ less@^2.7.2: request "^2.72.0" source-map "^0.5.3" -license-webpack-plugin@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-0.4.3.tgz#f9d88d4ebc04407a0061e8ccac26571f88e51a16" +license-webpack-plugin@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/license-webpack-plugin/-/license-webpack-plugin-0.5.1.tgz#68d8af103486a9c4ebceddb7ed5d3bd61f383be4" dependencies: object-assign "^4.1.0" @@ -3194,6 +3445,15 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + loader-runner@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" @@ -3215,7 +3475,14 @@ loader-utils@^1.0.1, loader-utils@^1.0.2, loader-utils@^1.1.0: emojis-list "^2.0.0" json5 "^0.5.0" -lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -3227,7 +3494,7 @@ lodash.clonedeep@^4.3.2, lodash.clonedeep@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" -lodash.memoize@^4.1.0: +lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -3239,7 +3506,7 @@ lodash.tail@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.tail/-/lodash.tail-4.1.1.tgz#d2333a36d9e7717c8ad2f7cacafec7c32b444664" -lodash.uniq@^4.3.0: +lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -3247,14 +3514,10 @@ lodash@^3.8.0: version "3.10.1" resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6" -lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@^4.5.0: +lodash@^4.0.0, lodash@^4.11.1, lodash@^4.14.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.3.0, lodash@^4.5.0, lodash@~4.17.4: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" -lodash@~4.16.4: - version "4.16.6" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.16.6.tgz#d22c9ac660288f3843e16ba7d2b5d06cca27d777" - log4js@^0.6.31: version "0.6.38" resolved "https://registry.yarnpkg.com/log4js/-/log4js-0.6.38.tgz#2c494116695d6fb25480943d3fc872e662a522fd" @@ -3288,11 +3551,11 @@ lru-cache@2.2.x: resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-2.2.4.tgz#6c658619becf14031d0d0b594b16042ce4dc063d" lru-cache@^4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e" + version "4.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.1.tgz#622e32e82488b49279114a4f9ecf45e7cd6bba55" dependencies: - pseudomap "^1.0.1" - yallist "^2.0.0" + pseudomap "^1.0.2" + yallist "^2.1.2" macaddress@^0.2.8: version "0.2.8" @@ -3305,8 +3568,8 @@ magic-string@^0.22.3: vlq "^0.2.1" make-error@^1.1.1: - version "1.2.3" - resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.2.3.tgz#6c4402df732e0977ac6faf754a5074b3d2b1d19d" + version "1.3.0" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.0.tgz#52ad3a339ccf10ce62b4040b708fe707244b8b96" map-obj@^1.0.0, map-obj@^1.0.1: version "1.0.1" @@ -3323,13 +3586,26 @@ material-design-icons-iconfont@3.0.3: resolved "https://registry.yarnpkg.com/material-design-icons-iconfont/-/material-design-icons-iconfont-3.0.3.tgz#154a1084047d4e27237fa7f5a37e1075ceea6df2" math-expression-evaluator@^1.2.14: - version "1.2.16" - resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.16.tgz#b357fa1ca9faefb8e48d10c14ef2bcb2d9f0a7c9" + version "1.2.17" + resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" + +md5.js@^1.3.4: + version "1.3.4" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" +mem@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mem/-/mem-1.1.0.tgz#5edd52b485ca1d900fe64895505399a0dfa45f76" + dependencies: + mimic-fn "^1.0.0" + memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" @@ -3337,7 +3613,7 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.7.0: +meow@^3.3.0, meow@^3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" dependencies: @@ -3385,20 +3661,28 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -"mime-db@>= 1.27.0 < 2", mime-db@~1.27.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" +"mime-db@>= 1.29.0 < 2", mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" mime-types@^2.1.12, mime-types@~2.1.11, mime-types@~2.1.15, mime-types@~2.1.7: - version "2.1.15" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" dependencies: - mime-db "~1.27.0" + mime-db "~1.30.0" -mime@1.3.4, mime@1.3.x, mime@^1.2.11, mime@^1.3.4: +mime@1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.4.tgz#115f9e3b6b3daf2959983cb38f149a2d40eb5d53" +mime@1.3.x: + version "1.3.6" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.3.6.tgz#591d84d3653a6b0b4a3b9df8de5aa8108e72e5e0" + +mime@^1.2.11, mime@^1.3.4: + version "1.4.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.0.tgz#69e9e0db51d44f2a3b56e48b7817d7d137f1a343" + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -3458,13 +3742,24 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" +multicast-dns-service-types@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz#899f11d9686e5e05cb91b35d5f0e63b773cfc901" + +multicast-dns@^6.0.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-6.1.1.tgz#6e7de86a570872ab17058adea7160bbeca814dde" + dependencies: + dns-packet "^1.0.1" + thunky "^0.1.0" + mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" nan@^2.3.0, nan@^2.3.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.5.1.tgz#d5b01691253326a97a2bbee9e61c55d8d60351e2" + version "2.7.0" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.7.0.tgz#d95bf721ec877e08db276ed3fc6eb78f9083ad46" ncname@1.0.x: version "1.0.0" @@ -3482,15 +3777,27 @@ ng2-charts@^1.6.0: dependencies: chart.js "^2.6.0" +ng2-cookies@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/ng2-cookies/-/ng2-cookies-1.0.12.tgz#3f3e613e0137b0649b705c678074b4bd08149ccc" + +ngx-loading@^1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/ngx-loading/-/ngx-loading-1.0.7.tgz#592a2114466c5a2a20a0b7c2e6305c6ad505cf9f" + no-case@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/no-case/-/no-case-2.3.1.tgz#7aeba1c73a52184265554b7dc03baf720df80081" dependencies: lower-case "^1.1.1" +node-forge@0.6.33: + version "0.6.33" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.6.33.tgz#463811879f573d45155ad6a9f43dc296e8e85ebc" + node-gyp@^3.3.1: - version "3.6.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.0.tgz#7474f63a3a0501161dda0b6341f022f14c423fa6" + version "3.6.2" + resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.6.2.tgz#9bfbe54562286284838e750eac05295853fa1c60" dependencies: fstream "^1.0.0" glob "^7.0.3" @@ -3538,9 +3845,9 @@ node-modules-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/node-modules-path/-/node-modules-path-1.0.1.tgz#40096b08ce7ad0ea14680863af449c7c75a5d1c8" -node-pre-gyp@^0.6.29: - version "0.6.34" - resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.34.tgz#94ad1c798a11d7fc67381b50d47f8cc18d9799f7" +node-pre-gyp@^0.6.36: + version "0.6.36" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" dependencies: mkdirp "^0.5.1" nopt "^4.0.1" @@ -3553,8 +3860,8 @@ node-pre-gyp@^0.6.29: tar-pack "^3.4.0" node-sass@^4.3.0: - version "4.5.2" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.2.tgz#4012fa2bd129b1d6365117e88d9da0500d99da64" + version "4.5.3" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.5.3.tgz#d09c9d1179641239d1b97ffc6231fdcec53e1568" dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -3589,15 +3896,15 @@ nopt@^4.0.1: osenv "^0.1.4" normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: - version "2.3.6" - resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.6.tgz#498fa420c96401f787402ba21e600def9f981fff" + version "2.4.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" dependencies: hosted-git-info "^2.1.4" is-builtin-module "^1.0.0" semver "2 || 3 || 4 || 5" validate-npm-package-license "^3.0.1" -normalize-path@^2.0.1: +normalize-path@^2.0.0, normalize-path@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" dependencies: @@ -3616,13 +3923,19 @@ normalize-url@^1.4.0: query-string "^4.1.0" sort-keys "^1.0.0" +npm-run-path@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + dependencies: + path-key "^2.0.0" + "npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.0.2.tgz#d03950e0e78ce1527ba26d2a7592e9348ac3e75f" + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" - gauge "~2.7.1" + gauge "~2.7.3" set-blocking "~2.0.0" nth-check@~1.0.1: @@ -3666,7 +3979,7 @@ object.omit@^2.0.0: for-own "^0.1.4" is-extendable "^0.1.1" -obuf@^1.0.0, obuf@^1.1.0: +obuf@^1.0.0, obuf@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.1.tgz#104124b6c602c6796881a042541d36db43a5264e" @@ -3742,6 +4055,14 @@ os-locale@^1.4.0: dependencies: lcid "^1.0.0" +os-locale@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-2.1.0.tgz#42bc2900a6b5b8bd17376c8e882b65afccf24bf2" + dependencies: + execa "^0.7.0" + lcid "^1.0.0" + mem "^1.1.0" + os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -3753,6 +4074,24 @@ osenv@0, osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" +p-finally@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + pako@~0.2.0: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" @@ -3826,6 +4165,10 @@ path-exists@^2.0.0: dependencies: pinkie-promise "^2.0.0" +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -3834,6 +4177,10 @@ path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" +path-key@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + path-parse@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" @@ -3850,11 +4197,21 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + pbkdf2@^3.0.3: - version "3.0.9" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.9.tgz#f2c4b25a600058b3c3773c086c37dbbee1ffe693" + version "3.0.13" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.13.tgz#c37d295531e786b1da3e3eadc840426accb0ae25" dependencies: - create-hmac "^1.1.2" + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" performance-now@^0.2.0: version "0.2.0" @@ -3864,6 +4221,10 @@ pify@^2.0.0, pify@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" +pify@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + pinkie-promise@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" @@ -4037,31 +4398,31 @@ postcss-minify-selectors@^2.0.4: postcss-selector-parser "^2.0.0" postcss-modules-extract-imports@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.0.1.tgz#8fb3fef9a6dd0420d3f6d4353cf1ff73f2b2a341" + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz#66140ecece38ef06bf0d3e355d69bf59d141ea85" dependencies: - postcss "^5.0.4" + postcss "^6.0.1" postcss-modules-local-by-default@^1.0.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.1.1.tgz#29a10673fa37d19251265ca2ba3150d9040eb4ce" + version "1.2.0" + resolved "https://registry.yarnpkg.com/postcss-modules-local-by-default/-/postcss-modules-local-by-default-1.2.0.tgz#f7d80c398c5a393fa7964466bd19500a7d61c069" dependencies: - css-selector-tokenizer "^0.6.0" - postcss "^5.0.4" + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" postcss-modules-scope@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.0.2.tgz#ff977395e5e06202d7362290b88b1e8cd049de29" + version "1.1.0" + resolved "https://registry.yarnpkg.com/postcss-modules-scope/-/postcss-modules-scope-1.1.0.tgz#d6ea64994c79f97b62a72b426fbe6056a194bb90" dependencies: - css-selector-tokenizer "^0.6.0" - postcss "^5.0.4" + css-selector-tokenizer "^0.7.0" + postcss "^6.0.1" postcss-modules-values@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.2.2.tgz#f0e7d476fe1ed88c5e4c7f97533a3e772ad94ca1" + version "1.3.0" + resolved "https://registry.yarnpkg.com/postcss-modules-values/-/postcss-modules-values-1.3.0.tgz#ecffa9d7e192518389f42ad0e83f72aec456ea20" dependencies: - icss-replace-symbols "^1.0.2" - postcss "^5.0.14" + icss-replace-symbols "^1.1.0" + postcss "^6.0.1" postcss-normalize-charset@^1.1.0: version "1.1.1" @@ -4156,14 +4517,22 @@ postcss-zindex@^2.0.1: uniqs "^2.0.0" postcss@^5.0.0, postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0.14, postcss@^5.0.16, postcss@^5.0.2, postcss@^5.0.4, postcss@^5.0.5, postcss@^5.0.6, postcss@^5.0.8, postcss@^5.2.15, postcss@^5.2.16: - version "5.2.16" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.16.tgz#732b3100000f9ff8379a48a53839ed097376ad57" + version "5.2.17" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-5.2.17.tgz#cf4f597b864d65c8a492b2eabe9d706c879c388b" dependencies: chalk "^1.1.3" js-base64 "^2.1.9" source-map "^0.5.6" supports-color "^3.2.3" +postcss@^6.0.1: + version "6.0.11" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.11.tgz#f48db210b1d37a7f7ab6499b7a54982997ab6f72" + dependencies: + chalk "^2.1.0" + source-map "^0.5.7" + supports-color "^4.4.0" + prepend-http@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" @@ -4173,8 +4542,8 @@ preserve@^0.2.0: resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" pretty-error@^2.0.2: - version "2.0.3" - resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.0.3.tgz#bed3d816a008e76da617cde8216f4b778849b5d9" + version "2.1.1" + resolved "https://registry.yarnpkg.com/pretty-error/-/pretty-error-2.1.1.tgz#5f4f87c8f91e5ae3f3ba87ab4cf5e03b1a17f1a3" dependencies: renderkid "^2.0.1" utila "~0.4" @@ -4184,12 +4553,12 @@ process-nextick-args@~1.0.6: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" process@^0.11.0: - version "0.11.9" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.9.tgz#7bd5ad21aa6253e7da8682264f1e11d11c0318c1" + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" promise@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" dependencies: asap "~2.0.3" @@ -4213,18 +4582,18 @@ protractor@~5.1.2: webdriver-js-extender "^1.0.0" webdriver-manager "^12.0.6" -proxy-addr@~1.1.3: - version "1.1.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.4.tgz#27e545f6960a44a627d9b44467e35c1b6b4ce2f3" +proxy-addr@~1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-1.1.5.tgz#71c0ee3b102de3f202f3b64f608d173fcba1a918" dependencies: forwarded "~0.1.0" - ipaddr.js "1.3.0" + ipaddr.js "1.4.0" prr@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" -pseudomap@^1.0.1: +pseudomap@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" @@ -4258,9 +4627,13 @@ qs@6.4.0, qs@~6.4.0: version "6.4.0" resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" +qs@6.5.0: + version "6.5.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.0.tgz#8d04954d364def3efc55b5a0793e1e2c8b1e6e49" + query-string@^4.1.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.2.tgz#ec0fd765f58a50031a3968c2431386f8947a5cdd" + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" dependencies: object-assign "^4.1.0" strict-uri-encode "^1.0.0" @@ -4277,6 +4650,10 @@ querystringify@0.0.x: version "0.0.4" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-0.0.4.tgz#0cf7f84f9463ff0ae51c4c4b142d95be37724d9c" +querystringify@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-1.0.0.tgz#6286242112c5b712fa654e526652bf6a13ff05cb" + queueing-subject@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/queueing-subject/-/queueing-subject-0.1.1.tgz#43f857b2b263276ff418cdd865b7d78d5d641d9d" @@ -4284,15 +4661,17 @@ queueing-subject@^0.1.1: rxjs "^5.0.1" randomatic@^1.1.3: - version "1.1.6" - resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.6.tgz#110dcabff397e9dcff7c0789ccc0a49adf1ec5bb" + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" dependencies: - is-number "^2.0.2" - kind-of "^3.0.2" + is-number "^3.0.0" + kind-of "^4.0.0" randombytes@^2.0.0, randombytes@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.3.tgz#674c99760901c3c4112771a31e521dc349cc09ec" + version "2.0.5" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.5.tgz#dc009a246b8d09a177b4b7a0ae77bc570f4b1b79" + dependencies: + safe-buffer "^5.1.0" range-parser@^1.0.3, range-parser@^1.2.0, range-parser@~1.2.0: version "1.2.0" @@ -4311,8 +4690,8 @@ raw-loader@^0.5.1, raw-loader@~0.5.1: resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-0.5.1.tgz#0c3d0beaed8a01c966d9787bf778281252a979aa" rc@^1.1.7: - version "1.2.0" - resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.0.tgz#c7de973b7b46297c041366b2fd3d2363b1697c66" + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" dependencies: deep-extend "~0.4.0" ini "~1.3.0" @@ -4326,6 +4705,13 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" @@ -4334,6 +4720,14 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + readable-stream@1.0, readable-stream@~1.0.2: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -4343,16 +4737,16 @@ readable-stream@1.0, readable-stream@~1.0.2: isarray "0.0.1" string_decoder "~0.10.x" -"readable-stream@^2.0.0 || ^1.1.13", readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.0, readable-stream@^2.1.4: - version "2.2.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.6.tgz#8b43aed76e71483938d12a8d46c6cf1a00b1f816" +readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.2.6, readable-stream@^2.2.9: + version "2.3.3" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" dependencies: - buffer-shims "^1.0.0" core-util-is "~1.0.0" - inherits "~2.0.1" + inherits "~2.0.3" isarray "~1.0.0" process-nextick-args "~1.0.6" - string_decoder "~0.10.x" + safe-buffer "~5.1.1" + string_decoder "~1.0.3" util-deprecate "~1.0.1" readdirp@^2.0.0: @@ -4393,16 +4787,15 @@ regenerate@^1.2.1: version "1.3.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" -regenerator-runtime@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.3.tgz#8c4367a904b51ea62a908ac310bf99ff90a82a3e" +regenerator-runtime@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.0.tgz#7e54fe5b5ccd5d6624ea6255c3473be090b802e1" regex-cache@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" dependencies: is-equal-shallow "^0.1.3" - is-primitive "^2.0.0" regexpu-core@^1.0.0: version "1.0.0" @@ -4427,8 +4820,8 @@ relateurl@0.2.x: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" remove-trailing-separator@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz#615ebb96af559552d4bf4057c8436d486ab63cc4" + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" renderkid@^2.0.1: version "2.0.1" @@ -4502,8 +4895,8 @@ requires-port@1.0.x, requires-port@1.x.x: resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.2.tgz#1f0442c9e0cbb8136e87b9305f932f46c7f28235" + version "1.4.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.4.0.tgz#a75be01c53da25d934a98ebd0e4c4a7312f92a86" dependencies: path-parse "^1.0.5" @@ -4530,17 +4923,20 @@ rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" -ripemd160@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-1.0.1.tgz#93a4bbd4942bc574b69a8fa57c71de10ecca7d6e" +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.1.tgz#0f4584295c53a3628af7e6d79aca21ce57d1c6e7" + dependencies: + hash-base "^2.0.0" + inherits "^2.0.1" roboto-fontface@0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/roboto-fontface/-/roboto-fontface-0.8.0.tgz#031a83c8f79932801a57d83bf743f37250163499" rsvp@^3.0.17: - version "3.5.0" - resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.5.0.tgz#a62c573a4ae4e1dfd0697ebc6242e79c681eaa34" + version "3.6.2" + resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-3.6.2.tgz#2e96491599a96cde1b515d5674a8f7a91452926a" rsvp@~3.2.1: version "3.2.1" @@ -4556,9 +4952,15 @@ rw@1: version "1.3.3" resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" -rx@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/rx/-/rx-4.1.0.tgz#a5f13ff79ef3b740fe30aa803fb09f98805d4782" +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" rxjs-websockets@^1.1.0: version "1.1.0" @@ -4566,33 +4968,34 @@ rxjs-websockets@^1.1.0: dependencies: rxjs "^5.0.1" -rxjs@^5.0.1, rxjs@^5.4.1: - version "5.4.2" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.2.tgz#2a3236fcbf03df57bae06fd6972fd99e5c08fcf7" +rxjs@^5.0.1, rxjs@^5.4.2: + version "5.4.3" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.4.3.tgz#0758cddee6033d68e0fd53676f0f3596ce3d483f" dependencies: symbol-observable "^1.0.1" -safe-buffer@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" +safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" sass-graph@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.1.2.tgz#965104be23e8103cb7e5f710df65935b317da57b" + version "2.2.4" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" dependencies: glob "^7.0.0" lodash "^4.0.0" - yargs "^4.7.1" + scss-tokenizer "^0.2.3" + yargs "^7.0.0" sass-loader@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.3.tgz#33983b1f90d27ddab0e57d0dac403dce9bc7ecfd" + version "6.0.6" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-6.0.6.tgz#e9d5e6c1f155faa32a4b26d7a9b7107c225e40f9" dependencies: async "^2.1.5" - clone-deep "^0.2.4" + clone-deep "^0.3.0" loader-utils "^1.0.1" lodash.tail "^4.1.1" - pify "^2.3.0" + pify "^3.0.0" saucelabs@~1.3.0: version "1.3.0" @@ -4609,8 +5012,14 @@ sax@0.6.x: resolved "https://registry.yarnpkg.com/sax/-/sax-0.6.1.tgz#563b19c7c1de892e09bfc4f2fc30e3c27f0952b9" sax@>=0.6.0, sax@~1.2.1: - version "1.2.2" - resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + +schema-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + dependencies: + ajv "^5.0.0" script-loader@^0.7.0: version "0.7.0" @@ -4618,6 +5027,13 @@ script-loader@^0.7.0: dependencies: raw-loader "~0.5.1" +scss-tokenizer@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" + dependencies: + js-base64 "^2.1.8" + source-map "^0.4.2" + select-hose@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" @@ -4641,15 +5057,21 @@ selenium-webdriver@^2.53.2: ws "^1.0.1" xml2js "0.4.4" +selfsigned@^1.9.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-1.10.1.tgz#bf8cb7b83256c4551e31347c6311778db99eec52" + dependencies: + node-forge "0.6.33" + semver-dsl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/semver-dsl/-/semver-dsl-1.0.1.tgz#d3678de5555e8a61f629eed025366ae5f27340a0" dependencies: semver "^5.3.0" -"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0, semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" +"semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.3.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" semver@~4.3.3: version "4.3.6" @@ -4659,44 +5081,48 @@ semver@~5.0.1: version "5.0.3" resolved "https://registry.yarnpkg.com/semver/-/semver-5.0.3.tgz#77466de589cd5d3c95f138aa78bc569a3cb5d27a" -send@0.15.1: - version "0.15.1" - resolved "https://registry.yarnpkg.com/send/-/send-0.15.1.tgz#8a02354c26e6f5cca700065f5f0cdeba90ec7b5f" +semver@~5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +send@0.15.4: + version "0.15.4" + resolved "https://registry.yarnpkg.com/send/-/send-0.15.4.tgz#985faa3e284b0273c793364a35c6737bd93905b9" dependencies: - debug "2.6.1" - depd "~1.1.0" + debug "2.6.8" + depd "~1.1.1" destroy "~1.0.4" encodeurl "~1.0.1" escape-html "~1.0.3" etag "~1.8.0" fresh "0.5.0" - http-errors "~1.6.1" + http-errors "~1.6.2" mime "1.3.4" - ms "0.7.2" + ms "2.0.0" on-finished "~2.3.0" range-parser "~1.2.0" statuses "~1.3.1" serve-index@^1.7.2: - version "1.8.0" - resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.8.0.tgz#7c5d96c13fb131101f93c1c5774f8516a1e78d3b" + version "1.9.0" + resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.0.tgz#d2b280fc560d616ee81b48bf0fa82abed2485ce7" dependencies: accepts "~1.3.3" - batch "0.5.3" - debug "~2.2.0" + batch "0.6.1" + debug "2.6.8" escape-html "~1.0.3" - http-errors "~1.5.0" - mime-types "~2.1.11" + http-errors "~1.6.1" + mime-types "~2.1.15" parseurl "~1.3.1" -serve-static@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.1.tgz#7443a965e3ced647aceb5639fa06bf4d1bbe0039" +serve-static@1.12.4: + version "1.12.4" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.12.4.tgz#9b6aa98eeb7253c4eedc4c1f6fdbca609901a961" dependencies: encodeurl "~1.0.1" escape-html "~1.0.3" parseurl "~1.3.1" - send "0.15.1" + send "0.15.4" set-blocking@^2.0.0, set-blocking@~2.0.0: version "2.0.0" @@ -4710,15 +5136,11 @@ setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" -setprototypeof@1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.2.tgz#81a552141ec104b88e89ce383103ad5c66564d08" - setprototypeof@1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" -sha.js@^2.3.6: +sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.8" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.8.tgz#37068c2c476b6baf402d14a49c67f597921f634f" dependencies: @@ -4733,13 +5155,23 @@ shallow-clone@^0.1.2: lazy-cache "^0.2.3" mixin-object "^2.0.1" +shebang-command@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + dependencies: + shebang-regex "^1.0.0" + +shebang-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" silent-error@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/silent-error/-/silent-error-1.0.1.tgz#71b7d503d1c6f94882b51b56be879b113cb4822c" + version "1.1.0" + resolved "https://registry.yarnpkg.com/silent-error/-/silent-error-1.1.0.tgz#2209706f1c850a9f1d10d0d840918b46f26e1bc9" dependencies: debug "^2.2.0" @@ -4817,13 +5249,9 @@ sort-keys@^1.0.0: dependencies: is-plain-obj "^1.0.0" -source-list-map@^0.1.7, source-list-map@~0.1.7: - version "0.1.8" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-0.1.8.tgz#c550b2ab5427f6b3f21f5afead88c4f5587b2106" - -source-list-map@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-1.1.2.tgz#9889019d1024cce55cdc069498337ef6186a11a1" +source-list-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" source-map-loader@^0.2.0: version "0.2.1" @@ -4834,8 +5262,8 @@ source-map-loader@^0.2.0: source-map "~0.1.33" source-map-support@^0.4.0, source-map-support@^0.4.1, source-map-support@^0.4.2, source-map-support@~0.4.0: - version "0.4.14" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.14.tgz#9d4463772598b86271b4f523f6c1f4e02a7d6aef" + version "0.4.17" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.17.tgz#6f2150553e6375375d0ccb3180502b78c18ba430" dependencies: source-map "^0.5.6" @@ -4845,11 +5273,11 @@ source-map@0.1.x, source-map@~0.1.33, source-map@~0.1.7: dependencies: amdefine ">=0.0.4" -source-map@0.5.x, source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1, source-map@~0.5.3: - version "0.5.6" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" +source-map@0.5.x, source-map@^0.5.3, source-map@^0.5.6, source-map@^0.5.7, source-map@~0.5.1, source-map@~0.5.3: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" -source-map@^0.4.4: +source-map@^0.4.2, source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -4869,33 +5297,40 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" -spdy-transport@^2.0.15: - version "2.0.18" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.18.tgz#43fc9c56be2cccc12bb3e2754aa971154e836ea6" +spdy-transport@^2.0.18: + version "2.0.20" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.0.20.tgz#735e72054c486b2354fe89e702256004a39ace4d" dependencies: - debug "^2.2.0" + debug "^2.6.8" + detect-node "^2.0.3" hpack.js "^2.1.6" - obuf "^1.1.0" - readable-stream "^2.0.1" - wbuf "^1.4.0" + obuf "^1.1.1" + readable-stream "^2.2.9" + safe-buffer "^5.0.1" + wbuf "^1.7.2" spdy@^3.4.1: - version "3.4.4" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.4.tgz#e0406407ca90ff01b553eb013505442649f5a819" + version "3.4.7" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" dependencies: - debug "^2.2.0" - handle-thing "^1.2.4" - http-deceiver "^1.2.4" + debug "^2.6.8" + handle-thing "^1.2.5" + http-deceiver "^1.2.7" + safe-buffer "^5.0.1" select-hose "^2.0.0" - spdy-transport "^2.0.15" + spdy-transport "^2.0.18" + +sprintf-js@^1.0.3: + version "1.1.1" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.1.tgz#36be78320afe5801f6cea3ee78b6e5aab940ea0c" -sprintf-js@^1.0.3, sprintf-js@~1.0.2: +sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.11.0.tgz#2d8d5ebb4a6fab28ffba37fa62a90f4a3ea59d77" + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" @@ -4904,7 +5339,6 @@ sshpk@^1.7.0: optionalDependencies: bcrypt-pbkdf "^1.0.0" ecc-jsbn "~0.1.1" - jodid25519 "^1.0.0" jsbn "~0.1.0" tweetnacl "~0.14.0" @@ -4926,12 +5360,12 @@ stream-browserify@^2.0.1: readable-stream "^2.0.2" stream-http@^2.3.1: - version "2.6.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.6.3.tgz#4c3ddbf9635968ea2cfd4e48d43de5def2625ac3" + version "2.7.2" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.7.2.tgz#40a050ec8dc3b53b33d9909415c02c0bf1abfbad" dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" - readable-stream "^2.1.0" + readable-stream "^2.2.6" to-arraybuffer "^1.0.0" xtend "^4.0.0" @@ -4947,17 +5381,23 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" +string-width@^2.0.0, string-width@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" dependencies: is-fullwidth-code-point "^2.0.0" - strip-ansi "^3.0.0" + strip-ansi "^4.0.0" string_decoder@^0.10.25, string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" +string_decoder@~1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" + dependencies: + safe-buffer "~5.1.0" + stringstream@~0.0.4: version "0.0.5" resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" @@ -4968,6 +5408,12 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + dependencies: + ansi-regex "^3.0.0" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -4978,6 +5424,10 @@ strip-bom@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" +strip-eof@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" + strip-indent@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" @@ -5017,15 +5467,15 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^3.1.0, supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3: +supports-color@^3.1.1, supports-color@^3.1.2, supports-color@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" dependencies: has-flag "^1.0.0" -supports-color@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.2.1.tgz#65a4bb2631e90e02420dba5554c375a4754bb836" +supports-color@^4.0.0, supports-color@^4.2.1, supports-color@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-4.4.0.tgz#883f7ddabc165142b2a61427f3352ded195d1a3e" dependencies: has-flag "^2.0.0" @@ -5045,9 +5495,9 @@ symbol-observable@^1.0.1: version "1.0.4" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.4.tgz#29bf615d4aa7121bdd898b22d4b3f9bc4e2aa03d" -tapable@^0.2.5, tapable@~0.2.5: - version "0.2.6" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.6.tgz#206be8e188860b514425375e6f1ae89bfb01fd8d" +tapable@^0.2.7: + version "0.2.8" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.2.8.tgz#99372a5c999bf2df160afc0d74bed4f47948cd22" tar-pack@^3.4.0: version "3.4.0" @@ -5081,9 +5531,17 @@ through@X.X.X, through@^2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" +thunky@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/thunky/-/thunky-0.1.0.tgz#bf30146824e2b6e67b0f2d7a4ac8beb26908684e" + +time-stamp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-2.0.0.tgz#95c6a44530e15ba8d6f4a3ecb8c3a3fac46da357" + timers-browserify@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.2.tgz#ab4883cf597dcd50af211349a00fbca56ac86b86" + version "2.0.4" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.4.tgz#96ca53f4b794a5e7c0e1bd7cc88a372298fa01e6" dependencies: setimmediate "^1.0.4" @@ -5111,9 +5569,9 @@ to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" -to-fast-properties@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.2.tgz#f3f5c0c3ba7299a7ef99427e44633257ade43320" +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" toposort@^1.0.0: version "1.0.3" @@ -5133,19 +5591,19 @@ trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" -ts-node@~3.0.4: - version "3.0.6" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.0.6.tgz#55127ff790c7eebf6ba68c1e6dde94b09aaa21e0" +ts-node@~3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-3.2.2.tgz#bbd28e38af4aaa3e96076c466e1b220197c1a3ce" dependencies: arrify "^1.0.0" - chalk "^1.1.1" + chalk "^2.0.0" diff "^3.1.0" make-error "^1.1.1" minimist "^1.2.0" mkdirp "^0.5.1" source-map-support "^0.4.0" tsconfig "^6.0.0" - v8flags "^2.0.11" + v8flags "^3.0.0" yn "^2.0.0" tsconfig@^6.0.0: @@ -5183,8 +5641,10 @@ tslint@~5.3.2: tsutils "^2.0.0" tsutils@^2.0.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.4.0.tgz#ad4ce6dba0e5a3edbddf8626b7ca040782189fea" + version "2.8.2" + resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-2.8.2.tgz#2c1486ba431260845b0ac6f902afd9d708a8ea6a" + dependencies: + tslib "^1.7.1" tty-browserify@0.0.0: version "0.0.0" @@ -5200,20 +5660,27 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" -type-is@~1.6.14, type-is@~1.6.15: +type-is@~1.6.15: version "1.6.15" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.15.tgz#cab10fb4909e441c82842eafe1ad646c81804410" dependencies: media-typer "0.3.0" mime-types "~2.1.15" -"typescript@>=2.0.0 <2.4.0", typescript@~2.3.3: +"typescript@>=2.0.0 <2.5.0", typescript@^2.3.3, typescript@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.3.4.tgz#3d38321828231e434f287514959c37a82b629f42" -uglify-js@2.8.x, uglify-js@^2.6, uglify-js@^2.8.5: - version "2.8.18" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.18.tgz#925d14bae48ab62d1883b41afe6e2261662adb8e" +uglify-js@3.0.x: + version "3.0.28" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.0.28.tgz#96b8495f0272944787b5843a1679aa326640d5f7" + dependencies: + commander "~2.11.0" + source-map "~0.5.1" + +uglify-js@^2.6, uglify-js@^2.8.29: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: source-map "~0.5.1" yargs "~3.10.0" @@ -5232,6 +5699,14 @@ uglify-to-browserify@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" +uglifyjs-webpack-plugin@^0.4.6: + version "0.4.6" + resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz#b951f4abb6bd617e66f63eb891498e391763e309" + dependencies: + source-map "^0.5.6" + uglify-js "^2.8.29" + webpack-sources "^1.0.1" + uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" @@ -5267,8 +5742,8 @@ upper-case@^1.1.1: resolved "https://registry.yarnpkg.com/upper-case/-/upper-case-1.1.3.tgz#f6b4501c2ec4cdd26ba78be7222961de77621598" url-loader@^0.5.7: - version "0.5.8" - resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.8.tgz#b9183b1801e0f847718673673040bc9dc1c715c5" + version "0.5.9" + resolved "https://registry.yarnpkg.com/url-loader/-/url-loader-0.5.9.tgz#cc8fea82c7b906e7777019250869e569e995c295" dependencies: loader-utils "^1.0.2" mime "1.3.x" @@ -5281,10 +5756,10 @@ url-parse@1.0.x: requires-port "1.0.x" url-parse@^1.1.1: - version "1.1.8" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.8.tgz#7a65b3a8d57a1e86af6b4e2276e34774167c0156" + version "1.1.9" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.1.9.tgz#c67f1d775d51f0a18911dd7b3ffad27bb9e5bd19" dependencies: - querystringify "0.0.x" + querystringify "~1.0.0" requires-port "1.0.x" url@^0.11.0: @@ -5299,8 +5774,8 @@ user-home@^1.1.1: resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" useragent@^2.1.12: - version "2.1.13" - resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.1.13.tgz#bba43e8aa24d5ceb83c2937473e102e21df74c10" + version "2.2.1" + resolved "https://registry.yarnpkg.com/useragent/-/useragent-2.2.1.tgz#cf593ef4f2d175875e8bb658ea92e18a4fd06d8e" dependencies: lru-cache "2.2.x" tmp "0.0.x" @@ -5332,12 +5807,12 @@ uuid@^2.0.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" uuid@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" -v8flags@^2.0.11: - version "2.0.11" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.0.11.tgz#bca8f30f0d6d60612cc2c00641e6962d42ae6881" +v8flags@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.0.0.tgz#4be9604488e0c4123645def705b1848d16b8e01f" dependencies: user-home "^1.1.1" @@ -5348,7 +5823,7 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -vary@~1.1.0: +vary@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.1.tgz#67535ebb694c1d52257457984665323f587e8d37" @@ -5356,15 +5831,17 @@ vendors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.1.tgz#37ad73c8ee417fb3d580e785312307d274847f22" -verror@1.3.6: - version "1.3.6" - resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" +verror@1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" dependencies: - extsprintf "1.0.2" + assert-plus "^1.0.0" + core-util-is "1.0.2" + extsprintf "^1.2.0" vlq@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.1.tgz#14439d711891e682535467f8587c5630e4222a6c" + version "0.2.2" + resolved "https://registry.yarnpkg.com/vlq/-/vlq-0.2.2.tgz#e316d5257b40b86bb43cb8d5fea5d7f54d6b0ca1" vm-browserify@0.0.4: version "0.0.4" @@ -5377,21 +5854,21 @@ void-elements@^2.0.0: resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec" walk-sync@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.3.1.tgz#558a16aeac8c0db59c028b73c66f397684ece465" + version "0.3.2" + resolved "https://registry.yarnpkg.com/walk-sync/-/walk-sync-0.3.2.tgz#4827280afc42d0e035367c4a4e31eeac0d136f75" dependencies: ensure-posix-path "^1.0.0" matcher-collection "^1.0.0" -watchpack@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.3.1.tgz#7d8693907b28ce6013e7f3610aa2a1acf07dad87" +watchpack@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.4.0.tgz#4a1472bcbb952bd0a9bb4036801f954dfb39faac" dependencies: async "^2.1.2" - chokidar "^1.4.3" + chokidar "^1.7.0" graceful-fs "^4.1.2" -wbuf@^1.1.0, wbuf@^1.4.0: +wbuf@^1.1.0, wbuf@^1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.2.tgz#d697b99f1f59512df2751be42769c1580b5801fe" dependencies: @@ -5424,82 +5901,81 @@ webdriver-manager@^12.0.6: semver "^5.3.0" xml2js "^0.4.17" -webpack-dev-middleware@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.10.2.tgz#2e252ce1dfb020dbda1ccb37df26f30ab014dbd1" +webpack-dev-middleware@^1.11.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-1.12.0.tgz#d34efefb2edda7e1d3b5dbe07289513219651709" dependencies: memory-fs "~0.4.1" mime "^1.3.4" path-is-absolute "^1.0.0" range-parser "^1.0.3" + time-stamp "^2.0.0" -webpack-dev-server@~2.4.5: - version "2.4.5" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.4.5.tgz#31384ce81136be1080b4b4cde0eb9b90e54ee6cf" +webpack-dev-server@~2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-2.5.1.tgz#a02e726a87bb603db5d71abb7d6d2649bf10c769" dependencies: ansi-html "0.0.7" + bonjour "^3.5.0" chokidar "^1.6.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" + del "^3.0.0" express "^4.13.3" html-entities "^1.2.0" http-proxy-middleware "~0.17.4" + internal-ip "^1.2.0" opn "4.0.2" portfinder "^1.0.9" + selfsigned "^1.9.1" serve-index "^1.7.2" sockjs "0.3.18" sockjs-client "1.1.2" spdy "^3.4.1" strip-ansi "^3.0.0" supports-color "^3.1.1" - webpack-dev-middleware "^1.10.2" + webpack-dev-middleware "^1.11.0" yargs "^6.0.0" -webpack-merge@^2.4.0: - version "2.6.1" - resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-2.6.1.tgz#f1d801d2c5d39f83ffec9f119240b3e3be994a1c" +webpack-merge@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-4.1.0.tgz#6ad72223b3e0b837e531e4597c199f909361511e" dependencies: lodash "^4.17.4" -webpack-sources@^0.1.0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.1.5.tgz#aa1f3abf0f0d74db7111c40e500b84f966640750" - dependencies: - source-list-map "~0.1.7" - source-map "~0.5.3" - -webpack-sources@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-0.2.3.tgz#17c62bfaf13c707f9d02c479e0dcdde8380697fb" +webpack-sources@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.0.1.tgz#c7356436a4d13123be2e2426a05d1dad9cbe65cf" dependencies: - source-list-map "^1.1.1" + source-list-map "^2.0.0" source-map "~0.5.3" -webpack@~2.4.0: - version "2.4.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-2.4.1.tgz#15a91dbe34966d8a4b99c7d656efd92a2e5a6f6a" +webpack@~3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-3.4.1.tgz#4c3f4f3fb318155a4db0cb6a36ff05c5697418f4" dependencies: acorn "^5.0.0" acorn-dynamic-import "^2.0.0" - ajv "^4.7.0" - ajv-keywords "^1.1.1" + ajv "^5.1.5" + ajv-keywords "^2.0.0" async "^2.1.2" - enhanced-resolve "^3.0.0" + enhanced-resolve "^3.4.0" + escope "^3.6.0" interpret "^1.0.0" json-loader "^0.5.4" json5 "^0.5.1" loader-runner "^2.3.0" - loader-utils "^0.2.16" + loader-utils "^1.1.0" memory-fs "~0.4.1" mkdirp "~0.5.0" node-libs-browser "^2.0.0" source-map "^0.5.3" - supports-color "^3.1.0" - tapable "~0.2.5" - uglify-js "^2.8.5" - watchpack "^1.3.1" - webpack-sources "^0.2.3" - yargs "^6.0.0" + supports-color "^4.2.1" + tapable "^0.2.7" + uglifyjs-webpack-plugin "^0.4.6" + watchpack "^1.4.0" + webpack-sources "^1.0.1" + yargs "^8.0.2" websocket-driver@>=0.5.1: version "0.6.5" @@ -5523,26 +5999,26 @@ which-module@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" +which-module@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" + which@1, which@^1.2.1, which@^1.2.9: - version "1.2.14" - resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" dependencies: isexe "^2.0.0" wide-align@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.0.tgz#40edde802a71fea1f070da3e62dcda2e7add96ad" + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" dependencies: - string-width "^1.0.1" + string-width "^1.0.2" window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" -window-size@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.2.0.tgz#b4315bb4214a3d7058ebeee892e13fa24d98b075" - wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" @@ -5589,17 +6065,15 @@ xml2js@0.4.4: xmlbuilder ">=1.0.0" xml2js@^0.4.17: - version "0.4.17" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.17.tgz#17be93eaae3f3b779359c795b419705a8817e868" + version "0.4.19" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" dependencies: sax ">=0.6.0" - xmlbuilder "^4.1.0" + xmlbuilder "~9.0.1" -xmlbuilder@>=1.0.0, xmlbuilder@^4.1.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-4.2.1.tgz#aa58a3041a066f90eaa16c2f5389ff19f3f461a5" - dependencies: - lodash "^4.0.0" +xmlbuilder@>=1.0.0, xmlbuilder@~9.0.1: + version "9.0.4" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" xmldom@^0.1.19: version "0.1.27" @@ -5621,45 +6095,49 @@ y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" -yallist@^2.0.0: +yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" -yargs-parser@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-2.4.1.tgz#85568de3cf150ff49fa51825f03a8c880ddcc5c4" - dependencies: - camelcase "^3.0.0" - lodash.assign "^4.0.6" - yargs-parser@^4.2.0: version "4.2.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-4.2.1.tgz#29cceac0dc4f03c6c87b4a9f217dd18c9f74871c" dependencies: camelcase "^3.0.0" -yargs@^4.7.1: - version "4.8.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0" +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + dependencies: + camelcase "^3.0.0" + +yargs-parser@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-7.0.0.tgz#8d0ac42f16ea55debd332caf4c4038b3e3f5dfd9" dependencies: + camelcase "^4.1.0" + +yargs@^6.0.0: + version "6.6.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" + dependencies: + camelcase "^3.0.0" cliui "^3.2.0" decamelize "^1.1.1" get-caller-file "^1.0.1" - lodash.assign "^4.0.3" os-locale "^1.4.0" read-pkg-up "^1.0.1" require-directory "^2.1.1" require-main-filename "^1.0.1" set-blocking "^2.0.0" - string-width "^1.0.1" + string-width "^1.0.2" which-module "^1.0.0" - window-size "^0.2.0" y18n "^3.2.1" - yargs-parser "^2.4.1" + yargs-parser "^4.2.0" -yargs@^6.0.0: - version "6.6.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" +yargs@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" dependencies: camelcase "^3.0.0" cliui "^3.2.0" @@ -5673,7 +6151,25 @@ yargs@^6.0.0: string-width "^1.0.2" which-module "^1.0.0" y18n "^3.2.1" - yargs-parser "^4.2.0" + yargs-parser "^5.0.0" + +yargs@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-8.0.2.tgz#6299a9055b1cefc969ff7e79c1d918dceb22c360" + dependencies: + camelcase "^4.1.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^2.0.0" + read-pkg-up "^2.0.0" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^2.0.0" + which-module "^2.0.0" + y18n "^3.2.1" + yargs-parser "^7.0.0" yargs@~3.10.0: version "3.10.0" @@ -5693,5 +6189,5 @@ yn@^2.0.0: resolved "https://registry.yarnpkg.com/yn/-/yn-2.0.0.tgz#e5adabc8acf408f6385fc76495684c88e6af689a" zone.js@^0.8.14: - version "0.8.14" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.14.tgz#0c4db24b178232274ccb43f78c99db7f3642b6cf" + version "0.8.17" + resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.8.17.tgz#4c5e5185a857da8da793daf3919371c5a36b2a0b"